use platform_image::vector_engine::{ GPT_IMAGE_2_MODEL, ReferenceImage, VECTOR_ENGINE_PROVIDER, VectorEngineImageSettings, build_vector_engine_image_http_client, build_vector_engine_image_request_body, create_vector_engine_image_edit, vector_engine_images_edit_url, vector_engine_images_generation_url, }; use std::{ sync::{ Arc, atomic::{AtomicUsize, Ordering}, }, time::Duration, }; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, net::TcpListener, }; #[test] fn vector_engine_module_exposes_provider_protocol_helpers() { let settings = VectorEngineImageSettings { base_url: "https://vector.example/v1".to_string(), api_key: "test-key".to_string(), request_timeout_ms: 1_000, }; let body = build_vector_engine_image_request_body("雾海神殿", Some("文字,水印"), "16:9", 9, &[]); assert_eq!(GPT_IMAGE_2_MODEL, "gpt-image-2"); assert_eq!(VECTOR_ENGINE_PROVIDER, "vector-engine"); assert_eq!(body["model"], GPT_IMAGE_2_MODEL); assert_eq!(body["size"], "1536x1024"); assert_eq!(body["n"], 4); assert_eq!(body["prompt"], "雾海神殿\n避免:文字,水印"); assert_eq!( vector_engine_images_generation_url(&settings), "https://vector.example/v1/images/generations" ); assert_eq!( vector_engine_images_edit_url(&settings), "https://vector.example/v1/images/edits" ); } #[tokio::test] async fn vector_engine_image_edit_retries_send_timeout_once_and_succeeds() { let listener = TcpListener::bind("127.0.0.1:0") .await .expect("mock server should bind"); let server_addr = listener .local_addr() .expect("mock server address should be readable"); let request_count = Arc::new(AtomicUsize::new(0)); let request_count_for_server = Arc::clone(&request_count); let server = tokio::spawn(async move { loop { let Ok((mut stream, _)) = listener.accept().await else { break; }; let request_index = request_count_for_server.fetch_add(1, Ordering::SeqCst); tokio::spawn(async move { let mut buffer = [0_u8; 4096]; let _ = stream.read(&mut buffer).await; if request_index == 0 { tokio::time::sleep(Duration::from_millis(120)).await; return; } let body = r#"{"data":[{"b64_json":"iVBORw0KGgpyZXN0"}]}"#; let response = format!( "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\r\n{}", body.len(), body ); let _ = stream.write_all(response.as_bytes()).await; }); } }); let settings = VectorEngineImageSettings { base_url: format!("http://{server_addr}/v1"), api_key: "test-key".to_string(), request_timeout_ms: 40, }; let http_client = build_vector_engine_image_http_client(&settings).expect("client should build"); let reference_image = ReferenceImage { bytes: b"reference".to_vec(), mime_type: "image/png".to_string(), file_name: "reference.png".to_string(), }; let generated = create_vector_engine_image_edit( &http_client, &settings, "测试提示词", None, "1024x1024", &reference_image, "测试 VectorEngine 图片编辑失败", ) .await .expect("second attempt should return generated image"); assert_eq!(generated.images.len(), 1); assert_eq!(generated.images[0].mime_type, "image/png"); assert_eq!(request_count.load(Ordering::SeqCst), 2); server.abort(); }