Files
Wyndham-RSVN-0918/scripts/tests/test_rate_room_effective_mapping.py
鲨鱼辣椒 31849411a8
verify / booking-verify (push) Has been cancelled
建立5178独立项目基线
2026-09-08 15:03:45 +08:00

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()