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:
|
else:
|
||||||
self.status_global = TechniqueStatus.partial
|
self.status_global = TechniqueStatus.partial
|
||||||
elif any(
|
elif any(
|
||||||
# Keyword argument: r
|
r in (TestResult.detected, "detected",
|
||||||
r == TestResult.partially_detected or r == "partially_detected"
|
TestResult.partially_detected, "partially_detected")
|
||||||
for r in results
|
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
|
self.status_global = TechniqueStatus.partial
|
||||||
# Fallback: handle remaining cases
|
# Fallback: handle remaining cases (all not_detected)
|
||||||
else:
|
else:
|
||||||
# Assign self.status_global = TechniqueStatus.not_covered
|
# Assign self.status_global = TechniqueStatus.not_covered
|
||||||
self.status_global = TechniqueStatus.not_covered
|
self.status_global = TechniqueStatus.not_covered
|
||||||
|
|||||||
@@ -824,6 +824,38 @@ def re_enrich_evaluation_round(
|
|||||||
return summary
|
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")
|
@router.post("/email-test")
|
||||||
def send_test_email(
|
def send_test_email(
|
||||||
payload: EmailTestRequest,
|
payload: EmailTestRequest,
|
||||||
|
|||||||
@@ -135,6 +135,18 @@ class TestRecalculateStatus:
|
|||||||
tests = [("validated", "not_detected"), ("validated", "not_detected")]
|
tests = [("validated", "not_detected"), ("validated", "not_detected")]
|
||||||
assert e.recalculate_status(tests) == TechniqueStatus.not_covered
|
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):
|
def test_all_validated_no_results_gives_not_covered(self):
|
||||||
e = _entity()
|
e = _entity()
|
||||||
tests = [("validated", None)]
|
tests = [("validated", None)]
|
||||||
|
|||||||
Reference in New Issue
Block a user