补齐 Tripo 3D 错误映射的剩余分支测试

- 增补 SDK 参数错误、无状态码 Api、Api 5xx、Sdk 兜底与传输错误的 502 用例,断言只回固定文案、不回显上游报文与 task id
- 增补 map_request_error 的字段口径用例:平台字段路径、provider 字段名与字段未知时不写 field
- 增补 map_pricing_error / map_pricing_store_error / oss_unavailable 的状态码、provider 与 reason 断言
This commit is contained in:
2026-09-23 12:12:26 +08:00
parent 5b488f5a59
commit 83913785ae
@@ -130,6 +130,13 @@ pub(crate) fn oss_unavailable(message: &str) -> AppError {
mod tests {
use super::*;
fn detail(error: &AppError, key: &str) -> Option<serde_json::Value> {
error
.details()
.and_then(|details| details.get(key))
.cloned()
}
#[test]
fn provider_parameter_errors_map_to_bad_request() {
let error = map_provider_error(TripoError::InvalidParameters {
@@ -202,4 +209,138 @@ mod tests {
}
}
}
/// SDK 参数错误与 provider 4xx 同档:都是「调用方参数不对」,不该报 5xx。
#[test]
fn provider_sdk_invalid_argument_maps_to_bad_request() {
let error = map_provider_error(TripoError::SdkInvalidArgument(
"unknown argument: secret-sdk-argument".to_string(),
));
assert_eq!(error.status_code(), StatusCode::BAD_REQUEST);
assert_eq!(
detail(&error, "reason"),
Some(json!("tripo-invalid-parameters"))
);
}
/// 没有可用状态码的 Api 错误、SDK 兜底错误与传输错误一律 502,且只回固定文案:
/// 上游报文与 task id 都不允许出现在响应里。
#[test]
fn provider_unknown_upstream_errors_use_fixed_text() {
let cases = [
(
"api-without-status",
TripoError::Api {
code: 1001,
message: Some("secret-api-message".to_string()),
suggestion: None,
status: None,
},
),
(
"api-5xx",
TripoError::Api {
code: 1001,
message: Some("secret-api-message".to_string()),
suggestion: None,
status: Some(500),
},
),
(
"sdk-fallback",
TripoError::Sdk {
message: "secret-sdk-message".to_string(),
},
),
(
"request",
TripoError::Request {
message: "secret-request-message".to_string(),
status: None,
},
),
];
for (name, case) in cases {
let error = map_provider_error(case);
assert_eq!(error.status_code(), StatusCode::BAD_GATEWAY, "{name}");
assert_eq!(
detail(&error, "reason"),
Some(json!("tripo-upstream-error")),
"{name}"
);
let serialized = serde_json::to_string(error.details().expect("details 必须存在"))
.expect("details 必须可序列化");
for leaked in ["secret-", "task-"] {
assert!(!serialized.contains(leaked), "{name}: {serialized}");
}
}
}
/// 平台规则直接给字段路径;provider 预检失败沿用 provider 字段名;字段未知时不写 field 键。
#[test]
fn request_errors_report_field_only_when_known() {
let platform = map_request_error(Model3dRequestError::InvalidRequest {
field: "prompt",
message: "prompt is required".to_string(),
});
assert_eq!(platform.status_code(), StatusCode::BAD_REQUEST);
assert_eq!(
detail(&platform, "reason"),
Some(json!("invalid-model3d-request"))
);
assert_eq!(detail(&platform, "field"), Some(json!("prompt")));
let provider = map_request_error(Model3dRequestError::Provider(
TripoError::InvalidParameters {
field: Some(platform_tripo::TripoField::GeometryQuality),
reason: platform_tripo::TripoValidationReason::InvalidCombination,
message: "geometry_quality is not supported".to_string(),
},
));
assert_eq!(provider.status_code(), StatusCode::BAD_REQUEST);
assert_eq!(detail(&provider, "field"), Some(json!("geometry_quality")));
let unknown = map_request_error(Model3dRequestError::Provider(TripoError::Request {
message: "transport".to_string(),
status: None,
}));
assert_eq!(unknown.status_code(), StatusCode::BAD_REQUEST);
assert_eq!(detail(&unknown, "field"), None);
}
/// 定价未配置属部署口径:503 且原因可被后台区分,不能混成 500。
#[test]
fn pricing_not_configured_is_service_unavailable() {
let error = map_pricing_error(Model3dPricingError::NotConfigured);
assert_eq!(error.status_code(), StatusCode::SERVICE_UNAVAILABLE);
assert_eq!(
detail(&error, "reason"),
Some(json!("model3d-pricing-unavailable"))
);
}
/// 定价快照读取失败属服务端问题:500,且沿用全仓库定价错误的 provider 口径。
#[test]
fn pricing_store_failures_are_internal_errors() {
let error = map_pricing_store_error(EditorGenerationPricingError::Model3d(
Model3dPricingError::NotConfigured,
));
assert_eq!(error.status_code(), StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(
detail(&error, "provider"),
Some(json!("editor-generation-pricing"))
);
assert_eq!(
detail(&error, "reason"),
Some(json!("model3d-pricing-store-error"))
);
}
#[test]
fn oss_unavailable_is_service_unavailable() {
let error = oss_unavailable("OSS 未完成环境变量配置,无法写入 3D 产物。");
assert_eq!(error.status_code(), StatusCode::SERVICE_UNAVAILABLE);
assert_eq!(detail(&error, "provider"), Some(json!("aliyun-oss")));
assert_eq!(detail(&error, "reason"), Some(json!("oss-unavailable")));
}
}