fix: allow empty provider secrets to attempt requests

This commit is contained in:
fawney19
2026-04-29 18:55:52 +08:00
parent d6de917878
commit a16550249b
5 changed files with 190 additions and 22 deletions

View File

@@ -82,3 +82,116 @@ pub(crate) async fn resolve_candidate_oauth_auth(
}
}
}
#[cfg(test)]
mod tests {
use aether_provider_transport::snapshot::{
GatewayProviderTransportEndpoint, GatewayProviderTransportKey,
GatewayProviderTransportProvider, GatewayProviderTransportSnapshot,
};
use aether_scheduler_core::SchedulerMinimalCandidateSelectionCandidate;
use super::{prepare_header_authenticated_candidate, OauthPreparationContext};
use crate::ai_pipeline::PlannerAppState;
fn sample_transport() -> GatewayProviderTransportSnapshot {
GatewayProviderTransportSnapshot {
provider: GatewayProviderTransportProvider {
id: "provider-1".to_string(),
name: "provider".to_string(),
provider_type: "custom".to_string(),
website: None,
is_active: true,
keep_priority_on_conversion: false,
enable_format_conversion: false,
concurrent_limit: None,
max_retries: None,
proxy: None,
request_timeout_secs: None,
stream_first_byte_timeout_secs: None,
config: None,
},
endpoint: GatewayProviderTransportEndpoint {
id: "endpoint-1".to_string(),
provider_id: "provider-1".to_string(),
api_format: "openai:chat".to_string(),
api_family: Some("openai".to_string()),
endpoint_kind: Some("chat".to_string()),
is_active: true,
base_url: "https://example.test".to_string(),
header_rules: None,
body_rules: None,
max_retries: None,
custom_path: None,
config: None,
format_acceptance_config: None,
proxy: None,
},
key: GatewayProviderTransportKey {
id: "key-1".to_string(),
provider_id: "provider-1".to_string(),
name: "key".to_string(),
auth_type: "api_key".to_string(),
is_active: true,
api_formats: Some(vec!["openai:chat".to_string()]),
auth_type_by_format: None,
allowed_models: None,
capabilities: None,
rate_multipliers: None,
global_priority_by_format: None,
expires_at_unix_secs: None,
proxy: None,
fingerprint: None,
decrypted_api_key: String::new(),
decrypted_auth_config: None,
},
}
}
fn sample_candidate() -> SchedulerMinimalCandidateSelectionCandidate {
SchedulerMinimalCandidateSelectionCandidate {
provider_id: "provider-1".to_string(),
provider_name: "provider".to_string(),
provider_type: "custom".to_string(),
provider_priority: 1,
endpoint_id: "endpoint-1".to_string(),
endpoint_api_format: "openai:chat".to_string(),
key_id: "key-1".to_string(),
key_name: "key".to_string(),
key_auth_type: "api_key".to_string(),
key_internal_priority: 1,
key_global_priority_for_format: None,
key_capabilities: None,
model_id: "model-1".to_string(),
global_model_id: "global-model-1".to_string(),
global_model_name: "gpt-test".to_string(),
selected_provider_model_name: "gpt-test-upstream".to_string(),
mapping_matched_model: None,
}
}
#[tokio::test]
async fn header_auth_preparation_allows_empty_auth_value() {
let state = crate::AppState::new().expect("state should build");
let transport = sample_transport();
let candidate = sample_candidate();
let prepared = prepare_header_authenticated_candidate(
PlannerAppState::new(&state),
&transport,
&candidate,
Some(("authorization".to_string(), String::new())),
OauthPreparationContext {
trace_id: "trace-empty-auth",
api_format: "openai:chat",
operation: "test",
},
)
.await
.expect("empty auth value should still prepare the candidate");
assert_eq!(prepared.auth_header, "authorization");
assert_eq!(prepared.auth_value, "");
assert_eq!(prepared.mapped_model, "gpt-test-upstream");
}
}

View File

@@ -242,22 +242,23 @@ pub(super) async fn maybe_build_local_test_connection_route_response(
return None;
};
let mut provider_request_headers = BTreeMap::from([
("content-type".to_string(), "application/json".to_string()),
(auth_header.clone(), auth_value.clone()),
]);
let mut provider_request_headers =
BTreeMap::from([("content-type".to_string(), "application/json".to_string())]);
if !auth_header.trim().is_empty() && !auth_value.trim().is_empty() {
provider_request_headers.insert(auth_header.clone(), auth_value.clone());
}
if uses_vertex_query_auth {
provider_request_headers.remove("x-goog-api-key");
}
let protected_headers = if uses_vertex_query_auth {
&["content-type"][..]
let protected_headers = if uses_vertex_query_auth || auth_value.trim().is_empty() {
vec!["content-type"]
} else {
&[auth_header.as_str(), "content-type"][..]
vec![auth_header.as_str(), "content-type"]
};
if !crate::provider_transport::apply_local_header_rules(
&mut provider_request_headers,
transport.endpoint.header_rules.as_ref(),
protected_headers,
&protected_headers,
&provider_request_body,
None,
) {