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:
kitos
2026-07-08 08:46:09 +02:00
parent 21c6febd22
commit 1c8fa436ab
12 changed files with 333 additions and 3 deletions
+20
View File
@@ -10,6 +10,7 @@ from datetime import datetime
from typing import Any
# Import Session, joinedload from sqlalchemy.orm
from sqlalchemy import or_
from sqlalchemy.orm import Session, joinedload
# Import from app.domain.errors
@@ -63,6 +64,8 @@ def list_tests(
created_by: uuid.UUID | None = None,
# Entry: pending_validation_side
pending_validation_side: str | None = None,
# Entry: reviewer_id — "my reviews" queue for the red_review/blue_review gate stage
reviewer_id: uuid.UUID | None = None,
not_in_any_campaign: bool = False,
offset: int = 0,
# Entry: limit
@@ -101,6 +104,23 @@ def list_tests(
Test.state == TestState.in_review,
Test.blue_validation_status.in_(["pending", None]),
)
# "My reviews" — tests currently assigned to *reviewer_id* at the
# red_review/blue_review lead-review gate (distinct from
# pending_validation_side, which covers the later in_review stage).
if reviewer_id:
if state == TestState.red_review.value:
query = query.filter(Test.red_reviewer_assignee == reviewer_id)
elif state == TestState.blue_review.value:
query = query.filter(Test.blue_reviewer_assignee == reviewer_id)
else:
query = query.filter(
Test.state.in_([TestState.red_review, TestState.blue_review]),
or_(
Test.red_reviewer_assignee == reviewer_id,
Test.blue_reviewer_assignee == reviewer_id,
),
)
if not_in_any_campaign:
linked = db.query(CampaignTest.test_id).distinct().subquery()
query = query.filter(~Test.id.in_(linked))