from __future__ import annotations import tempfile import unittest from datetime import date from pathlib import Path from arr_ingestion.contracts import IngestionError from arr_ingestion.repository import InMemoryIngestionRepository from arr_ingestion.service import IngestionService from arr_ingestion.validation import DeliveryValidator from arr_processing.local import LocalDailyProcessor from arr_processing.policy import load_processor_policy from arr_storage.filesystem import FilesystemObjectBackend from arr_storage.store import ManagedObjectStore from arr_web.contracts import PortalError from arr_web.programmatic import ProgrammaticUploadCoordinator from tests.test_arr_opera_daily_ingest import ( reservation, success_xml, xml_document, ) PROJECT_ROOT = Path(__file__).resolve().parents[1] class RejectingManualReplayProcessor: def __init__(self, delegate: LocalDailyProcessor) -> None: self._delegate = delegate def run(self, *args, **kwargs): if kwargs.get("manual_overrides") is not None: raise IngestionError( "PROCESSOR_RESULT_INVALID", "manual replay contract is invalid", ) return self._delegate.run(*args, **kwargs) def coordinator(root: Path): store = ManagedObjectStore( FilesystemObjectBackend(root / "objects", create=True) ) repository = InMemoryIngestionRepository() policy = load_processor_policy(PROJECT_ROOT) service = IngestionService(DeliveryValidator(store, policy), repository) return ( ProgrammaticUploadCoordinator( object_store=store, ingestion_repository=repository, ingestion_service=service, processor=LocalDailyProcessor(policy), processor_version=policy.processor_version, rule_set_sha256=policy.rule_set_sha256, ), repository, root / "objects", ) class ProgrammaticUploadTests(unittest.TestCase): def test_valid_xml_reaches_atomic_terminal_success_without_remote_boundary(self): with tempfile.TemporaryDirectory() as temporary: upload, repository, objects = coordinator(Path(temporary)) receipt = upload.submit("ARR.XML", success_xml().encode("utf-8")) self.assertEqual(receipt["status"], "succeeded") self.assertEqual(receipt["ingestion_status"], "committed") self.assertEqual(receipt["business_date"], "2026-07-27") self.assertEqual(receipt["version_no"], 1) self.assertEqual( repository._jobs[receipt["job_id"]].registration.uploaded_filename, "ARR.XML", ) self.assertEqual( repository.current_version_id(date(2026, 7, 27)), receipt["daily_version_id"], ) records = repository.version_records(int(receipt["daily_version_id"])) self.assertEqual(len(records), 5) self.assertEqual( sum(1 for item in records if item["outcome"] == "retained"), 3, ) committed = { path.name for path in objects.rglob("*") if path.is_file() and not path.name.endswith(".arr-metadata.json") } self.assertEqual( committed, {"source.xml", "result.json", "structured-result.json", "daily-report.xlsx"}, ) def test_price_unmatched_is_recorded_for_review_without_partial_active_version(self): price_review = xml_document( reservation(1, rate_code="GRPA1", rate_amount="900"), reservation(2, rate_code="GRPA1", rate_amount="1800"), ) with tempfile.TemporaryDirectory() as temporary: upload, repository, objects = coordinator(Path(temporary)) receipt = upload.submit("ARR.XML", price_review.encode("utf-8")) self.assertEqual(receipt["status"], "needs_review") self.assertEqual(receipt["ingestion_status"], "recorded_review") self.assertIsNotNone(receipt["review_case_id"]) self.assertEqual(receipt["review_completed_items"], 0) self.assertEqual(receipt["review_total_items"], 1) self.assertIsNone(receipt["version_no"]) self.assertIsNone(repository.current_version_id(date(2026, 7, 27))) committed = { path.name for path in objects.rglob("*") if path.is_file() and not path.name.endswith(".arr-metadata.json") } self.assertEqual( committed, {"source.xml", "result.json", "structured-result.json"}, ) def test_finalized_review_replays_original_xml_and_commits_manual_version(self): source = xml_document( reservation(1, rate_code="GRPA1", rate_amount="900"), reservation(2, rate_code="GRPA1", rate_amount="1800", rooms="2"), ) with tempfile.TemporaryDirectory() as temporary: upload, repository, objects = coordinator(Path(temporary)) receipt = upload.submit("ARR.XML", source.encode("utf-8")) self.assertEqual(receipt["status"], "needs_review") self.assertIsNone(repository.current_version_id(date(2026, 7, 27))) review = upload.get_price_review(receipt["job_id"], 50, 0) self.assertEqual(review["total_items"], 1) updated = upload.update_price_review_item( receipt["job_id"], review["items"][0]["item_id"], review["case_id"], review["revision"], "0", "operator@example.test", ) final = upload.finalize_price_review( receipt["job_id"], updated["case_id"], updated["revision"], "operator@example.test", ) self.assertEqual(final["status"], "succeeded") self.assertEqual(final["ingestion_status"], "committed") self.assertEqual(final["version_no"], 1) self.assertEqual(repository.current_version_id(date(2026, 7, 27)), final["daily_version_id"]) records = repository.version_records(int(final["daily_version_id"])) self.assertEqual( [record["pricing_method"] for record in records], ["price_reference_exact", "manual_review"], ) completed = upload.get_price_review(receipt["job_id"], 50, 0) self.assertEqual(completed["case_status"], "completed") committed = { path.name for path in objects.rglob("*") if path.is_file() and not path.name.endswith(".arr-metadata.json") } self.assertEqual( committed, { "source.xml", "result.json", "structured-result.json", "daily-report.xlsx", "manual-override.json", }, ) def test_non_price_validation_error_remains_business_failure(self): invalid = xml_document(reservation(1, departure="2026-07-26")) with tempfile.TemporaryDirectory() as temporary: upload, repository, _objects = coordinator(Path(temporary)) receipt = upload.submit("ARR.XML", invalid.encode("utf-8")) self.assertEqual(receipt["status"], "failed") self.assertEqual(receipt["ingestion_status"], "recorded_failure") self.assertIsNone(repository.current_version_id(date(2026, 7, 27))) def test_deterministic_finalization_error_closes_review_case(self): source = xml_document( reservation(1, rate_code="GRPA1", rate_amount="900"), reservation(2, rate_code="GRPA1", rate_amount="1800"), ) with tempfile.TemporaryDirectory() as temporary: upload, repository, _objects = coordinator(Path(temporary)) receipt = upload.submit("ARR.XML", source.encode("utf-8")) review = upload.get_price_review(receipt["job_id"], 50, 0) updated = upload.update_price_review_item( receipt["job_id"], review["items"][0]["item_id"], review["case_id"], review["revision"], "0", "operator@example.test", ) upload.processor = RejectingManualReplayProcessor(upload.processor) # type: ignore[assignment] with self.assertRaises(PortalError) as raised: upload.finalize_price_review( receipt["job_id"], updated["case_id"], updated["revision"], "operator@example.test", ) self.assertEqual(raised.exception.code, "PROCESSOR_RESULT_INVALID") self.assertEqual( upload.get_price_review(receipt["job_id"], 50, 0)["case_status"], "failed", ) self.assertEqual(repository._jobs[receipt["job_id"]].status, "failed") self.assertIsNone(repository.current_version_id(date(2026, 7, 27))) if __name__ == "__main__": unittest.main()