diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0/build.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0/build.rs index c82299f58a..75a892bd7a 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0/build.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0/build.rs @@ -212,303 +212,300 @@ mod tests { use super::*; - fn seedance2pro_builder() -> GenerateVideoRequestBuilder { - GenerateVideoRequestBuilder { - provider: Provider::Seedance2Pro, - prompt: Some("a cat dancing".to_string()), - duration_seconds: Some(5), - video_batch_count: Some(1), - ..Default::default() - } - } - - fn unwrap_draft(result: Result) -> KinoviSeedance2p0DraftState { - match result.expect("build should succeed") { - VideoGenerationDraftOrRequest::Draft( - VideoGenerationDraftRequest::KinoviSeedance2p0(draft) - ) => draft, - _ => panic!("expected KinoviSeedance2p0 draft"), - } - } - // ── Materialized field conversions ── - #[test] - fn prompt_is_passed_through() { - let draft = unwrap_draft(build_kinovi_seedance_2p0(seedance2pro_builder())); - assert_eq!(draft.prompt, "a cat dancing"); - } + mod materialized_field_conversions { + use super::*; - #[test] - fn prompt_defaults_to_empty() { - let builder = GenerateVideoRequestBuilder { prompt: None, ..seedance2pro_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert_eq!(draft.prompt, ""); - } + #[test] + fn prompt_is_passed_through() { + let draft = unwrap_draft(build_kinovi_seedance_2p0(seedance2pro_builder())); + assert_eq!(draft.prompt, "a cat dancing"); + } - #[test] - fn duration_seconds_converted() { - let builder = GenerateVideoRequestBuilder { duration_seconds: Some(10), ..seedance2pro_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert_eq!(draft.duration_seconds, 10); - } + #[test] + fn prompt_defaults_to_empty() { + let builder = GenerateVideoRequestBuilder { prompt: None, ..seedance2pro_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert_eq!(draft.prompt, ""); + } - #[test] - fn duration_defaults_to_5() { - let builder = GenerateVideoRequestBuilder { duration_seconds: None, ..seedance2pro_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert_eq!(draft.duration_seconds, 5); - } + #[test] + fn duration_seconds_converted() { + let builder = GenerateVideoRequestBuilder { duration_seconds: Some(10), ..seedance2pro_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert_eq!(draft.duration_seconds, 10); + } - #[test] - fn duration_clamped_to_max() { - let builder = GenerateVideoRequestBuilder { - duration_seconds: Some(99), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert_eq!(draft.duration_seconds, 15); - } + #[test] + fn duration_defaults_to_5() { + let builder = GenerateVideoRequestBuilder { duration_seconds: None, ..seedance2pro_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert_eq!(draft.duration_seconds, 5); + } - #[test] - fn batch_count_one() { - let builder = GenerateVideoRequestBuilder { video_batch_count: Some(1), ..seedance2pro_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert!(matches!(draft.batch_count, KinoviBatchCount::One)); - } + #[test] + fn duration_clamped_to_max() { + let builder = GenerateVideoRequestBuilder { + duration_seconds: Some(99), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert_eq!(draft.duration_seconds, 15); + } - #[test] - fn batch_count_two() { - let builder = GenerateVideoRequestBuilder { video_batch_count: Some(2), ..seedance2pro_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert!(matches!(draft.batch_count, KinoviBatchCount::Two)); - } + #[test] + fn batch_count_one() { + let builder = GenerateVideoRequestBuilder { video_batch_count: Some(1), ..seedance2pro_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert!(matches!(draft.batch_count, KinoviBatchCount::One)); + } - #[test] - fn batch_count_four() { - let builder = GenerateVideoRequestBuilder { video_batch_count: Some(4), ..seedance2pro_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert!(matches!(draft.batch_count, KinoviBatchCount::Four)); + #[test] + fn batch_count_two() { + let builder = GenerateVideoRequestBuilder { video_batch_count: Some(2), ..seedance2pro_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert!(matches!(draft.batch_count, KinoviBatchCount::Two)); + } + + #[test] + fn batch_count_four() { + let builder = GenerateVideoRequestBuilder { video_batch_count: Some(4), ..seedance2pro_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert!(matches!(draft.batch_count, KinoviBatchCount::Four)); + } } // ── Aspect ratio conversions ── - #[test] - fn aspect_ratio_wide() { - let builder = GenerateVideoRequestBuilder { - aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Landscape16x9)); - } + mod aspect_ratio_conversions { + use super::*; - #[test] - fn aspect_ratio_tall() { - let builder = GenerateVideoRequestBuilder { - aspect_ratio: Some(CommonAspectRatio::TallNineBySixteen), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Portrait9x16)); - } + #[test] + fn aspect_ratio_wide() { + let builder = GenerateVideoRequestBuilder { + aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Landscape16x9)); + } - #[test] - fn aspect_ratio_square() { - let builder = GenerateVideoRequestBuilder { - aspect_ratio: Some(CommonAspectRatio::Square), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Square1x1)); - } + #[test] + fn aspect_ratio_tall() { + let builder = GenerateVideoRequestBuilder { + aspect_ratio: Some(CommonAspectRatio::TallNineBySixteen), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Portrait9x16)); + } - #[test] - fn aspect_ratio_defaults_to_landscape() { - let builder = GenerateVideoRequestBuilder { aspect_ratio: None, ..seedance2pro_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Landscape16x9)); + #[test] + fn aspect_ratio_square() { + let builder = GenerateVideoRequestBuilder { + aspect_ratio: Some(CommonAspectRatio::Square), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Square1x1)); + } + + #[test] + fn aspect_ratio_defaults_to_landscape() { + let builder = GenerateVideoRequestBuilder { aspect_ratio: None, ..seedance2pro_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Landscape16x9)); + } } // ── Resolution conversions ── - #[test] - fn resolution_480p() { - let builder = GenerateVideoRequestBuilder { - resolution: Some(CommonResolution::FourEightyP), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert!(matches!(draft.resolution, Some(KinoviOutputResolution::FourEightyP))); - } + mod resolution_conversions { + use super::*; - #[test] - fn resolution_720p() { - let builder = GenerateVideoRequestBuilder { - resolution: Some(CommonResolution::SevenTwentyP), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP))); - } + #[test] + fn resolution_480p() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(CommonResolution::FourEightyP), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert!(matches!(draft.resolution, Some(KinoviOutputResolution::FourEightyP))); + } - #[test] - fn resolution_1080p() { - let builder = GenerateVideoRequestBuilder { - resolution: Some(CommonResolution::TenEightyP), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert!(matches!(draft.resolution, Some(KinoviOutputResolution::TenEightyP))); - } + #[test] + fn resolution_720p() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(CommonResolution::SevenTwentyP), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP))); + } - #[test] - fn resolution_none() { - let builder = GenerateVideoRequestBuilder { resolution: None, ..seedance2pro_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert!(draft.resolution.is_none()); - } + #[test] + fn resolution_1080p() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(CommonResolution::TenEightyP), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert!(matches!(draft.resolution, Some(KinoviOutputResolution::TenEightyP))); + } - #[test] - fn unsupported_resolution_error_out() { - let builder = GenerateVideoRequestBuilder { - resolution: Some(CommonResolution::FourK), - request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, - ..seedance2pro_builder() - }; - assert!(build_kinovi_seedance_2p0(builder).is_err()); - } + #[test] + fn resolution_none() { + let builder = GenerateVideoRequestBuilder { resolution: None, ..seedance2pro_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert!(draft.resolution.is_none()); + } - #[test] - fn unsupported_resolution_rounds_up() { - let builder = GenerateVideoRequestBuilder { - resolution: Some(CommonResolution::FourK), - request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - assert!(matches!(draft.resolution, Some(KinoviOutputResolution::TenEightyP))); + #[test] + fn unsupported_resolution_error_out() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(CommonResolution::FourK), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, + ..seedance2pro_builder() + }; + assert!(build_kinovi_seedance_2p0(builder).is_err()); + } + + #[test] + fn unsupported_resolution_rounds_up() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(CommonResolution::FourK), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + assert!(matches!(draft.resolution, Some(KinoviOutputResolution::TenEightyP))); + } } // ── unhandled_request_state: media refs are placed there ── - #[test] - fn unhandled_state_is_present() { - let draft = unwrap_draft(build_kinovi_seedance_2p0(seedance2pro_builder())); - assert!(draft.unhandled_request_state.is_some()); - } + mod unhandled_request_state { + use super::*; - #[test] - fn start_frame_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - start_frame: Some(ImageRef::Url("https://example.com/start.jpg".to_string())), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - assert!(matches!(remaining.start_frame, Some(ImageRef::Url(url)) if url == "https://example.com/start.jpg")); - } - - #[test] - fn end_frame_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - end_frame: Some(ImageRef::Url("https://example.com/end.jpg".to_string())), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - assert!(matches!(remaining.end_frame, Some(ImageRef::Url(url)) if url == "https://example.com/end.jpg")); - } - - #[test] - fn start_frame_media_token_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - start_frame: Some(ImageRef::MediaFileToken(MediaFileToken::new("mf_test123".to_string()))), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - assert!(matches!(remaining.start_frame, Some(ImageRef::MediaFileToken(t)) if t.as_str() == "mf_test123")); - } - - #[test] - fn reference_images_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - reference_images: Some(ImageListRef::Urls(vec![ - "https://example.com/ref1.jpg".to_string(), - "https://example.com/ref2.jpg".to_string(), - ])), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - match remaining.reference_images { - Some(ImageListRef::Urls(urls)) => { - assert_eq!(urls.len(), 2); - assert_eq!(urls[0], "https://example.com/ref1.jpg"); - assert_eq!(urls[1], "https://example.com/ref2.jpg"); - } - _ => panic!("expected Urls variant"), + #[test] + fn unhandled_state_is_present() { + let draft = unwrap_draft(build_kinovi_seedance_2p0(seedance2pro_builder())); + assert!(draft.unhandled_request_state.is_some()); } - } - #[test] - fn reference_videos_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - reference_videos: Some(VideoListRef::Urls(vec![ - "https://example.com/vid.mp4".to_string(), - ])), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - assert!(matches!(remaining.reference_videos, Some(VideoListRef::Urls(urls)) if urls.len() == 1)); - } - - #[test] - fn reference_audio_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - reference_audio: Some(AudioListRef::Urls(vec![ - "https://example.com/audio.mp3".to_string(), - ])), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - assert!(matches!(remaining.reference_audio, Some(AudioListRef::Urls(urls)) if urls.len() == 1)); - } - - #[test] - fn character_tokens_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - reference_character_tokens: Some(CharacterListRef::CharacterTokens(vec![ - CharacterToken::new("char_abc".to_string()), - CharacterToken::new("char_def".to_string()), - ])), - ..seedance2pro_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - match remaining.reference_character_tokens { - Some(CharacterListRef::CharacterTokens(tokens)) => { - assert_eq!(tokens.len(), 2); - assert_eq!(tokens[0].as_str(), "char_abc"); - assert_eq!(tokens[1].as_str(), "char_def"); - } - _ => panic!("expected CharacterTokens variant"), + #[test] + fn start_frame_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + start_frame: Some(ImageRef::Url("https://example.com/start.jpg".to_string())), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(matches!(remaining.start_frame, Some(ImageRef::Url(url)) if url == "https://example.com/start.jpg")); } - } - #[test] - fn empty_refs_are_none_in_unhandled() { - let draft = unwrap_draft(build_kinovi_seedance_2p0(seedance2pro_builder())); - let remaining = draft.unhandled_request_state.unwrap(); - assert!(remaining.start_frame.is_none()); - assert!(remaining.end_frame.is_none()); - assert!(remaining.reference_images.is_none()); - assert!(remaining.reference_videos.is_none()); - assert!(remaining.reference_audio.is_none()); - assert!(remaining.reference_character_tokens.is_none()); + #[test] + fn end_frame_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + end_frame: Some(ImageRef::Url("https://example.com/end.jpg".to_string())), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(matches!(remaining.end_frame, Some(ImageRef::Url(url)) if url == "https://example.com/end.jpg")); + } + + #[test] + fn start_frame_media_token_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + start_frame: Some(ImageRef::MediaFileToken(MediaFileToken::new("mf_test123".to_string()))), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(matches!(remaining.start_frame, Some(ImageRef::MediaFileToken(t)) if t.as_str() == "mf_test123")); + } + + #[test] + fn reference_images_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + reference_images: Some(ImageListRef::Urls(vec![ + "https://example.com/ref1.jpg".to_string(), + "https://example.com/ref2.jpg".to_string(), + ])), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + match remaining.reference_images { + Some(ImageListRef::Urls(urls)) => { + assert_eq!(urls.len(), 2); + assert_eq!(urls[0], "https://example.com/ref1.jpg"); + assert_eq!(urls[1], "https://example.com/ref2.jpg"); + } + _ => panic!("expected Urls variant"), + } + } + + #[test] + fn reference_videos_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + reference_videos: Some(VideoListRef::Urls(vec![ + "https://example.com/vid.mp4".to_string(), + ])), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(matches!(remaining.reference_videos, Some(VideoListRef::Urls(urls)) if urls.len() == 1)); + } + + #[test] + fn reference_audio_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + reference_audio: Some(AudioListRef::Urls(vec![ + "https://example.com/audio.mp3".to_string(), + ])), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(matches!(remaining.reference_audio, Some(AudioListRef::Urls(urls)) if urls.len() == 1)); + } + + #[test] + fn character_tokens_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + reference_character_tokens: Some(CharacterListRef::CharacterTokens(vec![ + CharacterToken::new("char_abc".to_string()), + CharacterToken::new("char_def".to_string()), + ])), + ..seedance2pro_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + match remaining.reference_character_tokens { + Some(CharacterListRef::CharacterTokens(tokens)) => { + assert_eq!(tokens.len(), 2); + assert_eq!(tokens[0].as_str(), "char_abc"); + assert_eq!(tokens[1].as_str(), "char_def"); + } + _ => panic!("expected CharacterTokens variant"), + } + } + + #[test] + fn empty_refs_are_none_in_unhandled() { + let draft = unwrap_draft(build_kinovi_seedance_2p0(seedance2pro_builder())); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(remaining.start_frame.is_none()); + assert!(remaining.end_frame.is_none()); + assert!(remaining.reference_images.is_none()); + assert!(remaining.reference_videos.is_none()); + assert!(remaining.reference_audio.is_none()); + assert!(remaining.reference_character_tokens.is_none()); + } } // ── Full combination ── @@ -547,4 +544,25 @@ mod tests { assert!(remaining.reference_audio.is_some()); assert!(remaining.reference_character_tokens.is_some()); } + + // ── Helpers ── + + fn seedance2pro_builder() -> GenerateVideoRequestBuilder { + GenerateVideoRequestBuilder { + provider: Provider::Seedance2Pro, + prompt: Some("a cat dancing".to_string()), + duration_seconds: Some(5), + video_batch_count: Some(1), + ..Default::default() + } + } + + fn unwrap_draft(result: Result) -> KinoviSeedance2p0DraftState { + match result.expect("build should succeed") { + VideoGenerationDraftOrRequest::Draft( + VideoGenerationDraftRequest::KinoviSeedance2p0(draft) + ) => draft, + _ => panic!("expected KinoviSeedance2p0 draft"), + } + } } diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0/cost.rs index 12d409c00d..a0207b1cbe 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0/cost.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0/cost.rs @@ -84,7 +84,296 @@ mod tests { use crate::api::video_list_ref::VideoListRef; use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; - // ======================== Helpers ======================== + // ── 720p pricing ── + + mod pricing_720p { + use super::*; + + #[test] + fn cost_720p_batch_1() { + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 64); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 80); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 6, KinoviBatchCount::One), 96); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 7, KinoviBatchCount::One), 112); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 160); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 240); + } + + #[test] + fn cost_720p_batch_2() { + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Two), 128); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 160); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Two), 480); + } + + #[test] + fn cost_720p_batch_4() { + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Four), 256); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 320); + assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Four), 960); + } + } + + // ── 480p pricing ── + + mod pricing_480p { + use super::*; + + #[test] + fn cost_480p_batch_1() { + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One), 31); + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 39); + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One), 78); + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One), 117); + } + + #[test] + fn cost_480p_batch_2() { + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Two), 78); + } + + #[test] + fn cost_480p_batch_4() { + assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 155); + } + } + + // ── 1080p pricing ── + + mod pricing_1080p { + use super::*; + + #[test] + fn cost_1080p_batch_1() { + assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 4, KinoviBatchCount::One), 187); + assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::One), 233); + assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 10, KinoviBatchCount::One), 466); + assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 15, KinoviBatchCount::One), 699); + } + + #[test] + fn cost_1080p_batch_2() { + assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::Two), 466); + } + + #[test] + fn cost_1080p_batch_4() { + assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::Four), 933); + } + } + + // ── Relative pricing ── + + mod relative_pricing_tests { + use super::*; + + #[test] + fn cost_480p_cheaper_than_720p_cheaper_than_1080p() { + let c480 = usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One); + let c720 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One); + let c1080 = usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::One); + assert!(c480 < c720, "480p ({}) should be cheaper than 720p ({})", c480, c720); + assert!(c720 < c1080, "720p ({}) should be cheaper than 1080p ({})", c720, c1080); + } + + #[test] + fn cost_scales_with_duration() { + let c4 = usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One); + let c10 = usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One); + let c15 = usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One); + assert!(c4 < c10); + assert!(c10 < c15); + } + + #[test] + fn cost_scales_with_batch() { + let b1 = usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::One); + let b2 = usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::Two); + let b4 = usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::Four); + assert!(b1 < b2); + assert!(b2 < b4); + } + } + + // -- Video reference does NOT affect cost (yet) -- + + #[test] + fn video_reference_does_not_affect_cost() { + let base = KinoviSeedance2p0CostState { + resolution: KinoviOutputResolution::SevenTwentyP, + duration_seconds: 5, + batch_count: KinoviBatchCount::One, + has_video_reference: false, + }; + let without = base.estimate_cost(); + let with = KinoviSeedance2p0CostState { has_video_reference: true, ..base }.estimate_cost(); + assert_eq!(without.cost_in_usd_cents, with.cost_in_usd_cents); + assert_eq!(without.cost_in_credits, with.cost_in_credits); + } + + // ── from_request() tests ── + + mod from_request_tests { + use super::*; + + #[test] + fn from_request_720p() { + let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, false); + let cost = KinoviSeedance2p0CostState::from_request(&req); + assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP)); + assert_eq!(cost.duration_seconds, 5); + assert!(matches!(cost.batch_count, KinoviBatchCount::One)); + assert!(!cost.has_video_reference); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80)); + } + + #[test] + fn from_request_none_defaults_to_720p() { + let req = make_request_state(None, 5, KinoviBatchCount::One, false); + let cost = KinoviSeedance2p0CostState::from_request(&req); + assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP)); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80)); + } + + #[test] + fn from_request_480p() { + let req = make_request_state(Some(KinoviOutputResolution::FourEightyP), 5, KinoviBatchCount::One, false); + let cost = KinoviSeedance2p0CostState::from_request(&req); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(39)); + } + + #[test] + fn from_request_1080p_batch_2() { + let req = make_request_state(Some(KinoviOutputResolution::TenEightyP), 5, KinoviBatchCount::Two, false); + let cost = KinoviSeedance2p0CostState::from_request(&req); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(466)); + } + + #[test] + fn from_request_with_video_reference() { + let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, true); + let cost = KinoviSeedance2p0CostState::from_request(&req); + assert!(cost.has_video_reference); + // Video refs don't affect cost yet + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80)); + } + + #[test] + fn from_request_without_video_reference() { + let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, false); + let cost = KinoviSeedance2p0CostState::from_request(&req); + assert!(!cost.has_video_reference); + } + } + + // ── from_draft() tests ── + + mod from_draft_tests { + use super::*; + + #[test] + fn from_draft_720p_default() { + let draft = make_draft(5, 1, None, false); + let cost = KinoviSeedance2p0CostState::from_draft(&draft); + assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP)); + assert_eq!(cost.duration_seconds, 5); + assert!(matches!(cost.batch_count, KinoviBatchCount::One)); + assert!(!cost.has_video_reference); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80)); + } + + #[test] + fn from_draft_480p() { + let draft = make_draft(5, 1, Some(CommonResolution::FourEightyP), false); + let cost = KinoviSeedance2p0CostState::from_draft(&draft); + assert!(matches!(cost.resolution, KinoviOutputResolution::FourEightyP)); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(39)); + } + + #[test] + fn from_draft_1080p_batch_4() { + let draft = make_draft(5, 4, Some(CommonResolution::TenEightyP), false); + let cost = KinoviSeedance2p0CostState::from_draft(&draft); + assert!(matches!(cost.resolution, KinoviOutputResolution::TenEightyP)); + assert!(matches!(cost.batch_count, KinoviBatchCount::Four)); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(933)); + } + + #[test] + fn from_draft_with_video_reference() { + let draft = make_draft(5, 1, None, true); + let cost = KinoviSeedance2p0CostState::from_draft(&draft); + assert!(cost.has_video_reference); + // Video refs don't affect cost yet + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80)); + } + + #[test] + fn from_draft_without_video_reference() { + let draft = make_draft(5, 1, None, false); + let cost = KinoviSeedance2p0CostState::from_draft(&draft); + assert!(!cost.has_video_reference); + } + + #[test] + fn from_draft_duration_15_batch_2() { + let draft = make_draft(15, 2, Some(CommonResolution::SevenTwentyP), false); + let cost = KinoviSeedance2p0CostState::from_draft(&draft); + assert_eq!(cost.duration_seconds, 15); + assert!(matches!(cost.batch_count, KinoviBatchCount::Two)); + assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(480)); + } + } + + // ── Cross-check: from_draft matches from_request ── + + mod cross_check_tests { + use super::*; + + #[test] + fn draft_and_request_produce_same_cost() { + // 720p, 5s, batch 1 + let draft = make_draft(5, 1, Some(CommonResolution::SevenTwentyP), false); + let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, false); + let draft_cost = KinoviSeedance2p0CostState::from_draft(&draft).estimate_cost(); + let req_cost = KinoviSeedance2p0CostState::from_request(&req).estimate_cost(); + assert_eq!(draft_cost.cost_in_usd_cents, req_cost.cost_in_usd_cents); + assert_eq!(draft_cost.cost_in_credits, req_cost.cost_in_credits); + } + + #[test] + fn draft_and_request_produce_same_cost_1080p() { + let draft = make_draft(10, 2, Some(CommonResolution::TenEightyP), false); + let req = make_request_state(Some(KinoviOutputResolution::TenEightyP), 10, KinoviBatchCount::Two, false); + let draft_cost = KinoviSeedance2p0CostState::from_draft(&draft).estimate_cost(); + let req_cost = KinoviSeedance2p0CostState::from_request(&req).estimate_cost(); + assert_eq!(draft_cost.cost_in_usd_cents, req_cost.cost_in_usd_cents); + assert_eq!(draft_cost.cost_in_credits, req_cost.cost_in_credits); + } + } + + // ── Credits spot checks ── + + mod credits_tests { + use super::*; + + #[test] + fn credits_720p() { + assert_eq!(credits(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 200); + } + + #[test] + fn credits_480p() { + assert_eq!(credits(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 75); + } + + #[test] + fn credits_1080p() { + assert_eq!(credits(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::One), 450); + } + } + + // ── Helpers ── fn usd_cents( resolution: KinoviOutputResolution, @@ -169,264 +458,4 @@ mod tests { }, } } - - // ======================== Direct estimate_cost() tests ======================== - // These must match the reference implementation values exactly. - - // -- 720p (legacy pricing: 250 credits/$1, 40 credits/sec) -- - - #[test] - fn cost_720p_batch_1() { - assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 64); - assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 80); - assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 6, KinoviBatchCount::One), 96); - assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 7, KinoviBatchCount::One), 112); - assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 160); - assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 240); - } - - #[test] - fn cost_720p_batch_2() { - assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Two), 128); - assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 160); - assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Two), 480); - } - - #[test] - fn cost_720p_batch_4() { - assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Four), 256); - assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 320); - assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Four), 960); - } - - // -- 480p (new pricing: 193 credits/$1, 15 credits/sec) -- - - #[test] - fn cost_480p_batch_1() { - assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One), 31); - assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 39); - assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One), 78); - assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One), 117); - } - - #[test] - fn cost_480p_batch_2() { - assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Two), 78); - } - - #[test] - fn cost_480p_batch_4() { - assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 155); - } - - // -- 1080p (new pricing: 193 credits/$1, 90 credits/sec) -- - - #[test] - fn cost_1080p_batch_1() { - assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 4, KinoviBatchCount::One), 187); - assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::One), 233); - assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 10, KinoviBatchCount::One), 466); - assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 15, KinoviBatchCount::One), 699); - } - - #[test] - fn cost_1080p_batch_2() { - assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::Two), 466); - } - - #[test] - fn cost_1080p_batch_4() { - assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::Four), 933); - } - - // -- Relative pricing -- - - #[test] - fn cost_480p_cheaper_than_720p_cheaper_than_1080p() { - let c480 = usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One); - let c720 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One); - let c1080 = usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::One); - assert!(c480 < c720, "480p ({}) should be cheaper than 720p ({})", c480, c720); - assert!(c720 < c1080, "720p ({}) should be cheaper than 1080p ({})", c720, c1080); - } - - #[test] - fn cost_scales_with_duration() { - let c4 = usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One); - let c10 = usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One); - let c15 = usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One); - assert!(c4 < c10); - assert!(c10 < c15); - } - - #[test] - fn cost_scales_with_batch() { - let b1 = usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::One); - let b2 = usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::Two); - let b4 = usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::Four); - assert!(b1 < b2); - assert!(b2 < b4); - } - - // -- Video reference does NOT affect cost (yet) -- - - #[test] - fn video_reference_does_not_affect_cost() { - let base = KinoviSeedance2p0CostState { - resolution: KinoviOutputResolution::SevenTwentyP, - duration_seconds: 5, - batch_count: KinoviBatchCount::One, - has_video_reference: false, - }; - let without = base.estimate_cost(); - let with = KinoviSeedance2p0CostState { has_video_reference: true, ..base }.estimate_cost(); - assert_eq!(without.cost_in_usd_cents, with.cost_in_usd_cents); - assert_eq!(without.cost_in_credits, with.cost_in_credits); - } - - // ======================== from_request() tests ======================== - - #[test] - fn from_request_720p() { - let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, false); - let cost = KinoviSeedance2p0CostState::from_request(&req); - assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP)); - assert_eq!(cost.duration_seconds, 5); - assert!(matches!(cost.batch_count, KinoviBatchCount::One)); - assert!(!cost.has_video_reference); - assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80)); - } - - #[test] - fn from_request_none_defaults_to_720p() { - let req = make_request_state(None, 5, KinoviBatchCount::One, false); - let cost = KinoviSeedance2p0CostState::from_request(&req); - assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP)); - assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80)); - } - - #[test] - fn from_request_480p() { - let req = make_request_state(Some(KinoviOutputResolution::FourEightyP), 5, KinoviBatchCount::One, false); - let cost = KinoviSeedance2p0CostState::from_request(&req); - assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(39)); - } - - #[test] - fn from_request_1080p_batch_2() { - let req = make_request_state(Some(KinoviOutputResolution::TenEightyP), 5, KinoviBatchCount::Two, false); - let cost = KinoviSeedance2p0CostState::from_request(&req); - assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(466)); - } - - #[test] - fn from_request_with_video_reference() { - let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, true); - let cost = KinoviSeedance2p0CostState::from_request(&req); - assert!(cost.has_video_reference); - // Video refs don't affect cost yet - assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80)); - } - - #[test] - fn from_request_without_video_reference() { - let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, false); - let cost = KinoviSeedance2p0CostState::from_request(&req); - assert!(!cost.has_video_reference); - } - - // ======================== from_draft() tests ======================== - - #[test] - fn from_draft_720p_default() { - let draft = make_draft(5, 1, None, false); - let cost = KinoviSeedance2p0CostState::from_draft(&draft); - assert!(matches!(cost.resolution, KinoviOutputResolution::SevenTwentyP)); - assert_eq!(cost.duration_seconds, 5); - assert!(matches!(cost.batch_count, KinoviBatchCount::One)); - assert!(!cost.has_video_reference); - assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80)); - } - - #[test] - fn from_draft_480p() { - let draft = make_draft(5, 1, Some(CommonResolution::FourEightyP), false); - let cost = KinoviSeedance2p0CostState::from_draft(&draft); - assert!(matches!(cost.resolution, KinoviOutputResolution::FourEightyP)); - assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(39)); - } - - #[test] - fn from_draft_1080p_batch_4() { - let draft = make_draft(5, 4, Some(CommonResolution::TenEightyP), false); - let cost = KinoviSeedance2p0CostState::from_draft(&draft); - assert!(matches!(cost.resolution, KinoviOutputResolution::TenEightyP)); - assert!(matches!(cost.batch_count, KinoviBatchCount::Four)); - assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(933)); - } - - #[test] - fn from_draft_with_video_reference() { - let draft = make_draft(5, 1, None, true); - let cost = KinoviSeedance2p0CostState::from_draft(&draft); - assert!(cost.has_video_reference); - // Video refs don't affect cost yet - assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(80)); - } - - #[test] - fn from_draft_without_video_reference() { - let draft = make_draft(5, 1, None, false); - let cost = KinoviSeedance2p0CostState::from_draft(&draft); - assert!(!cost.has_video_reference); - } - - #[test] - fn from_draft_duration_15_batch_2() { - let draft = make_draft(15, 2, Some(CommonResolution::SevenTwentyP), false); - let cost = KinoviSeedance2p0CostState::from_draft(&draft); - assert_eq!(cost.duration_seconds, 15); - assert!(matches!(cost.batch_count, KinoviBatchCount::Two)); - assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(480)); - } - - // ======================== Cross-check: from_draft matches from_request ======================== - - #[test] - fn draft_and_request_produce_same_cost() { - // 720p, 5s, batch 1 - let draft = make_draft(5, 1, Some(CommonResolution::SevenTwentyP), false); - let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, false); - let draft_cost = KinoviSeedance2p0CostState::from_draft(&draft).estimate_cost(); - let req_cost = KinoviSeedance2p0CostState::from_request(&req).estimate_cost(); - assert_eq!(draft_cost.cost_in_usd_cents, req_cost.cost_in_usd_cents); - assert_eq!(draft_cost.cost_in_credits, req_cost.cost_in_credits); - } - - #[test] - fn draft_and_request_produce_same_cost_1080p() { - let draft = make_draft(10, 2, Some(CommonResolution::TenEightyP), false); - let req = make_request_state(Some(KinoviOutputResolution::TenEightyP), 10, KinoviBatchCount::Two, false); - let draft_cost = KinoviSeedance2p0CostState::from_draft(&draft).estimate_cost(); - let req_cost = KinoviSeedance2p0CostState::from_request(&req).estimate_cost(); - assert_eq!(draft_cost.cost_in_usd_cents, req_cost.cost_in_usd_cents); - assert_eq!(draft_cost.cost_in_credits, req_cost.cost_in_credits); - } - - // ======================== Credits spot checks ======================== - - #[test] - fn credits_720p() { - assert_eq!(credits(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 200); - } - - #[test] - fn credits_480p() { - assert_eq!(credits(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 75); - } - - #[test] - fn credits_1080p() { - assert_eq!(credits(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::One), 450); - } } diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0_fast/build.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0_fast/build.rs index 4f2aa069de..acc1fa6e63 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0_fast/build.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0_fast/build.rs @@ -229,326 +229,322 @@ mod tests { use super::*; - fn seedance2pro_fast_builder() -> GenerateVideoRequestBuilder { - GenerateVideoRequestBuilder { - model: CommonVideoModel::Seedance2p0Fast, - provider: Provider::Seedance2Pro, - prompt: Some("a cat dancing".to_string()), - duration_seconds: Some(5), - video_batch_count: Some(1), - ..Default::default() - } - } - - fn unwrap_draft(result: Result) -> KinoviSeedance2p0FastDraftState { - match result.expect("build should succeed") { - VideoGenerationDraftOrRequest::Draft( - VideoGenerationDraftRequest::KinoviSeedance2p0Fast(draft) - ) => draft, - _ => panic!("expected KinoviSeedance2p0Fast draft"), - } - } - // ── Materialized field conversions ── - #[test] - fn prompt_is_passed_through() { - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(seedance2pro_fast_builder())); - assert_eq!(draft.prompt, "a cat dancing"); - } + mod materialized_field_conversions { + use super::*; - #[test] - fn prompt_defaults_to_empty() { - let builder = GenerateVideoRequestBuilder { prompt: None, ..seedance2pro_fast_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert_eq!(draft.prompt, ""); - } + #[test] + fn prompt_is_passed_through() { + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(seedance2pro_fast_builder())); + assert_eq!(draft.prompt, "a cat dancing"); + } - #[test] - fn duration_seconds_converted() { - let builder = GenerateVideoRequestBuilder { duration_seconds: Some(10), ..seedance2pro_fast_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert_eq!(draft.duration_seconds, 10); - } + #[test] + fn prompt_defaults_to_empty() { + let builder = GenerateVideoRequestBuilder { prompt: None, ..seedance2pro_fast_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert_eq!(draft.prompt, ""); + } - #[test] - fn duration_defaults_to_5() { - let builder = GenerateVideoRequestBuilder { duration_seconds: None, ..seedance2pro_fast_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert_eq!(draft.duration_seconds, 5); - } + #[test] + fn duration_seconds_converted() { + let builder = GenerateVideoRequestBuilder { duration_seconds: Some(10), ..seedance2pro_fast_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert_eq!(draft.duration_seconds, 10); + } - #[test] - fn duration_clamped_to_max() { - let builder = GenerateVideoRequestBuilder { - duration_seconds: Some(99), - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert_eq!(draft.duration_seconds, 15); - } + #[test] + fn duration_defaults_to_5() { + let builder = GenerateVideoRequestBuilder { duration_seconds: None, ..seedance2pro_fast_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert_eq!(draft.duration_seconds, 5); + } - #[test] - fn batch_count_one() { - let builder = GenerateVideoRequestBuilder { video_batch_count: Some(1), ..seedance2pro_fast_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert!(matches!(draft.batch_count, KinoviBatchCount::One)); - } + #[test] + fn duration_clamped_to_max() { + let builder = GenerateVideoRequestBuilder { + duration_seconds: Some(99), + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert_eq!(draft.duration_seconds, 15); + } - #[test] - fn batch_count_two() { - let builder = GenerateVideoRequestBuilder { video_batch_count: Some(2), ..seedance2pro_fast_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert!(matches!(draft.batch_count, KinoviBatchCount::Two)); - } + #[test] + fn batch_count_one() { + let builder = GenerateVideoRequestBuilder { video_batch_count: Some(1), ..seedance2pro_fast_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert!(matches!(draft.batch_count, KinoviBatchCount::One)); + } - #[test] - fn batch_count_four() { - let builder = GenerateVideoRequestBuilder { video_batch_count: Some(4), ..seedance2pro_fast_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert!(matches!(draft.batch_count, KinoviBatchCount::Four)); + #[test] + fn batch_count_two() { + let builder = GenerateVideoRequestBuilder { video_batch_count: Some(2), ..seedance2pro_fast_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert!(matches!(draft.batch_count, KinoviBatchCount::Two)); + } + + #[test] + fn batch_count_four() { + let builder = GenerateVideoRequestBuilder { video_batch_count: Some(4), ..seedance2pro_fast_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert!(matches!(draft.batch_count, KinoviBatchCount::Four)); + } } // ── Aspect ratio conversions ── - #[test] - fn aspect_ratio_wide() { - let builder = GenerateVideoRequestBuilder { - aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine), - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Landscape16x9)); - } + mod aspect_ratio_conversions { + use super::*; - #[test] - fn aspect_ratio_tall() { - let builder = GenerateVideoRequestBuilder { - aspect_ratio: Some(CommonAspectRatio::TallNineBySixteen), - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Portrait9x16)); - } + #[test] + fn aspect_ratio_wide() { + let builder = GenerateVideoRequestBuilder { + aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine), + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Landscape16x9)); + } - #[test] - fn aspect_ratio_square() { - let builder = GenerateVideoRequestBuilder { - aspect_ratio: Some(CommonAspectRatio::Square), - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Square1x1)); - } + #[test] + fn aspect_ratio_tall() { + let builder = GenerateVideoRequestBuilder { + aspect_ratio: Some(CommonAspectRatio::TallNineBySixteen), + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Portrait9x16)); + } - #[test] - fn aspect_ratio_defaults_to_landscape() { - let builder = GenerateVideoRequestBuilder { aspect_ratio: None, ..seedance2pro_fast_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Landscape16x9)); + #[test] + fn aspect_ratio_square() { + let builder = GenerateVideoRequestBuilder { + aspect_ratio: Some(CommonAspectRatio::Square), + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Square1x1)); + } + + #[test] + fn aspect_ratio_defaults_to_landscape() { + let builder = GenerateVideoRequestBuilder { aspect_ratio: None, ..seedance2pro_fast_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert!(matches!(draft.aspect_ratio, KinoviAspectRatio::Landscape16x9)); + } } // ── Resolution conversions ── - #[test] - fn resolution_480p() { - let builder = GenerateVideoRequestBuilder { - resolution: Some(CommonResolution::FourEightyP), - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert!(matches!(draft.resolution, Some(KinoviOutputResolution::FourEightyP))); - } + mod resolution_conversions { + use super::*; - #[test] - fn resolution_720p() { - let builder = GenerateVideoRequestBuilder { - resolution: Some(CommonResolution::SevenTwentyP), - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP))); - } + #[test] + fn resolution_480p() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(CommonResolution::FourEightyP), + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert!(matches!(draft.resolution, Some(KinoviOutputResolution::FourEightyP))); + } - #[test] - fn resolution_1080p_downgrades_to_720p() { - let builder = GenerateVideoRequestBuilder { - resolution: Some(CommonResolution::TenEightyP), - request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayLessDowngrade, - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP))); - } + #[test] + fn resolution_720p() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(CommonResolution::SevenTwentyP), + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP))); + } - #[test] - fn resolution_1080p_upgrade_also_downgrades_to_720p() { - let builder = GenerateVideoRequestBuilder { - resolution: Some(CommonResolution::TenEightyP), - request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP))); - } + #[test] + fn resolution_1080p_downgrades_to_720p() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(CommonResolution::TenEightyP), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayLessDowngrade, + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP))); + } - #[test] - fn resolution_1080p_error_out() { - let builder = GenerateVideoRequestBuilder { - resolution: Some(CommonResolution::TenEightyP), - request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, - ..seedance2pro_fast_builder() - }; - assert!(build_kinovi_seedance_2p0_fast(builder).is_err()); - } + #[test] + fn resolution_1080p_upgrade_also_downgrades_to_720p() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(CommonResolution::TenEightyP), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP))); + } - #[test] - fn resolution_none() { - let builder = GenerateVideoRequestBuilder { resolution: None, ..seedance2pro_fast_builder() }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert!(draft.resolution.is_none()); - } + #[test] + fn resolution_1080p_error_out() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(CommonResolution::TenEightyP), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, + ..seedance2pro_fast_builder() + }; + assert!(build_kinovi_seedance_2p0_fast(builder).is_err()); + } - #[test] - fn unsupported_resolution_error_out() { - let builder = GenerateVideoRequestBuilder { - resolution: Some(CommonResolution::FourK), - request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, - ..seedance2pro_fast_builder() - }; - assert!(build_kinovi_seedance_2p0_fast(builder).is_err()); - } + #[test] + fn resolution_none() { + let builder = GenerateVideoRequestBuilder { resolution: None, ..seedance2pro_fast_builder() }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert!(draft.resolution.is_none()); + } - #[test] - fn unsupported_resolution_rounds_to_720p() { - let builder = GenerateVideoRequestBuilder { - resolution: Some(CommonResolution::FourK), - request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP))); + #[test] + fn unsupported_resolution_error_out() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(CommonResolution::FourK), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut, + ..seedance2pro_fast_builder() + }; + assert!(build_kinovi_seedance_2p0_fast(builder).is_err()); + } + + #[test] + fn unsupported_resolution_rounds_to_720p() { + let builder = GenerateVideoRequestBuilder { + resolution: Some(CommonResolution::FourK), + request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade, + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + assert!(matches!(draft.resolution, Some(KinoviOutputResolution::SevenTwentyP))); + } } // ── unhandled_request_state: media refs are placed there ── - #[test] - fn unhandled_state_is_present() { - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(seedance2pro_fast_builder())); - assert!(draft.unhandled_request_state.is_some()); - } + mod unhandled_request_state { + use super::*; - #[test] - fn start_frame_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - start_frame: Some(ImageRef::Url("https://example.com/start.jpg".to_string())), - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - assert!(matches!(remaining.start_frame, Some(ImageRef::Url(url)) if url == "https://example.com/start.jpg")); - } - - #[test] - fn end_frame_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - end_frame: Some(ImageRef::Url("https://example.com/end.jpg".to_string())), - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - assert!(matches!(remaining.end_frame, Some(ImageRef::Url(url)) if url == "https://example.com/end.jpg")); - } - - #[test] - fn start_frame_media_token_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - start_frame: Some(ImageRef::MediaFileToken(MediaFileToken::new("mf_test123".to_string()))), - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - assert!(matches!(remaining.start_frame, Some(ImageRef::MediaFileToken(t)) if t.as_str() == "mf_test123")); - } - - #[test] - fn reference_images_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - reference_images: Some(ImageListRef::Urls(vec![ - "https://example.com/ref1.jpg".to_string(), - "https://example.com/ref2.jpg".to_string(), - ])), - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - match remaining.reference_images { - Some(ImageListRef::Urls(urls)) => { - assert_eq!(urls.len(), 2); - assert_eq!(urls[0], "https://example.com/ref1.jpg"); - assert_eq!(urls[1], "https://example.com/ref2.jpg"); - } - _ => panic!("expected Urls variant"), + #[test] + fn unhandled_state_is_present() { + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(seedance2pro_fast_builder())); + assert!(draft.unhandled_request_state.is_some()); } - } - #[test] - fn reference_videos_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - reference_videos: Some(VideoListRef::Urls(vec![ - "https://example.com/vid.mp4".to_string(), - ])), - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - assert!(matches!(remaining.reference_videos, Some(VideoListRef::Urls(urls)) if urls.len() == 1)); - } - - #[test] - fn reference_audio_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - reference_audio: Some(AudioListRef::Urls(vec![ - "https://example.com/audio.mp3".to_string(), - ])), - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - assert!(matches!(remaining.reference_audio, Some(AudioListRef::Urls(urls)) if urls.len() == 1)); - } - - #[test] - fn character_tokens_placed_in_unhandled() { - let builder = GenerateVideoRequestBuilder { - reference_character_tokens: Some(CharacterListRef::CharacterTokens(vec![ - CharacterToken::new("char_abc".to_string()), - CharacterToken::new("char_def".to_string()), - ])), - ..seedance2pro_fast_builder() - }; - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); - let remaining = draft.unhandled_request_state.unwrap(); - match remaining.reference_character_tokens { - Some(CharacterListRef::CharacterTokens(tokens)) => { - assert_eq!(tokens.len(), 2); - assert_eq!(tokens[0].as_str(), "char_abc"); - assert_eq!(tokens[1].as_str(), "char_def"); - } - _ => panic!("expected CharacterTokens variant"), + #[test] + fn start_frame_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + start_frame: Some(ImageRef::Url("https://example.com/start.jpg".to_string())), + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(matches!(remaining.start_frame, Some(ImageRef::Url(url)) if url == "https://example.com/start.jpg")); } - } - #[test] - fn empty_refs_are_none_in_unhandled() { - let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(seedance2pro_fast_builder())); - let remaining = draft.unhandled_request_state.unwrap(); - assert!(remaining.start_frame.is_none()); - assert!(remaining.end_frame.is_none()); - assert!(remaining.reference_images.is_none()); - assert!(remaining.reference_videos.is_none()); - assert!(remaining.reference_audio.is_none()); - assert!(remaining.reference_character_tokens.is_none()); + #[test] + fn end_frame_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + end_frame: Some(ImageRef::Url("https://example.com/end.jpg".to_string())), + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(matches!(remaining.end_frame, Some(ImageRef::Url(url)) if url == "https://example.com/end.jpg")); + } + + #[test] + fn start_frame_media_token_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + start_frame: Some(ImageRef::MediaFileToken(MediaFileToken::new("mf_test123".to_string()))), + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(matches!(remaining.start_frame, Some(ImageRef::MediaFileToken(t)) if t.as_str() == "mf_test123")); + } + + #[test] + fn reference_images_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + reference_images: Some(ImageListRef::Urls(vec![ + "https://example.com/ref1.jpg".to_string(), + "https://example.com/ref2.jpg".to_string(), + ])), + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + match remaining.reference_images { + Some(ImageListRef::Urls(urls)) => { + assert_eq!(urls.len(), 2); + assert_eq!(urls[0], "https://example.com/ref1.jpg"); + assert_eq!(urls[1], "https://example.com/ref2.jpg"); + } + _ => panic!("expected Urls variant"), + } + } + + #[test] + fn reference_videos_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + reference_videos: Some(VideoListRef::Urls(vec![ + "https://example.com/vid.mp4".to_string(), + ])), + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(matches!(remaining.reference_videos, Some(VideoListRef::Urls(urls)) if urls.len() == 1)); + } + + #[test] + fn reference_audio_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + reference_audio: Some(AudioListRef::Urls(vec![ + "https://example.com/audio.mp3".to_string(), + ])), + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(matches!(remaining.reference_audio, Some(AudioListRef::Urls(urls)) if urls.len() == 1)); + } + + #[test] + fn character_tokens_placed_in_unhandled() { + let builder = GenerateVideoRequestBuilder { + reference_character_tokens: Some(CharacterListRef::CharacterTokens(vec![ + CharacterToken::new("char_abc".to_string()), + CharacterToken::new("char_def".to_string()), + ])), + ..seedance2pro_fast_builder() + }; + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(builder)); + let remaining = draft.unhandled_request_state.unwrap(); + match remaining.reference_character_tokens { + Some(CharacterListRef::CharacterTokens(tokens)) => { + assert_eq!(tokens.len(), 2); + assert_eq!(tokens[0].as_str(), "char_abc"); + assert_eq!(tokens[1].as_str(), "char_def"); + } + _ => panic!("expected CharacterTokens variant"), + } + } + + #[test] + fn empty_refs_are_none_in_unhandled() { + let draft = unwrap_draft(build_kinovi_seedance_2p0_fast(seedance2pro_fast_builder())); + let remaining = draft.unhandled_request_state.unwrap(); + assert!(remaining.start_frame.is_none()); + assert!(remaining.end_frame.is_none()); + assert!(remaining.reference_images.is_none()); + assert!(remaining.reference_videos.is_none()); + assert!(remaining.reference_audio.is_none()); + assert!(remaining.reference_character_tokens.is_none()); + } } // ── Full combination ── @@ -587,4 +583,26 @@ mod tests { assert!(remaining.reference_audio.is_some()); assert!(remaining.reference_character_tokens.is_some()); } + + // ── Helpers ── + + fn seedance2pro_fast_builder() -> GenerateVideoRequestBuilder { + GenerateVideoRequestBuilder { + model: CommonVideoModel::Seedance2p0Fast, + provider: Provider::Seedance2Pro, + prompt: Some("a cat dancing".to_string()), + duration_seconds: Some(5), + video_batch_count: Some(1), + ..Default::default() + } + } + + fn unwrap_draft(result: Result) -> KinoviSeedance2p0FastDraftState { + match result.expect("build should succeed") { + VideoGenerationDraftOrRequest::Draft( + VideoGenerationDraftRequest::KinoviSeedance2p0Fast(draft) + ) => draft, + _ => panic!("expected KinoviSeedance2p0Fast draft"), + } + } } diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0_fast/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0_fast/cost.rs index 24a73e537e..92de98a101 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0_fast/cost.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0_fast/cost.rs @@ -138,29 +138,33 @@ mod tests { // -- Relative pricing -- - #[test] - fn cost_480p_cheaper_than_720p() { - let c480 = usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One); - let c720 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One); - assert!(c480 < c720, "480p ({}) should be cheaper than 720p ({})", c480, c720); - } + mod relative_pricing_tests { + use super::*; - #[test] - fn cost_scales_with_duration() { - let c4 = usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One); - let c10 = usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One); - let c15 = usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One); - assert!(c4 < c10); - assert!(c10 < c15); - } + #[test] + fn cost_480p_cheaper_than_720p() { + let c480 = usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One); + let c720 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One); + assert!(c480 < c720, "480p ({}) should be cheaper than 720p ({})", c480, c720); + } - #[test] - fn cost_scales_with_batch() { - let b1 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One); - let b2 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two); - let b4 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four); - assert!(b1 < b2); - assert!(b2 < b4); + #[test] + fn cost_scales_with_duration() { + let c4 = usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One); + let c10 = usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One); + let c15 = usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One); + assert!(c4 < c10); + assert!(c10 < c15); + } + + #[test] + fn cost_scales_with_batch() { + let b1 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One); + let b2 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two); + let b4 = usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four); + assert!(b1 < b2); + assert!(b2 < b4); + } } // -- Video reference does NOT affect cost (yet) -- @@ -306,14 +310,18 @@ mod tests { // ── Credits spot checks ── - #[test] - fn credits_720p() { - assert_eq!(credits(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 140); - } + mod credits_tests { + use super::*; - #[test] - fn credits_480p() { - assert_eq!(credits(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 50); + #[test] + fn credits_720p() { + assert_eq!(credits(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 140); + } + + #[test] + fn credits_480p() { + assert_eq!(credits(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 50); + } } // ── Helpers ──