Merge branch 'fawney19:main' into main

This commit is contained in:
ZheFox
2026-05-29 12:27:38 +08:00
committed by GitHub
45 changed files with 1192 additions and 206 deletions
@@ -5,7 +5,9 @@ use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::time::{Duration, Instant};
use sqlx::{migrate::AppliedMigration, query, query_scalar, Connection, PgConnection, PgPool};
use sqlx::{
migrate::AppliedMigration, query, query_scalar, Connection, PgConnection, PgPool, SqlitePool,
};
use super::{
all_up_migrations, pending_migrations_from_applied, prepare_database_for_startup,
@@ -723,6 +725,7 @@ fn mysql_and_sqlite_migrations_include_enabled_incrementals() {
20260520010000,
20260524000000,
20260527000000,
20260528000000,
]
);
assert_eq!(
@@ -748,10 +751,287 @@ fn mysql_and_sqlite_migrations_include_enabled_incrementals() {
20260520010000,
20260524000000,
20260527000000,
20260528000000,
]
);
}
#[tokio::test]
async fn endpoint_api_root_migration_moves_v1_from_stored_default_paths() {
let pool = SqlitePool::connect("sqlite::memory:")
.await
.expect("sqlite pool should connect");
query(
r#"
CREATE TABLE providers (
id TEXT PRIMARY KEY,
provider_type TEXT NOT NULL
);
"#,
)
.execute(&pool)
.await
.expect("providers table should be created");
query(
r#"
CREATE TABLE provider_endpoints (
id TEXT PRIMARY KEY,
provider_id TEXT NOT NULL,
api_format TEXT NOT NULL,
base_url TEXT NOT NULL,
custom_path TEXT
);
"#,
)
.execute(&pool)
.await
.expect("provider_endpoints table should be created");
query(
r#"
INSERT INTO providers (id, provider_type) VALUES
('provider-custom', 'custom'),
('provider-fixed-vertex', 'vertex_ai'),
('provider-fixed-grok', 'grok');
"#,
)
.execute(&pool)
.await
.expect("providers fixture should insert");
query(
r#"
INSERT INTO provider_endpoints (id, provider_id, api_format, base_url, custom_path) VALUES
('openai-root', 'provider-custom', 'openai:chat', 'https://api.openai.example', NULL),
('responses-root', 'provider-custom', 'openai:responses', 'https://responses.example.com', NULL),
('responses-compact-root', 'provider-custom', 'openai:responses:compact', 'https://compact.example.com', NULL),
('openai-path-root', 'provider-custom', 'openai:chat', 'https://proxy.example.com/api', NULL),
('openai-old-default-path', 'provider-custom', 'openai:chat', 'https://proxy.example.com/api?tenant=demo', '/v1/chat/completions'),
('openai-mismatched-custom-path', 'provider-custom', 'openai:chat', 'https://proxy.example.com/api', '/v1/responses'),
('openai-v1-slash-old-default', 'provider-custom', 'openai:chat', 'https://already-versioned.example.com/v1/', '/v1/chat/completions'),
('openai-v4-old-default-path', 'provider-custom', 'openai:chat', 'https://open.bigmodel.cn/api/coding/paas/v4', '/v1/chat/completions'),
('embedding-root', 'provider-custom', 'openai:embedding', 'https://embedding.example.com', NULL),
('embedding-v4-old-default-path', 'provider-custom', 'openai:embedding', 'https://embedding.example.com/api/v4', '/v1/embeddings'),
('jina-embedding-root', 'provider-custom', 'jina:embedding', 'https://api.jina.example', NULL),
('rerank-old-default-path', 'provider-custom', 'openai:rerank', 'https://rerank.example.com/api', '/v1/rerank'),
('jina-rerank-old-default-path', 'provider-custom', 'jina:rerank', 'https://api.jina.example?tenant=demo', '/v1/rerank'),
('image-root', 'provider-custom', 'openai:image', 'https://image.example.com', NULL),
('image-edit-custom-path', 'provider-custom', 'openai:image', 'https://image.example.com/api', '/v1/images/edits'),
('image-v4-edit-custom-path', 'provider-custom', 'openai:image', 'https://image.example.com/api/v4', '/v1/images/edits'),
('video-root', 'provider-custom', 'openai:video', 'https://video.example.com', NULL),
('video-v1beta-old-default-path', 'provider-custom', 'openai:video', 'https://video.example.com/api/v1beta', '/v1/videos'),
('video-versioned-root', 'provider-custom', 'openai:video', 'https://ark.example.com/api/v3', NULL),
('google-versioned-segment-root', 'provider-custom', 'openai:embedding', 'https://generativelanguage.googleapis.com/v1beta/openai', NULL),
('gemini-root', 'provider-custom', 'gemini:generate_content', 'https://generativelanguage.googleapis.com', NULL),
('gemini-old-default-path', 'provider-custom', 'gemini:generate_content', 'https://generativelanguage.googleapis.com?tenant=demo', '/v1beta/models/{model}:{action}'),
('gemini-custom-path', 'provider-custom', 'gemini:generate_content', 'https://proxy.example.com/google', '/v1beta/models/gemini-upstream:generateContent'),
('gemini-versioned-old-default', 'provider-custom', 'gemini:generate_content', 'https://generativelanguage.googleapis.com/v1beta', '/v1beta/models/{model}:{action}'),
('gemini-embedding-root', 'provider-custom', 'gemini:embedding', 'https://generativelanguage.googleapis.com', NULL),
('gemini-embedding-old-default', 'provider-custom', 'gemini:embedding', 'https://generativelanguage.googleapis.com', '/v1beta/models/{model}:embedContent'),
('gemini-video-root', 'provider-custom', 'gemini:video', 'https://generativelanguage.googleapis.com', NULL),
('gemini-video-versioned-old-default', 'provider-custom', 'gemini:video', 'https://generativelanguage.googleapis.com/v1beta', '/v1beta/models/{model}:predictLongRunning'),
('fixed-vertex-gemini-root', 'provider-fixed-vertex', 'gemini:embedding', 'https://aiplatform.googleapis.com', NULL),
('claude-path-root', 'provider-custom', 'claude:messages', 'https://proxy.example.com/anthropic', NULL),
('claude-old-default-path', 'provider-custom', 'claude:messages', 'https://proxy.example.com/anthropic', '/v1/messages'),
('fixed-grok-root', 'provider-fixed-grok', 'openai:chat', 'https://grok.com', NULL);
"#,
)
.execute(&pool)
.await
.expect("endpoint fixture should insert");
let migration = super::sqlite::MIGRATOR
.iter()
.find(|migration| migration.version == 20260528000000)
.expect("endpoint API root migration should be embedded");
sqlx::raw_sql(migration.sql.as_ref())
.execute(&pool)
.await
.expect("endpoint API root migration should apply");
let rows: Vec<(String, String, Option<String>)> =
sqlx::query_as("SELECT id, base_url, custom_path FROM provider_endpoints ORDER BY id")
.fetch_all(&pool)
.await
.expect("endpoint rows should load");
let rows = rows
.into_iter()
.map(|(id, base_url, custom_path)| (id, (base_url, custom_path)))
.collect::<std::collections::BTreeMap<_, _>>();
assert_eq!(
rows.get("openai-root"),
Some(&("https://api.openai.example/v1".to_string(), None))
);
assert_eq!(
rows.get("responses-root"),
Some(&("https://responses.example.com/v1".to_string(), None))
);
assert_eq!(
rows.get("responses-compact-root"),
Some(&("https://compact.example.com/v1".to_string(), None))
);
assert_eq!(
rows.get("openai-path-root"),
Some(&("https://proxy.example.com/api/v1".to_string(), None))
);
assert_eq!(
rows.get("openai-old-default-path"),
Some(&(
"https://proxy.example.com/api/v1?tenant=demo".to_string(),
None
))
);
assert_eq!(
rows.get("openai-mismatched-custom-path"),
Some(&(
"https://proxy.example.com/api/v1".to_string(),
Some("/responses".to_string())
))
);
assert_eq!(
rows.get("openai-v1-slash-old-default"),
Some(&(
"https://already-versioned.example.com/v1/".to_string(),
None
))
);
assert_eq!(
rows.get("openai-v4-old-default-path"),
Some(&(
"https://open.bigmodel.cn/api/coding/paas/v4".to_string(),
None
))
);
assert_eq!(
rows.get("embedding-root"),
Some(&("https://embedding.example.com/v1".to_string(), None))
);
assert_eq!(
rows.get("embedding-v4-old-default-path"),
Some(&("https://embedding.example.com/api/v4".to_string(), None))
);
assert_eq!(
rows.get("jina-embedding-root"),
Some(&("https://api.jina.example/v1".to_string(), None))
);
assert_eq!(
rows.get("rerank-old-default-path"),
Some(&("https://rerank.example.com/api/v1".to_string(), None))
);
assert_eq!(
rows.get("jina-rerank-old-default-path"),
Some(&("https://api.jina.example/v1?tenant=demo".to_string(), None))
);
assert_eq!(
rows.get("image-root"),
Some(&("https://image.example.com/v1".to_string(), None))
);
assert_eq!(
rows.get("image-edit-custom-path"),
Some(&(
"https://image.example.com/api/v1".to_string(),
Some("/images/edits".to_string())
))
);
assert_eq!(
rows.get("image-v4-edit-custom-path"),
Some(&(
"https://image.example.com/api/v4".to_string(),
Some("/images/edits".to_string())
))
);
assert_eq!(
rows.get("video-root"),
Some(&("https://video.example.com/v1".to_string(), None))
);
assert_eq!(
rows.get("video-v1beta-old-default-path"),
Some(&("https://video.example.com/api/v1beta".to_string(), None))
);
assert_eq!(
rows.get("video-versioned-root"),
Some(&("https://ark.example.com/api/v3".to_string(), None))
);
assert_eq!(
rows.get("google-versioned-segment-root"),
Some(&(
"https://generativelanguage.googleapis.com/v1beta/openai".to_string(),
None
))
);
assert_eq!(
rows.get("gemini-root"),
Some(&(
"https://generativelanguage.googleapis.com/v1beta".to_string(),
None
))
);
assert_eq!(
rows.get("gemini-old-default-path"),
Some(&(
"https://generativelanguage.googleapis.com/v1beta?tenant=demo".to_string(),
None
))
);
assert_eq!(
rows.get("gemini-custom-path"),
Some(&(
"https://proxy.example.com/google/v1beta".to_string(),
Some("/models/gemini-upstream:generateContent".to_string())
))
);
assert_eq!(
rows.get("gemini-versioned-old-default"),
Some(&(
"https://generativelanguage.googleapis.com/v1beta".to_string(),
None
))
);
assert_eq!(
rows.get("gemini-embedding-root"),
Some(&(
"https://generativelanguage.googleapis.com/v1beta".to_string(),
None
))
);
assert_eq!(
rows.get("gemini-embedding-old-default"),
Some(&(
"https://generativelanguage.googleapis.com/v1beta".to_string(),
None
))
);
assert_eq!(
rows.get("gemini-video-root"),
Some(&(
"https://generativelanguage.googleapis.com/v1beta".to_string(),
None
))
);
assert_eq!(
rows.get("gemini-video-versioned-old-default"),
Some(&(
"https://generativelanguage.googleapis.com/v1beta".to_string(),
None
))
);
assert_eq!(
rows.get("fixed-vertex-gemini-root"),
Some(&("https://aiplatform.googleapis.com".to_string(), None))
);
assert_eq!(
rows.get("claude-path-root"),
Some(&("https://proxy.example.com/anthropic/v1".to_string(), None))
);
assert_eq!(
rows.get("claude-old-default-path"),
Some(&("https://proxy.example.com/anthropic/v1".to_string(), None))
);
assert_eq!(
rows.get("fixed-grok-root"),
Some(&("https://grok.com".to_string(), None))
);
}
#[test]
fn fresh_usage_schema_projects_upstream_stream_mode_for_all_drivers() {
let mysql_baseline = include_str!("../../../migrations/mysql/20260403000000_baseline.sql");