feat(security): harden gateway boundaries and usage policies

Consolidate subscription usage policy enforcement, privacy-safe persistence, and gateway security hardening into one reviewable change.

Includes bounded HTTP and execution envelopes, header and protocol guards, DNS and relay validation, authentication and secret projection hardening, secure backup/install paths, and regression coverage.
This commit is contained in:
elky
2026-09-04 03:45:52 +08:00
parent ddcbeb3ae9
commit 579f2c7cc1
1019 changed files with 190437 additions and 26080 deletions
@@ -20,8 +20,8 @@ use serde_json::json;
use super::super::{
build_router_with_state, build_state_with_execution_runtime_override,
sample_admin_provider_model, sample_endpoint, sample_key, sample_provider, start_server,
AppState,
sample_admin_provider_model, sample_bound_auth_config, sample_bound_key, sample_endpoint,
sample_provider, start_server, AppState,
};
use crate::constants::{
GATEWAY_HEADER, TRUSTED_ADMIN_SESSION_ID_HEADER, TRUSTED_ADMIN_USER_ID_HEADER,
@@ -216,7 +216,7 @@ async fn gateway_handles_admin_provider_query_models_fetches_upstream_for_select
None,
)
.expect("endpoint transport should build")],
vec![sample_key(
vec![sample_bound_key(
"key-openai-selected",
"provider-openai",
"openai:chat",
@@ -342,7 +342,7 @@ async fn gateway_handles_admin_provider_query_models_fetches_windsurf_model_conf
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let mut provider = sample_provider("provider-windsurf", "Windsurf", 10);
provider.provider_type = "windsurf".to_string();
let mut windsurf_key = sample_key(
let mut windsurf_key = sample_bound_key(
"key-windsurf-selected",
"provider-windsurf",
"openai:chat",
@@ -487,7 +487,7 @@ async fn gateway_handles_admin_provider_query_models_with_openai_responses_endpo
None,
)
.expect("endpoint transport should build")],
vec![sample_key(
vec![sample_bound_key(
"key-openai-responses",
"provider-openai",
"openai:responses",
@@ -600,7 +600,7 @@ async fn gateway_recovers_codex_slug_only_models_from_an_empty_legacy_cache_impl
"openai:responses",
"https://chatgpt.com/backend-api/codex",
)],
vec![sample_key(
vec![sample_bound_key(
"key-codex-dynamic",
"provider-codex-dynamic",
"openai:responses",
@@ -743,7 +743,7 @@ async fn gateway_handles_admin_provider_query_models_falls_back_to_codex_preset_
"openai:responses",
"https://chatgpt.com/backend-api/codex",
)],
vec![sample_key(
vec![sample_bound_key(
"key-codex-invalidated",
"provider-codex",
"openai:responses",
@@ -782,7 +782,13 @@ async fn gateway_handles_admin_provider_query_models_falls_back_to_codex_preset_
.as_str()
.expect("Codex fallback warning should be present");
assert!(warning.contains("Codex 动态模型目录不可用"));
assert!(warning.contains("invalidated"));
// Model-fetch diagnostics are intentionally projected to a credential-safe
// category before being returned from the admin endpoint. The raw
// upstream invalidation text must not cross the response boundary.
assert!(
warning.contains("authorization failed (status 403)"),
"warning={warning}"
);
let model_ids = payload["data"]["models"]
.as_array()
.expect("models should be an array")
@@ -900,7 +906,7 @@ async fn gateway_handles_admin_provider_query_models_respecting_key_api_formats_
)
.expect("endpoint transport should build"),
],
vec![sample_key(
vec![sample_bound_key(
"key-openai-cli",
"provider-openai",
"openai:responses",
@@ -1036,13 +1042,13 @@ async fn gateway_handles_admin_provider_query_models_aggregating_active_keys_imp
)
.expect("endpoint transport should build")],
vec![
sample_key(
sample_bound_key(
"key-openai-1",
"provider-openai",
"openai:chat",
"sk-test-1",
),
sample_key(
sample_bound_key(
"key-openai-2",
"provider-openai",
"openai:chat",
@@ -1131,7 +1137,7 @@ async fn gateway_handles_admin_provider_query_models_for_fixed_provider_without_
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
vec![],
vec![sample_key(
vec![sample_bound_key(
"key-codex-oauth",
"provider-codex",
"openai:responses",
@@ -1251,7 +1257,7 @@ async fn gateway_handles_admin_provider_query_test_model_locally_with_trusted_ad
"openai:chat",
"https://api.openai.example/v1",
)],
vec![sample_key(
vec![sample_bound_key(
"key-openai-primary",
"provider-openai",
"openai:chat",
@@ -1365,7 +1371,7 @@ async fn gateway_handles_admin_provider_query_embedding_model_test_impl() {
"openai:embedding",
"https://api.siliconflow.example",
)],
vec![sample_key(
vec![sample_bound_key(
"key-siliconflow-embedding",
"provider-siliconflow",
"openai:embedding",
@@ -1486,7 +1492,7 @@ async fn gateway_handles_admin_provider_query_doubao_text_embedding_model_test_i
"doubao:embedding",
"https://ark.volces.example/api/v3",
)],
vec![sample_key(
vec![sample_bound_key(
"key-doubao-embedding",
"provider-doubao",
"doubao:embedding",
@@ -1608,7 +1614,7 @@ async fn gateway_handles_admin_provider_query_gemini_embedding_model_test_impl()
"gemini:embedding",
"https://generativelanguage.googleapis.com/v1beta",
)],
vec![sample_key(
vec![sample_bound_key(
"key-gemini-embedding",
"provider-gemini",
"gemini:embedding",
@@ -1726,7 +1732,7 @@ async fn gateway_handles_admin_provider_query_vertex_gemini_embedding_model_test
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let mut provider = sample_provider("provider-vertex-ai", "Vertex AI", 10);
provider.provider_type = "vertex_ai".to_string();
let mut key = sample_key(
let mut key = sample_bound_key(
"key-vertex-gemini-embedding",
"provider-vertex-ai",
"gemini:embedding",
@@ -1873,7 +1879,7 @@ async fn gateway_handles_admin_provider_query_jina_embedding_model_test_impl() {
"jina:embedding",
"https://api.jina.example",
)],
vec![sample_key(
vec![sample_bound_key(
"key-jina-embedding",
"provider-jina-embedding",
"jina:embedding",
@@ -1995,7 +2001,7 @@ async fn gateway_handles_admin_provider_query_openai_rerank_model_test_impl() {
"openai:rerank",
"https://api.openai.example/v1",
)],
vec![sample_key(
vec![sample_bound_key(
"key-openai-rerank",
"provider-openai-rerank",
"openai:rerank",
@@ -2123,7 +2129,7 @@ async fn gateway_handles_admin_provider_query_rerank_model_test_impl() {
"jina:rerank",
"https://api.jina.example",
)],
vec![sample_key(
vec![sample_bound_key(
"key-jina-rerank",
"provider-jina",
"jina:rerank",
@@ -2254,7 +2260,7 @@ async fn gateway_maps_admin_provider_model_before_model_list_test_request_impl()
"openai:chat",
"https://api.minimax.example",
)],
vec![sample_key(
vec![sample_bound_key(
"key-minimax-primary",
"provider-minimax",
"openai:chat",
@@ -2498,7 +2504,7 @@ async fn gateway_streams_codex_openai_responses_upstream_for_admin_pool_model_te
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
vec![endpoint],
vec![sample_key(
vec![sample_bound_key(
"key-codex-primary",
"provider-codex",
"openai:responses",
@@ -2656,20 +2662,18 @@ async fn gateway_executes_codex_search_admin_pool_model_test_with_search_contrac
"https://chatgpt.com/backend-api/codex",
);
endpoint.config = Some(json!({"upstream_stream_policy": "force_stream"}));
let mut key = sample_key(
let mut key = sample_bound_key(
"key-codex-search",
"provider-codex-search",
"openai:search",
"codex-search-access-token",
);
key.auth_type = "oauth".to_string();
key.encrypted_auth_config = Some(
aether_crypto::encrypt_python_fernet_plaintext(
DEVELOPMENT_ENCRYPTION_KEY,
r#"{"provider_type":"codex","account_id":"account-search-admin","is_fedramp":true}"#,
)
.expect("auth config should encrypt"),
);
key.encrypted_auth_config = Some(sample_bound_auth_config(
"provider-codex-search",
"key-codex-search",
r#"{"provider_type":"codex","account_id":"account-search-admin","is_fedramp":true}"#,
));
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
vec![endpoint],
@@ -2796,24 +2800,22 @@ async fn gateway_routes_grok_responses_admin_pool_model_test_through_grok_runtim
let mut provider = sample_provider("provider-grok", "Grok", 10);
provider.provider_type = "grok".to_string();
provider.config = Some(json!({"pool_advanced": {}}));
let mut key = sample_key(
let mut key = sample_bound_key(
"key-grok-oauth",
"provider-grok",
"openai:responses",
"__placeholder__",
);
key.auth_type = "oauth".to_string();
key.encrypted_auth_config = Some(
aether_crypto::encrypt_python_fernet_plaintext(
DEVELOPMENT_ENCRYPTION_KEY,
r#"{
key.encrypted_auth_config = Some(sample_bound_auth_config(
"provider-grok",
"key-grok-oauth",
r#"{
"provider_type":"grok",
"sso_token":"grok-sso",
"sso_rw_token":"grok-rw"
}"#,
)
.expect("auth config should encrypt"),
);
));
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
vec![sample_endpoint(
@@ -2933,7 +2935,7 @@ async fn gateway_streams_windsurf_connect_upstream_for_admin_model_test_impl() {
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let mut provider = sample_provider("provider-windsurf", "Windsurf", 10);
provider.provider_type = "windsurf".to_string();
let mut key = sample_key(
let mut key = sample_bound_key(
"key-windsurf-primary",
"provider-windsurf",
"openai:chat",
@@ -3045,7 +3047,7 @@ async fn gateway_uses_pool_scheduler_order_for_admin_pool_model_test_impl() {
]
}
}));
let mut free_key = sample_key(
let mut free_key = sample_bound_key(
"key-codex-free",
"provider-codex",
"openai:responses",
@@ -3060,7 +3062,7 @@ async fn gateway_uses_pool_scheduler_order_for_admin_pool_model_test_impl() {
"usage_ratio": 0.1
}
}));
let mut plus_key = sample_key(
let mut plus_key = sample_bound_key(
"key-codex-plus",
"provider-codex",
"openai:responses",
@@ -3253,13 +3255,13 @@ async fn gateway_handles_admin_provider_query_test_model_failover_locally_with_t
"https://api.openai.example/v1",
)],
vec![
sample_key(
sample_bound_key(
"key-openai-first",
"provider-openai",
"openai:chat",
"sk-test-first",
),
sample_key(
sample_bound_key(
"key-openai-second",
"provider-openai",
"openai:chat",
@@ -3382,17 +3384,17 @@ async fn gateway_handles_admin_provider_query_test_model_for_kiro_locally_impl()
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let mut provider = sample_provider("provider-kiro", "Kiro", 10);
provider.provider_type = "kiro".to_string();
let mut key = sample_key(
let mut key = sample_bound_key(
"key-kiro-primary",
"provider-kiro",
"claude:messages",
"__placeholder__",
);
key.auth_type = "oauth".to_string();
key.encrypted_auth_config = Some(
aether_crypto::encrypt_python_fernet_plaintext(
DEVELOPMENT_ENCRYPTION_KEY,
r#"{
key.encrypted_auth_config = Some(sample_bound_auth_config(
"provider-kiro",
"key-kiro-primary",
r#"{
"provider_type":"kiro",
"auth_method":"idc",
"access_token":"cached-kiro-token",
@@ -3403,9 +3405,7 @@ async fn gateway_handles_admin_provider_query_test_model_for_kiro_locally_impl()
"client_id":"client-id",
"client_secret":"client-secret"
}"#,
)
.expect("auth config should encrypt"),
);
));
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
@@ -3518,17 +3518,17 @@ async fn gateway_uses_kiro_mapped_model_name_for_explicit_model_mapping_test_imp
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let mut provider = sample_provider("provider-kiro", "Kiro", 10);
provider.provider_type = "kiro".to_string();
let mut key = sample_key(
let mut key = sample_bound_key(
"key-kiro-primary",
"provider-kiro",
"claude:messages",
"__placeholder__",
);
key.auth_type = "oauth".to_string();
key.encrypted_auth_config = Some(
aether_crypto::encrypt_python_fernet_plaintext(
DEVELOPMENT_ENCRYPTION_KEY,
r#"{
key.encrypted_auth_config = Some(sample_bound_auth_config(
"provider-kiro",
"key-kiro-primary",
r#"{
"provider_type":"kiro",
"auth_method":"idc",
"access_token":"cached-kiro-token",
@@ -3539,9 +3539,7 @@ async fn gateway_uses_kiro_mapped_model_name_for_explicit_model_mapping_test_imp
"client_id":"client-id",
"client_secret":"client-secret"
}"#,
)
.expect("auth config should encrypt"),
);
));
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
@@ -3728,12 +3726,12 @@ async fn gateway_handles_admin_provider_query_test_model_failover_for_kiro_local
let mut provider = sample_provider("provider-kiro", "Kiro", 10);
provider.provider_type = "kiro".to_string();
let build_key = |id: &str| {
let mut key = sample_key(id, "provider-kiro", "claude:messages", "__placeholder__");
let mut key = sample_bound_key(id, "provider-kiro", "claude:messages", "__placeholder__");
key.auth_type = "oauth".to_string();
key.encrypted_auth_config = Some(
aether_crypto::encrypt_python_fernet_plaintext(
DEVELOPMENT_ENCRYPTION_KEY,
r#"{
key.encrypted_auth_config = Some(sample_bound_auth_config(
"provider-kiro",
id,
r#"{
"provider_type":"kiro",
"auth_method":"idc",
"access_token":"cached-kiro-token",
@@ -3744,9 +3742,7 @@ async fn gateway_handles_admin_provider_query_test_model_failover_for_kiro_local
"client_id":"client-id",
"client_secret":"client-secret"
}"#,
)
.expect("auth config should encrypt"),
);
));
key
};
@@ -3890,12 +3886,12 @@ async fn gateway_retries_kiro_failover_after_http_error_without_message_impl() {
let mut provider = sample_provider("provider-kiro", "Kiro", 10);
provider.provider_type = "kiro".to_string();
let build_key = |id: &str| {
let mut key = sample_key(id, "provider-kiro", "claude:messages", "__placeholder__");
let mut key = sample_bound_key(id, "provider-kiro", "claude:messages", "__placeholder__");
key.auth_type = "oauth".to_string();
key.encrypted_auth_config = Some(
aether_crypto::encrypt_python_fernet_plaintext(
DEVELOPMENT_ENCRYPTION_KEY,
r#"{
key.encrypted_auth_config = Some(sample_bound_auth_config(
"provider-kiro",
id,
r#"{
"provider_type":"kiro",
"auth_method":"idc",
"access_token":"cached-kiro-token",
@@ -3906,9 +3902,7 @@ async fn gateway_retries_kiro_failover_after_http_error_without_message_impl() {
"client_id":"client-id",
"client_secret":"client-secret"
}"#,
)
.expect("auth config should encrypt"),
);
));
key
};
@@ -4055,7 +4049,7 @@ async fn gateway_handles_non_kiro_multi_model_failover_locally_impl() {
"openai:chat",
"https://api.openai.example/v1",
)],
vec![sample_key(
vec![sample_bound_key(
"key-openai-primary",
"provider-openai",
"openai:chat",
@@ -4235,7 +4229,7 @@ async fn gateway_handles_openai_responses_test_model_locally_impl() {
"openai:responses",
"https://tiger.bookapi.cc/codex",
)],
vec![sample_key(
vec![sample_bound_key(
"key-openai-cli",
"provider-openai",
"openai:responses",
@@ -4367,7 +4361,7 @@ async fn gateway_handles_openai_image_test_model_locally_impl() {
"openai:image",
"https://api.openai.example/v1",
)],
vec![sample_key(
vec![sample_bound_key(
"key-openai-image",
"provider-openai",
"openai:image",
@@ -4431,7 +4425,7 @@ async fn gateway_reports_transport_unsupported_reason_for_non_kiro_provider_impl
"gemini:generate_content",
"https://cloudcode-pa.googleapis.com",
)],
vec![sample_key(
vec![sample_bound_key(
"key-antigravity-gemini",
"provider-antigravity",
"gemini:generate_content",
@@ -4582,26 +4576,24 @@ async fn gateway_handles_antigravity_endpoint_test_model_locally_impl() {
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let mut provider = sample_provider("provider-antigravity", "Antigravity", 10);
provider.provider_type = "antigravity".to_string();
let mut key = sample_key(
let mut key = sample_bound_key(
"key-antigravity-gemini",
"provider-antigravity",
"gemini:generate_content",
"cached-antigravity-token",
);
key.auth_type = "oauth".to_string();
key.encrypted_auth_config = Some(
aether_crypto::encrypt_python_fernet_plaintext(
DEVELOPMENT_ENCRYPTION_KEY,
r#"{
key.encrypted_auth_config = Some(sample_bound_auth_config(
"provider-antigravity",
"key-antigravity-gemini",
r#"{
"provider_type":"antigravity",
"project_id":"project-ant-123",
"client_version":"1.2.3",
"session_id":"sess-ant-123",
"refresh_token":"rt-ant-123"
}"#,
)
.expect("auth config should encrypt"),
);
));
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
vec![sample_endpoint(
@@ -4764,20 +4756,18 @@ async fn gateway_hydrates_antigravity_project_id_from_load_code_assist_for_test_
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let mut provider = sample_provider("provider-antigravity", "Antigravity", 10);
provider.provider_type = "antigravity".to_string();
let mut key = sample_key(
let mut key = sample_bound_key(
"key-antigravity-gemini",
"provider-antigravity",
"gemini:generate_content",
"cached-antigravity-token",
);
key.auth_type = "oauth".to_string();
key.encrypted_auth_config = Some(
aether_crypto::encrypt_python_fernet_plaintext(
DEVELOPMENT_ENCRYPTION_KEY,
r#"{"provider_type":"antigravity","refresh_token":"rt-antigravity-123"}"#,
)
.expect("auth config should encrypt"),
);
key.encrypted_auth_config = Some(sample_bound_auth_config(
"provider-antigravity",
"key-antigravity-gemini",
r#"{"provider_type":"antigravity","refresh_token":"rt-antigravity-123"}"#,
));
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
vec![sample_endpoint(
@@ -4906,13 +4896,13 @@ async fn gateway_prefers_supported_non_kiro_endpoint_when_api_format_is_omitted_
),
],
vec![
sample_key(
sample_bound_key(
"key-openai-cli",
"provider-openai",
"openai:responses",
"sk-test-cli",
),
sample_key(
sample_bound_key(
"key-openai-chat",
"provider-openai",
"openai:chat",
@@ -5020,7 +5010,7 @@ async fn gateway_prefers_transport_supported_non_kiro_endpoint_when_api_format_i
"https://api.openai.example/v1",
),
],
vec![sample_key(
vec![sample_bound_key(
"key-openai-chat",
"provider-openai",
"openai:chat",
@@ -5123,7 +5113,7 @@ async fn gateway_prefers_supported_non_kiro_endpoint_with_compatible_key_when_ap
"https://api.openai.example/v1",
),
],
vec![sample_key(
vec![sample_bound_key(
"key-openai-chat",
"provider-openai",
"openai:chat",
@@ -5225,7 +5215,7 @@ async fn gateway_uses_compatible_cli_endpoint_when_api_format_is_omitted_impl()
"https://api.openai.example/v1",
),
],
vec![sample_key(
vec![sample_bound_key(
"key-openai-cli",
"provider-openai",
"openai:responses",
@@ -5325,7 +5315,7 @@ async fn gateway_uses_runnable_cli_endpoint_after_chat_preference_when_api_forma
"openai:responses",
"https://api.openai.example/v1",
);
let mut shared_key = sample_key(
let mut shared_key = sample_bound_key(
"key-openai-shared",
"provider-openai",
"openai:chat",
@@ -5426,7 +5416,7 @@ async fn gateway_handles_openai_responses_test_model_failover_locally_impl() {
"openai:responses",
"https://api.openai.example/v1",
)],
vec![sample_key(
vec![sample_bound_key(
"key-openai-cli",
"provider-openai",
"openai:responses",
@@ -5527,7 +5517,7 @@ async fn gateway_handles_claude_cli_test_model_locally_impl() {
"claude:messages",
"https://api.anthropic.example/v1",
)],
vec![sample_key(
vec![sample_bound_key(
"key-claude-cli",
"provider-claude",
"claude:messages",
@@ -5622,7 +5612,7 @@ async fn gateway_uses_compatible_claude_cli_endpoint_when_api_format_is_omitted_
"claude:messages",
"https://api.anthropic.example/v1",
)],
vec![sample_key(
vec![sample_bound_key(
"key-claude-cli",
"provider-claude",
"claude:messages",
@@ -5718,7 +5708,7 @@ async fn gateway_handles_claude_cli_test_model_failover_locally_impl() {
"claude:messages",
"https://api.anthropic.example/v1",
)],
vec![sample_key(
vec![sample_bound_key(
"key-claude-cli",
"provider-claude",
"claude:messages",
@@ -5822,7 +5812,7 @@ async fn gateway_handles_gemini_cli_test_model_locally_impl() {
"gemini:generate_content",
"https://generativelanguage.googleapis.com",
)],
vec![sample_key(
vec![sample_bound_key(
"key-gemini-cli",
"provider-gemini",
"gemini:generate_content",
@@ -5931,20 +5921,18 @@ async fn gateway_handles_gemini_cli_test_model_with_oauth_header_fallback_impl()
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let mut provider = sample_provider("provider-gemini", "Gemini", 10);
provider.provider_type = "gemini_cli".to_string();
let mut key = sample_key(
let mut key = sample_bound_key(
"key-gemini-cli",
"provider-gemini",
"gemini:generate_content",
"cached-gemini-cli-token",
);
key.auth_type = "oauth".to_string();
key.encrypted_auth_config = Some(
aether_crypto::encrypt_python_fernet_plaintext(
DEVELOPMENT_ENCRYPTION_KEY,
r#"{"provider_type":"gemini_cli","project_id":"project-1"}"#,
)
.expect("auth config should encrypt"),
);
key.encrypted_auth_config = Some(sample_bound_auth_config(
"provider-gemini",
"key-gemini-cli",
r#"{"provider_type":"gemini_cli","project_id":"project-1"}"#,
));
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
vec![sample_endpoint(
@@ -6086,20 +6074,18 @@ async fn gateway_hydrates_gemini_cli_project_id_from_load_code_assist_for_test_m
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let mut provider = sample_provider("provider-gemini", "Gemini", 10);
provider.provider_type = "gemini_cli".to_string();
let mut key = sample_key(
let mut key = sample_bound_key(
"key-gemini-cli",
"provider-gemini",
"gemini:generate_content",
"cached-gemini-cli-token",
);
key.auth_type = "oauth".to_string();
key.encrypted_auth_config = Some(
aether_crypto::encrypt_python_fernet_plaintext(
DEVELOPMENT_ENCRYPTION_KEY,
r#"{"provider_type":"gemini_cli","refresh_token":"rt-gemini-cli-123"}"#,
)
.expect("auth config should encrypt"),
);
key.encrypted_auth_config = Some(sample_bound_auth_config(
"provider-gemini",
"key-gemini-cli",
r#"{"provider_type":"gemini_cli","refresh_token":"rt-gemini-cli-123"}"#,
));
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
vec![sample_endpoint(
@@ -6219,7 +6205,7 @@ async fn gateway_uses_compatible_gemini_cli_endpoint_when_api_format_is_omitted_
"gemini:generate_content",
"https://generativelanguage.googleapis.com",
)],
vec![sample_key(
vec![sample_bound_key(
"key-gemini-cli",
"provider-gemini",
"gemini:generate_content",
@@ -6315,7 +6301,7 @@ async fn gateway_handles_gemini_cli_test_model_failover_locally_impl() {
"gemini:generate_content",
"https://generativelanguage.googleapis.com",
)],
vec![sample_key(
vec![sample_bound_key(
"key-gemini-cli",
"provider-gemini",
"gemini:generate_content",
@@ -6431,20 +6417,18 @@ async fn gateway_unwraps_gemini_cli_v1internal_response_for_failover_model_test_
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
let mut provider = sample_provider("provider-gemini-cli", "Gemini CLI", 10);
provider.provider_type = "gemini_cli".to_string();
let mut key = sample_key(
let mut key = sample_bound_key(
"key-gemini-cli",
"provider-gemini-cli",
"gemini:generate_content",
"cached-gemini-cli-token",
);
key.auth_type = "oauth".to_string();
key.encrypted_auth_config = Some(
aether_crypto::encrypt_python_fernet_plaintext(
DEVELOPMENT_ENCRYPTION_KEY,
r#"{"provider_type":"gemini_cli","project_id":"project-1"}"#,
)
.expect("auth config should encrypt"),
);
key.encrypted_auth_config = Some(sample_bound_auth_config(
"provider-gemini-cli",
"key-gemini-cli",
r#"{"provider_type":"gemini_cli","project_id":"project-1"}"#,
));
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
vec![provider],
vec![sample_endpoint(
@@ -6546,7 +6530,7 @@ async fn gateway_handles_admin_provider_query_test_model_failover_with_single_mo
"openai:chat",
"https://api.openai.example/v1",
)],
vec![sample_key(
vec![sample_bound_key(
"key-openai-alias",
"provider-openai",
"openai:chat",
@@ -6667,13 +6651,13 @@ async fn gateway_retries_non_kiro_failover_after_http_error_without_message_impl
"https://api.openai.example/v1",
)],
vec![
sample_key(
sample_bound_key(
"key-openai-first",
"provider-openai",
"openai:chat",
"sk-test-first",
),
sample_key(
sample_bound_key(
"key-openai-second",
"provider-openai",
"openai:chat",
@@ -6794,13 +6778,13 @@ async fn gateway_retries_non_kiro_failover_after_success_status_without_body_imp
"https://api.openai.example/v1",
)],
vec![
sample_key(
sample_bound_key(
"key-openai-first",
"provider-openai",
"openai:chat",
"sk-test-first",
),
sample_key(
sample_bound_key(
"key-openai-second",
"provider-openai",
"openai:chat",
@@ -6850,7 +6834,9 @@ async fn gateway_retries_non_kiro_failover_after_success_status_without_body_imp
assert_eq!(attempts[0]["status_code"], json!(200));
assert_eq!(
attempts[0]["error_message"],
json!("Provider returned HTTP 200 without a model-test response body")
// Attempt diagnostics intentionally expose only the status class;
// detailed provider response text is not returned to the admin UI.
json!("HTTP 200")
);
assert_eq!(attempts[1]["status"], json!("success"));