import importlib.util from pathlib import Path import unittest spec = importlib.util.spec_from_file_location( "debian_acceptance", Path(__file__).resolve().parents[1] / "deploy" / "debian_acceptance.py") acceptance = importlib.util.module_from_spec(spec) spec.loader.exec_module(acceptance) COMMIT = "a" * 40 RELEASE_SHA = "b" * 64 def common(system): return { "schemaVersion": 1, "system": system, "evidenceId": acceptance.SYSTEMS[system]["evidenceId"], "releaseVersion": "0.2.1", "releaseCommit": COMMIT, "releaseRecordSha256": RELEASE_SHA, "archiveSha256": "c" * 64, "environment": "https://sandbox-guestops.futuresens.co.uk", "hostIdentifier": "guestops-sandbox-01", "images": { "api": {"reference": f"guestops-api:{COMMIT}", "id": "sha256:" + "d" * 64}, "worker": {"reference": f"guestops-worker:{COMMIT}", "id": "sha256:" + "e" * 64}, }, "operator": "Deployment operator", "reviewedBy": "Independent reviewer", "startedAt": "2026-09-30T09:00:00Z", "endedAt": "2026-09-30T10:00:00Z", "reviewedAt": "2026-09-30T11:00:00Z", "unresolvedCriticalFindings": 0, "scenarios": [ {"id": scenario, "status": "pass", "evidence": [f"restricted-{index}"]} for index, scenario in enumerate(sorted(acceptance.SYSTEMS[system]["scenarios"]), 1) ], } def valid_records(): host = common("guestops-debian-host") host["hostFacts"] = { "debianMajor": 12, "cpuCores": 4, "memoryBytes": 8_140_382_208, "freeDiskBytes": 14_275_686_400, "publicTcpPorts": [80, 443], } host["featureControls"] = { "googleSending": "disabled", "faqLiveMode": "disabled", "pmsWrites": "disabled", "paymentCreation": "disabled", } persistence = common("guestops-persistence") persistence["drillCommand"] = "python3 deploy/ops.py persistence-drill --confirm-restart" return host, persistence class DebianAcceptanceTests(unittest.TestCase): def test_complete_matching_records_pass(self): acceptance.validate_pair(*valid_records(), COMMIT, RELEASE_SHA) def test_expected_release_identity_is_required(self): host, persistence = valid_records() host["releaseCommit"] = "f" * 40 with self.assertRaisesRegex(ValueError, "approved candidate"): acceptance.validate_pair(host, persistence, COMMIT, RELEASE_SHA) host, persistence = valid_records() persistence["releaseRecordSha256"] = "f" * 64 with self.assertRaisesRegex(ValueError, "retained release record"): acceptance.validate_pair(host, persistence, COMMIT, RELEASE_SHA) def test_exact_images_and_cross_record_identity_are_required(self): host, persistence = valid_records() host["images"]["api"]["reference"] = "guestops-api:latest" with self.assertRaisesRegex(ValueError, "full approved commit"): acceptance.validate_pair(host, persistence, COMMIT, RELEASE_SHA) host, persistence = valid_records() persistence["archiveSha256"] = "f" * 64 with self.assertRaisesRegex(ValueError, "same archiveSha256"): acceptance.validate_pair(host, persistence, COMMIT, RELEASE_SHA) def test_host_capacity_ports_and_disabled_controls_are_required(self): host, persistence = valid_records() host["hostFacts"]["publicTcpPorts"] = [80, 443, 8080] with self.assertRaisesRegex(ValueError, "Only TCP ports"): acceptance.validate_pair(host, persistence, COMMIT, RELEASE_SHA) host, persistence = valid_records() host["featureControls"]["googleSending"] = "enabled" with self.assertRaisesRegex(ValueError, "must remain disabled"): acceptance.validate_pair(host, persistence, COMMIT, RELEASE_SHA) def test_independent_review_scenarios_and_findings_are_required(self): host, persistence = valid_records() host["reviewedBy"] = host["operator"] with self.assertRaisesRegex(ValueError, "different people"): acceptance.validate_pair(host, persistence, COMMIT, RELEASE_SHA) host, persistence = valid_records() persistence["scenarios"][0]["status"] = "not-run" with self.assertRaisesRegex(ValueError, "has not passed"): acceptance.validate_pair(host, persistence, COMMIT, RELEASE_SHA) host, persistence = valid_records() host["unresolvedCriticalFindings"] = 1 with self.assertRaisesRegex(ValueError, "critical findings"): acceptance.validate_pair(host, persistence, COMMIT, RELEASE_SHA) if __name__ == "__main__": unittest.main()