86 lines
3.7 KiB
Python
86 lines
3.7 KiB
Python
import importlib.util
|
|
from pathlib import Path
|
|
import unittest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def module(name):
|
|
spec = importlib.util.spec_from_file_location(name, ROOT / "deploy" / f"{name}.py")
|
|
result = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(result)
|
|
return result
|
|
|
|
|
|
automation = module("automation_acceptance")
|
|
privacy = module("identity_privacy_acceptance")
|
|
|
|
|
|
def base(system):
|
|
return {
|
|
"schemaVersion": 1, "system": system, "targetGate": "B",
|
|
"releaseCommit": "a" * 40, "releaseRecordSha256": "b" * 64,
|
|
"environment": "https://sandbox-guestops.futuresens.co.uk",
|
|
"operator": "Acceptance operator", "reviewedBy": "Independent reviewer",
|
|
"startedAt": "2026-10-01T09:00:00Z", "endedAt": "2026-10-01T10:00:00Z", "reviewedAt": "2026-10-01T11:00:00Z",
|
|
}
|
|
|
|
|
|
def automation_record():
|
|
record = base("guestops-automation-acceptance")
|
|
record.update({
|
|
"dataClassification": "synthetic-only",
|
|
"faqEvaluation": {"positiveCases": 20, "negativeCases": 20, "falsePositives": 0, "falseNegatives": 0, "reportSha256": "c" * 64},
|
|
"aiEvaluation": {"casesReviewed": 20, "unsafeDraftsApproved": 0, "reportSha256": "d" * 64},
|
|
"staffTrained": 3, "monitoringOwner": "Monitoring owner", "rollbackOwner": "Rollback owner",
|
|
"scenarios": [{"id": item, "status": "pass", "evidence": ["restricted-" + item]} for item in sorted(automation.SCENARIOS)],
|
|
"postAcceptanceState": {"faqMode": "off", "pmsWrites": "disabled", "paymentCreation": "disabled"},
|
|
})
|
|
return record
|
|
|
|
|
|
def privacy_record():
|
|
record = base("guestops-identity-privacy")
|
|
record.update({
|
|
"retention": {"conversationDays": 365, "auditDays": 730, "backupDays": 30, "accountDays": 730,
|
|
"privacyOwner": "Privacy owner", "deletionOwner": "Deletion owner", "legalHoldOwner": "Legal hold owner",
|
|
"deletionProcedure": "restricted-deletion", "legalHoldProcedure": "restricted-legal-hold"},
|
|
"providers": {"google": {"status": "accepted", "evidence": ["restricted-google"]},
|
|
"openai": {"status": "disabled", "evidence": ["restricted-openai-decision"]}},
|
|
"preferencesReviewed": sorted(privacy.PREFERENCES),
|
|
"scenarios": [{"id": item, "status": "pass", "evidence": ["restricted-" + item]} for item in sorted(privacy.SCENARIOS)],
|
|
})
|
|
return record
|
|
|
|
|
|
class GateBAcceptanceTests(unittest.TestCase):
|
|
def test_complete_records_pass(self):
|
|
automation.validate(automation_record())
|
|
privacy.validate(privacy_record())
|
|
|
|
def test_automation_requires_zero_faq_errors_and_training(self):
|
|
record = automation_record();record["faqEvaluation"]["falsePositives"] = 1
|
|
with self.assertRaisesRegex(ValueError, "zero false positives"):
|
|
automation.validate(record)
|
|
record = automation_record();record["staffTrained"] = 0
|
|
with self.assertRaisesRegex(ValueError, "staff member"):
|
|
automation.validate(record)
|
|
|
|
def test_privacy_requires_retention_and_google_decisions(self):
|
|
record = privacy_record();record["retention"]["conversationDays"] = 0
|
|
with self.assertRaisesRegex(ValueError, "conversationDays"):
|
|
privacy.validate(record)
|
|
record = privacy_record();record["providers"]["google"]["status"] = "pending"
|
|
with self.assertRaisesRegex(ValueError, "Google processing"):
|
|
privacy.validate(record)
|
|
|
|
def test_privacy_requires_exact_preference_coverage(self):
|
|
record = privacy_record();record["preferencesReviewed"].pop()
|
|
with self.assertRaisesRegex(ValueError, "exact owner-controlled"):
|
|
privacy.validate(record)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|