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
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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user