test(jimeng): cover classify edges and response parsing

Add unit tests for error-code classification boundaries and response
decoding that the existing cases left open:
- 50516/50517 are capability-independent (retryable for image too)
- adjacent copyright codes 50518 (fatal) vs 50519 (retryable)
- full 50520..=50522 service-exception range is fatal
- 50500/50501 fatal-for-image / retryable-for-video via classify directly
- negative codes are fatal (no poll loop)
- base64 image output and absent (not just null) data field decode safely
This commit is contained in:
boos
2026-06-23 14:22:34 +08:00
parent 294ea7ccea
commit bf16139695
+69
View File
@@ -379,4 +379,73 @@ mod tests {
assert_eq!(parsed.code, 50429);
assert!(parsed.data.is_none());
}
#[test]
fn video_audio_moderation_codes_are_capability_independent() {
// 50516/50517 are documented as "video only" but `classify` does not
// gate them by capability — they are retryable for both. Pin that
// actual behavior so a future capability gate is a conscious change.
for code in [50516, 50517] {
assert_eq!(classify(code, JimengCapability::Video), CodeClass::Retryable);
assert_eq!(classify(code, JimengCapability::Image), CodeClass::Retryable);
}
}
#[test]
fn adjacent_copyright_codes_split_by_class() {
// Input copyright pre-moderation (50518) is fatal; output copyright
// post-moderation (50519) is retryable — adjacent codes, opposite class.
assert_eq!(classify(50518, JimengCapability::Image), CodeClass::Fatal);
assert_eq!(classify(50519, JimengCapability::Image), CodeClass::Retryable);
}
#[test]
fn moderation_service_exception_range_is_fatal() {
// The whole 50520..=50522 upstream-exception range is fatal for both
// capabilities (covers the inclusive upper bound 50522 explicitly).
for code in [50520, 50521, 50522] {
assert_eq!(classify(code, JimengCapability::Image), CodeClass::Fatal);
assert_eq!(classify(code, JimengCapability::Video), CodeClass::Fatal);
}
}
#[test]
fn internal_error_is_fatal_for_image_via_classify() {
// Complements the `retryable()` helper test with a direct CodeClass
// assertion: 50500/50501 are Fatal for image, Retryable for video.
for code in [50500, 50501] {
assert_eq!(classify(code, JimengCapability::Image), CodeClass::Fatal);
assert_eq!(classify(code, JimengCapability::Video), CodeClass::Retryable);
}
}
#[test]
fn negative_code_is_fatal() {
// Defensive: out-of-domain codes never loop the poller.
assert_eq!(classify(-1, JimengCapability::Image), CodeClass::Fatal);
assert!(!retryable(-1, JimengCapability::Video));
}
#[test]
fn done_image_response_parses_base64() {
// Default GetResult output (no return_url) carries base64, not urls.
let raw = r#"{"code":10000,"message":"Success","data":{"status":"done","binary_data_base64":["aGVsbG8="]}}"#;
let parsed: GetResultResponse = serde_json::from_str(raw).unwrap();
assert_eq!(parsed.code, CODE_SUCCESS);
assert_eq!(parsed.task_status(), TaskStatus::Done);
let data = parsed.data.unwrap();
assert!(data.image_urls.is_none());
assert_eq!(data.binary_data_base64.unwrap(), vec!["aGVsbG8=".to_string()]);
}
#[test]
fn response_with_absent_data_field_parses_to_none() {
// `data` omitted entirely (not just null) must still decode (serde
// default) and report Unknown status without panicking.
let submit: SubmitResponse = serde_json::from_str(r#"{"code":50429}"#).unwrap();
assert!(submit.data.is_none());
let result: GetResultResponse = serde_json::from_str(r#"{"code":50429}"#).unwrap();
assert!(result.data.is_none());
assert_eq!(result.task_status(), TaskStatus::Unknown);
}
}