Files
ARR-2.0-0918/tests/test_arr_web_download_handoff.py
T

270 lines
13 KiB
Python

"""Synthetic integration only: no OHIP/source adapter, real DB or cloud store."""
import hashlib
import json
import tempfile
import threading
import time
import unittest
from datetime import date
from pathlib import Path
from arr_ingestion.repository import InMemoryIngestionRepository
from arr_ingestion.service import IngestionService
from arr_ingestion.validation import DeliveryValidator
from arr_processing.policy import load_processor_policy
from arr_storage.filesystem import FilesystemObjectBackend
from arr_storage.store import ManagedObjectStore
from arr_web.app import PortalApplication
from arr_web.arr_download_handoff import outcome_from_handoff
from arr_web.arr_downloads import PersistentARRDownloads
from arr_web.programmatic import ProgrammaticUploadCoordinator
from arr_web.repository import UnavailablePortalRepository
from integrations.ohip import processing_handoff as handoff
from tests.test_arr_opera_daily_ingest import reservation, xml_document
from tests.test_arr_web import TEST_CREDENTIALS, login
from tests.test_ohip_processing_handoff import CountingProcessor
DAY = date(2026, 9, 15)
REQUEST_ID = "a" * 32
class FixtureExecutor:
"""Test adapter uses declared synthetic XML; never imported by the runtime."""
def __init__(self, root, policy, store, repository, service):
self.root, self.policy, self.store = root, policy, store
self.repository, self.service = repository, service
self.processor = CountingProcessor(policy)
self.calls = []
self.payload = self.xml()
self.prepared = self.receipt = None
self.after_commit_error_once = False
self.entered, self.release = threading.Event(), threading.Event()
self.release.set()
@staticmethod
def xml(**kwargs):
return xml_document(reservation(1, **kwargs)).replace(
"2026-07-27", DAY.isoformat()
).replace("2026-07-28", "2026-09-16").replace(
"20260727", "20260915"
).replace("27-07-26", "15-09-26").encode()
def execute(self, *, request_id, from_date, to_date, report_stage):
self.calls.append((request_id, from_date, to_date))
self.entered.set()
if not self.release.wait(5):
raise RuntimeError("fixture wait expired")
if from_date != to_date:
raise ValueError("fixture requires one day")
binding = handoff.CaptureBinding(request_id, "SYNTHETIC", from_date.isoformat(),
"b" * 64, "synthetic-xml-fixture/v1")
report_stage("processing")
self.prepared = handoff.prepare(self.root / "handoffs", binding, "ARR.XML", self.payload,
self.policy, processor=self.processor)
self.receipt = handoff.deliver(Path(self.prepared["directory"]), self.prepared["manifest_sha256"],
object_store=self.store, repository=self.repository, service=self.service)
if self.after_commit_error_once:
self.after_commit_error_once = False
raise ConnectionError("synthetic lost acknowledgement with private details")
return outcome_from_handoff(self.receipt, job_id=binding.job_id, report_date=from_date,
manifest_sha256=self.prepared["manifest_sha256"])
class MemoryTrace(UnavailablePortalRepository):
def __init__(self, repository):
self.repository = repository
def get_job_trace(self, job_id):
return {"job": {"status": self.repository._jobs[job_id].status}}
class HandoffIntegrationTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.policy = load_processor_policy(Path(__file__).resolve().parents[1])
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.root = Path(self.temp.name)
self.repository = InMemoryIngestionRepository()
self.store = ManagedObjectStore(FilesystemObjectBackend(self.root / "objects", create=True))
self.ingestion = IngestionService(DeliveryValidator(self.store, self.policy), self.repository)
self.executor = FixtureExecutor(self.root, self.policy, self.store, self.repository, self.ingestion)
self.start_web()
def start_web(self):
self.queue = PersistentARRDownloads(self.root / "queue", self.executor)
self.app = PortalApplication(repository=MemoryTrace(self.repository), arr_downloads=self.queue,
credentials=TEST_CREDENTIALS)
_, self.headers = login(self.app)
def tearDown(self):
self.executor.release.set()
self.queue.close()
self.temp.cleanup()
def post(self, path="/api/arr-downloads", payload=None):
response = self.app.handle("POST", path, self.headers, json.dumps(
payload if payload is not None else {"report_date": DAY.isoformat(), "request_id": REQUEST_ID}
).encode())
self.assertEqual(response.status, 202, response.body)
return json.loads(response.body)["data"]
def finished(self):
deadline = time.monotonic() + 15
while time.monotonic() < deadline:
response = self.app.handle("GET", "/api/arr-downloads/" + REQUEST_ID, self.headers)
self.assertEqual(response.status, 200)
result = json.loads(response.body)["data"]
if result["status"] not in {"queued", "downloading", "processing"}:
return result
time.sleep(.01)
self.fail("integration worker did not settle")
def frozen_hashes(self):
root = Path(self.executor.prepared["directory"]) / "frozen"
return {str(p.relative_to(root)): hashlib.sha256(p.read_bytes()).hexdigest()
for p in root.rglob("*") if p.is_file()}
def test_selected_day_reaches_processing_and_double_click_commits_once(self):
self.executor.release.clear()
self.post()
self.assertTrue(self.executor.entered.wait(1))
second = self.post(payload={"report_date": DAY.isoformat(), "request_id": "c" * 32})
self.assertEqual(second["request_id"], REQUEST_ID)
self.executor.release.set()
result = self.finished()
self.assertEqual(result["status"], "succeeded")
self.assertEqual(self.executor.calls, [(REQUEST_ID, DAY, DAY)])
self.assertEqual(self.executor.receipt["business_date"], DAY.isoformat())
self.assertEqual(result["job_id"], self.executor.receipt["job_id"])
self.assertEqual(len(self.repository._versions), 1)
self.assertEqual(self.post()["job_id"], result["job_id"])
self.assertEqual(self.executor.processor.calls, 1)
def test_restart_and_lost_ack_retry_keep_one_version_and_identical_artifacts(self):
self.executor.after_commit_error_once = True
self.post()
interrupted = self.finished()
self.assertEqual(interrupted["status"], "interrupted")
self.assertNotIn("private", json.dumps(interrupted))
before = self.frozen_hashes()
self.assertEqual(len(self.repository._versions), 1)
self.queue.close()
self.start_web()
self.assertEqual(self.finished()["status"], "interrupted")
self.post("/api/arr-downloads/" + REQUEST_ID + "/retry", {})
self.assertEqual(self.finished()["status"], "succeeded")
self.assertEqual(self.executor.calls, [(REQUEST_ID, DAY, DAY)] * 2)
self.assertEqual(self.frozen_hashes(), before)
self.assertEqual(self.executor.processor.calls, 1)
self.assertEqual((len(self.repository._jobs), len(self.repository._callbacks), len(self.repository._versions)),
(1, 1, 1))
def test_price_review_is_persisted_without_finance_or_download_retry(self):
self.executor.payload = self.executor.xml(rate_amount="1800")
self.post()
result = self.finished()
self.assertEqual(result["status"], "needs_review")
self.assertFalse(result["can_retry"])
self.assertIn(result["job_id"], self.repository._reviews)
self.assertFalse(self.repository._versions)
self.post("/api/arr-downloads/" + REQUEST_ID + "/retry", {})
self.assertEqual(len(self.executor.calls), 1)
def test_authoritative_business_failure_does_not_become_success(self):
self.executor.payload = self.executor.xml(departure="2026-09-14")
self.post()
result = self.finished()
self.assertEqual(result["status"], "failed")
self.assertFalse(result["can_retry"])
self.assertEqual(self.repository._jobs[result["job_id"]].status, "failed")
self.assertFalse(self.repository._versions)
def test_finished_manual_review_updates_web_status_without_recapturing(self):
self.executor.payload = self.executor.xml(rate_amount="1800")
self.post()
task = self.finished()
self.assertEqual(task["status"], "needs_review")
review_service = ProgrammaticUploadCoordinator(
object_store=self.store, ingestion_repository=self.repository,
ingestion_service=self.ingestion, processor=self.executor.processor,
processor_version=self.policy.processor_version, rule_set_sha256=self.policy.rule_set_sha256,
)
review = review_service.get_price_review(task["job_id"], 50, 0)
updated = review_service.update_price_review_item(
task["job_id"], review["items"][0]["item_id"], review["case_id"], review["revision"],
"0", "synthetic-reviewer",
)
result = review_service.finalize_price_review(
task["job_id"], updated["case_id"], updated["revision"], "synthetic-reviewer",
)
self.assertEqual(result["status"], "succeeded")
self.assertEqual(self.finished()["status"], "succeeded")
self.assertEqual(self.finished()["job_id"], task["job_id"])
self.assertEqual(len(self.executor.calls), 1)
self.assertEqual(len(self.repository._versions), 1)
def test_changed_source_on_retry_cannot_create_another_finance_version(self):
self.executor.after_commit_error_once = True
self.post()
self.assertEqual(self.finished()["status"], "interrupted")
before = self.frozen_hashes()
self.executor.payload += b"\n"
self.post("/api/arr-downloads/" + REQUEST_ID + "/retry", {})
self.assertEqual(self.finished()["status"], "interrupted")
self.assertEqual(self.frozen_hashes(), before)
self.assertEqual(len(self.repository._versions), 1)
self.assertEqual(self.executor.processor.calls, 1)
def test_mismatched_or_unknown_receipt_is_rejected(self):
self.post()
self.assertEqual(self.finished()["status"], "succeeded")
receipt = self.executor.receipt
for changes in (
{"job_id": "different-job"}, {"manifest_sha256": "c" * 64},
{"business_date": "2026-09-16"}, {"version": "unknown"},
{"delivery_id": "other-delivery"}, {"ingestion_status": "candidate_capture_complete"},
{"ingestion_status": "future-status"}, {"daily_version_id": None},
{"daily_version_id": True}, {"version_no": 0},
{"ingestion_status": "recorded_review"},
{"daily_version_id": "1"}, {"version_no": -1},
):
with self.subTest(changes=changes), self.assertRaises(ValueError):
outcome_from_handoff({**receipt, **changes}, job_id=receipt["job_id"], report_date=DAY,
manifest_sha256=self.executor.prepared["manifest_sha256"])
failure = {**receipt, "ingestion_status": "recorded_failure", "business_date": None,
"daily_version_id": None, "version_no": None}
outcome = outcome_from_handoff(failure, job_id=receipt["job_id"], report_date=DAY,
manifest_sha256=self.executor.prepared["manifest_sha256"])
self.assertEqual(outcome.status, "failed")
self.assertFalse(outcome.retryable)
def test_failure_accepts_rejected_audit_id_but_not_committed_version(self):
self.executor.payload = self.executor.xml(rate_code="NOT-ALLOWED")
self.post()
self.assertEqual(self.finished()["status"], "failed")
receipt = self.executor.receipt
expected = dict(job_id=receipt["job_id"], report_date=DAY,
manifest_sha256=receipt["manifest_sha256"])
for audit_id in (None, 17):
with self.subTest(audit_id=audit_id):
outcome = outcome_from_handoff({**receipt, "daily_version_id": audit_id}, **expected)
self.assertEqual(outcome.status, "failed")
self.assertEqual(outcome.job_id, receipt["job_id"])
self.assertFalse(outcome.retryable)
for audit_id in (True, False, 0, -1, 17.0, "17", {}, []):
with self.subTest(invalid_audit_id=audit_id), self.assertRaises(ValueError):
outcome_from_handoff({**receipt, "daily_version_id": audit_id}, **expected)
for changes in ({"version_no": 1}, {"version_no": 0}, {"version_no": False},
{"ingestion_status": "recorded_review"}, {"business_date": "2026-09-16"}):
with self.subTest(changes=changes), self.assertRaises(ValueError):
outcome_from_handoff({**receipt, "daily_version_id": 17, **changes}, **expected)
if __name__ == "__main__":
unittest.main()