fix(tests): keep free-text round fields in place on reopen, only blank verdicts
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
Only the per-round verdict/timing fields (attack_success, execution start/end for red; detection_result, containment_result, detection/ containment time for blue) get cleared on reopen — the operator must answer those fresh for each round, and the prior round's answer stays visible via round_history + the archived Phase Timeline card. Free-text fields (procedure_text, tool_used, red_summary, blue_summary) are no longer cleared: the operator edits them in place for the new round instead of retyping everything from scratch.
This commit is contained in:
@@ -405,12 +405,14 @@ def reopen_red_review(db: Session, test: Test, user: User, notes: str) -> Test:
|
||||
test.red_review_at = datetime.utcnow()
|
||||
test.red_review_notes = notes.strip()
|
||||
test.red_round_number = (test.red_round_number or 1) + 1
|
||||
# New sheet — the operator fills these in fresh for this round; the
|
||||
# values that were just archived stay visible via round_history.
|
||||
test.procedure_text = None
|
||||
test.tool_used = None
|
||||
# Only the per-round verdict/timing fields get a blank slate — the
|
||||
# operator must explicitly answer "was THIS attempt successful" and
|
||||
# "when did THIS attempt run" rather than silently inheriting round 1's
|
||||
# answer. The old values aren't lost: they're in round_history and
|
||||
# shown on the archived Phase Timeline card. Free-text fields
|
||||
# (procedure, tool, summary) are NOT cleared — the operator edits them
|
||||
# in place for the new round instead of retyping from scratch.
|
||||
test.attack_success = None
|
||||
test.red_summary = None
|
||||
test.execution_start_time = None
|
||||
test.execution_end_time = None
|
||||
db.flush()
|
||||
@@ -640,13 +642,14 @@ def reopen_blue_review(db: Session, test: Test, user: User, notes: str) -> Test:
|
||||
test.blue_review_at = datetime.utcnow()
|
||||
test.blue_review_notes = notes.strip()
|
||||
test.blue_round_number = (test.blue_round_number or 1) + 1
|
||||
# New sheet for this round; the archived values above remain visible
|
||||
# via round_history.
|
||||
# Same split as the red side: verdict/timing fields get a blank slate
|
||||
# for this round (old values live on in round_history + the archived
|
||||
# Phase Timeline card), but blue_summary is free text the operator
|
||||
# edits in place rather than retyping.
|
||||
test.detection_result = None
|
||||
test.containment_result = None
|
||||
test.detection_time = None
|
||||
test.containment_time = None
|
||||
test.blue_summary = None
|
||||
db.flush()
|
||||
|
||||
log_action(
|
||||
|
||||
@@ -740,9 +740,11 @@ class TestReviewDecisions:
|
||||
|
||||
@patch("app.services.test_workflow_service.log_action")
|
||||
def test_reopen_red_review_archives_round_and_resets_fields(self, mock_log):
|
||||
"""Reopening must not erase what Red already submitted — it archives
|
||||
the round (visible via round_history) and only then blanks the live
|
||||
fields for a fresh attempt, bumping the round counter."""
|
||||
"""Reopening must not erase what Red already submitted. The round is
|
||||
archived (visible via round_history) and only the per-round verdict
|
||||
fields (attack_success, execution start/end) are blanked for a fresh
|
||||
answer — free-text fields (procedure, tool, summary) stay in place
|
||||
for the operator to edit, not retype from scratch."""
|
||||
from app.models.test_round_history import TestRoundHistory
|
||||
|
||||
test = _make_test(
|
||||
@@ -766,10 +768,12 @@ class TestReviewDecisions:
|
||||
assert archived[0].red_summary == "Got a shell."
|
||||
assert archived[0].review_notes == "incomplete evidence"
|
||||
|
||||
assert test.procedure_text is None
|
||||
assert test.tool_used is None
|
||||
assert test.procedure_text == "ran mimikatz"
|
||||
assert test.tool_used == "mimikatz"
|
||||
assert test.red_summary == "Got a shell."
|
||||
assert test.attack_success is None
|
||||
assert test.red_summary is None
|
||||
assert test.execution_start_time is None
|
||||
assert test.execution_end_time is None
|
||||
assert test.red_round_number == 2
|
||||
|
||||
@patch("app.services.test_workflow_service.log_action")
|
||||
@@ -797,7 +801,8 @@ class TestReviewDecisions:
|
||||
assert archived[0].review_notes == "missing containment steps"
|
||||
|
||||
assert test.detection_result is None
|
||||
assert test.blue_summary is None
|
||||
assert test.containment_result is None
|
||||
assert test.blue_summary == "Caught it via EDR."
|
||||
assert test.blue_round_number == 2
|
||||
|
||||
@patch("app.services.test_workflow_service.log_action")
|
||||
|
||||
Reference in New Issue
Block a user