feat: prepare ARR for controlled public deployment
This commit is contained in:
220
tests/test_company_reports_repository.py
Normal file
220
tests/test_company_reports_repository.py
Normal file
@@ -0,0 +1,220 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from contextlib import contextmanager
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
from typing import Any, Callable, List, Optional, Sequence, Tuple
|
||||
from unittest.mock import patch
|
||||
|
||||
from company_reports.contracts import BatchSnapshot, ErrorCode
|
||||
from company_reports.core import build_company_report
|
||||
from company_reports.repository import (
|
||||
CURRENT_PARSE_SQL,
|
||||
ROOM_ITEMS_SQL,
|
||||
DatabaseConfig,
|
||||
FileMetadata,
|
||||
PostgresReportRepository,
|
||||
RepositoryError,
|
||||
ReservedReport,
|
||||
)
|
||||
|
||||
|
||||
Handler = Callable[[str, Optional[Sequence[Any]]], Tuple[List[Tuple[Any, ...]], int]]
|
||||
|
||||
|
||||
class FakeCursor:
|
||||
def __init__(self, handler: Handler):
|
||||
self.handler = handler
|
||||
self.calls: List[Tuple[str, Optional[Sequence[Any]]]] = []
|
||||
self.many_calls: List[Tuple[str, Sequence[Sequence[Any]]]] = []
|
||||
self._rows: List[Tuple[Any, ...]] = []
|
||||
self.rowcount = 0
|
||||
|
||||
def __enter__(self) -> "FakeCursor":
|
||||
return self
|
||||
|
||||
def __exit__(self, *_args: Any) -> None:
|
||||
return None
|
||||
|
||||
def execute(self, sql: str, params: Optional[Sequence[Any]] = None) -> None:
|
||||
normalized = " ".join(sql.split())
|
||||
self.calls.append((normalized, params))
|
||||
self._rows, self.rowcount = self.handler(normalized, params)
|
||||
|
||||
def executemany(self, sql: str, params: Sequence[Sequence[Any]]) -> None:
|
||||
normalized = " ".join(sql.split())
|
||||
self.many_calls.append((normalized, params))
|
||||
self._rows, self.rowcount = self.handler(normalized, params)
|
||||
|
||||
def fetchall(self) -> List[Tuple[Any, ...]]:
|
||||
rows = list(self._rows)
|
||||
self._rows = []
|
||||
return rows
|
||||
|
||||
def fetchone(self) -> Tuple[Any, ...]:
|
||||
row = self._rows[0]
|
||||
self._rows = self._rows[1:]
|
||||
return row
|
||||
|
||||
|
||||
class FakeConnection:
|
||||
def __init__(self, handler: Handler):
|
||||
self.cursor_instance = FakeCursor(handler)
|
||||
self.closed = False
|
||||
|
||||
@contextmanager
|
||||
def transaction(self):
|
||||
yield
|
||||
|
||||
def cursor(self) -> FakeCursor:
|
||||
return self.cursor_instance
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
|
||||
class CompanyReportRepositoryTests(unittest.TestCase):
|
||||
def test_database_config_accepts_company_or_arr_dsn(self):
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"ARR_DATABASE_URL": "postgresql://arr"},
|
||||
clear=True,
|
||||
):
|
||||
self.assertEqual(
|
||||
DatabaseConfig.from_environment().dsn,
|
||||
"postgresql://arr",
|
||||
)
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
with self.assertRaises(RepositoryError) as caught:
|
||||
DatabaseConfig.from_environment()
|
||||
self.assertEqual(caught.exception.code, ErrorCode.REQUEST_INVALID)
|
||||
|
||||
def test_file_metadata_rejects_absolute_or_parent_paths(self):
|
||||
for storage_key in ("/tmp/SYN.xlsx", "outputs/../SYN.xlsx"):
|
||||
with self.subTest(storage_key=storage_key):
|
||||
metadata = FileMetadata(
|
||||
file_kind="company_ten_day_xlsx",
|
||||
original_filename="SYN.xlsx",
|
||||
storage_key=storage_key,
|
||||
sha256="a" * 64,
|
||||
byte_size=1,
|
||||
mime_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
)
|
||||
with self.assertRaises(RepositoryError) as caught:
|
||||
metadata.validate()
|
||||
self.assertEqual(caught.exception.code, ErrorCode.PUBLISH_FAILED)
|
||||
|
||||
def test_snapshot_reads_booking_data_by_group_code_only(self):
|
||||
source_row = (
|
||||
date(2026, 6, 30),
|
||||
101,
|
||||
501,
|
||||
"QBD",
|
||||
"QBD",
|
||||
"SYN-COMPANY",
|
||||
"SYN-BLOCK-A",
|
||||
"SYN-GROUP-A",
|
||||
" syn-group-a ",
|
||||
"SYN-RM2",
|
||||
date(2026, 6, 30),
|
||||
date(2026, 7, 2),
|
||||
2,
|
||||
Decimal("900"),
|
||||
"matched",
|
||||
)
|
||||
room_row = (
|
||||
"SYN-GROUP-A",
|
||||
"SYN-SUP-TWN",
|
||||
2,
|
||||
)
|
||||
|
||||
def handler(sql: str, _params: Optional[Sequence[Any]]):
|
||||
if "FROM finance.v_active_daily_facts" in sql:
|
||||
return [source_row], 1
|
||||
if "FROM booking.v_current_room_items" in sql:
|
||||
return [("SYN-GROUP-A", 701)], 1
|
||||
if "FROM booking.v_group_room_item_summary" in sql:
|
||||
return [room_row], 1
|
||||
return [], 0
|
||||
|
||||
connection = FakeConnection(handler)
|
||||
repository = PostgresReportRepository(
|
||||
DatabaseConfig("postgresql://synthetic"),
|
||||
connect=lambda _dsn: connection,
|
||||
)
|
||||
|
||||
snapshot = repository.load_snapshot(2026, 7, date(2026, 7, 10))
|
||||
|
||||
self.assertTrue(connection.closed)
|
||||
self.assertEqual(len(snapshot.facts), 1)
|
||||
self.assertEqual(snapshot.facts[0].business_date, date(2026, 6, 30))
|
||||
self.assertEqual(snapshot.group_parse_versions, {"SYN-GROUP-A": 701})
|
||||
self.assertEqual(snapshot.room_items[0].quantity, 2)
|
||||
booking_calls = [
|
||||
(sql, params)
|
||||
for sql, params in connection.cursor_instance.calls
|
||||
if "booking.v_current_room_items" in sql
|
||||
or "booking.v_group_room_item_summary" in sql
|
||||
]
|
||||
self.assertEqual(len(booking_calls), 2)
|
||||
self.assertTrue(all(params == (["SYN-GROUP-A"],) for _sql, params in booking_calls))
|
||||
self.assertNotIn("arrival", CURRENT_PARSE_SQL.lower())
|
||||
self.assertNotIn("departure", CURRENT_PARSE_SQL.lower())
|
||||
room_where = ROOM_ITEMS_SQL.lower().split("where", 1)[1]
|
||||
self.assertNotIn("arrival", room_where)
|
||||
self.assertNotIn("departure", room_where)
|
||||
|
||||
def test_reserve_and_activate_do_not_persist_generated_report_rows(self):
|
||||
report = build_company_report(
|
||||
"HanaTour",
|
||||
2026,
|
||||
7,
|
||||
date(2026, 7, 10),
|
||||
BatchSnapshot((), (), (), {}),
|
||||
)
|
||||
|
||||
reserve_connection = FakeConnection(lambda _sql, _params: ([], 0))
|
||||
repository = PostgresReportRepository(
|
||||
DatabaseConfig("postgresql://synthetic"),
|
||||
connect=lambda _dsn: reserve_connection,
|
||||
)
|
||||
reservation = repository.reserve_report(report)
|
||||
self.assertGreater(reservation.report_version_id, 0)
|
||||
self.assertEqual(reservation.report_version_id, reservation.version_no)
|
||||
self.assertEqual(reservation.company, "HanaTour")
|
||||
self.assertFalse(reserve_connection.closed)
|
||||
|
||||
activate_connection = FakeConnection(lambda _sql, _params: ([], 0))
|
||||
repository = PostgresReportRepository(
|
||||
DatabaseConfig("postgresql://synthetic"),
|
||||
connect=lambda _dsn: activate_connection,
|
||||
)
|
||||
artifact = FileMetadata(
|
||||
file_kind="company_ten_day_xlsx",
|
||||
original_filename=report.filename,
|
||||
storage_key="outputs/company_reports/2026/07/archive/v0003/" + report.filename,
|
||||
sha256="a" * 64,
|
||||
byte_size=100,
|
||||
mime_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
)
|
||||
result_json = FileMetadata(
|
||||
file_kind="result_json",
|
||||
original_filename="HanaTour-v0003.result.json",
|
||||
storage_key="outputs/company_reports/2026/07/archive/v0003/HanaTour-v0003.result.json",
|
||||
sha256="b" * 64,
|
||||
byte_size=50,
|
||||
mime_type="application/json",
|
||||
)
|
||||
|
||||
repository.activate_report(reservation, artifact, result_json)
|
||||
|
||||
self.assertFalse(activate_connection.closed)
|
||||
executed = " ".join(sql for sql, _params in activate_connection.cursor_instance.calls)
|
||||
self.assertNotIn("finance.report_versions", executed)
|
||||
self.assertNotIn("booking.file_objects", executed)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user