fix(backend): AC-21.6 — matrix tactic_id returns TA-format (TA0007 not slug)

- mitre.py: add _SLUG_TO_TA_ID reverse map; _build_matrix() now emits tactic_id
  as TA-id (e.g. "TA0007") so frontend can send it back verbatim in PATCH tactic_ids
- test_mitre.py: update all matrix assertions to use TA-ids; add
  test_get_matrix_tactic_id_is_ta_format regression guard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Knacky
2026-05-27 21:30:48 +02:00
parent 5aa839d105
commit a824df06b2
2 changed files with 27 additions and 13 deletions

View File

@@ -305,14 +305,14 @@ def test_get_matrix_returns_ordered_tactics(bundle_file: pathlib.Path) -> None:
mitre_svc.load_bundle(bundle_file)
matrix = mitre_svc.get_matrix()
tactic_ids = [t["tactic_id"] for t in matrix]
# initial-access must come before execution in canonical order.
assert tactic_ids.index("initial-access") < tactic_ids.index("execution")
# TA0001 (initial-access) must come before TA0002 (execution) in canonical order.
assert tactic_ids.index("TA0001") < tactic_ids.index("TA0002")
def test_get_matrix_subtechniques_nested(bundle_file: pathlib.Path) -> None:
mitre_svc.load_bundle(bundle_file)
matrix = mitre_svc.get_matrix()
exec_tactic = next(t for t in matrix if t["tactic_id"] == "execution")
exec_tactic = next(t for t in matrix if t["tactic_id"] == "TA0002")
t1059 = next((t for t in exec_tactic["techniques"] if t["id"] == "T1059"), None)
assert t1059 is not None
sub_ids = [s["id"] for s in t1059["subtechniques"]]
@@ -323,7 +323,7 @@ def test_get_matrix_subtechniques_nested(bundle_file: pathlib.Path) -> None:
def test_get_matrix_subtechniques_sorted_by_name(bundle_file: pathlib.Path) -> None:
mitre_svc.load_bundle(bundle_file)
matrix = mitre_svc.get_matrix()
exec_tactic = next(t for t in matrix if t["tactic_id"] == "execution")
exec_tactic = next(t for t in matrix if t["tactic_id"] == "TA0002")
t1059 = next(t for t in exec_tactic["techniques"] if t["id"] == "T1059")
names = [s["name"] for s in t1059["subtechniques"]]
assert names == sorted(names)
@@ -332,7 +332,7 @@ def test_get_matrix_subtechniques_sorted_by_name(bundle_file: pathlib.Path) -> N
def test_get_matrix_techniques_sorted_by_name(bundle_file: pathlib.Path) -> None:
mitre_svc.load_bundle(bundle_file)
matrix = mitre_svc.get_matrix()
ia_tactic = next(t for t in matrix if t["tactic_id"] == "initial-access")
ia_tactic = next(t for t in matrix if t["tactic_id"] == "TA0001")
names = [t["name"] for t in ia_tactic["techniques"]]
assert names == sorted(names)
@@ -340,7 +340,7 @@ def test_get_matrix_techniques_sorted_by_name(bundle_file: pathlib.Path) -> None
def test_get_matrix_technique_no_subtechniques(bundle_file: pathlib.Path) -> None:
mitre_svc.load_bundle(bundle_file)
matrix = mitre_svc.get_matrix()
ia_tactic = next(t for t in matrix if t["tactic_id"] == "initial-access")
ia_tactic = next(t for t in matrix if t["tactic_id"] == "TA0001")
phishing = next((t for t in ia_tactic["techniques"] if t["id"] == "T1566"), None)
assert phishing is not None
assert phishing["subtechniques"] == []
@@ -355,8 +355,8 @@ def test_matrix_endpoint_ok(
data = resp.get_json()
assert isinstance(data, list)
tactic_ids = [t["tactic_id"] for t in data]
assert "initial-access" in tactic_ids
assert "execution" in tactic_ids
assert "TA0001" in tactic_ids # initial-access
assert "TA0002" in tactic_ids # execution
def test_matrix_endpoint_503_when_not_loaded(
@@ -389,6 +389,15 @@ def test_get_matrix_command_and_control_display_name(bundle_file: pathlib.Path)
"""MITRE official name uses lowercase 'and' — not title-cased."""
mitre_svc.load_bundle(bundle_file)
matrix = mitre_svc.get_matrix()
c2 = next((t for t in matrix if t["tactic_id"] == "command-and-control"), None)
c2 = next((t for t in matrix if t["tactic_id"] == "TA0011"), None)
assert c2 is not None
assert c2["tactic_name"] == "Command and Control"
def test_get_matrix_tactic_id_is_ta_format(bundle_file: pathlib.Path) -> None:
"""Matrix tactic_id must use TA-format so frontend can send it back in PATCH tactic_ids."""
mitre_svc.load_bundle(bundle_file)
matrix = mitre_svc.get_matrix()
for entry in matrix:
tid = entry["tactic_id"]
assert tid.startswith("TA"), f"tactic_id {tid!r} must be TA-format, not a slug"