fix(coverage): partial coverage when mix of detected+not_detected; add bulk recalculate endpoint
Aegis CI / lint-and-test (push) Waiting to run
Snyk Security Scan / Python vulnerabilities (backend) (push) Waiting to run
Snyk Security Scan / npm vulnerabilities (frontend) (push) Waiting to run
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Waiting to run

This commit is contained in:
kitos
2026-06-18 17:05:15 +02:00
parent a58f9fd357
commit 30ca709c11
3 changed files with 48 additions and 4 deletions
+4 -4
View File
@@ -271,13 +271,13 @@ class TechniqueEntity:
else:
self.status_global = TechniqueStatus.partial
elif any(
# Keyword argument: r
r == TestResult.partially_detected or r == "partially_detected"
r in (TestResult.detected, "detected",
TestResult.partially_detected, "partially_detected")
for r in results
):
# Assign self.status_global = TechniqueStatus.partial
# Mix of detected + not_detected, or any partially_detected → partial
self.status_global = TechniqueStatus.partial
# Fallback: handle remaining cases
# Fallback: handle remaining cases (all not_detected)
else:
# Assign self.status_global = TechniqueStatus.not_covered
self.status_global = TechniqueStatus.not_covered
+32
View File
@@ -824,6 +824,38 @@ def re_enrich_evaluation_round(
return summary
@router.post("/recalculate-coverage")
def recalculate_all_coverage(
db: Session = Depends(get_db),
current_user: User = Depends(require_role("admin")),
):
"""Recompute status_global for every technique based on its current tests.
**Requires** the ``admin`` role.
Run this after logic changes to the coverage scoring rules.
"""
from app.models.technique import Technique
from app.services.status_service import recalculate_technique_status
techniques = db.query(Technique).all()
updated = 0
for tech in techniques:
old_status = tech.status_global
recalculate_technique_status(db, tech)
if tech.status_global != old_status:
updated += 1
db.commit()
logger.info(
"Bulk coverage recalculation: %d/%d techniques updated (by %s)",
updated, len(techniques), current_user.email,
)
return {
"total": len(techniques),
"updated": updated,
"message": f"Coverage recalculated for {len(techniques)} techniques. {updated} statuses changed.",
}
@router.post("/email-test")
def send_test_email(
payload: EmailTestRequest,
+12
View File
@@ -135,6 +135,18 @@ class TestRecalculateStatus:
tests = [("validated", "not_detected"), ("validated", "not_detected")]
assert e.recalculate_status(tests) == TechniqueStatus.not_covered
def test_all_validated_mix_detected_and_not_detected_gives_partial(self):
e = _entity()
tests = [
("validated", "detected"),
("validated", "detected"),
("validated", "detected"),
("validated", "detected"),
("validated", "detected"),
("validated", "not_detected"),
]
assert e.recalculate_status(tests) == TechniqueStatus.partial
def test_all_validated_no_results_gives_not_covered(self):
e = _entity()
tests = [("validated", None)]