mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +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:
628
apps/aether-gateway/src/control/auth/credentials.rs
Normal file
628
apps/aether-gateway/src/control/auth/credentials.rs
Normal file
@@ -0,0 +1,628 @@
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use axum::body::Bytes;
|
||||
use axum::http::Uri;
|
||||
use sha2::{Digest, Sha256};
|
||||
use url::form_urlencoded;
|
||||
|
||||
use crate::gateway::headers::{header_value_str, is_json_request};
|
||||
|
||||
use super::super::GatewayControlDecision;
|
||||
use super::types::{
|
||||
GatewayCredentialBundle, GatewayCredentialCarrier, GatewayExtractedCredentials,
|
||||
GatewayPrimaryCredential, GatewayTrustedAdminHeaders, GatewayTrustedAuthHeaders,
|
||||
};
|
||||
|
||||
pub(crate) fn extract_requested_model(
|
||||
decision: &GatewayControlDecision,
|
||||
uri: &Uri,
|
||||
headers: &http::HeaderMap,
|
||||
body: &Bytes,
|
||||
) -> Option<String> {
|
||||
if decision.route_family.as_deref() == Some("gemini") {
|
||||
if let Some(model) = extract_gemini_model_from_path(uri.path()) {
|
||||
return Some(model);
|
||||
}
|
||||
}
|
||||
|
||||
if !is_json_request(headers) || body.is_empty() {
|
||||
return None;
|
||||
}
|
||||
serde_json::from_slice::<serde_json::Value>(body)
|
||||
.ok()
|
||||
.and_then(|payload| {
|
||||
payload
|
||||
.get("model")
|
||||
.and_then(|value| value.as_str())
|
||||
.map(|value| value.trim().to_string())
|
||||
})
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
pub(super) fn extract_request_credentials(
|
||||
headers: &http::HeaderMap,
|
||||
uri: &Uri,
|
||||
auth_endpoint_signature: &str,
|
||||
) -> GatewayExtractedCredentials {
|
||||
let bundle = GatewayCredentialBundle {
|
||||
authorization_bearer: header_value_str(headers, http::header::AUTHORIZATION.as_str())
|
||||
.as_deref()
|
||||
.and_then(extract_bearer_token)
|
||||
.map(ToOwned::to_owned),
|
||||
x_api_key: header_value_str(headers, "x-api-key"),
|
||||
api_key: header_value_str(headers, "api-key"),
|
||||
x_goog_api_key: header_value_str(headers, "x-goog-api-key"),
|
||||
query_key: extract_query_api_key(uri),
|
||||
cookie_header: header_value_str(headers, http::header::COOKIE.as_str()),
|
||||
};
|
||||
let trusted_headers = extract_trusted_auth_headers(headers);
|
||||
let trusted_admin_headers = extract_trusted_admin_headers(headers);
|
||||
let primary = select_primary_credential(auth_endpoint_signature, &bundle);
|
||||
|
||||
GatewayExtractedCredentials {
|
||||
trusted_headers,
|
||||
trusted_admin_headers,
|
||||
bundle,
|
||||
primary,
|
||||
}
|
||||
}
|
||||
|
||||
fn has_trusted_gateway_marker(headers: &http::HeaderMap) -> bool {
|
||||
header_value_str(headers, crate::gateway::constants::GATEWAY_HEADER)
|
||||
.unwrap_or_default()
|
||||
.trim()
|
||||
.to_ascii_lowercase()
|
||||
.starts_with("rust-phase3")
|
||||
}
|
||||
|
||||
pub(super) fn build_auth_context_cache_key(
|
||||
headers: &http::HeaderMap,
|
||||
uri: &Uri,
|
||||
auth_endpoint_signature: &str,
|
||||
) -> Option<String> {
|
||||
let signature = auth_endpoint_signature.trim();
|
||||
if signature.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let extracted = extract_request_credentials(headers, uri, signature);
|
||||
let bundle = extracted.bundle;
|
||||
if bundle.authorization_bearer.is_none()
|
||||
&& bundle.x_api_key.is_none()
|
||||
&& bundle.api_key.is_none()
|
||||
&& bundle.x_goog_api_key.is_none()
|
||||
&& bundle.query_key.is_none()
|
||||
&& bundle.cookie_header.is_none()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(format!(
|
||||
"{signature}\n{}\n{}\n{}\n{}\n{}\n{}",
|
||||
bundle.authorization_bearer.unwrap_or_default(),
|
||||
bundle.x_api_key.unwrap_or_default(),
|
||||
bundle.api_key.unwrap_or_default(),
|
||||
bundle.x_goog_api_key.unwrap_or_default(),
|
||||
bundle.query_key.unwrap_or_default(),
|
||||
bundle.cookie_header.unwrap_or_default(),
|
||||
))
|
||||
}
|
||||
|
||||
fn extract_gemini_model_from_path(path: &str) -> Option<String> {
|
||||
let (_, suffix) = path.split_once("/models/")?;
|
||||
let model = suffix
|
||||
.split_once(':')
|
||||
.map(|(value, _)| value)
|
||||
.unwrap_or(suffix);
|
||||
let model = model.trim();
|
||||
if model.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(model.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_trusted_auth_headers(headers: &http::HeaderMap) -> Option<GatewayTrustedAuthHeaders> {
|
||||
if !has_trusted_gateway_marker(headers) {
|
||||
return None;
|
||||
}
|
||||
let user_id = header_value_str(
|
||||
headers,
|
||||
crate::gateway::constants::TRUSTED_AUTH_USER_ID_HEADER,
|
||||
)
|
||||
.filter(|value| !value.is_empty())?;
|
||||
let api_key_id = header_value_str(
|
||||
headers,
|
||||
crate::gateway::constants::TRUSTED_AUTH_API_KEY_ID_HEADER,
|
||||
)
|
||||
.filter(|value| !value.is_empty())?;
|
||||
let balance_remaining = header_value_str(
|
||||
headers,
|
||||
crate::gateway::constants::TRUSTED_AUTH_BALANCE_HEADER,
|
||||
)
|
||||
.as_deref()
|
||||
.and_then(parse_f64_header);
|
||||
let access_allowed = header_value_str(
|
||||
headers,
|
||||
crate::gateway::constants::TRUSTED_AUTH_ACCESS_ALLOWED_HEADER,
|
||||
)
|
||||
.as_deref()
|
||||
.and_then(parse_bool_header);
|
||||
|
||||
Some(GatewayTrustedAuthHeaders {
|
||||
user_id,
|
||||
api_key_id,
|
||||
balance_remaining,
|
||||
access_allowed,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn extract_trusted_admin_headers(
|
||||
headers: &http::HeaderMap,
|
||||
) -> Option<GatewayTrustedAdminHeaders> {
|
||||
if !has_trusted_gateway_marker(headers) {
|
||||
return None;
|
||||
}
|
||||
let user_id = header_value_str(
|
||||
headers,
|
||||
crate::gateway::constants::TRUSTED_ADMIN_USER_ID_HEADER,
|
||||
)?
|
||||
.trim()
|
||||
.to_string();
|
||||
if user_id.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let user_role = header_value_str(
|
||||
headers,
|
||||
crate::gateway::constants::TRUSTED_ADMIN_USER_ROLE_HEADER,
|
||||
)?
|
||||
.trim()
|
||||
.to_string();
|
||||
if !user_role.eq_ignore_ascii_case("admin") {
|
||||
return None;
|
||||
}
|
||||
let session_id = header_value_str(
|
||||
headers,
|
||||
crate::gateway::constants::TRUSTED_ADMIN_SESSION_ID_HEADER,
|
||||
)
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty());
|
||||
let management_token_id = header_value_str(
|
||||
headers,
|
||||
crate::gateway::constants::TRUSTED_ADMIN_MANAGEMENT_TOKEN_ID_HEADER,
|
||||
)
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty());
|
||||
if session_id.is_none() && management_token_id.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(GatewayTrustedAdminHeaders {
|
||||
user_id,
|
||||
user_role: "admin".to_string(),
|
||||
session_id,
|
||||
management_token_id,
|
||||
})
|
||||
}
|
||||
|
||||
fn select_primary_credential(
|
||||
auth_endpoint_signature: &str,
|
||||
bundle: &GatewayCredentialBundle,
|
||||
) -> Option<GatewayPrimaryCredential> {
|
||||
let signature = auth_endpoint_signature.trim().to_ascii_lowercase();
|
||||
if signature.starts_with("gemini:") {
|
||||
return select_gemini_credential(bundle);
|
||||
}
|
||||
if signature == "claude:cli" {
|
||||
return select_claude_cli_credential(bundle);
|
||||
}
|
||||
if signature.starts_with("claude:") {
|
||||
return select_claude_chat_credential(bundle);
|
||||
}
|
||||
if signature.starts_with("openai:") {
|
||||
return select_openai_credential(bundle);
|
||||
}
|
||||
|
||||
select_generic_credential(bundle)
|
||||
}
|
||||
|
||||
fn select_openai_credential(bundle: &GatewayCredentialBundle) -> Option<GatewayPrimaryCredential> {
|
||||
first_provider_api_key(
|
||||
bundle,
|
||||
&[
|
||||
GatewayCredentialCarrier::AuthorizationBearer,
|
||||
GatewayCredentialCarrier::XApiKey,
|
||||
GatewayCredentialCarrier::ApiKey,
|
||||
GatewayCredentialCarrier::XGoogApiKey,
|
||||
GatewayCredentialCarrier::QueryKey,
|
||||
],
|
||||
)
|
||||
.or_else(|| select_cookie_credential(bundle))
|
||||
}
|
||||
|
||||
fn select_claude_cli_credential(
|
||||
bundle: &GatewayCredentialBundle,
|
||||
) -> Option<GatewayPrimaryCredential> {
|
||||
first_provider_api_key(
|
||||
bundle,
|
||||
&[
|
||||
GatewayCredentialCarrier::AuthorizationBearer,
|
||||
GatewayCredentialCarrier::XApiKey,
|
||||
GatewayCredentialCarrier::ApiKey,
|
||||
],
|
||||
)
|
||||
.or_else(|| first_bearer_token(bundle))
|
||||
.or_else(|| select_cookie_credential(bundle))
|
||||
}
|
||||
|
||||
fn select_claude_chat_credential(
|
||||
bundle: &GatewayCredentialBundle,
|
||||
) -> Option<GatewayPrimaryCredential> {
|
||||
first_provider_api_key(
|
||||
bundle,
|
||||
&[
|
||||
GatewayCredentialCarrier::XApiKey,
|
||||
GatewayCredentialCarrier::ApiKey,
|
||||
],
|
||||
)
|
||||
.or_else(|| first_bearer_token(bundle))
|
||||
.or_else(|| select_cookie_credential(bundle))
|
||||
}
|
||||
|
||||
fn select_gemini_credential(bundle: &GatewayCredentialBundle) -> Option<GatewayPrimaryCredential> {
|
||||
first_provider_api_key(
|
||||
bundle,
|
||||
&[
|
||||
GatewayCredentialCarrier::QueryKey,
|
||||
GatewayCredentialCarrier::XGoogApiKey,
|
||||
GatewayCredentialCarrier::XApiKey,
|
||||
GatewayCredentialCarrier::ApiKey,
|
||||
],
|
||||
)
|
||||
.or_else(|| first_bearer_token(bundle))
|
||||
.or_else(|| select_cookie_credential(bundle))
|
||||
}
|
||||
|
||||
fn select_generic_credential(bundle: &GatewayCredentialBundle) -> Option<GatewayPrimaryCredential> {
|
||||
first_bearer_token(bundle)
|
||||
.or_else(|| {
|
||||
first_provider_api_key(
|
||||
bundle,
|
||||
&[
|
||||
GatewayCredentialCarrier::XApiKey,
|
||||
GatewayCredentialCarrier::ApiKey,
|
||||
GatewayCredentialCarrier::XGoogApiKey,
|
||||
GatewayCredentialCarrier::QueryKey,
|
||||
],
|
||||
)
|
||||
})
|
||||
.or_else(|| select_cookie_credential(bundle))
|
||||
}
|
||||
|
||||
fn first_provider_api_key(
|
||||
bundle: &GatewayCredentialBundle,
|
||||
carriers: &[GatewayCredentialCarrier],
|
||||
) -> Option<GatewayPrimaryCredential> {
|
||||
for carrier in carriers {
|
||||
if let Some(raw) = credential_value(bundle, *carrier) {
|
||||
return Some(GatewayPrimaryCredential::ProviderApiKey {
|
||||
raw,
|
||||
carrier: *carrier,
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn first_bearer_token(bundle: &GatewayCredentialBundle) -> Option<GatewayPrimaryCredential> {
|
||||
credential_value(bundle, GatewayCredentialCarrier::AuthorizationBearer).map(|raw| {
|
||||
GatewayPrimaryCredential::BearerToken {
|
||||
raw,
|
||||
carrier: GatewayCredentialCarrier::AuthorizationBearer,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn select_cookie_credential(bundle: &GatewayCredentialBundle) -> Option<GatewayPrimaryCredential> {
|
||||
credential_value(bundle, GatewayCredentialCarrier::CookieHeader).map(|raw| {
|
||||
GatewayPrimaryCredential::CookieHeader {
|
||||
raw,
|
||||
carrier: GatewayCredentialCarrier::CookieHeader,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn credential_value(
|
||||
bundle: &GatewayCredentialBundle,
|
||||
carrier: GatewayCredentialCarrier,
|
||||
) -> Option<String> {
|
||||
match carrier {
|
||||
GatewayCredentialCarrier::AuthorizationBearer => bundle.authorization_bearer.clone(),
|
||||
GatewayCredentialCarrier::XApiKey => bundle.x_api_key.clone(),
|
||||
GatewayCredentialCarrier::ApiKey => bundle.api_key.clone(),
|
||||
GatewayCredentialCarrier::XGoogApiKey => bundle.x_goog_api_key.clone(),
|
||||
GatewayCredentialCarrier::QueryKey => bundle.query_key.clone(),
|
||||
GatewayCredentialCarrier::CookieHeader => bundle.cookie_header.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_query_api_key(uri: &Uri) -> Option<String> {
|
||||
let query = uri.query()?;
|
||||
form_urlencoded::parse(query.as_bytes())
|
||||
.find(|(key, value)| key == "key" && !value.trim().is_empty())
|
||||
.map(|(_, value)| value.into_owned())
|
||||
}
|
||||
|
||||
fn extract_bearer_token(value: &str) -> Option<&str> {
|
||||
let trimmed = value.trim();
|
||||
let (scheme, token) = trimmed.split_once(' ')?;
|
||||
if !scheme.eq_ignore_ascii_case("bearer") {
|
||||
return None;
|
||||
}
|
||||
let token = token.trim();
|
||||
if token.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(token)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn hash_api_key(value: &str) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(value.as_bytes());
|
||||
format!("{:x}", hasher.finalize())
|
||||
}
|
||||
|
||||
pub(super) fn contains_string(items: &[String], target: &str) -> bool {
|
||||
items
|
||||
.iter()
|
||||
.any(|item| item.trim().eq_ignore_ascii_case(target.trim()))
|
||||
}
|
||||
|
||||
pub(super) fn parse_bool_header(value: &str) -> Option<bool> {
|
||||
match value.trim().to_ascii_lowercase().as_str() {
|
||||
"true" | "1" | "yes" => Some(true),
|
||||
"false" | "0" | "no" => Some(false),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn parse_f64_header(value: &str) -> Option<f64> {
|
||||
value.trim().parse::<f64>().ok()
|
||||
}
|
||||
|
||||
pub(super) fn current_unix_secs() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn uri(path: &str) -> Uri {
|
||||
path.parse().expect("uri should parse")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selects_openai_bearer_as_provider_api_key() {
|
||||
let mut headers = http::HeaderMap::new();
|
||||
headers.insert(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer sk-openai".parse().unwrap(),
|
||||
);
|
||||
|
||||
let extracted =
|
||||
extract_request_credentials(&headers, &uri("/v1/chat/completions"), "openai:chat");
|
||||
assert_eq!(
|
||||
extracted.primary,
|
||||
Some(GatewayPrimaryCredential::ProviderApiKey {
|
||||
raw: "sk-openai".to_string(),
|
||||
carrier: GatewayCredentialCarrier::AuthorizationBearer,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefers_claude_chat_x_api_key_over_bearer() {
|
||||
let mut headers = http::HeaderMap::new();
|
||||
headers.insert(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer cli-token".parse().unwrap(),
|
||||
);
|
||||
headers.insert("x-api-key", "claude-key".parse().unwrap());
|
||||
|
||||
let extracted = extract_request_credentials(&headers, &uri("/v1/messages"), "claude:chat");
|
||||
assert_eq!(
|
||||
extracted.primary,
|
||||
Some(GatewayPrimaryCredential::ProviderApiKey {
|
||||
raw: "claude-key".to_string(),
|
||||
carrier: GatewayCredentialCarrier::XApiKey,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selects_claude_cli_bearer_as_provider_api_key() {
|
||||
let mut headers = http::HeaderMap::new();
|
||||
headers.insert(
|
||||
http::header::AUTHORIZATION,
|
||||
"Bearer cli-token".parse().unwrap(),
|
||||
);
|
||||
|
||||
let extracted = extract_request_credentials(&headers, &uri("/v1/messages"), "claude:cli");
|
||||
assert_eq!(
|
||||
extracted.primary,
|
||||
Some(GatewayPrimaryCredential::ProviderApiKey {
|
||||
raw: "cli-token".to_string(),
|
||||
carrier: GatewayCredentialCarrier::AuthorizationBearer,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefers_gemini_query_key_over_header_key() {
|
||||
let mut headers = http::HeaderMap::new();
|
||||
headers.insert("x-goog-api-key", "gemini-header".parse().unwrap());
|
||||
|
||||
let extracted = extract_request_credentials(
|
||||
&headers,
|
||||
&uri("/v1beta/models?key=gemini-query"),
|
||||
"gemini:chat",
|
||||
);
|
||||
assert_eq!(
|
||||
extracted.primary,
|
||||
Some(GatewayPrimaryCredential::ProviderApiKey {
|
||||
raw: "gemini-query".to_string(),
|
||||
carrier: GatewayCredentialCarrier::QueryKey,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_cookie_as_fallback_credential() {
|
||||
let mut headers = http::HeaderMap::new();
|
||||
headers.insert(http::header::COOKIE, "session=abc123".parse().unwrap());
|
||||
|
||||
let extracted =
|
||||
extract_request_credentials(&headers, &uri("/v1/chat/completions"), "internal:session");
|
||||
assert_eq!(
|
||||
extracted.primary,
|
||||
Some(GatewayPrimaryCredential::CookieHeader {
|
||||
raw: "session=abc123".to_string(),
|
||||
carrier: GatewayCredentialCarrier::CookieHeader,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cache_key_includes_cookie_header() {
|
||||
let mut headers = http::HeaderMap::new();
|
||||
headers.insert(http::header::COOKIE, "session=abc123".parse().unwrap());
|
||||
|
||||
let cache_key = build_auth_context_cache_key(
|
||||
&headers,
|
||||
&uri("/v1/chat/completions"),
|
||||
"internal:session",
|
||||
)
|
||||
.expect("cache key should exist");
|
||||
assert!(cache_key.contains("session=abc123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_trusted_auth_headers() {
|
||||
let mut headers = http::HeaderMap::new();
|
||||
headers.insert(
|
||||
crate::gateway::constants::GATEWAY_HEADER,
|
||||
"rust-phase3b".parse().unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
crate::gateway::constants::TRUSTED_AUTH_USER_ID_HEADER,
|
||||
"user-1".parse().unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
crate::gateway::constants::TRUSTED_AUTH_API_KEY_ID_HEADER,
|
||||
"key-1".parse().unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
crate::gateway::constants::TRUSTED_AUTH_BALANCE_HEADER,
|
||||
"1.5".parse().unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
crate::gateway::constants::TRUSTED_AUTH_ACCESS_ALLOWED_HEADER,
|
||||
"true".parse().unwrap(),
|
||||
);
|
||||
|
||||
let extracted =
|
||||
extract_request_credentials(&headers, &uri("/v1/chat/completions"), "openai:chat");
|
||||
assert_eq!(
|
||||
extracted.trusted_headers,
|
||||
Some(GatewayTrustedAuthHeaders {
|
||||
user_id: "user-1".to_string(),
|
||||
api_key_id: "key-1".to_string(),
|
||||
balance_remaining: Some(1.5),
|
||||
access_allowed: Some(true),
|
||||
})
|
||||
);
|
||||
assert_eq!(extracted.trusted_admin_headers, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_trusted_auth_headers_without_gateway_marker() {
|
||||
let mut headers = http::HeaderMap::new();
|
||||
headers.insert(
|
||||
crate::gateway::constants::TRUSTED_AUTH_USER_ID_HEADER,
|
||||
"user-1".parse().unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
crate::gateway::constants::TRUSTED_AUTH_API_KEY_ID_HEADER,
|
||||
"key-1".parse().unwrap(),
|
||||
);
|
||||
|
||||
let extracted =
|
||||
extract_request_credentials(&headers, &uri("/v1/chat/completions"), "openai:chat");
|
||||
assert_eq!(extracted.trusted_headers, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extracts_trusted_admin_headers() {
|
||||
let mut headers = http::HeaderMap::new();
|
||||
headers.insert(
|
||||
crate::gateway::constants::GATEWAY_HEADER,
|
||||
"rust-phase3b".parse().unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
crate::gateway::constants::TRUSTED_ADMIN_USER_ID_HEADER,
|
||||
"admin-user-1".parse().unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
crate::gateway::constants::TRUSTED_ADMIN_USER_ROLE_HEADER,
|
||||
"admin".parse().unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
crate::gateway::constants::TRUSTED_ADMIN_SESSION_ID_HEADER,
|
||||
"sess-1".parse().unwrap(),
|
||||
);
|
||||
|
||||
let extracted = extract_request_credentials(
|
||||
&headers,
|
||||
&uri("/api/admin/endpoints/health/api-formats"),
|
||||
"admin:endpoints_health",
|
||||
);
|
||||
assert_eq!(
|
||||
extracted.trusted_admin_headers,
|
||||
Some(GatewayTrustedAdminHeaders {
|
||||
user_id: "admin-user-1".to_string(),
|
||||
user_role: "admin".to_string(),
|
||||
session_id: Some("sess-1".to_string()),
|
||||
management_token_id: None,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignores_trusted_admin_headers_without_gateway_marker() {
|
||||
let mut headers = http::HeaderMap::new();
|
||||
headers.insert(
|
||||
crate::gateway::constants::TRUSTED_ADMIN_USER_ID_HEADER,
|
||||
"admin-user-1".parse().unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
crate::gateway::constants::TRUSTED_ADMIN_USER_ROLE_HEADER,
|
||||
"admin".parse().unwrap(),
|
||||
);
|
||||
headers.insert(
|
||||
crate::gateway::constants::TRUSTED_ADMIN_SESSION_ID_HEADER,
|
||||
"sess-1".parse().unwrap(),
|
||||
);
|
||||
|
||||
let extracted = extract_request_credentials(
|
||||
&headers,
|
||||
&uri("/api/admin/endpoints/health/api-formats"),
|
||||
"admin:endpoints_health",
|
||||
);
|
||||
assert_eq!(extracted.trusted_admin_headers, None);
|
||||
}
|
||||
}
|
||||
65
apps/aether-gateway/src/control/auth/gate.rs
Normal file
65
apps/aether-gateway/src/control/auth/gate.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use axum::body::Bytes;
|
||||
use axum::http::Uri;
|
||||
|
||||
use super::super::GatewayControlDecision;
|
||||
use super::credentials::{contains_string, extract_requested_model};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) enum GatewayLocalAuthRejection {
|
||||
InvalidApiKey,
|
||||
LockedApiKey,
|
||||
WalletUnavailable,
|
||||
BalanceDenied { remaining: Option<f64> },
|
||||
ProviderNotAllowed { provider: String },
|
||||
ApiFormatNotAllowed { api_format: String },
|
||||
ModelNotAllowed { model: String },
|
||||
}
|
||||
|
||||
pub(crate) fn trusted_auth_local_rejection(
|
||||
decision: Option<&GatewayControlDecision>,
|
||||
_headers: &http::HeaderMap,
|
||||
) -> Option<GatewayLocalAuthRejection> {
|
||||
let decision = decision?;
|
||||
if decision.route_class.as_deref() != Some("ai_public") {
|
||||
return None;
|
||||
}
|
||||
|
||||
decision
|
||||
.local_auth_rejection
|
||||
.clone()
|
||||
.or_else(|| decision.auth_context.as_ref()?.local_rejection.clone())
|
||||
}
|
||||
|
||||
pub(crate) fn should_buffer_request_for_local_auth(
|
||||
decision: Option<&GatewayControlDecision>,
|
||||
headers: &http::HeaderMap,
|
||||
) -> bool {
|
||||
let Some(decision) = decision else {
|
||||
return false;
|
||||
};
|
||||
decision.route_class.as_deref() == Some("ai_public")
|
||||
&& decision.route_kind.as_deref() != Some("files")
|
||||
&& crate::gateway::headers::is_json_request(headers)
|
||||
}
|
||||
|
||||
pub(crate) fn request_model_local_rejection(
|
||||
decision: Option<&GatewayControlDecision>,
|
||||
uri: &Uri,
|
||||
headers: &http::HeaderMap,
|
||||
body: &Bytes,
|
||||
) -> Option<GatewayLocalAuthRejection> {
|
||||
let decision = decision?;
|
||||
if decision.route_class.as_deref() != Some("ai_public") {
|
||||
return None;
|
||||
}
|
||||
let auth_context = decision.auth_context.as_ref()?;
|
||||
let allowed_models = auth_context.allowed_models.as_deref()?;
|
||||
let requested_model = extract_requested_model(decision, uri, headers, body)?;
|
||||
if contains_string(allowed_models, &requested_model) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(GatewayLocalAuthRejection::ModelNotAllowed {
|
||||
model: requested_model,
|
||||
})
|
||||
}
|
||||
15
apps/aether-gateway/src/control/auth/mod.rs
Normal file
15
apps/aether-gateway/src/control/auth/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
mod credentials;
|
||||
mod gate;
|
||||
mod principal;
|
||||
mod resolution;
|
||||
mod types;
|
||||
|
||||
pub(crate) use credentials::extract_requested_model;
|
||||
pub(crate) use gate::{
|
||||
request_model_local_rejection, should_buffer_request_for_local_auth,
|
||||
trusted_auth_local_rejection, GatewayLocalAuthRejection,
|
||||
};
|
||||
pub(super) use resolution::{resolve_control_decision_auth, ControlDecisionAuthResolution};
|
||||
pub(crate) use resolution::{
|
||||
resolve_execution_runtime_auth_context, GatewayAdminPrincipalContext, GatewayControlAuthContext,
|
||||
};
|
||||
110
apps/aether-gateway/src/control/auth/principal.rs
Normal file
110
apps/aether-gateway/src/control/auth/principal.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use super::credentials::hash_api_key;
|
||||
use super::types::{
|
||||
GatewayExtractedCredentials, GatewayPrimaryCredential, GatewayPrincipalCandidate,
|
||||
};
|
||||
|
||||
pub(super) fn derive_principal_candidate(
|
||||
credentials: &GatewayExtractedCredentials,
|
||||
) -> Option<GatewayPrincipalCandidate> {
|
||||
if let Some(trusted_headers) = credentials.trusted_headers.clone() {
|
||||
return Some(GatewayPrincipalCandidate::TrustedHeaders(trusted_headers));
|
||||
}
|
||||
|
||||
match credentials.primary.as_ref()? {
|
||||
GatewayPrimaryCredential::ProviderApiKey { raw, carrier } => {
|
||||
Some(GatewayPrincipalCandidate::ApiKeyHash {
|
||||
key_hash: hash_api_key(raw),
|
||||
carrier: *carrier,
|
||||
})
|
||||
}
|
||||
GatewayPrimaryCredential::BearerToken { raw, carrier } => {
|
||||
Some(GatewayPrincipalCandidate::DeferredBearerToken {
|
||||
raw: raw.clone(),
|
||||
carrier: *carrier,
|
||||
})
|
||||
}
|
||||
GatewayPrimaryCredential::CookieHeader { raw, carrier } => {
|
||||
Some(GatewayPrincipalCandidate::DeferredCookieHeader {
|
||||
raw: raw.clone(),
|
||||
carrier: *carrier,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::gateway::control::auth::types::{
|
||||
GatewayCredentialBundle, GatewayCredentialCarrier, GatewayExtractedCredentials,
|
||||
GatewayPrimaryCredential, GatewayTrustedAuthHeaders,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn derives_trusted_headers_candidate_before_primary_credential() {
|
||||
let candidate = derive_principal_candidate(&GatewayExtractedCredentials {
|
||||
trusted_headers: Some(GatewayTrustedAuthHeaders {
|
||||
user_id: "user-1".to_string(),
|
||||
api_key_id: "key-1".to_string(),
|
||||
balance_remaining: Some(1.5),
|
||||
access_allowed: Some(true),
|
||||
}),
|
||||
trusted_admin_headers: None,
|
||||
bundle: GatewayCredentialBundle::default(),
|
||||
primary: Some(GatewayPrimaryCredential::ProviderApiKey {
|
||||
raw: "sk-test".to_string(),
|
||||
carrier: GatewayCredentialCarrier::AuthorizationBearer,
|
||||
}),
|
||||
});
|
||||
|
||||
assert!(matches!(
|
||||
candidate,
|
||||
Some(GatewayPrincipalCandidate::TrustedHeaders(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derives_api_key_hash_candidate_for_provider_api_key() {
|
||||
let candidate = derive_principal_candidate(&GatewayExtractedCredentials {
|
||||
trusted_headers: None,
|
||||
trusted_admin_headers: None,
|
||||
bundle: GatewayCredentialBundle::default(),
|
||||
primary: Some(GatewayPrimaryCredential::ProviderApiKey {
|
||||
raw: "sk-test".to_string(),
|
||||
carrier: GatewayCredentialCarrier::XApiKey,
|
||||
}),
|
||||
});
|
||||
|
||||
match candidate {
|
||||
Some(GatewayPrincipalCandidate::ApiKeyHash { key_hash, carrier }) => {
|
||||
assert_eq!(
|
||||
key_hash,
|
||||
"f3abf2a6cc4f00987743db5f544ba345b4899ae31f326d8ee9c4816de153c9e0"
|
||||
);
|
||||
assert_eq!(carrier, GatewayCredentialCarrier::XApiKey);
|
||||
}
|
||||
other => panic!("unexpected candidate: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derives_deferred_cookie_candidate_for_cookie_credentials() {
|
||||
let candidate = derive_principal_candidate(&GatewayExtractedCredentials {
|
||||
trusted_headers: None,
|
||||
trusted_admin_headers: None,
|
||||
bundle: GatewayCredentialBundle::default(),
|
||||
primary: Some(GatewayPrimaryCredential::CookieHeader {
|
||||
raw: "session=test".to_string(),
|
||||
carrier: GatewayCredentialCarrier::CookieHeader,
|
||||
}),
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
candidate,
|
||||
Some(GatewayPrincipalCandidate::DeferredCookieHeader {
|
||||
raw: "session=test".to_string(),
|
||||
carrier: GatewayCredentialCarrier::CookieHeader,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
631
apps/aether-gateway/src/control/auth/resolution.rs
Normal file
631
apps/aether-gateway/src/control/auth/resolution.rs
Normal file
@@ -0,0 +1,631 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use axum::http::Uri;
|
||||
use base64::Engine as _;
|
||||
use hmac::Mac;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
use super::super::GatewayControlDecision;
|
||||
use super::credentials::{
|
||||
build_auth_context_cache_key, contains_string, current_unix_secs, extract_request_credentials,
|
||||
extract_trusted_admin_headers,
|
||||
};
|
||||
use super::gate::GatewayLocalAuthRejection;
|
||||
use super::principal::derive_principal_candidate;
|
||||
use super::types::{GatewayPrincipalCandidate, GatewayTrustedAuthHeaders};
|
||||
use crate::gateway::headers::header_value_str;
|
||||
use crate::gateway::{local_rejection_from_wallet_access, resolve_wallet_auth_gate};
|
||||
|
||||
const AUTH_CONTEXT_CACHE_TTL: Duration = Duration::from_secs(60);
|
||||
const AUTH_CONTEXT_CACHE_MAX_ENTRIES: usize = 256;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub(crate) struct GatewayControlAuthContext {
|
||||
pub(crate) user_id: String,
|
||||
pub(crate) api_key_id: String,
|
||||
pub(crate) balance_remaining: Option<f64>,
|
||||
pub(crate) access_allowed: bool,
|
||||
#[serde(skip)]
|
||||
pub(crate) user_rate_limit: Option<i32>,
|
||||
#[serde(skip)]
|
||||
pub(crate) api_key_rate_limit: Option<i32>,
|
||||
#[serde(skip)]
|
||||
pub(crate) api_key_is_standalone: bool,
|
||||
#[serde(skip)]
|
||||
pub(crate) local_rejection: Option<GatewayLocalAuthRejection>,
|
||||
#[serde(skip)]
|
||||
pub(crate) allowed_models: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct GatewayAdminPrincipalContext {
|
||||
pub(crate) user_id: String,
|
||||
pub(crate) user_role: String,
|
||||
pub(crate) session_id: Option<String>,
|
||||
pub(crate) management_token_id: Option<String>,
|
||||
}
|
||||
|
||||
pub(in super::super) enum ControlDecisionAuthResolution {
|
||||
Resolved(GatewayControlDecision),
|
||||
}
|
||||
|
||||
pub(in super::super) async fn resolve_control_decision_auth(
|
||||
state: &AppState,
|
||||
headers: &http::HeaderMap,
|
||||
uri: &Uri,
|
||||
mut decision: GatewayControlDecision,
|
||||
) -> Result<ControlDecisionAuthResolution, GatewayError> {
|
||||
if let Some(admin_principal) =
|
||||
resolve_trusted_admin_principal(headers, decision.auth_endpoint_signature.as_deref())
|
||||
{
|
||||
decision.admin_principal = Some(admin_principal);
|
||||
} else if let Some(admin_principal) = resolve_local_admin_principal(
|
||||
state,
|
||||
headers,
|
||||
uri,
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
decision.admin_principal = Some(admin_principal);
|
||||
}
|
||||
|
||||
if let Some(auth_context) = resolve_data_backed_auth_context(
|
||||
state,
|
||||
headers,
|
||||
uri,
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
)
|
||||
.await?
|
||||
{
|
||||
decision.local_auth_rejection = auth_context.local_rejection.clone();
|
||||
if !auth_context.user_id.is_empty() && !auth_context.api_key_id.is_empty() {
|
||||
if let Some(cache_key) = decision
|
||||
.auth_endpoint_signature
|
||||
.as_deref()
|
||||
.and_then(|signature| build_auth_context_cache_key(headers, uri, signature))
|
||||
{
|
||||
put_cached_auth_context(state, cache_key, auth_context.clone());
|
||||
}
|
||||
decision.auth_context = Some(auth_context);
|
||||
}
|
||||
}
|
||||
|
||||
if decision.local_auth_rejection.is_some() {
|
||||
return Ok(ControlDecisionAuthResolution::Resolved(decision));
|
||||
}
|
||||
|
||||
if decision.is_execution_runtime_candidate() {
|
||||
return Ok(ControlDecisionAuthResolution::Resolved(decision));
|
||||
}
|
||||
|
||||
if decision.auth_context.is_some() {
|
||||
return Ok(ControlDecisionAuthResolution::Resolved(decision));
|
||||
}
|
||||
|
||||
if skips_legacy_python_auth_context(&decision) {
|
||||
return Ok(ControlDecisionAuthResolution::Resolved(decision));
|
||||
}
|
||||
|
||||
Ok(ControlDecisionAuthResolution::Resolved(decision))
|
||||
}
|
||||
|
||||
fn skips_legacy_python_auth_context(decision: &GatewayControlDecision) -> bool {
|
||||
matches!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("chat" | "cli" | "compact")
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_trusted_admin_principal(
|
||||
headers: &http::HeaderMap,
|
||||
auth_endpoint_signature: Option<&str>,
|
||||
) -> Option<GatewayAdminPrincipalContext> {
|
||||
if !auth_endpoint_signature
|
||||
.map(str::trim)
|
||||
.unwrap_or_default()
|
||||
.starts_with("admin:")
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let trusted_headers = extract_trusted_admin_headers(headers)?;
|
||||
Some(GatewayAdminPrincipalContext {
|
||||
user_id: trusted_headers.user_id,
|
||||
user_role: trusted_headers.user_role,
|
||||
session_id: trusted_headers.session_id,
|
||||
management_token_id: trusted_headers.management_token_id,
|
||||
})
|
||||
}
|
||||
|
||||
async fn resolve_local_admin_principal(
|
||||
state: &AppState,
|
||||
headers: &http::HeaderMap,
|
||||
uri: &Uri,
|
||||
auth_endpoint_signature: Option<&str>,
|
||||
) -> Result<Option<GatewayAdminPrincipalContext>, GatewayError> {
|
||||
let Some(signature) = auth_endpoint_signature
|
||||
.map(str::trim)
|
||||
.filter(|value| value.starts_with("admin:"))
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let extracted = extract_request_credentials(headers, uri, signature);
|
||||
let Some(access_token) = extracted.bundle.authorization_bearer.as_deref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let claims = match decode_local_auth_token(access_token, "access") {
|
||||
Ok(claims) => claims,
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
if claims
|
||||
.get("role")
|
||||
.and_then(Value::as_str)
|
||||
.is_some_and(|role| !role.eq_ignore_ascii_case("admin"))
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
resolve_local_admin_principal_from_claims(state, headers, uri, &claims).await
|
||||
}
|
||||
|
||||
async fn resolve_local_admin_principal_from_claims(
|
||||
state: &AppState,
|
||||
headers: &http::HeaderMap,
|
||||
uri: &Uri,
|
||||
claims: &serde_json::Map<String, Value>,
|
||||
) -> Result<Option<GatewayAdminPrincipalContext>, GatewayError> {
|
||||
let Some(user_id) = claims.get("user_id").and_then(Value::as_str) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(session_id) = claims.get("session_id").and_then(Value::as_str) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(client_device_id) = extract_local_admin_client_device_id(headers, uri) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(user) = state.find_user_auth_by_id(user_id).await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
if !user.is_active || user.is_deleted || !user.role.eq_ignore_ascii_case("admin") {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let now = chrono::Utc::now();
|
||||
let Some(session) = state.find_user_session(user_id, session_id).await? else {
|
||||
return Ok(None);
|
||||
};
|
||||
if session.is_revoked()
|
||||
|| session.is_expired(now)
|
||||
|| session.client_device_id != client_device_id
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if session.should_touch(now) {
|
||||
let _ = state
|
||||
.touch_user_session(
|
||||
user_id,
|
||||
session_id,
|
||||
now,
|
||||
None,
|
||||
local_admin_user_agent(headers).as_deref(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(Some(GatewayAdminPrincipalContext {
|
||||
user_id: user.id,
|
||||
user_role: "admin".to_string(),
|
||||
session_id: Some(session.id),
|
||||
management_token_id: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn extract_local_admin_client_device_id(headers: &http::HeaderMap, uri: &Uri) -> Option<String> {
|
||||
let header_value = header_value_str(headers, "x-client-device-id");
|
||||
let query_value = uri.query().and_then(|query| {
|
||||
url::form_urlencoded::parse(query.as_bytes())
|
||||
.find(|(key, _)| key == "client_device_id")
|
||||
.map(|(_, value)| value.into_owned())
|
||||
});
|
||||
let candidate = header_value.or(query_value)?;
|
||||
let candidate = candidate.trim();
|
||||
if candidate.is_empty()
|
||||
|| candidate.len() > 128
|
||||
|| !candidate
|
||||
.chars()
|
||||
.all(|ch| ch.is_ascii_alphanumeric() || ch == '-' || ch == '_')
|
||||
{
|
||||
return None;
|
||||
}
|
||||
Some(candidate.to_string())
|
||||
}
|
||||
|
||||
fn local_admin_user_agent(headers: &http::HeaderMap) -> Option<String> {
|
||||
header_value_str(headers, http::header::USER_AGENT.as_str())
|
||||
.map(|value| value.chars().take(1000).collect())
|
||||
}
|
||||
|
||||
fn local_auth_secret() -> String {
|
||||
std::env::var("JWT_SECRET_KEY")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or_else(|| "aether-rust-dev-jwt-secret".to_string())
|
||||
}
|
||||
|
||||
fn decode_local_auth_token(
|
||||
token: &str,
|
||||
expected_type: &str,
|
||||
) -> Result<serde_json::Map<String, Value>, String> {
|
||||
let mut parts = token.split('.');
|
||||
let Some(header_segment) = parts.next() else {
|
||||
return Err("invalid token".to_string());
|
||||
};
|
||||
let Some(payload_segment) = parts.next() else {
|
||||
return Err("invalid token".to_string());
|
||||
};
|
||||
let Some(signature_segment) = parts.next() else {
|
||||
return Err("invalid token".to_string());
|
||||
};
|
||||
if parts.next().is_some() {
|
||||
return Err("invalid token".to_string());
|
||||
}
|
||||
|
||||
let signing_input = format!("{header_segment}.{payload_segment}");
|
||||
let signature = base64::engine::general_purpose::URL_SAFE_NO_PAD
|
||||
.decode(signature_segment)
|
||||
.map_err(|_| "invalid token".to_string())?;
|
||||
let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(local_auth_secret().as_bytes())
|
||||
.map_err(|_| "invalid token".to_string())?;
|
||||
mac.update(signing_input.as_bytes());
|
||||
mac.verify_slice(&signature)
|
||||
.map_err(|_| "invalid token".to_string())?;
|
||||
|
||||
let payload_bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
|
||||
.decode(payload_segment)
|
||||
.map_err(|_| "invalid token".to_string())?;
|
||||
let payload =
|
||||
serde_json::from_slice::<Value>(&payload_bytes).map_err(|_| "invalid token".to_string())?;
|
||||
let payload = payload
|
||||
.as_object()
|
||||
.cloned()
|
||||
.ok_or_else(|| "invalid token".to_string())?;
|
||||
let actual_type = payload
|
||||
.get("type")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default();
|
||||
if actual_type != expected_type {
|
||||
return Err("invalid token".to_string());
|
||||
}
|
||||
let exp = payload
|
||||
.get("exp")
|
||||
.and_then(Value::as_i64)
|
||||
.ok_or_else(|| "invalid token".to_string())?;
|
||||
if exp <= chrono::Utc::now().timestamp() {
|
||||
return Err("expired token".to_string());
|
||||
}
|
||||
Ok(payload)
|
||||
}
|
||||
|
||||
pub(crate) async fn resolve_execution_runtime_auth_context(
|
||||
state: &AppState,
|
||||
decision: &GatewayControlDecision,
|
||||
headers: &http::HeaderMap,
|
||||
uri: &Uri,
|
||||
trace_id: &str,
|
||||
) -> Result<Option<GatewayControlAuthContext>, GatewayError> {
|
||||
let _ = trace_id;
|
||||
|
||||
if let Some(auth_context) = decision.auth_context.clone() {
|
||||
return Ok(Some(auth_context));
|
||||
}
|
||||
|
||||
let Some(auth_endpoint_signature) = decision.auth_endpoint_signature.as_deref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(cache_key) = build_auth_context_cache_key(headers, uri, auth_endpoint_signature)
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
if let Some(auth_context) = get_cached_auth_context(state, &cache_key) {
|
||||
return Ok(Some(auth_context));
|
||||
}
|
||||
|
||||
if let Some(auth_context) =
|
||||
resolve_data_backed_auth_context(state, headers, uri, Some(auth_endpoint_signature)).await?
|
||||
{
|
||||
if auth_context.user_id.is_empty() || auth_context.api_key_id.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
put_cached_auth_context(state, cache_key, auth_context.clone());
|
||||
return Ok(Some(auth_context));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn put_cached_auth_context(
|
||||
state: &AppState,
|
||||
cache_key: String,
|
||||
auth_context: GatewayControlAuthContext,
|
||||
) {
|
||||
state.auth_context_cache.insert(
|
||||
cache_key,
|
||||
auth_context,
|
||||
AUTH_CONTEXT_CACHE_TTL,
|
||||
AUTH_CONTEXT_CACHE_MAX_ENTRIES,
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) async fn resolve_data_backed_auth_context(
|
||||
state: &AppState,
|
||||
headers: &http::HeaderMap,
|
||||
uri: &Uri,
|
||||
auth_endpoint_signature: Option<&str>,
|
||||
) -> Result<Option<GatewayControlAuthContext>, GatewayError> {
|
||||
let Some(signature) = auth_endpoint_signature
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
if !state.has_auth_api_key_reader() {
|
||||
return Ok(None);
|
||||
}
|
||||
let extracted = extract_request_credentials(headers, uri, signature);
|
||||
let principal = derive_principal_candidate(&extracted);
|
||||
let now_unix_secs = current_unix_secs();
|
||||
|
||||
match principal {
|
||||
Some(GatewayPrincipalCandidate::TrustedHeaders(trusted_headers)) => {
|
||||
resolve_trusted_auth_context(state, signature, trusted_headers, now_unix_secs).await
|
||||
}
|
||||
Some(GatewayPrincipalCandidate::ApiKeyHash { key_hash, .. }) => {
|
||||
let snapshot = state
|
||||
.read_auth_api_key_snapshot_by_key_hash(&key_hash, now_unix_secs)
|
||||
.await?;
|
||||
let Some(snapshot) = snapshot else {
|
||||
return Ok(Some(GatewayControlAuthContext {
|
||||
user_id: String::new(),
|
||||
api_key_id: String::new(),
|
||||
balance_remaining: None,
|
||||
access_allowed: false,
|
||||
user_rate_limit: None,
|
||||
api_key_rate_limit: None,
|
||||
api_key_is_standalone: false,
|
||||
local_rejection: Some(GatewayLocalAuthRejection::InvalidApiKey),
|
||||
allowed_models: None,
|
||||
}));
|
||||
};
|
||||
|
||||
state
|
||||
.touch_auth_api_key_last_used_best_effort(&snapshot.api_key_id)
|
||||
.await;
|
||||
|
||||
let wallet_access = resolve_wallet_auth_gate(state, &snapshot).await?;
|
||||
Ok(Some(build_data_backed_auth_context(
|
||||
snapshot,
|
||||
signature,
|
||||
None,
|
||||
None,
|
||||
wallet_access,
|
||||
)))
|
||||
}
|
||||
Some(
|
||||
GatewayPrincipalCandidate::DeferredBearerToken { .. }
|
||||
| GatewayPrincipalCandidate::DeferredCookieHeader { .. },
|
||||
) => Ok(None),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve_trusted_auth_context(
|
||||
state: &AppState,
|
||||
auth_endpoint_signature: &str,
|
||||
trusted_headers: GatewayTrustedAuthHeaders,
|
||||
now_unix_secs: u64,
|
||||
) -> Result<Option<GatewayControlAuthContext>, GatewayError> {
|
||||
let snapshot = state
|
||||
.read_auth_api_key_snapshot(
|
||||
&trusted_headers.user_id,
|
||||
&trusted_headers.api_key_id,
|
||||
now_unix_secs,
|
||||
)
|
||||
.await?;
|
||||
let Some(snapshot) = snapshot else {
|
||||
return Ok(Some(GatewayControlAuthContext {
|
||||
user_id: trusted_headers.user_id,
|
||||
api_key_id: trusted_headers.api_key_id,
|
||||
balance_remaining: trusted_headers.balance_remaining,
|
||||
access_allowed: false,
|
||||
user_rate_limit: None,
|
||||
api_key_rate_limit: None,
|
||||
api_key_is_standalone: false,
|
||||
local_rejection: Some(GatewayLocalAuthRejection::InvalidApiKey),
|
||||
allowed_models: None,
|
||||
}));
|
||||
};
|
||||
|
||||
let wallet_access = resolve_wallet_auth_gate(state, &snapshot).await?;
|
||||
Ok(Some(build_data_backed_auth_context(
|
||||
snapshot,
|
||||
auth_endpoint_signature,
|
||||
trusted_headers.access_allowed,
|
||||
trusted_headers.balance_remaining,
|
||||
wallet_access,
|
||||
)))
|
||||
}
|
||||
|
||||
fn build_data_backed_auth_context(
|
||||
snapshot: crate::gateway::gateway_data::StoredGatewayAuthApiKeySnapshot,
|
||||
auth_endpoint_signature: &str,
|
||||
header_access_allowed: Option<bool>,
|
||||
balance_remaining: Option<f64>,
|
||||
wallet_access: Option<aether_wallet::WalletAccessDecision>,
|
||||
) -> GatewayControlAuthContext {
|
||||
let allowed_models = snapshot
|
||||
.effective_allowed_models()
|
||||
.map(|items| items.to_vec());
|
||||
let invalid_api_key = !snapshot.user_is_active
|
||||
|| snapshot.user_is_deleted
|
||||
|| !snapshot.api_key_is_active
|
||||
|| snapshot
|
||||
.api_key_expires_at_unix_secs
|
||||
.is_some_and(|expires_at| expires_at < current_unix_secs());
|
||||
let locked_api_key = snapshot.api_key_is_locked && !snapshot.api_key_is_standalone;
|
||||
let access_allowed = header_access_allowed
|
||||
.map(|value| value && snapshot.currently_usable)
|
||||
.unwrap_or(snapshot.currently_usable);
|
||||
let wallet_remaining = wallet_access
|
||||
.as_ref()
|
||||
.and_then(|decision| decision.remaining);
|
||||
let requested_provider = auth_endpoint_signature
|
||||
.split_once(':')
|
||||
.map(|(provider, _)| provider)
|
||||
.unwrap_or(auth_endpoint_signature)
|
||||
.trim();
|
||||
let local_rejection = if invalid_api_key {
|
||||
Some(GatewayLocalAuthRejection::InvalidApiKey)
|
||||
} else if locked_api_key {
|
||||
Some(GatewayLocalAuthRejection::LockedApiKey)
|
||||
} else if let Some(rejection) = wallet_access
|
||||
.as_ref()
|
||||
.and_then(local_rejection_from_wallet_access)
|
||||
{
|
||||
Some(rejection)
|
||||
} else if header_access_allowed.is_some_and(|value| !value) && snapshot.currently_usable {
|
||||
Some(GatewayLocalAuthRejection::BalanceDenied {
|
||||
remaining: balance_remaining.or(wallet_remaining),
|
||||
})
|
||||
} else if !requested_provider.is_empty()
|
||||
&& snapshot
|
||||
.effective_allowed_providers()
|
||||
.is_some_and(|allowed| !contains_string(allowed, requested_provider))
|
||||
{
|
||||
Some(GatewayLocalAuthRejection::ProviderNotAllowed {
|
||||
provider: requested_provider.to_string(),
|
||||
})
|
||||
} else if snapshot
|
||||
.effective_allowed_api_formats()
|
||||
.is_some_and(|allowed| !contains_string(allowed, auth_endpoint_signature))
|
||||
{
|
||||
Some(GatewayLocalAuthRejection::ApiFormatNotAllowed {
|
||||
api_format: auth_endpoint_signature.to_string(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
GatewayControlAuthContext {
|
||||
user_id: snapshot.user_id,
|
||||
api_key_id: snapshot.api_key_id,
|
||||
balance_remaining: wallet_remaining.or(balance_remaining),
|
||||
access_allowed,
|
||||
user_rate_limit: snapshot.user_rate_limit,
|
||||
api_key_rate_limit: snapshot.api_key_rate_limit,
|
||||
api_key_is_standalone: snapshot.api_key_is_standalone,
|
||||
local_rejection,
|
||||
allowed_models,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_cached_auth_context(state: &AppState, cache_key: &str) -> Option<GatewayControlAuthContext> {
|
||||
state
|
||||
.auth_context_cache
|
||||
.get_fresh(cache_key, AUTH_CONTEXT_CACHE_TTL)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use aether_data::repository::auth::{
|
||||
InMemoryAuthApiKeySnapshotRepository, StoredAuthApiKeySnapshot,
|
||||
};
|
||||
use axum::http::{HeaderMap, Uri};
|
||||
|
||||
use super::resolve_data_backed_auth_context;
|
||||
use crate::gateway::control::auth::credentials::hash_api_key;
|
||||
use crate::gateway::{AppState, GatewayDataState};
|
||||
|
||||
fn sample_snapshot(api_key_id: &str, user_id: &str) -> StoredAuthApiKeySnapshot {
|
||||
StoredAuthApiKeySnapshot::new(
|
||||
user_id.to_string(),
|
||||
"alice".to_string(),
|
||||
Some("alice@example.com".to_string()),
|
||||
"user".to_string(),
|
||||
"local".to_string(),
|
||||
true,
|
||||
false,
|
||||
Some(serde_json::json!(["openai"])),
|
||||
Some(serde_json::json!(["openai:chat"])),
|
||||
Some(serde_json::json!(["gpt-4.1"])),
|
||||
api_key_id.to_string(),
|
||||
Some("default".to_string()),
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
Some(60),
|
||||
Some(5),
|
||||
Some(4_102_444_800),
|
||||
Some(serde_json::json!(["openai"])),
|
||||
Some(serde_json::json!(["openai:chat"])),
|
||||
Some(serde_json::json!(["gpt-4.1"])),
|
||||
)
|
||||
.expect("snapshot should build")
|
||||
}
|
||||
|
||||
fn uri(path: &str) -> Uri {
|
||||
path.parse().expect("uri should parse")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn data_backed_api_key_auth_touches_last_used_once_per_throttle_window() {
|
||||
let api_key = "sk-test-touch";
|
||||
let repository = Arc::new(InMemoryAuthApiKeySnapshotRepository::seed(vec![(
|
||||
Some(hash_api_key(api_key)),
|
||||
sample_snapshot("key-1", "user-1"),
|
||||
)]));
|
||||
let data = GatewayDataState::with_auth_api_key_repository_for_tests(repository.clone());
|
||||
let state = AppState::new("http://127.0.0.1:9")
|
||||
.expect("state should build")
|
||||
.with_data_state_for_tests(data);
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
http::header::AUTHORIZATION,
|
||||
format!("Bearer {api_key}").parse().unwrap(),
|
||||
);
|
||||
|
||||
let first = resolve_data_backed_auth_context(
|
||||
&state,
|
||||
&headers,
|
||||
&uri("/v1/chat/completions"),
|
||||
Some("openai:chat"),
|
||||
)
|
||||
.await
|
||||
.expect("resolution should succeed")
|
||||
.expect("auth context should exist");
|
||||
assert_eq!(first.user_id, "user-1");
|
||||
assert_eq!(first.api_key_id, "key-1");
|
||||
assert_eq!(repository.touch_count("key-1"), 1);
|
||||
|
||||
let second = resolve_data_backed_auth_context(
|
||||
&state,
|
||||
&headers,
|
||||
&uri("/v1/chat/completions"),
|
||||
Some("openai:chat"),
|
||||
)
|
||||
.await
|
||||
.expect("resolution should succeed")
|
||||
.expect("auth context should exist");
|
||||
assert_eq!(second.api_key_id, "key-1");
|
||||
assert_eq!(repository.touch_count("key-1"), 1);
|
||||
}
|
||||
}
|
||||
76
apps/aether-gateway/src/control/auth/types.rs
Normal file
76
apps/aether-gateway/src/control/auth/types.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum GatewayCredentialCarrier {
|
||||
AuthorizationBearer,
|
||||
XApiKey,
|
||||
ApiKey,
|
||||
XGoogApiKey,
|
||||
QueryKey,
|
||||
CookieHeader,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(super) struct GatewayTrustedAuthHeaders {
|
||||
pub(super) user_id: String,
|
||||
pub(super) api_key_id: String,
|
||||
pub(super) balance_remaining: Option<f64>,
|
||||
pub(super) access_allowed: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) struct GatewayTrustedAdminHeaders {
|
||||
pub(super) user_id: String,
|
||||
pub(super) user_role: String,
|
||||
pub(super) session_id: Option<String>,
|
||||
pub(super) management_token_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub(super) struct GatewayCredentialBundle {
|
||||
pub(super) authorization_bearer: Option<String>,
|
||||
pub(super) x_api_key: Option<String>,
|
||||
pub(super) api_key: Option<String>,
|
||||
pub(super) x_goog_api_key: Option<String>,
|
||||
pub(super) query_key: Option<String>,
|
||||
pub(super) cookie_header: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(super) enum GatewayPrimaryCredential {
|
||||
ProviderApiKey {
|
||||
raw: String,
|
||||
carrier: GatewayCredentialCarrier,
|
||||
},
|
||||
BearerToken {
|
||||
raw: String,
|
||||
carrier: GatewayCredentialCarrier,
|
||||
},
|
||||
CookieHeader {
|
||||
raw: String,
|
||||
carrier: GatewayCredentialCarrier,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(super) struct GatewayExtractedCredentials {
|
||||
pub(super) trusted_headers: Option<GatewayTrustedAuthHeaders>,
|
||||
pub(super) trusted_admin_headers: Option<GatewayTrustedAdminHeaders>,
|
||||
pub(super) bundle: GatewayCredentialBundle,
|
||||
pub(super) primary: Option<GatewayPrimaryCredential>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(super) enum GatewayPrincipalCandidate {
|
||||
TrustedHeaders(GatewayTrustedAuthHeaders),
|
||||
ApiKeyHash {
|
||||
key_hash: String,
|
||||
carrier: GatewayCredentialCarrier,
|
||||
},
|
||||
DeferredBearerToken {
|
||||
raw: String,
|
||||
carrier: GatewayCredentialCarrier,
|
||||
},
|
||||
DeferredCookieHeader {
|
||||
raw: String,
|
||||
carrier: GatewayCredentialCarrier,
|
||||
},
|
||||
}
|
||||
68
apps/aether-gateway/src/control/execute.rs
Normal file
68
apps/aether-gateway/src/control/execute.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use axum::body::{Body, Bytes};
|
||||
use axum::http::{HeaderName, HeaderValue, Response};
|
||||
|
||||
use crate::gateway::constants::CONTROL_EXECUTED_HEADER;
|
||||
use crate::gateway::{AppState, GatewayControlDecision, GatewayError};
|
||||
|
||||
use super::resolve_execution_runtime_auth_context;
|
||||
|
||||
pub(crate) fn allows_control_execute_emergency(decision: &GatewayControlDecision) -> bool {
|
||||
decision.is_execution_runtime_candidate()
|
||||
}
|
||||
|
||||
pub(crate) async fn maybe_execute_via_control(
|
||||
state: &AppState,
|
||||
parts: &http::request::Parts,
|
||||
body_bytes: Bytes,
|
||||
trace_id: &str,
|
||||
decision: Option<&GatewayControlDecision>,
|
||||
require_stream: bool,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
let Some(decision) = decision else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut local_decision = decision.clone();
|
||||
if let Some(auth_context) = resolve_execution_runtime_auth_context(
|
||||
state,
|
||||
&local_decision,
|
||||
&parts.headers,
|
||||
&parts.uri,
|
||||
trace_id,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
local_decision.auth_context = Some(auth_context);
|
||||
local_decision.local_auth_rejection = None;
|
||||
}
|
||||
|
||||
let response = if require_stream {
|
||||
crate::gateway::maybe_execute_via_execution_runtime_stream(
|
||||
state,
|
||||
parts,
|
||||
&body_bytes,
|
||||
trace_id,
|
||||
Some(&local_decision),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
crate::gateway::maybe_execute_via_execution_runtime_sync(
|
||||
state,
|
||||
parts,
|
||||
&body_bytes,
|
||||
trace_id,
|
||||
Some(&local_decision),
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
Ok(response.map(mark_control_executed))
|
||||
}
|
||||
|
||||
fn mark_control_executed(mut response: Response<Body>) -> Response<Body> {
|
||||
response.headers_mut().insert(
|
||||
HeaderName::from_static(CONTROL_EXECUTED_HEADER),
|
||||
HeaderValue::from_static("true"),
|
||||
);
|
||||
response
|
||||
}
|
||||
25
apps/aether-gateway/src/control/mod.rs
Normal file
25
apps/aether-gateway/src/control/mod.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
#[cfg(test)]
|
||||
use axum::http::Uri;
|
||||
|
||||
#[path = "auth/mod.rs"]
|
||||
mod auth;
|
||||
#[path = "execute.rs"]
|
||||
mod execute;
|
||||
#[path = "public.rs"]
|
||||
mod public;
|
||||
#[path = "route.rs"]
|
||||
mod route;
|
||||
|
||||
pub(crate) use auth::{
|
||||
extract_requested_model, request_model_local_rejection, resolve_execution_runtime_auth_context,
|
||||
should_buffer_request_for_local_auth, trusted_auth_local_rejection,
|
||||
GatewayAdminPrincipalContext, GatewayControlAuthContext, GatewayLocalAuthRejection,
|
||||
};
|
||||
pub(crate) use execute::{allows_control_execute_emergency, maybe_execute_via_control};
|
||||
pub(crate) use public::{resolve_public_request_context, GatewayPublicRequestContext};
|
||||
#[cfg(test)]
|
||||
pub(crate) use route::classify_control_route;
|
||||
pub(crate) use route::{resolve_control_route, GatewayControlDecision};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
73
apps/aether-gateway/src/control/public.rs
Normal file
73
apps/aether-gateway/src/control/public.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use axum::http::Uri;
|
||||
|
||||
use crate::gateway::headers::header_value_str;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
use super::{resolve_control_route, GatewayControlDecision};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct GatewayPublicRequestContext {
|
||||
pub(crate) trace_id: String,
|
||||
pub(crate) request_method: http::Method,
|
||||
pub(crate) request_path: String,
|
||||
pub(crate) request_query_string: Option<String>,
|
||||
pub(crate) request_content_type: Option<String>,
|
||||
pub(crate) host_header: Option<String>,
|
||||
pub(crate) control_decision: Option<GatewayControlDecision>,
|
||||
}
|
||||
|
||||
impl GatewayPublicRequestContext {
|
||||
pub(crate) fn from_request_parts(
|
||||
trace_id: impl Into<String>,
|
||||
method: &http::Method,
|
||||
uri: &Uri,
|
||||
headers: &http::HeaderMap,
|
||||
control_decision: Option<GatewayControlDecision>,
|
||||
) -> Self {
|
||||
let request_path = if uri.path().starts_with('/') {
|
||||
uri.path().to_string()
|
||||
} else {
|
||||
format!("/{}", uri.path())
|
||||
};
|
||||
let request_query_string = uri.query().map(ToOwned::to_owned);
|
||||
|
||||
Self {
|
||||
trace_id: trace_id.into(),
|
||||
request_method: method.clone(),
|
||||
request_path,
|
||||
request_query_string,
|
||||
request_content_type: header_value_str(headers, http::header::CONTENT_TYPE.as_str()),
|
||||
host_header: header_value_str(headers, http::header::HOST.as_str()),
|
||||
control_decision,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn request_path_and_query(&self) -> String {
|
||||
if let Some(query) = self
|
||||
.request_query_string
|
||||
.as_deref()
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
format!("{}?{query}", self.request_path)
|
||||
} else {
|
||||
self.request_path.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn resolve_public_request_context(
|
||||
state: &AppState,
|
||||
method: &http::Method,
|
||||
uri: &Uri,
|
||||
headers: &http::HeaderMap,
|
||||
trace_id: &str,
|
||||
) -> Result<GatewayPublicRequestContext, GatewayError> {
|
||||
let control_decision = resolve_control_route(state, method, uri, headers, trace_id).await?;
|
||||
Ok(GatewayPublicRequestContext::from_request_parts(
|
||||
trace_id,
|
||||
method,
|
||||
uri,
|
||||
headers,
|
||||
control_decision,
|
||||
))
|
||||
}
|
||||
224
apps/aether-gateway/src/control/route.rs
Normal file
224
apps/aether-gateway/src/control/route.rs
Normal file
@@ -0,0 +1,224 @@
|
||||
use axum::http::Uri;
|
||||
|
||||
use crate::gateway::headers::header_value_str;
|
||||
use crate::gateway::{AppState, GatewayError};
|
||||
|
||||
#[path = "route/admin.rs"]
|
||||
mod admin;
|
||||
#[path = "route/ai.rs"]
|
||||
mod ai;
|
||||
#[path = "route/internal.rs"]
|
||||
mod internal;
|
||||
#[path = "route/oauth.rs"]
|
||||
mod oauth;
|
||||
#[path = "route/public_support.rs"]
|
||||
mod public_support;
|
||||
|
||||
use super::auth::{resolve_control_decision_auth, ControlDecisionAuthResolution};
|
||||
use super::{GatewayAdminPrincipalContext, GatewayControlAuthContext, GatewayLocalAuthRejection};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct GatewayControlDecision {
|
||||
pub(crate) public_path: String,
|
||||
pub(crate) public_query_string: Option<String>,
|
||||
pub(crate) route_class: Option<String>,
|
||||
pub(crate) route_family: Option<String>,
|
||||
pub(crate) route_kind: Option<String>,
|
||||
pub(crate) auth_endpoint_signature: Option<String>,
|
||||
pub(crate) execution_runtime_candidate: bool,
|
||||
pub(crate) auth_context: Option<GatewayControlAuthContext>,
|
||||
pub(crate) admin_principal: Option<GatewayAdminPrincipalContext>,
|
||||
pub(crate) local_auth_rejection: Option<GatewayLocalAuthRejection>,
|
||||
}
|
||||
|
||||
impl GatewayControlDecision {
|
||||
pub(crate) fn synthetic(
|
||||
public_path: impl Into<String>,
|
||||
route_class: Option<String>,
|
||||
route_family: Option<String>,
|
||||
route_kind: Option<String>,
|
||||
auth_endpoint_signature: Option<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
public_path: public_path.into(),
|
||||
public_query_string: None,
|
||||
route_class,
|
||||
route_family,
|
||||
route_kind,
|
||||
auth_endpoint_signature,
|
||||
execution_runtime_candidate: false,
|
||||
auth_context: None,
|
||||
admin_principal: None,
|
||||
local_auth_rejection: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn proxy_path_and_query(&self) -> String {
|
||||
if let Some(query) = self
|
||||
.public_query_string
|
||||
.as_deref()
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
format!("{}?{}", self.public_path, query)
|
||||
} else {
|
||||
self.public_path.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_execution_runtime_candidate(&self) -> bool {
|
||||
self.execution_runtime_candidate
|
||||
}
|
||||
|
||||
pub(crate) fn with_execution_runtime_candidate(mut self, value: bool) -> Self {
|
||||
self.execution_runtime_candidate = value;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ClassifiedRoute {
|
||||
route_class: &'static str,
|
||||
route_family: &'static str,
|
||||
route_kind: &'static str,
|
||||
auth_endpoint_signature: String,
|
||||
execution_runtime_candidate: bool,
|
||||
}
|
||||
|
||||
pub(super) fn classified(
|
||||
route_class: &'static str,
|
||||
route_family: &'static str,
|
||||
route_kind: &'static str,
|
||||
auth_endpoint_signature: impl Into<String>,
|
||||
execution_runtime_candidate: bool,
|
||||
) -> ClassifiedRoute {
|
||||
ClassifiedRoute {
|
||||
route_class,
|
||||
route_family,
|
||||
route_kind,
|
||||
auth_endpoint_signature: auth_endpoint_signature.into(),
|
||||
execution_runtime_candidate,
|
||||
}
|
||||
}
|
||||
|
||||
impl ClassifiedRoute {
|
||||
fn into_decision(self, public_path: String) -> GatewayControlDecision {
|
||||
GatewayControlDecision {
|
||||
public_path,
|
||||
public_query_string: None,
|
||||
route_class: Some(self.route_class.to_string()),
|
||||
route_family: Some(self.route_family.to_string()),
|
||||
route_kind: Some(self.route_kind.to_string()),
|
||||
auth_endpoint_signature: Some(self.auth_endpoint_signature),
|
||||
execution_runtime_candidate: self.execution_runtime_candidate,
|
||||
auth_context: None,
|
||||
admin_principal: None,
|
||||
local_auth_rejection: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn resolve_control_route(
|
||||
state: &AppState,
|
||||
method: &http::Method,
|
||||
uri: &Uri,
|
||||
headers: &http::HeaderMap,
|
||||
_trace_id: &str,
|
||||
) -> Result<Option<GatewayControlDecision>, GatewayError> {
|
||||
let Some(mut decision) = classify_control_route(method, uri, headers) else {
|
||||
return Ok(None);
|
||||
};
|
||||
decision.public_query_string = uri.query().map(ToOwned::to_owned);
|
||||
|
||||
match resolve_control_decision_auth(state, headers, uri, decision).await? {
|
||||
ControlDecisionAuthResolution::Resolved(decision) => Ok(Some(decision)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn classify_control_route(
|
||||
method: &http::Method,
|
||||
uri: &Uri,
|
||||
headers: &http::HeaderMap,
|
||||
) -> Option<GatewayControlDecision> {
|
||||
let path = uri.path();
|
||||
let normalized_path = if path.starts_with('/') {
|
||||
path.to_string()
|
||||
} else {
|
||||
format!("/{path}")
|
||||
};
|
||||
|
||||
let public_models_auth_signature = detect_public_models_auth_signature(uri, headers);
|
||||
|
||||
let classified = public_support::classify_public_support_route(
|
||||
method,
|
||||
&normalized_path,
|
||||
&public_models_auth_signature,
|
||||
)
|
||||
.or_else(|| oauth::classify_oauth_route(method, &normalized_path))
|
||||
.or_else(|| admin::classify_admin_route(method, &normalized_path))
|
||||
.or_else(|| internal::classify_internal_route(method, &normalized_path))
|
||||
.or_else(|| ai::classify_ai_public_route(method, &normalized_path, headers))?;
|
||||
|
||||
Some(classified.into_decision(normalized_path))
|
||||
}
|
||||
|
||||
pub(super) fn detect_public_models_auth_signature(uri: &Uri, headers: &http::HeaderMap) -> String {
|
||||
let has_claude_key = header_value_str(headers, "x-api-key")
|
||||
.or_else(|| header_value_str(headers, "api-key"))
|
||||
.is_some();
|
||||
let has_anthropic_version = header_value_str(headers, "anthropic-version").is_some();
|
||||
if has_claude_key && has_anthropic_version {
|
||||
return "claude:chat".to_string();
|
||||
}
|
||||
|
||||
let has_gemini_key = header_value_str(headers, "x-goog-api-key").is_some()
|
||||
|| uri.query().is_some_and(|query| {
|
||||
url::form_urlencoded::parse(query.as_bytes())
|
||||
.any(|(key, value)| key == "key" && !value.trim().is_empty())
|
||||
});
|
||||
if has_gemini_key {
|
||||
return "gemini:chat".to_string();
|
||||
}
|
||||
|
||||
if uri.path().starts_with("/v1beta/models") {
|
||||
return "gemini:chat".to_string();
|
||||
}
|
||||
|
||||
"openai:chat".to_string()
|
||||
}
|
||||
|
||||
pub(super) fn is_claude_cli_request(headers: &http::HeaderMap) -> bool {
|
||||
let auth_header = header_value_str(headers, http::header::AUTHORIZATION.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase();
|
||||
let has_bearer = auth_header.starts_with("bearer ");
|
||||
let has_api_key =
|
||||
header_value_str(headers, "x-api-key").is_some_and(|value| !value.trim().is_empty());
|
||||
has_bearer && !has_api_key
|
||||
}
|
||||
|
||||
pub(super) fn is_gemini_cli_request(headers: &http::HeaderMap) -> bool {
|
||||
let x_app = header_value_str(headers, "x-app")
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase();
|
||||
if x_app.contains("cli") {
|
||||
return true;
|
||||
}
|
||||
|
||||
let user_agent = header_value_str(headers, http::header::USER_AGENT.as_str())
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase();
|
||||
user_agent.contains("geminicli") || user_agent.contains("gemini-cli")
|
||||
}
|
||||
|
||||
pub(super) fn is_gemini_models_route(path: &str) -> bool {
|
||||
(path.starts_with("/v1/models/") || path.starts_with("/v1beta/models/"))
|
||||
&& (path.contains(":generateContent")
|
||||
|| path.contains(":streamGenerateContent")
|
||||
|| path.contains(":predictLongRunning"))
|
||||
}
|
||||
|
||||
pub(super) fn is_gemini_operation_route(path: &str) -> bool {
|
||||
(path.starts_with("/v1beta/models/") && path.contains("/operations/"))
|
||||
|| path == "/v1beta/operations"
|
||||
|| path.starts_with("/v1beta/operations/")
|
||||
}
|
||||
78
apps/aether-gateway/src/control/route/admin.rs
Normal file
78
apps/aether-gateway/src/control/route/admin.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
use super::*;
|
||||
|
||||
#[path = "admin/basic_families.rs"]
|
||||
mod basic_families;
|
||||
#[path = "admin/endpoints_families.rs"]
|
||||
mod endpoints_families;
|
||||
#[path = "admin/model_provider_families.rs"]
|
||||
mod model_provider_families;
|
||||
#[path = "admin/observability_families.rs"]
|
||||
mod observability_families;
|
||||
#[path = "admin/operations_families.rs"]
|
||||
mod operations_families;
|
||||
#[path = "admin/provider_ops_routes.rs"]
|
||||
mod provider_ops_routes;
|
||||
#[path = "admin/system_families.rs"]
|
||||
mod system_families;
|
||||
|
||||
use basic_families::classify_admin_basic_family_route;
|
||||
use endpoints_families::classify_admin_endpoints_family_route;
|
||||
use model_provider_families::classify_admin_model_provider_family_route;
|
||||
use observability_families::classify_admin_observability_family_route;
|
||||
use operations_families::classify_admin_operations_family_route;
|
||||
use provider_ops_routes::classify_admin_provider_ops_routes;
|
||||
use system_families::classify_admin_system_family_route;
|
||||
|
||||
pub(super) fn classify_admin_route(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
let normalized_path_no_trailing = normalized_path.trim_end_matches('/');
|
||||
let normalized_path_no_trailing = if normalized_path_no_trailing.is_empty() {
|
||||
"/"
|
||||
} else {
|
||||
normalized_path_no_trailing
|
||||
};
|
||||
|
||||
if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/providers" | "/api/admin/providers/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"providers_manage",
|
||||
"list_providers",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if let Some(route) =
|
||||
classify_admin_basic_family_route(method, normalized_path, normalized_path_no_trailing)
|
||||
{
|
||||
Some(route)
|
||||
} else if let Some(route) = classify_admin_observability_family_route(
|
||||
method,
|
||||
normalized_path,
|
||||
normalized_path_no_trailing,
|
||||
) {
|
||||
Some(route)
|
||||
} else if let Some(route) =
|
||||
classify_admin_operations_family_route(method, normalized_path, normalized_path_no_trailing)
|
||||
{
|
||||
Some(route)
|
||||
} else if let Some(route) =
|
||||
classify_admin_system_family_route(method, normalized_path, normalized_path_no_trailing)
|
||||
{
|
||||
Some(route)
|
||||
} else if let Some(route) = classify_admin_provider_ops_routes(method, normalized_path) {
|
||||
Some(route)
|
||||
} else if let Some(route) = classify_admin_model_provider_family_route(method, normalized_path)
|
||||
{
|
||||
Some(route)
|
||||
} else if let Some(route) = classify_admin_endpoints_family_route(method, normalized_path) {
|
||||
Some(route)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
514
apps/aether-gateway/src/control/route/admin/basic_families.rs
Normal file
514
apps/aether-gateway/src/control/route/admin/basic_families.rs
Normal file
@@ -0,0 +1,514 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn classify_admin_basic_family_route(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
normalized_path_no_trailing: &str,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/management-tokens" | "/api/admin/management-tokens/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"management_tokens_manage",
|
||||
"list_tokens",
|
||||
"admin:management_tokens",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/management-tokens/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"management_tokens_manage",
|
||||
"get_token",
|
||||
"admin:management_tokens",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/management-tokens/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"management_tokens_manage",
|
||||
"delete_token",
|
||||
"admin:management_tokens",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH
|
||||
&& normalized_path.starts_with("/api/admin/management-tokens/")
|
||||
&& normalized_path.ends_with("/status")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"management_tokens_manage",
|
||||
"toggle_status",
|
||||
"admin:management_tokens",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/ldap/config" | "/api/admin/ldap/config/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"ldap_manage",
|
||||
"get_config",
|
||||
"admin:ldap",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/ldap/config" | "/api/admin/ldap/config/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"ldap_manage",
|
||||
"set_config",
|
||||
"admin:ldap",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/ldap/test" | "/api/admin/ldap/test/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"ldap_manage",
|
||||
"test_connection",
|
||||
"admin:ldap",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/gemini-files/mappings" | "/api/admin/gemini-files/mappings/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"gemini_files_manage",
|
||||
"list_mappings",
|
||||
"admin:gemini_files",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/gemini-files/stats" | "/api/admin/gemini-files/stats/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"gemini_files_manage",
|
||||
"stats",
|
||||
"admin:gemini_files",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/gemini-files/mappings" | "/api/admin/gemini-files/mappings/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"gemini_files_manage",
|
||||
"cleanup_mappings",
|
||||
"admin:gemini_files",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/gemini-files/mappings/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"gemini_files_manage",
|
||||
"delete_mapping",
|
||||
"admin:gemini_files",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/gemini-files/capable-keys" | "/api/admin/gemini-files/capable-keys/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"gemini_files_manage",
|
||||
"capable_keys",
|
||||
"admin:gemini_files",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/gemini-files/upload" | "/api/admin/gemini-files/upload/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"gemini_files_manage",
|
||||
"upload",
|
||||
"admin:gemini_files",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/modules/status" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"modules_manage",
|
||||
"status_list",
|
||||
"admin:modules",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/modules/status/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"modules_manage",
|
||||
"status_detail",
|
||||
"admin:modules",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/modules/status/")
|
||||
&& normalized_path.ends_with("/enabled")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"modules_manage",
|
||||
"set_enabled",
|
||||
"admin:modules",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/adaptive/keys" | "/api/admin/adaptive/keys/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"adaptive_manage",
|
||||
"list_keys",
|
||||
"admin:adaptive",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/adaptive/summary" | "/api/admin/adaptive/summary/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"adaptive_manage",
|
||||
"summary",
|
||||
"admin:adaptive",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/adaptive/keys/")
|
||||
&& normalized_path.ends_with("/stats")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"adaptive_manage",
|
||||
"get_stats",
|
||||
"admin:adaptive",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH
|
||||
&& normalized_path.starts_with("/api/admin/adaptive/keys/")
|
||||
&& normalized_path.ends_with("/mode")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"adaptive_manage",
|
||||
"toggle_mode",
|
||||
"admin:adaptive",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH
|
||||
&& normalized_path.starts_with("/api/admin/adaptive/keys/")
|
||||
&& normalized_path.ends_with("/limit")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"adaptive_manage",
|
||||
"set_limit",
|
||||
"admin:adaptive",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/adaptive/keys/")
|
||||
&& normalized_path.ends_with("/learning")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"adaptive_manage",
|
||||
"reset_learning",
|
||||
"admin:adaptive",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/provider-strategy/strategies" | "/api/admin/provider-strategy/strategies/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_strategy_manage",
|
||||
"list_strategies",
|
||||
"admin:provider_strategy",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/provider-strategy/providers/")
|
||||
&& normalized_path.ends_with("/billing")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_strategy_manage",
|
||||
"update_provider_billing",
|
||||
"admin:provider_strategy",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/provider-strategy/providers/")
|
||||
&& normalized_path.ends_with("/stats")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_strategy_manage",
|
||||
"get_provider_stats",
|
||||
"admin:provider_strategy",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/provider-strategy/providers/")
|
||||
&& normalized_path.ends_with("/quota")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_strategy_manage",
|
||||
"reset_provider_quota",
|
||||
"admin:provider_strategy",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/billing/presets" | "/api/admin/billing/presets/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"billing_manage",
|
||||
"list_presets",
|
||||
"admin:billing",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/billing/presets/apply" | "/api/admin/billing/presets/apply/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"billing_manage",
|
||||
"apply_preset",
|
||||
"admin:billing",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/billing/rules" | "/api/admin/billing/rules/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"billing_manage",
|
||||
"list_rules",
|
||||
"admin:billing",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/billing/rules/")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"billing_manage",
|
||||
"get_rule",
|
||||
"admin:billing",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/billing/rules" | "/api/admin/billing/rules/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"billing_manage",
|
||||
"create_rule",
|
||||
"admin:billing",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/billing/rules/")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"billing_manage",
|
||||
"update_rule",
|
||||
"admin:billing",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/billing/collectors" | "/api/admin/billing/collectors/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"billing_manage",
|
||||
"list_collectors",
|
||||
"admin:billing",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/billing/collectors/")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"billing_manage",
|
||||
"get_collector",
|
||||
"admin:billing",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/billing/collectors" | "/api/admin/billing/collectors/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"billing_manage",
|
||||
"create_collector",
|
||||
"admin:billing",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/billing/collectors/")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"billing_manage",
|
||||
"update_collector",
|
||||
"admin:billing",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/payments/orders" | "/api/admin/payments/orders/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"payments_manage",
|
||||
"list_orders",
|
||||
"admin:payments",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path_no_trailing.starts_with("/api/admin/payments/orders/")
|
||||
&& normalized_path_no_trailing.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"payments_manage",
|
||||
"get_order",
|
||||
"admin:payments",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path_no_trailing.starts_with("/api/admin/payments/orders/")
|
||||
&& normalized_path_no_trailing.ends_with("/expire")
|
||||
&& normalized_path_no_trailing.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"payments_manage",
|
||||
"expire_order",
|
||||
"admin:payments",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path_no_trailing.starts_with("/api/admin/payments/orders/")
|
||||
&& normalized_path_no_trailing.ends_with("/credit")
|
||||
&& normalized_path_no_trailing.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"payments_manage",
|
||||
"credit_order",
|
||||
"admin:payments",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path_no_trailing.starts_with("/api/admin/payments/orders/")
|
||||
&& normalized_path_no_trailing.ends_with("/fail")
|
||||
&& normalized_path_no_trailing.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"payments_manage",
|
||||
"fail_order",
|
||||
"admin:payments",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/payments/callbacks" | "/api/admin/payments/callbacks/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"payments_manage",
|
||||
"list_callbacks",
|
||||
"admin:payments",
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn classify_admin_endpoints_family_route(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
if method == http::Method::GET && normalized_path == "/api/admin/endpoints/health/summary" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_health",
|
||||
"health_summary",
|
||||
"admin:endpoints_health",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/health/key/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_health",
|
||||
"key_health",
|
||||
"admin:endpoints_health",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/health/keys/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_health",
|
||||
"recover_key_health",
|
||||
"admin:endpoints_health",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH && normalized_path == "/api/admin/endpoints/health/keys"
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_health",
|
||||
"recover_all_keys_health",
|
||||
"admin:endpoints_health",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/endpoints/health/status"
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_health",
|
||||
"health_status",
|
||||
"admin:endpoints_health",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path == "/api/admin/endpoints/health/api-formats"
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_health",
|
||||
"health_api_formats",
|
||||
"admin:endpoints_health",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/rpm/key/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_rpm",
|
||||
"key_rpm",
|
||||
"admin:endpoints_rpm",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/rpm/key/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_rpm",
|
||||
"reset_key_rpm",
|
||||
"admin:endpoints_rpm",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path == "/api/admin/endpoints/keys/grouped-by-format"
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"keys_grouped_by_format",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/keys/")
|
||||
&& normalized_path.ends_with("/reveal")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"reveal_key",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/keys/")
|
||||
&& normalized_path.ends_with("/export")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"export_key",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/keys/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"update_key",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/keys/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"delete_key",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path == "/api/admin/endpoints/keys/batch-delete"
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"batch_delete_keys",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/keys/")
|
||||
&& normalized_path.ends_with("/clear-oauth-invalid")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"clear_oauth_invalid",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/providers/")
|
||||
&& normalized_path.ends_with("/refresh-quota")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"refresh_quota",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/providers/")
|
||||
&& normalized_path.ends_with("/keys")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"create_provider_key",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/providers/")
|
||||
&& normalized_path.ends_with("/keys")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"list_provider_keys",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/providers/")
|
||||
&& normalized_path.ends_with("/endpoints")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"list_provider_endpoints",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/providers/")
|
||||
&& normalized_path.ends_with("/endpoints")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"create_endpoint",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/health/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/rpm/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/providers/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/defaults/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/keys/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"update_endpoint",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/health/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/rpm/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/providers/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/defaults/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/keys/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"delete_endpoint",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/defaults/")
|
||||
&& normalized_path.ends_with("/body-rules")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"default_body_rules",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/endpoints/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/health/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/rpm/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/providers/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/defaults/")
|
||||
&& !normalized_path.starts_with("/api/admin/endpoints/keys/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"endpoints_manage",
|
||||
"get_endpoint",
|
||||
"admin:endpoints_manage",
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,352 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn classify_admin_model_provider_family_route(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
if method == http::Method::GET && normalized_path == "/api/admin/models/catalog" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"model_catalog_manage",
|
||||
"catalog",
|
||||
"admin:models",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/models/external" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"model_external_manage",
|
||||
"external",
|
||||
"admin:models",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path == "/api/admin/models/external/cache"
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"model_external_manage",
|
||||
"clear_external_cache",
|
||||
"admin:models",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/providers" | "/api/admin/providers/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"providers_manage",
|
||||
"create_provider",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.matches('/').count() == 4
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"providers_manage",
|
||||
"update_provider",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.matches('/').count() == 4
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"providers_manage",
|
||||
"delete_provider",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/providers/summary" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"providers_manage",
|
||||
"summary_list",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.ends_with("/summary")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"providers_manage",
|
||||
"provider_summary",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.ends_with("/health-monitor")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"providers_manage",
|
||||
"health_monitor",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.ends_with("/mapping-preview")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"providers_manage",
|
||||
"mapping_preview",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.contains("/delete-task/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"providers_manage",
|
||||
"delete_provider_task",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.ends_with("/pool-status")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"providers_manage",
|
||||
"pool_status",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.contains("/pool/clear-cooldown/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"providers_manage",
|
||||
"clear_pool_cooldown",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.contains("/pool/reset-cost/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"providers_manage",
|
||||
"reset_pool_cost",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.ends_with("/models")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_models_manage",
|
||||
"list_provider_models",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.ends_with("/models")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_models_manage",
|
||||
"create_provider_model",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.contains("/models/")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_models_manage",
|
||||
"get_provider_model",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.contains("/models/")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_models_manage",
|
||||
"update_provider_model",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.contains("/models/")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_models_manage",
|
||||
"delete_provider_model",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.ends_with("/models/batch")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_models_manage",
|
||||
"batch_create_provider_models",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.ends_with("/available-source-models")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_models_manage",
|
||||
"available_source_models",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.ends_with("/assign-global-models")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_models_manage",
|
||||
"assign_global_models",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/providers/")
|
||||
&& normalized_path.ends_with("/import-from-upstream")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_models_manage",
|
||||
"import_from_upstream",
|
||||
"admin:providers",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path == "/api/admin/models/global/batch-delete"
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"global_models_manage",
|
||||
"batch_delete_global_models",
|
||||
"admin:models",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/models/global" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"global_models_manage",
|
||||
"list_global_models",
|
||||
"admin:models",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && normalized_path == "/api/admin/models/global" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"global_models_manage",
|
||||
"create_global_model",
|
||||
"admin:models",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/models/global/")
|
||||
&& normalized_path.ends_with("/assign-to-providers")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"global_models_manage",
|
||||
"assign_to_providers",
|
||||
"admin:models",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/models/global/")
|
||||
&& normalized_path.ends_with("/providers")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"global_models_manage",
|
||||
"global_model_providers",
|
||||
"admin:models",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/models/global/")
|
||||
&& normalized_path.ends_with("/routing")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"global_models_manage",
|
||||
"routing_preview",
|
||||
"admin:models",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/models/global/")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"global_models_manage",
|
||||
"get_global_model",
|
||||
"admin:models",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH
|
||||
&& normalized_path.starts_with("/api/admin/models/global/")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"global_models_manage",
|
||||
"update_global_model",
|
||||
"admin:models",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/models/global/")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"global_models_manage",
|
||||
"delete_global_model",
|
||||
"admin:models",
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,664 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn classify_admin_observability_family_route(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
normalized_path_no_trailing: &str,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/provider-query/models" | "/api/admin/provider-query/models/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_query_manage",
|
||||
"query_models",
|
||||
"admin:provider_query",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/provider-query/test-model" | "/api/admin/provider-query/test-model/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_query_manage",
|
||||
"test_model",
|
||||
"admin:provider_query",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/provider-query/test-model-failover"
|
||||
| "/api/admin/provider-query/test-model-failover/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_query_manage",
|
||||
"test_model_failover",
|
||||
"admin:provider_query",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/security/ip/blacklist" | "/api/admin/security/ip/blacklist/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"security_manage",
|
||||
"blacklist_add",
|
||||
"admin:security",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/security/ip/blacklist/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"security_manage",
|
||||
"blacklist_remove",
|
||||
"admin:security",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/security/ip/blacklist/stats" | "/api/admin/security/ip/blacklist/stats/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"security_manage",
|
||||
"blacklist_stats",
|
||||
"admin:security",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/security/ip/blacklist" | "/api/admin/security/ip/blacklist/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"security_manage",
|
||||
"blacklist_list",
|
||||
"admin:security",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/security/ip/whitelist" | "/api/admin/security/ip/whitelist/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"security_manage",
|
||||
"whitelist_add",
|
||||
"admin:security",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/security/ip/whitelist/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"security_manage",
|
||||
"whitelist_remove",
|
||||
"admin:security",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/security/ip/whitelist" | "/api/admin/security/ip/whitelist/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"security_manage",
|
||||
"whitelist_list",
|
||||
"admin:security",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/api-keys" | "/api/admin/api-keys/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"api_keys_manage",
|
||||
"list_api_keys",
|
||||
"admin:api_keys",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/api-keys" | "/api/admin/api-keys/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"api_keys_manage",
|
||||
"create_api_key",
|
||||
"admin:api_keys",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/api-keys/")
|
||||
&& normalized_path.matches('/').count() == 4
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"api_keys_manage",
|
||||
"api_key_detail",
|
||||
"admin:api_keys",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/api-keys/")
|
||||
&& normalized_path.matches('/').count() == 4
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"api_keys_manage",
|
||||
"update_api_key",
|
||||
"admin:api_keys",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH
|
||||
&& normalized_path.starts_with("/api/admin/api-keys/")
|
||||
&& normalized_path.matches('/').count() == 4
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"api_keys_manage",
|
||||
"toggle_api_key",
|
||||
"admin:api_keys",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/api-keys/")
|
||||
&& normalized_path.matches('/').count() == 4
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"api_keys_manage",
|
||||
"delete_api_key",
|
||||
"admin:api_keys",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/pool/overview" | "/api/admin/pool/overview/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"pool_manage",
|
||||
"overview",
|
||||
"admin:pool",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/pool/scheduling-presets" | "/api/admin/pool/scheduling-presets/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"pool_manage",
|
||||
"scheduling_presets",
|
||||
"admin:pool",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path_no_trailing.starts_with("/api/admin/pool/")
|
||||
&& normalized_path_no_trailing.ends_with("/keys")
|
||||
&& normalized_path_no_trailing.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"pool_manage",
|
||||
"list_keys",
|
||||
"admin:pool",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path_no_trailing.starts_with("/api/admin/pool/")
|
||||
&& normalized_path_no_trailing.ends_with("/keys/batch-import")
|
||||
&& normalized_path_no_trailing.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"pool_manage",
|
||||
"batch_import_keys",
|
||||
"admin:pool",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path_no_trailing.starts_with("/api/admin/pool/")
|
||||
&& normalized_path_no_trailing.ends_with("/keys/batch-action")
|
||||
&& normalized_path_no_trailing.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"pool_manage",
|
||||
"batch_action_keys",
|
||||
"admin:pool",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path_no_trailing.starts_with("/api/admin/pool/")
|
||||
&& normalized_path_no_trailing.ends_with("/keys/resolve-selection")
|
||||
&& normalized_path_no_trailing.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"pool_manage",
|
||||
"resolve_selection",
|
||||
"admin:pool",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path_no_trailing.starts_with("/api/admin/pool/")
|
||||
&& normalized_path_no_trailing.contains("/keys/batch-delete-task/")
|
||||
&& normalized_path_no_trailing.matches('/').count() == 7
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"pool_manage",
|
||||
"batch_delete_task_status",
|
||||
"admin:pool",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path_no_trailing.starts_with("/api/admin/pool/")
|
||||
&& normalized_path_no_trailing.ends_with("/keys/cleanup-banned")
|
||||
&& normalized_path_no_trailing.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"pool_manage",
|
||||
"cleanup_banned_keys",
|
||||
"admin:pool",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/usage/aggregation/stats" | "/api/admin/usage/aggregation/stats/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"usage_manage",
|
||||
"aggregation_stats",
|
||||
"admin:usage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/usage/stats" | "/api/admin/usage/stats/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"usage_manage",
|
||||
"stats",
|
||||
"admin:usage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/usage/heatmap" | "/api/admin/usage/heatmap/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"usage_manage",
|
||||
"heatmap",
|
||||
"admin:usage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/usage/records" | "/api/admin/usage/records/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"usage_manage",
|
||||
"records",
|
||||
"admin:usage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/usage/active" | "/api/admin/usage/active/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"usage_manage",
|
||||
"active",
|
||||
"admin:usage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/usage/cache-affinity/hit-analysis"
|
||||
| "/api/admin/usage/cache-affinity/hit-analysis/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"usage_manage",
|
||||
"cache_affinity_hit_analysis",
|
||||
"admin:usage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/usage/cache-affinity/interval-timeline"
|
||||
| "/api/admin/usage/cache-affinity/interval-timeline/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"usage_manage",
|
||||
"cache_affinity_interval_timeline",
|
||||
"admin:usage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/usage/cache-affinity/ttl-analysis"
|
||||
| "/api/admin/usage/cache-affinity/ttl-analysis/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"usage_manage",
|
||||
"cache_affinity_ttl_analysis",
|
||||
"admin:usage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/usage/")
|
||||
&& normalized_path.ends_with("/curl")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"usage_manage",
|
||||
"curl",
|
||||
"admin:usage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/usage/")
|
||||
&& normalized_path.ends_with("/replay")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"usage_manage",
|
||||
"replay",
|
||||
"admin:usage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/usage/")
|
||||
&& normalized_path.matches('/').count() == 4
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"usage_manage",
|
||||
"detail",
|
||||
"admin:usage",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/stats/providers/quota-usage" | "/api/admin/stats/providers/quota-usage/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"stats_manage",
|
||||
"provider_quota_usage",
|
||||
"admin:stats",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/stats/comparison" | "/api/admin/stats/comparison/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"stats_manage",
|
||||
"comparison",
|
||||
"admin:stats",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/stats/errors/distribution" | "/api/admin/stats/errors/distribution/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"stats_manage",
|
||||
"error_distribution",
|
||||
"admin:stats",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/stats/performance/percentiles"
|
||||
| "/api/admin/stats/performance/percentiles/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"stats_manage",
|
||||
"performance_percentiles",
|
||||
"admin:stats",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/stats/cost/forecast" | "/api/admin/stats/cost/forecast/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"stats_manage",
|
||||
"cost_forecast",
|
||||
"admin:stats",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/stats/cost/savings" | "/api/admin/stats/cost/savings/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"stats_manage",
|
||||
"cost_savings",
|
||||
"admin:stats",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/stats/leaderboard/api-keys" | "/api/admin/stats/leaderboard/api-keys/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"stats_manage",
|
||||
"leaderboard_api_keys",
|
||||
"admin:stats",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/stats/leaderboard/models" | "/api/admin/stats/leaderboard/models/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"stats_manage",
|
||||
"leaderboard_models",
|
||||
"admin:stats",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/stats/leaderboard/users" | "/api/admin/stats/leaderboard/users/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"stats_manage",
|
||||
"leaderboard_users",
|
||||
"admin:stats",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/stats/time-series" | "/api/admin/stats/time-series/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"stats_manage",
|
||||
"time_series",
|
||||
"admin:stats",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/monitoring/audit-logs" | "/api/admin/monitoring/audit-logs/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"monitoring",
|
||||
"audit_logs",
|
||||
"admin:monitoring",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& (matches!(
|
||||
normalized_path,
|
||||
"/api/admin/monitoring/system-status"
|
||||
| "/api/admin/monitoring/system-status/"
|
||||
| "/api/admin/monitoring/suspicious-activities"
|
||||
| "/api/admin/monitoring/suspicious-activities/"
|
||||
| "/api/admin/monitoring/user-behavior"
|
||||
) || normalized_path.starts_with("/api/admin/monitoring/user-behavior/"))
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"monitoring",
|
||||
"user_behavior",
|
||||
"admin:monitoring",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& (matches!(
|
||||
normalized_path,
|
||||
"/api/admin/monitoring/resilience-status"
|
||||
| "/api/admin/monitoring/resilience-status/"
|
||||
| "/api/admin/monitoring/resilience/circuit-history"
|
||||
| "/api/admin/monitoring/resilience/circuit-history/"
|
||||
) || (normalized_path == "/api/admin/monitoring/resilience/error-stats"))
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"monitoring",
|
||||
"monitoring_resilience",
|
||||
"admin:monitoring",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path == "/api/admin/monitoring/resilience/error-stats"
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"monitoring",
|
||||
"monitoring_resilience",
|
||||
"admin:monitoring",
|
||||
false,
|
||||
))
|
||||
} else if (method == http::Method::GET || method == http::Method::DELETE)
|
||||
&& normalized_path.starts_with("/api/admin/monitoring/cache")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"monitoring",
|
||||
"monitoring_cache",
|
||||
"admin:monitoring",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/monitoring/trace/stats/provider/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"monitoring",
|
||||
"trace_provider_stats",
|
||||
"admin:monitoring",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/monitoring/trace/")
|
||||
&& !normalized_path.starts_with("/api/admin/monitoring/trace/stats/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"monitoring",
|
||||
"trace_request",
|
||||
"admin:monitoring",
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn classify_admin_operations_family_route(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
_normalized_path_no_trailing: &str,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/provider-ops/architectures" | "/api/admin/provider-ops/architectures/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"list_architectures",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/video-tasks" | "/api/admin/video-tasks/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"video_tasks_manage",
|
||||
"list_tasks",
|
||||
"admin:video_tasks",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/video-tasks/stats" | "/api/admin/video-tasks/stats/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"video_tasks_manage",
|
||||
"stats",
|
||||
"admin:video_tasks",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/video-tasks/")
|
||||
&& normalized_path.ends_with("/video")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"video_tasks_manage",
|
||||
"video",
|
||||
"admin:video_tasks",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/video-tasks/")
|
||||
&& normalized_path.ends_with("/cancel")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"video_tasks_manage",
|
||||
"cancel",
|
||||
"admin:video_tasks",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/video-tasks/")
|
||||
&& normalized_path["/api/admin/video-tasks/".len()..]
|
||||
.split('/')
|
||||
.count()
|
||||
== 1
|
||||
&& !matches!(
|
||||
normalized_path,
|
||||
"/api/admin/video-tasks/stats" | "/api/admin/video-tasks/stats/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"video_tasks_manage",
|
||||
"detail",
|
||||
"admin:video_tasks",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/provider-ops/architectures/")
|
||||
&& !normalized_path.ends_with('/')
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"get_architecture",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/proxy-nodes" | "/api/admin/proxy-nodes/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"proxy_nodes_manage",
|
||||
"list_nodes",
|
||||
"admin:proxy_nodes",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/proxy-nodes/register" | "/api/admin/proxy-nodes/register/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"proxy_nodes_manage",
|
||||
"register_node",
|
||||
"admin:proxy_nodes",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/proxy-nodes/heartbeat" | "/api/admin/proxy-nodes/heartbeat/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"proxy_nodes_manage",
|
||||
"heartbeat_node",
|
||||
"admin:proxy_nodes",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/proxy-nodes/unregister" | "/api/admin/proxy-nodes/unregister/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"proxy_nodes_manage",
|
||||
"unregister_node",
|
||||
"admin:proxy_nodes",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/proxy-nodes/manual" | "/api/admin/proxy-nodes/manual/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"proxy_nodes_manage",
|
||||
"create_manual_node",
|
||||
"admin:proxy_nodes",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/proxy-nodes/upgrade" | "/api/admin/proxy-nodes/upgrade/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"proxy_nodes_manage",
|
||||
"batch_upgrade_nodes",
|
||||
"admin:proxy_nodes",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/proxy-nodes/test-url" | "/api/admin/proxy-nodes/test-url/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"proxy_nodes_manage",
|
||||
"test_proxy_url",
|
||||
"admin:proxy_nodes",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH
|
||||
&& normalized_path.starts_with("/api/admin/proxy-nodes/")
|
||||
&& !normalized_path.ends_with("/test")
|
||||
&& !normalized_path.ends_with("/config")
|
||||
&& !normalized_path.ends_with("/events")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"proxy_nodes_manage",
|
||||
"update_manual_node",
|
||||
"admin:proxy_nodes",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/proxy-nodes/")
|
||||
&& !normalized_path.ends_with("/test")
|
||||
&& !normalized_path.ends_with("/config")
|
||||
&& !normalized_path.ends_with("/events")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"proxy_nodes_manage",
|
||||
"delete_node",
|
||||
"admin:proxy_nodes",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/proxy-nodes/")
|
||||
&& normalized_path.ends_with("/test")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"proxy_nodes_manage",
|
||||
"test_node",
|
||||
"admin:proxy_nodes",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/proxy-nodes/")
|
||||
&& normalized_path.ends_with("/config")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"proxy_nodes_manage",
|
||||
"update_node_config",
|
||||
"admin:proxy_nodes",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/proxy-nodes/")
|
||||
&& normalized_path.ends_with("/events")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"proxy_nodes_manage",
|
||||
"list_node_events",
|
||||
"admin:proxy_nodes",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/wallets" | "/api/admin/wallets/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"wallets_manage",
|
||||
"list_wallets",
|
||||
"admin:wallets",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/wallets/ledger" | "/api/admin/wallets/ledger/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"wallets_manage",
|
||||
"ledger",
|
||||
"admin:wallets",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/wallets/refund-requests" | "/api/admin/wallets/refund-requests/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"wallets_manage",
|
||||
"list_refund_requests",
|
||||
"admin:wallets",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/wallets/")
|
||||
&& normalized_path.ends_with("/transactions")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"wallets_manage",
|
||||
"list_wallet_transactions",
|
||||
"admin:wallets",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/wallets/")
|
||||
&& normalized_path.ends_with("/refunds")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"wallets_manage",
|
||||
"list_wallet_refunds",
|
||||
"admin:wallets",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/wallets/")
|
||||
&& !normalized_path.ends_with("/transactions")
|
||||
&& !normalized_path.ends_with("/refunds")
|
||||
&& normalized_path.matches('/').count() == 4
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"wallets_manage",
|
||||
"wallet_detail",
|
||||
"admin:wallets",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/wallets/")
|
||||
&& normalized_path.ends_with("/adjust")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"wallets_manage",
|
||||
"adjust_balance",
|
||||
"admin:wallets",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/wallets/")
|
||||
&& normalized_path.ends_with("/recharge")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"wallets_manage",
|
||||
"recharge_balance",
|
||||
"admin:wallets",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/wallets/")
|
||||
&& normalized_path.contains("/refunds/")
|
||||
&& normalized_path.ends_with("/process")
|
||||
&& normalized_path.matches('/').count() == 7
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"wallets_manage",
|
||||
"process_refund",
|
||||
"admin:wallets",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/wallets/")
|
||||
&& normalized_path.contains("/refunds/")
|
||||
&& normalized_path.ends_with("/complete")
|
||||
&& normalized_path.matches('/').count() == 7
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"wallets_manage",
|
||||
"complete_refund",
|
||||
"admin:wallets",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/wallets/")
|
||||
&& normalized_path.contains("/refunds/")
|
||||
&& normalized_path.ends_with("/fail")
|
||||
&& normalized_path.matches('/').count() == 7
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"wallets_manage",
|
||||
"fail_refund",
|
||||
"admin:wallets",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(normalized_path, "/api/admin/users" | "/api/admin/users/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"list_users",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(normalized_path, "/api/admin/users" | "/api/admin/users/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"create_user",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/users/")
|
||||
&& normalized_path.ends_with("/sessions")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"list_user_sessions",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/users/")
|
||||
&& normalized_path.ends_with("/sessions")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"delete_user_sessions",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/users/")
|
||||
&& normalized_path.contains("/sessions/")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"delete_user_session",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/users/")
|
||||
&& normalized_path.ends_with("/api-keys")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"list_user_api_keys",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/users/")
|
||||
&& normalized_path.ends_with("/api-keys")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"create_user_api_key",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/users/")
|
||||
&& normalized_path.contains("/api-keys/")
|
||||
&& !normalized_path.ends_with("/lock")
|
||||
&& !normalized_path.ends_with("/full-key")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"delete_user_api_key",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/users/")
|
||||
&& normalized_path.contains("/api-keys/")
|
||||
&& !normalized_path.ends_with("/lock")
|
||||
&& !normalized_path.ends_with("/full-key")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"update_user_api_key",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH
|
||||
&& normalized_path.starts_with("/api/admin/users/")
|
||||
&& normalized_path.ends_with("/lock")
|
||||
&& normalized_path.matches('/').count() == 7
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"lock_user_api_key",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/users/")
|
||||
&& normalized_path.ends_with("/full-key")
|
||||
&& normalized_path.matches('/').count() == 7
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"reveal_user_api_key",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/users/")
|
||||
&& !normalized_path.ends_with("/sessions")
|
||||
&& !normalized_path.contains("/sessions/")
|
||||
&& !normalized_path.ends_with("/api-keys")
|
||||
&& !normalized_path.contains("/api-keys/")
|
||||
&& normalized_path.matches('/').count() == 4
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"get_user",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/users/")
|
||||
&& !normalized_path.ends_with("/sessions")
|
||||
&& !normalized_path.contains("/sessions/")
|
||||
&& !normalized_path.ends_with("/api-keys")
|
||||
&& !normalized_path.contains("/api-keys/")
|
||||
&& normalized_path.matches('/').count() == 4
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"update_user",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/users/")
|
||||
&& !normalized_path.ends_with("/sessions")
|
||||
&& !normalized_path.contains("/sessions/")
|
||||
&& !normalized_path.ends_with("/api-keys")
|
||||
&& !normalized_path.contains("/api-keys/")
|
||||
&& normalized_path.matches('/').count() == 4
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"users_manage",
|
||||
"delete_user",
|
||||
"admin:users",
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn classify_admin_provider_ops_routes(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/provider-ops/providers/")
|
||||
&& normalized_path.ends_with("/status")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"get_provider_status",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/provider-ops/providers/")
|
||||
&& normalized_path.ends_with("/config")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"get_provider_config",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/provider-ops/providers/")
|
||||
&& normalized_path.ends_with("/config")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"save_provider_config",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/provider-ops/providers/")
|
||||
&& normalized_path.ends_with("/config")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"delete_provider_config",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-ops/providers/")
|
||||
&& normalized_path.ends_with("/connect")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"connect_provider",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-ops/providers/")
|
||||
&& normalized_path.ends_with("/verify")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"verify_provider",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-ops/providers/")
|
||||
&& normalized_path.ends_with("/disconnect")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"disconnect_provider",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/provider-ops/providers/")
|
||||
&& normalized_path.ends_with("/balance")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"get_provider_balance",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-ops/providers/")
|
||||
&& normalized_path.ends_with("/balance")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"refresh_provider_balance",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-ops/providers/")
|
||||
&& normalized_path.ends_with("/checkin")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"provider_checkin",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-ops/providers/")
|
||||
&& normalized_path.contains("/actions/")
|
||||
&& normalized_path.matches('/').count() == 7
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"execute_provider_action",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/provider-ops/batch/balance" | "/api/admin/provider-ops/batch/balance/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_ops_manage",
|
||||
"batch_balance",
|
||||
"admin:provider_ops",
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
270
apps/aether-gateway/src/control/route/admin/system_families.rs
Normal file
270
apps/aether-gateway/src/control/route/admin/system_families.rs
Normal file
@@ -0,0 +1,270 @@
|
||||
use super::*;
|
||||
|
||||
pub(super) fn classify_admin_system_family_route(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
_normalized_path_no_trailing: &str,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
if method == http::Method::GET && normalized_path == "/api/admin/system/version" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"version",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/system/check-update" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"check_update",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/system/aws-regions" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"aws_regions",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/system/stats" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"stats",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/system/settings" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"settings_get",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/system/config/export" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"config_export",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/system/users/export" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"users_export",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && normalized_path == "/api/admin/system/config/import" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"config_import",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && normalized_path == "/api/admin/system/users/import" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"users_import",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && normalized_path == "/api/admin/system/smtp/test" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"smtp_test",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && normalized_path == "/api/admin/system/cleanup" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"cleanup",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && normalized_path == "/api/admin/system/purge/config" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"purge_config",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && normalized_path == "/api/admin/system/purge/users" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"purge_users",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && normalized_path == "/api/admin/system/purge/usage" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"purge_usage",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path == "/api/admin/system/purge/audit-logs"
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"purge_audit_logs",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path == "/api/admin/system/purge/request-bodies"
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"purge_request_bodies",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && normalized_path == "/api/admin/system/purge/stats" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"purge_stats",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT && normalized_path == "/api/admin/system/settings" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"settings_set",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/system/configs" | "/api/admin/system/configs/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"configs_list",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/system/configs/")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"config_get",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/system/configs/")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"config_set",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/system/configs/")
|
||||
&& normalized_path.matches('/').count() == 5
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"config_delete",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/system/api-formats" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"api_formats",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/system/email/templates" | "/api/admin/system/email/templates/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"email_templates_list",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/system/email/templates/")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"email_template_get",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/system/email/templates/")
|
||||
&& normalized_path.matches('/').count() == 6
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"email_template_set",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/system/email/templates/")
|
||||
&& normalized_path.ends_with("/preview")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"email_template_preview",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/system/email/templates/")
|
||||
&& normalized_path.ends_with("/reset")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"system_manage",
|
||||
"email_template_reset",
|
||||
"admin:system",
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
102
apps/aether-gateway/src/control/route/ai.rs
Normal file
102
apps/aether-gateway/src/control/route/ai.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use super::{
|
||||
classified, is_claude_cli_request, is_gemini_cli_request, is_gemini_models_route,
|
||||
is_gemini_operation_route, ClassifiedRoute,
|
||||
};
|
||||
|
||||
pub(super) fn classify_ai_public_route(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
headers: &http::HeaderMap,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
if method == http::Method::POST && normalized_path == "/v1/chat/completions" {
|
||||
Some(classified(
|
||||
"ai_public",
|
||||
"openai",
|
||||
"chat",
|
||||
"openai:chat",
|
||||
true,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(normalized_path, "/v1/responses" | "/v1/responses/compact")
|
||||
{
|
||||
if normalized_path.ends_with("/compact") {
|
||||
Some(classified(
|
||||
"ai_public",
|
||||
"openai",
|
||||
"compact",
|
||||
"openai:compact",
|
||||
true,
|
||||
))
|
||||
} else {
|
||||
Some(classified("ai_public", "openai", "cli", "openai:cli", true))
|
||||
}
|
||||
} else if method == http::Method::POST && normalized_path == "/v1/messages/count_tokens" {
|
||||
Some(classified(
|
||||
"ai_public",
|
||||
"claude",
|
||||
"count_tokens",
|
||||
"claude:chat",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && normalized_path == "/v1/messages" {
|
||||
if is_claude_cli_request(headers) {
|
||||
Some(classified("ai_public", "claude", "cli", "claude:cli", true))
|
||||
} else {
|
||||
Some(classified(
|
||||
"ai_public",
|
||||
"claude",
|
||||
"chat",
|
||||
"claude:chat",
|
||||
true,
|
||||
))
|
||||
}
|
||||
} else if normalized_path.starts_with("/v1/videos") {
|
||||
Some(classified(
|
||||
"ai_public",
|
||||
"openai",
|
||||
"video",
|
||||
"openai:video",
|
||||
true,
|
||||
))
|
||||
} else if is_gemini_models_route(normalized_path) {
|
||||
if normalized_path.ends_with(":predictLongRunning") {
|
||||
Some(classified(
|
||||
"ai_public",
|
||||
"gemini",
|
||||
"video",
|
||||
"gemini:video",
|
||||
true,
|
||||
))
|
||||
} else if is_gemini_cli_request(headers) {
|
||||
Some(classified("ai_public", "gemini", "cli", "gemini:cli", true))
|
||||
} else {
|
||||
Some(classified(
|
||||
"ai_public",
|
||||
"gemini",
|
||||
"chat",
|
||||
"gemini:chat",
|
||||
true,
|
||||
))
|
||||
}
|
||||
} else if is_gemini_operation_route(normalized_path) {
|
||||
Some(classified(
|
||||
"ai_public",
|
||||
"gemini",
|
||||
"video",
|
||||
"gemini:video",
|
||||
true,
|
||||
))
|
||||
} else if (method == http::Method::POST && normalized_path == "/upload/v1beta/files")
|
||||
|| normalized_path.starts_with("/v1beta/files")
|
||||
{
|
||||
Some(classified(
|
||||
"ai_public",
|
||||
"gemini",
|
||||
"files",
|
||||
"gemini:chat",
|
||||
true,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
49
apps/aether-gateway/src/control/route/internal.rs
Normal file
49
apps/aether-gateway/src/control/route/internal.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use super::{classified, ClassifiedRoute};
|
||||
use crate::gateway::{is_tunnel_heartbeat_path, is_tunnel_node_status_path, TUNNEL_ROUTE_FAMILY};
|
||||
|
||||
pub(super) fn classify_internal_route(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
if method == http::Method::POST && normalized_path.starts_with("/api/internal/gateway/") {
|
||||
let route_kind = match normalized_path {
|
||||
"/api/internal/gateway/resolve" => "resolve",
|
||||
"/api/internal/gateway/auth-context" => "auth_context",
|
||||
"/api/internal/gateway/decision-sync" => "decision_sync",
|
||||
"/api/internal/gateway/decision-stream" => "decision_stream",
|
||||
"/api/internal/gateway/plan-sync" => "plan_sync",
|
||||
"/api/internal/gateway/plan-stream" => "plan_stream",
|
||||
"/api/internal/gateway/report-sync" => "report_sync",
|
||||
"/api/internal/gateway/report-stream" => "report_stream",
|
||||
"/api/internal/gateway/finalize-sync" => "finalize_sync",
|
||||
"/api/internal/gateway/execute-sync" => "execute_sync",
|
||||
"/api/internal/gateway/execute-stream" => "execute_stream",
|
||||
_ => "legacy_gateway",
|
||||
};
|
||||
Some(classified(
|
||||
"internal_proxy",
|
||||
"gateway_legacy",
|
||||
route_kind,
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && is_tunnel_heartbeat_path(normalized_path) {
|
||||
Some(classified(
|
||||
"internal_proxy",
|
||||
TUNNEL_ROUTE_FAMILY,
|
||||
"heartbeat",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && is_tunnel_node_status_path(normalized_path) {
|
||||
Some(classified(
|
||||
"internal_proxy",
|
||||
TUNNEL_ROUTE_FAMILY,
|
||||
"node_status",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
278
apps/aether-gateway/src/control/route/oauth.rs
Normal file
278
apps/aether-gateway/src/control/route/oauth.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
use super::{classified, ClassifiedRoute};
|
||||
|
||||
pub(super) fn classify_oauth_route(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
if method == http::Method::GET && normalized_path == "/api/oauth/providers" {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_public_legacy",
|
||||
"providers",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path.starts_with("/api/oauth/") {
|
||||
if normalized_path.ends_with("/authorize") {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_public_legacy",
|
||||
"authorize",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if normalized_path.ends_with("/callback") {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_public_legacy",
|
||||
"callback",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if method == http::Method::GET && normalized_path == "/api/user/oauth/bindable-providers"
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_user_legacy",
|
||||
"bindable_providers",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/user/oauth/links" {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_user_legacy",
|
||||
"links",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/user/oauth/")
|
||||
&& normalized_path.ends_with("/bind-token")
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_user_legacy",
|
||||
"bind_token",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/user/oauth/")
|
||||
&& normalized_path.ends_with("/bind")
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_user_legacy",
|
||||
"bind",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE && normalized_path.starts_with("/api/user/oauth/") {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"oauth_user_legacy",
|
||||
"unbind",
|
||||
"",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/admin/oauth/supported-types" {
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"oauth_manage",
|
||||
"supported_types",
|
||||
"admin:oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/admin/oauth/providers" | "/api/admin/oauth/providers/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"oauth_manage",
|
||||
"list_providers",
|
||||
"admin:oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/oauth/providers/")
|
||||
&& normalized_path.ends_with("/test")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"oauth_manage",
|
||||
"test_provider",
|
||||
"admin:oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/oauth/providers/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"oauth_manage",
|
||||
"get_provider",
|
||||
"admin:oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/admin/oauth/providers/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"oauth_manage",
|
||||
"upsert_provider",
|
||||
"admin:oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/admin/oauth/providers/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"oauth_manage",
|
||||
"delete_provider",
|
||||
"admin:oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path == "/api/admin/provider-oauth/supported-types"
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_oauth_manage",
|
||||
"supported_types",
|
||||
"admin:provider_oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-oauth/keys/")
|
||||
&& normalized_path.ends_with("/start")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_oauth_manage",
|
||||
"start_key_oauth",
|
||||
"admin:provider_oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-oauth/providers/")
|
||||
&& normalized_path.ends_with("/start")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_oauth_manage",
|
||||
"start_provider_oauth",
|
||||
"admin:provider_oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-oauth/keys/")
|
||||
&& normalized_path.ends_with("/complete")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_oauth_manage",
|
||||
"complete_key_oauth",
|
||||
"admin:provider_oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-oauth/keys/")
|
||||
&& normalized_path.ends_with("/refresh")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_oauth_manage",
|
||||
"refresh_key_oauth",
|
||||
"admin:provider_oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-oauth/providers/")
|
||||
&& normalized_path.ends_with("/complete")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_oauth_manage",
|
||||
"complete_provider_oauth",
|
||||
"admin:provider_oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-oauth/providers/")
|
||||
&& normalized_path.ends_with("/import-refresh-token")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_oauth_manage",
|
||||
"import_refresh_token",
|
||||
"admin:provider_oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-oauth/providers/")
|
||||
&& normalized_path.ends_with("/batch-import")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_oauth_manage",
|
||||
"batch_import_oauth",
|
||||
"admin:provider_oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-oauth/providers/")
|
||||
&& normalized_path.ends_with("/batch-import/tasks")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_oauth_manage",
|
||||
"start_batch_import_oauth_task",
|
||||
"admin:provider_oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/admin/provider-oauth/providers/")
|
||||
&& normalized_path.contains("/batch-import/tasks/")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_oauth_manage",
|
||||
"get_batch_import_task_status",
|
||||
"admin:provider_oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-oauth/providers/")
|
||||
&& normalized_path.ends_with("/device-authorize")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_oauth_manage",
|
||||
"device_authorize",
|
||||
"admin:provider_oauth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/admin/provider-oauth/providers/")
|
||||
&& normalized_path.ends_with("/device-poll")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"provider_oauth_manage",
|
||||
"device_poll",
|
||||
"admin:provider_oauth",
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
635
apps/aether-gateway/src/control/route/public_support.rs
Normal file
635
apps/aether-gateway/src/control/route/public_support.rs
Normal file
@@ -0,0 +1,635 @@
|
||||
use super::{classified, is_gemini_models_route, is_gemini_operation_route, ClassifiedRoute};
|
||||
|
||||
pub(super) fn classify_public_support_route(
|
||||
method: &http::Method,
|
||||
normalized_path: &str,
|
||||
public_models_auth_signature: &str,
|
||||
) -> Option<ClassifiedRoute> {
|
||||
if method == http::Method::GET && normalized_path == "/v1/models" {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"models",
|
||||
"list",
|
||||
public_models_auth_signature,
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/v1/models/")
|
||||
&& !is_gemini_models_route(normalized_path)
|
||||
&& !is_gemini_operation_route(normalized_path)
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"models",
|
||||
"detail",
|
||||
public_models_auth_signature,
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/v1beta/models" {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"models",
|
||||
"list",
|
||||
public_models_auth_signature,
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/v1beta/models/")
|
||||
&& !is_gemini_models_route(normalized_path)
|
||||
&& !is_gemini_operation_route(normalized_path)
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"models",
|
||||
"detail",
|
||||
public_models_auth_signature,
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/announcements" | "/api/announcements/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"announcements_manage",
|
||||
"create_announcement",
|
||||
"admin:announcements",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& normalized_path.starts_with("/api/announcements/")
|
||||
&& normalized_path != "/api/announcements/active"
|
||||
&& normalized_path != "/api/announcements/users/me/unread-count"
|
||||
&& normalized_path != "/api/announcements/users/me/unread-count/"
|
||||
&& !normalized_path.ends_with("/read-status")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"announcements_manage",
|
||||
"update_announcement",
|
||||
"admin:announcements",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE
|
||||
&& normalized_path.starts_with("/api/announcements/")
|
||||
&& normalized_path != "/api/announcements/active"
|
||||
&& normalized_path != "/api/announcements/users/me/unread-count"
|
||||
&& normalized_path != "/api/announcements/users/me/unread-count/"
|
||||
&& !normalized_path.ends_with("/read-status")
|
||||
{
|
||||
Some(classified(
|
||||
"admin_proxy",
|
||||
"announcements_manage",
|
||||
"delete_announcement",
|
||||
"admin:announcements",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/announcements" | "/api/announcements/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"announcements",
|
||||
"list",
|
||||
"public:announcements",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/announcements/active" | "/api/announcements/active/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"announcements",
|
||||
"active",
|
||||
"public:announcements",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& normalized_path.starts_with("/api/announcements/")
|
||||
&& normalized_path != "/api/announcements/active"
|
||||
&& normalized_path != "/api/announcements/users/me/unread-count"
|
||||
&& normalized_path != "/api/announcements/users/me/unread-count/"
|
||||
&& !normalized_path.ends_with("/read-status")
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"announcements",
|
||||
"detail",
|
||||
"public:announcements",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/public/site-info"
|
||||
| "/api/public/providers"
|
||||
| "/api/public/models"
|
||||
| "/api/public/search/models"
|
||||
| "/api/public/stats"
|
||||
| "/api/public/global-models"
|
||||
| "/api/public/health/api-formats"
|
||||
)
|
||||
{
|
||||
let route_kind = match normalized_path {
|
||||
"/api/public/site-info" => "site_info",
|
||||
"/api/public/providers" => "providers",
|
||||
"/api/public/models" => "models",
|
||||
"/api/public/search/models" => "search_models",
|
||||
"/api/public/stats" => "stats",
|
||||
"/api/public/global-models" => "global_models",
|
||||
"/api/public/health/api-formats" => "health_api_formats",
|
||||
_ => "site_info",
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"public_catalog",
|
||||
route_kind,
|
||||
"public:catalog",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/auth/registration-settings" | "/api/auth/settings"
|
||||
)
|
||||
{
|
||||
let route_kind = match normalized_path {
|
||||
"/api/auth/registration-settings" => "registration_settings",
|
||||
"/api/auth/settings" => "settings",
|
||||
_ => "settings",
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"auth_public",
|
||||
route_kind,
|
||||
"public:auth",
|
||||
false,
|
||||
))
|
||||
} else if matches!(method, &http::Method::GET | &http::Method::POST)
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/auth/login"
|
||||
| "/api/auth/refresh"
|
||||
| "/api/auth/register"
|
||||
| "/api/auth/me"
|
||||
| "/api/auth/logout"
|
||||
| "/api/auth/send-verification-code"
|
||||
| "/api/auth/verify-email"
|
||||
| "/api/auth/verification-status"
|
||||
)
|
||||
{
|
||||
let route_kind = match normalized_path {
|
||||
"/api/auth/login" => "login",
|
||||
"/api/auth/refresh" => "refresh",
|
||||
"/api/auth/register" => "register",
|
||||
"/api/auth/me" => "me",
|
||||
"/api/auth/logout" => "logout",
|
||||
"/api/auth/send-verification-code" => "send_verification_code",
|
||||
"/api/auth/verify-email" => "verify_email",
|
||||
"/api/auth/verification-status" => "verification_status",
|
||||
_ => "login",
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"auth_legacy",
|
||||
route_kind,
|
||||
"user:auth",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/dashboard/stats"
|
||||
| "/api/dashboard/recent-requests"
|
||||
| "/api/dashboard/provider-status"
|
||||
| "/api/dashboard/daily-stats"
|
||||
)
|
||||
{
|
||||
let route_kind = match normalized_path {
|
||||
"/api/dashboard/stats" => "stats",
|
||||
"/api/dashboard/recent-requests" => "recent_requests",
|
||||
"/api/dashboard/provider-status" => "provider_status",
|
||||
"/api/dashboard/daily-stats" => "daily_stats",
|
||||
_ => "stats",
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"dashboard_legacy",
|
||||
route_kind,
|
||||
"user:dashboard",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/monitoring/my-audit-logs" | "/api/monitoring/rate-limit-status"
|
||||
)
|
||||
{
|
||||
let route_kind = match normalized_path {
|
||||
"/api/monitoring/my-audit-logs" => "audit_logs",
|
||||
"/api/monitoring/rate-limit-status" => "rate_limit_status",
|
||||
_ => "audit_logs",
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"monitoring_user_legacy",
|
||||
route_kind,
|
||||
"user:monitoring",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/announcements/users/me/unread-count"
|
||||
| "/api/announcements/users/me/unread-count/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"announcement_user_legacy",
|
||||
"unread_count",
|
||||
"user:announcements",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/announcements/read-all" | "/api/announcements/read-all/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"announcement_user_legacy",
|
||||
"read_all",
|
||||
"user:announcements",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH
|
||||
&& normalized_path.starts_with("/api/announcements/")
|
||||
&& (normalized_path.ends_with("/read-status") || normalized_path.ends_with("/read-status/"))
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"announcement_user_legacy",
|
||||
"read_status",
|
||||
"user:announcements",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/wallet/balance"
|
||||
| "/api/wallet/transactions"
|
||||
| "/api/wallet/flow"
|
||||
| "/api/wallet/today-cost"
|
||||
| "/api/wallet/recharge"
|
||||
| "/api/wallet/refunds"
|
||||
)
|
||||
{
|
||||
let route_kind = match normalized_path {
|
||||
"/api/wallet/balance" => "balance",
|
||||
"/api/wallet/transactions" => "transactions",
|
||||
"/api/wallet/flow" => "flow",
|
||||
"/api/wallet/today-cost" => "today_cost",
|
||||
"/api/wallet/recharge" => "list_recharge_orders",
|
||||
"/api/wallet/refunds" => "list_refunds",
|
||||
_ => "balance",
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"wallet_legacy",
|
||||
route_kind,
|
||||
"user:wallet",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path.starts_with("/api/wallet/recharge/") {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"wallet_legacy",
|
||||
"recharge_detail",
|
||||
"user:wallet",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path.starts_with("/api/wallet/refunds/") {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"wallet_legacy",
|
||||
"refund_detail",
|
||||
"user:wallet",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/wallet/recharge" | "/api/wallet/refunds"
|
||||
)
|
||||
{
|
||||
let route_kind = match normalized_path {
|
||||
"/api/wallet/recharge" => "create_recharge_order",
|
||||
"/api/wallet/refunds" => "create_refund",
|
||||
_ => "create_recharge_order",
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"wallet_legacy",
|
||||
route_kind,
|
||||
"user:wallet",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST && normalized_path.starts_with("/api/payment/callback/")
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"payment_callback_legacy",
|
||||
"callback",
|
||||
"public:payment",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/users/me"
|
||||
| "/api/users/me/sessions"
|
||||
| "/api/users/me/api-keys"
|
||||
| "/api/users/me/usage"
|
||||
| "/api/users/me/usage/active"
|
||||
| "/api/users/me/usage/interval-timeline"
|
||||
| "/api/users/me/usage/heatmap"
|
||||
| "/api/users/me/providers"
|
||||
| "/api/users/me/available-models"
|
||||
| "/api/users/me/endpoint-status"
|
||||
| "/api/users/me/preferences"
|
||||
| "/api/users/me/model-capabilities"
|
||||
)
|
||||
{
|
||||
let route_kind = match normalized_path {
|
||||
"/api/users/me" => "detail",
|
||||
"/api/users/me/sessions" => "sessions",
|
||||
"/api/users/me/api-keys" => "api_keys_list",
|
||||
"/api/users/me/usage" => "usage",
|
||||
"/api/users/me/usage/active" => "usage_active",
|
||||
"/api/users/me/usage/interval-timeline" => "usage_interval_timeline",
|
||||
"/api/users/me/usage/heatmap" => "usage_heatmap",
|
||||
"/api/users/me/providers" => "providers",
|
||||
"/api/users/me/available-models" => "available_models",
|
||||
"/api/users/me/endpoint-status" => "endpoint_status",
|
||||
"/api/users/me/preferences" => "preferences",
|
||||
"/api/users/me/model-capabilities" => "model_capabilities",
|
||||
_ => "detail",
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/me/management-tokens" | "/api/me/management-tokens/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"management_tokens_list",
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/users/me" | "/api/users/me/preferences" | "/api/users/me/model-capabilities"
|
||||
)
|
||||
{
|
||||
let route_kind = match normalized_path {
|
||||
"/api/users/me" => "update_detail",
|
||||
"/api/users/me/preferences" => "preferences_update",
|
||||
"/api/users/me/model-capabilities" => "model_capabilities_update",
|
||||
_ => "update_detail",
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/me/management-tokens" | "/api/me/management-tokens/"
|
||||
)
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"management_tokens_create",
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::POST
|
||||
&& normalized_path.starts_with("/api/me/management-tokens/")
|
||||
&& normalized_path.ends_with("/regenerate")
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"management_token_regenerate",
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH && normalized_path == "/api/users/me/password" {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"password",
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PATCH
|
||||
&& normalized_path.starts_with("/api/me/management-tokens/")
|
||||
&& normalized_path.ends_with("/status")
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"management_token_toggle",
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::DELETE && normalized_path == "/api/users/me/sessions/others" {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
"sessions_others_delete",
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if matches!(method, &http::Method::PATCH | &http::Method::DELETE)
|
||||
&& normalized_path.starts_with("/api/users/me/sessions/")
|
||||
{
|
||||
let route_kind = if method == http::Method::PATCH {
|
||||
"session_update"
|
||||
} else {
|
||||
"session_delete"
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if matches!(method, &http::Method::GET | &http::Method::POST)
|
||||
&& normalized_path == "/api/users/me/api-keys"
|
||||
{
|
||||
let route_kind = if method == http::Method::GET {
|
||||
"api_keys_list"
|
||||
} else {
|
||||
"api_keys_create"
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::PUT
|
||||
&& (normalized_path.ends_with("/providers") || normalized_path.ends_with("/capabilities"))
|
||||
&& normalized_path.starts_with("/api/users/me/api-keys/")
|
||||
{
|
||||
let route_kind = if normalized_path.ends_with("/providers") {
|
||||
"api_key_providers_update"
|
||||
} else {
|
||||
"api_key_capabilities_update"
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if matches!(
|
||||
method,
|
||||
&http::Method::GET | &http::Method::PUT | &http::Method::PATCH | &http::Method::DELETE
|
||||
) && normalized_path.starts_with("/api/users/me/api-keys/")
|
||||
{
|
||||
let route_kind = match *method {
|
||||
http::Method::GET => "api_key_detail",
|
||||
http::Method::PUT => "api_key_update",
|
||||
http::Method::PATCH => "api_key_patch",
|
||||
http::Method::DELETE => "api_key_delete",
|
||||
_ => "api_key_detail",
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if matches!(
|
||||
method,
|
||||
&http::Method::GET | &http::Method::PUT | &http::Method::DELETE
|
||||
) && normalized_path.starts_with("/api/me/management-tokens/")
|
||||
{
|
||||
let route_kind = match *method {
|
||||
http::Method::GET => "management_token_detail",
|
||||
http::Method::PUT => "management_token_update",
|
||||
http::Method::DELETE => "management_token_delete",
|
||||
_ => "management_token_detail",
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"users_me_legacy",
|
||||
route_kind,
|
||||
"user:self",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/api/capabilities" | "/api/capabilities/user-configurable"
|
||||
)
|
||||
{
|
||||
let route_kind = match normalized_path {
|
||||
"/api/capabilities" => "list",
|
||||
"/api/capabilities/user-configurable" => "user_configurable",
|
||||
_ => "list",
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"capabilities",
|
||||
route_kind,
|
||||
"public:capabilities",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path.starts_with("/api/capabilities/model/")
|
||||
{
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"capabilities",
|
||||
"model",
|
||||
"public:capabilities",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/api/modules/auth-status" {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"modules",
|
||||
"auth_status",
|
||||
"public:modules",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET
|
||||
&& matches!(
|
||||
normalized_path,
|
||||
"/" | "/health" | "/v1/health" | "/v1/providers" | "/v1/test-connection"
|
||||
)
|
||||
{
|
||||
let route_kind = match normalized_path {
|
||||
"/" => "root",
|
||||
"/health" | "/v1/health" => "health",
|
||||
"/v1/providers" => "providers",
|
||||
"/v1/test-connection" => "test_connection",
|
||||
_ => "root",
|
||||
};
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"system_catalog",
|
||||
route_kind,
|
||||
"public:system_catalog",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path.starts_with("/v1/providers/") {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"system_catalog",
|
||||
"provider_detail",
|
||||
"public:system_catalog",
|
||||
false,
|
||||
))
|
||||
} else if method == http::Method::GET && normalized_path == "/test-connection" {
|
||||
Some(classified(
|
||||
"public_support",
|
||||
"system_catalog",
|
||||
"test_connection",
|
||||
"public:system_catalog",
|
||||
false,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
115
apps/aether-gateway/src/control/tests/admin_adaptive.rs
Normal file
115
apps/aether-gateway/src/control/tests/admin_adaptive.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_adaptive_keys_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/adaptive/keys?provider_id=provider-openai"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("adaptive_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_keys"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:adaptive")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_adaptive_summary_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/adaptive/summary"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("adaptive_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("summary"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:adaptive")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_adaptive_stats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/adaptive/keys/key-openai/stats"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("adaptive_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("get_stats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:adaptive")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_adaptive_toggle_mode_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/adaptive/keys/key-openai/mode"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::PATCH, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("adaptive_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("toggle_mode"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:adaptive")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_adaptive_set_limit_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/adaptive/keys/key-openai/limit?limit=9"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::PATCH, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("adaptive_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("set_limit"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:adaptive")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_adaptive_reset_learning_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/adaptive/keys/key-openai/learning"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("adaptive_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("reset_learning"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:adaptive")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
113
apps/aether-gateway/src/control/tests/admin_api_keys.rs
Normal file
113
apps/aether-gateway/src/control/tests/admin_api_keys.rs
Normal file
@@ -0,0 +1,113 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_api_keys_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/api-keys?limit=100"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("api_keys_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_api_keys"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:api_keys")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_api_keys_create_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/api-keys".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("api_keys_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("create_api_key"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:api_keys")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_api_keys_detail_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/api-keys/key-123"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("api_keys_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("api_key_detail"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:api_keys")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_api_keys_update_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/api-keys/key-123"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::PUT, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("api_keys_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("update_api_key"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:api_keys")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_api_keys_toggle_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/api-keys/key-123"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::PATCH, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("api_keys_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("toggle_api_key"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:api_keys")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_api_keys_delete_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/api-keys/key-123"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("api_keys_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("delete_api_key"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:api_keys")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
121
apps/aether-gateway/src/control/tests/admin_billing.rs
Normal file
121
apps/aether-gateway/src/control/tests/admin_billing.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_billing_presets_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/billing/presets"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("billing_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_presets"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:billing")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_billing_apply_preset_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/billing/presets/apply"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("billing_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("apply_preset"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:billing")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_billing_rule_routes_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
let list_uri: Uri = "/api/admin/billing/rules?page=1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let list = classify_control_route(&http::Method::GET, &list_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(list.route_family.as_deref(), Some("billing_manage"));
|
||||
assert_eq!(list.route_kind.as_deref(), Some("list_rules"));
|
||||
|
||||
let detail_uri: Uri = "/api/admin/billing/rules/rule-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let detail = classify_control_route(&http::Method::GET, &detail_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(detail.route_family.as_deref(), Some("billing_manage"));
|
||||
assert_eq!(detail.route_kind.as_deref(), Some("get_rule"));
|
||||
|
||||
let create_uri: Uri = "/api/admin/billing/rules"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let create = classify_control_route(&http::Method::POST, &create_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(create.route_family.as_deref(), Some("billing_manage"));
|
||||
assert_eq!(create.route_kind.as_deref(), Some("create_rule"));
|
||||
|
||||
let update_uri: Uri = "/api/admin/billing/rules/rule-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let update = classify_control_route(&http::Method::PUT, &update_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(update.route_family.as_deref(), Some("billing_manage"));
|
||||
assert_eq!(update.route_kind.as_deref(), Some("update_rule"));
|
||||
assert_eq!(
|
||||
update.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:billing")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_billing_collector_routes_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
let list_uri: Uri = "/api/admin/billing/collectors?page=1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let list = classify_control_route(&http::Method::GET, &list_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(list.route_family.as_deref(), Some("billing_manage"));
|
||||
assert_eq!(list.route_kind.as_deref(), Some("list_collectors"));
|
||||
|
||||
let detail_uri: Uri = "/api/admin/billing/collectors/collector-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let detail = classify_control_route(&http::Method::GET, &detail_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(detail.route_family.as_deref(), Some("billing_manage"));
|
||||
assert_eq!(detail.route_kind.as_deref(), Some("get_collector"));
|
||||
|
||||
let create_uri: Uri = "/api/admin/billing/collectors"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let create = classify_control_route(&http::Method::POST, &create_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(create.route_family.as_deref(), Some("billing_manage"));
|
||||
assert_eq!(create.route_kind.as_deref(), Some("create_collector"));
|
||||
|
||||
let update_uri: Uri = "/api/admin/billing/collectors/collector-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let update = classify_control_route(&http::Method::PUT, &update_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(update.route_family.as_deref(), Some("billing_manage"));
|
||||
assert_eq!(update.route_kind.as_deref(), Some("update_collector"));
|
||||
assert_eq!(
|
||||
update.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:billing")
|
||||
);
|
||||
}
|
||||
753
apps/aether-gateway/src/control/tests/admin_core.rs
Normal file
753
apps/aether-gateway/src/control/tests/admin_core.rs
Normal file
@@ -0,0 +1,753 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_endpoint_health_api_formats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/endpoints/health/api-formats?lookback_hours=12"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_health"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("health_api_formats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:endpoints_health")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_modules_status_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/modules/status"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("modules_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("status_list"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:modules")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_modules_status_detail_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/modules/status/oauth"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("modules_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("status_detail"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:modules")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_modules_set_enabled_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/modules/status/management_tokens/enabled"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::PUT, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("modules_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("set_enabled"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:modules")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_version_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/version"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("version"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_settings_get_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/settings"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("settings_get"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_config_export_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/config/export"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("config_export"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_users_export_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/users/export"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("users_export"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_maintenance_write_routes_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let cases = [
|
||||
("/api/admin/system/config/import", "config_import"),
|
||||
("/api/admin/system/users/import", "users_import"),
|
||||
("/api/admin/system/smtp/test", "smtp_test"),
|
||||
("/api/admin/system/cleanup", "cleanup"),
|
||||
("/api/admin/system/purge/config", "purge_config"),
|
||||
("/api/admin/system/purge/users", "purge_users"),
|
||||
("/api/admin/system/purge/usage", "purge_usage"),
|
||||
("/api/admin/system/purge/audit-logs", "purge_audit_logs"),
|
||||
(
|
||||
"/api/admin/system/purge/request-bodies",
|
||||
"purge_request_bodies",
|
||||
),
|
||||
("/api/admin/system/purge/stats", "purge_stats"),
|
||||
];
|
||||
|
||||
for (path, expected_kind) in cases {
|
||||
let uri: Uri = path.parse().expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::POST, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some(expected_kind));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_check_update_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/check-update"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("check_update"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_aws_regions_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/aws-regions"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("aws_regions"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_stats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/stats".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("stats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_settings_set_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/settings"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::PUT, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("settings_set"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_email_templates_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/email/templates"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("email_templates_list"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_email_template_get_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/email/templates/verification"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("email_template_get"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_email_template_set_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/email/templates/verification"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::PUT, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("email_template_set"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_email_template_preview_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/email/templates/verification/preview"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("email_template_preview")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_email_template_reset_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/email/templates/verification/reset"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("email_template_reset"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_configs_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/configs?limit=20"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("configs_list"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_config_get_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/configs/smtp_password"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("config_get"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_config_set_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/configs/smtp_password"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::PUT, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("config_set"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_config_delete_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/configs/request_log_level"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("config_delete"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_system_api_formats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/system/api-formats"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("api_formats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:system")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_list_providers_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/?skip=0&limit=50"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("providers_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_providers"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_list_management_tokens_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/management-tokens?limit=20"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("management_tokens_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_tokens"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:management_tokens")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_ldap_config_get_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/ldap/config".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("ldap_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("get_config"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:ldap")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_ldap_config_set_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/ldap/config".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::PUT, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("ldap_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("set_config"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:ldap")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_ldap_test_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/ldap/test".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("ldap_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("test_connection"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:ldap")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_list_gemini_file_mappings_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/gemini-files/mappings?page=1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("gemini_files_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_mappings"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:gemini_files")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_gemini_file_stats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/gemini-files/stats"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("gemini_files_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("stats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:gemini_files")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_delete_gemini_file_mapping_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/gemini-files/mappings/mapping-123"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("gemini_files_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("delete_mapping"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:gemini_files")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_cleanup_gemini_file_mappings_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/gemini-files/mappings"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("gemini_files_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("cleanup_mappings"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:gemini_files")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_list_gemini_file_capable_keys_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/gemini-files/capable-keys"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("gemini_files_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("capable_keys"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:gemini_files")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_gemini_file_upload_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/gemini-files/upload"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("gemini_files_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("upload"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:gemini_files")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_oauth_supported_types_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/oauth/supported-types"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("oauth_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("supported_types"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:oauth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_oauth_supported_types_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-oauth/supported-types"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_oauth_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("supported_types"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_oauth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_trace_request_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/trace/request-id-123"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("trace_request"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_trace_provider_stats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/trace/stats/provider/provider-id"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("trace_provider_stats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
382
apps/aether-gateway/src/control/tests/admin_endpoints.rs
Normal file
382
apps/aether-gateway/src/control/tests/admin_endpoints.rs
Normal file
@@ -0,0 +1,382 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_endpoint_health_summary_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/endpoints/health/summary"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_health"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("health_summary"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:endpoints_health")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_endpoint_key_health_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/endpoints/health/key/key-openai?api_format=openai:chat"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_health"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("key_health"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:endpoints_health")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_endpoint_recover_key_health_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/endpoints/health/keys/key-openai?api_format=openai:chat"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::PATCH, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_health"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("recover_key_health"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:endpoints_health")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_endpoint_recover_all_keys_health_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/endpoints/health/keys"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::PATCH, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_health"));
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("recover_all_keys_health")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:endpoints_health")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_endpoint_health_status_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/endpoints/health/status?lookback_hours=12"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_health"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("health_status"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:endpoints_health")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_endpoint_key_rpm_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/endpoints/rpm/key/key-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_rpm"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("key_rpm"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:endpoints_rpm")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_endpoint_reset_key_rpm_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/endpoints/rpm/key/key-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_rpm"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("reset_key_rpm"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:endpoints_rpm")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_list_provider_endpoints_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/endpoints/providers/provider-1/endpoints?skip=0&limit=50"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("list_provider_endpoints")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:endpoints_manage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_list_provider_keys_as_admin_proxy_route() {
|
||||
let headers = http::HeaderMap::new();
|
||||
let uri: Uri = "/api/admin/endpoints/providers/provider-openai/keys?skip=0&limit=20"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::GET, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_provider_keys"));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_keys_grouped_by_format_as_admin_proxy_route() {
|
||||
let headers = http::HeaderMap::new();
|
||||
let uri: Uri = "/api/admin/endpoints/keys/grouped-by-format"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::GET, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("keys_grouped_by_format")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_reveal_key_as_admin_proxy_route() {
|
||||
let headers = http::HeaderMap::new();
|
||||
let uri: Uri = "/api/admin/endpoints/keys/key-openai/reveal"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::GET, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("reveal_key"));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_export_key_as_admin_proxy_route() {
|
||||
let headers = http::HeaderMap::new();
|
||||
let uri: Uri = "/api/admin/endpoints/keys/key-openai/export"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::GET, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("export_key"));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_update_key_as_admin_proxy_route() {
|
||||
let headers = http::HeaderMap::new();
|
||||
let uri: Uri = "/api/admin/endpoints/keys/key-openai"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::PUT, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("update_key"));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_delete_key_as_admin_proxy_route() {
|
||||
let headers = http::HeaderMap::new();
|
||||
let uri: Uri = "/api/admin/endpoints/keys/key-openai"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("delete_key"));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_batch_delete_keys_as_admin_proxy_route() {
|
||||
let headers = http::HeaderMap::new();
|
||||
let uri: Uri = "/api/admin/endpoints/keys/batch-delete"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::POST, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("batch_delete_keys"));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_clear_oauth_invalid_as_admin_proxy_route() {
|
||||
let headers = http::HeaderMap::new();
|
||||
let uri: Uri = "/api/admin/endpoints/keys/key-openai/clear-oauth-invalid"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::POST, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("clear_oauth_invalid"));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_create_provider_key_as_admin_proxy_route() {
|
||||
let headers = http::HeaderMap::new();
|
||||
let uri: Uri = "/api/admin/endpoints/providers/provider-openai/keys"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::POST, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("create_provider_key"));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_get_endpoint_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/endpoints/endpoint-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("get_endpoint"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:endpoints_manage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_create_endpoint_as_admin_proxy_route() {
|
||||
let headers = http::HeaderMap::new();
|
||||
let uri: Uri = "/api/admin/endpoints/providers/provider-openai/endpoints"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::POST, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("create_endpoint"));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_update_endpoint_as_admin_proxy_route() {
|
||||
let headers = http::HeaderMap::new();
|
||||
let uri: Uri = "/api/admin/endpoints/endpoint-openai-chat"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::PUT, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("update_endpoint"));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_delete_endpoint_as_admin_proxy_route() {
|
||||
let headers = http::HeaderMap::new();
|
||||
let uri: Uri = "/api/admin/endpoints/endpoint-openai-chat"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("delete_endpoint"));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_refresh_provider_quota_as_admin_proxy_route() {
|
||||
let headers = http::HeaderMap::new();
|
||||
let uri: Uri = "/api/admin/endpoints/providers/provider-codex/refresh-quota"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::POST, &uri, &headers)
|
||||
.expect("decision should resolve");
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("refresh_quota"));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_default_body_rules_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/endpoints/defaults/openai:cli/body-rules?provider_type=codex"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("endpoints_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("default_body_rules"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:endpoints_manage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
387
apps/aether-gateway/src/control/tests/admin_monitoring.rs
Normal file
387
apps/aether-gateway/src/control/tests/admin_monitoring.rs
Normal file
@@ -0,0 +1,387 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_audit_logs_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/audit-logs"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("audit_logs"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_trace_request_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/trace/request-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("trace_request"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_cache_stats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/stats"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_cache_affinities_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/affinities"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_cache_affinity_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/affinity/user-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_cache_users_delete_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/users/user-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_cache_affinity_delete_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/affinity/user-key-1/endpoint-1/model-alpha/openai"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_cache_flush_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_cache_provider_delete_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/providers/provider-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_cache_metrics_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/metrics"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_cache_config_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/config"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_model_mapping_stats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/model-mapping/stats"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_model_mapping_delete_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/model-mapping"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_model_mapping_delete_model_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/model-mapping/model-alpha"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_model_mapping_delete_provider_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/model-mapping/provider/provider-1/model-alpha"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_redis_keys_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/redis-keys"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_redis_keys_delete_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/cache/redis-keys/dashboard"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("monitoring_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_resilience_status_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/resilience-status"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("monitoring_resilience")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_resilience_error_stats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/resilience/error-stats"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("monitoring_resilience")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_monitoring_user_behavior_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/monitoring/user-behavior/user-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("monitoring"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("user_behavior"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
284
apps/aether-gateway/src/control/tests/admin_oauth.rs
Normal file
284
apps/aether-gateway/src/control/tests/admin_oauth.rs
Normal file
@@ -0,0 +1,284 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_oauth_start_key_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-oauth/keys/key-123/start"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_oauth_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("start_key_oauth"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_oauth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_oauth_start_provider_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-oauth/providers/provider-123/start"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_oauth_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("start_provider_oauth"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_oauth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_oauth_batch_import_task_status_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-oauth/providers/provider-123/batch-import/tasks/task-456"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_oauth_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("get_batch_import_task_status")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_oauth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_oauth_maintenance_routes_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
for (method, path, route_kind) in [
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/admin/provider-oauth/keys/key-123/complete",
|
||||
"complete_key_oauth",
|
||||
),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/admin/provider-oauth/keys/key-123/refresh",
|
||||
"refresh_key_oauth",
|
||||
),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/admin/provider-oauth/providers/provider-123/complete",
|
||||
"complete_provider_oauth",
|
||||
),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/admin/provider-oauth/providers/provider-123/import-refresh-token",
|
||||
"import_refresh_token",
|
||||
),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/admin/provider-oauth/providers/provider-123/batch-import",
|
||||
"batch_import_oauth",
|
||||
),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/admin/provider-oauth/providers/provider-123/batch-import/tasks",
|
||||
"start_batch_import_oauth_task",
|
||||
),
|
||||
(
|
||||
http::Method::GET,
|
||||
"/api/admin/provider-oauth/providers/provider-123/batch-import/tasks/task-123",
|
||||
"get_batch_import_task_status",
|
||||
),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/admin/provider-oauth/providers/provider-123/device-authorize",
|
||||
"device_authorize",
|
||||
),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/admin/provider-oauth/providers/provider-123/device-poll",
|
||||
"device_poll",
|
||||
),
|
||||
] {
|
||||
let uri: Uri = path.parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&method, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_oauth_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_oauth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_oauth_list_providers_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/oauth/providers?limit=20"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("oauth_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_providers"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:oauth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_oauth_get_provider_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/oauth/providers/linuxdo"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("oauth_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("get_provider"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:oauth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_oauth_upsert_provider_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/oauth/providers/linuxdo"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::PUT, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("oauth_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("upsert_provider"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:oauth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_oauth_delete_provider_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/oauth/providers/linuxdo"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("oauth_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("delete_provider"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:oauth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_oauth_test_provider_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/oauth/providers/linuxdo/test"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("oauth_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("test_provider"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:oauth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_get_management_token_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/management-tokens/token-123"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("management_tokens_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("get_token"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_delete_management_token_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/management-tokens/token-123"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("management_tokens_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("delete_token"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_toggle_management_token_status_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/management-tokens/token-123/status"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::PATCH, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("management_tokens_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("toggle_status"));
|
||||
}
|
||||
136
apps/aether-gateway/src/control/tests/admin_payments.rs
Normal file
136
apps/aether-gateway/src/control/tests/admin_payments.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_payments_list_orders_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/payments/orders"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("payments_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_orders"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:payments")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_payments_get_order_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/payments/orders/order-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("payments_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("get_order"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:payments")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_payments_trailing_slash_routes_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
let detail_uri: Uri = "/api/admin/payments/orders/order-1/"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let detail = classify_control_route(&http::Method::GET, &detail_uri, &headers)
|
||||
.expect("detail route should classify");
|
||||
assert_eq!(detail.route_family.as_deref(), Some("payments_manage"));
|
||||
assert_eq!(detail.route_kind.as_deref(), Some("get_order"));
|
||||
|
||||
let credit_uri: Uri = "/api/admin/payments/orders/order-1/credit/"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let credit = classify_control_route(&http::Method::POST, &credit_uri, &headers)
|
||||
.expect("credit route should classify");
|
||||
assert_eq!(credit.route_family.as_deref(), Some("payments_manage"));
|
||||
assert_eq!(credit.route_kind.as_deref(), Some("credit_order"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_payments_expire_order_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/payments/orders/order-1/expire"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("payments_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("expire_order"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:payments")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_payments_credit_order_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/payments/orders/order-1/credit"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("payments_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("credit_order"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:payments")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_payments_fail_order_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/payments/orders/order-1/fail"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("payments_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("fail_order"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:payments")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_payments_callbacks_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/payments/callbacks"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("payments_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_callbacks"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:payments")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
174
apps/aether-gateway/src/control/tests/admin_pool.rs
Normal file
174
apps/aether-gateway/src/control/tests/admin_pool.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_pool_overview_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/pool/overview"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("pool_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("overview"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:pool")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_pool_scheduling_presets_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/pool/scheduling-presets"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("pool_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("scheduling_presets"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:pool")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_pool_provider_key_routes_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
let list_uri: Uri = "/api/admin/pool/provider-1/keys?page=1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let list = classify_control_route(&http::Method::GET, &list_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(list.route_family.as_deref(), Some("pool_manage"));
|
||||
assert_eq!(list.route_kind.as_deref(), Some("list_keys"));
|
||||
|
||||
let batch_import_uri: Uri = "/api/admin/pool/provider-1/keys/batch-import"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let batch_import = classify_control_route(&http::Method::POST, &batch_import_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(batch_import.route_family.as_deref(), Some("pool_manage"));
|
||||
assert_eq!(
|
||||
batch_import.route_kind.as_deref(),
|
||||
Some("batch_import_keys")
|
||||
);
|
||||
|
||||
let batch_action_uri: Uri = "/api/admin/pool/provider-1/keys/batch-action"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let batch_action = classify_control_route(&http::Method::POST, &batch_action_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(batch_action.route_family.as_deref(), Some("pool_manage"));
|
||||
assert_eq!(
|
||||
batch_action.route_kind.as_deref(),
|
||||
Some("batch_action_keys")
|
||||
);
|
||||
|
||||
let resolve_selection_uri: Uri = "/api/admin/pool/provider-1/keys/resolve-selection"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let resolve_selection =
|
||||
classify_control_route(&http::Method::POST, &resolve_selection_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(
|
||||
resolve_selection.route_family.as_deref(),
|
||||
Some("pool_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_selection.route_kind.as_deref(),
|
||||
Some("resolve_selection")
|
||||
);
|
||||
|
||||
let batch_delete_task_uri: Uri = "/api/admin/pool/provider-1/keys/batch-delete-task/task-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let batch_delete_task =
|
||||
classify_control_route(&http::Method::GET, &batch_delete_task_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(
|
||||
batch_delete_task.route_family.as_deref(),
|
||||
Some("pool_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
batch_delete_task.route_kind.as_deref(),
|
||||
Some("batch_delete_task_status")
|
||||
);
|
||||
|
||||
let cleanup_banned_uri: Uri = "/api/admin/pool/provider-1/keys/cleanup-banned"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let cleanup_banned = classify_control_route(&http::Method::POST, &cleanup_banned_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(cleanup_banned.route_family.as_deref(), Some("pool_manage"));
|
||||
assert_eq!(
|
||||
cleanup_banned.route_kind.as_deref(),
|
||||
Some("cleanup_banned_keys")
|
||||
);
|
||||
assert_eq!(
|
||||
cleanup_banned.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:pool")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_pool_trailing_slash_routes_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
let list_uri: Uri = "/api/admin/pool/provider-1/keys/"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let list = classify_control_route(&http::Method::GET, &list_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(list.route_family.as_deref(), Some("pool_manage"));
|
||||
assert_eq!(list.route_kind.as_deref(), Some("list_keys"));
|
||||
|
||||
let resolve_selection_uri: Uri = "/api/admin/pool/provider-1/keys/resolve-selection/"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let resolve_selection =
|
||||
classify_control_route(&http::Method::POST, &resolve_selection_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(
|
||||
resolve_selection.route_family.as_deref(),
|
||||
Some("pool_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
resolve_selection.route_kind.as_deref(),
|
||||
Some("resolve_selection")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_pool_malformed_provider_id_routes_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
let list_uri: Uri = "/api/admin/pool//keys".parse().expect("uri should parse");
|
||||
let list = classify_control_route(&http::Method::GET, &list_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(list.route_family.as_deref(), Some("pool_manage"));
|
||||
assert_eq!(list.route_kind.as_deref(), Some("list_keys"));
|
||||
|
||||
let import_uri: Uri = "/api/admin/pool//keys/batch-import"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let import = classify_control_route(&http::Method::POST, &import_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(import.route_family.as_deref(), Some("pool_manage"));
|
||||
assert_eq!(import.route_kind.as_deref(), Some("batch_import_keys"));
|
||||
|
||||
let cleanup_uri: Uri = "/api/admin/pool//keys/cleanup-banned"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let cleanup = classify_control_route(&http::Method::POST, &cleanup_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(cleanup.route_family.as_deref(), Some("pool_manage"));
|
||||
assert_eq!(cleanup.route_kind.as_deref(), Some("cleanup_banned_keys"));
|
||||
}
|
||||
318
apps/aether-gateway/src/control/tests/admin_provider_ops.rs
Normal file
318
apps/aether-gateway/src/control/tests/admin_provider_ops.rs
Normal file
@@ -0,0 +1,318 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_architectures_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/architectures?limit=20"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_architectures"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_architecture_detail_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/architectures/generic_api"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("get_architecture"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_provider_status_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/providers/provider-openai/status"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("get_provider_status"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_provider_config_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/providers/provider-openai/config"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("get_provider_config"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_save_provider_config_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/providers/provider-openai/config"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::PUT, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("save_provider_config"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_delete_provider_config_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/providers/provider-openai/config"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("delete_provider_config")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_disconnect_provider_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/providers/provider-openai/disconnect"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("disconnect_provider"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_connect_provider_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/providers/provider-openai/connect"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("connect_provider"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_verify_provider_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/providers/provider-openai/verify"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("verify_provider"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_get_balance_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/providers/provider-openai/balance"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("get_provider_balance"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_refresh_balance_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/providers/provider-openai/balance"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("refresh_provider_balance")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_checkin_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/providers/provider-openai/checkin"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("provider_checkin"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_execute_action_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/providers/provider-openai/actions/query_balance"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("execute_provider_action")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_ops_batch_balance_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-ops/batch/balance"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_ops_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("batch_balance"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_ops")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_query_models_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-query/models"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_query_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("query_models"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_query")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_query_test_model_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-query/test-model"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_query_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("test_model"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_query")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_query_test_model_failover_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-query/test-model-failover"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_query_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("test_model_failover"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_query")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_strategy_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-strategy/strategies"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_strategy_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_strategies"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_strategy")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_strategy_reset_quota_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-strategy/providers/provider-openai/quota"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_strategy_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("reset_provider_quota"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_strategy")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_strategy_update_billing_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-strategy/providers/provider-openai/billing"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::PUT, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_strategy_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("update_provider_billing")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_strategy")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_strategy_stats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/provider-strategy/providers/provider-openai/stats?hours=48"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_strategy_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("get_provider_stats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:provider_strategy")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
691
apps/aether-gateway/src/control/tests/admin_providers_models.rs
Normal file
691
apps/aether-gateway/src/control/tests/admin_providers_models.rs
Normal file
@@ -0,0 +1,691 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_create_provider_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("providers_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("create_provider"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_providers_summary_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/summary?page=1&page_size=20"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("providers_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("summary_list"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_update_provider_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::PATCH, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("providers_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("update_provider"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_delete_provider_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("providers_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("delete_provider"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_summary_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/summary"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("providers_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("provider_summary"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_health_monitor_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/health-monitor?lookback_hours=6"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("providers_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("health_monitor"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_mapping_preview_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/mapping-preview"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("providers_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("mapping_preview"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_delete_task_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/delete-task/task-1234"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("providers_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("delete_provider_task"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_pool_status_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/pool-status"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("providers_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("pool_status"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_clear_pool_cooldown_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/pool/clear-cooldown/key-openai"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("providers_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("clear_pool_cooldown"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_reset_pool_cost_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/pool/reset-cost/key-openai"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("providers_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("reset_pool_cost"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_list_provider_models_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/models?skip=0&limit=20"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_models_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_provider_models"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_get_provider_model_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/models/model-gpt-5"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_models_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("get_provider_model"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_create_provider_model_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/models"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_models_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("create_provider_model")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_update_provider_model_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/models/model-gpt-5"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::PATCH, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_models_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("update_provider_model")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_delete_provider_model_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/models/model-gpt-5"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_models_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("delete_provider_model")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_batch_create_provider_models_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/models/batch"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_models_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("batch_create_provider_models")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_provider_available_source_models_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/available-source-models"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_models_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("available_source_models")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_assign_global_models_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/assign-global-models"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_models_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("assign_global_models"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_import_provider_models_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/providers/provider-openai/import-from-upstream"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("provider_models_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("import_from_upstream"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:providers")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_list_global_models_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/models/global?skip=0&limit=20"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("global_models_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_global_models"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:models")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_model_catalog_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/models/catalog"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("model_catalog_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("catalog"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:models")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_external_models_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/models/external"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("model_external_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("external"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:models")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_clear_external_models_cache_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/models/external/cache"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("model_external_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("clear_external_cache"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:models")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_global_model_routing_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/models/global/global-gpt-5/routing"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("global_models_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("routing_preview"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:models")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_get_global_model_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/models/global/global-gpt-5"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("global_models_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("get_global_model"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:models")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_create_global_model_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/models/global"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("global_models_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("create_global_model"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:models")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_assign_global_model_to_providers_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/models/global/global-gpt-5/assign-to-providers"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("global_models_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("assign_to_providers"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:models")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_global_model_providers_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/models/global/global-gpt-5/providers"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("global_models_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("global_model_providers")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:models")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_update_global_model_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/models/global/global-gpt-5"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::PATCH, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("global_models_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("update_global_model"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:models")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_delete_global_model_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/models/global/global-gpt-5"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("global_models_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("delete_global_model"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:models")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_batch_delete_global_models_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/models/global/batch-delete"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("global_models_manage")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("batch_delete_global_models")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:models")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
58
apps/aether-gateway/src/control/tests/admin_proxy_nodes.rs
Normal file
58
apps/aether-gateway/src/control/tests/admin_proxy_nodes.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_proxy_nodes_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/proxy-nodes?status=online&skip=10&limit=20"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("proxy_nodes_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_nodes"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:proxy_nodes")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_proxy_nodes_register_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/proxy-nodes/register"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("proxy_nodes_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("register_node"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:proxy_nodes")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_proxy_nodes_events_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/proxy-nodes/node-1/events?limit=50"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("proxy_nodes_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_node_events"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:proxy_nodes")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
134
apps/aether-gateway/src/control/tests/admin_security.rs
Normal file
134
apps/aether-gateway/src/control/tests/admin_security.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_security_blacklist_add_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/security/ip/blacklist"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("security_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("blacklist_add"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:security")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_security_blacklist_remove_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/security/ip/blacklist/1.2.3.4"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("security_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("blacklist_remove"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:security")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_security_blacklist_stats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/security/ip/blacklist/stats"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("security_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("blacklist_stats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:security")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_security_blacklist_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/security/ip/blacklist"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("security_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("blacklist_list"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:security")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_security_whitelist_add_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/security/ip/whitelist"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("security_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("whitelist_add"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:security")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_security_whitelist_remove_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/security/ip/whitelist/1.2.3.4"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("security_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("whitelist_remove"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:security")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_security_whitelist_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/security/ip/whitelist"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("security_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("whitelist_list"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:security")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
194
apps/aether-gateway/src/control/tests/admin_stats.rs
Normal file
194
apps/aether-gateway/src/control/tests/admin_stats.rs
Normal file
@@ -0,0 +1,194 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_stats_provider_quota_usage_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/stats/providers/quota-usage"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("stats_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("provider_quota_usage"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:stats")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_stats_comparison_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/stats/comparison"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("stats_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("comparison"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:stats")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_stats_error_distribution_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/stats/errors/distribution"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("stats_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("error_distribution"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:stats")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_stats_performance_percentiles_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/stats/performance/percentiles"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("stats_manage"));
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("performance_percentiles")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:stats")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_stats_cost_forecast_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/stats/cost/forecast"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("stats_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("cost_forecast"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:stats")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_stats_leaderboard_api_keys_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/stats/leaderboard/api-keys"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("stats_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("leaderboard_api_keys"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:stats")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_stats_leaderboard_models_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/stats/leaderboard/models"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("stats_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("leaderboard_models"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:stats")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_stats_leaderboard_users_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/stats/leaderboard/users"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("stats_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("leaderboard_users"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:stats")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_stats_cost_savings_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/stats/cost/savings"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("stats_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("cost_savings"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:stats")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_stats_time_series_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/stats/time-series"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("stats_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("time_series"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:stats")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
249
apps/aether-gateway/src/control/tests/admin_usage.rs
Normal file
249
apps/aether-gateway/src/control/tests/admin_usage.rs
Normal file
@@ -0,0 +1,249 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_stats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/usage/stats".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("usage_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("stats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:usage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_aggregation_stats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/usage/aggregation/stats"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("usage_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("aggregation_stats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:usage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_heatmap_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/usage/heatmap"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("usage_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("heatmap"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:usage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_records_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/usage/records"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("usage_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("records"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:usage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_active_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/usage/active".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("usage_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("active"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:usage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_cache_affinity_hit_analysis_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/usage/cache-affinity/hit-analysis"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("usage_manage"));
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("cache_affinity_hit_analysis")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:usage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_cache_affinity_interval_timeline_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/usage/cache-affinity/interval-timeline"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("usage_manage"));
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("cache_affinity_interval_timeline")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:usage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_cache_affinity_ttl_analysis_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/usage/cache-affinity/ttl-analysis"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("usage_manage"));
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("cache_affinity_ttl_analysis")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:usage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_detail_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/usage/usage-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("usage_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("detail"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:usage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_detail_with_empty_usage_id_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/usage/".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("usage_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("detail"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:usage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_curl_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/usage/usage-1/curl"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("usage_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("curl"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:usage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_curl_with_empty_usage_id_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/usage//curl".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("usage_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("curl"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:usage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_usage_replay_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/usage/usage-1/replay"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("usage_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("replay"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:usage")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
182
apps/aether-gateway/src/control/tests/admin_users.rs
Normal file
182
apps/aether-gateway/src/control/tests/admin_users.rs
Normal file
@@ -0,0 +1,182 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_users_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/users".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_users"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:users")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_users_create_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/users".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("create_user"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:users")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_user_detail_routes_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
let get_uri: Uri = "/api/admin/users/user-1".parse().expect("uri should parse");
|
||||
let get = classify_control_route(&http::Method::GET, &get_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(get.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(get.route_kind.as_deref(), Some("get_user"));
|
||||
|
||||
let put_uri: Uri = "/api/admin/users/user-1".parse().expect("uri should parse");
|
||||
let put = classify_control_route(&http::Method::PUT, &put_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(put.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(put.route_kind.as_deref(), Some("update_user"));
|
||||
|
||||
let delete_uri: Uri = "/api/admin/users/user-1".parse().expect("uri should parse");
|
||||
let delete = classify_control_route(&http::Method::DELETE, &delete_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(delete.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(delete.route_kind.as_deref(), Some("delete_user"));
|
||||
assert_eq!(
|
||||
delete.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:users")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_user_session_routes_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
let list_uri: Uri = "/api/admin/users/user-1/sessions"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let list = classify_control_route(&http::Method::GET, &list_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(list.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(list.route_kind.as_deref(), Some("list_user_sessions"));
|
||||
|
||||
let delete_all_uri: Uri = "/api/admin/users/user-1/sessions"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let delete_all = classify_control_route(&http::Method::DELETE, &delete_all_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(delete_all.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(
|
||||
delete_all.route_kind.as_deref(),
|
||||
Some("delete_user_sessions")
|
||||
);
|
||||
|
||||
let delete_one_uri: Uri = "/api/admin/users/user-1/sessions/session-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let delete_one = classify_control_route(&http::Method::DELETE, &delete_one_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(delete_one.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(
|
||||
delete_one.route_kind.as_deref(),
|
||||
Some("delete_user_session")
|
||||
);
|
||||
assert_eq!(
|
||||
delete_one.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:users")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_user_api_key_routes_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
let list_uri: Uri = "/api/admin/users/user-1/api-keys"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let list = classify_control_route(&http::Method::GET, &list_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(list.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(list.route_kind.as_deref(), Some("list_user_api_keys"));
|
||||
|
||||
let create_uri: Uri = "/api/admin/users/user-1/api-keys"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let create = classify_control_route(&http::Method::POST, &create_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(create.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(create.route_kind.as_deref(), Some("create_user_api_key"));
|
||||
|
||||
let delete_uri: Uri = "/api/admin/users/user-1/api-keys/key-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let delete = classify_control_route(&http::Method::DELETE, &delete_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(delete.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(delete.route_kind.as_deref(), Some("delete_user_api_key"));
|
||||
|
||||
let update_uri: Uri = "/api/admin/users/user-1/api-keys/key-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let update = classify_control_route(&http::Method::PUT, &update_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(update.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(update.route_kind.as_deref(), Some("update_user_api_key"));
|
||||
|
||||
let lock_uri: Uri = "/api/admin/users/user-1/api-keys/key-1/lock"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let lock = classify_control_route(&http::Method::PATCH, &lock_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(lock.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(lock.route_kind.as_deref(), Some("lock_user_api_key"));
|
||||
|
||||
let full_key_uri: Uri = "/api/admin/users/user-1/api-keys/key-1/full-key"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let full_key = classify_control_route(&http::Method::GET, &full_key_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(full_key.route_family.as_deref(), Some("users_manage"));
|
||||
assert_eq!(full_key.route_kind.as_deref(), Some("reveal_user_api_key"));
|
||||
assert_eq!(
|
||||
full_key.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:users")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_user_api_key_mutation_routes_with_admin_users_signature() {
|
||||
let headers = headers(&[]);
|
||||
|
||||
let create_uri: Uri = "/api/admin/users/user-1/api-keys"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let create = classify_control_route(&http::Method::POST, &create_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(create.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
create.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:users")
|
||||
);
|
||||
|
||||
let lock_uri: Uri = "/api/admin/users/user-1/api-keys/key-1/lock"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let lock = classify_control_route(&http::Method::PATCH, &lock_uri, &headers)
|
||||
.expect("route should classify");
|
||||
assert_eq!(lock.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(lock.auth_endpoint_signature.as_deref(), Some("admin:users"));
|
||||
}
|
||||
96
apps/aether-gateway/src/control/tests/admin_video_tasks.rs
Normal file
96
apps/aether-gateway/src/control/tests/admin_video_tasks.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_video_tasks_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/video-tasks?status=completed&page=2"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("video_tasks_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_tasks"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:video_tasks")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_video_tasks_stats_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/video-tasks/stats"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("video_tasks_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("stats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:video_tasks")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_video_tasks_detail_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/video-tasks/task-123"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("video_tasks_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("detail"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:video_tasks")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_video_tasks_cancel_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/video-tasks/task-123/cancel"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("video_tasks_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("cancel"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:video_tasks")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_video_tasks_video_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/video-tasks/task-123/video"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("video_tasks_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("video"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:video_tasks")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
254
apps/aether-gateway/src/control/tests/admin_wallets.rs
Normal file
254
apps/aether-gateway/src/control/tests/admin_wallets.rs
Normal file
@@ -0,0 +1,254 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_list_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/wallets?status=active&limit=20"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallets_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_wallets"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:wallets")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_ledger_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/wallets/ledger?owner_type=user"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallets_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("ledger"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:wallets")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_refund_requests_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/wallets/refund-requests?status=pending_approval"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallets_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_refund_requests"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:wallets")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_detail_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/wallets/wallet-123"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallets_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("wallet_detail"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:wallets")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_transactions_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/wallets/wallet-123/transactions?limit=50"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallets_manage"));
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("list_wallet_transactions")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:wallets")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_transactions_with_empty_wallet_id_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/wallets//transactions"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallets_manage"));
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("list_wallet_transactions")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:wallets")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_refunds_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/wallets/wallet-123/refunds?limit=50"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallets_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list_wallet_refunds"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:wallets")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_adjust_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/wallets/wallet-123/adjust"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallets_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("adjust_balance"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:wallets")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_recharge_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/wallets/wallet-123/recharge"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallets_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("recharge_balance"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:wallets")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_process_refund_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/wallets/wallet-123/refunds/refund-1/process"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallets_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("process_refund"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:wallets")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_process_refund_with_empty_refund_id_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/wallets/wallet-123/refunds//process"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallets_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("process_refund"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:wallets")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_complete_refund_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/wallets/wallet-123/refunds/refund-1/complete"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallets_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("complete_refund"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:wallets")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_wallets_fail_refund_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/admin/wallets/wallet-123/refunds/refund-1/fail"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallets_manage"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("fail_refund"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:wallets")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
87
apps/aether-gateway/src/control/tests/ai.rs
Normal file
87
apps/aether-gateway/src/control/tests/ai.rs
Normal file
@@ -0,0 +1,87 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_claude_count_tokens_as_non_execution_runtime_public_route() {
|
||||
let headers = headers(&[("x-api-key", "sk-test")]);
|
||||
let uri: Uri = "/v1/messages/count_tokens"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_family.as_deref(), Some("claude"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("count_tokens"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("claude:chat")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_models_list_as_claude_when_headers_match() {
|
||||
let headers = headers(&[
|
||||
("x-api-key", "sk-claude"),
|
||||
("anthropic-version", "2023-06-01"),
|
||||
]);
|
||||
let uri: Uri = "/v1/models".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("claude:chat")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_claude_messages_cli_when_bearer_without_api_key() {
|
||||
let headers = headers(&[("authorization", "Bearer token-123")]);
|
||||
let uri: Uri = "/v1/messages".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_family.as_deref(), Some("claude"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("cli"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("claude:cli")
|
||||
);
|
||||
assert!(decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_gemini_cli_generate_content_when_x_app_contains_cli() {
|
||||
let headers = headers(&[("x-app", "Gemini-CLI")]);
|
||||
let uri: Uri = "/v1beta/models/gemini-2.5-pro:generateContent"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_family.as_deref(), Some("gemini"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("cli"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("gemini:cli")
|
||||
);
|
||||
assert!(decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_gemini_predict_long_running_as_video_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/v1beta/models/veo-3:predictLongRunning"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_family.as_deref(), Some("gemini"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("video"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("gemini:video")
|
||||
);
|
||||
assert!(decision.is_execution_runtime_candidate());
|
||||
}
|
||||
55
apps/aether-gateway/src/control/tests/internal.rs
Normal file
55
apps/aether-gateway/src/control/tests/internal.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_internal_tunnel_heartbeat_as_internal_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/internal/tunnel/heartbeat"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("internal_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some(crate::gateway::TUNNEL_ROUTE_FAMILY)
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("heartbeat"));
|
||||
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_internal_gateway_resolve_as_internal_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/internal/gateway/resolve"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("internal_proxy"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("gateway_legacy"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("resolve"));
|
||||
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_internal_tunnel_node_status_as_internal_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/internal/tunnel/node-status"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("internal_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some(crate::gateway::TUNNEL_ROUTE_FAMILY)
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("node_status"));
|
||||
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
88
apps/aether-gateway/src/control/tests/mod.rs
Normal file
88
apps/aether-gateway/src/control/tests/mod.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use super::*;
|
||||
|
||||
fn headers(items: &[(&str, &str)]) -> http::HeaderMap {
|
||||
let mut headers = http::HeaderMap::new();
|
||||
for (name, value) in items {
|
||||
headers.insert(
|
||||
http::header::HeaderName::from_bytes(name.as_bytes()).expect("valid header name"),
|
||||
http::HeaderValue::from_str(value).expect("valid header value"),
|
||||
);
|
||||
}
|
||||
headers
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_public_request_context_from_request_parts() {
|
||||
let mut headers = headers(&[
|
||||
(http::header::HOST.as_str(), "api.example.test"),
|
||||
(http::header::CONTENT_TYPE.as_str(), "application/json"),
|
||||
]);
|
||||
headers.insert("x-app", http::HeaderValue::from_static("gemini-cli"));
|
||||
let uri: Uri = "/v1beta/models/gemini-2.5-pro:generateContent?alt=sse"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::POST, &uri, &headers);
|
||||
|
||||
let context = GatewayPublicRequestContext::from_request_parts(
|
||||
"trace-123",
|
||||
&http::Method::POST,
|
||||
&uri,
|
||||
&headers,
|
||||
decision,
|
||||
);
|
||||
|
||||
assert_eq!(context.trace_id, "trace-123");
|
||||
assert_eq!(context.request_method, http::Method::POST);
|
||||
assert_eq!(
|
||||
context.request_path,
|
||||
"/v1beta/models/gemini-2.5-pro:generateContent"
|
||||
);
|
||||
assert_eq!(context.request_query_string.as_deref(), Some("alt=sse"));
|
||||
assert_eq!(
|
||||
context.request_path_and_query(),
|
||||
"/v1beta/models/gemini-2.5-pro:generateContent?alt=sse"
|
||||
);
|
||||
assert_eq!(
|
||||
context.request_content_type.as_deref(),
|
||||
Some("application/json")
|
||||
);
|
||||
assert_eq!(context.host_header.as_deref(), Some("api.example.test"));
|
||||
assert_eq!(
|
||||
context
|
||||
.control_decision
|
||||
.as_ref()
|
||||
.and_then(|value| value.route_family.as_deref()),
|
||||
Some("gemini")
|
||||
);
|
||||
assert_eq!(
|
||||
context
|
||||
.control_decision
|
||||
.as_ref()
|
||||
.and_then(|value| value.route_kind.as_deref()),
|
||||
Some("cli")
|
||||
);
|
||||
}
|
||||
|
||||
mod admin_adaptive;
|
||||
mod admin_api_keys;
|
||||
mod admin_billing;
|
||||
mod admin_core;
|
||||
mod admin_endpoints;
|
||||
mod admin_monitoring;
|
||||
mod admin_oauth;
|
||||
mod admin_payments;
|
||||
mod admin_pool;
|
||||
mod admin_provider_ops;
|
||||
mod admin_provider_query;
|
||||
mod admin_provider_strategy;
|
||||
mod admin_providers_models;
|
||||
mod admin_proxy_nodes;
|
||||
mod admin_security;
|
||||
mod admin_stats;
|
||||
mod admin_usage;
|
||||
mod admin_users;
|
||||
mod admin_video_tasks;
|
||||
mod admin_wallets;
|
||||
mod ai;
|
||||
mod internal;
|
||||
mod public_support;
|
||||
817
apps/aether-gateway/src/control/tests/public_support.rs
Normal file
817
apps/aether-gateway/src/control/tests/public_support.rs
Normal file
@@ -0,0 +1,817 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn classifies_models_list_as_public_support_route() {
|
||||
let headers = headers(&[("authorization", "Bearer sk-test")]);
|
||||
let uri: Uri = "/v1/models".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("models"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("openai:chat")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_v1beta_models_as_gemini_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/v1beta/models?pageSize=10"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("models"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("gemini:chat")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_public_catalog_site_info_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/public/site-info".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("public_catalog"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("site_info"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:catalog")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_public_announcement_list_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/announcements?limit=20"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("announcements"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:announcements")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_announcement_create_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/announcements".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("announcements_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("create_announcement"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:announcements")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_announcement_update_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/announcements/announcement-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::PUT, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("announcements_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("update_announcement"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:announcements")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_admin_announcement_delete_as_admin_proxy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/announcements/announcement-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::DELETE, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("admin_proxy"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("announcements_manage")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("delete_announcement"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("admin:announcements")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_public_active_announcements_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/announcements/active"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("announcements"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("active"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:announcements")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_public_announcement_detail_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/announcements/announcement-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("announcements"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("detail"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:announcements")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_dashboard_stats_as_public_support_legacy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/dashboard/stats".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("dashboard_legacy"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("stats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("user:dashboard")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_user_monitoring_audit_logs_as_public_support_legacy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/monitoring/my-audit-logs"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("monitoring_user_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("audit_logs"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("user:monitoring")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_announcement_unread_count_as_public_support_legacy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/announcements/users/me/unread-count"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("announcement_user_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("unread_count"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("user:announcements")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_announcement_read_status_as_public_support_legacy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/announcements/announcement-1/read-status"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision = classify_control_route(&http::Method::PATCH, &uri, &headers)
|
||||
.expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("announcement_user_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("read_status"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("user:announcements")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_announcement_read_all_as_public_support_legacy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/announcements/read-all"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("announcement_user_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("read_all"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("user:announcements")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_wallet_legacy_routes_as_public_support_legacy_route() {
|
||||
let headers = headers(&[]);
|
||||
for (method, uri, route_kind) in [
|
||||
(http::Method::GET, "/api/wallet/balance", "balance"),
|
||||
(
|
||||
http::Method::GET,
|
||||
"/api/wallet/transactions?limit=20",
|
||||
"transactions",
|
||||
),
|
||||
(http::Method::GET, "/api/wallet/flow?limit=20", "flow"),
|
||||
(http::Method::GET, "/api/wallet/today-cost", "today_cost"),
|
||||
(
|
||||
http::Method::GET,
|
||||
"/api/wallet/recharge?limit=20",
|
||||
"list_recharge_orders",
|
||||
),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/wallet/recharge",
|
||||
"create_recharge_order",
|
||||
),
|
||||
(
|
||||
http::Method::GET,
|
||||
"/api/wallet/recharge/order-1",
|
||||
"recharge_detail",
|
||||
),
|
||||
(
|
||||
http::Method::GET,
|
||||
"/api/wallet/refunds?limit=20",
|
||||
"list_refunds",
|
||||
),
|
||||
(http::Method::POST, "/api/wallet/refunds", "create_refund"),
|
||||
(
|
||||
http::Method::GET,
|
||||
"/api/wallet/refunds/refund-1",
|
||||
"refund_detail",
|
||||
),
|
||||
] {
|
||||
let uri: Uri = uri.parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&method, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("wallet_legacy"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("user:wallet")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_users_me_legacy_routes_as_public_support_legacy_route() {
|
||||
let headers = headers(&[]);
|
||||
for (method, uri, route_kind) in [
|
||||
(http::Method::GET, "/api/users/me", "detail"),
|
||||
(http::Method::PUT, "/api/users/me", "update_detail"),
|
||||
(http::Method::PATCH, "/api/users/me/password", "password"),
|
||||
(http::Method::GET, "/api/users/me/sessions", "sessions"),
|
||||
(
|
||||
http::Method::DELETE,
|
||||
"/api/users/me/sessions/others",
|
||||
"sessions_others_delete",
|
||||
),
|
||||
(
|
||||
http::Method::PATCH,
|
||||
"/api/users/me/sessions/session-1",
|
||||
"session_update",
|
||||
),
|
||||
(
|
||||
http::Method::GET,
|
||||
"/api/users/me/api-keys/key-1",
|
||||
"api_key_detail",
|
||||
),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/users/me/api-keys",
|
||||
"api_keys_create",
|
||||
),
|
||||
(
|
||||
http::Method::PUT,
|
||||
"/api/users/me/api-keys/key-1",
|
||||
"api_key_update",
|
||||
),
|
||||
(
|
||||
http::Method::PATCH,
|
||||
"/api/users/me/api-keys/key-1",
|
||||
"api_key_patch",
|
||||
),
|
||||
(
|
||||
http::Method::DELETE,
|
||||
"/api/users/me/api-keys/key-1",
|
||||
"api_key_delete",
|
||||
),
|
||||
(
|
||||
http::Method::PUT,
|
||||
"/api/users/me/api-keys/key-1/providers",
|
||||
"api_key_providers_update",
|
||||
),
|
||||
(
|
||||
http::Method::PUT,
|
||||
"/api/users/me/api-keys/key-1/capabilities",
|
||||
"api_key_capabilities_update",
|
||||
),
|
||||
(
|
||||
http::Method::GET,
|
||||
"/api/users/me/available-models",
|
||||
"available_models",
|
||||
),
|
||||
(
|
||||
http::Method::PUT,
|
||||
"/api/users/me/model-capabilities",
|
||||
"model_capabilities_update",
|
||||
),
|
||||
(
|
||||
http::Method::GET,
|
||||
"/api/me/management-tokens",
|
||||
"management_tokens_list",
|
||||
),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/me/management-tokens",
|
||||
"management_tokens_create",
|
||||
),
|
||||
(
|
||||
http::Method::GET,
|
||||
"/api/me/management-tokens/token-1",
|
||||
"management_token_detail",
|
||||
),
|
||||
(
|
||||
http::Method::PUT,
|
||||
"/api/me/management-tokens/token-1",
|
||||
"management_token_update",
|
||||
),
|
||||
(
|
||||
http::Method::DELETE,
|
||||
"/api/me/management-tokens/token-1",
|
||||
"management_token_delete",
|
||||
),
|
||||
(
|
||||
http::Method::PATCH,
|
||||
"/api/me/management-tokens/token-1/status",
|
||||
"management_token_toggle",
|
||||
),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/me/management-tokens/token-1/regenerate",
|
||||
"management_token_regenerate",
|
||||
),
|
||||
] {
|
||||
let uri: Uri = uri.parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&method, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("users_me_legacy"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("user:self")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_payment_callback_as_public_support_legacy_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/payment/callback/alipay"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("payment_callback_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("callback"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:payment")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_public_catalog_providers_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/public/providers?limit=20"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("public_catalog"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("providers"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:catalog")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_public_catalog_models_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/public/models?provider_id=provider-openai"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("public_catalog"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("models"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:catalog")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_public_catalog_search_models_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/public/search/models?q=gpt"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("public_catalog"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("search_models"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:catalog")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_public_catalog_stats_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/public/stats".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("public_catalog"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("stats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:catalog")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_public_catalog_global_models_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/public/global-models?limit=10"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("public_catalog"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("global_models"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:catalog")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_public_catalog_health_api_formats_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/public/health/api-formats?lookback_hours=12"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("public_catalog"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("health_api_formats"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:catalog")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_auth_registration_settings_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/auth/registration-settings"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("auth_public"));
|
||||
assert_eq!(
|
||||
decision.route_kind.as_deref(),
|
||||
Some("registration_settings")
|
||||
);
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:auth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_auth_settings_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/auth/settings".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("auth_public"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("settings"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:auth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_auth_legacy_routes_as_public_support_route() {
|
||||
for (method, path, route_kind) in [
|
||||
(http::Method::POST, "/api/auth/login", "login"),
|
||||
(http::Method::POST, "/api/auth/refresh", "refresh"),
|
||||
(http::Method::POST, "/api/auth/register", "register"),
|
||||
(http::Method::GET, "/api/auth/me", "me"),
|
||||
(http::Method::POST, "/api/auth/logout", "logout"),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/auth/send-verification-code",
|
||||
"send_verification_code",
|
||||
),
|
||||
(http::Method::POST, "/api/auth/verify-email", "verify_email"),
|
||||
(
|
||||
http::Method::POST,
|
||||
"/api/auth/verification-status",
|
||||
"verification_status",
|
||||
),
|
||||
] {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = path.parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&method, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("auth_legacy"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("user:auth")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_oauth_public_providers_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/oauth/providers".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("oauth_public_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("providers"));
|
||||
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_oauth_public_authorize_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/oauth/linuxdo/authorize?client_device_id=device-1"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(
|
||||
decision.route_family.as_deref(),
|
||||
Some("oauth_public_legacy")
|
||||
);
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("authorize"));
|
||||
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_oauth_user_bindable_providers_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/user/oauth/bindable-providers"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("oauth_user_legacy"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("bindable_providers"));
|
||||
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_oauth_user_bind_token_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/user/oauth/linuxdo/bind-token"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("oauth_user_legacy"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("bind_token"));
|
||||
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(""));
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_capabilities_list_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/capabilities".parse().expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("capabilities"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("list"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:capabilities")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_capabilities_user_configurable_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/capabilities/user-configurable"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("capabilities"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("user_configurable"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:capabilities")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_capabilities_model_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/capabilities/model/gpt-5"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("capabilities"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("model"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:capabilities")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_modules_auth_status_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/api/modules/auth-status"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("modules"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("auth_status"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:modules")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_system_catalog_provider_detail_as_public_support_route() {
|
||||
let headers = headers(&[]);
|
||||
let uri: Uri = "/v1/providers/provider-openai"
|
||||
.parse()
|
||||
.expect("uri should parse");
|
||||
let decision =
|
||||
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
||||
|
||||
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
||||
assert_eq!(decision.route_family.as_deref(), Some("system_catalog"));
|
||||
assert_eq!(decision.route_kind.as_deref(), Some("provider_detail"));
|
||||
assert_eq!(
|
||||
decision.auth_endpoint_signature.as_deref(),
|
||||
Some("public:system_catalog")
|
||||
);
|
||||
assert!(!decision.is_execution_runtime_candidate());
|
||||
}
|
||||
Reference in New Issue
Block a user