补齐 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:
@@ -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")));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user