"""Tests for CampaignEntity — pure domain logic, no DB.""" import sys import os import uuid from unittest.mock import MagicMock import pytest backend_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) if backend_dir not in sys.path: sys.path.insert(0, backend_dir) from app.domain.entities.campaign import ( CampaignEntity, CampaignStatus, CampaignType, ) from app.domain.errors import BusinessRuleViolation, InvalidStateTransition # ── Helpers ────────────────────────────────────────────────────────── def _entity(status: str = "draft", test_count: int = 0, **overrides) -> CampaignEntity: defaults = dict( id=uuid.uuid4(), name="Test Campaign", type=CampaignType.custom, status=CampaignStatus(status), description=None, threat_actor_id=None, created_by=None, target_platform=None, tags=[], test_count=test_count, ) defaults.update(overrides) return CampaignEntity(**defaults) def _fake_orm(status: str = "draft", test_count: int = 0, **overrides) -> MagicMock: m = MagicMock() m.id = uuid.uuid4() m.name = "Test Campaign" m.type = "custom" m.status = status m.description = None m.threat_actor_id = None m.created_by = None m.target_platform = None m.tags = [] m.campaign_tests = [MagicMock()] * test_count if test_count else [] for k, v in overrides.items(): setattr(m, k, v) return m # ── 1. Test activation from draft with tests → success ─────────────── def test_activate_from_draft_with_tests_success(): e = _entity("draft", test_count=1) e.activate() assert e.status == CampaignStatus.active def test_activate_from_draft_with_multiple_tests_success(): e = _entity("draft", test_count=3) e.activate() assert e.status == CampaignStatus.active # ── 2. Test activation from draft with 0 tests → BusinessRuleViolation ─── def test_activate_from_draft_with_zero_tests_raises(): e = _entity("draft", test_count=0) with pytest.raises(BusinessRuleViolation, match="at least one test"): e.activate() assert e.status == CampaignStatus.draft # ── 3. Test activation from active → InvalidStateTransition ──────────── def test_activate_from_active_raises(): e = _entity("active", test_count=2) with pytest.raises(InvalidStateTransition) as exc_info: e.activate() assert exc_info.value.current_state == "active" assert exc_info.value.target_state == "active" assert "completed" in exc_info.value.valid_transitions # ── 4. Test complete from active → success ────────────────────────────── def test_complete_from_active_success(): e = _entity("active", test_count=2) e.complete() assert e.status == CampaignStatus.completed # ── 5. Test complete from draft → InvalidStateTransition ──────────────── def test_complete_from_draft_raises(): e = _entity("draft", test_count=1) with pytest.raises(InvalidStateTransition) as exc_info: e.complete() assert exc_info.value.current_state == "draft" assert exc_info.value.target_state == "completed" assert "active" in exc_info.value.valid_transitions # ── 6. Test ensure_modifiable in draft/active → ok ────────────────────── def test_ensure_modifiable_draft_ok(): e = _entity("draft") e.ensure_modifiable() # no raise def test_ensure_modifiable_active_ok(): e = _entity("active", test_count=1) e.ensure_modifiable() # no raise # ── 7. Test ensure_modifiable in completed → BusinessRuleViolation ────── def test_ensure_modifiable_completed_raises(): e = _entity("completed", test_count=1) with pytest.raises(BusinessRuleViolation, match="Cannot modify"): e.ensure_modifiable() def test_ensure_modifiable_archived_raises(): e = _entity("archived", test_count=1) with pytest.raises(BusinessRuleViolation, match="Cannot modify"): e.ensure_modifiable() # ── 8. Test from_orm conversion ────────────────────────────────────────── def test_from_orm_basic(): orm = _fake_orm("draft", test_count=0) e = CampaignEntity.from_orm(orm) assert e.name == "Test Campaign" assert e.type == CampaignType.custom assert e.status == CampaignStatus.draft assert e.id == orm.id assert e.test_count == 0 def test_from_orm_with_tests(): orm = _fake_orm("draft", test_count=3) e = CampaignEntity.from_orm(orm) assert e.test_count == 3 def test_from_orm_coerces_type_and_status(): orm = _fake_orm(status="active", type="apt_emulation", test_count=1) e = CampaignEntity.from_orm(orm) assert e.status == CampaignStatus.active assert e.type == CampaignType.apt_emulation def test_from_orm_handles_none_tags(): orm = _fake_orm("draft", test_count=0) orm.tags = None e = CampaignEntity.from_orm(orm) assert e.tags == []