"""Tests for TechniqueEntity — pure domain logic, no DB.""" import uuid import sys import os from datetime import datetime from unittest.mock import MagicMock 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) import pytest from app.domain.entities.technique import TechniqueEntity from app.domain.enums import TechniqueStatus # ── Helpers ────────────────────────────────────────────────────────── def _entity(**overrides) -> TechniqueEntity: defaults = dict( id=uuid.uuid4(), mitre_id="T1059", name="Command and Scripting Interpreter", tactic="execution", status_global=TechniqueStatus.not_evaluated, ) defaults.update(overrides) return TechniqueEntity(**defaults) def _fake_orm(**overrides) -> MagicMock: m = MagicMock() m.id = uuid.uuid4() m.mitre_id = "T1059" m.name = "Command and Scripting Interpreter" m.tactic = "execution" m.description = None m.platforms = ["windows", "linux"] m.is_subtechnique = False m.parent_mitre_id = None m.status_global = "not_evaluated" m.review_required = False m.last_review_date = None for k, v in overrides.items(): setattr(m, k, v) return m # ── 1. Factory: create() ──────────────────────────────────────────── class TestCreate: def test_valid_technique(self): e = TechniqueEntity.create( mitre_id="T1059", name="Command and Scripting Interpreter", tactic="execution", ) assert e.mitre_id == "T1059" assert e.name == "Command and Scripting Interpreter" assert e.status_global == TechniqueStatus.not_evaluated assert not e.is_subtechnique def test_valid_subtechnique(self): e = TechniqueEntity.create( mitre_id="T1059.001", name="PowerShell", tactic="execution", ) assert e.is_subtechnique assert e.parent_mitre_id == "T1059" def test_invalid_mitre_id_raises(self): with pytest.raises(ValueError, match="Invalid MITRE"): TechniqueEntity.create(mitre_id="INVALID", name="Bad", tactic="x") def test_platforms_default_to_empty(self): e = TechniqueEntity.create(mitre_id="T1059", name="Test") assert e.platforms == [] # ── 2. from_orm / apply_to ────────────────────────────────────────── class TestOrmRoundTrip: def test_from_orm_basic(self): orm = _fake_orm() e = TechniqueEntity.from_orm(orm) assert e.mitre_id == "T1059" assert e.status_global == TechniqueStatus.not_evaluated def test_from_orm_coerces_string_status(self): orm = _fake_orm(status_global="validated") e = TechniqueEntity.from_orm(orm) assert e.status_global == TechniqueStatus.validated def test_apply_to_updates_model(self): orm = _fake_orm() e = TechniqueEntity.from_orm(orm) e.status_global = TechniqueStatus.validated e.review_required = True e.apply_to(orm) assert orm.status_global == TechniqueStatus.validated assert orm.review_required is True # ── 3. recalculate_status ─────────────────────────────────────────── class TestRecalculateStatus: def test_no_tests_gives_not_evaluated(self): e = _entity() result = e.recalculate_status([]) assert result == TechniqueStatus.not_evaluated assert e.status_global == TechniqueStatus.not_evaluated def test_all_validated_all_detected_gives_validated(self): e = _entity() tests = [("validated", "detected"), ("validated", "detected")] assert e.recalculate_status(tests) == TechniqueStatus.validated def test_all_validated_some_partially_gives_partial(self): e = _entity() tests = [("validated", "detected"), ("validated", "partially_detected")] assert e.recalculate_status(tests) == TechniqueStatus.partial def test_all_validated_none_detected_gives_not_covered(self): e = _entity() tests = [("validated", "not_detected"), ("validated", "not_detected")] assert e.recalculate_status(tests) == TechniqueStatus.not_covered def test_all_validated_no_results_gives_not_covered(self): e = _entity() tests = [("validated", None)] assert e.recalculate_status(tests) == TechniqueStatus.not_covered def test_mixed_validated_and_in_progress_gives_partial(self): e = _entity() tests = [("validated", "detected"), ("draft", None)] assert e.recalculate_status(tests) == TechniqueStatus.partial def test_all_in_progress_gives_in_progress(self): e = _entity() tests = [("draft", None), ("red_executing", None)] assert e.recalculate_status(tests) == TechniqueStatus.in_progress # ── 4. mark_reviewed / flag_for_review ────────────────────────────── class TestReviewCycle: def test_mark_reviewed_clears_flag(self): e = _entity(review_required=True) e.mark_reviewed() assert e.review_required is False assert e.last_review_date is not None def test_flag_for_review(self): e = _entity(review_required=False) e.flag_for_review() assert e.review_required is True