Files
wyndham-ARR/tests/test_arr_web_job_trace.py

237 lines
9.4 KiB
Python

from __future__ import annotations
import json
import unittest
from datetime import datetime, timedelta, timezone
from arr_web.job_trace import build_job_trace
class JobTraceTests(unittest.TestCase):
def setUp(self) -> None:
self.started = datetime(2026, 7, 29, 11, 0, tzinfo=timezone.utc)
def test_programmatic_success_exposes_persisted_safe_trace(self) -> None:
finished = self.started + timedelta(seconds=8)
trace = build_job_trace(
{
"job_id": "arrjob-trace-001",
"run_status": "accepted",
"public_status": "succeeded",
"delivery_mode": "artifact_callback",
"business_date": "2026-07-29",
"created_at": self.started,
"updated_at": finished,
"validated_at": finished,
"finished_at": finished,
"filename": "source.xml",
"byte_size": 2048,
"source_sha256": "a" * 64,
"requested_processor_version": "3.0.0",
"delivered_processor_version": "3.0.0",
"object_key": "must/not/escape",
},
attempts=[
{
"attempt_no": 1,
"attempt_status": "succeeded",
"remote_run_id": None,
"created_at": self.started + timedelta(seconds=1),
"started_at": self.started + timedelta(seconds=2),
"finished_at": finished,
}
],
deliveries=[
{
"delivery_key": "local-arrjob-trace-001-a1",
"attempt_no": 1,
"delivery_status": "committed",
"result_status": "success",
"processor_version": "3.0.0",
"business_date": "2026-07-29",
"daily_version_id": 41,
"received_at": self.started + timedelta(seconds=5),
"validated_at": self.started + timedelta(seconds=6),
"committed_at": finished,
}
],
versions=[
{
"daily_version_id": 41,
"business_date": "2026-07-29",
"version_no": 2,
"version_status": "active",
"result_delivery_mode": "artifact_callback",
"source_rows": 20,
"retained_rows": 17,
"excluded_rate_code_rows": 1,
"duplicate_rows": 2,
"validation_failed_rows": 0,
"price_unmatched_rows": 0,
"created_at": self.started + timedelta(seconds=7),
"validated_at": finished,
"activated_at": finished,
}
],
outbox_events=[
{
"event_key": "processing-run:1:accepted",
"event_type": "arr.daily_version_committed",
"publish_status": "pending",
"publish_attempts": 0,
"created_at": finished,
"available_at": finished,
"payload": {"must": "not escape"},
}
],
refreshed_at=finished + timedelta(seconds=1),
)
self.assertEqual(trace["trace_version"], "arr-job-trace-3")
self.assertEqual(trace["job"]["status"], "succeeded")
self.assertEqual(trace["job"]["current_stage"], "downstream")
self.assertFalse(trace["job"]["active"])
self.assertIsNone(trace["job"]["remote_run_id"])
self.assertEqual(trace["job"]["execution_scope"], "arr_runtime")
self.assertEqual(trace["job"]["processor_mode"], "fixed_processor")
self.assertEqual(trace["job"]["remote_dispatch"], "none")
self.assertEqual(trace["job"]["version_no"], 2)
self.assertEqual(trace["job"]["output_rows"], 17)
codes = [item["code"] for item in trace["logs"]]
for expected in (
"UPLOAD_REGISTERED",
"ATTEMPT_QUEUED",
"PROCESSOR_STARTED",
"ARTIFACT_RESULT_RECEIVED",
"ARTIFACT_RESULT_VALIDATED",
"ARTIFACT_RESULT_COMMITTED",
"FINANCE_VERSION_VALIDATED",
"FINANCE_VERSION_ACTIVATED",
"DOWNSTREAM_EVENT_QUEUED",
"JOB_SUCCEEDED",
):
self.assertIn(expected, codes)
self.assertEqual(trace["evidence"]["artifact_deliveries"], 1)
rendered = json.dumps(trace, ensure_ascii=False)
for private_value in (
"must/not/escape",
"must not escape",
'"payload"',
'"object_key"',
"submission_grant",
"Agent",
"MCP",
):
self.assertNotIn(private_value, rendered)
self.assertNotIn("本地处理", rendered)
self.assertIn("ARR 固定处理器输出", rendered)
self.assertIn("Finance 数据库提交已完成", rendered)
def test_runtime_failure_is_located_at_processor_boundary(self) -> None:
finished = self.started + timedelta(minutes=4)
trace = build_job_trace(
{
"job_id": "arrjob-timeout-001",
"run_status": "failed",
"public_status": "failed",
"delivery_mode": "artifact_callback",
"failure_code": "PROCESSOR_TIMEOUT",
"failure_message": "programmatic processing failed",
"created_at": self.started,
"updated_at": finished,
"finished_at": finished,
"filename": "source.xml",
"byte_size": 99,
"source_sha256": "b" * 64,
},
attempts=[
{
"attempt_no": 1,
"attempt_status": "failed",
"remote_run_id": None,
"failure_code": "PROCESSOR_TIMEOUT",
"failure_message": "programmatic processing failed",
"created_at": self.started + timedelta(seconds=1),
"started_at": self.started + timedelta(seconds=2),
"finished_at": finished,
}
],
refreshed_at=finished,
)
self.assertEqual(trace["job"]["current_stage"], "processor")
self.assertEqual(trace["job"]["failure"]["code"], "PROCESSOR_TIMEOUT")
errors = [item for item in trace["logs"] if item["level"] == "error"]
self.assertGreaterEqual(len(errors), 2)
self.assertTrue(all(item["stage"] == "processor" for item in errors))
def test_legacy_direct_mcp_trace_exposes_remote_semantics(self) -> None:
finished = self.started + timedelta(minutes=4)
trace = build_job_trace(
{
"job_id": "arrjob-legacy-001",
"run_status": "failed",
"public_status": "failed",
"delivery_mode": "direct_mcp",
"failure_code": "REMOTE_FAILED",
"failure_message": "remote processor failed",
"created_at": self.started,
"updated_at": finished,
"finished_at": finished,
"filename": "source.xml",
"byte_size": 99,
"source_sha256": "d" * 64,
},
attempts=[
{
"attempt_no": 1,
"attempt_status": "failed",
"remote_run_id": "remote-run-123",
"failure_code": "REMOTE_FAILED",
"failure_message": "remote processor failed",
"created_at": self.started + timedelta(seconds=1),
"started_at": self.started + timedelta(seconds=2),
"finished_at": finished,
}
],
refreshed_at=finished,
)
self.assertEqual(trace["job"]["execution_scope"], "remote_agent")
self.assertEqual(trace["job"]["processor_mode"], "legacy_direct_mcp")
self.assertEqual(trace["job"]["remote_dispatch"], "mcp")
self.assertEqual(trace["job"]["remote_run_id"], "remote-run-123")
def test_active_run_ends_with_current_programmatic_state(self) -> None:
trace = build_job_trace(
{
"job_id": "arrjob-running-001",
"run_status": "running",
"public_status": "running",
"delivery_mode": "artifact_callback",
"created_at": self.started,
"updated_at": self.started + timedelta(seconds=4),
"filename": "source.xml",
"byte_size": 128,
"source_sha256": "c" * 64,
},
attempts=[
{
"attempt_no": 1,
"attempt_status": "running",
"remote_run_id": None,
"created_at": self.started + timedelta(seconds=1),
"started_at": self.started + timedelta(seconds=2),
}
],
)
self.assertTrue(trace["job"]["active"])
self.assertEqual(trace["job"]["current_stage"], "processor")
self.assertEqual(trace["logs"][-1]["code"], "CURRENT_RUNNING")
if __name__ == "__main__":
unittest.main(verbosity=2)