diff --git a/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/extract_model_glb_payload.rs b/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/extract_model_glb_payload.rs index 25598cf3fc..24dffc9ea1 100644 --- a/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/extract_model_glb_payload.rs +++ b/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/extract_model_glb_payload.rs @@ -11,6 +11,45 @@ pub (crate) fn extract_model_glb(obj: &Map) -> Option RawWebhookPayload { + let path = format!("test_data/webhooks/{}", filename); + let json = std::fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("Failed to read {}: {}", path, e)); + serde_json::from_str(&json) + .unwrap_or_else(|e| panic!("Failed to parse {}: {}", path, e)) + } + + #[test] + fn model_glb_from_test_file() { + let webhook = load_test_webhook("success/model_glb_urls_and_thumbnail_1.json"); + let result = hydrate_webhook_contents(&webhook); + + let HydratedWebhookContents::Success(data) = result else { + panic!("Expected Success, got {:?}", result); + }; + + let contents = data.extracted_contents + .expect("extracted_contents should be Some"); + + let glb = contents.model_glb.expect("model_glb should be Some"); + assert_eq!(glb.url.as_deref(), Some("https://v3b.fal.media/files/b/0a95eb37/f7UaAhyfcx0BAnYy0GpFM_model.glb")); + assert_eq!(glb.content_type.as_deref(), Some("model/gltf-binary")); + assert_eq!(glb.file_name.as_deref(), Some("model.glb")); + assert_eq!(glb.file_size, Some(66284788)); + + // This payload also has a thumbnail. + assert!(contents.thumbnail.is_some()); + + // No image/images/video/mesh in this payload. + assert!(contents.image.is_none()); + assert!(contents.images.is_none()); + assert!(contents.video.is_none()); + assert!(contents.model_mesh.is_none()); + } #[test] fn synthetic_model_glb_payload() { diff --git a/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/extract_model_mesh_payload.rs b/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/extract_model_mesh_payload.rs index 5079ec0315..e30bf1d031 100644 --- a/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/extract_model_mesh_payload.rs +++ b/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/extract_model_mesh_payload.rs @@ -11,6 +11,43 @@ pub (crate) fn extract_model_mesh(obj: &Map) -> Option RawWebhookPayload { + let path = format!("test_data/webhooks/{}", filename); + let json = std::fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("Failed to read {}: {}", path, e)); + serde_json::from_str(&json) + .unwrap_or_else(|e| panic!("Failed to parse {}: {}", path, e)) + } + + #[test] + fn model_mesh_from_test_file() { + let webhook = load_test_webhook("success/model_mesh_payload_1.json"); + let result = hydrate_webhook_contents(&webhook); + + let HydratedWebhookContents::Success(data) = result else { + panic!("Expected Success, got {:?}", result); + }; + + let contents = data.extracted_contents + .expect("extracted_contents should be Some"); + + let mesh = contents.model_mesh.expect("model_mesh should be Some"); + assert_eq!(mesh.url.as_deref(), Some("https://v3b.fal.media/files/b/0a95eb2d/XCpH7fk-tZVXr1XMUr7DY_mesh-1775972273-765284.glb")); + assert_eq!(mesh.content_type.as_deref(), Some("application/octet-stream")); + assert_eq!(mesh.file_name.as_deref(), Some("mesh-1775972273-765284.glb")); + assert_eq!(mesh.file_size, Some(3886928)); + + // No other content types in this payload. + assert!(contents.image.is_none()); + assert!(contents.images.is_none()); + assert!(contents.video.is_none()); + assert!(contents.model_glb.is_none()); + assert!(contents.thumbnail.is_none()); + } #[test] fn synthetic_model_mesh_payload() { diff --git a/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/extract_thumbnail_payload.rs b/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/extract_thumbnail_payload.rs new file mode 100644 index 0000000000..c3241732be --- /dev/null +++ b/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/extract_thumbnail_payload.rs @@ -0,0 +1,86 @@ +use serde_json::{Map, Value}; + +use crate::webhook_api::hydrated::hydrated_webhook_contents::ThumbnailData; + +/// Extract and deserialize the `thumbnail` key from a webhook success payload. +pub(crate) fn extract_thumbnail(obj: &Map) -> Option { + let value = obj.get("thumbnail")?; + serde_json::from_value(value.clone()).ok() +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::webhook_api::hydrate_webhook_contents::hydrate_webhook_contents; + use crate::webhook_api::hydrated::hydrated_webhook_contents::HydratedWebhookContents; + use crate::webhook_api::raw::raw_webhook_payload::RawWebhookPayload; + + fn load_test_webhook(filename: &str) -> RawWebhookPayload { + let path = format!("test_data/webhooks/{}", filename); + let json = std::fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("Failed to read {}: {}", path, e)); + serde_json::from_str(&json) + .unwrap_or_else(|e| panic!("Failed to parse {}: {}", path, e)) + } + + #[test] + fn thumbnail_from_model_glb_test_file() { + let webhook = load_test_webhook("success/model_glb_urls_and_thumbnail_1.json"); + let result = hydrate_webhook_contents(&webhook); + + let HydratedWebhookContents::Success(data) = result else { + panic!("Expected Success, got {:?}", result); + }; + + let contents = data.extracted_contents + .expect("extracted_contents should be Some"); + + let thumbnail = contents.thumbnail.expect("thumbnail should be Some"); + assert_eq!(thumbnail.url.as_deref(), Some("https://v3b.fal.media/files/b/0a95eb38/yvwGJWZV9TkWZuZvjxZNj_preview.png")); + assert_eq!(thumbnail.content_type.as_deref(), Some("image/png")); + assert_eq!(thumbnail.file_name.as_deref(), Some("preview.png")); + assert_eq!(thumbnail.file_size, Some(93436)); + } + + #[test] + fn synthetic_thumbnail_payload() { + let obj: serde_json::Map = serde_json::from_str(r#"{ + "thumbnail": { + "url": "https://cdn.example.com/thumb.png", + "content_type": "image/png", + "file_name": "thumb.png", + "file_size": 12345 + } + }"#).unwrap(); + + let thumb = extract_thumbnail(&obj).expect("should extract thumbnail"); + assert_eq!(thumb.url.as_deref(), Some("https://cdn.example.com/thumb.png")); + assert_eq!(thumb.content_type.as_deref(), Some("image/png")); + assert_eq!(thumb.file_name.as_deref(), Some("thumb.png")); + assert_eq!(thumb.file_size, Some(12345)); + } + + #[test] + fn missing_thumbnail_key_returns_none() { + let obj: serde_json::Map = serde_json::from_str(r#"{ + "model_glb": {"url": "https://example.com/model.glb"} + }"#).unwrap(); + + assert!(extract_thumbnail(&obj).is_none()); + } + + #[test] + fn model_mesh_payload_has_no_thumbnail() { + let webhook = load_test_webhook("success/model_mesh_payload_1.json"); + let result = hydrate_webhook_contents(&webhook); + + let HydratedWebhookContents::Success(data) = result else { + panic!("Expected Success, got {:?}", result); + }; + + let contents = data.extracted_contents + .expect("extracted_contents should be Some"); + + assert!(contents.thumbnail.is_none()); + } +} diff --git a/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/mod.rs b/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/mod.rs index 8f7c25342d..cd40e6e06d 100644 --- a/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/mod.rs +++ b/crates/api_clients/fal_client/src/webhook_api/hydrated/success_case_extractors/mod.rs @@ -3,6 +3,7 @@ pub mod extract_images_payload; pub mod extract_video_payload; pub mod extract_model_glb_payload; pub mod extract_model_mesh_payload; +pub mod extract_thumbnail_payload; use serde_json::Value; @@ -23,8 +24,11 @@ pub fn extract_contents_from_payload(payload: &Value) -> Option Option