#!/usr/bin/env python3 """Regression tests for the final 0812 effective Rate/Room mapping generator.""" from __future__ import annotations import importlib.util import json import sys import tempfile import unittest from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[2] SCRIPT_PATH = REPO_ROOT / "scripts" / "generate_rate_room_effective_mapping.py" CATALOG_ROOT = REPO_ROOT / "server/src/main/resources/reservation-booking/catalog" MAPPING_PATH = CATALOG_ROOT / "rate-room-effective-mapping-v20260812.json" MANIFEST_PATH = CATALOG_ROOT / "rate-room-effective-mapping-v20260812.manifest.json" DIRECTORY_PATH = CATALOG_ROOT / "rate-room-directory-v20260810.json" CORRECTION_PATH = CATALOG_ROOT / "rate-room-knowledge-corrections-v20260812.json" SPEC = importlib.util.spec_from_file_location("generate_rate_room_effective_mapping", SCRIPT_PATH) if SPEC is None or SPEC.loader is None: raise RuntimeError("Cannot load effective Rate/Room mapping generator") generator = importlib.util.module_from_spec(SPEC) sys.modules[SPEC.name] = generator SPEC.loader.exec_module(generator) class RateRoomEffectiveMappingTest(unittest.TestCase): def setUp(self) -> None: self.mapping = json.loads(MAPPING_PATH.read_text(encoding="utf-8")) self.manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8")) self.directory = json.loads(DIRECTORY_PATH.read_text(encoding="utf-8")) self.correction = json.loads(CORRECTION_PATH.read_text(encoding="utf-8")) def test_generated_artifacts_have_frozen_counts_and_hashes(self) -> None: self.assertEqual("rate-room-effective-mapping-v20260812.1", self.mapping["effective_mapping_version"]) self.assertEqual(213, len(self.mapping["mapping_rows"])) self.assertEqual(163, len(self.mapping["scenario_summaries"])) self.assertEqual(27, self.mapping["counts"]["multi_candidate_scenarios"]) self.assertEqual( generator.semantic_fingerprint(self.mapping["mapping_rows"]), self.mapping["mapping_content_sha256"], ) self.assertEqual( generator.semantic_fingerprint(self.mapping["scenario_summaries"]), self.mapping["scenario_content_sha256"], ) self.assertEqual( generator.semantic_fingerprint(generator.rate_projection(self.mapping["mapping_rows"])), self.mapping["rate_projection_sha256"], ) self.assertEqual( generator.sha256_bytes(MAPPING_PATH.read_bytes()), self.manifest["effective_mapping"]["sha256"], ) self.assertEqual( self.mapping["rate_projection_sha256"], self.manifest["semantic_fingerprints"]["rate_projection"], ) def test_rate_projection_is_independent_of_roomtype_candidates(self) -> None: expected = generator.expected_rows(self.directory, self.correction) actual = generator.rate_projection(self.mapping["mapping_rows"]) self.assertEqual(expected, actual) self.assertEqual( generator.semantic_fingerprint(actual), "90d5c73318039de2fd78b5018e5e8f5ad85f87698f7bd89944f84a1fd54fe27a", ) changed = json.loads(json.dumps(self.correction)) for rule in changed["room_type_source_label_rules"]: if rule["normalized_literal"] == "ONE-BEDROOM-SUITE-DBL": rule["candidate_room_type_codes"] = ["SU1", "SU2"] rule["resolution"] = "MULTIPLE_CANDIDATES" self.assertEqual(expected, generator.expected_rows(self.directory, changed)) def test_company_and_explicit_rate_option_contract_is_explicit(self) -> None: contract = self.mapping["input_contract"] self.assertEqual("EXPLICIT_LAYER4_RATE_OPTION", contract["booking_type_source"]) self.assertEqual(4, contract["fit_max_inclusive"]) self.assertEqual(5, contract["group_min_inclusive"]) self.assertEqual({"Q.B.D": "Group&FIT"}, contract["company_type_overrides"]) self.assertEqual( {"LIAN TAI": "LIANTAI ONLINE/DY"}, contract["fit_directory_company_overrides"], ) qbd_types = {row["type"] for row in self.mapping["mapping_rows"] if row["company"] == "Q.B.D"} non_qbd_group_fit = [ row for row in self.mapping["mapping_rows"] if row["company"] != "Q.B.D" and row["type"] == "Group&FIT" ] self.assertEqual({"Group&FIT"}, qbd_types) self.assertEqual([], non_qbd_group_fit) def test_breakfast_formula_projection_matches_rate_code_metadata(self) -> None: rate_codes = {item["code"]: item for item in self.directory["rate_codes"]} for row in self.mapping["mapping_rows"]: definition = rate_codes[row["base_rate_code"]] expected_breakfast = definition["breakfast_restaurant_code"] or "RO" self.assertEqual(expected_breakfast, row["breakfast"]) self.assertEqual(definition["breakfast_included"], row["breakfast_included"]) self.assertEqual(row["breakfast"] != "RO", row["breakfast_included"]) def test_semantic_mutation_is_detected(self) -> None: tampered = json.loads(json.dumps(self.mapping)) tampered["mapping_rows"][0]["room_type"] = "RM3" self.assertNotEqual( generator.semantic_fingerprint(tampered["mapping_rows"]), tampered["mapping_content_sha256"], ) def test_check_mode_rejects_stale_artifact(self) -> None: with tempfile.TemporaryDirectory() as directory: path = Path(directory) / "stale.json" path.write_text("{}\n", encoding="utf-8") with self.assertRaises(ValueError): generator.write_or_check(path, "{\"expected\":true}\n", True) if __name__ == "__main__": unittest.main()