from __future__ import annotations import json import unittest from decimal import Decimal from channel_analytics.contracts import ( AggregatedRoomFact, AnalyticsError, build_dashboard, ) class ChannelAnalyticsContractsTests(unittest.TestCase): def test_dashboard_keeps_manifest_order_empty_channels_and_frozen_totals(self): payload = build_dashboard( "2026-07", "2026-07-28T15:54:00+08:00", "2026-07-26", "a" * 64, ("B", "A", "EMPTY"), ( AggregatedRoomFact("A", "RM2", 2, Decimal("100"), 6, 1), AggregatedRoomFact("B", "RM3", 3, Decimal("90"), 4, 2), AggregatedRoomFact("B", "RM2", 1, Decimal("50"), 2, 1), ), ) self.assertEqual(payload["version"], "1.2") self.assertEqual( [channel["worksheet"] for channel in payload["channels"]], ["B", "A", "EMPTY"], ) self.assertEqual(payload["overall"]["totals"], { "rooms_sold": 6, "total_price": 240, "room_nights": 12, "reservation_rows": 4, "room_type_count": 2, "channel_count": 3, }) self.assertEqual(payload["channels"][0]["totals"]["total_price"], 140) self.assertEqual(payload["channels"][2]["room_types"], []) self.assertEqual( [room["room_type"] for room in payload["channels"][0]["room_types"]], ["RM3", "RM2"], ) def test_dashboard_rejects_channels_outside_manifest_and_private_rows_are_absent(self): with self.assertRaises(AnalyticsError): build_dashboard( "2026-07", "2026-07-28T15:54:00+08:00", "2026-07-26", "a" * 64, ("A",), (AggregatedRoomFact("B", "RM2", 1, Decimal("10"), 1, 1),), ) serialized = json.dumps( build_dashboard( "2026-07", "2026-07-28T15:54:00+08:00", "2026-07-26", "a" * 64, ("A",), (AggregatedRoomFact("A", "RM2", 1, Decimal("10"), 1, 1),), ), ensure_ascii=False, ).lower() for forbidden in ("full_name", "confirmation_no", "disp_room_no", "res_comment"): self.assertNotIn(forbidden, serialized) if __name__ == "__main__": unittest.main()