feat: prepare ARR for controlled public deployment
This commit is contained in:
147
tests/test_monthly_reports_core.py
Normal file
147
tests/test_monthly_reports_core.py
Normal file
@@ -0,0 +1,147 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from monthly_reports.contracts import (
|
||||
KB_HEADER,
|
||||
ChannelObservation,
|
||||
DailyVersionPin,
|
||||
ErrorCode,
|
||||
MonthlyFact,
|
||||
MonthlyReportError,
|
||||
MonthlySnapshot,
|
||||
STANDARD_SHEETS,
|
||||
)
|
||||
from monthly_reports.core import build_monthly_report
|
||||
|
||||
|
||||
def fact(
|
||||
record_id: int,
|
||||
*,
|
||||
business_date: date = date(2026, 7, 2),
|
||||
daily_version_id: int = 102,
|
||||
channel: str = "QBD",
|
||||
rooms: int = 1,
|
||||
nights: int = 2,
|
||||
real_price: Decimal = Decimal("450.00"),
|
||||
) -> MonthlyFact:
|
||||
return MonthlyFact(
|
||||
daily_record_id=record_id,
|
||||
daily_version_id=daily_version_id,
|
||||
business_date=business_date,
|
||||
channel_key=channel,
|
||||
arrival=business_date,
|
||||
departure=date.fromordinal(business_date.toordinal() + nights),
|
||||
nights=nights,
|
||||
adults=2,
|
||||
children=0,
|
||||
block_code="SYN-BLOCK",
|
||||
no_of_rooms=rooms,
|
||||
company_name="SYN-COMPANY",
|
||||
confirmation_no=f"SYN-{record_id}",
|
||||
disp_room_no=f"SYN-RM-{record_id}",
|
||||
effective_rate_amount=real_price,
|
||||
full_name="SYN-GUEST",
|
||||
res_comment="SYN-COMMENT",
|
||||
trace_text="",
|
||||
products="",
|
||||
rate_code="SYN-RATE",
|
||||
room_category_label="SYN-TYPE",
|
||||
real_price=real_price,
|
||||
total_price=real_price * rooms * nights,
|
||||
kb_amount=Decimal(rooms * 100) if channel == "DY-AI-Easy-KB" else None,
|
||||
)
|
||||
|
||||
|
||||
def snapshot(*facts: MonthlyFact, preferred: tuple[str, ...] = ()) -> MonthlySnapshot:
|
||||
pins = tuple(
|
||||
DailyVersionPin(day, version)
|
||||
for day, version in sorted(
|
||||
{item.business_date: item.daily_version_id for item in facts}.items()
|
||||
)
|
||||
)
|
||||
counts: dict[tuple[date, int, str], int] = {}
|
||||
for item in facts:
|
||||
key = (item.business_date, item.daily_version_id, item.channel_key)
|
||||
counts[key] = counts.get(key, 0) + 1
|
||||
observations = tuple(
|
||||
ChannelObservation(day, version, channel, None, count)
|
||||
for (day, version, channel), count in sorted(counts.items())
|
||||
)
|
||||
return MonthlySnapshot(tuple(facts), pins, observations, preferred)
|
||||
|
||||
|
||||
class MonthlyReportsCoreTests(unittest.TestCase):
|
||||
def test_standard_sheets_are_retained_and_unknown_order_is_stable(self):
|
||||
report = build_monthly_report(
|
||||
2026,
|
||||
7,
|
||||
date(2026, 7, 26),
|
||||
snapshot(
|
||||
fact(2, channel="T- HANATOUR TD CO., L"),
|
||||
fact(1),
|
||||
preferred=("T- HANATOUR TD CO., L",),
|
||||
),
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
tuple(channel.worksheet for channel in report.channels),
|
||||
STANDARD_SHEETS + ("T- HANATOUR TD CO., L",),
|
||||
)
|
||||
self.assertEqual(report.row_count, 2)
|
||||
self.assertEqual(report.channel_manifest[-1], ("T- HANATOUR TD CO., L", 6, 1))
|
||||
self.assertEqual(report.filename, "各渠道情况-2026年07月-更新至7.26.xlsx")
|
||||
|
||||
def test_kb_payload_has_extra_typed_amount(self):
|
||||
report = build_monthly_report(
|
||||
2026,
|
||||
7,
|
||||
date(2026, 7, 26),
|
||||
snapshot(fact(1, channel="DY-AI-Easy-KB", rooms=2)),
|
||||
)
|
||||
|
||||
payload = report.to_workbook_payload()
|
||||
kb = next(item for item in payload["channels"] if item["worksheet"] == "DY-AI-Easy-KB")
|
||||
self.assertEqual(kb["headers"][-1], KB_HEADER)
|
||||
self.assertEqual(kb["rows"][0][KB_HEADER], "200")
|
||||
self.assertEqual(kb["rows"][0]["TOTAL PRICE"], "1800")
|
||||
|
||||
def test_empty_current_day_is_pinned(self):
|
||||
report = build_monthly_report(
|
||||
2026,
|
||||
7,
|
||||
date(2026, 7, 26),
|
||||
MonthlySnapshot(
|
||||
facts=(),
|
||||
daily_versions=(DailyVersionPin(date(2026, 7, 26), 126),),
|
||||
channel_observations=(),
|
||||
),
|
||||
)
|
||||
self.assertEqual(report.row_count, 0)
|
||||
self.assertEqual(report.daily_versions[0].daily_version_id, 126)
|
||||
|
||||
def test_mismatched_metric_or_pin_is_rejected(self):
|
||||
source = fact(1)
|
||||
cases = (
|
||||
MonthlySnapshot(
|
||||
(source,),
|
||||
(DailyVersionPin(source.business_date, 999),),
|
||||
(ChannelObservation(source.business_date, 999, "QBD", 1, 1),),
|
||||
),
|
||||
MonthlySnapshot(
|
||||
(source,),
|
||||
(DailyVersionPin(source.business_date, source.daily_version_id),),
|
||||
(ChannelObservation(source.business_date, source.daily_version_id, "QBD", 1, 2),),
|
||||
),
|
||||
)
|
||||
for current in cases:
|
||||
with self.subTest(current=current):
|
||||
with self.assertRaises(MonthlyReportError) as caught:
|
||||
build_monthly_report(2026, 7, date(2026, 7, 26), current)
|
||||
self.assertEqual(caught.exception.code, ErrorCode.SOURCE_INVALID)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user