78 lines
2.5 KiB
Python
78 lines
2.5 KiB
Python
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),
|
|
),
|
|
min_arrival_date="2026-07-01",
|
|
)
|
|
|
|
self.assertEqual(payload["version"], "1.2")
|
|
self.assertEqual(payload["min_arrival_date"], "2026-07-01")
|
|
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()
|