Align runtime miss tests with skipped candidates

This commit is contained in:
fawney19
2026-04-27 18:09:56 +08:00
parent 17f09fc8c1
commit 4c16b11cb4
4 changed files with 41 additions and 21 deletions

View File

@@ -1610,6 +1610,7 @@ fn local_execution_runtime_miss_skip_reason_label(reason: &str) -> &str {
"auth_snapshot_missing" => "API Key 本地执行配置缺失", "auth_snapshot_missing" => "API Key 本地执行配置缺失",
"endpoint_api_format_changed" => "端点 API 格式已变更", "endpoint_api_format_changed" => "端点 API 格式已变更",
"endpoint_inactive" => "端点未启用", "endpoint_inactive" => "端点未启用",
"format_conversion_disabled" => "格式转换未启用",
"key_api_format_disabled" => "API Key 未启用该 API 格式", "key_api_format_disabled" => "API Key 未启用该 API 格式",
"key_inactive" => "API Key 未启用", "key_inactive" => "API Key 未启用",
"key_model_disabled" => "API Key 未允许该模型", "key_model_disabled" => "API Key 未允许该模型",

View File

@@ -952,20 +952,25 @@ async fn gateway_marks_claude_cli_cross_format_runtime_miss_when_format_conversi
.headers() .headers()
.get(LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER) .get(LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER)
.and_then(|value| value.to_str().ok()), .and_then(|value| value.to_str().ok()),
Some("candidate_list_empty") Some("all_candidates_skipped")
); );
let response_json: serde_json::Value = response.json().await.expect("body should parse"); let response_json: serde_json::Value = response.json().await.expect("body should parse");
assert_eq!(response_json["error"]["type"], "http_error"); assert_eq!(response_json["error"]["type"], "http_error");
assert_eq!( assert_eq!(
response_json["error"]["message"], response_json["error"]["message"],
"没有可用提供商支持模型 gpt-5.4 的同步请求" "找到 1 个支持模型 gpt-5.4 的候选提供商,但本次同步请求全部不可用:格式转换未启用 2 次"
); );
let stored_candidates = request_candidate_repository let stored_candidates = request_candidate_repository
.list_by_request_id("trace-claude-cli-openai-local-miss-123") .list_by_request_id("trace-claude-cli-openai-local-miss-123")
.await .await
.expect("request candidate trace should read"); .expect("request candidate trace should read");
assert!(stored_candidates.is_empty()); assert_eq!(stored_candidates.len(), 1);
assert_eq!(stored_candidates[0].status, RequestCandidateStatus::Skipped);
assert_eq!(
stored_candidates[0].skip_reason.as_deref(),
Some("format_conversion_disabled")
);
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0); assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
gateway_handle.abort(); gateway_handle.abort();

View File

