feat: 安全应用采购规格解析结果 (#257)

This commit is contained in:
chengma
2026-08-17 17:43:25 +08:00
parent 484ce4e8ec
commit afc1cdf719
19 changed files with 1669 additions and 24 deletions
+101
View File
@@ -206,6 +206,107 @@ class HttpAdminGatewayTest(unittest.TestCase):
self.assertTrue(receipt.accepted)
self.assertTrue(receipt.result_id.startswith("legacy-failure-"))
def test_resolve_purchase_spec_posts_once_and_parses_whitelisted_match(self):
response = {
"schema_version": 1,
"resolution_id": "psr-001",
"outcome": "matched",
"source": "rule",
"candidate_snapshot_hash": "a" * 64,
"match": {
"candidate_id": "c1",
"raw_text": "120斤",
"options": {"color": "黑色", "size": "120斤"},
},
"confidence_bps": 10000,
"reason": "唯一等价",
"resolved_at": "2026-08-17T08:00:01Z",
"future_field": "ignored",
}
opener = RecordingOpener(FakeResponse(200, response))
gateway = HttpAdminGateway(
opener=opener, client_id="CLIENT-001", timeout_seconds=5
)
receipt = gateway.resolve_purchase_spec(
"PUR-001", "spec-resolution-v1:key", {"schema_version": 1}
)
self.assertEqual(receipt.resolution_id, "psr-001")
self.assertEqual(receipt.match.raw_text, "120斤")
self.assertTrue(opener.request.full_url.endswith(
"/api/v1/client/tasks/PUR-001/spec-resolution"
))
headers = {
key.lower(): value for key, value in opener.request.header_items()
}
self.assertEqual(
headers["idempotency-key"], "spec-resolution-v1:key"
)
self.assertEqual(opener.timeout, 5)
def test_resolve_purchase_spec_accepts_all_nonmatched_business_outcomes(self):
for outcome in ("uncertain", "rejected", "failed"):
with self.subTest(outcome=outcome):
gateway = HttpAdminGateway(
opener=RecordingOpener(
FakeResponse(
200,
{
"schema_version": 1,
"resolution_id": f"psr-{outcome}",
"outcome": outcome,
"source": None,
"candidate_snapshot_hash": "b" * 64,
"match": None,
"confidence_bps": None,
"reason": "没有唯一安全候选",
"resolved_at": "2026-08-17T08:00:01Z",
},
)
),
client_id="CLIENT-001",
)
receipt = gateway.resolve_purchase_spec(
"PUR-001", "spec-resolution-v1:key", {}
)
self.assertEqual(receipt.outcome, outcome)
self.assertIsNone(receipt.match)
def test_resolve_purchase_spec_rejects_match_on_nonmatched_outcome(self):
gateway = HttpAdminGateway(
opener=RecordingOpener(
FakeResponse(
200,
{
"schema_version": 1,
"resolution_id": "psr-invalid",
"outcome": "uncertain",
"source": "ai",
"candidate_snapshot_hash": "c" * 64,
"match": {
"candidate_id": "c1",
"raw_text": "120斤",
"options": {"color": "黑色", "size": "120斤"},
},
"confidence_bps": 5000,
"reason": "响应自相矛盾",
"resolved_at": "2026-08-17T08:00:01Z",
},
)
),
client_id="CLIENT-001",
)
with self.assertRaises(AdminGatewayError) as raised:
gateway.resolve_purchase_spec(
"PUR-001", "spec-resolution-v1:key", {}
)
self.assertEqual(raised.exception.code, "ADMIN_INVALID_RESPONSE")
def test_malformed_2xx_submission_is_retryable_and_not_called_rejected(self):
gateway = HttpAdminGateway(
opener=RecordingOpener(FakeResponse(200, {"accepted": True})),