feat(tests): reopen rejected test keeps all content + rejection notes
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled

Backend (reopen_test):
- Preserve red/blue validation NOTES — teams see exactly what to fix
  without losing the rejection context. Previously both notes were cleared.
- Preserve all content fields: procedure_text, tool_used, red_summary,
  attack_success, blue_summary, detection_result (already the case).
- Preserve evidences (separate table, unaffected — already the case).
- Still clear: validation statuses + who/when validated (fresh re-validation
  required). Phase timing reset so the new execution starts clean.

Frontend:
- Button label: 'Reopen Test' → 'Continue Test' (more accurate intent)
- Dialog title: 'Reopen Test' → 'Continue Test'
- Dialog message: replaces alarming 'workflow will be restarted / clear all'
  with accurate description of what is preserved vs reset
- Toast: explains what to do next
This commit is contained in:
kitos
2026-06-03 11:31:37 +02:00
parent 74ca8dc53a
commit 2de95a3082
3 changed files with 17 additions and 11 deletions

View File

@@ -655,27 +655,33 @@ def get_retest_chain(db: Session, test_id) -> list[Test]:
def reopen_test(db: Session, test: Test, user: User) -> Test:
"""Move a ``rejected`` test back to ``draft``, clearing validation fields.
"""Move a ``rejected`` test back to ``draft`` for continued work.
This allows the teams to redo the test cycle.
Preserves all content (procedure, summaries, evidences) and — crucially —
the rejection NOTES so teams know what to fix without losing context.
Clears validation decisions (status, who validated, when) so leads must
re-validate the updated submission. Phase timing is reset so the timer
starts fresh for the new execution attempt.
"""
test = transition_state(
db, test, TestState.draft, user,
action_name="reopen_test",
)
# Clear dual-validation fields
# Clear validation DECISIONS — leads must re-validate the new attempt.
# Rejection NOTES are intentionally kept so teams see what needs fixing.
test.red_validation_status = None
test.red_validated_by = None
test.red_validated_at = None
test.red_validation_notes = None
# test.red_validation_notes → KEEP (rejection reason / clarification needed)
test.blue_validation_status = None
test.blue_validated_by = None
test.blue_validated_at = None
test.blue_validation_notes = None
# test.blue_validation_notes → KEEP (rejection reason / clarification needed)
# Clear phase timing fields
# Reset phase timing so the new execution starts fresh
test.red_started_at = None
test.blue_started_at = None
test.blue_work_started_at = None

View File

@@ -225,7 +225,7 @@ export default function TestDetailHeader({
className="flex items-center gap-1.5 rounded-lg border border-cyan-500/30 bg-cyan-900/20 px-4 py-2 text-sm font-medium text-cyan-400 hover:bg-cyan-900/40 transition-colors disabled:opacity-50"
>
{isTransitioning ? <Loader2 className="h-4 w-4 animate-spin" /> : <RotateCcw className="h-4 w-4" />}
Reopen Test
Continue Test
</button>,
);
}

View File

@@ -230,7 +230,7 @@ export default function TestDetailPage() {
onSuccess: () => {
invalidateAll();
setConfirmReopen(false);
showToast("Test reopened", "success");
showToast("Test returned to Draft — address the feedback and resubmit", "success");
},
onError: (err: unknown) => {
setConfirmReopen(false);
@@ -587,9 +587,9 @@ export default function TestDetailPage() {
{/* Confirm Reopen Dialog */}
<ConfirmDialog
open={confirmReopen}
title="Reopen Test"
message="This will move the test back to Draft state and clear all validation decisions. The Red/Blue workflow will need to be restarted. Are you sure?"
confirmLabel="Reopen"
title="Continue Test"
message="The test will return to Draft so you can address the rejection feedback. All content is preserved — procedure, summaries, evidences and rejection notes. Only the validation decisions are cleared so leads can re-validate the updated submission."
confirmLabel="Continue"
variant="warning"
isLoading={reopenMutation.isPending}
onConfirm={() => reopenMutation.mutate()}