"""Deterministic and mutation tests for the Rate/Room directory foundation.""" from __future__ import annotations import copy import importlib.util import json import tempfile import unittest from pathlib import Path REPOSITORY_ROOT = Path(__file__).resolve().parents[2] GENERATOR_PATH = REPOSITORY_ROOT / "scripts" / "generate_rate_room_directory.py" DIRECTORY_PATH = ( REPOSITORY_ROOT / "server" / "src" / "main" / "resources" / "reservation-booking" / "catalog" / "rate-room-directory-v20260810.json" ) MANIFEST_PATH = DIRECTORY_PATH.with_name("rate-room-directory-v20260810.manifest.json") GOLDEN_PATH = ( REPOSITORY_ROOT / "server" / "src" / "test" / "resources" / "reservation-booking" / "catalog" / "rate-room-directory-v20260810-golden.json" ) def load_generator(): spec = importlib.util.spec_from_file_location("rate_room_directory_generator", GENERATOR_PATH) if spec is None or spec.loader is None: raise RuntimeError("Rate/Room generator module cannot be loaded") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module class RateRoomDirectoryGeneratorTest(unittest.TestCase): @classmethod def setUpClass(cls) -> None: cls.generator = load_generator() cls.directory_bytes = DIRECTORY_PATH.read_bytes() cls.directory = json.loads(cls.directory_bytes) cls.manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8")) cls.golden = json.loads(GOLDEN_PATH.read_text(encoding="utf-8")) def test_directory_manifest_and_independent_golden_match(self) -> None: self.generator.validate_directory(self.directory) self.generator.validate_manifest(self.directory_bytes, self.manifest) self.assertEqual(self.golden["directory_version"], self.directory["directory_version"]) self.assertEqual(self.golden["source_excel_sha256"], self.directory["source"]["sha256"]) self.assertEqual(self.golden["source_word_sha256"], self.directory["source"]["word"]["sha256"]) self.assertEqual(self.golden["semantic_fingerprints"], self.generator.semantic_fingerprints(self.directory)) self.assertEqual(self.golden["semantic_fingerprints"], self.manifest["semantic_fingerprints"]) self.assertEqual(self.golden["room_types"], [item["code"] for item in self.directory["room_types"]]) self.assertEqual( self.golden["rate_codes"], [ { "code": item["code"], "breakfast_included": item["breakfast_included"], "breakfast_restaurant_code": item["breakfast_restaurant_code"], } for item in self.directory["rate_codes"] ], ) def test_golden_word_corrections_are_bound_to_exact_source_rows(self) -> None: rows = {item["source_row"]: item for item in self.directory["mapping_rows"]} projected = [] for expected in self.golden["word_corrections"]: row = rows[expected["source_row"]] projected.append({key: row[key] for key in expected}) self.assertEqual(self.golden["word_corrections"], projected) def test_room_type_and_rate_code_projections_are_independently_fingerprinted(self) -> None: fingerprints = self.generator.semantic_fingerprints(self.directory) self.assertEqual( "f1bfce959809ac47b65d7a0291f036e43ef9f99b80a774bbd6af9603e9d06eac", fingerprints["room_type_mapping_projection"], ) self.assertEqual( "2810b5c812858da4be694dfa7259b8cdd1c98c1e8133f368e4a4ad9fe5e02885", fingerprints["rate_code_mapping_projection"], ) self.assertEqual(50, len(self.generator.room_type_mapping_projection(self.directory))) self.assertEqual(165, len(self.generator.rate_code_mapping_projection(self.directory))) roomtype_only = copy.deepcopy(self.directory) roomtype_only["mapping_rows"][0]["room_type"] = "RM4" roomtype_only_fingerprints = self.generator.semantic_fingerprints(roomtype_only) self.assertNotEqual( fingerprints["room_type_mapping_projection"], roomtype_only_fingerprints["room_type_mapping_projection"], ) self.assertEqual( fingerprints["rate_code_mapping_projection"], roomtype_only_fingerprints["rate_code_mapping_projection"], ) def test_semantic_duplicate_ignores_source_provenance(self) -> None: mutated = copy.deepcopy(self.directory) duplicate = copy.deepcopy(mutated["mapping_rows"][0]) duplicate["source_row"] = mutated["mapping_rows"][1]["source_row"] mutated["mapping_rows"][1] = duplicate with self.assertRaisesRegex(ValueError, "Duplicate semantic source mappings"): self.generator.validate_directory(mutated) def test_duplicate_source_row_is_rejected(self) -> None: mutated = copy.deepcopy(self.directory) mutated["mapping_rows"][1]["source_row"] = mutated["mapping_rows"][0]["source_row"] with self.assertRaisesRegex(ValueError, "Duplicate source_row"): self.generator.validate_directory(mutated) def test_unknown_room_type_reference_is_rejected(self) -> None: mutated = copy.deepcopy(self.directory) mutated["mapping_rows"][0]["room_type"] = "UNKNOWN" with self.assertRaisesRegex(ValueError, "references unknown Roomtype"): self.generator.validate_directory(mutated) def test_breakfast_cross_reference_is_rejected(self) -> None: mutated = copy.deepcopy(self.directory) mutated["mapping_rows"][0]["breakfast_restaurant_code"] = "LEELA" with self.assertRaisesRegex(ValueError, "breakfast state differs"): self.generator.validate_directory(mutated) def test_bad_alias_target_is_rejected(self) -> None: mutated = copy.deepcopy(self.directory) mutated["legacy_rate_code_aliases"][0]["base_rate_code"] = "UNKNOWN" with self.assertRaisesRegex(ValueError, "points outside"): self.generator.validate_directory(mutated) def test_candidate_scenario_drift_is_rejected(self) -> None: mutated = copy.deepcopy(self.directory) mutated["candidate_scenarios"][0]["candidate_room_types"] = ["RM2"] with self.assertRaisesRegex(ValueError, "Candidate scenarios are stale"): self.generator.validate_directory(mutated) def test_missing_price_default_must_be_canonical_grpa1(self) -> None: mutated = copy.deepcopy(self.directory) mutated["policies"]["rate_code"]["missing_price_default_base_rate_code"] = "GRPA1-900" with self.assertRaisesRegex(ValueError, "canonical base Rate Code"): self.generator.validate_directory(mutated) def test_company_specific_segment_override_is_rejected(self) -> None: mutated = copy.deepcopy(self.directory) mutated["policies"]["segment"]["fixed_by_company"]["Q.B.D"] = "Group" with self.assertRaisesRegex(ValueError, "Unexpected segment policy"): self.generator.validate_directory(mutated) def test_manifest_hash_mismatch_is_rejected(self) -> None: with self.assertRaisesRegex(ValueError, "Manifest directory SHA-256 mismatch"): self.generator.validate_manifest(self.directory_bytes + b"\n", self.manifest) def test_check_mode_never_creates_or_rewrites_files(self) -> None: with tempfile.TemporaryDirectory(prefix="rate-room-generator-test-") as temporary_directory: target = Path(temporary_directory) / "artifact.txt" target.write_text("stable", encoding="utf-8") before = target.stat().st_mtime_ns self.generator.write_or_check(target, "stable", True) self.assertEqual(before, target.stat().st_mtime_ns) missing = Path(temporary_directory) / "missing.txt" with self.assertRaisesRegex(ValueError, "stale or missing"): self.generator.write_or_check(missing, "expected", True) self.assertFalse(missing.exists()) if __name__ == "__main__": unittest.main()