GuestOps/tests/test_incident_exercise.py
wolf-demon 4dd33c90e1
Some checks are pending
Build and verify web migration / verify (push) Waiting to run
milestone 19 &20
2026-09-29 20:50:40 +01:00

79 lines
3.1 KiB
Python

import importlib.util
from pathlib import Path
import unittest
spec = importlib.util.spec_from_file_location("incident_exercise", Path(__file__).resolve().parents[1] / "deploy" / "incident_exercise.py")
exercise = importlib.util.module_from_spec(spec)
spec.loader.exec_module(exercise)
def valid_record(gate="B"):
required = exercise.GATE_C_SCENARIOS if gate == "C" else exercise.GATE_B_SCENARIOS
return {
"schemaVersion": 1,
"system": "guestops-incident-exercise",
"targetGate": gate,
"dataClassification": "synthetic-only",
"releaseCommit": "a" * 40,
"releaseRecordSha256": "b" * 64,
"environment": "https://sandbox-guestops.futuresens.co.uk",
"operator": "Exercise operator",
"incidentCommander": "Incident commander",
"reviewedBy": "Independent reviewer",
"startedAt": "2026-09-29T09:00:00Z",
"endedAt": "2026-09-29T10:00:00Z",
"reviewedAt": "2026-09-29T11:00:00Z",
"targetsMinutes": {"detection": 10, "containment": 20, "recovery": 60},
"observedMinutes": {"detection": 5, "containment": 15, "recovery": 45},
"scenarios": [
{"id": scenario, "status": "pass", "evidence": [f"restricted-ticket-{index}"]}
for index, scenario in enumerate(sorted(required), 1)
],
"postExerciseState": {
"externalWrites": "disabled", "faqMode": "off", "unresolvedOperations": 0,
},
}
class IncidentExerciseTests(unittest.TestCase):
def test_gate_b_and_c_complete_records_pass(self):
exercise.validate(valid_record("B"))
exercise.validate(valid_record("C"))
def test_gate_specific_scenarios_are_required(self):
record = valid_record("C")
record["scenarios"].pop()
with self.assertRaisesRegex(ValueError, "exact incident scenario"):
exercise.validate(record)
def test_timings_must_meet_preagreed_targets(self):
record = valid_record()
record["observedMinutes"]["containment"] = 21
with self.assertRaisesRegex(ValueError, "Observed containment"):
exercise.validate(record)
record = valid_record()
record["targetsMinutes"]["recovery"] = True
with self.assertRaisesRegex(ValueError, "recovery target"):
exercise.validate(record)
def test_independent_people_and_safe_evidence_are_required(self):
record = valid_record()
record["reviewedBy"] = record["operator"]
with self.assertRaisesRegex(ValueError, "different people"):
exercise.validate(record)
record = valid_record()
record["scenarios"][0]["evidence"] = ["guest@example.invalid"]
with self.assertRaisesRegex(ValueError, "safe opaque"):
exercise.validate(record)
def test_safe_post_exercise_state_is_required(self):
record = valid_record()
record["postExerciseState"]["externalWrites"] = "enabled"
with self.assertRaisesRegex(ValueError, "writes disabled"):
exercise.validate(record)
if __name__ == "__main__":
unittest.main()