@@ -370,14 +370,18 @@ fn scheduler_candidate_runtime_paths_depend_on_scheduler_core_and_state_trait()
} }
let affinity = read_workspace_file("apps/aether-gateway/src/scheduler/candidate/affinity.rs"); let affinity = read_workspace_file("apps/aether-gateway/src/scheduler/candidate/affinity.rs");
let ranking = read_workspace_file("apps/aether-gateway/src/scheduler/candidate/ranking.rs");
let resolution =
read_workspace_file("apps/aether-gateway/src/scheduler/candidate/resolution.rs");
let candidate_runtime_path_source = format!("{affinity}\n{ranking}\n{resolution}");
assert!( assert!(
affinity.contains("SchedulerRuntimeState"), affinity.contains("SchedulerRuntimeState"),
"candidate/affinity.rs should depend on SchedulerRuntimeState" "candidate/affinity.rs should depend on SchedulerRuntimeState"
); );
assert!( assert!(
affinity.contains("aether_scheduler_core::{") candidate_runtime_path_source.contains("aether_scheduler_core::{")
&& affinity.contains("SchedulerAffinityTarget"), && candidate_runtime_path_source.contains("SchedulerAffinityTarget"),
"candidate/affinity.rs should depend on core SchedulerAffinityTarget" "scheduler candidate runtime path should depend on core SchedulerAffinityTarget"
); );
for pattern in [ for pattern in [
"candidate_affinity_hash", "candidate_affinity_hash",
@@ -385,21 +389,21 @@ fn scheduler_candidate_runtime_paths_depend_on_scheduler_core_and_state_trait()
"candidate_key", "candidate_key",
] { ] {
assert!( assert!(
affinity.contains(pattern), candidate_runtime_path_source.contains(pattern),
"candidate/affinity.rs should depend on core affinity helper {pattern}" "scheduler candidate runtime path should depend on core affinity helper {pattern}"
); );
} }
assert!( assert!(
!affinity.contains("use crate::AppState"), !candidate_runtime_path_source.contains("use crate::AppState"),
"candidate/affinity.rs should not depend on AppState directly" "scheduler candidate runtime path should not depend on AppState directly"
); );
assert!( assert!(
!affinity.contains("crate::cache::SchedulerAffinityTarget"), !candidate_runtime_path_source.contains("crate::cache::SchedulerAffinityTarget"),
"candidate/affinity.rs should not depend on gateway-local SchedulerAffinityTarget" "scheduler candidate runtime path should not depend on gateway-local SchedulerAffinityTarget"
); );
assert!( assert!(
!affinity.contains("use sha2::{Digest, Sha256};"), !candidate_runtime_path_source.contains("use sha2::{Digest, Sha256};"),
"candidate/affinity.rs should not own affinity hashing implementation anymore" "scheduler candidate runtime path should not own affinity hashing implementation anymore"
); );
for pattern in [ for pattern in [
"fn compare_affinity_order(", "fn compare_affinity_order(",

View File

@@ -1570,13 +1570,13 @@ async fn gateway_records_failed_usage_when_all_local_claude_cli_candidates_are_s
.headers() .headers()
.get(LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER) .get(LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER)
.and_then(|value| value.to_str().ok()), .and_then(|value| value.to_str().ok()),
Some("candidate_list_empty") Some("all_candidates_skipped")
); );
let body_json: serde_json::Value = response.json().await.expect("body should parse"); let body_json: serde_json::Value = response.json().await.expect("body should parse");
assert_eq!(body_json["error"]["type"], "http_error"); assert_eq!(body_json["error"]["type"], "http_error");
assert_eq!( assert_eq!(
body_json["error"]["message"], body_json["error"]["message"],
"没有可用提供商支持模型 gpt-5.4 的同步请求" "找到 1 个支持模型 gpt-5.4 的候选提供商,但本次同步请求全部不可用:格式转换未启用 2 次"
); );
let stored_usage = wait_for_usage_status( let stored_usage = wait_for_usage_status(
@@ -1610,12 +1610,12 @@ async fn gateway_records_failed_usage_when_all_local_claude_cli_candidates_are_s
); );
assert_eq!( assert_eq!(
stored_usage.routing_local_execution_runtime_miss_reason(), stored_usage.routing_local_execution_runtime_miss_reason(),
Some("candidate_list_empty") Some("all_candidates_skipped")
); );
assert_eq!( assert_eq!(
stored_usage.error_message.as_deref(), stored_usage.error_message.as_deref(),
Some( Some(
"没有可用提供商支持模型 gpt-5.4 的同步请求。请检查模型映射、端点启用状态和 API Key 权限(原因代码: candidate_list_empty" "找到 1 个支持模型 gpt-5.4 的候选提供商,但本次同步请求全部不可用:格式转换未启用 2 次(原因代码: all_candidates_skipped"
) )
); );
assert_eq!( assert_eq!(
@@ -1656,7 +1656,12 @@ async fn gateway_records_failed_usage_when_all_local_claude_cli_candidates_are_s
.list_by_request_id("trace-claude-cli-usage-local-miss-123") .list_by_request_id("trace-claude-cli-usage-local-miss-123")
.await .await
.expect("request candidate trace should read"); .expect("request candidate trace should read");
assert!(stored_candidates.is_empty()); assert_eq!(stored_candidates.len(), 1);
assert_eq!(stored_candidates[0].status, RequestCandidateStatus::Skipped);
assert_eq!(
stored_candidates[0].skip_reason.as_deref(),
Some("format_conversion_disabled")
);
assert_eq!(stored_usage.routing_candidate_id(), None); assert_eq!(stored_usage.routing_candidate_id(), None);
assert_eq!(*public_hits.lock().expect("mutex should lock"), 0); assert_eq!(*public_hits.lock().expect("mutex should lock"), 0);
@@ -1878,7 +1883,7 @@ async fn gateway_keeps_failed_usage_request_capture_lightweight_for_large_local_
.headers() .headers()
.get(LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER) .get(LOCAL_EXECUTION_RUNTIME_MISS_REASON_HEADER)
.and_then(|value| value.to_str().ok()), .and_then(|value| value.to_str().ok()),
Some("candidate_list_empty") Some("all_candidates_skipped")
); );
let stored_usage = wait_for_usage_status( let stored_usage = wait_for_usage_status(
@@ -1914,7 +1919,12 @@ async fn gateway_keeps_failed_usage_request_capture_lightweight_for_large_local_
.list_by_request_id("trace-claude-cli-usage-local-miss-large-123") .list_by_request_id("trace-claude-cli-usage-local-miss-large-123")
.await .await
.expect("request candidate trace should read"); .expect("request candidate trace should read");
assert!(stored_candidates.is_empty()); assert_eq!(stored_candidates.len(), 1);
assert_eq!(stored_candidates[0].status, RequestCandidateStatus::Skipped);
assert_eq!(
stored_candidates[0].skip_reason.as_deref(),
Some("format_conversion_disabled")
);
gateway_handle.abort(); gateway_handle.abort();
execution_runtime_handle.abort(); execution_runtime_handle.abort();