GuestOps/tests/test_pilot_approval.py
wolf-demon a3ef408de6
Some checks failed
Build and verify web migration / verify (push) Has been cancelled
Prepare 0.2.0 Gate B pilot candidate
2026-09-29 21:17:40 +01:00

83 lines
4.3 KiB
Python

import importlib.util
from pathlib import Path
import unittest
spec = importlib.util.spec_from_file_location("pilot_approval", Path(__file__).resolve().parents[1] / "deploy" / "pilot_approval.py")
approval = importlib.util.module_from_spec(spec)
spec.loader.exec_module(approval)
def valid_record(gate="B"):
ids = approval.GATE_C if gate == "C" else approval.GATE_B
return {
"schemaVersion": 2, "targetGate": gate, "decision": "approved",
"releaseCommit": "a" * 40, "releaseRecordSha256": "b" * 64,
"decidedAt": "2026-09-29T12:00:00Z",
"approvals": {
"hotelOwner": {"name": "Hotel owner", "approvedAt": "2026-09-29T11:00:00Z"},
"technicalOwner": {"name": "Technical owner", "approvedAt": "2026-09-29T11:30:00Z"},
},
"capacity": {"reportSha256": "c" * 64, "hostMetricsSha256": "d" * 64, "targetConcurrency": 10, "observedConcurrency": 10, "targetP95Ms": 500, "observedP95Ms": 250, "targetErrorRate": 0.01, "observedErrorRate": 0, "targetCpuHeadroomPercent": 25, "observedCpuHeadroomPercent": 40, "targetMemoryHeadroomPercent": 25, "observedMemoryHeadroomPercent": 35},
"pilot": {"recordSha256": "e" * 64, "businessDaysObserved": 5, "hotelsObserved": 1, "stopConditionsObserved": 0, "unresolvedFindings": 0},
"evidence": [{"id": item, "status": "pass", "references": ["restricted-ticket-" + item]} for item in sorted(ids)],
}
class PilotApprovalTests(unittest.TestCase):
def test_gate_b_and_c_complete_records_pass(self):
approval.validate(valid_record("B"))
approval.validate(valid_record("C"))
def test_pending_or_missing_evidence_fails(self):
record = valid_record();record["decision"] = "pending"
with self.assertRaisesRegex(ValueError, "explicit approved"):
approval.validate(record)
record = valid_record();record["evidence"].pop()
with self.assertRaisesRegex(ValueError, "exact evidence"):
approval.validate(record)
def test_capacity_must_meet_preapproved_targets(self):
record = valid_record();record["capacity"]["observedP95Ms"] = 501
with self.assertRaisesRegex(ValueError, "observedP95Ms"):
approval.validate(record)
record = valid_record();record["capacity"]["observedConcurrency"] = 9
with self.assertRaisesRegex(ValueError, "Observed concurrency"):
approval.validate(record)
record = valid_record();record["capacity"]["targetConcurrency"] = True
with self.assertRaisesRegex(ValueError, "Observed concurrency"):
approval.validate(record)
record = valid_record();record["capacity"]["targetErrorRate"] = 2
with self.assertRaisesRegex(ValueError, "ratio"):
approval.validate(record)
record = valid_record();record["capacity"]["observedCpuHeadroomPercent"] = 24
with self.assertRaisesRegex(ValueError, "cpu headroom"):
approval.validate(record)
def test_completed_five_day_single_hotel_pilot_is_required(self):
record = valid_record();record["pilot"]["businessDaysObserved"] = 4
with self.assertRaisesRegex(ValueError, "five business days"):
approval.validate(record)
record = valid_record();record["pilot"]["stopConditionsObserved"] = 1
with self.assertRaisesRegex(ValueError, "stop condition"):
approval.validate(record)
def test_approvers_must_be_separate_people_without_email_addresses(self):
record = valid_record();record["approvals"]["technicalOwner"]["name"] = "Hotel owner"
with self.assertRaisesRegex(ValueError, "different people"):
approval.validate(record)
record = valid_record();record["approvals"]["hotelOwner"]["name"] = "owner@example.invalid"
with self.assertRaisesRegex(ValueError, "without an email"):
approval.validate(record)
def test_containment_requires_owner_future_expiry_and_trigger(self):
record = valid_record();item = record["evidence"][0];item["status"] = "contained"
with self.assertRaisesRegex(ValueError, "containment details"):
approval.validate(record)
item["containment"] = {"owner": "Release owner", "expiresAt": "2026-10-10T12:00:00Z", "rollbackTrigger": "Rollback if the contained condition occurs."}
approval.validate(record)
if __name__ == "__main__":
unittest.main()