Files
wyndham-ARR/tests/test_monthly_reports_xlsx.py
2026-07-31 15:11:42 +08:00

177 lines
5.8 KiB
Python

from __future__ import annotations
import json
import os
import shutil
import subprocess
import tempfile
import unittest
from datetime import date
from decimal import Decimal
from pathlib import Path
from openpyxl import load_workbook
from monthly_reports.contracts import (
ChannelObservation,
DailyVersionPin,
MonthlyFact,
MonthlySnapshot,
)
from monthly_reports.core import build_monthly_report
PROJECT_ROOT = Path(__file__).resolve().parents[1]
BUILDER = PROJECT_ROOT / "monthly_reports" / "xlsx" / "build_workbook.mjs"
def artifact_tool_module() -> str:
return os.environ.get("MONTHLY_REPORT_ARTIFACT_TOOL_MODULE", "").strip()
def node_binary() -> str:
return os.environ.get("MONTHLY_REPORT_NODE_BINARY", "").strip() or shutil.which("node") or ""
def report_payload() -> dict:
source = MonthlyFact(
daily_record_id=1,
daily_version_id=100,
business_date=date(2026, 7, 8),
channel_key="QBD",
arrival=date(2026, 7, 8),
departure=date(2026, 7, 10),
nights=2,
adults=2,
children=0,
block_code="=SYN-FORMULA-LIKE",
no_of_rooms=1,
company_name="SYN-COMPANY",
confirmation_no="SYN-CONFIRMATION",
disp_room_no="SYN-ROOM",
effective_rate_amount=Decimal("450.50"),
full_name="SYN-GUEST",
res_comment="SYN-COMMENT",
trace_text="",
products="",
rate_code="SYN-RATE",
room_category_label="SYN-TYPE",
real_price=Decimal("450.50"),
total_price=Decimal("901.00"),
kb_amount=None,
)
report = build_monthly_report(
2026,
7,
date(2026, 7, 8),
MonthlySnapshot(
(source,),
(DailyVersionPin(date(2026, 7, 8), 100),),
(ChannelObservation(date(2026, 7, 8), 100, "QBD", 1, 1),),
),
)
return report.to_workbook_payload()
@unittest.skipUnless(
bool(artifact_tool_module() and Path(artifact_tool_module()).is_file()),
"artifact-tool module is not configured",
)
class MonthlyReportsXlsxTests(unittest.TestCase):
def test_builder_exports_reopens_scans_and_renders_every_channel(self):
node = node_binary()
if not node:
self.skipTest("Node.js is unavailable")
with tempfile.TemporaryDirectory(prefix="monthly-report-xlsx-") as temp_dir:
root = Path(temp_dir)
payload_path = root / "payload.json"
payload_path.write_text(
json.dumps(report_payload(), ensure_ascii=False),
encoding="utf-8",
)
output = root / "monthly.xlsx"
previews = root / "previews"
summary_path = root / "summary.json"
completed = subprocess.run(
[
node,
str(BUILDER),
str(payload_path),
str(output),
str(previews),
str(summary_path),
],
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
timeout=120,
check=False,
env=os.environ.copy(),
)
self.assertEqual(completed.returncode, 0, completed.stderr)
summary = json.loads(summary_path.read_text(encoding="utf-8"))
self.assertEqual(summary["sheet_names"][:5], [
"LIANTAI-GROUP", "LIANTAI-FIT", "QBD", "DY-AI-Easy-KB", "FENGRUN"
])
self.assertEqual(summary["row_counts"], [0, 0, 1, 0, 0])
self.assertEqual(summary["formula_count"], 1)
self.assertEqual(summary["preview_count"], 5)
self.assertEqual(len(list(previews.glob("sheet-*.png"))), 5)
self.assertEqual(output.stat().st_mode & 0o777, 0o600)
self.assertFalse(Path(f"{output}.inspect.ndjson").exists())
workbook = load_workbook(output, data_only=False, read_only=True)
try:
self.assertEqual(workbook["QBD"]["S2"].value, "=R2*C2*G2")
formulas = [
cell.value
for sheet in workbook.worksheets
for row in sheet.iter_rows()
for cell in row
if cell.data_type == "f"
]
self.assertEqual(formulas, ["=R2*C2*G2"])
finally:
workbook.close()
def test_invalid_payload_returns_only_stable_error_code(self):
node = node_binary()
if not node:
self.skipTest("Node.js is unavailable")
with tempfile.TemporaryDirectory(prefix="monthly-report-xlsx-") as temp_dir:
root = Path(temp_dir)
invalid = root / "invalid.json"
invalid.write_text(
json.dumps({"schema_version": "bad", "sensitive": "SYN-SECRET"}),
encoding="utf-8",
)
completed = subprocess.run(
[
node,
str(BUILDER),
str(invalid),
str(root / "invalid.xlsx"),
str(root / "previews"),
str(root / "summary.json"),
],
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
timeout=30,
check=False,
env=os.environ.copy(),
)
self.assertEqual(completed.returncode, 4)
self.assertEqual(
json.loads(completed.stderr),
{
"status": "failed",
"code": "MONTHLY_REPORT_OUTPUT_VALIDATION_FAILED",
},
)
self.assertNotIn("SYN-SECRET", completed.stderr)
if __name__ == "__main__":
unittest.main()