Fix production ARR reads and add audited source field review
This commit is contained in:
1 parent
2417b1a41c
commit
fe8735aac2
26 files changed
+2417
-157
No files matched your search
+121
-10
@@ -93,12 +93,13 @@ class SimulatedOHIP:
|
||||
i = int(request["id"][3:])
|
||||
amount = float(self.cases[i]["record"]["EFFECTIVE_RATE_AMOUNT"])
|
||||
result = {"detail": {"totalRateAmount": amount, "rateSuppressed": False, "revenue": {"currencyCode": "THB"}}}
|
||||
elif url.path == "/api/v1/profiles/searches":
|
||||
operation = "searchProfiles"
|
||||
identity = request["profileIds"][0]
|
||||
elif url.path == "/api/v1/profiles":
|
||||
operation = "getProfiles"
|
||||
identity = query["profileIds"][0]
|
||||
i = int(identity[5:])
|
||||
name = self.cases[i]["record"]["FULL_NAME"]
|
||||
result = {"profileSummaries": {"hasMore": False, "profileInfo": [{"profileIdList": [{"type": "Profile", "id": identity}],
|
||||
result = {"profileSummaries": {"hasMore": False, "limit": 1, "offset": 1, "totalResults": 1,
|
||||
"totalPages": 1, "profileInfo": [{"profileIdList": [{"type": "Profile", "id": identity}],
|
||||
"profile": {"formerName": {"nameType": "Primary", "name": name, "givenName": "", "fullName": name}}}]}}
|
||||
elif url.path.startswith("/api/v1/profiles/"):
|
||||
operation = "getProfile"
|
||||
@@ -199,12 +200,19 @@ class ARRDataTests(unittest.TestCase):
|
||||
self.assertFalse(list(self.root.rglob("*.xml")))
|
||||
for call in self.service.calls:
|
||||
if call["operation"] == base.SEARCH:
|
||||
self.assertEqual(set(call["body"]), {"arrivalStartDate", "arrivalEndDate", "limit", "offset"})
|
||||
self.assertEqual(call["body"]["arrivalStartDate"], DAY)
|
||||
self.assertEqual(call["body"]["arrivalEndDate"], DAY)
|
||||
if call["operation"] == base.DETAIL:
|
||||
self.assertNotIn("Traces", call["query"]["fetchInstructions"])
|
||||
if call["operation"] == "searchRateInfo":
|
||||
self.assertEqual(call["body"], {"id": call["body"]["id"], "type": "Reservation", "detailDate": DAY, "summaryInfo": False})
|
||||
if call["operation"] == "getProfiles":
|
||||
self.assertEqual(call["method"], "GET")
|
||||
self.assertIsNone(call["body"])
|
||||
self.assertEqual(call["query"], {"profileIds": call["query"]["profileIds"], "summaryInfo": ["true"],
|
||||
"limit": ["1"], "offset": ["0"]})
|
||||
self.assertEqual(len(call["query"]["profileIds"]), 1)
|
||||
self.assertEqual([c["body"]["offset"] for c in self.service.calls if c["operation"] == base.SEARCH], [0, 2, 4, 0, 2, 4])
|
||||
|
||||
def test_completed_replay_does_not_create_transport_or_requery(self):
|
||||
@@ -248,11 +256,11 @@ class ARRDataTests(unittest.TestCase):
|
||||
self.source().fetch(DAY, REQUEST)
|
||||
|
||||
def test_failed_related_read_retries_and_next_fetch_uses_new_attempt(self):
|
||||
self.service.status = lambda op, _: 503 if op == "searchProfiles" else 200
|
||||
self.service.status = lambda op, _: 503 if op == "getProfiles" else 200
|
||||
first, payload = self.fetch()
|
||||
self.assertEqual(first["status"], "failed")
|
||||
self.assertEqual(payload["records"][0]["fields"]["FULL_NAME"]["state"], "failed")
|
||||
self.assertEqual(sum(c["operation"] == "searchProfiles" for c in self.service.calls), 3)
|
||||
self.assertEqual(sum(c["operation"] == "getProfiles" for c in self.service.calls), 3)
|
||||
original = Path(first["data_path"]).read_bytes()
|
||||
self.service.status = lambda *_: 200
|
||||
second, _ = self.fetch()
|
||||
@@ -261,10 +269,10 @@ class ARRDataTests(unittest.TestCase):
|
||||
self.assertEqual(Path(first["data_path"]).read_bytes(), original)
|
||||
|
||||
def test_permission_denied_is_not_retried_or_made_blank(self):
|
||||
self.service.status = lambda op, _: 403 if op == "searchProfiles" else 200
|
||||
self.service.status = lambda op, _: 403 if op == "getProfiles" else 200
|
||||
result, field = self.field("FULL_NAME")
|
||||
self.assertEqual((result["status"], field["state"], field["reason"]), ("failed", "failed", "http_permission_denied"))
|
||||
self.assertEqual(sum(c["operation"] == "searchProfiles" for c in self.service.calls), 1)
|
||||
self.assertEqual(sum(c["operation"] == "getProfiles" for c in self.service.calls), 1)
|
||||
|
||||
def test_suppressed_price_is_not_zero_or_base_price(self):
|
||||
def edit(op, doc, _):
|
||||
@@ -338,7 +346,7 @@ class ARRDataTests(unittest.TestCase):
|
||||
self.service.rows[1]["reservationGuests"] = copy.deepcopy(self.service.rows[0]["reservationGuests"])
|
||||
summary, payload = self.fetch()
|
||||
self.assertEqual(summary["status"], "collected")
|
||||
self.assertEqual(sum(c["operation"] == "searchProfiles" for c in self.service.calls), 1)
|
||||
self.assertEqual(sum(c["operation"] == "getProfiles" for c in self.service.calls), 1)
|
||||
for row in payload["records"]:
|
||||
self.assertTrue(row["sources"])
|
||||
self.assertEqual(row["fields"]["FULL_NAME"]["value"], "模拟客人001")
|
||||
@@ -348,7 +356,36 @@ class ARRDataTests(unittest.TestCase):
|
||||
guests.append(copy.deepcopy(guests[0]))
|
||||
_, field = self.field("FULL_NAME")
|
||||
self.assertEqual(field["reason"], "ambiguous_primary_guest")
|
||||
self.assertFalse(any(c["operation"] == "searchProfiles" for c in self.service.calls))
|
||||
self.assertFalse(any(c["operation"] == "getProfiles" for c in self.service.calls))
|
||||
|
||||
def test_get_profile_summary_rejects_wrong_context_identity_and_coverage(self):
|
||||
modes = {"operation": "operation_mismatch", "hotel": "hotel_mismatch",
|
||||
"request_id": "missing_oracle_request_id", "identity": "profile_identity_mismatch",
|
||||
"truncated": "profile_unexpected_more_results", "total": "profile_inconsistent_pagination",
|
||||
"multiple": "profile_ambiguous_summary_count", "name": "profile_name_components_mismatch"}
|
||||
for mode, reason in modes.items():
|
||||
self.root = Path(self.temp.name) / ("summary-" + mode)
|
||||
self.service = SimulatedOHIP()
|
||||
def edit(op, doc, _, mode=mode):
|
||||
if op != "getProfiles": return doc
|
||||
summary = doc["data"]["profileSummaries"]
|
||||
row = summary["profileInfo"][0]
|
||||
if mode == "operation": doc["operation_id"] = "searchProfiles"
|
||||
elif mode == "hotel": doc["hotel_id"] = "OTHER"
|
||||
elif mode == "request_id": doc.pop("oracle_request_id")
|
||||
elif mode == "identity": row["profileIdList"][0]["id"] = "other-guest"
|
||||
elif mode == "truncated": summary["hasMore"] = True
|
||||
elif mode == "total": summary["totalResults"] = 2
|
||||
elif mode == "multiple": summary["profileInfo"].append(copy.deepcopy(row))
|
||||
else: row["profile"]["formerName"]["name"] = "OTHER"
|
||||
return doc
|
||||
self.service.edit = edit
|
||||
with self.subTest(mode=mode):
|
||||
result, field = self.field("FULL_NAME")
|
||||
self.assertFalse(result["input_complete"])
|
||||
self.assertIsNone(field["value"])
|
||||
self.assertEqual(field["reason"], reason)
|
||||
self.assertEqual(sum(c["operation"] == "getProfiles" for c in self.service.calls), 1)
|
||||
|
||||
def test_missing_typed_block_code_fetches_bound_block(self):
|
||||
block = {"blockIdList": [{"type": "Block", "id": "b1"}]}
|
||||
@@ -365,6 +402,23 @@ class ARRDataTests(unittest.TestCase):
|
||||
self.assertEqual(result["status"], "collected_with_gaps")
|
||||
self.assertEqual(field["state"], "missing")
|
||||
|
||||
def test_native_optional_absence_requires_review_even_with_zero_package_price(self):
|
||||
row = self.service.rows[0]
|
||||
row.pop("reservationProfiles")
|
||||
row.pop("reservationPackages")
|
||||
row["roomStay"].pop("reservationBlock")
|
||||
row["roomStay"]["roomRates"][0].pop("reservationBlock")
|
||||
def edit(op, doc, _):
|
||||
if op == "searchRateInfo": doc["data"]["detail"]["totalPackageAmount"] = 0
|
||||
return doc
|
||||
self.service.edit = edit
|
||||
result, payload = self.fetch()
|
||||
self.assertEqual(result["status"], "collected_with_gaps")
|
||||
self.assertTrue(result["collection_complete"])
|
||||
for name in ("BLOCK_CODE", "COMPANY_NAME", "PRODUCTS"):
|
||||
self.assertEqual(payload["records"][0]["fields"][name]["state"], "missing")
|
||||
self.assertIsNone(payload["records"][0]["fields"][name]["value"])
|
||||
|
||||
def test_block_supplement_cannot_overwrite_earlier_code_or_hotel(self):
|
||||
for mismatch in ("code", "hotel"):
|
||||
self.root = Path(self.temp.name) / ("block-" + mismatch)
|
||||
@@ -456,6 +510,16 @@ class ARRDataTests(unittest.TestCase):
|
||||
_, field = self.field("DISP_ROOM_NO")
|
||||
self.assertEqual((field["state"], field["value"], field["reason"]), ("missing", None, "calendar_missing_rooms"))
|
||||
|
||||
def test_empty_native_calendar_is_an_unresolved_room_gap(self):
|
||||
self.missing_room()
|
||||
def edit(op, doc, _):
|
||||
if op == "getRoomCalendar": doc["data"]["roomCalendar"] = {}
|
||||
return doc
|
||||
self.service.edit = edit
|
||||
result, field = self.field("DISP_ROOM_NO")
|
||||
self.assertEqual(result["status"], "collected_with_gaps")
|
||||
self.assertEqual((field["state"], field["value"], field["reason"]), ("missing", None, "calendar_missing_rooms"))
|
||||
|
||||
def test_duplicate_search_ids_and_empty_day_fail_without_processing(self):
|
||||
for duplicate in (True, False):
|
||||
self.root = Path(self.temp.name) / str(duplicate)
|
||||
@@ -478,6 +542,53 @@ class ARRDataTests(unittest.TestCase):
|
||||
summary, _ = self.fetch()
|
||||
self.assertEqual(summary["error"], "source_changed_during_collection")
|
||||
|
||||
def test_default_server_order_is_retained_and_order_change_fails_recheck(self):
|
||||
self.service = SimulatedOHIP(2)
|
||||
self.service.rows.reverse()
|
||||
result, payload = self.fetch()
|
||||
self.assertEqual(result["status"], "collected")
|
||||
self.assertEqual([(r["source_sequence"], r["reservation_id"]) for r in payload["records"]],
|
||||
[(1, "res1"), (2, "res0")])
|
||||
self.root = Path(self.temp.name) / "order-change"
|
||||
self.service = SimulatedOHIP(2)
|
||||
count = 0
|
||||
def edit(op, doc, _):
|
||||
nonlocal count
|
||||
if op == base.SEARCH:
|
||||
count += 1
|
||||
if count == 2: doc["data"]["reservations"]["reservationInfo"].reverse()
|
||||
return doc
|
||||
self.service.edit = edit
|
||||
result, _ = self.fetch()
|
||||
self.assertEqual((result["status"], result["error"]), ("failed", "source_changed_during_collection"))
|
||||
|
||||
def test_unsorted_paging_rejects_truncation_and_changing_totals(self):
|
||||
for mode, expected in (("truncated", "incomplete_search_total"),
|
||||
("totals", "pagination_totals_changed"), ("offset", "pagination_mismatch")):
|
||||
self.root = Path(self.temp.name) / ("paging-" + mode)
|
||||
self.service = SimulatedOHIP(2)
|
||||
def edit(op, doc, request, mode=mode):
|
||||
if op != base.SEARCH: return doc
|
||||
page = doc["data"]["reservations"]
|
||||
if mode == "truncated": page["hasMore"] = False
|
||||
elif mode == "totals" and request["offset"] == 1: page["totalResults"] += 1
|
||||
elif mode == "offset": page["offset"] = request["offset"]
|
||||
return doc
|
||||
self.service.edit = edit
|
||||
with self.subTest(mode=mode):
|
||||
result, _ = self.fetch(page_size=1)
|
||||
self.assertEqual((result["status"], result["error"]), ("failed", expected))
|
||||
|
||||
def test_detail_must_match_searched_reservation_identity(self):
|
||||
def edit(op, doc, _):
|
||||
if op == base.DETAIL:
|
||||
doc["data"]["reservations"]["reservation"][0]["reservationIdList"][0]["id"] = "other-reservation"
|
||||
return doc
|
||||
self.service.edit = edit
|
||||
result, payload = self.fetch()
|
||||
self.assertEqual((result["status"], result["error"]), ("failed", "detail_identity_mismatch"))
|
||||
self.assertEqual(payload["records"], [])
|
||||
|
||||
def test_request_budget_is_enforced_before_network(self):
|
||||
result, _ = self.fetch(max_requests=2)
|
||||
self.assertEqual(result["status"], "failed")
|
||||
|
||||
Reference in new issue
Block a user