feat(backend): enforce campaign scheduling, add reviewer queue filter, tactic order, duplicate-test counts
- start_execution now blocks a test from starting before its campaign's start_date, closing a gap where the field was persisted but never enforced - GET /tests gains a reviewer_id "my reviews" filter for the red_review/ blue_review lead-gate stage, distinct from pending_validation_side (which only ever covered the later in_review stage and ignored assignment entirely) - get_coverage_by_tactic now returns all 14 MITRE tactics in canonical kill-chain order instead of an alphabetical, partial list — the regular Dashboard and Executive Dashboard both consume this endpoint and previously disagreed on order/completeness - test-template listing now includes existing_test_count per technique so the catalog can warn before creating a likely-duplicate test
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
"""Tests for canonical MITRE kill-chain ordering in coverage-by-tactic.
|
||||
|
||||
Block 3 fix: TacticCoverageChart.tsx (dashboard) rendered whatever order
|
||||
the backend happened to return (alphabetical, and only tactics with
|
||||
seeded techniques), while ExecutiveDashboardPage.tsx re-sorted client-side
|
||||
into the correct kill-chain order. get_coverage_by_tactic now returns the
|
||||
canonical order — and all 14 tactics, zero-filled — so both consumers get
|
||||
consistent results without needing their own reordering logic.
|
||||
"""
|
||||
|
||||
from app.models.technique import Technique
|
||||
from app.services.metrics_query_service import MITRE_TACTIC_ORDER, get_coverage_by_tactic
|
||||
|
||||
|
||||
def test_coverage_by_tactic_returns_canonical_kill_chain_order(db):
|
||||
# Seed a few tactics out of order to prove the response isn't alphabetical.
|
||||
db.add(Technique(mitre_id="T3001", name="A", tactic="impact"))
|
||||
db.add(Technique(mitre_id="T3002", name="B", tactic="execution"))
|
||||
db.add(Technique(mitre_id="T3003", name="C", tactic="reconnaissance"))
|
||||
db.commit()
|
||||
|
||||
rows = get_coverage_by_tactic(db)
|
||||
tactics = [r.tactic for r in rows]
|
||||
|
||||
canonical_present = [t for t in tactics if t in MITRE_TACTIC_ORDER]
|
||||
assert canonical_present == MITRE_TACTIC_ORDER
|
||||
|
||||
|
||||
def test_coverage_by_tactic_zero_fills_tactics_with_no_techniques(db):
|
||||
db.add(Technique(mitre_id="T3004", name="D", tactic="execution"))
|
||||
db.commit()
|
||||
|
||||
rows = get_coverage_by_tactic(db)
|
||||
by_tactic = {r.tactic: r for r in rows}
|
||||
|
||||
assert "persistence" in by_tactic
|
||||
assert by_tactic["persistence"].total == 0
|
||||
assert by_tactic["execution"].total == 1
|
||||
|
||||
|
||||
def test_coverage_by_tactic_unknown_tactic_sorts_last(db):
|
||||
db.add(Technique(mitre_id="T3005", name="E", tactic=None))
|
||||
db.commit()
|
||||
|
||||
rows = get_coverage_by_tactic(db)
|
||||
tactics = [r.tactic for r in rows]
|
||||
|
||||
assert tactics[-1] == "unknown"
|
||||
Reference in New Issue
Block a user