mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-09 10:57:03 +08:00
refactor: 移除独立 hub/proxy/executor/gateway crate,统一为 gateway tunnel 架构
- 删除 aether-hub、aether-proxy 独立项目及其 Dockerfile/配置 - 删除 crates/aether-executor 和 crates/aether-gateway 全部模块 - 新增 apps/ 目录作为应用入口 - 将 hub 概念重构为 gateway tunnel transport - 将 executor 重构为 execution runtime - 新增 tunnel.rs 合约定义和 testkit tunnel/execution_runtime 模块 - 更新 Python 服务层和测试适配新架构命名
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn sample_transport(
|
||||
base_url: &str,
|
||||
provider_api_format: &str,
|
||||
) -> LocalVideoTaskTransport {
|
||||
let url = match provider_api_format {
|
||||
"openai:video" => format!("{base_url}/v1/videos"),
|
||||
"gemini:video" => {
|
||||
format!("{base_url}/v1beta/models/veo-3:predictLongRunning")
|
||||
}
|
||||
_ => panic!("unsupported provider api format"),
|
||||
};
|
||||
LocalVideoTaskTransport::from_plan(&sample_plan(&url, provider_api_format))
|
||||
.expect("transport should build")
|
||||
}
|
||||
|
||||
pub(super) fn sample_persistence(provider_api_format: &str) -> LocalVideoTaskPersistence {
|
||||
LocalVideoTaskPersistence {
|
||||
request_id: "request-123".to_string(),
|
||||
username: Some("user".to_string()),
|
||||
api_key_name: Some("primary".to_string()),
|
||||
client_api_format: provider_api_format.to_string(),
|
||||
provider_api_format: provider_api_format.to_string(),
|
||||
original_request_body: json!({
|
||||
"prompt": "hello",
|
||||
"seconds": 4,
|
||||
"resolution": "720p",
|
||||
"aspect_ratio": "16:9",
|
||||
"size": "1280x720"
|
||||
}),
|
||||
format_converted: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn sample_auth_context() -> GatewayControlAuthContext {
|
||||
GatewayControlAuthContext {
|
||||
user_id: "user-123".to_string(),
|
||||
api_key_id: "key-123".to_string(),
|
||||
balance_remaining: None,
|
||||
access_allowed: true,
|
||||
user_rate_limit: None,
|
||||
api_key_rate_limit: None,
|
||||
api_key_is_standalone: false,
|
||||
local_rejection: None,
|
||||
allowed_models: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn sample_plan(url: &str, provider_api_format: &str) -> ExecutionPlan {
|
||||
ExecutionPlan {
|
||||
request_id: "req-123".to_string(),
|
||||
candidate_id: None,
|
||||
provider_name: Some(
|
||||
provider_api_format
|
||||
.split(':')
|
||||
.next()
|
||||
.expect("provider name should exist")
|
||||
.to_string(),
|
||||
),
|
||||
provider_id: "provider-123".to_string(),
|
||||
endpoint_id: "endpoint-123".to_string(),
|
||||
key_id: "key-123".to_string(),
|
||||
method: "POST".to_string(),
|
||||
url: url.to_string(),
|
||||
headers: BTreeMap::from([(
|
||||
"authorization".to_string(),
|
||||
"Bearer upstream-key".to_string(),
|
||||
)]),
|
||||
content_type: Some("application/json".to_string()),
|
||||
content_encoding: None,
|
||||
body: RequestBody::from_json(json!({})),
|
||||
stream: false,
|
||||
client_api_format: provider_api_format.to_string(),
|
||||
provider_api_format: provider_api_format.to_string(),
|
||||
model_name: Some(
|
||||
if provider_api_format == "gemini:video" {
|
||||
"veo-3"
|
||||
} else {
|
||||
"sora-2"
|
||||
}
|
||||
.to_string(),
|
||||
),
|
||||
proxy: Some(ProxySnapshot {
|
||||
enabled: Some(false),
|
||||
mode: Some("direct".to_string()),
|
||||
node_id: None,
|
||||
label: None,
|
||||
url: None,
|
||||
extra: None,
|
||||
}),
|
||||
tls_profile: Some("chrome".to_string()),
|
||||
timeouts: Some(ExecutionTimeouts {
|
||||
connect_ms: Some(10_000),
|
||||
read_ms: Some(30_000),
|
||||
first_byte_ms: None,
|
||||
write_ms: Some(30_000),
|
||||
pool_ms: Some(10_000),
|
||||
total_ms: Some(300_000),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
use super::fixtures::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_service_builds_openai_cancel_follow_up_plan() {
|
||||
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
|
||||
local_task_id: "task-local-123".to_string(),
|
||||
upstream_task_id: "ext-video-task-123".to_string(),
|
||||
created_at_unix_secs: 1712345678,
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: Some("sora-2".to_string()),
|
||||
prompt: None,
|
||||
size: None,
|
||||
seconds: None,
|
||||
remixed_from_video_id: None,
|
||||
status: LocalVideoTaskStatus::Submitted,
|
||||
progress_percent: 0,
|
||||
completed_at_unix_secs: None,
|
||||
expires_at_unix_secs: None,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
video_url: None,
|
||||
persistence: sample_persistence("openai:video"),
|
||||
transport: sample_transport("https://api.openai.example", "openai:video"),
|
||||
}));
|
||||
|
||||
let follow_up = service
|
||||
.prepare_follow_up_sync_plan(
|
||||
"openai_video_cancel_sync",
|
||||
"/v1/videos/task-local-123/cancel",
|
||||
None,
|
||||
Some(&sample_auth_context()),
|
||||
"trace-openai-cancel-123",
|
||||
)
|
||||
.expect("follow-up plan should build");
|
||||
|
||||
assert_eq!(follow_up.plan.method, "DELETE");
|
||||
assert_eq!(
|
||||
follow_up.plan.url,
|
||||
"https://api.openai.example/v1/videos/ext-video-task-123"
|
||||
);
|
||||
assert_eq!(follow_up.plan.provider_api_format, "openai:video");
|
||||
assert!(follow_up.plan.body.json_body.is_none());
|
||||
assert_eq!(
|
||||
follow_up.report_kind.as_deref(),
|
||||
Some("openai_video_cancel_sync_finalize")
|
||||
);
|
||||
assert_eq!(
|
||||
follow_up
|
||||
.report_context
|
||||
.as_ref()
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|value| value.get("task_id"))
|
||||
.and_then(Value::as_str),
|
||||
Some("task-local-123")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_service_builds_openai_remix_follow_up_plan() {
|
||||
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
|
||||
local_task_id: "task-local-123".to_string(),
|
||||
upstream_task_id: "ext-video-task-123".to_string(),
|
||||
created_at_unix_secs: 1712345678,
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: Some("sora-2".to_string()),
|
||||
prompt: None,
|
||||
size: None,
|
||||
seconds: None,
|
||||
remixed_from_video_id: None,
|
||||
status: LocalVideoTaskStatus::Completed,
|
||||
progress_percent: 100,
|
||||
completed_at_unix_secs: Some(1712345688),
|
||||
expires_at_unix_secs: None,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
video_url: Some("https://cdn.example.com/original.mp4".to_string()),
|
||||
persistence: sample_persistence("openai:video"),
|
||||
transport: sample_transport("https://api.openai.example", "openai:video"),
|
||||
}));
|
||||
|
||||
let remix_body = json!({
|
||||
"prompt": "remix this",
|
||||
"model": "sora-2",
|
||||
});
|
||||
let follow_up = service
|
||||
.prepare_follow_up_sync_plan(
|
||||
"openai_video_remix_sync",
|
||||
"/v1/videos/task-local-123/remix",
|
||||
Some(&remix_body),
|
||||
Some(&sample_auth_context()),
|
||||
"trace-openai-remix-123",
|
||||
)
|
||||
.expect("follow-up plan should build");
|
||||
|
||||
assert_eq!(follow_up.plan.method, "POST");
|
||||
assert_eq!(
|
||||
follow_up.plan.url,
|
||||
"https://api.openai.example/v1/videos/ext-video-task-123/remix"
|
||||
);
|
||||
assert_eq!(follow_up.plan.provider_api_format, "openai:video");
|
||||
assert_eq!(follow_up.plan.body.json_body, Some(remix_body.clone()));
|
||||
assert_eq!(
|
||||
follow_up.report_kind.as_deref(),
|
||||
Some("openai_video_remix_sync_finalize")
|
||||
);
|
||||
assert_eq!(
|
||||
follow_up
|
||||
.report_context
|
||||
.as_ref()
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|value| value.get("task_id"))
|
||||
.and_then(Value::as_str),
|
||||
Some("task-local-123")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_service_builds_openai_delete_follow_up_plan() {
|
||||
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
|
||||
local_task_id: "task-local-123".to_string(),
|
||||
upstream_task_id: "ext-video-task-123".to_string(),
|
||||
created_at_unix_secs: 1712345678,
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: Some("sora-2".to_string()),
|
||||
prompt: None,
|
||||
size: None,
|
||||
seconds: None,
|
||||
remixed_from_video_id: None,
|
||||
status: LocalVideoTaskStatus::Completed,
|
||||
progress_percent: 100,
|
||||
completed_at_unix_secs: Some(1712345688),
|
||||
expires_at_unix_secs: None,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
video_url: None,
|
||||
persistence: sample_persistence("openai:video"),
|
||||
transport: sample_transport("https://api.openai.example", "openai:video"),
|
||||
}));
|
||||
|
||||
let follow_up = service
|
||||
.prepare_follow_up_sync_plan(
|
||||
"openai_video_delete_sync",
|
||||
"/v1/videos/task-local-123",
|
||||
None,
|
||||
Some(&sample_auth_context()),
|
||||
"trace-openai-delete-123",
|
||||
)
|
||||
.expect("follow-up plan should build");
|
||||
|
||||
assert_eq!(follow_up.plan.method, "DELETE");
|
||||
assert_eq!(
|
||||
follow_up.plan.url,
|
||||
"https://api.openai.example/v1/videos/ext-video-task-123"
|
||||
);
|
||||
assert_eq!(follow_up.plan.provider_api_format, "openai:video");
|
||||
assert!(follow_up.plan.body.json_body.is_none());
|
||||
assert_eq!(
|
||||
follow_up.report_kind.as_deref(),
|
||||
Some("openai_video_delete_sync_finalize")
|
||||
);
|
||||
assert_eq!(
|
||||
follow_up
|
||||
.report_context
|
||||
.as_ref()
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|value| value.get("task_id"))
|
||||
.and_then(Value::as_str),
|
||||
Some("task-local-123")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_service_builds_gemini_cancel_follow_up_plan() {
|
||||
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::Gemini(GeminiVideoTaskSeed {
|
||||
local_short_id: "localshort123".to_string(),
|
||||
upstream_operation_name: "operations/ext-video-123".to_string(),
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: "veo-3".to_string(),
|
||||
status: LocalVideoTaskStatus::Submitted,
|
||||
progress_percent: 0,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
metadata: json!({}),
|
||||
persistence: sample_persistence("gemini:video"),
|
||||
transport: sample_transport("https://generativelanguage.googleapis.com", "gemini:video"),
|
||||
}));
|
||||
|
||||
let follow_up = service
|
||||
.prepare_follow_up_sync_plan(
|
||||
"gemini_video_cancel_sync",
|
||||
"/v1beta/models/veo-3/operations/localshort123:cancel",
|
||||
None,
|
||||
Some(&sample_auth_context()),
|
||||
"trace-gemini-cancel-123",
|
||||
)
|
||||
.expect("follow-up plan should build");
|
||||
|
||||
assert_eq!(follow_up.plan.method, "POST");
|
||||
assert_eq!(
|
||||
follow_up.plan.url,
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/veo-3/operations/ext-video-123:cancel"
|
||||
);
|
||||
assert_eq!(follow_up.plan.provider_api_format, "gemini:video");
|
||||
assert_eq!(follow_up.plan.body.json_body, Some(json!({})));
|
||||
assert_eq!(
|
||||
follow_up.report_kind.as_deref(),
|
||||
Some("gemini_video_cancel_sync_finalize")
|
||||
);
|
||||
assert_eq!(
|
||||
follow_up
|
||||
.report_context
|
||||
.as_ref()
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|value| value.get("task_id"))
|
||||
.and_then(Value::as_str),
|
||||
Some("localshort123")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_service_builds_openai_read_refresh_plan() {
|
||||
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
|
||||
local_task_id: "task-local-123".to_string(),
|
||||
upstream_task_id: "ext-video-task-123".to_string(),
|
||||
created_at_unix_secs: 1712345678,
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: Some("sora-2".to_string()),
|
||||
prompt: None,
|
||||
size: None,
|
||||
seconds: None,
|
||||
remixed_from_video_id: None,
|
||||
status: LocalVideoTaskStatus::Submitted,
|
||||
progress_percent: 0,
|
||||
completed_at_unix_secs: None,
|
||||
expires_at_unix_secs: None,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
video_url: None,
|
||||
persistence: sample_persistence("openai:video"),
|
||||
transport: sample_transport("https://api.openai.example", "openai:video"),
|
||||
}));
|
||||
|
||||
let refresh = service
|
||||
.prepare_read_refresh_sync_plan(
|
||||
Some("openai"),
|
||||
"/v1/videos/task-local-123",
|
||||
"trace-openai-read-123",
|
||||
)
|
||||
.expect("read refresh plan should build");
|
||||
|
||||
assert_eq!(refresh.plan.method, "GET");
|
||||
assert_eq!(
|
||||
refresh.plan.url,
|
||||
"https://api.openai.example/v1/videos/ext-video-task-123"
|
||||
);
|
||||
assert!(refresh.plan.body.json_body.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_service_builds_gemini_read_refresh_plan() {
|
||||
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::Gemini(GeminiVideoTaskSeed {
|
||||
local_short_id: "localshort123".to_string(),
|
||||
upstream_operation_name: "operations/ext-video-123".to_string(),
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: "veo-3".to_string(),
|
||||
status: LocalVideoTaskStatus::Submitted,
|
||||
progress_percent: 0,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
metadata: json!({}),
|
||||
persistence: sample_persistence("gemini:video"),
|
||||
transport: sample_transport("https://generativelanguage.googleapis.com", "gemini:video"),
|
||||
}));
|
||||
|
||||
let refresh = service
|
||||
.prepare_read_refresh_sync_plan(
|
||||
Some("gemini"),
|
||||
"/v1beta/models/veo-3/operations/localshort123",
|
||||
"trace-gemini-read-123",
|
||||
)
|
||||
.expect("read refresh plan should build");
|
||||
|
||||
assert_eq!(refresh.plan.method, "GET");
|
||||
assert_eq!(
|
||||
refresh.plan.url,
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/veo-3/operations/ext-video-123"
|
||||
);
|
||||
assert!(refresh.plan.body.json_body.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_service_builds_poll_refresh_batch_for_active_tasks_only() {
|
||||
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
|
||||
local_task_id: "task-active-123".to_string(),
|
||||
upstream_task_id: "ext-video-task-123".to_string(),
|
||||
created_at_unix_secs: 1712345678,
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: Some("sora-2".to_string()),
|
||||
prompt: None,
|
||||
size: None,
|
||||
seconds: None,
|
||||
remixed_from_video_id: None,
|
||||
status: LocalVideoTaskStatus::Processing,
|
||||
progress_percent: 42,
|
||||
completed_at_unix_secs: None,
|
||||
expires_at_unix_secs: None,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
video_url: None,
|
||||
persistence: sample_persistence("openai:video"),
|
||||
transport: sample_transport("https://api.openai.example", "openai:video"),
|
||||
}));
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
|
||||
local_task_id: "task-completed-123".to_string(),
|
||||
upstream_task_id: "ext-video-task-999".to_string(),
|
||||
created_at_unix_secs: 1712345678,
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: Some("sora-2".to_string()),
|
||||
prompt: None,
|
||||
size: None,
|
||||
seconds: None,
|
||||
remixed_from_video_id: None,
|
||||
status: LocalVideoTaskStatus::Completed,
|
||||
progress_percent: 100,
|
||||
completed_at_unix_secs: Some(1712345688),
|
||||
expires_at_unix_secs: None,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
video_url: Some("https://cdn.example.com/ext-video-task-999.mp4".to_string()),
|
||||
persistence: sample_persistence("openai:video"),
|
||||
transport: sample_transport("https://api.openai.example", "openai:video"),
|
||||
}));
|
||||
|
||||
let batch = service.prepare_poll_refresh_batch(10, "trace-poller");
|
||||
|
||||
assert_eq!(batch.len(), 1);
|
||||
assert_eq!(batch[0].plan.method, "GET");
|
||||
assert_eq!(
|
||||
batch[0].plan.url,
|
||||
"https://api.openai.example/v1/videos/ext-video-task-123"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_video_task_store_persists_snapshots_across_service_rebuilds() {
|
||||
let store_path =
|
||||
std::env::temp_dir().join(format!("aether-video-task-store-{}.json", Uuid::new_v4()));
|
||||
let service =
|
||||
VideoTaskService::with_file_store(VideoTaskTruthSourceMode::RustAuthoritative, &store_path)
|
||||
.expect("file-backed service should build");
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
|
||||
local_task_id: "task-file-123".to_string(),
|
||||
upstream_task_id: "ext-video-task-123".to_string(),
|
||||
created_at_unix_secs: 1712345678,
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: Some("sora-2".to_string()),
|
||||
prompt: Some("hello".to_string()),
|
||||
size: None,
|
||||
seconds: None,
|
||||
remixed_from_video_id: None,
|
||||
status: LocalVideoTaskStatus::Submitted,
|
||||
progress_percent: 0,
|
||||
completed_at_unix_secs: None,
|
||||
expires_at_unix_secs: None,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
video_url: None,
|
||||
persistence: sample_persistence("openai:video"),
|
||||
transport: sample_transport("https://api.openai.example", "openai:video"),
|
||||
}));
|
||||
drop(service);
|
||||
|
||||
let reopened =
|
||||
VideoTaskService::with_file_store(VideoTaskTruthSourceMode::RustAuthoritative, &store_path)
|
||||
.expect("reopened file-backed service should build");
|
||||
let response = reopened
|
||||
.read_response(Some("openai"), "/v1/videos/task-file-123")
|
||||
.expect("persisted read response should exist");
|
||||
assert_eq!(response.status_code, 200);
|
||||
assert_eq!(
|
||||
response.body_json.get("id").and_then(Value::as_str),
|
||||
Some("task-file-123")
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_file(store_path);
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
use super::fixtures::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_service_projects_openai_status_into_local_read_response() {
|
||||
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
|
||||
local_task_id: "task-local-123".to_string(),
|
||||
upstream_task_id: "ext-video-task-123".to_string(),
|
||||
created_at_unix_secs: 1712345678,
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: Some("sora-2".to_string()),
|
||||
prompt: Some("hello".to_string()),
|
||||
size: None,
|
||||
seconds: None,
|
||||
remixed_from_video_id: None,
|
||||
status: LocalVideoTaskStatus::Submitted,
|
||||
progress_percent: 0,
|
||||
completed_at_unix_secs: None,
|
||||
expires_at_unix_secs: None,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
video_url: None,
|
||||
persistence: sample_persistence("openai:video"),
|
||||
transport: sample_transport("https://api.openai.example", "openai:video"),
|
||||
}));
|
||||
|
||||
assert!(service.project_openai_task_response(
|
||||
"task-local-123",
|
||||
json!({
|
||||
"status": "processing",
|
||||
"progress": 42
|
||||
})
|
||||
.as_object()
|
||||
.expect("provider body should be object"),
|
||||
));
|
||||
let processing = service
|
||||
.read_response(Some("openai"), "/v1/videos/task-local-123")
|
||||
.expect("processing read response should exist");
|
||||
assert_eq!(processing.status_code, 200);
|
||||
assert_eq!(
|
||||
processing.body_json.get("status"),
|
||||
Some(&json!("processing"))
|
||||
);
|
||||
assert_eq!(processing.body_json.get("progress"), Some(&json!(42)));
|
||||
|
||||
assert!(service.project_openai_task_response(
|
||||
"task-local-123",
|
||||
json!({
|
||||
"status": "failed",
|
||||
"progress": 100,
|
||||
"completed_at": 1712345688u64,
|
||||
"error": {
|
||||
"code": "upstream_failed",
|
||||
"message": "provider failed"
|
||||
}
|
||||
})
|
||||
.as_object()
|
||||
.expect("provider body should be object"),
|
||||
));
|
||||
let failed = service
|
||||
.read_response(Some("openai"), "/v1/videos/task-local-123")
|
||||
.expect("failed read response should exist");
|
||||
assert_eq!(failed.status_code, 200);
|
||||
assert_eq!(failed.body_json.get("status"), Some(&json!("failed")));
|
||||
assert_eq!(
|
||||
failed.body_json.get("completed_at"),
|
||||
Some(&json!(1712345688u64))
|
||||
);
|
||||
assert_eq!(
|
||||
failed
|
||||
.body_json
|
||||
.get("error")
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|value| value.get("code")),
|
||||
Some(&json!("upstream_failed"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_service_builds_openai_content_stream_plan_from_direct_video_url() {
|
||||
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
|
||||
local_task_id: "task-local-123".to_string(),
|
||||
upstream_task_id: "ext-video-task-123".to_string(),
|
||||
created_at_unix_secs: 1712345678,
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: Some("sora-2".to_string()),
|
||||
prompt: None,
|
||||
size: None,
|
||||
seconds: None,
|
||||
remixed_from_video_id: None,
|
||||
status: LocalVideoTaskStatus::Completed,
|
||||
progress_percent: 100,
|
||||
completed_at_unix_secs: Some(1712345688),
|
||||
expires_at_unix_secs: None,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
video_url: Some("https://cdn.example.com/ext-video-task-123.mp4".to_string()),
|
||||
persistence: sample_persistence("openai:video"),
|
||||
transport: sample_transport("https://api.openai.example", "openai:video"),
|
||||
}));
|
||||
|
||||
let action = service
|
||||
.prepare_openai_content_stream_action(
|
||||
"/v1/videos/task-local-123/content",
|
||||
Some("variant=video"),
|
||||
"trace-openai-content-123",
|
||||
)
|
||||
.expect("content action should exist");
|
||||
|
||||
let LocalVideoTaskContentAction::StreamPlan(plan) = action else {
|
||||
panic!("content action should be stream plan");
|
||||
};
|
||||
assert_eq!(plan.method, "GET");
|
||||
assert_eq!(plan.url, "https://cdn.example.com/ext-video-task-123.mp4");
|
||||
assert!(plan.headers.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_service_returns_processing_content_response_for_pending_openai_task() {
|
||||
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
|
||||
local_task_id: "task-local-123".to_string(),
|
||||
upstream_task_id: "ext-video-task-123".to_string(),
|
||||
created_at_unix_secs: 1712345678,
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: Some("sora-2".to_string()),
|
||||
prompt: None,
|
||||
size: None,
|
||||
seconds: None,
|
||||
remixed_from_video_id: None,
|
||||
status: LocalVideoTaskStatus::Processing,
|
||||
progress_percent: 42,
|
||||
completed_at_unix_secs: None,
|
||||
expires_at_unix_secs: None,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
video_url: None,
|
||||
persistence: sample_persistence("openai:video"),
|
||||
transport: sample_transport("https://api.openai.example", "openai:video"),
|
||||
}));
|
||||
|
||||
let action = service
|
||||
.prepare_openai_content_stream_action(
|
||||
"/v1/videos/task-local-123/content",
|
||||
Some("variant=video"),
|
||||
"trace-openai-content-processing-123",
|
||||
)
|
||||
.expect("content action should exist");
|
||||
|
||||
let LocalVideoTaskContentAction::Immediate {
|
||||
status_code,
|
||||
body_json,
|
||||
} = action
|
||||
else {
|
||||
panic!("content action should be immediate response");
|
||||
};
|
||||
assert_eq!(status_code, 202);
|
||||
assert_eq!(
|
||||
body_json,
|
||||
json!({"detail": "Video is still processing (status: processing)"})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_service_projects_gemini_status_into_local_read_response() {
|
||||
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::Gemini(GeminiVideoTaskSeed {
|
||||
local_short_id: "localshort123".to_string(),
|
||||
upstream_operation_name: "operations/ext-video-123".to_string(),
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: "veo-3".to_string(),
|
||||
status: LocalVideoTaskStatus::Submitted,
|
||||
progress_percent: 0,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
metadata: json!({}),
|
||||
persistence: sample_persistence("gemini:video"),
|
||||
transport: sample_transport("https://generativelanguage.googleapis.com", "gemini:video"),
|
||||
}));
|
||||
|
||||
assert!(service.project_gemini_task_response(
|
||||
"localshort123",
|
||||
json!({
|
||||
"done": false,
|
||||
"metadata": {
|
||||
"state": "PROCESSING"
|
||||
}
|
||||
})
|
||||
.as_object()
|
||||
.expect("provider body should be object"),
|
||||
));
|
||||
let processing = service
|
||||
.read_response(
|
||||
Some("gemini"),
|
||||
"/v1beta/models/veo-3/operations/localshort123",
|
||||
)
|
||||
.expect("processing read response should exist");
|
||||
assert_eq!(processing.status_code, 200);
|
||||
assert_eq!(processing.body_json.get("done"), Some(&json!(false)));
|
||||
assert_eq!(
|
||||
processing.body_json.get("metadata"),
|
||||
Some(&json!({"state": "PROCESSING"}))
|
||||
);
|
||||
|
||||
assert!(service.project_gemini_task_response(
|
||||
"localshort123",
|
||||
json!({
|
||||
"done": true,
|
||||
"response": {
|
||||
"generateVideoResponse": {
|
||||
"generatedSamples": [
|
||||
{"video": {"uri": "https://example.invalid/video.mp4"}}
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
.as_object()
|
||||
.expect("provider body should be object"),
|
||||
));
|
||||
let completed = service
|
||||
.read_response(
|
||||
Some("gemini"),
|
||||
"/v1beta/models/veo-3/operations/localshort123",
|
||||
)
|
||||
.expect("completed read response should exist");
|
||||
assert_eq!(completed.status_code, 200);
|
||||
assert_eq!(completed.body_json.get("done"), Some(&json!(true)));
|
||||
assert_eq!(
|
||||
completed
|
||||
.body_json
|
||||
.get("response")
|
||||
.and_then(|value| value.get("generateVideoResponse"))
|
||||
.and_then(|value| value.get("generatedSamples"))
|
||||
.and_then(Value::as_array)
|
||||
.and_then(|value| value.first())
|
||||
.and_then(|value| value.get("video"))
|
||||
.and_then(|value| value.get("uri")),
|
||||
Some(&json!("/v1beta/files/aev_localshort123:download?alt=media"))
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
use super::fixtures::*;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn openai_video_seed_preserves_existing_local_identity() {
|
||||
let provider_body = json!({"id": "ext-video-task-123"});
|
||||
let report_context = json!({
|
||||
"local_task_id": "task-local-123",
|
||||
"local_created_at": 1712345678u64,
|
||||
"model": "sora-2",
|
||||
"original_request_body": {
|
||||
"prompt": "hello",
|
||||
"size": "1024x1024",
|
||||
"seconds": "8"
|
||||
}
|
||||
});
|
||||
|
||||
let seed = LocalVideoTaskSeed::from_sync_finalize(
|
||||
"openai_video_create_sync_finalize",
|
||||
provider_body
|
||||
.as_object()
|
||||
.expect("provider body should be object"),
|
||||
report_context
|
||||
.as_object()
|
||||
.expect("report context should be object"),
|
||||
&sample_plan("https://api.openai.example/v1/videos", "openai:video"),
|
||||
)
|
||||
.expect("seed should build");
|
||||
|
||||
let mut patched_context = report_context
|
||||
.as_object()
|
||||
.expect("report context should be object")
|
||||
.clone();
|
||||
seed.apply_to_report_context(&mut patched_context);
|
||||
let client_body = seed.client_body_json();
|
||||
|
||||
assert_eq!(
|
||||
seed.success_report_kind(),
|
||||
"openai_video_create_sync_success"
|
||||
);
|
||||
assert_eq!(
|
||||
patched_context.get("local_task_id").and_then(Value::as_str),
|
||||
Some("task-local-123")
|
||||
);
|
||||
assert_eq!(
|
||||
patched_context
|
||||
.get("local_created_at")
|
||||
.and_then(Value::as_u64),
|
||||
Some(1712345678)
|
||||
);
|
||||
assert_eq!(
|
||||
client_body.get("id").and_then(Value::as_str),
|
||||
Some("task-local-123")
|
||||
);
|
||||
assert_eq!(
|
||||
client_body.get("created_at").and_then(Value::as_u64),
|
||||
Some(1712345678)
|
||||
);
|
||||
assert_eq!(
|
||||
client_body.get("model").and_then(Value::as_str),
|
||||
Some("sora-2")
|
||||
);
|
||||
assert_eq!(
|
||||
client_body.get("prompt").and_then(Value::as_str),
|
||||
Some("hello")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn openai_video_remix_seed_carries_source_task() {
|
||||
let provider_body = json!({"id": "ext-remix-task-123"});
|
||||
let report_context = json!({
|
||||
"task_id": "task-source-123",
|
||||
"original_request_body": {
|
||||
"prompt": "remix it"
|
||||
}
|
||||
});
|
||||
|
||||
let seed = LocalVideoTaskSeed::from_sync_finalize(
|
||||
"openai_video_remix_sync_finalize",
|
||||
provider_body
|
||||
.as_object()
|
||||
.expect("provider body should be object"),
|
||||
report_context
|
||||
.as_object()
|
||||
.expect("report context should be object"),
|
||||
&sample_plan(
|
||||
"https://api.openai.example/v1/videos/ext-source/remix",
|
||||
"openai:video",
|
||||
),
|
||||
)
|
||||
.expect("seed should build");
|
||||
let client_body = seed.client_body_json();
|
||||
|
||||
assert_eq!(
|
||||
seed.success_report_kind(),
|
||||
"openai_video_remix_sync_success"
|
||||
);
|
||||
assert_eq!(
|
||||
client_body
|
||||
.get("remixed_from_video_id")
|
||||
.and_then(Value::as_str),
|
||||
Some("task-source-123")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gemini_video_seed_generates_short_id_and_pending_operation_name() {
|
||||
let provider_body = json!({"name": "operations/ext-video-task-123"});
|
||||
let report_context = json!({
|
||||
"model": "veo-3"
|
||||
});
|
||||
|
||||
let seed = LocalVideoTaskSeed::from_sync_finalize(
|
||||
"gemini_video_create_sync_finalize",
|
||||
provider_body
|
||||
.as_object()
|
||||
.expect("provider body should be object"),
|
||||
report_context
|
||||
.as_object()
|
||||
.expect("report context should be object"),
|
||||
&sample_plan(
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/veo-3:predictLongRunning",
|
||||
"gemini:video",
|
||||
),
|
||||
)
|
||||
.expect("seed should build");
|
||||
let mut patched_context = Map::new();
|
||||
seed.apply_to_report_context(&mut patched_context);
|
||||
let client_body = seed.client_body_json();
|
||||
let local_short_id = patched_context
|
||||
.get("local_short_id")
|
||||
.and_then(Value::as_str)
|
||||
.expect("local_short_id should exist");
|
||||
|
||||
assert_eq!(
|
||||
seed.success_report_kind(),
|
||||
"gemini_video_create_sync_success"
|
||||
);
|
||||
assert_eq!(local_short_id.len(), 12);
|
||||
assert_eq!(
|
||||
client_body.get("name").and_then(Value::as_str),
|
||||
Some(format!("models/veo-3/operations/{local_short_id}").as_str())
|
||||
);
|
||||
assert_eq!(
|
||||
client_body.get("done").and_then(Value::as_bool),
|
||||
Some(false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_backed_video_truth_source_requires_inline_sync_report() {
|
||||
let provider_body = json!({"id": "ext-video-task-123"});
|
||||
let report_context = json!({});
|
||||
|
||||
let plan = VideoTaskTruthSourceMode::PythonSyncReport
|
||||
.prepare_sync_success(
|
||||
"openai_video_create_sync_finalize",
|
||||
provider_body
|
||||
.as_object()
|
||||
.expect("provider body should be object"),
|
||||
report_context
|
||||
.as_object()
|
||||
.expect("report context should be object"),
|
||||
&sample_plan("https://api.openai.example/v1/videos", "openai:video"),
|
||||
)
|
||||
.expect("plan should build");
|
||||
|
||||
assert_eq!(plan.report_mode(), VideoTaskSyncReportMode::InlineSync);
|
||||
assert_eq!(
|
||||
plan.success_report_kind(),
|
||||
"openai_video_create_sync_success"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_video_truth_source_can_background_success_report() {
|
||||
let provider_body = json!({"name": "operations/ext-video-task-123"});
|
||||
let report_context = json!({
|
||||
"model": "veo-3"
|
||||
});
|
||||
|
||||
let plan = VideoTaskTruthSourceMode::RustAuthoritative
|
||||
.prepare_sync_success(
|
||||
"gemini_video_create_sync_finalize",
|
||||
provider_body
|
||||
.as_object()
|
||||
.expect("provider body should be object"),
|
||||
report_context
|
||||
.as_object()
|
||||
.expect("report context should be object"),
|
||||
&sample_plan(
|
||||
"https://generativelanguage.googleapis.com/v1beta/models/veo-3:predictLongRunning",
|
||||
"gemini:video",
|
||||
),
|
||||
)
|
||||
.expect("plan should build");
|
||||
|
||||
assert_eq!(plan.report_mode(), VideoTaskSyncReportMode::Background);
|
||||
assert_eq!(
|
||||
plan.success_report_kind(),
|
||||
"gemini_video_create_sync_success"
|
||||
);
|
||||
let mut report_context = Map::new();
|
||||
plan.apply_to_report_context(&mut report_context);
|
||||
assert_eq!(
|
||||
report_context.get("rust_video_task_persisted"),
|
||||
Some(&Value::Bool(true))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_service_reads_openai_task_from_local_registry() {
|
||||
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
|
||||
let snapshot = LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
|
||||
local_task_id: "task-local-123".to_string(),
|
||||
upstream_task_id: "ext-video-task-123".to_string(),
|
||||
created_at_unix_secs: 1712345678,
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: Some("sora-2".to_string()),
|
||||
prompt: Some("hello".to_string()),
|
||||
size: None,
|
||||
seconds: None,
|
||||
remixed_from_video_id: None,
|
||||
status: LocalVideoTaskStatus::Submitted,
|
||||
progress_percent: 0,
|
||||
completed_at_unix_secs: None,
|
||||
expires_at_unix_secs: None,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
video_url: None,
|
||||
persistence: sample_persistence("openai:video"),
|
||||
transport: sample_transport("https://api.openai.example", "openai:video"),
|
||||
});
|
||||
service.record_snapshot(snapshot);
|
||||
|
||||
let response = service
|
||||
.read_response(Some("openai"), "/v1/videos/task-local-123")
|
||||
.expect("read response should exist");
|
||||
|
||||
assert_eq!(response.status_code, 200);
|
||||
assert_eq!(
|
||||
response.body_json.get("id").and_then(Value::as_str),
|
||||
Some("task-local-123")
|
||||
);
|
||||
assert_eq!(
|
||||
response.body_json.get("status").and_then(Value::as_str),
|
||||
Some("queued")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_authoritative_service_applies_cancel_and_delete_mutations() {
|
||||
let service = VideoTaskService::new(VideoTaskTruthSourceMode::RustAuthoritative);
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::OpenAi(OpenAiVideoTaskSeed {
|
||||
local_task_id: "task-local-123".to_string(),
|
||||
upstream_task_id: "ext-video-task-123".to_string(),
|
||||
created_at_unix_secs: 1712345678,
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: Some("sora-2".to_string()),
|
||||
prompt: None,
|
||||
size: None,
|
||||
seconds: None,
|
||||
remixed_from_video_id: None,
|
||||
status: LocalVideoTaskStatus::Submitted,
|
||||
progress_percent: 0,
|
||||
completed_at_unix_secs: None,
|
||||
expires_at_unix_secs: None,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
video_url: None,
|
||||
persistence: sample_persistence("openai:video"),
|
||||
transport: sample_transport("https://api.openai.example", "openai:video"),
|
||||
}));
|
||||
service.record_snapshot(LocalVideoTaskSnapshot::Gemini(GeminiVideoTaskSeed {
|
||||
local_short_id: "short12345678".to_string(),
|
||||
upstream_operation_name: "operations/ext-video-task-123".to_string(),
|
||||
user_id: Some("user-123".to_string()),
|
||||
api_key_id: Some("key-123".to_string()),
|
||||
model: "veo-3".to_string(),
|
||||
status: LocalVideoTaskStatus::Submitted,
|
||||
progress_percent: 0,
|
||||
error_code: None,
|
||||
error_message: None,
|
||||
metadata: json!({}),
|
||||
persistence: sample_persistence("gemini:video"),
|
||||
transport: sample_transport("https://generativelanguage.googleapis.com", "gemini:video"),
|
||||
}));
|
||||
|
||||
service.apply_finalize_mutation(
|
||||
"/v1/videos/task-local-123/cancel",
|
||||
"openai_video_cancel_sync_finalize",
|
||||
);
|
||||
let cancelled_openai = service
|
||||
.read_response(Some("openai"), "/v1/videos/task-local-123")
|
||||
.expect("openai read response should exist");
|
||||
assert_eq!(cancelled_openai.status_code, 404);
|
||||
assert_eq!(
|
||||
cancelled_openai.body_json,
|
||||
json!({"detail": "Video task was cancelled"})
|
||||
);
|
||||
|
||||
service.apply_finalize_mutation(
|
||||
"/v1beta/models/veo-3/operations/short12345678:cancel",
|
||||
"gemini_video_cancel_sync_finalize",
|
||||
);
|
||||
let cancelled_gemini = service
|
||||
.read_response(
|
||||
Some("gemini"),
|
||||
"/v1beta/models/veo-3/operations/short12345678",
|
||||
)
|
||||
.expect("gemini read response should exist");
|
||||
assert_eq!(cancelled_gemini.status_code, 404);
|
||||
assert_eq!(
|
||||
cancelled_gemini.body_json,
|
||||
json!({"detail": "Video task was cancelled"})
|
||||
);
|
||||
|
||||
service.apply_finalize_mutation(
|
||||
"/v1/videos/task-local-123",
|
||||
"openai_video_delete_sync_finalize",
|
||||
);
|
||||
let deleted_openai = service
|
||||
.read_response(Some("openai"), "/v1/videos/task-local-123")
|
||||
.expect("deleted openai read response should exist");
|
||||
assert_eq!(deleted_openai.status_code, 404);
|
||||
assert_eq!(
|
||||
deleted_openai.body_json,
|
||||
json!({"detail": "Video task not found"})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seed_captures_transport_metadata_from_execution_plan() {
|
||||
let provider_body = json!({"id": "ext-video-task-123"});
|
||||
let report_context = json!({});
|
||||
|
||||
let seed = LocalVideoTaskSeed::from_sync_finalize(
|
||||
"openai_video_create_sync_finalize",
|
||||
provider_body
|
||||
.as_object()
|
||||
.expect("provider body should be object"),
|
||||
report_context
|
||||
.as_object()
|
||||
.expect("report context should be object"),
|
||||
&sample_plan("https://api.openai.example/v1/videos", "openai:video"),
|
||||
)
|
||||
.expect("seed should build");
|
||||
|
||||
let LocalVideoTaskSeed::OpenAiCreate(seed) = seed else {
|
||||
panic!("seed should be openai");
|
||||
};
|
||||
assert_eq!(
|
||||
seed.transport.upstream_base_url,
|
||||
"https://api.openai.example"
|
||||
);
|
||||
assert_eq!(seed.transport.provider_id, "provider-123");
|
||||
assert_eq!(seed.transport.endpoint_id, "endpoint-123");
|
||||
assert_eq!(seed.transport.key_id, "key-123");
|
||||
assert_eq!(
|
||||
seed.transport
|
||||
.headers
|
||||
.get("authorization")
|
||||
.map(String::as_str),
|
||||
Some("Bearer upstream-key")
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user