This commit is contained in:
Brandon Thomas committed 2026-06-01 18:06:31 -04:00
1 parent ef41b77ce8
commit afd831ee2c
2 files changed
+218

No files matched your search

@@ -210,5 +210,102 @@ mod tests {
);
}
/// Parity vs the legacy
/// `requests_old/http/video/image/http_kling_v2p6_pro_image_to_video.rs`.
///
/// The legacy has no cost calculator, so cost parity is N/A. The wire
/// shape *intentionally diverges*: fal renamed the image field from
/// `image_url` (legacy) to `start_image_url` (current docs), and the
/// legacy gained no concept of `voice_ids` or `end_image_url`. These
/// tests document the differences and pin the endpoint URL, so we'll
/// notice if fal ever flips back or further changes the schema.
mod legacy_parity {
use super::*;
use crate::requests_old::http::video::image::http_kling_v2p6_pro_image_to_video::{
kling_v2p6_pro_image_to_video, KlingV2p6ProImageToVideoInput,
};
/// Same `fal-ai/kling-video/v2.6/pro/image-to-video` path.
#[test]
fn endpoint_path_matches_legacy() {
let legacy = kling_v2p6_pro_image_to_video(KlingV2p6ProImageToVideoInput {
prompt: "p".to_string(),
image_url: "https://example.com/x.png".to_string(),
..Default::default()
});
assert_eq!(
Kling2p6ProImageToVideoRequest::ENDPOINT,
legacy.endpoint,
);
}
/// The image-URL field was renamed by fal: legacy `image_url` →
/// current `start_image_url`. This test pins the new module to the
/// current name and confirms the legacy field name is *not* emitted.
#[test]
fn new_module_emits_start_image_url_not_image_url() {
let new = Kling2p6ProImageToVideoRequest {
prompt: "p".to_string(),
start_image_url: "https://example.com/x.png".to_string(),
end_image_url: None,
duration: None,
negative_prompt: None,
generate_audio: None,
voice_ids: None,
};
let json = serde_json::to_value(new.to_raw_request().unwrap()).unwrap();
assert!(json.get("start_image_url").is_some(), "missing start_image_url: {json}");
assert!(json.get("image_url").is_none(), "unexpected legacy image_url: {json}");
}
/// Legacy struct still emits the *old* `image_url` field name —
/// asserting this here flags any future schema drift in the legacy
/// archive (it shouldn't change; if it does we want to know).
#[test]
fn legacy_module_still_emits_image_url() {
let legacy = KlingV2p6ProImageToVideoInput {
prompt: "p".to_string(),
image_url: "https://example.com/x.png".to_string(),
..Default::default()
};
let json = serde_json::to_value(&legacy).unwrap();
assert!(json.get("image_url").is_some(), "missing image_url: {json}");
assert!(json.get("start_image_url").is_none(), "unexpected start_image_url: {json}");
}
/// On fields that *did* survive the rename (prompt, duration,
/// negative_prompt, generate_audio), wire JSON must match between
/// new and legacy when populated with the same values.
#[test]
fn surviving_fields_match_legacy_wire_shape() {
let new = Kling2p6ProImageToVideoRequest {
prompt: "shared prompt".to_string(),
start_image_url: "https://example.com/x.png".to_string(),
end_image_url: None,
duration: Some(Kling2p6ProImageToVideoDuration::TenSeconds),
negative_prompt: Some("blurry".to_string()),
generate_audio: Some(false),
voice_ids: None,
};
let mut new_json = serde_json::to_value(new.to_raw_request().unwrap()).unwrap();
let legacy = KlingV2p6ProImageToVideoInput {
prompt: "shared prompt".to_string(),
image_url: "https://example.com/x.png".to_string(),
duration: Some("10".to_string()),
negative_prompt: Some("blurry".to_string()),
generate_audio: Some(false),
};
let mut legacy_json = serde_json::to_value(&legacy).unwrap();
// Strip the renamed image-URL field from each side, then compare.
// (`voice_ids` and `end_image_url` are new in 2.6 — neither is set
// in this test, so they don't appear in JSON.)
new_json.as_object_mut().unwrap().remove("start_image_url");
legacy_json.as_object_mut().unwrap().remove("image_url");
assert_eq!(new_json, legacy_json);
}
}
// NB: Pricing tests are in cost.rs
}
@@ -233,5 +233,126 @@ mod tests {
);
}
/// Wire-shape and endpoint parity vs the legacy
/// `requests_old/http/video/text/http_kling_v2p6_pro_text_to_video.rs`.
/// The legacy module has no cost calculator, so cost parity is N/A;
/// these tests verify the *shape sent on the wire* hasn't drifted.
mod legacy_parity {
use super::*;
use crate::requests_old::http::video::text::http_kling_v2p6_pro_text_to_video::{
kling_v2p6_pro_text_to_video, KlingV2p6ProTextToVideoInput,
};
/// Same `fal-ai/kling-video/v2.6/pro/text-to-video` path.
#[test]
fn endpoint_path_matches_legacy() {
let legacy = kling_v2p6_pro_text_to_video(KlingV2p6ProTextToVideoInput::default());
assert_eq!(
Kling2p6ProTextToVideoRequest::ENDPOINT,
legacy.endpoint,
);
}
/// At a representative populated case, the new module's serialized
/// `RawRequest` must equal the legacy `Input`'s JSON byte-for-byte
/// (modulo serde field ordering, which is stable within each struct).
#[test]
fn wire_json_matches_legacy_fully_populated() {
let new = Kling2p6ProTextToVideoRequest {
prompt: "a wave at dawn".to_string(),
generate_audio: Some(true),
negative_prompt: Some("blurry".to_string()),
duration: Some(Kling2p6ProTextToVideoDuration::TenSeconds),
aspect_ratio: Some(Kling2p6ProTextToVideoAspectRatio::NineBySixteen),
cfg_scale: Some(0.5),
};
let new_json = serde_json::to_value(new.to_raw_request().unwrap()).unwrap();
let legacy = KlingV2p6ProTextToVideoInput {
prompt: "a wave at dawn".to_string(),
aspect_ratio: Some("9:16".to_string()),
generate_audio: Some(true),
negative_prompt: Some("blurry".to_string()),
duration: Some("10".to_string()),
cfg_scale: Some(0.5),
};
let legacy_json = serde_json::to_value(&legacy).unwrap();
assert_eq!(new_json, legacy_json);
}
/// At minimum (prompt only), unset optionals must be omitted on the
/// wire just like the legacy struct.
#[test]
fn wire_json_matches_legacy_minimal() {
let new = Kling2p6ProTextToVideoRequest {
prompt: "minimal".to_string(),
generate_audio: None,
negative_prompt: None,
duration: None,
aspect_ratio: None,
cfg_scale: None,
};
let new_json = serde_json::to_value(new.to_raw_request().unwrap()).unwrap();
let legacy = KlingV2p6ProTextToVideoInput {
prompt: "minimal".to_string(),
..Default::default()
};
let legacy_json = serde_json::to_value(&legacy).unwrap();
assert_eq!(new_json, legacy_json);
// And the minimal payload should literally be `{"prompt": "minimal"}`.
assert_eq!(new_json, serde_json::json!({ "prompt": "minimal" }));
}
/// Cross product over duration × aspect_ratio × audio: every cell's wire
/// JSON must match the equivalent legacy `Input`.
#[test]
fn wire_json_matches_legacy_at_every_combo() {
let durations = [
(None, None),
(Some(Kling2p6ProTextToVideoDuration::FiveSeconds), Some("5")),
(Some(Kling2p6ProTextToVideoDuration::TenSeconds), Some("10")),
];
let aspect_ratios = [
(None, None),
(Some(Kling2p6ProTextToVideoAspectRatio::Square), Some("1:1")),
(Some(Kling2p6ProTextToVideoAspectRatio::SixteenByNine), Some("16:9")),
(Some(Kling2p6ProTextToVideoAspectRatio::NineBySixteen), Some("9:16")),
];
let audio_options = [None, Some(false), Some(true)];
for (d_new, d_legacy) in durations {
for (ar_new, ar_legacy) in aspect_ratios {
for audio in audio_options {
let new = Kling2p6ProTextToVideoRequest {
prompt: "p".to_string(),
generate_audio: audio,
negative_prompt: None,
duration: d_new,
aspect_ratio: ar_new,
cfg_scale: None,
};
let legacy = KlingV2p6ProTextToVideoInput {
prompt: "p".to_string(),
aspect_ratio: ar_legacy.map(String::from),
generate_audio: audio,
negative_prompt: None,
duration: d_legacy.map(String::from),
cfg_scale: None,
};
let new_json = serde_json::to_value(new.to_raw_request().unwrap()).unwrap();
let legacy_json = serde_json::to_value(&legacy).unwrap();
assert_eq!(
new_json, legacy_json,
"duration={d_new:?} aspect_ratio={ar_new:?} audio={audio:?}",
);
}
}
}
}
}
// NB: Pricing tests are in cost.rs
}