"""Tests for domain value objects: MitreId and ScoringWeights.""" import sys import os 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.value_objects.mitre_id import MitreId from app.domain.value_objects.scoring_weights import ScoringWeights # ── MitreId ────────────────────────────────────────────────────────── class TestMitreId: def test_valid_technique(self): mid = MitreId("T1059") assert mid.value == "T1059" assert str(mid) == "T1059" assert not mid.is_subtechnique assert mid.parent_id is None def test_valid_subtechnique(self): mid = MitreId("T1059.001") assert mid.value == "T1059.001" assert mid.is_subtechnique assert mid.parent_id == "T1059" def test_invalid_empty_string(self): with pytest.raises(ValueError, match="Invalid MITRE"): MitreId("") def test_invalid_no_prefix(self): with pytest.raises(ValueError, match="Invalid MITRE"): MitreId("1059") def test_invalid_wrong_prefix(self): with pytest.raises(ValueError, match="Invalid MITRE"): MitreId("A1059") def test_invalid_too_few_digits(self): with pytest.raises(ValueError, match="Invalid MITRE"): MitreId("T105") def test_invalid_subtechnique_format(self): with pytest.raises(ValueError, match="Invalid MITRE"): MitreId("T1059.01") # needs 3 digits after dot def test_invalid_trailing_garbage(self): with pytest.raises(ValueError, match="Invalid MITRE"): MitreId("T1059.001.002") def test_equality_with_same_mitre_id(self): assert MitreId("T1059") == MitreId("T1059") def test_equality_with_string(self): assert MitreId("T1059") == "T1059" def test_inequality(self): assert MitreId("T1059") != MitreId("T1060") def test_hashable(self): s = {MitreId("T1059"), MitreId("T1059"), MitreId("T1060")} assert len(s) == 2 def test_immutable(self): mid = MitreId("T1059") with pytest.raises(AttributeError): mid.value = "T1060" # ── ScoringWeights ─────────────────────────────────────────────────── class TestScoringWeights: def test_valid_default(self): w = ScoringWeights.default() assert w.tests == 40.0 assert w.detection_rules == 25.0 assert w.d3fend == 15.0 assert w.freshness == 10.0 assert w.platform_diversity == 10.0 def test_valid_custom(self): w = ScoringWeights( tests=50, detection_rules=20, d3fend=10, freshness=10, platform_diversity=10, ) assert w.tests == 50 def test_invalid_sum_not_100(self): with pytest.raises(ValueError, match="sum to 100"): ScoringWeights( tests=50, detection_rules=20, d3fend=10, freshness=10, platform_diversity=5, ) def test_invalid_negative_weight(self): with pytest.raises(ValueError, match="non-negative"): ScoringWeights( tests=-10, detection_rules=40, d3fend=30, freshness=20, platform_diversity=20, ) def test_immutable(self): w = ScoringWeights.default() with pytest.raises(AttributeError): w.tests = 50