255 lines
9.7 KiB
Python
255 lines
9.7 KiB
Python
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.legacy_agent_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()
|