extract image, more tests

This commit is contained in:
Brandon Thomas committed 2026-05-11 21:11:01 -04:00
1 parent 85c20cd3f9
commit 3581d2c698
4 files changed
+121 -1

No files matched your search

@@ -268,6 +268,98 @@ mod tests {
assert!(value.get("model_urls").and_then(|v| v.get("fbx")).and_then(|v| v.as_object()).is_none());
assert!(value.get("model_urls").and_then(|v| v.get("usdz")).and_then(|v| v.as_object()).is_none());
}
#[test]
fn parse_real_kling3_video_response_offline() {
let json = r#"{"video":{"url":"https://v3b.fal.media/files/b/0a99d90a/v1tQEfvnY2bVcn38IvmfD_output.mp4","content_type":"video/mp4","file_name":"output.mp4","file_size":3096422}}"#;
let value: Value = serde_json::from_str(json).unwrap();
let extracted = extract_contents_from_response(&value).expect("should extract contents");
// Should have video, not images or mesh
assert!(extracted.image.is_none());
assert!(extracted.images.is_none());
assert!(extracted.model_glb.is_none());
assert!(extracted.thumbnail.is_none());
let video = extracted.video.expect("should have video");
assert_eq!(video.url.as_deref(), Some("https://v3b.fal.media/files/b/0a99d90a/v1tQEfvnY2bVcn38IvmfD_output.mp4"));
assert_eq!(video.content_type.as_deref(), Some("video/mp4"));
assert_eq!(video.file_name.as_deref(), Some("output.mp4"));
assert_eq!(video.file_size, Some(3096422));
}
#[test]
fn parse_real_nano_banana_pro_edit_response_offline() {
let json = r#"{"images":[{"url":"https://v3b.fal.media/files/b/0a99d90e/IXN1B_4MHnozvltxEHfvX_BwTMO9xM.png","content_type":"image/png","file_name":"IXN1B_4MHnozvltxEHfvX_BwTMO9xM.png","file_size":null,"width":null,"height":null},{"url":"https://v3b.fal.media/files/b/0a99d90e/aj_Xu0cIUoVSZ6qSSxaZf_wy3l60NO.png","content_type":"image/png","file_name":"aj_Xu0cIUoVSZ6qSSxaZf_wy3l60NO.png","file_size":null,"width":null,"height":null}],"description":"**Developing Spooky Atmosphere**\n\ntest description"}"#;
let value: Value = serde_json::from_str(json).unwrap();
let extracted = extract_contents_from_response(&value).expect("should extract contents");
// Should have images, not video or mesh
assert!(extracted.image.is_none());
assert!(extracted.video.is_none());
assert!(extracted.model_glb.is_none());
assert!(extracted.thumbnail.is_none());
let images = extracted.images.expect("should have images");
assert_eq!(images.len(), 2);
assert_eq!(images[0].url.as_deref(), Some("https://v3b.fal.media/files/b/0a99d90e/IXN1B_4MHnozvltxEHfvX_BwTMO9xM.png"));
assert_eq!(images[0].content_type.as_deref(), Some("image/png"));
assert!(images[0].width.is_none(), "nano banana pro edit returns null width");
assert!(images[0].height.is_none(), "nano banana pro edit returns null height");
assert!(images[0].file_size.is_none(), "nano banana pro edit returns null file_size");
assert_eq!(images[1].url.as_deref(), Some("https://v3b.fal.media/files/b/0a99d90e/aj_Xu0cIUoVSZ6qSSxaZf_wy3l60NO.png"));
assert_eq!(images[1].content_type.as_deref(), Some("image/png"));
}
#[test]
fn parse_real_flux_angle_lora_response_offline() {
let json = r#"{"images":[{"url":"https://v3b.fal.media/files/b/0a99d915/_61CGILQXk8A7A6FrAbdu_uytUbw2B.png","content_type":"image/png","file_name":"_61CGILQXk8A7A6FrAbdu_uytUbw2B.png","file_size":null,"width":1024,"height":1024}],"seed":225652535,"prompt":"<sks> front-right quarter view elevated shot medium shot"}"#;
let value: Value = serde_json::from_str(json).unwrap();
let extracted = extract_contents_from_response(&value).expect("should extract contents");
// Should have images, not video or mesh
assert!(extracted.image.is_none());
assert!(extracted.video.is_none());
assert!(extracted.model_glb.is_none());
assert!(extracted.thumbnail.is_none());
let images = extracted.images.expect("should have images");
assert_eq!(images.len(), 1);
let image = &images[0];
assert_eq!(image.url.as_deref(), Some("https://v3b.fal.media/files/b/0a99d915/_61CGILQXk8A7A6FrAbdu_uytUbw2B.png"));
assert_eq!(image.content_type.as_deref(), Some("image/png"));
assert_eq!(image.width, Some(1024));
assert_eq!(image.height, Some(1024));
assert!(image.file_size.is_none());
// Verify raw payload fields
assert_eq!(value.get("seed").and_then(|v| v.as_u64()), Some(225652535));
assert_eq!(value.get("prompt").and_then(|v| v.as_str()), Some("<sks> front-right quarter view elevated shot medium shot"));
}
#[test]
fn parse_real_remove_bg_response_offline() {
let json = r#"{"image":{"url":"https://v3b.fal.media/files/b/0a99d916/J7OyBamMKRI_RUbkUxnJE_f5b8faa705534b19ad87bc3d48b5c6c5.png","content_type":"image/png","file_name":"f5b8faa705534b19ad87bc3d48b5c6c5.png","file_size":496291,"width":1000,"height":664}}"#;
let value: Value = serde_json::from_str(json).unwrap();
let extracted = extract_contents_from_response(&value).expect("should extract contents");
// Should have single image, not images (plural), video, or mesh
assert!(extracted.images.is_none());
assert!(extracted.video.is_none());
assert!(extracted.model_glb.is_none());
assert!(extracted.thumbnail.is_none());
let image = extracted.image.expect("should have single image");
assert_eq!(image.url.as_deref(), Some("https://v3b.fal.media/files/b/0a99d916/J7OyBamMKRI_RUbkUxnJE_f5b8faa705534b19ad87bc3d48b5c6c5.png"));
assert_eq!(image.content_type.as_deref(), Some("image/png"));
assert_eq!(image.file_name.as_deref(), Some("f5b8faa705534b19ad87bc3d48b5c6c5.png"));
assert_eq!(image.file_size, Some(496291));
assert_eq!(image.width, Some(1000));
assert_eq!(image.height, Some(664));
}
}
// ── Live tests ──
@@ -0,0 +1,17 @@
use serde::Deserialize;
use serde_json::Value;
#[derive(Debug, Clone, Deserialize)]
pub struct PollResponseSingleImageData {
pub url: Option<String>,
pub width: Option<u64>,
pub height: Option<u64>,
pub content_type: Option<String>,
pub file_name: Option<String>,
pub file_size: Option<u64>,
}
pub fn extract_image(value: &Value) -> Option<PollResponseSingleImageData> {
let image = value.get("image")?;
serde_json::from_value(image.clone()).ok()
}
@@ -7,6 +7,8 @@ pub struct PollResponseImageEntry {
pub width: Option<u64>,
pub height: Option<u64>,
pub content_type: Option<String>,
pub file_name: Option<String>,
pub file_size: Option<u64>,
}
pub fn extract_images(value: &Value) -> Option<Vec<PollResponseImageEntry>> {
@@ -1,3 +1,4 @@
pub mod extract_image;
pub mod extract_images;
pub mod extract_model_glb;
pub mod extract_thumbnail;
@@ -5,6 +6,7 @@ pub mod extract_video;
use serde_json::Value;
use crate::polling::poll_job_response::success_case_extractors::extract_image::PollResponseSingleImageData;
use crate::polling::poll_job_response::success_case_extractors::extract_images::PollResponseImageEntry;
use crate::polling::poll_job_response::success_case_extractors::extract_model_glb::PollResponseModelGlbData;
use crate::polling::poll_job_response::success_case_extractors::extract_thumbnail::PollResponseThumbnailData;
@@ -14,6 +16,9 @@ use crate::polling::poll_job_response::success_case_extractors::extract_video::P
/// Multiple fields can be populated simultaneously (e.g. model_glb + thumbnail).
#[derive(Debug, Clone)]
pub struct PollResponseExtractedContents {
/// Parsed from `payload.image` (single image result, e.g. background removal).
pub image: Option<PollResponseSingleImageData>,
/// Parsed from `payload.images` (batch image results).
pub images: Option<Vec<PollResponseImageEntry>>,
pub video: Option<PollResponseVideoData>,
pub model_glb: Option<PollResponseModelGlbData>,
@@ -22,16 +27,20 @@ pub struct PollResponseExtractedContents {
/// Try to extract known content keys from a success response payload.
pub fn extract_contents_from_response(value: &Value) -> Option<PollResponseExtractedContents> {
let image = extract_image::extract_image(value);
let images = extract_images::extract_images(value);
let video = extract_video::extract_video(value);
let model_glb = extract_model_glb::extract_model_glb(value);
let thumbnail = extract_thumbnail::extract_thumbnail(value);
if images.is_none() && video.is_none() && model_glb.is_none() && thumbnail.is_none() {
if image.is_none() && images.is_none() && video.is_none()
&& model_glb.is_none() && thumbnail.is_none()
{
return None;
}
Some(PollResponseExtractedContents {
image,
images,
video,
model_glb,