"""Tests for generating a campaign from a threat actor's uncovered techniques. Covers the Block 1 fix: the frontend sends a `start_date` when generating a campaign from a Threat Actor, but `GenerateFromActorPayload` used to be an empty schema, so the date was silently discarded and the campaign was created with `start_date = NULL`. """ from datetime import datetime from app.models.enums import TechniqueStatus from app.models.technique import Technique from app.models.test_template import TestTemplate from app.models.threat_actor import ThreatActor, ThreatActorTechnique from app.services.campaign_service import generate_campaign_from_threat_actor def _seed_actor_with_gap_technique(db): tech = Technique( mitre_id="T1059.001", name="PowerShell", tactic="execution", platforms=["windows"], status_global=TechniqueStatus.not_evaluated, ) db.add(tech) db.flush() template = TestTemplate( mitre_technique_id=tech.mitre_id, name="PowerShell template", source="custom", severity="high", is_active=True, ) db.add(template) actor = ThreatActor(name="APT-Test", mitre_id="G9999") db.add(actor) db.flush() db.add(ThreatActorTechnique(threat_actor_id=actor.id, technique_id=tech.id)) db.commit() db.refresh(actor) return actor def test_generate_from_actor_without_start_date_leaves_it_null(db, red_lead_user): actor = _seed_actor_with_gap_technique(db) campaign = generate_campaign_from_threat_actor(db, actor.id, red_lead_user) assert campaign.start_date is None def test_generate_from_actor_persists_start_date(db, red_lead_user): actor = _seed_actor_with_gap_technique(db) start_date = datetime(2026, 9, 1) campaign = generate_campaign_from_threat_actor( db, actor.id, red_lead_user, start_date=start_date, ) assert campaign.start_date == start_date def test_generate_from_actor_router_forwards_start_date(api, db, red_lead_user, red_lead_headers): actor = _seed_actor_with_gap_technique(db) resp = api( "post", f"/api/v1/campaigns/from-threat-actor/{actor.id}", red_lead_headers, json={"start_date": "2026-09-01T00:00:00"}, ) assert resp.status_code == 201, resp.text assert resp.json()["start_date"] is not None