125 lines
5.7 KiB
Python
125 lines
5.7 KiB
Python
#!/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()
|