feat: prepare ARR for controlled public deployment
This commit is contained in:
254
tests/test_arr_end_to_end.py
Normal file
254
tests/test_arr_end_to_end.py
Normal file
@@ -0,0 +1,254 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import contextlib
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from datetime import date, datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from arr_ingestion.contracts import XLSX_MIME
|
||||
from arr_ingestion.direct_contracts import SubmissionGrant
|
||||
from arr_ingestion.repository import InMemoryIngestionRepository
|
||||
from arr_ingestion.service import IngestionService
|
||||
from arr_ingestion.validation import DeliveryValidator
|
||||
from arr_processing.callbacks import AgentResultWriteback
|
||||
from arr_processing.contracts import ProcessingRequest, ProcessingResult
|
||||
from arr_processing.ledger import InMemoryProcessingLedger
|
||||
from arr_processing.registration import ProcessingOutputRegistrar
|
||||
from arr_processing.runner import ProcessingRunner, RemoteRunSnapshot
|
||||
from arr_processing.signatures import SignedResultCodec
|
||||
from arr_storage.filesystem import FilesystemObjectBackend
|
||||
from arr_storage.contracts import ObjectAddress
|
||||
from arr_storage.store import ManagedObjectStore
|
||||
from arr_web.services import ObjectStoreUploadCoordinator
|
||||
from tests.test_arr_ingestion_validation import policy
|
||||
from tests.test_arr_opera_daily_ingest import (
|
||||
core,
|
||||
success_xml,
|
||||
)
|
||||
|
||||
|
||||
NOW = datetime(2026, 7, 28, 9, 0, tzinfo=timezone.utc)
|
||||
SIGNING_KEY = b"fake-superagent-signing-key-at-least-32-bytes"
|
||||
|
||||
|
||||
class CapturingRepository:
|
||||
def __init__(self) -> None:
|
||||
self.inner = InMemoryIngestionRepository()
|
||||
self.registration: Any = None
|
||||
|
||||
def register_job(self, registration: Any) -> None:
|
||||
self.registration = registration
|
||||
self.inner.register_job(registration)
|
||||
|
||||
def issue_grant(
|
||||
self,
|
||||
job_id: str,
|
||||
attempt_no: int,
|
||||
*,
|
||||
ttl_seconds: int,
|
||||
) -> SubmissionGrant:
|
||||
return SubmissionGrant(
|
||||
"G" * 43,
|
||||
job_id,
|
||||
attempt_no,
|
||||
NOW + timedelta(seconds=ttl_seconds),
|
||||
)
|
||||
|
||||
def commit_delivery(self, delivery: Any) -> Any:
|
||||
return self.inner.commit_delivery(delivery)
|
||||
|
||||
|
||||
class FakeSuperAgentTransport:
|
||||
"""Fetches one opaque source handle, invokes the real Skill, and signs handles."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
root: Path,
|
||||
store: ManagedObjectStore,
|
||||
codec: SignedResultCodec,
|
||||
) -> None:
|
||||
self.root = root
|
||||
self.store = store
|
||||
self.codec = codec
|
||||
self.remote_files: dict[str, bytes] = {}
|
||||
self.signed_result: str | None = None
|
||||
self.request: ProcessingRequest | None = None
|
||||
self.run_id = "run-fake-superagent-001"
|
||||
|
||||
@staticmethod
|
||||
def _handle(role: str, filename: str, value: bytes, mime_type: str) -> dict[str, object]:
|
||||
return {
|
||||
"file_handle": f"remote-{role}-001",
|
||||
"original_filename": filename,
|
||||
"sha256": hashlib.sha256(value).hexdigest(),
|
||||
"byte_size": len(value),
|
||||
"mime_type": mime_type,
|
||||
}
|
||||
|
||||
def submit(self, request: ProcessingRequest, idempotency_key: str) -> RemoteRunSnapshot:
|
||||
self.request = request
|
||||
fetched = self.root / "agent" / "source.xml"
|
||||
fetched.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.store.materialize(
|
||||
self.store.key_policy.build(
|
||||
ObjectAddress(
|
||||
request.job_id,
|
||||
request.attempt_no,
|
||||
"committed",
|
||||
"source_xml",
|
||||
)
|
||||
),
|
||||
fetched,
|
||||
25 * 1024 * 1024,
|
||||
)
|
||||
output_dir = self.root / "agent" / "skill-run" / "output"
|
||||
result_path = output_dir / "result.json"
|
||||
structured_path = output_dir / "structured-result.json"
|
||||
arguments = argparse.Namespace(
|
||||
xml=str(fetched.resolve()),
|
||||
output_dir=str(output_dir.resolve()),
|
||||
result_json=str(result_path.resolve()),
|
||||
structured_result_json=str(structured_path.resolve()),
|
||||
)
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
exit_code = core.process(arguments)
|
||||
result = json.loads(result_path.read_text(encoding="utf-8"))
|
||||
if exit_code != 0:
|
||||
raise AssertionError("synthetic success fixture failed in the deterministic Skill")
|
||||
values = {
|
||||
"daily_report": (output_dir / result["outputs"]["daily_report"]).read_bytes(),
|
||||
"result_json": (output_dir / "result.json").read_bytes(),
|
||||
"structured_result_json": (output_dir / "structured-result.json").read_bytes(),
|
||||
}
|
||||
filenames = {
|
||||
"daily_report": result["outputs"]["daily_report"],
|
||||
"result_json": "result.json",
|
||||
"structured_result_json": "structured-result.json",
|
||||
}
|
||||
mime_types = {
|
||||
"daily_report": XLSX_MIME,
|
||||
"result_json": "application/json",
|
||||
"structured_result_json": "application/json",
|
||||
}
|
||||
artifacts = {
|
||||
role: self._handle(role, filenames[role], value, mime_types[role])
|
||||
for role, value in values.items()
|
||||
}
|
||||
artifacts["exception_report"] = None
|
||||
for role, value in values.items():
|
||||
self.remote_files[str(artifacts[role]["file_handle"])] = value
|
||||
payload = {
|
||||
"contract_version": "arr-opera-daily-result-1",
|
||||
"delivery_id": "delivery-" + request.job_id,
|
||||
"job_id": request.job_id,
|
||||
"attempt_no": request.attempt_no,
|
||||
"remote_run_id": self.run_id,
|
||||
"status": "success",
|
||||
"business_date": result["business_date"],
|
||||
"processor_version": request.processor_version,
|
||||
"rule_set_sha256": request.rule_set_sha256,
|
||||
"result_schema_version": "3.0",
|
||||
"artifacts": artifacts,
|
||||
}
|
||||
signed = self.codec.sign(
|
||||
ProcessingResult.from_dict(payload),
|
||||
issued_at=NOW,
|
||||
nonce="fake-superagent-nonce-001",
|
||||
)
|
||||
self.signed_result = signed.decode("utf-8")
|
||||
return RemoteRunSnapshot(self.run_id, "running")
|
||||
|
||||
def get(self, request: ProcessingRequest, remote_run_id: str) -> RemoteRunSnapshot:
|
||||
if request != self.request or remote_run_id != self.run_id or self.signed_result is None:
|
||||
raise AssertionError("fake SuperAgent correlation failed")
|
||||
return RemoteRunSnapshot(self.run_id, "success", self.signed_result)
|
||||
|
||||
def cancel(self, request: ProcessingRequest, remote_run_id: str) -> RemoteRunSnapshot:
|
||||
return RemoteRunSnapshot(remote_run_id, "cancelled")
|
||||
|
||||
def materialize(self, file_handle: str, destination: Path, max_bytes: int) -> None:
|
||||
value = self.remote_files[file_handle]
|
||||
if len(value) > max_bytes:
|
||||
raise AssertionError("fake output exceeded its declared limit")
|
||||
destination.write_bytes(value)
|
||||
|
||||
|
||||
class ArrFullVerticalSliceTests(unittest.TestCase):
|
||||
def test_xml_object_remote_skill_arr_validation_and_atomic_fact_commit(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
store = ManagedObjectStore(
|
||||
FilesystemObjectBackend(root / "private-objects", create=True)
|
||||
)
|
||||
codec = SignedResultCodec(
|
||||
{"fake-key-1": SIGNING_KEY},
|
||||
signing_key_id="fake-key-1",
|
||||
clock=lambda: NOW,
|
||||
)
|
||||
transport = FakeSuperAgentTransport(root, store, codec)
|
||||
runner = ProcessingRunner(
|
||||
transport,
|
||||
InMemoryProcessingLedger(),
|
||||
codec,
|
||||
sleep=lambda _seconds: None,
|
||||
)
|
||||
repository = CapturingRepository()
|
||||
upload = ObjectStoreUploadCoordinator(
|
||||
object_store=store,
|
||||
ingestion_repository=repository,
|
||||
processing_starter=runner,
|
||||
processor_version=core.PROCESSOR_VERSION,
|
||||
rule_set_sha256=core.rule_set_sha256(),
|
||||
)
|
||||
|
||||
receipt = upload.submit("ARR.XML", success_xml().encode("utf-8"))
|
||||
self.assertIsNotNone(transport.signed_result)
|
||||
|
||||
class SourceResolver:
|
||||
def source_for_attempt(self, job_id: str, attempt_no: int):
|
||||
self_outer.assertEqual(job_id, receipt["job_id"])
|
||||
self_outer.assertEqual(attempt_no, 1)
|
||||
return repository.registration.source
|
||||
|
||||
self_outer = self
|
||||
writeback = AgentResultWriteback(
|
||||
runner,
|
||||
SourceResolver(),
|
||||
ProcessingOutputRegistrar(transport, store),
|
||||
IngestionService(
|
||||
DeliveryValidator(store, policy()),
|
||||
repository,
|
||||
),
|
||||
)
|
||||
outcome = writeback.accept(str(transport.signed_result).encode("utf-8"))
|
||||
|
||||
self.assertEqual(outcome.status, "committed")
|
||||
self.assertEqual(outcome.business_date, "2026-07-27")
|
||||
self.assertEqual(
|
||||
repository.inner.current_version_id(date.fromisoformat(outcome.business_date)),
|
||||
outcome.daily_version_id,
|
||||
)
|
||||
records = repository.inner.version_records(int(outcome.daily_version_id))
|
||||
self.assertEqual(len(records), 5)
|
||||
self.assertEqual(sum(1 for row in records if row["outcome"] == "retained"), 3)
|
||||
self.assertEqual(
|
||||
sum(int(row.get("no_of_rooms") or 0) for row in records if row["outcome"] == "retained"),
|
||||
4,
|
||||
)
|
||||
self.assertNotIn("object_key", transport.request.message())
|
||||
self.assertNotIn(str(root), transport.request.message())
|
||||
self.assertTrue(
|
||||
repository.registration.source.object_key.endswith(
|
||||
"/committed/source_xml/source.xml"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user