mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-02 16:38:14 +00:00
tests
This commit is contained in:
1 parent
479d27cd49
commit
74f0fb1731
9 files changed
+1089
No files matched your search
+205
@@ -194,3 +194,208 @@ fn resolve_image_urls(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::api::common_image_model::CommonImageModel;
|
||||
use crate::api::provider::Provider;
|
||||
|
||||
fn base_builder() -> GenerateImageRequestBuilder {
|
||||
GenerateImageRequestBuilder {
|
||||
model: CommonImageModel::GptImage1,
|
||||
provider: Provider::Fal,
|
||||
prompt: Some("a cat in space".to_string()),
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
horizontal_angle: None,
|
||||
vertical_angle: None,
|
||||
zoom: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_t2i(result: Result<ImageGenerationDraftOrRequest, ArtcraftRouterError>) -> GptImage1TextToImageRequest {
|
||||
let ImageGenerationDraftOrRequest::Request(
|
||||
ImageGenerationRequest::FalGptImage1(
|
||||
FalGptImage1RequestState::TextToImage(req)
|
||||
)
|
||||
) = result.expect("build should succeed") else {
|
||||
panic!("expected GPT Image 1 text-to-image request")
|
||||
};
|
||||
req
|
||||
}
|
||||
|
||||
fn unwrap_edit(result: Result<ImageGenerationDraftOrRequest, ArtcraftRouterError>) -> GptImage1EditImageRequest {
|
||||
let ImageGenerationDraftOrRequest::Request(
|
||||
ImageGenerationRequest::FalGptImage1(
|
||||
FalGptImage1RequestState::EditImage(req)
|
||||
)
|
||||
) = result.expect("build should succeed") else {
|
||||
panic!("expected GPT Image 1 edit-image request")
|
||||
};
|
||||
req
|
||||
}
|
||||
|
||||
fn assert_debug<T: Debug>(actual: T, expected: &str) {
|
||||
assert_eq!(format!("{:?}", actual), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build2_routes_to_gpt_image_1_request() {
|
||||
let req = unwrap_t2i(base_builder().build2());
|
||||
assert_eq!(req.prompt, "a cat in space");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode_detection_is_based_on_image_urls() {
|
||||
let text_req = unwrap_t2i(build_fal_gpt_image_1(base_builder()));
|
||||
assert_eq!(text_req.prompt, "a cat in space");
|
||||
|
||||
let edit_req = unwrap_edit(build_fal_gpt_image_1(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::Urls(vec!["https://example.com/img.jpg".to_string()])),
|
||||
..base_builder()
|
||||
}));
|
||||
assert_eq!(edit_req.image_urls, vec!["https://example.com/img.jpg"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn media_file_tokens_are_rejected() {
|
||||
let result = build_fal_gpt_image_1(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(vec![])),
|
||||
..base_builder()
|
||||
});
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_maps_exhaustively_for_text_and_edit() {
|
||||
let text_cases = [
|
||||
(1, "One"),
|
||||
(2, "Two"),
|
||||
(3, "Three"),
|
||||
(4, "Four"),
|
||||
];
|
||||
|
||||
for (count, expected) in text_cases {
|
||||
let req = unwrap_t2i(build_fal_gpt_image_1(GenerateImageRequestBuilder {
|
||||
image_batch_count: Some(count),
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.num_images, expected);
|
||||
}
|
||||
|
||||
for (count, expected) in text_cases {
|
||||
let req = unwrap_edit(build_fal_gpt_image_1(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::Urls(vec!["https://example.com/img.jpg".to_string()])),
|
||||
image_batch_count: Some(count),
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.num_images, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_rejects_zero_and_handles_overflow_by_strategy() {
|
||||
assert!(matches!(
|
||||
build_fal_gpt_image_1(GenerateImageRequestBuilder {
|
||||
image_batch_count: Some(0),
|
||||
..base_builder()
|
||||
}),
|
||||
Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations))
|
||||
));
|
||||
assert!(matches!(
|
||||
build_fal_gpt_image_1(GenerateImageRequestBuilder {
|
||||
image_batch_count: Some(5),
|
||||
..base_builder()
|
||||
}),
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
|
||||
));
|
||||
|
||||
let req = unwrap_t2i(build_fal_gpt_image_1(GenerateImageRequestBuilder {
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.num_images, "Four");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quality_maps_exhaustively_for_text_and_edit() {
|
||||
let cases = [
|
||||
(None, "Some(High)"),
|
||||
(Some(CommonQuality::Low), "Some(Low)"),
|
||||
(Some(CommonQuality::Medium), "Some(Medium)"),
|
||||
(Some(CommonQuality::High), "Some(High)"),
|
||||
];
|
||||
|
||||
for (quality, expected) in cases {
|
||||
let req = unwrap_t2i(build_fal_gpt_image_1(GenerateImageRequestBuilder {
|
||||
quality,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.quality, expected);
|
||||
}
|
||||
|
||||
for (quality, expected) in cases {
|
||||
let req = unwrap_edit(build_fal_gpt_image_1(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::Urls(vec!["https://example.com/img.jpg".to_string()])),
|
||||
quality,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.quality, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_maps_exhaustively_for_text_and_edit() {
|
||||
let cases = [
|
||||
(None, "None"),
|
||||
(Some(CommonAspectRatio::Auto), "None"),
|
||||
(Some(CommonAspectRatio::Auto2k), "None"),
|
||||
(Some(CommonAspectRatio::Auto3k), "None"),
|
||||
(Some(CommonAspectRatio::Auto4k), "None"),
|
||||
(Some(CommonAspectRatio::Square), "Some(Square)"),
|
||||
(Some(CommonAspectRatio::SquareHd), "Some(Square)"),
|
||||
(Some(CommonAspectRatio::WideThreeByTwo), "Some(Horizontal)"),
|
||||
(Some(CommonAspectRatio::WideFourByThree), "Some(Horizontal)"),
|
||||
(Some(CommonAspectRatio::WideFiveByFour), "Some(Horizontal)"),
|
||||
(Some(CommonAspectRatio::WideSixteenByNine), "Some(Horizontal)"),
|
||||
(Some(CommonAspectRatio::WideTwentyOneByNine), "Some(Horizontal)"),
|
||||
(Some(CommonAspectRatio::Wide), "Some(Horizontal)"),
|
||||
(Some(CommonAspectRatio::TallTwoByThree), "Some(Vertical)"),
|
||||
(Some(CommonAspectRatio::TallThreeByFour), "Some(Vertical)"),
|
||||
(Some(CommonAspectRatio::TallFourByFive), "Some(Vertical)"),
|
||||
(Some(CommonAspectRatio::TallNineBySixteen), "Some(Vertical)"),
|
||||
(Some(CommonAspectRatio::TallNineByTwentyOne), "Some(Vertical)"),
|
||||
(Some(CommonAspectRatio::Tall), "Some(Vertical)"),
|
||||
];
|
||||
|
||||
for (aspect_ratio, expected) in cases {
|
||||
let req = unwrap_t2i(build_fal_gpt_image_1(GenerateImageRequestBuilder {
|
||||
aspect_ratio,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.image_size, expected);
|
||||
}
|
||||
|
||||
for (aspect_ratio, expected) in cases {
|
||||
let req = unwrap_edit(build_fal_gpt_image_1(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::Urls(vec!["https://example.com/img.jpg".to_string()])),
|
||||
aspect_ratio,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.image_size, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -29,3 +29,93 @@ impl FalGptImage1CostState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use fal_client::requests::api::image::edit::gpt_image_1_edit_image::api::{
|
||||
GptImage1EditImageNumImages, GptImage1EditImageQuality, GptImage1EditImageRequest,
|
||||
GptImage1EditImageSize,
|
||||
};
|
||||
use fal_client::requests::api::image::text::gpt_image_1_text_to_image::api::{
|
||||
GptImage1TextToImageNumImages, GptImage1TextToImageQuality,
|
||||
GptImage1TextToImageRequest, GptImage1TextToImageSize,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn text_to_image_cost_spots() {
|
||||
let cases = [
|
||||
(
|
||||
GptImage1TextToImageNumImages::One,
|
||||
Some(GptImage1TextToImageQuality::High),
|
||||
Some(GptImage1TextToImageSize::Square),
|
||||
17,
|
||||
),
|
||||
(
|
||||
GptImage1TextToImageNumImages::Three,
|
||||
Some(GptImage1TextToImageQuality::Medium),
|
||||
Some(GptImage1TextToImageSize::Horizontal),
|
||||
21,
|
||||
),
|
||||
(
|
||||
GptImage1TextToImageNumImages::Four,
|
||||
Some(GptImage1TextToImageQuality::Low),
|
||||
Some(GptImage1TextToImageSize::Vertical),
|
||||
8,
|
||||
),
|
||||
];
|
||||
|
||||
for (num_images, quality, image_size, expected) in cases {
|
||||
let request = FalGptImage1RequestState::TextToImage(GptImage1TextToImageRequest {
|
||||
prompt: "test".to_string(),
|
||||
num_images,
|
||||
image_size,
|
||||
quality,
|
||||
background: None,
|
||||
output_format: None,
|
||||
});
|
||||
let cost = FalGptImage1CostState::from_request(&request).estimate_cost();
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(expected));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_image_cost_spots() {
|
||||
let cases = [
|
||||
(
|
||||
GptImage1EditImageNumImages::One,
|
||||
Some(GptImage1EditImageQuality::High),
|
||||
Some(GptImage1EditImageSize::Horizontal),
|
||||
25,
|
||||
),
|
||||
(
|
||||
GptImage1EditImageNumImages::Two,
|
||||
Some(GptImage1EditImageQuality::Medium),
|
||||
Some(GptImage1EditImageSize::Square),
|
||||
10,
|
||||
),
|
||||
(
|
||||
GptImage1EditImageNumImages::Four,
|
||||
Some(GptImage1EditImageQuality::Low),
|
||||
Some(GptImage1EditImageSize::Auto),
|
||||
8,
|
||||
),
|
||||
];
|
||||
|
||||
for (num_images, quality, image_size, expected) in cases {
|
||||
let request = FalGptImage1RequestState::EditImage(GptImage1EditImageRequest {
|
||||
prompt: "test".to_string(),
|
||||
image_urls: vec!["https://example.com/image.png".to_string()],
|
||||
num_images,
|
||||
mask_image_url: None,
|
||||
image_size,
|
||||
quality,
|
||||
input_fidelity: None,
|
||||
background: None,
|
||||
output_format: None,
|
||||
});
|
||||
let cost = FalGptImage1CostState::from_request(&request).estimate_cost();
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
@@ -68,3 +68,58 @@ async fn send_fal_request<T: FalEndpoint>(
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use fal_client::requests::api::image::edit::gpt_image_1_edit_image::api::{
|
||||
GptImage1EditImageNumImages, GptImage1EditImageQuality, GptImage1EditImageSize,
|
||||
};
|
||||
use fal_client::requests::api::image::text::gpt_image_1_text_to_image::api::{
|
||||
GptImage1TextToImageNumImages, GptImage1TextToImageQuality,
|
||||
GptImage1TextToImageSize,
|
||||
};
|
||||
use test_data::web::image_urls::JUNO_AT_LAKE_IMAGE_URL;
|
||||
|
||||
fn client_without_webhook() -> RouterFalWebhookOptionalClient {
|
||||
let secret = std::fs::read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")
|
||||
.expect("Failed to read fal_api_key.txt");
|
||||
RouterFalWebhookOptionalClient::new(FalApiKey::from_str(secret.trim()))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // requires real API key, incurs cost
|
||||
async fn send_text_to_image_queue() {
|
||||
let request = FalGptImage1RequestState::TextToImage(GptImage1TextToImageRequest {
|
||||
prompt: "a small ceramic robot reading by a window".to_string(),
|
||||
num_images: GptImage1TextToImageNumImages::One,
|
||||
image_size: Some(GptImage1TextToImageSize::Square),
|
||||
quality: Some(GptImage1TextToImageQuality::Low),
|
||||
background: None,
|
||||
output_format: None,
|
||||
});
|
||||
let response = request.send(&client_without_webhook()).await.expect("send should succeed");
|
||||
let payload = response.get_fal_payload().expect("expected Fal payload");
|
||||
assert!(payload.request_id.is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // requires real API key, incurs cost
|
||||
async fn send_edit_image_queue() {
|
||||
let request = FalGptImage1RequestState::EditImage(GptImage1EditImageRequest {
|
||||
prompt: "make the image feel like a quiet watercolor study".to_string(),
|
||||
image_urls: vec![JUNO_AT_LAKE_IMAGE_URL.to_string()],
|
||||
num_images: GptImage1EditImageNumImages::One,
|
||||
mask_image_url: None,
|
||||
image_size: Some(GptImage1EditImageSize::Square),
|
||||
quality: Some(GptImage1EditImageQuality::Low),
|
||||
input_fidelity: None,
|
||||
background: None,
|
||||
output_format: None,
|
||||
});
|
||||
let response = request.send(&client_without_webhook()).await.expect("send should succeed");
|
||||
let payload = response.get_fal_payload().expect("expected Fal payload");
|
||||
assert!(payload.request_id.is_some());
|
||||
}
|
||||
}
|
||||
+205
@@ -194,3 +194,208 @@ fn resolve_image_urls(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::api::common_image_model::CommonImageModel;
|
||||
use crate::api::provider::Provider;
|
||||
|
||||
fn base_builder() -> GenerateImageRequestBuilder {
|
||||
GenerateImageRequestBuilder {
|
||||
model: CommonImageModel::GptImage1p5,
|
||||
provider: Provider::Fal,
|
||||
prompt: Some("a cat in space".to_string()),
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
horizontal_angle: None,
|
||||
vertical_angle: None,
|
||||
zoom: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_t2i(result: Result<ImageGenerationDraftOrRequest, ArtcraftRouterError>) -> GptImage1p5TextToImageRequest {
|
||||
let ImageGenerationDraftOrRequest::Request(
|
||||
ImageGenerationRequest::FalGptImage1p5(
|
||||
FalGptImage1p5RequestState::TextToImage(req)
|
||||
)
|
||||
) = result.expect("build should succeed") else {
|
||||
panic!("expected GPT Image 1.5 text-to-image request")
|
||||
};
|
||||
req
|
||||
}
|
||||
|
||||
fn unwrap_edit(result: Result<ImageGenerationDraftOrRequest, ArtcraftRouterError>) -> GptImage1p5EditImageRequest {
|
||||
let ImageGenerationDraftOrRequest::Request(
|
||||
ImageGenerationRequest::FalGptImage1p5(
|
||||
FalGptImage1p5RequestState::EditImage(req)
|
||||
)
|
||||
) = result.expect("build should succeed") else {
|
||||
panic!("expected GPT Image 1.5 edit-image request")
|
||||
};
|
||||
req
|
||||
}
|
||||
|
||||
fn assert_debug<T: Debug>(actual: T, expected: &str) {
|
||||
assert_eq!(format!("{:?}", actual), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build2_routes_to_gpt_image_1p5_request() {
|
||||
let req = unwrap_t2i(base_builder().build2());
|
||||
assert_eq!(req.prompt, "a cat in space");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode_detection_is_based_on_image_urls() {
|
||||
let text_req = unwrap_t2i(build_fal_gpt_image_1p5(base_builder()));
|
||||
assert_eq!(text_req.prompt, "a cat in space");
|
||||
|
||||
let edit_req = unwrap_edit(build_fal_gpt_image_1p5(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::Urls(vec!["https://example.com/img.jpg".to_string()])),
|
||||
..base_builder()
|
||||
}));
|
||||
assert_eq!(edit_req.image_urls, vec!["https://example.com/img.jpg"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn media_file_tokens_are_rejected() {
|
||||
let result = build_fal_gpt_image_1p5(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(vec![])),
|
||||
..base_builder()
|
||||
});
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_maps_exhaustively_for_text_and_edit() {
|
||||
let cases = [
|
||||
(1, "One"),
|
||||
(2, "Two"),
|
||||
(3, "Three"),
|
||||
(4, "Four"),
|
||||
];
|
||||
|
||||
for (count, expected) in cases {
|
||||
let req = unwrap_t2i(build_fal_gpt_image_1p5(GenerateImageRequestBuilder {
|
||||
image_batch_count: Some(count),
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.num_images, expected);
|
||||
}
|
||||
|
||||
for (count, expected) in cases {
|
||||
let req = unwrap_edit(build_fal_gpt_image_1p5(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::Urls(vec!["https://example.com/img.jpg".to_string()])),
|
||||
image_batch_count: Some(count),
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.num_images, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_rejects_zero_and_handles_overflow_by_strategy() {
|
||||
assert!(matches!(
|
||||
build_fal_gpt_image_1p5(GenerateImageRequestBuilder {
|
||||
image_batch_count: Some(0),
|
||||
..base_builder()
|
||||
}),
|
||||
Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations))
|
||||
));
|
||||
assert!(matches!(
|
||||
build_fal_gpt_image_1p5(GenerateImageRequestBuilder {
|
||||
image_batch_count: Some(5),
|
||||
..base_builder()
|
||||
}),
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
|
||||
));
|
||||
|
||||
let req = unwrap_t2i(build_fal_gpt_image_1p5(GenerateImageRequestBuilder {
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.num_images, "Four");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quality_maps_exhaustively_for_text_and_edit() {
|
||||
let cases = [
|
||||
(None, "Some(High)"),
|
||||
(Some(CommonQuality::Low), "Some(Low)"),
|
||||
(Some(CommonQuality::Medium), "Some(Medium)"),
|
||||
(Some(CommonQuality::High), "Some(High)"),
|
||||
];
|
||||
|
||||
for (quality, expected) in cases {
|
||||
let req = unwrap_t2i(build_fal_gpt_image_1p5(GenerateImageRequestBuilder {
|
||||
quality,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.quality, expected);
|
||||
}
|
||||
|
||||
for (quality, expected) in cases {
|
||||
let req = unwrap_edit(build_fal_gpt_image_1p5(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::Urls(vec!["https://example.com/img.jpg".to_string()])),
|
||||
quality,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.quality, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_maps_exhaustively_for_text_and_edit() {
|
||||
let cases = [
|
||||
(None, "None"),
|
||||
(Some(CommonAspectRatio::Auto), "None"),
|
||||
(Some(CommonAspectRatio::Auto2k), "None"),
|
||||
(Some(CommonAspectRatio::Auto3k), "None"),
|
||||
(Some(CommonAspectRatio::Auto4k), "None"),
|
||||
(Some(CommonAspectRatio::Square), "Some(Square)"),
|
||||
(Some(CommonAspectRatio::SquareHd), "Some(Square)"),
|
||||
(Some(CommonAspectRatio::WideThreeByTwo), "Some(Wide)"),
|
||||
(Some(CommonAspectRatio::WideFourByThree), "Some(Wide)"),
|
||||
(Some(CommonAspectRatio::WideFiveByFour), "Some(Wide)"),
|
||||
(Some(CommonAspectRatio::WideSixteenByNine), "Some(Wide)"),
|
||||
(Some(CommonAspectRatio::WideTwentyOneByNine), "Some(Wide)"),
|
||||
(Some(CommonAspectRatio::Wide), "Some(Wide)"),
|
||||
(Some(CommonAspectRatio::TallTwoByThree), "Some(Tall)"),
|
||||
(Some(CommonAspectRatio::TallThreeByFour), "Some(Tall)"),
|
||||
(Some(CommonAspectRatio::TallFourByFive), "Some(Tall)"),
|
||||
(Some(CommonAspectRatio::TallNineBySixteen), "Some(Tall)"),
|
||||
(Some(CommonAspectRatio::TallNineByTwentyOne), "Some(Tall)"),
|
||||
(Some(CommonAspectRatio::Tall), "Some(Tall)"),
|
||||
];
|
||||
|
||||
for (aspect_ratio, expected) in cases {
|
||||
let req = unwrap_t2i(build_fal_gpt_image_1p5(GenerateImageRequestBuilder {
|
||||
aspect_ratio,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.image_size, expected);
|
||||
}
|
||||
|
||||
for (aspect_ratio, expected) in cases {
|
||||
let req = unwrap_edit(build_fal_gpt_image_1p5(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::Urls(vec!["https://example.com/img.jpg".to_string()])),
|
||||
aspect_ratio,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.image_size, expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
+90
@@ -29,3 +29,93 @@ impl FalGptImage1p5CostState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use fal_client::requests::api::image::edit::gpt_image_1p5_edit_image::api::{
|
||||
GptImage1p5EditImageNumImages, GptImage1p5EditImageQuality,
|
||||
GptImage1p5EditImageRequest, GptImage1p5EditImageSize,
|
||||
};
|
||||
use fal_client::requests::api::image::text::gpt_image_1p5_text_to_image::api::{
|
||||
GptImage1p5TextToImageNumImages, GptImage1p5TextToImageQuality,
|
||||
GptImage1p5TextToImageRequest, GptImage1p5TextToImageSize,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn text_to_image_cost_spots() {
|
||||
let cases = [
|
||||
(
|
||||
GptImage1p5TextToImageNumImages::One,
|
||||
Some(GptImage1p5TextToImageQuality::High),
|
||||
Some(GptImage1p5TextToImageSize::Square),
|
||||
13,
|
||||
),
|
||||
(
|
||||
GptImage1p5TextToImageNumImages::Three,
|
||||
Some(GptImage1p5TextToImageQuality::Medium),
|
||||
Some(GptImage1p5TextToImageSize::Wide),
|
||||
15,
|
||||
),
|
||||
(
|
||||
GptImage1p5TextToImageNumImages::Four,
|
||||
Some(GptImage1p5TextToImageQuality::Low),
|
||||
Some(GptImage1p5TextToImageSize::Tall),
|
||||
4,
|
||||
),
|
||||
];
|
||||
|
||||
for (num_images, quality, image_size, expected) in cases {
|
||||
let request = FalGptImage1p5RequestState::TextToImage(GptImage1p5TextToImageRequest {
|
||||
prompt: "test".to_string(),
|
||||
num_images,
|
||||
image_size,
|
||||
background: None,
|
||||
quality,
|
||||
output_format: None,
|
||||
});
|
||||
let cost = FalGptImage1p5CostState::from_request(&request).estimate_cost();
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(expected));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_image_cost_spots() {
|
||||
let cases = [
|
||||
(
|
||||
GptImage1p5EditImageNumImages::One,
|
||||
Some(GptImage1p5EditImageQuality::High),
|
||||
Some(GptImage1p5EditImageSize::Wide),
|
||||
20,
|
||||
),
|
||||
(
|
||||
GptImage1p5EditImageNumImages::Two,
|
||||
Some(GptImage1p5EditImageQuality::Medium),
|
||||
Some(GptImage1p5EditImageSize::Square),
|
||||
6,
|
||||
),
|
||||
(
|
||||
GptImage1p5EditImageNumImages::Four,
|
||||
Some(GptImage1p5EditImageQuality::Low),
|
||||
Some(GptImage1p5EditImageSize::Tall),
|
||||
4,
|
||||
),
|
||||
];
|
||||
|
||||
for (num_images, quality, image_size, expected) in cases {
|
||||
let request = FalGptImage1p5RequestState::EditImage(GptImage1p5EditImageRequest {
|
||||
prompt: "test".to_string(),
|
||||
image_urls: vec!["https://example.com/image.png".to_string()],
|
||||
num_images,
|
||||
mask_image_url: None,
|
||||
image_size,
|
||||
background: None,
|
||||
quality,
|
||||
input_fidelity: None,
|
||||
output_format: None,
|
||||
});
|
||||
let cost = FalGptImage1p5CostState::from_request(&request).estimate_cost();
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -68,3 +68,59 @@ async fn send_fal_request<T: FalEndpoint>(
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use fal_client::requests::api::image::edit::gpt_image_1p5_edit_image::api::{
|
||||
GptImage1p5EditImageNumImages, GptImage1p5EditImageQuality,
|
||||
GptImage1p5EditImageSize,
|
||||
};
|
||||
use fal_client::requests::api::image::text::gpt_image_1p5_text_to_image::api::{
|
||||
GptImage1p5TextToImageNumImages, GptImage1p5TextToImageQuality,
|
||||
GptImage1p5TextToImageSize,
|
||||
};
|
||||
use test_data::web::image_urls::JUNO_AT_LAKE_IMAGE_URL;
|
||||
|
||||
fn client_without_webhook() -> RouterFalWebhookOptionalClient {
|
||||
let secret = std::fs::read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")
|
||||
.expect("Failed to read fal_api_key.txt");
|
||||
RouterFalWebhookOptionalClient::new(FalApiKey::from_str(secret.trim()))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // requires real API key, incurs cost
|
||||
async fn send_text_to_image_queue() {
|
||||
let request = FalGptImage1p5RequestState::TextToImage(GptImage1p5TextToImageRequest {
|
||||
prompt: "a tiny brass observatory in a snowy field".to_string(),
|
||||
num_images: GptImage1p5TextToImageNumImages::One,
|
||||
image_size: Some(GptImage1p5TextToImageSize::Square),
|
||||
background: None,
|
||||
quality: Some(GptImage1p5TextToImageQuality::Low),
|
||||
output_format: None,
|
||||
});
|
||||
let response = request.send(&client_without_webhook()).await.expect("send should succeed");
|
||||
let payload = response.get_fal_payload().expect("expected Fal payload");
|
||||
assert!(payload.request_id.is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // requires real API key, incurs cost
|
||||
async fn send_edit_image_queue() {
|
||||
let request = FalGptImage1p5RequestState::EditImage(GptImage1p5EditImageRequest {
|
||||
prompt: "turn this into a softly lit editorial photograph".to_string(),
|
||||
image_urls: vec![JUNO_AT_LAKE_IMAGE_URL.to_string()],
|
||||
num_images: GptImage1p5EditImageNumImages::One,
|
||||
mask_image_url: None,
|
||||
image_size: Some(GptImage1p5EditImageSize::Square),
|
||||
background: None,
|
||||
quality: Some(GptImage1p5EditImageQuality::Low),
|
||||
input_fidelity: None,
|
||||
output_format: None,
|
||||
});
|
||||
let response = request.send(&client_without_webhook()).await.expect("send should succeed");
|
||||
let payload = response.get_fal_payload().expect("expected Fal payload");
|
||||
assert!(payload.request_id.is_some());
|
||||
}
|
||||
}
|
||||
+237
@@ -219,3 +219,240 @@ fn resolve_image_urls(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::api::common_image_model::CommonImageModel;
|
||||
use crate::api::provider::Provider;
|
||||
|
||||
fn base_builder() -> GenerateImageRequestBuilder {
|
||||
GenerateImageRequestBuilder {
|
||||
model: CommonImageModel::GptImage2,
|
||||
provider: Provider::Fal,
|
||||
prompt: Some("a cat in space".to_string()),
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
image_batch_count: None,
|
||||
horizontal_angle: None,
|
||||
vertical_angle: None,
|
||||
zoom: None,
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::ErrorOut,
|
||||
generation_mode_mismatch_strategy: None,
|
||||
idempotency_token: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_t2i(result: Result<ImageGenerationDraftOrRequest, ArtcraftRouterError>) -> GptImage2TextToImageRequest {
|
||||
let ImageGenerationDraftOrRequest::Request(
|
||||
ImageGenerationRequest::FalGptImage2(
|
||||
FalGptImage2RequestState::TextToImage(req)
|
||||
)
|
||||
) = result.expect("build should succeed") else {
|
||||
panic!("expected GPT Image 2 text-to-image request")
|
||||
};
|
||||
req
|
||||
}
|
||||
|
||||
fn unwrap_edit(result: Result<ImageGenerationDraftOrRequest, ArtcraftRouterError>) -> GptImage2EditImageRequest {
|
||||
let ImageGenerationDraftOrRequest::Request(
|
||||
ImageGenerationRequest::FalGptImage2(
|
||||
FalGptImage2RequestState::EditImage(req)
|
||||
)
|
||||
) = result.expect("build should succeed") else {
|
||||
panic!("expected GPT Image 2 edit-image request")
|
||||
};
|
||||
req
|
||||
}
|
||||
|
||||
fn assert_debug<T: Debug>(actual: T, expected: &str) {
|
||||
assert_eq!(format!("{:?}", actual), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build2_routes_to_gpt_image_2_request() {
|
||||
let req = unwrap_t2i(base_builder().build2());
|
||||
assert_eq!(req.prompt, "a cat in space");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mode_detection_is_based_on_image_urls() {
|
||||
let text_req = unwrap_t2i(build_fal_gpt_image_2(base_builder()));
|
||||
assert_eq!(text_req.prompt, "a cat in space");
|
||||
|
||||
let edit_req = unwrap_edit(build_fal_gpt_image_2(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::Urls(vec!["https://example.com/img.jpg".to_string()])),
|
||||
..base_builder()
|
||||
}));
|
||||
assert_eq!(edit_req.image_urls, vec!["https://example.com/img.jpg"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn media_file_tokens_are_rejected() {
|
||||
let result = build_fal_gpt_image_2(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(vec![])),
|
||||
..base_builder()
|
||||
});
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ArtcraftRouterError::Client(ClientError::FalOnlySupportsUrls))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_maps_exhaustively_for_text_and_edit() {
|
||||
let cases = [
|
||||
(1, "One"),
|
||||
(2, "Two"),
|
||||
(3, "Three"),
|
||||
(4, "Four"),
|
||||
];
|
||||
|
||||
for (count, expected) in cases {
|
||||
let req = unwrap_t2i(build_fal_gpt_image_2(GenerateImageRequestBuilder {
|
||||
image_batch_count: Some(count),
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.num_images, expected);
|
||||
}
|
||||
|
||||
for (count, expected) in cases {
|
||||
let req = unwrap_edit(build_fal_gpt_image_2(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::Urls(vec!["https://example.com/img.jpg".to_string()])),
|
||||
image_batch_count: Some(count),
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.num_images, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_images_rejects_zero_and_handles_overflow_by_strategy() {
|
||||
assert!(matches!(
|
||||
build_fal_gpt_image_2(GenerateImageRequestBuilder {
|
||||
image_batch_count: Some(0),
|
||||
..base_builder()
|
||||
}),
|
||||
Err(ArtcraftRouterError::Client(ClientError::UserRequestedZeroGenerations))
|
||||
));
|
||||
assert!(matches!(
|
||||
build_fal_gpt_image_2(GenerateImageRequestBuilder {
|
||||
image_batch_count: Some(5),
|
||||
..base_builder()
|
||||
}),
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { .. }))
|
||||
));
|
||||
|
||||
let req = unwrap_t2i(build_fal_gpt_image_2(GenerateImageRequestBuilder {
|
||||
image_batch_count: Some(5),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.num_images, "Four");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quality_maps_exhaustively_for_text_and_edit() {
|
||||
let cases = [
|
||||
(None, "Some(High)"),
|
||||
(Some(CommonQuality::Low), "Some(Low)"),
|
||||
(Some(CommonQuality::Medium), "Some(Medium)"),
|
||||
(Some(CommonQuality::High), "Some(High)"),
|
||||
];
|
||||
|
||||
for (quality, expected) in cases {
|
||||
let req = unwrap_t2i(build_fal_gpt_image_2(GenerateImageRequestBuilder {
|
||||
quality,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.quality, expected);
|
||||
}
|
||||
|
||||
for (quality, expected) in cases {
|
||||
let req = unwrap_edit(build_fal_gpt_image_2(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::Urls(vec!["https://example.com/img.jpg".to_string()])),
|
||||
quality,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.quality, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolution_maps_exhaustively_for_text_and_edit() {
|
||||
let cases = [
|
||||
(None, "None"),
|
||||
(Some(CommonResolution::HalfK), "Some(OneK)"),
|
||||
(Some(CommonResolution::FourEightyP), "Some(OneK)"),
|
||||
(Some(CommonResolution::SevenTwentyP), "Some(OneK)"),
|
||||
(Some(CommonResolution::OneK), "Some(OneK)"),
|
||||
(Some(CommonResolution::TenEightyP), "Some(TwoK)"),
|
||||
(Some(CommonResolution::TwoK), "Some(TwoK)"),
|
||||
(Some(CommonResolution::ThreeK), "Some(ThreeK)"),
|
||||
(Some(CommonResolution::FourK), "Some(FourK)"),
|
||||
];
|
||||
|
||||
for (resolution, expected) in cases {
|
||||
let req = unwrap_t2i(build_fal_gpt_image_2(GenerateImageRequestBuilder {
|
||||
resolution,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.resolution, expected);
|
||||
}
|
||||
|
||||
for (resolution, expected) in cases {
|
||||
let req = unwrap_edit(build_fal_gpt_image_2(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::Urls(vec!["https://example.com/img.jpg".to_string()])),
|
||||
resolution,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.resolution, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_maps_exhaustively_for_text_and_edit() {
|
||||
let cases = [
|
||||
(None, "None", "None"),
|
||||
(Some(CommonAspectRatio::Auto), "None", "Some(Auto)"),
|
||||
(Some(CommonAspectRatio::Auto2k), "None", "Some(Auto)"),
|
||||
(Some(CommonAspectRatio::Auto3k), "None", "Some(Auto)"),
|
||||
(Some(CommonAspectRatio::Auto4k), "None", "Some(Auto)"),
|
||||
(Some(CommonAspectRatio::Square), "Some(Square)", "Some(Square)"),
|
||||
(Some(CommonAspectRatio::SquareHd), "Some(SquareHd)", "Some(SquareHd)"),
|
||||
(Some(CommonAspectRatio::WideThreeByTwo), "Some(Landscape16x9)", "Some(Landscape16x9)"),
|
||||
(Some(CommonAspectRatio::WideFourByThree), "Some(Landscape4x3)", "Some(Landscape4x3)"),
|
||||
(Some(CommonAspectRatio::WideFiveByFour), "Some(Landscape4x3)", "Some(Landscape4x3)"),
|
||||
(Some(CommonAspectRatio::WideSixteenByNine), "Some(Landscape16x9)", "Some(Landscape16x9)"),
|
||||
(Some(CommonAspectRatio::WideTwentyOneByNine), "Some(Landscape16x9)", "Some(Landscape16x9)"),
|
||||
(Some(CommonAspectRatio::Wide), "Some(Landscape16x9)", "Some(Landscape16x9)"),
|
||||
(Some(CommonAspectRatio::TallTwoByThree), "Some(Portrait16x9)", "Some(Portrait16x9)"),
|
||||
(Some(CommonAspectRatio::TallThreeByFour), "Some(Portrait4x3)", "Some(Portrait4x3)"),
|
||||
(Some(CommonAspectRatio::TallFourByFive), "Some(Portrait4x3)", "Some(Portrait4x3)"),
|
||||
(Some(CommonAspectRatio::TallNineBySixteen), "Some(Portrait16x9)", "Some(Portrait16x9)"),
|
||||
(Some(CommonAspectRatio::TallNineByTwentyOne), "Some(Portrait16x9)", "Some(Portrait16x9)"),
|
||||
(Some(CommonAspectRatio::Tall), "Some(Portrait16x9)", "Some(Portrait16x9)"),
|
||||
];
|
||||
|
||||
for (aspect_ratio, expected_t2i, _) in cases {
|
||||
let req = unwrap_t2i(build_fal_gpt_image_2(GenerateImageRequestBuilder {
|
||||
aspect_ratio,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.image_size, expected_t2i);
|
||||
}
|
||||
|
||||
for (aspect_ratio, _, expected_edit) in cases {
|
||||
let req = unwrap_edit(build_fal_gpt_image_2(GenerateImageRequestBuilder {
|
||||
image_inputs: Some(ImageListRef::Urls(vec!["https://example.com/img.jpg".to_string()])),
|
||||
aspect_ratio,
|
||||
..base_builder()
|
||||
}));
|
||||
assert_debug(req.image_size, expected_edit);
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
@@ -29,3 +29,99 @@ impl FalGptImage2CostState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use fal_client::requests::api::image::common::gpt_image_2_resolution::GptImage2Resolution;
|
||||
use fal_client::requests::api::image::edit::gpt_image_2_edit_image::api::{
|
||||
GptImage2EditImageNumImages, GptImage2EditImageQuality, GptImage2EditImageRequest,
|
||||
GptImage2EditImageSize,
|
||||
};
|
||||
use fal_client::requests::api::image::text::gpt_image_2_text_to_image::api::{
|
||||
GptImage2TextToImageNumImages, GptImage2TextToImageQuality,
|
||||
GptImage2TextToImageRequest, GptImage2TextToImageSize,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn text_to_image_cost_spots() {
|
||||
let cases = [
|
||||
(
|
||||
GptImage2TextToImageNumImages::One,
|
||||
Some(GptImage2TextToImageQuality::High),
|
||||
Some(GptImage2TextToImageSize::Landscape4x3),
|
||||
Some(GptImage2Resolution::OneK),
|
||||
16,
|
||||
),
|
||||
(
|
||||
GptImage2TextToImageNumImages::Two,
|
||||
Some(GptImage2TextToImageQuality::Medium),
|
||||
Some(GptImage2TextToImageSize::Landscape16x9),
|
||||
Some(GptImage2Resolution::TwoK),
|
||||
11,
|
||||
),
|
||||
(
|
||||
GptImage2TextToImageNumImages::Four,
|
||||
Some(GptImage2TextToImageQuality::Low),
|
||||
Some(GptImage2TextToImageSize::Square),
|
||||
Some(GptImage2Resolution::FourK),
|
||||
10,
|
||||
),
|
||||
];
|
||||
|
||||
for (num_images, quality, image_size, resolution, expected) in cases {
|
||||
let request = FalGptImage2RequestState::TextToImage(GptImage2TextToImageRequest {
|
||||
prompt: "test".to_string(),
|
||||
num_images,
|
||||
image_size,
|
||||
resolution,
|
||||
quality,
|
||||
output_format: None,
|
||||
});
|
||||
let cost = FalGptImage2CostState::from_request(&request).estimate_cost();
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(expected));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edit_image_cost_spots() {
|
||||
let cases = [
|
||||
(
|
||||
GptImage2EditImageNumImages::One,
|
||||
Some(GptImage2EditImageQuality::High),
|
||||
Some(GptImage2EditImageSize::Portrait4x3),
|
||||
Some(GptImage2Resolution::TwoK),
|
||||
24,
|
||||
),
|
||||
(
|
||||
GptImage2EditImageNumImages::Three,
|
||||
Some(GptImage2EditImageQuality::Medium),
|
||||
Some(GptImage2EditImageSize::SquareHd),
|
||||
Some(GptImage2Resolution::TwoK),
|
||||
21,
|
||||
),
|
||||
(
|
||||
GptImage2EditImageNumImages::Four,
|
||||
Some(GptImage2EditImageQuality::Low),
|
||||
Some(GptImage2EditImageSize::Auto),
|
||||
Some(GptImage2Resolution::OneK),
|
||||
10,
|
||||
),
|
||||
];
|
||||
|
||||
for (num_images, quality, image_size, resolution, expected) in cases {
|
||||
let request = FalGptImage2RequestState::EditImage(GptImage2EditImageRequest {
|
||||
prompt: "test".to_string(),
|
||||
image_urls: vec!["https://example.com/image.png".to_string()],
|
||||
num_images,
|
||||
mask_url: None,
|
||||
image_size,
|
||||
resolution,
|
||||
quality,
|
||||
output_format: None,
|
||||
});
|
||||
let cost = FalGptImage2CostState::from_request(&request).estimate_cost();
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
@@ -68,3 +68,58 @@ async fn send_fal_request<T: FalEndpoint>(
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use fal_client::creds::fal_api_key::FalApiKey;
|
||||
use fal_client::requests::api::image::common::gpt_image_2_resolution::GptImage2Resolution;
|
||||
use fal_client::requests::api::image::edit::gpt_image_2_edit_image::api::{
|
||||
GptImage2EditImageNumImages, GptImage2EditImageQuality, GptImage2EditImageSize,
|
||||
};
|
||||
use fal_client::requests::api::image::text::gpt_image_2_text_to_image::api::{
|
||||
GptImage2TextToImageNumImages, GptImage2TextToImageQuality,
|
||||
GptImage2TextToImageSize,
|
||||
};
|
||||
use test_data::web::image_urls::JUNO_AT_LAKE_IMAGE_URL;
|
||||
|
||||
fn client_without_webhook() -> RouterFalWebhookOptionalClient {
|
||||
let secret = std::fs::read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")
|
||||
.expect("Failed to read fal_api_key.txt");
|
||||
RouterFalWebhookOptionalClient::new(FalApiKey::from_str(secret.trim()))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // requires real API key, incurs cost
|
||||
async fn send_text_to_image_queue() {
|
||||
let request = FalGptImage2RequestState::TextToImage(GptImage2TextToImageRequest {
|
||||
prompt: "a polished product photo of a glass compass".to_string(),
|
||||
num_images: GptImage2TextToImageNumImages::One,
|
||||
image_size: Some(GptImage2TextToImageSize::Square),
|
||||
resolution: Some(GptImage2Resolution::OneK),
|
||||
quality: Some(GptImage2TextToImageQuality::Low),
|
||||
output_format: None,
|
||||
});
|
||||
let response = request.send(&client_without_webhook()).await.expect("send should succeed");
|
||||
let payload = response.get_fal_payload().expect("expected Fal payload");
|
||||
assert!(payload.request_id.is_some());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // requires real API key, incurs cost
|
||||
async fn send_edit_image_queue() {
|
||||
let request = FalGptImage2RequestState::EditImage(GptImage2EditImageRequest {
|
||||
prompt: "make this look like an elegant magazine cover image".to_string(),
|
||||
image_urls: vec![JUNO_AT_LAKE_IMAGE_URL.to_string()],
|
||||
num_images: GptImage2EditImageNumImages::One,
|
||||
mask_url: None,
|
||||
image_size: Some(GptImage2EditImageSize::Square),
|
||||
resolution: Some(GptImage2Resolution::OneK),
|
||||
quality: Some(GptImage2EditImageQuality::Low),
|
||||
output_format: None,
|
||||
});
|
||||
let response = request.send(&client_without_webhook()).await.expect("send should succeed");
|
||||
let payload = response.get_fal_payload().expect("expected Fal payload");
|
||||
assert!(payload.request_id.is_some());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user