pub mod request;
pub mod response;
#[cfg(test)]
mod tests {
use crate::videos::request::{CreateVideoOptions, InputReference, VideoModel, VideoSeconds, VideoSize, VideoVariant};
use crate::videos::response::{DeleteVideoResponse, Video, VideoListResponse, VideoStatus};
#[test]
fn test_video_model_as_str() {
assert_eq!(VideoModel::Sora2.as_str(), "sora-2");
assert_eq!(VideoModel::Sora2Pro.as_str(), "sora-2-pro");
}
#[test]
fn test_video_model_default_is_sora2() {
assert_eq!(VideoModel::default(), VideoModel::Sora2);
}
#[test]
fn test_video_model_serialization() {
for (model, expected) in [(VideoModel::Sora2, "sora-2"), (VideoModel::Sora2Pro, "sora-2-pro")] {
let json = serde_json::to_string(&model).unwrap();
assert_eq!(json, format!("\"{}\"", expected), "Wrong serialization for {:?}", model);
}
}
#[test]
fn test_video_size_as_str() {
assert_eq!(VideoSize::Size720x1280.as_str(), "720x1280");
assert_eq!(VideoSize::Size1280x720.as_str(), "1280x720");
assert_eq!(VideoSize::Size1024x1792.as_str(), "1024x1792");
assert_eq!(VideoSize::Size1792x1024.as_str(), "1792x1024");
}
#[test]
fn test_video_size_default_is_portrait() {
assert_eq!(VideoSize::default(), VideoSize::Size720x1280);
}
#[test]
fn test_video_seconds_serializes_as_string() {
for (seconds, expected) in [(VideoSeconds::Four, "4"), (VideoSeconds::Eight, "8"), (VideoSeconds::Twelve, "12")] {
assert_eq!(seconds.as_str(), expected);
let json = serde_json::to_string(&seconds).unwrap();
assert_eq!(json, format!("\"{}\"", expected), "seconds must serialize as a JSON string, got {}", json);
}
}
#[test]
fn test_video_seconds_default_is_four() {
assert_eq!(VideoSeconds::default(), VideoSeconds::Four);
}
#[test]
fn test_video_variant_as_str() {
assert_eq!(VideoVariant::Video.as_str(), "video");
assert_eq!(VideoVariant::Thumbnail.as_str(), "thumbnail");
assert_eq!(VideoVariant::Spritesheet.as_str(), "spritesheet");
}
#[test]
fn test_create_request_omits_unset_options() {
let body = CreateVideoOptions::default().into_request("A red balloon");
let value = serde_json::to_value(&body).unwrap();
assert_eq!(value.get("prompt").and_then(|v| v.as_str()), Some("A red balloon"));
assert!(value.get("model").is_none(), "model must be omitted when unset");
assert!(value.get("size").is_none(), "size must be omitted when unset");
assert!(value.get("seconds").is_none(), "seconds must be omitted when unset");
assert!(value.get("input_reference").is_none(), "input_reference must be omitted when unset");
}
#[test]
fn test_create_request_serializes_all_options() {
let options = CreateVideoOptions {
model: Some(VideoModel::Sora2Pro),
size: Some(VideoSize::Size1280x720),
seconds: Some(VideoSeconds::Twelve),
input_reference: None,
};
let value = serde_json::to_value(options.into_request("A cat")).unwrap();
assert_eq!(value.get("model").and_then(|v| v.as_str()), Some("sora-2-pro"));
assert_eq!(value.get("size").and_then(|v| v.as_str()), Some("1280x720"));
assert_eq!(value.get("seconds").and_then(|v| v.as_str()), Some("12"));
}
#[test]
fn test_create_request_with_file_input_reference() {
let options = CreateVideoOptions { input_reference: Some(InputReference::file_id("file-abc123")), ..Default::default() };
let value = serde_json::to_value(options.into_request("A cat")).unwrap();
let reference = value.get("input_reference").expect("input_reference should be present");
assert_eq!(reference.get("file_id").and_then(|v| v.as_str()), Some("file-abc123"));
assert!(reference.get("image_url").is_none(), "only the set variant should be serialized");
}
#[test]
fn test_create_request_with_image_url_input_reference() {
let options = CreateVideoOptions { input_reference: Some(InputReference::image_url("https://example.com/a.png")), ..Default::default() };
let value = serde_json::to_value(options.into_request("A cat")).unwrap();
let reference = value.get("input_reference").expect("input_reference should be present");
assert_eq!(reference.get("image_url").and_then(|v| v.as_str()), Some("https://example.com/a.png"));
assert!(reference.get("file_id").is_none(), "only the set variant should be serialized");
}
#[test]
fn test_video_deserialization_queued() {
let json = r#"{
"id": "video_abc123",
"object": "video",
"model": "sora-2",
"status": "queued",
"progress": 0,
"prompt": "A red balloon",
"created_at": 1760000000,
"completed_at": null,
"expires_at": null,
"remixed_from_video_id": null,
"seconds": "4",
"size": "720x1280",
"error": null
}"#;
let video: Video = serde_json::from_str(json).expect("Should deserialize Video");
assert_eq!(video.id, "video_abc123");
assert_eq!(video.object, "video");
assert_eq!(video.status, VideoStatus::Queued);
assert_eq!(video.progress, 0);
assert_eq!(video.prompt.as_deref(), Some("A red balloon"));
assert_eq!(video.seconds.as_deref(), Some("4"));
assert!(video.error.is_none());
assert!(!video.is_terminal(), "a queued job has not settled");
assert!(!video.is_completed());
}
#[test]
fn test_video_deserialization_completed() {
let json = r#"{
"id": "video_abc123",
"object": "video",
"model": "sora-2-pro",
"status": "completed",
"progress": 100,
"prompt": "A red balloon",
"created_at": 1760000000,
"completed_at": 1760000600,
"expires_at": 1760600000,
"remixed_from_video_id": null,
"seconds": "8",
"size": "1280x720"
}"#;
let video: Video = serde_json::from_str(json).expect("Should deserialize completed Video");
assert_eq!(video.status, VideoStatus::Completed);
assert_eq!(video.progress, 100);
assert_eq!(video.completed_at, Some(1760000600));
assert!(video.is_terminal());
assert!(video.is_completed());
}
#[test]
fn test_video_deserialization_failed_with_error() {
let json = r#"{
"id": "video_abc123",
"object": "video",
"model": "sora-2",
"status": "failed",
"progress": 0,
"created_at": 1760000000,
"seconds": "4",
"size": "720x1280",
"error": { "code": "content_policy_violation", "message": "Rejected by the safety system" }
}"#;
let video: Video = serde_json::from_str(json).expect("Should deserialize failed Video");
assert_eq!(video.status, VideoStatus::Failed);
assert!(video.is_terminal(), "a failed job has settled");
assert!(!video.is_completed());
let error = video.error.expect("error details should be present");
assert_eq!(error.code, "content_policy_violation");
assert_eq!(error.message, "Rejected by the safety system");
}
#[test]
fn test_video_status_in_progress() {
let json = r#"{
"id": "video_abc123",
"object": "video",
"model": "sora-2",
"status": "in_progress",
"progress": 42,
"created_at": 1760000000,
"seconds": "4",
"size": "720x1280"
}"#;
let video: Video = serde_json::from_str(json).expect("Should deserialize in-progress Video");
assert_eq!(video.status, VideoStatus::InProgress);
assert_eq!(video.progress, 42);
assert!(!video.is_terminal());
}
#[test]
fn test_unknown_video_status_is_preserved() {
let json = r#"{
"id": "video_abc123",
"object": "video",
"model": "sora-2",
"status": "cancelled",
"progress": 0,
"created_at": 1760000000,
"seconds": "4",
"size": "720x1280"
}"#;
let video: Video = serde_json::from_str(json).expect("Unknown status should still deserialize");
assert_eq!(video.status, VideoStatus::Other("cancelled".to_string()));
assert!(!video.is_completed());
}
#[test]
fn test_video_list_response_deserialization() {
let json = r#"{
"object": "list",
"data": [],
"first_id": null,
"last_id": null,
"has_more": false
}"#;
let response: VideoListResponse = serde_json::from_str(json).expect("Should deserialize VideoListResponse");
assert_eq!(response.object, "list");
assert!(response.data.is_empty());
assert!(!response.has_more);
assert!(response.first_id.is_none());
}
#[test]
fn test_delete_video_response_deserialization() {
let json = r#"{
"id": "video_abc123",
"object": "video.deleted",
"deleted": true
}"#;
let response: DeleteVideoResponse = serde_json::from_str(json).expect("Should deserialize DeleteVideoResponse");
assert_eq!(response.id, "video_abc123");
assert_eq!(response.object, "video.deleted");
assert!(response.deleted);
}
}