4762358f54
CI / ai-gates (push) Failing after 29s
`api_key=volcengine.api_key` 这类**依赖注入传参**被 KEY-ASSIGN 判为疑似凭证。 这是规则的真缺口而非代码问题:行里没有任何秘密,而做 DI 的代码到处都是这种 写法——绕过一次就得在每个调用点绕过,那等于把规则废掉。故改规则。 新增 ALLOW_REFERENCE,刻意收得很紧以免开后门: - 值必须是**未加引号**的小写起首标识符引用(真密钥要么带引号,要么不是标识符形状) - 只允许**恰好一个点且随即行尾** - 连字符不在字符集内 对抗性夹具验证(三真三假): - 拦下 `api_key = "sk-T3Blbk…"`(OpenAI 形态) - 拦下 `secret_key: "AKID…"`(腾讯云形态) - 拦下 `auth_token=eyJ….eyJ….Sfl…`(**三段式 JWT——两个点,故不落进放行**) - 放行 `api_key=lowercase.reference` / `api_key = settings.api_key` / `api_token: config.authToken` 原 5 形态泄漏夹具复验仍 8 条命中,一条不少。 另更正口径:脚本头与 git-workflow.md 仍写「三仓同构」,实际已是四仓—— patbond-ai 是唯一持有 AI 服务商凭证的仓,漏同步它风险最大。四仓 md5 已一致 (4945940093b4b8413b7d3eec8bf6a5ec),四仓 --all 全绿。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
177 lines
7.1 KiB
Python
177 lines
7.1 KiB
Python
"""火山方舟错误分类的测试。
|
|
|
|
每个用例对应官方错误码表里的一行或一类。重点在三处**如果搞错只会在生产暴露**
|
|
的地方:`QuotaExceeded` 的一码两义、内容安全类必须不可重试、未知码必须偏向
|
|
可重试而不是直接判死。
|
|
"""
|
|
|
|
from app.domain.generation import Retry, UserFacing
|
|
from app.providers.volcengine.errors import classify
|
|
|
|
|
|
class TestQuotaExceededIsTwoDifferentThings:
|
|
"""同一个 code,两种完全不同的处置。只看 code 必然错一半。"""
|
|
|
|
def test_queue_backlog_is_a_seconds_long_wait(self):
|
|
# 官方原文:「当前账号处于排队中状态的任务数已超过限制,请稍后重试。」
|
|
result = classify(
|
|
"QuotaExceeded", "The request has exceeded the quota. Request ID: x."
|
|
)
|
|
assert result.retry is Retry.BACKOFF
|
|
assert result.user_facing is UserFacing.RATE_LIMITED
|
|
|
|
def test_queue_backlog_recognised_from_chinese_message(self):
|
|
result = classify("QuotaExceeded", "当前账号处于排队中状态的任务数已超过限制")
|
|
assert result.retry is Retry.BACKOFF
|
|
assert result.user_facing is UserFacing.RATE_LIMITED
|
|
|
|
def test_free_trial_exhausted_needs_billing_action(self):
|
|
# 官方原文:「免费试用额度已消耗完毕,请前往控制台开通对应模型服务。」
|
|
result = classify(
|
|
"QuotaExceeded",
|
|
"Your account [x] has exhausted its free trial quota for the [y] model.",
|
|
)
|
|
assert result.retry is Retry.QUOTA
|
|
assert result.user_facing is UserFacing.QUOTA_EXHAUSTED
|
|
|
|
def test_periodic_limit_exhausted_needs_to_wait_for_reset(self):
|
|
result = classify(
|
|
"QuotaExceeded",
|
|
"You have exceeded the 5-hour/weekly/monthly usage quota. It will reset at T.",
|
|
)
|
|
assert result.retry is Retry.QUOTA
|
|
assert result.user_facing is UserFacing.QUOTA_EXHAUSTED
|
|
|
|
|
|
class TestContentRejectionMustNotBeRetried:
|
|
"""同样的输入必然同样被拒。重试只会白烧额度再失败。"""
|
|
|
|
def test_input_image_sensitive(self):
|
|
result = classify("InputImageSensitiveContentDetected")
|
|
assert result.retry is Retry.NEVER
|
|
assert result.user_facing is UserFacing.CONTENT_REJECTED
|
|
|
|
def test_output_video_sensitive(self):
|
|
result = classify("OutputVideoSensitiveContentDetected")
|
|
assert result.retry is Retry.NEVER
|
|
assert result.user_facing is UserFacing.CONTENT_REJECTED
|
|
|
|
def test_policy_violation_subcode(self):
|
|
result = classify("InputVideoSensitiveContentDetected.PolicyViolation")
|
|
assert result.retry is Retry.NEVER
|
|
|
|
def test_real_person_subcode(self):
|
|
result = classify("InputImageSensitiveContentDetected.PrivacyInformation")
|
|
assert result.retry is Retry.NEVER
|
|
|
|
def test_risk_detection_product_codes(self):
|
|
assert classify("InputTextRiskDetection").retry is Retry.NEVER
|
|
assert classify("OutputImageRiskDetection").retry is Retry.NEVER
|
|
|
|
def test_but_the_detector_itself_failing_is_retryable(self):
|
|
# 风险识别产品请求失败 ≠ 内容真的违规。
|
|
result = classify("ContentSecurityDetectionError", "Internal error.")
|
|
assert result.retry is Retry.BACKOFF
|
|
assert result.user_facing is UserFacing.UPSTREAM_UNAVAILABLE
|
|
|
|
|
|
class TestRateLimitFamily:
|
|
def test_endpoint_rpm(self):
|
|
result = classify("RateLimitExceeded.EndpointRPMExceeded")
|
|
assert result.retry is Retry.BACKOFF
|
|
assert result.user_facing is UserFacing.RATE_LIMITED
|
|
|
|
def test_model_account_tpm(self):
|
|
assert classify("ModelAccountTpmRateLimitExceeded").retry is Retry.BACKOFF
|
|
|
|
def test_server_overloaded(self):
|
|
assert classify("ServerOverloaded").retry is Retry.BACKOFF
|
|
|
|
def test_request_burst_too_fast(self):
|
|
# doubao-seed-2.0 之后版本的突增流量保护码。
|
|
assert classify("RequestBurstTooFast").retry is Retry.BACKOFF
|
|
|
|
def test_images_per_minute(self):
|
|
assert classify("ModelAccountIpmRateLimitExceeded").retry is Retry.BACKOFF
|
|
|
|
|
|
class TestQuotaAndBillingFamily:
|
|
def test_inference_limit_set_by_account(self):
|
|
result = classify("SetLimitExceeded")
|
|
assert result.retry is Retry.QUOTA
|
|
assert result.user_facing is UserFacing.QUOTA_EXHAUSTED
|
|
|
|
def test_concurrency_cap_needs_topup(self):
|
|
assert classify("InflightBatchsizeExceeded").retry is Retry.QUOTA
|
|
|
|
def test_account_overdue_is_403_but_same_user_action(self):
|
|
# HTTP 状态码不同(403 而非 429),但用户要做的事一样:去充值。
|
|
result = classify("AccountOverdueError")
|
|
assert result.retry is Retry.QUOTA
|
|
assert result.user_facing is UserFacing.QUOTA_EXHAUSTED
|
|
|
|
def test_model_not_activated(self):
|
|
assert classify("ModelNotOpen").retry is Retry.QUOTA
|
|
|
|
|
|
class TestOurOwnFaults:
|
|
"""重试无意义,需要人改代码或改配置。"""
|
|
|
|
def test_bad_api_key(self):
|
|
result = classify("AuthenticationError")
|
|
assert result.retry is Retry.NEVER
|
|
assert result.user_facing is UserFacing.SERVER_ERROR
|
|
|
|
def test_unknown_model_id(self):
|
|
assert classify("InvalidEndpointOrModel.NotFound").retry is Retry.NEVER
|
|
|
|
def test_invalid_parameter(self):
|
|
assert classify("InvalidParameter").retry is Retry.NEVER
|
|
|
|
def test_task_type_constraint_is_our_parameter_problem(self):
|
|
# Seedance 2.5 全模态任务的参数与模型判定的任务类型不兼容。
|
|
assert classify("InvalidParameter.TaskTypeConstraint").retry is Retry.NEVER
|
|
|
|
def test_task_type_mismatch(self):
|
|
assert classify("InvalidParameter.TaskTypeMismatch").retry is Retry.NEVER
|
|
|
|
def test_missing_parameter(self):
|
|
assert classify("MissingParameter").retry is Retry.NEVER
|
|
|
|
def test_payload_too_large(self):
|
|
assert classify("RequestTooLarge").retry is Retry.NEVER
|
|
|
|
|
|
class TestUpstreamErrors:
|
|
def test_internal_service_error_is_retryable(self):
|
|
result = classify("InternalServiceError")
|
|
assert result.retry is Retry.BACKOFF
|
|
assert result.user_facing is UserFacing.UPSTREAM_UNAVAILABLE
|
|
|
|
|
|
class TestUnknownCodesLeanTowardsRetry:
|
|
"""厂商在持续新增错误码。把未知码判死会让一次抖动变成用户看到的失败;
|
|
重试预算本身有上限,最坏只是多试两次。"""
|
|
|
|
def test_unknown_code(self):
|
|
result = classify("SomeBrandNewCodeVolcengineAddedLastWeek")
|
|
assert result.retry is Retry.BACKOFF
|
|
assert result.user_facing is UserFacing.UPSTREAM_UNAVAILABLE
|
|
|
|
def test_missing_code(self):
|
|
assert classify(None).retry is Retry.BACKOFF
|
|
assert classify("").retry is Retry.BACKOFF
|
|
|
|
|
|
class TestProviderCodeIsAlwaysPreserved:
|
|
"""原始码绝不丢弃——排查时它是唯一线索。"""
|
|
|
|
def test_known_code(self):
|
|
assert classify("ServerOverloaded").provider_code == "ServerOverloaded"
|
|
|
|
def test_unknown_code(self):
|
|
assert classify("WeirdCode").provider_code == "WeirdCode"
|
|
|
|
def test_whitespace_is_trimmed(self):
|
|
assert classify(" ServerOverloaded ").provider_code == "ServerOverloaded"
|