2026-04-04 01:40:24 +08:00
|
|
|
use http::Uri;
|
|
|
|
|
|
2026-05-09 10:29:36 +08:00
|
|
|
use super::{classify_control_route, headers, GatewayPublicRequestContext};
|
|
|
|
|
use crate::handlers::shared::local_proxy_route_requires_buffered_body;
|
2026-03-31 19:19:04 +08:00
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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(),
|
2026-04-29 09:25:19 +08:00
|
|
|
Some("gemini:generate_content")
|
2026-03-31 19:19:04 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-04-04 01:40:24 +08:00
|
|
|
fn classifies_dashboard_stats_as_public_support_route() {
|
2026-03-31 19:19:04 +08:00
|
|
|
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"));
|
2026-04-04 01:40:24 +08:00
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("dashboard"));
|
2026-03-31 19:19:04 +08:00
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some("stats"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("user:dashboard")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-04-04 01:40:24 +08:00
|
|
|
fn classifies_user_monitoring_audit_logs_as_public_support_route() {
|
2026-03-31 19:19:04 +08:00
|
|
|
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"));
|
2026-04-04 01:40:24 +08:00
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("monitoring_user"));
|
2026-03-31 19:19:04 +08:00
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some("audit_logs"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("user:monitoring")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 10:07:52 +08:00
|
|
|
#[test]
|
|
|
|
|
fn classifies_wallet_redeem_as_public_support_route() {
|
|
|
|
|
let headers = headers(&[("authorization", "Bearer sk-test")]);
|
|
|
|
|
let uri: Uri = "/api/wallet/redeem".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("wallet"));
|
|
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some("redeem"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("user:wallet")
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 19:19:04 +08:00
|
|
|
#[test]
|
2026-04-04 01:40:24 +08:00
|
|
|
fn classifies_announcement_unread_count_as_public_support_route() {
|
2026-03-31 19:19:04 +08:00
|
|
|
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"));
|
2026-04-04 01:40:24 +08:00
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("announcement_user"));
|
2026-03-31 19:19:04 +08:00
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some("unread_count"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("user:announcements")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-04-04 01:40:24 +08:00
|
|
|
fn classifies_announcement_read_status_as_public_support_route() {
|
2026-03-31 19:19:04 +08:00
|
|
|
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"));
|
2026-04-04 01:40:24 +08:00
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("announcement_user"));
|
2026-03-31 19:19:04 +08:00
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some("read_status"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("user:announcements")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-04-04 01:40:24 +08:00
|
|
|
fn classifies_announcement_read_all_as_public_support_route() {
|
2026-03-31 19:19:04 +08:00
|
|
|
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"));
|
2026-04-04 01:40:24 +08:00
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("announcement_user"));
|
2026-03-31 19:19:04 +08:00
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some("read_all"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("user:announcements")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-04-04 01:40:24 +08:00
|
|
|
fn classifies_wallet_routes_as_public_support_route() {
|
2026-03-31 19:19:04 +08:00
|
|
|
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",
|
|
|
|
|
),
|
2026-05-19 19:01:10 +08:00
|
|
|
(
|
|
|
|
|
http::Method::GET,
|
|
|
|
|
"/api/wallet/refunds/eligible-providers",
|
|
|
|
|
"refund_eligible_providers",
|
|
|
|
|
),
|
2026-03-31 19:19:04 +08:00
|
|
|
(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"));
|
2026-04-04 01:40:24 +08:00
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("wallet"));
|
2026-03-31 19:19:04 +08:00
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("user:wallet")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-04-04 01:40:24 +08:00
|
|
|
fn classifies_users_me_routes_as_public_support_route() {
|
2026-03-31 19:19:04 +08:00
|
|
|
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",
|
|
|
|
|
),
|
2026-05-09 10:29:36 +08:00
|
|
|
(
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
"/api/users/me/api-keys/key-1/install-sessions",
|
|
|
|
|
"api_key_install_session_create",
|
|
|
|
|
),
|
2026-03-31 19:19:04 +08:00
|
|
|
(
|
|
|
|
|
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"));
|
2026-04-04 01:40:24 +08:00
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("users_me"));
|
2026-03-31 19:19:04 +08:00
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("user:self")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-29 21:39:28 +08:00
|
|
|
#[test]
|
|
|
|
|
fn classifies_ccswitch_usage_as_api_key_public_support_route() {
|
|
|
|
|
let headers = headers(&[]);
|
|
|
|
|
let uri: Uri = "/api/ccswitch/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("public_support"));
|
|
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("ccswitch"));
|
|
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some("usage"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("aether:ccswitch_usage")
|
|
|
|
|
);
|
|
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-09 10:29:36 +08:00
|
|
|
#[test]
|
|
|
|
|
fn user_api_key_install_session_create_buffers_request_body() {
|
|
|
|
|
let headers = headers(&[]);
|
|
|
|
|
let uri: Uri = "/api/users/me/api-keys/key-1/install-sessions"
|
|
|
|
|
.parse()
|
|
|
|
|
.expect("uri should parse");
|
|
|
|
|
let decision =
|
|
|
|
|
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
|
|
|
|
let context = GatewayPublicRequestContext::from_request_parts(
|
|
|
|
|
"trace-install-session",
|
|
|
|
|
&http::Method::POST,
|
|
|
|
|
&uri,
|
|
|
|
|
&headers,
|
|
|
|
|
Some(decision),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert!(local_proxy_route_requires_buffered_body(&context));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 19:19:04 +08:00
|
|
|
#[test]
|
2026-04-04 01:40:24 +08:00
|
|
|
fn classifies_payment_callback_as_public_support_route() {
|
2026-03-31 19:19:04 +08:00
|
|
|
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"));
|
2026-04-04 01:40:24 +08:00
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("payment_callback"));
|
2026-03-31 19:19:04 +08:00
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some("callback"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("public:payment")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-13 01:18:38 +08:00
|
|
|
#[test]
|
|
|
|
|
fn classifies_epay_callback_routes_as_public_support_route() {
|
|
|
|
|
let headers = headers(&[]);
|
|
|
|
|
for (method, uri, route_kind) in [
|
|
|
|
|
(http::Method::GET, "/api/payment/epay/notify", "epay_notify"),
|
|
|
|
|
(
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
"/api/payment/epay/notify",
|
|
|
|
|
"epay_notify",
|
|
|
|
|
),
|
|
|
|
|
(http::Method::GET, "/api/payment/epay/return", "epay_return"),
|
|
|
|
|
(
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
"/api/payment/epay/return",
|
|
|
|
|
"epay_return",
|
|
|
|
|
),
|
|
|
|
|
] {
|
|
|
|
|
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("payment_callback"));
|
|
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("public:payment")
|
|
|
|
|
);
|
|
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn epay_post_callback_routes_buffer_request_body() {
|
|
|
|
|
let headers = headers(&[]);
|
|
|
|
|
for path in ["/api/payment/epay/notify", "/api/payment/epay/return"] {
|
|
|
|
|
let uri: Uri = path.parse().expect("uri should parse");
|
|
|
|
|
let decision = classify_control_route(&http::Method::POST, &uri, &headers)
|
|
|
|
|
.expect("route should classify");
|
|
|
|
|
let context = GatewayPublicRequestContext::from_request_parts(
|
|
|
|
|
"trace-epay-callback",
|
|
|
|
|
&http::Method::POST,
|
|
|
|
|
&uri,
|
|
|
|
|
&headers,
|
|
|
|
|
Some(decision),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
local_proxy_route_requires_buffered_body(&context),
|
|
|
|
|
"POST {path} should buffer request body"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn classifies_billing_plan_routes_as_public_support_routes() {
|
|
|
|
|
let headers = headers(&[]);
|
|
|
|
|
for (method, uri, route_kind, signature) in [
|
|
|
|
|
(
|
|
|
|
|
http::Method::GET,
|
|
|
|
|
"/api/billing/plans",
|
|
|
|
|
"plans",
|
|
|
|
|
"public:billing",
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
http::Method::POST,
|
|
|
|
|
"/api/billing/plans/plan-1/checkout",
|
|
|
|
|
"plan_checkout",
|
|
|
|
|
"user:billing",
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
http::Method::GET,
|
|
|
|
|
"/api/billing/entitlements",
|
|
|
|
|
"entitlements",
|
|
|
|
|
"user:billing",
|
|
|
|
|
),
|
|
|
|
|
] {
|
|
|
|
|
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("billing"));
|
|
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
|
|
|
|
|
assert_eq!(decision.auth_endpoint_signature.as_deref(), Some(signature));
|
|
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn billing_plan_checkout_buffers_request_body() {
|
|
|
|
|
let headers = headers(&[]);
|
|
|
|
|
let uri: Uri = "/api/billing/plans/plan-1/checkout"
|
|
|
|
|
.parse()
|
|
|
|
|
.expect("uri should parse");
|
|
|
|
|
let decision =
|
|
|
|
|
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
|
|
|
|
let context = GatewayPublicRequestContext::from_request_parts(
|
|
|
|
|
"trace-billing-checkout",
|
|
|
|
|
&http::Method::POST,
|
|
|
|
|
&uri,
|
|
|
|
|
&headers,
|
|
|
|
|
Some(decision),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert!(local_proxy_route_requires_buffered_body(&context));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 19:19:04 +08:00
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-28 12:00:20 +08:00
|
|
|
#[test]
|
|
|
|
|
fn classifies_public_catalog_health_models_as_public_support_route() {
|
|
|
|
|
let headers = headers(&[]);
|
|
|
|
|
let uri: Uri = "/api/public/health/models?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_models"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("public:catalog")
|
|
|
|
|
);
|
|
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 00:10:59 +08:00
|
|
|
#[test]
|
|
|
|
|
fn classifies_public_catalog_health_related_as_public_support_route() {
|
|
|
|
|
let headers = headers(&[]);
|
|
|
|
|
let uri: Uri = "/api/public/health/related?dimension=endpoint&value=openai%3Achat"
|
|
|
|
|
.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_related"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("public:catalog")
|
|
|
|
|
);
|
|
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 19:19:04 +08:00
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-04-04 01:40:24 +08:00
|
|
|
fn classifies_auth_routes_as_public_support_route() {
|
2026-03-31 19:19:04 +08:00
|
|
|
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"));
|
2026-04-04 01:40:24 +08:00
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("auth"));
|
2026-03-31 19:19:04 +08:00
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some(route_kind));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("user:auth")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-04-28 15:46:21 +08:00
|
|
|
fn classifies_oauth_public_providers_route() {
|
2026-03-31 19:19:04 +08:00
|
|
|
let headers = headers(&[]);
|
|
|
|
|
let uri: Uri = "/api/oauth/providers".parse().expect("uri should parse");
|
2026-04-28 15:46:21 +08:00
|
|
|
let decision =
|
|
|
|
|
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
2026-03-31 19:19:04 +08:00
|
|
|
|
2026-04-28 15:46:21 +08:00
|
|
|
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
|
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("oauth"));
|
|
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some("list_providers"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("user:oauth")
|
|
|
|
|
);
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-04-28 15:46:21 +08:00
|
|
|
fn classifies_oauth_public_authorize_route() {
|
2026-03-31 19:19:04 +08:00
|
|
|
let headers = headers(&[]);
|
|
|
|
|
let uri: Uri = "/api/oauth/linuxdo/authorize?client_device_id=device-1"
|
|
|
|
|
.parse()
|
|
|
|
|
.expect("uri should parse");
|
2026-04-28 15:46:21 +08:00
|
|
|
let decision =
|
|
|
|
|
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
2026-03-31 19:19:04 +08:00
|
|
|
|
2026-04-28 15:46:21 +08:00
|
|
|
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
|
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("oauth"));
|
|
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some("authorize"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("user:oauth")
|
|
|
|
|
);
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-04-28 15:46:21 +08:00
|
|
|
fn classifies_oauth_user_bindable_providers_route() {
|
2026-03-31 19:19:04 +08:00
|
|
|
let headers = headers(&[]);
|
|
|
|
|
let uri: Uri = "/api/user/oauth/bindable-providers"
|
|
|
|
|
.parse()
|
|
|
|
|
.expect("uri should parse");
|
2026-04-28 15:46:21 +08:00
|
|
|
let decision =
|
|
|
|
|
classify_control_route(&http::Method::GET, &uri, &headers).expect("route should classify");
|
2026-03-31 19:19:04 +08:00
|
|
|
|
2026-04-28 15:46:21 +08:00
|
|
|
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
|
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("oauth"));
|
|
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some("bindable_providers"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("user:oauth")
|
|
|
|
|
);
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2026-04-28 15:46:21 +08:00
|
|
|
fn classifies_oauth_user_bind_token_route() {
|
2026-03-31 19:19:04 +08:00
|
|
|
let headers = headers(&[]);
|
|
|
|
|
let uri: Uri = "/api/user/oauth/linuxdo/bind-token"
|
|
|
|
|
.parse()
|
|
|
|
|
.expect("uri should parse");
|
2026-04-28 15:46:21 +08:00
|
|
|
let decision =
|
|
|
|
|
classify_control_route(&http::Method::POST, &uri, &headers).expect("route should classify");
|
2026-03-31 19:19:04 +08:00
|
|
|
|
2026-04-28 15:46:21 +08:00
|
|
|
assert_eq!(decision.route_class.as_deref(), Some("public_support"));
|
|
|
|
|
assert_eq!(decision.route_family.as_deref(), Some("oauth"));
|
|
|
|
|
assert_eq!(decision.route_kind.as_deref(), Some("bind_token"));
|
|
|
|
|
assert_eq!(
|
|
|
|
|
decision.auth_endpoint_signature.as_deref(),
|
|
|
|
|
Some("user:oauth")
|
|
|
|
|
);
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
|
);
|
2026-04-03 14:59:58 +08:00
|
|
|
assert!(!decision.is_execution_runtime_candidate());
|
2026-03-31 19:19:04 +08:00
|
|
|
}
|