Files
wyndham-ARR/tests/test_package_contract.py
2026-07-29 16:38:05 +08:00

182 lines
7.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import hashlib
import json
import sys
import unittest
import zipfile
from decimal import Decimal
from pathlib import Path
from openpyxl import load_workbook
PROJECT_ROOT = Path(__file__).resolve().parents[1]
SKILL_ROOT = PROJECT_ROOT / "opera-daily-channel-report"
SCRIPTS = SKILL_ROOT / "scripts"
PRICE_REFERENCE = SKILL_ROOT / "references" / "价格对照.xlsx"
ARCHIVE = PROJECT_ROOT / "opera-daily-channel-report.zip"
sys.path.insert(0, str(SCRIPTS))
import process_reports as core # noqa: E402
def sha256_bytes(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
class PackageContractTests(unittest.TestCase):
def test_required_skill_layout(self):
required = {
"SKILL.md",
"scripts/process_reports.py",
"scripts/validate_outputs.py",
"references/business-rules.md",
"references/field-contracts.md",
"references/error-contract.md",
"references/codex-result.schema.json",
"references/structured-output.md",
"references/structured-result.schema.json",
"references/价格对照.xlsx",
"assets/daily-template.xlsx",
"assets/channel-report-template.xlsx",
}
actual = {
str(path.relative_to(SKILL_ROOT))
for path in SKILL_ROOT.rglob("*")
if path.is_file() and path.name != ".DS_Store" and "__pycache__" not in path.parts
}
self.assertEqual(actual, required)
def test_frozen_constants_and_company_identities(self):
self.assertEqual(core.VERSION, "1.0")
self.assertEqual(core.DAILY_RESULT_VERSION, "2.0")
self.assertEqual(core.PROCESSOR_VERSION, "2.2.0")
self.assertEqual(core.STRUCTURED_RESULT_SCHEMA_VERSION, "1.0")
self.assertEqual(core.DAILY_STRUCTURED_RESULT_SCHEMA_VERSION, "2.0")
self.assertEqual(core.PROCESSING_MODES, {"daily", "daily-monthly"})
self.assertEqual(
core.STANDARD_SHEETS,
["LIANTAI-GROUP", "LIANTAI-FIT", "QBD", "DY-AI-Easy-KB", "FENGRUN"],
)
self.assertEqual(len(core.DAILY_HEADERS), 19)
self.assertEqual(core.DAILY_HEADERS[-2:], ["REAL PRICE", "TOTAL PRICE"])
self.assertEqual(len(core.CHANNEL_HEADERS), 19)
self.assertEqual(core.CHANNEL_HEADERS[-2:], ["REAL PRICE", "TOTAL PRICE"])
self.assertEqual(len(core.KB_CHANNEL_HEADERS), 20)
self.assertEqual(core.KB_CHANNEL_HEADERS[-1], "KB100/晚/间)")
self.assertEqual(len(core.RATE_WHITELIST), 20)
self.assertEqual(core.ZERO_TOTAL_COMPANIES, {"RAINBOW/AI", "GUANGZHOU GO EASY"})
self.assertEqual(core.ZERO_TOTAL_RATE_CODES, {"LBMS", "LBSM"})
self.assertEqual(core.price_company_key("T- Rainbow Holiday Se"), "RAINBOW/AI")
self.assertEqual(core.price_company_key("T- FENGRUN TRAVEL TRA"), "FENGRUN")
self.assertEqual(core.price_company_key("T- HANATOUR TD CO., L"), "HANA TOUR")
self.assertEqual(core.price_company_key("T- HONGTAI TRAVEL (TH"), "HONGTAI")
self.assertEqual(core.price_company_key("T- Guangzhou Go Easy"), "GUANGZHOU GO EASY")
def test_price_reference_is_unique_and_contains_alias_targets(self):
price_map = core.load_price_map(PRICE_REFERENCE.resolve())
self.assertEqual(len(price_map), 73)
cases = {
("HANA TOUR", "WHKR2100B", Decimal("2100")): Decimal("2100"),
("HANA TOUR", "WHKR2100B", Decimal("3300")): Decimal("3300"),
("FENGRUN", "GLSPCB", Decimal("1800")): Decimal("1800"),
("HONGTAI", "LBKB", Decimal("900")): Decimal("1800"),
("RAINBOW/AI", "LBSM", Decimal("900")): Decimal("0"),
}
for key, expected in cases.items():
with self.subTest(key=key):
self.assertEqual(price_map[key], expected)
def test_templates_have_exact_headers_and_no_data_or_formulas(self):
daily_path = SKILL_ROOT / "assets" / "daily-template.xlsx"
daily = load_workbook(daily_path, data_only=False)
try:
for sheet in daily.worksheets:
column_count = len(core.DAILY_HEADERS)
headers = [
sheet.cell(1, column).value for column in range(1, column_count + 1)
]
self.assertEqual(headers, core.DAILY_HEADERS)
self.assertIsNone(sheet.cell(1, column_count + 1).value)
for row in sheet.iter_rows(
min_row=2,
max_row=max(sheet.max_row, 2),
min_col=1,
max_col=column_count,
):
for cell in row:
self.assertIn(cell.value, (None, ""))
self.assertNotEqual(cell.data_type, "f")
finally:
daily.close()
monthly_path = SKILL_ROOT / "assets" / "channel-report-template.xlsx"
monthly = load_workbook(monthly_path, data_only=False)
try:
self.assertEqual(monthly.sheetnames, core.STANDARD_SHEETS)
for sheet in monthly.worksheets:
expected_headers = core.channel_headers_for_sheet(sheet.title)
column_count = len(expected_headers)
headers = [
sheet.cell(1, column).value for column in range(1, column_count + 1)
]
self.assertEqual(headers, expected_headers)
self.assertIsNone(sheet.cell(1, column_count + 1).value)
for row in sheet.iter_rows(
min_row=2,
max_row=max(sheet.max_row, 2),
min_col=1,
max_col=column_count,
):
for cell in row:
self.assertIn(cell.value, (None, ""))
self.assertNotEqual(cell.data_type, "f")
finally:
monthly.close()
def test_result_schema_strictly_supports_legacy_and_daily_only_versions(self):
schema_path = SKILL_ROOT / "references" / "codex-result.schema.json"
schema = json.loads(schema_path.read_text(encoding="utf-8"))
self.assertFalse(schema["additionalProperties"])
self.assertEqual(
schema["properties"]["version"],
{"type": "string", "enum": ["1.0", "2.0"]},
)
self.assertEqual(schema["properties"]["processing_mode"], {"type": "string", "const": "daily"})
self.assertEqual(schema["properties"]["status"]["enum"], ["success", "failed"])
self.assertEqual(
schema["required"],
[
"version",
"status",
"business_date",
"month_key",
"message",
"metrics",
"outputs",
"errors",
],
)
def test_zip_payload_matches_skill_source(self):
source_files = {
str(path.relative_to(PROJECT_ROOT)): path
for path in SKILL_ROOT.rglob("*")
if path.is_file() and path.name != ".DS_Store" and "__pycache__" not in path.parts
}
with zipfile.ZipFile(ARCHIVE) as archive:
archived_files = {
name: archive.read(name)
for name in archive.namelist()
if not name.endswith("/")
}
self.assertEqual(set(archived_files), set(source_files))
for name, path in source_files.items():
with self.subTest(path=name):
self.assertEqual(sha256_bytes(archived_files[name]), sha256_bytes(path.read_bytes()))
if __name__ == "__main__":
unittest.main(verbosity=2)