from __future__ import annotations import hashlib import json import unittest from pathlib import Path from booking_ingestion.md_fixture import ( build_fixture_parse_result, parse_fixture_markdown, ) ROOT = Path(__file__).resolve().parents[1] FIXTURE_PATH = ( ROOT / "outputs" / "res_comment_20260727" / "RES_COMMENT_TYPE_OF_ROOM.md" ) CONTRACT_PATH = ( ROOT / "database" / "contracts" / "booking-row-parse-result.schema.json" ) class BookingFixtureParserTests(unittest.TestCase): @unittest.skipUnless( FIXTURE_PATH.is_file(), "private booking acceptance fixture is not bundled with ARR2.0", ) def test_real_fixture_preserves_every_source_row(self): document = parse_fixture_markdown( FIXTURE_PATH.read_text(encoding="utf-8") ) self.assertEqual(len(document.rows), 867) self.assertEqual(document.worksheet_count, 6) self.assertEqual(document.distinct_group_code_count, 348) self.assertEqual( sum(row.no_of_rooms for row in document.rows), 867, ) repeated = len(document.rows) - document.distinct_group_code_count self.assertEqual(repeated, 519) coordinates = { (row.worksheet, row.row_no) for row in document.rows } self.assertEqual(len(coordinates), 867) @unittest.skipUnless( FIXTURE_PATH.is_file(), "private booking acceptance fixture is not bundled with ARR2.0", ) def test_group_code_is_not_unique_or_merged(self): document = parse_fixture_markdown( FIXTURE_PATH.read_text(encoding="utf-8") ) rows = [ row for row in document.rows if row.group_code_key == "LT260718KB" ] self.assertEqual(len(rows), 12) self.assertEqual({row.type_of_room_raw for row in rows}, {"DBL"}) self.assertEqual(sum(row.no_of_rooms for row in rows), 12) @unittest.skipUnless( FIXTURE_PATH.is_file(), "private booking acceptance fixture is not bundled with ARR2.0", ) def test_simple_fixture_row_maps_to_one_room_item(self): document = parse_fixture_markdown( FIXTURE_PATH.read_text(encoding="utf-8") ) rule_sha = hashlib.sha256(b"test-rule").hexdigest() result = build_fixture_parse_result( document.rows[0], rule_sha256=rule_sha, ) self.assertEqual(result["status"], "accepted") self.assertEqual(result["no_of_rooms"], 1) self.assertEqual(len(result["room_items"]), 1) self.assertEqual(result["room_items"][0]["quantity"], 1) self.assertEqual(result["errors"], []) def test_contract_is_valid_json_and_declares_required_fields(self): schema = json.loads(CONTRACT_PATH.read_text(encoding="utf-8")) self.assertEqual(schema["$schema"], ( "https://json-schema.org/draft/2020-12/schema" )) required = set(schema["required"]) self.assertIn("source_row", required) self.assertIn("room_items", required) self.assertIn("no_of_rooms", required) def test_duplicate_source_coordinate_is_rejected(self): text = """ # GOURP_CODE、TYPE OF ROOM 与数量 来源文件:`source.xlsx` 房型映射文件:`mapping.xlsx` ## 工作表:SHEET | 第 2 行 | G1 | DBL | 1 | | 第 2 行 | G2 | TWN | 1 | """ with self.assertRaisesRegex(ValueError, "duplicate"): parse_fixture_markdown(text) if __name__ == "__main__": unittest.main()