mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-05 00:47:48 +08:00
Merge branch 'fawney19:main' into main
This commit is contained in:
@@ -6,6 +6,7 @@ use aether_contracts::{
|
||||
use aether_crypto::{
|
||||
decrypt_python_fernet_ciphertext, encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY,
|
||||
};
|
||||
use aether_data::repository::background_tasks::InMemoryBackgroundTaskRepository;
|
||||
use aether_data::repository::management_tokens::{
|
||||
InMemoryManagementTokenRepository, ManagementTokenReadRepository,
|
||||
};
|
||||
@@ -15,6 +16,7 @@ use aether_data::repository::oauth_providers::{
|
||||
use aether_data::repository::pool_scores::InMemoryPoolMemberScoreRepository;
|
||||
use aether_data::repository::provider_catalog::InMemoryProviderCatalogReadRepository;
|
||||
use aether_data::repository::proxy_nodes::InMemoryProxyNodeRepository;
|
||||
use aether_data_contracts::repository::background_tasks::BackgroundTaskReadRepository;
|
||||
use aether_data_contracts::repository::pool_scores::{
|
||||
GetPoolMemberScoresByIdsQuery, PoolMemberHardState, PoolMemberIdentity, PoolScoreReadRepository,
|
||||
};
|
||||
@@ -26,7 +28,7 @@ use axum::response::{IntoResponse, Response};
|
||||
use axum::routing::{any, delete, get, patch, post, put};
|
||||
use axum::{extract::Request, Json, Router};
|
||||
use http::{HeaderMap, HeaderValue, StatusCode};
|
||||
use serde_json::json;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use super::super::{
|
||||
build_router_with_state, build_state_with_execution_runtime_override, hash_management_token,
|
||||
@@ -309,17 +311,489 @@ async fn gateway_handles_admin_provider_oauth_supported_types_locally_with_trust
|
||||
let items = payload.as_array().expect("items should be array");
|
||||
assert_eq!(items.len(), 6);
|
||||
assert_eq!(items[0]["provider_type"], "claude_code");
|
||||
assert_eq!(
|
||||
items[0]["authorize_url"],
|
||||
"https://claude.ai/oauth/authorize"
|
||||
);
|
||||
assert_eq!(
|
||||
items[0]["token_url"],
|
||||
"https://platform.claude.com/v1/oauth/token"
|
||||
);
|
||||
assert_eq!(
|
||||
items[0]["redirect_uri"],
|
||||
"https://platform.claude.com/oauth/code/callback"
|
||||
);
|
||||
assert_eq!(items[0]["supports_cookie_authorization"], true);
|
||||
assert_eq!(items[1]["provider_type"], "codex");
|
||||
assert_eq!(items[2]["provider_type"], "chatgpt_web");
|
||||
assert_eq!(items[3]["provider_type"], "gemini_cli");
|
||||
assert_eq!(items[4]["provider_type"], "antigravity");
|
||||
assert_eq!(items[5]["provider_type"], "windsurf");
|
||||
assert!(items[1..]
|
||||
.iter()
|
||||
.all(|item| item["supports_cookie_authorization"] == false));
|
||||
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
gateway_handle.abort();
|
||||
upstream_handle.abort();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_authorizes_claude_cookie_without_persisting_cookie() {
|
||||
run_admin_oauth_test(
|
||||
"gateway_authorizes_claude_cookie_without_persisting_cookie",
|
||||
gateway_authorizes_claude_cookie_without_persisting_cookie_impl,
|
||||
);
|
||||
}
|
||||
|
||||
async fn gateway_authorizes_claude_cookie_without_persisting_cookie_impl() {
|
||||
let execution_plans = Arc::new(Mutex::new(Vec::<ExecutionPlan>::new()));
|
||||
let execution_plans_clone = Arc::clone(&execution_plans);
|
||||
let execution_runtime = Router::new().route(
|
||||
"/v1/execute/sync",
|
||||
any(move |Json(plan): Json<ExecutionPlan>| {
|
||||
let execution_plans_inner = Arc::clone(&execution_plans_clone);
|
||||
async move {
|
||||
execution_plans_inner
|
||||
.lock()
|
||||
.expect("mutex should lock")
|
||||
.push(plan.clone());
|
||||
let json_body = match plan.request_id.as_str() {
|
||||
"provider-oauth:claude-cookie-organizations" => json!([
|
||||
{"uuid": "org-personal", "raven_type": "personal"},
|
||||
{"uuid": "org-team", "raven_type": "team"}
|
||||
]),
|
||||
"provider-oauth:claude-cookie-authorize" => {
|
||||
let state = plan
|
||||
.body
|
||||
.json_body
|
||||
.as_ref()
|
||||
.and_then(|body| body.get("state"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.expect("authorize plan should contain state");
|
||||
let mut redirect =
|
||||
url::Url::parse("https://platform.claude.com/oauth/code/callback")
|
||||
.expect("redirect URL should parse");
|
||||
redirect
|
||||
.query_pairs_mut()
|
||||
.append_pair("code", "claude-authorization-code")
|
||||
.append_pair("state", state);
|
||||
json!({"redirect_uri": redirect.to_string()})
|
||||
}
|
||||
"provider-oauth:exchange-code" => json!({
|
||||
"access_token": "sk-ant-oat01-created",
|
||||
"refresh_token": "sk-ant-ort01-created",
|
||||
"expires_in": 3600,
|
||||
"organization": {"uuid": "org-team"},
|
||||
"account": {
|
||||
"uuid": "account-claude-123",
|
||||
"email_address": "[email protected]"
|
||||
}
|
||||
}),
|
||||
unexpected => panic!("unexpected execution plan: {unexpected}"),
|
||||
};
|
||||
Json(json!({
|
||||
"request_id": plan.request_id,
|
||||
"status_code": 200,
|
||||
"headers": {"content-type": "application/json"},
|
||||
"body": {"json_body": json_body}
|
||||
}))
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let mut provider = sample_provider("provider-claude", "claude", 10);
|
||||
provider.provider_type = "claude_code".to_string();
|
||||
provider.proxy = Some(json!({
|
||||
"mode": "tunnel",
|
||||
"node_id": "proxy-node-claude",
|
||||
"url": "http://proxy.example:8080",
|
||||
"enabled": true
|
||||
}));
|
||||
let endpoint = sample_endpoint(
|
||||
"endpoint-claude-messages",
|
||||
"provider-claude",
|
||||
"claude:messages",
|
||||
"https://api.anthropic.com",
|
||||
);
|
||||
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
|
||||
vec![provider],
|
||||
vec![endpoint],
|
||||
vec![],
|
||||
));
|
||||
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let state = build_state_with_execution_runtime_override(execution_runtime_url)
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_provider_catalog_repository_for_tests(
|
||||
provider_catalog_repository.clone(),
|
||||
)
|
||||
.with_encryption_key_for_tests(DEVELOPMENT_ENCRYPTION_KEY),
|
||||
)
|
||||
.with_provider_oauth_token_url_for_tests(
|
||||
"claude_code_cookie_base_url",
|
||||
"https://claude.example",
|
||||
)
|
||||
.with_provider_oauth_token_url_for_tests(
|
||||
"claude_code",
|
||||
"https://platform.example/v1/oauth/token",
|
||||
);
|
||||
|
||||
let response = local_admin_provider_oauth_response(
|
||||
&state,
|
||||
http::Method::POST,
|
||||
"/api/admin/provider-oauth/providers/provider-claude/cookie-authorize",
|
||||
Some(json!({
|
||||
"cookie": "Cookie: other=value; sessionKey=sk-ant-sid01-secret; theme=dark",
|
||||
"name": "claude-cookie-account"
|
||||
})),
|
||||
)
|
||||
.await;
|
||||
let status = response.status();
|
||||
let payload: serde_json::Value = serde_json::from_slice(
|
||||
&to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("body should read"),
|
||||
)
|
||||
.expect("response should be JSON");
|
||||
assert_eq!(status, StatusCode::OK, "payload={payload}");
|
||||
assert_eq!(payload["provider_type"], "claude_code");
|
||||
assert_eq!(payload["has_refresh_token"], true);
|
||||
assert_eq!(payload["temporary"], false);
|
||||
assert_eq!(payload["email"], "[email protected]");
|
||||
|
||||
let keys = provider_catalog_repository
|
||||
.list_keys_by_provider_ids(&["provider-claude".to_string()])
|
||||
.await
|
||||
.expect("keys should load");
|
||||
assert_eq!(keys.len(), 1);
|
||||
let decrypted_api_key = decrypt_python_fernet_ciphertext(
|
||||
DEVELOPMENT_ENCRYPTION_KEY,
|
||||
keys[0]
|
||||
.encrypted_api_key
|
||||
.as_deref()
|
||||
.expect("api key should be encrypted"),
|
||||
)
|
||||
.expect("api key should decrypt");
|
||||
assert_eq!(decrypted_api_key, "sk-ant-oat01-created");
|
||||
let decrypted_auth_config = decrypt_python_fernet_ciphertext(
|
||||
DEVELOPMENT_ENCRYPTION_KEY,
|
||||
keys[0]
|
||||
.encrypted_auth_config
|
||||
.as_deref()
|
||||
.expect("auth config should be encrypted"),
|
||||
)
|
||||
.expect("auth config should decrypt");
|
||||
let auth_config: serde_json::Value =
|
||||
serde_json::from_str(&decrypted_auth_config).expect("auth config should parse");
|
||||
assert_eq!(auth_config["refresh_token"], "sk-ant-ort01-created");
|
||||
assert_eq!(auth_config["org_uuid"], "org-team");
|
||||
assert_eq!(auth_config["account_uuid"], "account-claude-123");
|
||||
assert_eq!(auth_config["email"], "[email protected]");
|
||||
assert!(!decrypted_auth_config.contains("sk-ant-sid01-secret"));
|
||||
assert!(!decrypted_auth_config.contains("sessionKey"));
|
||||
assert!(auth_config.get("cookie").is_none());
|
||||
|
||||
let plans = execution_plans.lock().expect("mutex should lock");
|
||||
assert_eq!(plans.len(), 3);
|
||||
for plan in &plans[..2] {
|
||||
assert_eq!(
|
||||
plan.headers.get("cookie").map(String::as_str),
|
||||
Some("sessionKey=sk-ant-sid01-secret")
|
||||
);
|
||||
assert_eq!(
|
||||
plan.headers
|
||||
.get(EXECUTION_REQUEST_FOLLOW_REDIRECTS_HEADER)
|
||||
.map(String::as_str),
|
||||
Some("false")
|
||||
);
|
||||
assert_eq!(
|
||||
plan.proxy.as_ref().and_then(|proxy| proxy.mode.as_deref()),
|
||||
Some("tunnel")
|
||||
);
|
||||
assert!(plan.transport_profile.is_none());
|
||||
}
|
||||
let token_plan = &plans[2];
|
||||
assert_eq!(token_plan.request_id, "provider-oauth:exchange-code");
|
||||
assert!(!token_plan.headers.contains_key("cookie"));
|
||||
assert!(token_plan
|
||||
.body
|
||||
.json_body
|
||||
.as_ref()
|
||||
.is_some_and(|body| body.get("scope").is_none()));
|
||||
|
||||
execution_runtime_handle.abort();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_batch_authorizes_claude_cookies_as_redacted_task() {
|
||||
run_admin_oauth_test(
|
||||
"gateway_batch_authorizes_claude_cookies_as_redacted_task",
|
||||
gateway_batch_authorizes_claude_cookies_as_redacted_task_impl,
|
||||
);
|
||||
}
|
||||
|
||||
async fn gateway_batch_authorizes_claude_cookies_as_redacted_task_impl() {
|
||||
let execution_plans = Arc::new(Mutex::new(Vec::<ExecutionPlan>::new()));
|
||||
let execution_plans_clone = Arc::clone(&execution_plans);
|
||||
let execution_runtime = Router::new().route(
|
||||
"/v1/execute/sync",
|
||||
any(move |Json(plan): Json<ExecutionPlan>| {
|
||||
let execution_plans_inner = Arc::clone(&execution_plans_clone);
|
||||
async move {
|
||||
execution_plans_inner
|
||||
.lock()
|
||||
.expect("mutex should lock")
|
||||
.push(plan.clone());
|
||||
let json_body = match plan.request_id.as_str() {
|
||||
"provider-oauth:claude-cookie-organizations" => {
|
||||
json!([{"uuid": "org-team", "raven_type": "team"}])
|
||||
}
|
||||
"provider-oauth:claude-cookie-authorize" => {
|
||||
let cookie = plan
|
||||
.headers
|
||||
.get("cookie")
|
||||
.map(String::as_str)
|
||||
.expect("authorize plan should contain cookie");
|
||||
let label = if cookie.contains("batch-sid-one") {
|
||||
"one"
|
||||
} else if cookie.contains("batch-sid-two") {
|
||||
"two"
|
||||
} else {
|
||||
panic!("unexpected Cookie authorization input")
|
||||
};
|
||||
let state = plan
|
||||
.body
|
||||
.json_body
|
||||
.as_ref()
|
||||
.and_then(|body| body.get("state"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.expect("authorize plan should contain state");
|
||||
let mut redirect =
|
||||
url::Url::parse("https://platform.claude.com/oauth/code/callback")
|
||||
.expect("redirect URL should parse");
|
||||
redirect
|
||||
.query_pairs_mut()
|
||||
.append_pair("code", format!("code-{label}").as_str())
|
||||
.append_pair("state", state);
|
||||
json!({"redirect_uri": redirect.to_string()})
|
||||
}
|
||||
"provider-oauth:exchange-code" => {
|
||||
let code = plan
|
||||
.body
|
||||
.json_body
|
||||
.as_ref()
|
||||
.and_then(|body| body.get("code"))
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.expect("token plan should contain code");
|
||||
let label = code.strip_prefix("code-").expect("code should be tagged");
|
||||
json!({
|
||||
"access_token": format!("sk-ant-oat01-{label}"),
|
||||
"refresh_token": format!("sk-ant-ort01-{label}"),
|
||||
"expires_in": 3600,
|
||||
"organization": {"uuid": "org-team"},
|
||||
"account": {
|
||||
"uuid": format!("account-{label}"),
|
||||
"email_address": format!("{label}@example.com")
|
||||
}
|
||||
})
|
||||
}
|
||||
unexpected => panic!("unexpected execution plan: {unexpected}"),
|
||||
};
|
||||
Json(json!({
|
||||
"request_id": plan.request_id,
|
||||
"status_code": 200,
|
||||
"headers": {"content-type": "application/json"},
|
||||
"body": {"json_body": json_body}
|
||||
}))
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
let mut provider = sample_provider("provider-claude", "claude", 10);
|
||||
provider.provider_type = "claude_code".to_string();
|
||||
provider.proxy = Some(json!({
|
||||
"mode": "tunnel",
|
||||
"node_id": "proxy-node-claude",
|
||||
"url": "http://proxy.example:8080",
|
||||
"enabled": true
|
||||
}));
|
||||
let endpoint = sample_endpoint(
|
||||
"endpoint-claude-messages",
|
||||
"provider-claude",
|
||||
"claude:messages",
|
||||
"https://api.anthropic.com",
|
||||
);
|
||||
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
|
||||
vec![provider],
|
||||
vec![endpoint],
|
||||
vec![],
|
||||
));
|
||||
let background_task_repository = Arc::new(InMemoryBackgroundTaskRepository::default());
|
||||
let (execution_runtime_url, execution_runtime_handle) = start_server(execution_runtime).await;
|
||||
let state = build_state_with_execution_runtime_override(execution_runtime_url)
|
||||
.with_data_state_for_tests(
|
||||
GatewayDataState::with_provider_catalog_repository_for_tests(
|
||||
provider_catalog_repository.clone(),
|
||||
)
|
||||
.with_encryption_key_for_tests(DEVELOPMENT_ENCRYPTION_KEY)
|
||||
.with_background_task_repository_for_tests(background_task_repository.clone()),
|
||||
)
|
||||
.with_provider_oauth_token_url_for_tests(
|
||||
"claude_code_cookie_base_url",
|
||||
"https://claude.example",
|
||||
)
|
||||
.with_provider_oauth_token_url_for_tests(
|
||||
"claude_code",
|
||||
"https://platform.example/v1/oauth/token",
|
||||
);
|
||||
|
||||
let response = local_admin_provider_oauth_response(
|
||||
&state,
|
||||
http::Method::POST,
|
||||
"/api/admin/provider-oauth/providers/provider-claude/cookie-authorize/tasks",
|
||||
Some(json!({
|
||||
"cookies": [
|
||||
"sessionKey=batch-sid-one",
|
||||
"foo=bar",
|
||||
"Cookie: sessionKey=batch-sid-one",
|
||||
"sessionKey=batch-sid-two"
|
||||
]
|
||||
})),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let submitted: serde_json::Value = serde_json::from_slice(
|
||||
&to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("submitted body should read"),
|
||||
)
|
||||
.expect("submitted body should parse");
|
||||
assert_eq!(submitted["status"], "submitted");
|
||||
assert_eq!(submitted["total"], 4);
|
||||
assert_eq!(submitted["import_kind"], "cookie_authorize");
|
||||
let task_id = submitted["task_id"]
|
||||
.as_str()
|
||||
.expect("task id should exist")
|
||||
.to_string();
|
||||
assert!(task_id.starts_with("claude-cookie-"));
|
||||
|
||||
let mut status_payload = Value::Null;
|
||||
for _ in 0..80 {
|
||||
let response = local_admin_provider_oauth_response(
|
||||
&state,
|
||||
http::Method::GET,
|
||||
format!(
|
||||
"/api/admin/provider-oauth/providers/provider-claude/cookie-authorize/tasks/{task_id}"
|
||||
)
|
||||
.as_str(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
status_payload = serde_json::from_slice(
|
||||
&to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("status body should read"),
|
||||
)
|
||||
.expect("status body should parse");
|
||||
if status_payload["status"] == "completed" {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_millis(25)).await;
|
||||
}
|
||||
|
||||
assert_eq!(status_payload["status"], "completed");
|
||||
assert_eq!(status_payload["import_kind"], "cookie_authorize");
|
||||
assert_eq!(status_payload["total"], 4);
|
||||
assert_eq!(status_payload["processed"], 4);
|
||||
assert_eq!(status_payload["success"], 2);
|
||||
assert_eq!(status_payload["failed"], 2);
|
||||
assert_eq!(status_payload["created_count"], 2);
|
||||
assert_eq!(status_payload["replaced_count"], 0);
|
||||
let error_samples = status_payload["error_samples"]
|
||||
.as_array()
|
||||
.expect("error samples should be an array");
|
||||
assert_eq!(error_samples.len(), 2);
|
||||
assert_eq!(error_samples[0]["index"], 1);
|
||||
assert_eq!(error_samples[1]["index"], 2);
|
||||
|
||||
let status_text = status_payload.to_string();
|
||||
for forbidden in ["sessionKey", "batch-sid-one", "batch-sid-two"] {
|
||||
assert!(!status_text.contains(forbidden), "forbidden={forbidden}");
|
||||
}
|
||||
let raw_task_state = state
|
||||
.load_provider_oauth_batch_task_for_tests(
|
||||
format!("provider_oauth_batch_task:{task_id}").as_str(),
|
||||
)
|
||||
.expect("raw task state should exist");
|
||||
for forbidden in ["sessionKey", "batch-sid-one", "batch-sid-two"] {
|
||||
assert!(
|
||||
!raw_task_state.contains(forbidden),
|
||||
"raw task state contains {forbidden}"
|
||||
);
|
||||
}
|
||||
let background_run = background_task_repository
|
||||
.find_run(&task_id)
|
||||
.await
|
||||
.expect("background run should load")
|
||||
.expect("background run should exist");
|
||||
let background_text = serde_json::to_string(&background_run)
|
||||
.expect("background run should serialize for assertion");
|
||||
for forbidden in ["sessionKey", "batch-sid-one", "batch-sid-two"] {
|
||||
assert!(
|
||||
!background_text.contains(forbidden),
|
||||
"forbidden={forbidden}"
|
||||
);
|
||||
}
|
||||
|
||||
let keys = provider_catalog_repository
|
||||
.list_keys_by_provider_ids(&["provider-claude".to_string()])
|
||||
.await
|
||||
.expect("keys should load");
|
||||
assert_eq!(keys.len(), 2);
|
||||
for key in &keys {
|
||||
let auth_config = decrypt_python_fernet_ciphertext(
|
||||
DEVELOPMENT_ENCRYPTION_KEY,
|
||||
key.encrypted_auth_config
|
||||
.as_deref()
|
||||
.expect("auth config should be encrypted"),
|
||||
)
|
||||
.expect("auth config should decrypt");
|
||||
assert!(!auth_config.contains("batch-sid"));
|
||||
assert!(!auth_config.contains("sessionKey"));
|
||||
}
|
||||
|
||||
let plans = execution_plans.lock().expect("mutex should lock");
|
||||
assert_eq!(plans.len(), 6);
|
||||
assert_eq!(
|
||||
plans
|
||||
.iter()
|
||||
.filter(|plan| plan.request_id == "provider-oauth:claude-cookie-organizations")
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
plans
|
||||
.iter()
|
||||
.filter(|plan| plan.request_id == "provider-oauth:claude-cookie-authorize")
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
assert_eq!(
|
||||
plans
|
||||
.iter()
|
||||
.filter(|plan| plan.request_id == "provider-oauth:exchange-code")
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
assert!(plans.iter().all(|plan| {
|
||||
plan.proxy.as_ref().and_then(|proxy| proxy.mode.as_deref()) == Some("tunnel")
|
||||
}));
|
||||
|
||||
execution_runtime_handle.abort();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gateway_handles_admin_provider_oauth_device_authorize_for_windsurf_browser() {
|
||||
run_admin_oauth_test(
|
||||
@@ -7955,6 +8429,7 @@ async fn gateway_handles_admin_provider_oauth_unavailable_routes_locally_with_tr
|
||||
let client = reqwest::Client::new();
|
||||
for path in [
|
||||
"/api/admin/provider-oauth/providers/provider-123/import-refresh-token",
|
||||
"/api/admin/provider-oauth/providers/provider-123/cookie-authorize/tasks",
|
||||
"/api/admin/provider-oauth/providers/provider-123/batch-import",
|
||||
"/api/admin/provider-oauth/providers/provider-123/batch-import/tasks",
|
||||
"/api/admin/provider-oauth/providers/provider-123/device-authorize",
|
||||
|
||||
@@ -279,11 +279,24 @@ async fn gateway_handles_admin_pool_scheduling_presets_locally_with_trusted_admi
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let payload: serde_json::Value = response.json().await.expect("json body should parse");
|
||||
let items = payload.as_array().expect("payload should be an array");
|
||||
assert_eq!(items.len(), 14);
|
||||
assert_eq!(items[0]["name"], "lru");
|
||||
assert_eq!(items[1]["name"], "cache_affinity");
|
||||
assert_eq!(items[8]["name"], "pro_first");
|
||||
assert_eq!(items[13]["name"], "team_first");
|
||||
assert_eq!(items.len(), 15);
|
||||
let preset_names = items
|
||||
.iter()
|
||||
.filter_map(|item| item["name"].as_str())
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(preset_names.first(), Some(&"lru"));
|
||||
assert_eq!(preset_names.get(1), Some(&"cache_affinity"));
|
||||
assert_eq!(preset_names.last(), Some(&"team_first"));
|
||||
|
||||
let preset_index = |name| {
|
||||
preset_names
|
||||
.iter()
|
||||
.position(|preset_name| *preset_name == name)
|
||||
.unwrap_or_else(|| panic!("missing scheduling preset {name}"))
|
||||
};
|
||||
assert!(preset_index("cost_first") < preset_index("free_team_first"));
|
||||
assert!(preset_index("free_team_first") < preset_index("free_first"));
|
||||
assert!(preset_index("pro_first") < preset_index("team_first"));
|
||||
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
||||
|
||||
gateway_handle.abort();
|
||||
|
||||
Reference in New Issue
Block a user