test: 补齐规格纠偏安全场景 (#258)

This commit is contained in:
chengma
2026-08-17 18:37:39 +08:00
parent be900b2411
commit 29cf66ce96
4 changed files with 138 additions and 11 deletions
@@ -220,6 +220,18 @@ func applyAPISpecContractMutation(mutation string, taskID *string, request map[s
case "attempt_id_too_long": case "attempt_id_too_long":
request["attempt_id"] = strings.Repeat("a", 192) request["attempt_id"] = strings.Repeat("a", 192)
*key = apiSpecIdempotencyKey(*taskID, request) *key = apiSpecIdempotencyKey(*taskID, request)
case "candidate_count_101":
candidates := make([]any, 0, 101)
for index := 1; index <= 101; index++ {
value := "测试尺码" + strconv.Itoa(index)
candidates = append(candidates, map[string]any{
"candidate_id": "c" + strconv.Itoa(index),
"raw_text": value,
"options": map[string]any{"color": "测试黑", "size": value},
})
}
request["candidates"] = candidates
apiSpecRefreshHashes(*taskID, request, key)
case "body_too_large": case "body_too_large":
request["future_padding"] = strings.Repeat("x", 64<<10) request["future_padding"] = strings.Repeat("x", 64<<10)
case "task_not_found": case "task_not_found":
@@ -46,12 +46,13 @@ type purchaseSpecContractCase struct {
} }
type purchaseSpecContractModel struct { type purchaseSpecContractModel struct {
Conclusion string `json:"conclusion"` Conclusion string `json:"conclusion"`
CandidateID string `json:"candidate_id"` CandidateID string `json:"candidate_id"`
ConfidenceBPS int `json:"confidence_bps"` ConfidenceBPS int `json:"confidence_bps"`
Reason string `json:"reason"` Reason string `json:"reason"`
MissingDimensions []string `json:"missing_dimensions"` ConflictDimensions []string `json:"conflict_dimensions"`
Error string `json:"error"` MissingDimensions []string `json:"missing_dimensions"`
Error string `json:"error"`
} }
type purchaseSpecContractExpected struct { type purchaseSpecContractExpected struct {
@@ -230,11 +231,12 @@ func contractAIClientForCase(testCase purchaseSpecContractCase) *contractAIModel
return client return client
} }
client.response = AIModelMatchResponse{ client.response = AIModelMatchResponse{
Conclusion: testCase.Model.Conclusion, Conclusion: testCase.Model.Conclusion,
CandidateID: testCase.Model.CandidateID, CandidateID: testCase.Model.CandidateID,
ConfidenceBPS: testCase.Model.ConfidenceBPS, ConfidenceBPS: testCase.Model.ConfidenceBPS,
Reason: testCase.Model.Reason, Reason: testCase.Model.Reason,
MissingDimensions: append([]string(nil), testCase.Model.MissingDimensions...), ConflictDimensions: append([]string(nil), testCase.Model.ConflictDimensions...),
MissingDimensions: append([]string(nil), testCase.Model.MissingDimensions...),
} }
return client return client
} }
@@ -375,6 +375,20 @@ class PurchaseSpecResolutionContractVectorTest(unittest.TestCase):
elif mutation == "attempt_id_too_long": elif mutation == "attempt_id_too_long":
request["attempt_id"] = "a" * 192 request["attempt_id"] = "a" * 192
key = _idempotency_key(task_id, request) key = _idempotency_key(task_id, request)
elif mutation == "candidate_count_101":
request["candidates"] = [
{
"candidate_id": f"c{index}",
"raw_text": f"测试尺码{index}",
"options": {
"color": "测试黑",
"size": f"测试尺码{index}",
},
}
for index in range(1, 102)
]
request["candidate_snapshot_hash"] = _snapshot_hash(request)
key = _idempotency_key(task_id, request)
elif mutation == "body_too_large": elif mutation == "body_too_large":
request["future_padding"] = "x" * (64 * 1024) request["future_padding"] = "x" * (64 * 1024)
elif mutation == "task_not_found": elif mutation == "task_not_found":
+99
View File
@@ -30,6 +30,40 @@
"model_calls": 0 "model_calls": 0
} }
}, },
{
"name": "rule_traditional_simplified",
"task_id": "cg-contract-traditional",
"target_size": "藍 色 / XL 碼",
"candidates": ["蓝色/xl码"],
"candidate_snapshot_hash": "5433787fa5bc7cae41939504a221bb59cee26639e683f89506e524b0bf318202",
"idempotency_key": "spec-resolution-v1:f1c239574dcbf5c8bbb12a71f21dadf874d5d0b544e4fa18b957f896295801e4",
"mode": "rule",
"model": null,
"expected": {
"outcome": "matched",
"source": "rule",
"candidate_id": "c1",
"confidence_bps": 10000,
"model_calls": 0
}
},
{
"name": "rule_weight_range_unique",
"task_id": "cg-contract-range",
"target_size": "45-55公斤",
"candidates": ["70-90斤", "90-110斤", "110-130斤"],
"candidate_snapshot_hash": "1b81a16f0c1efcd457311e0f229859ad7df03453f9f9ee5946a9e78f353e95b6",
"idempotency_key": "spec-resolution-v1:e70666cb6141d09059c4bf9de0e755185fc1b5bd72e93c3b399e1c46507f63b6",
"mode": "rule",
"model": null,
"expected": {
"outcome": "matched",
"source": "rule",
"candidate_id": "c2",
"confidence_bps": 10000,
"model_calls": 0
}
},
{ {
"name": "rule_overlap_uncertain", "name": "rule_overlap_uncertain",
"task_id": "cg-contract-overlap", "task_id": "cg-contract-overlap",
@@ -136,6 +170,70 @@
"model_calls": 1 "model_calls": 1
} }
}, },
{
"name": "ai_uncertain",
"task_id": "cg-contract-ai-uncertain",
"target_size": "L码",
"candidates": ["M码", "XL码"],
"candidate_snapshot_hash": "f634515c8a7a0ebd688fd926520405051e12c622c87a4783a05936099ecfb2fa",
"idempotency_key": "spec-resolution-v1:7b531267d5ba16ed9d81a7d8d7e9e8e4553776bd35d3401e2b260609c6b022fb",
"mode": "ai",
"model": {
"conclusion": "uncertain",
"confidence_bps": 8500,
"reason": "测试信息不足"
},
"expected": {
"outcome": "uncertain",
"source": "ai",
"candidate_id": null,
"confidence_bps": 8500,
"model_calls": 1
}
},
{
"name": "ai_conflict",
"task_id": "cg-contract-ai-conflict",
"target_size": "L码",
"candidates": ["M码", "XL码"],
"candidate_snapshot_hash": "f634515c8a7a0ebd688fd926520405051e12c622c87a4783a05936099ecfb2fa",
"idempotency_key": "spec-resolution-v1:4d6581611767a1a56a305f1b769bb651be80d0113af1827c2fa8c66e72ea14b0",
"mode": "ai",
"model": {
"conclusion": "conflict",
"confidence_bps": 9500,
"reason": "测试规格维度冲突",
"conflict_dimensions": ["尺码"]
},
"expected": {
"outcome": "rejected",
"source": "ai",
"candidate_id": null,
"confidence_bps": 9500,
"model_calls": 1
}
},
{
"name": "ai_invalid_format",
"task_id": "cg-contract-ai-invalid",
"target_size": "L码",
"candidates": ["M码", "XL码"],
"candidate_snapshot_hash": "f634515c8a7a0ebd688fd926520405051e12c622c87a4783a05936099ecfb2fa",
"idempotency_key": "spec-resolution-v1:5a9dcc28c6dc6f8d877a93932533ca5f64e6e5d10153acf7ddb63cb6e06428de",
"mode": "ai",
"model": {
"conclusion": "other",
"confidence_bps": 9200,
"reason": "测试非法结论"
},
"expected": {
"outcome": "failed",
"source": "ai",
"candidate_id": null,
"confidence_bps": null,
"model_calls": 1
}
},
{ {
"name": "ai_timeout", "name": "ai_timeout",
"task_id": "cg-contract-ai-timeout", "task_id": "cg-contract-ai-timeout",
@@ -162,6 +260,7 @@
{"name": "target_size_not_claimed", "base_case": "rule_weight_unique", "mutation": "target_size_not_claimed", "expected_http_status": 422, "expected_error_code": "INVALID_SPEC_RESOLUTION_REQUEST"}, {"name": "target_size_not_claimed", "base_case": "rule_weight_unique", "mutation": "target_size_not_claimed", "expected_http_status": 422, "expected_error_code": "INVALID_SPEC_RESOLUTION_REQUEST"},
{"name": "observed_at_without_timezone", "base_case": "rule_weight_unique", "mutation": "observed_at_without_timezone", "expected_http_status": 422, "expected_error_code": "INVALID_SPEC_RESOLUTION_REQUEST"}, {"name": "observed_at_without_timezone", "base_case": "rule_weight_unique", "mutation": "observed_at_without_timezone", "expected_http_status": 422, "expected_error_code": "INVALID_SPEC_RESOLUTION_REQUEST"},
{"name": "attempt_id_too_long", "base_case": "rule_weight_unique", "mutation": "attempt_id_too_long", "expected_http_status": 422, "expected_error_code": "INVALID_SPEC_RESOLUTION_REQUEST"}, {"name": "attempt_id_too_long", "base_case": "rule_weight_unique", "mutation": "attempt_id_too_long", "expected_http_status": 422, "expected_error_code": "INVALID_SPEC_RESOLUTION_REQUEST"},
{"name": "candidate_count_101", "base_case": "rule_weight_unique", "mutation": "candidate_count_101", "expected_http_status": 422, "expected_error_code": "INVALID_SPEC_RESOLUTION_REQUEST"},
{"name": "body_too_large", "base_case": "rule_weight_unique", "mutation": "body_too_large", "expected_http_status": 400, "expected_error_code": "INVALID_BODY"}, {"name": "body_too_large", "base_case": "rule_weight_unique", "mutation": "body_too_large", "expected_http_status": 400, "expected_error_code": "INVALID_BODY"},
{"name": "task_not_found", "base_case": "rule_weight_unique", "mutation": "task_not_found", "expected_http_status": 404, "expected_error_code": "TASK_NOT_FOUND"}, {"name": "task_not_found", "base_case": "rule_weight_unique", "mutation": "task_not_found", "expected_http_status": 404, "expected_error_code": "TASK_NOT_FOUND"},
{"name": "task_not_purchase", "base_case": "rule_weight_unique", "mutation": "task_not_purchase", "expected_http_status": 422, "expected_error_code": "TASK_NOT_PURCHASE"}, {"name": "task_not_purchase", "base_case": "rule_weight_unique", "mutation": "task_not_purchase", "expected_http_status": 422, "expected_error_code": "TASK_NOT_PURCHASE"},