mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
fix: 修复优先级管理重新打开顺序回退
优先级管理弹窗依赖 grouped-by-format 接口回显格式优先级,但该接口此前读取 summary key 行。summary 查询会清空 global_priority_by_format 和 internal_priority 等路由字段,导致保存后的数据库顺序存在,重新打开页面却回退为前端占位顺序。 改为使用完整 key 查询,并增加 summary 字段被清空时仍能回显真实优先级的回归测试。 Fixes #505 Constraint: grouped-by-format 是优先级管理弹窗的数据源,必须返回真实 per-format priority 字段。 Rejected: 修改前端继续猜测顺序 | 无法区分真实数据库优先级与占位回退。 Confidence: high Scope-risk: narrow Tested: cargo fmt --check; git diff --check Not-tested: cargo test on local Windows blocked by missing NASM for boring-sys2
This commit is contained in:
@@ -46,7 +46,7 @@ pub(crate) async fn build_admin_keys_grouped_by_format_payload(
|
|||||||
|
|
||||||
let (endpoints_result, keys_result) = tokio::join!(
|
let (endpoints_result, keys_result) = tokio::join!(
|
||||||
state.list_provider_catalog_endpoints_by_provider_ids(&provider_ids),
|
state.list_provider_catalog_endpoints_by_provider_ids(&provider_ids),
|
||||||
state.list_provider_catalog_key_summaries_by_provider_ids(&provider_ids),
|
state.list_provider_catalog_keys_by_provider_ids(&provider_ids),
|
||||||
);
|
);
|
||||||
|
|
||||||
let active_endpoints = endpoints_result
|
let active_endpoints = endpoints_result
|
||||||
|
|||||||
@@ -5,7 +5,12 @@ use aether_crypto::{
|
|||||||
decrypt_python_fernet_ciphertext, encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY,
|
decrypt_python_fernet_ciphertext, encrypt_python_fernet_plaintext, DEVELOPMENT_ENCRYPTION_KEY,
|
||||||
};
|
};
|
||||||
use aether_data::repository::provider_catalog::InMemoryProviderCatalogReadRepository;
|
use aether_data::repository::provider_catalog::InMemoryProviderCatalogReadRepository;
|
||||||
use aether_data_contracts::repository::provider_catalog::ProviderCatalogReadRepository;
|
use aether_data_contracts::repository::provider_catalog::{
|
||||||
|
ProviderCatalogKeyListQuery, ProviderCatalogReadRepository, StoredProviderCatalogEndpoint,
|
||||||
|
StoredProviderCatalogKey, StoredProviderCatalogKeyPage, StoredProviderCatalogKeyStats,
|
||||||
|
StoredProviderCatalogProvider,
|
||||||
|
};
|
||||||
|
use aether_data_contracts::DataLayerError;
|
||||||
use axum::body::Body;
|
use axum::body::Body;
|
||||||
use axum::routing::any;
|
use axum::routing::any;
|
||||||
use axum::{extract::Request, Json, Router};
|
use axum::{extract::Request, Json, Router};
|
||||||
@@ -22,6 +27,103 @@ use crate::constants::{
|
|||||||
};
|
};
|
||||||
use crate::data::GatewayDataState;
|
use crate::data::GatewayDataState;
|
||||||
|
|
||||||
|
struct SummaryNullingProviderCatalogReadRepository {
|
||||||
|
inner: InMemoryProviderCatalogReadRepository,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SummaryNullingProviderCatalogReadRepository {
|
||||||
|
fn seed(
|
||||||
|
providers: Vec<StoredProviderCatalogProvider>,
|
||||||
|
endpoints: Vec<StoredProviderCatalogEndpoint>,
|
||||||
|
keys: Vec<StoredProviderCatalogKey>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: InMemoryProviderCatalogReadRepository::seed(providers, endpoints, keys),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ProviderCatalogReadRepository for SummaryNullingProviderCatalogReadRepository {
|
||||||
|
async fn list_providers(
|
||||||
|
&self,
|
||||||
|
active_only: bool,
|
||||||
|
) -> Result<Vec<StoredProviderCatalogProvider>, DataLayerError> {
|
||||||
|
self.inner.list_providers(active_only).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_providers_by_ids(
|
||||||
|
&self,
|
||||||
|
provider_ids: &[String],
|
||||||
|
) -> Result<Vec<StoredProviderCatalogProvider>, DataLayerError> {
|
||||||
|
self.inner.list_providers_by_ids(provider_ids).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_endpoints_by_ids(
|
||||||
|
&self,
|
||||||
|
endpoint_ids: &[String],
|
||||||
|
) -> Result<Vec<StoredProviderCatalogEndpoint>, DataLayerError> {
|
||||||
|
self.inner.list_endpoints_by_ids(endpoint_ids).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_endpoints_by_provider_ids(
|
||||||
|
&self,
|
||||||
|
provider_ids: &[String],
|
||||||
|
) -> Result<Vec<StoredProviderCatalogEndpoint>, DataLayerError> {
|
||||||
|
self.inner
|
||||||
|
.list_endpoints_by_provider_ids(provider_ids)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_keys_by_ids(
|
||||||
|
&self,
|
||||||
|
key_ids: &[String],
|
||||||
|
) -> Result<Vec<StoredProviderCatalogKey>, DataLayerError> {
|
||||||
|
self.inner.list_keys_by_ids(key_ids).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_keys_by_provider_ids(
|
||||||
|
&self,
|
||||||
|
provider_ids: &[String],
|
||||||
|
) -> Result<Vec<StoredProviderCatalogKey>, DataLayerError> {
|
||||||
|
self.inner.list_keys_by_provider_ids(provider_ids).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_key_summaries_by_provider_ids(
|
||||||
|
&self,
|
||||||
|
provider_ids: &[String],
|
||||||
|
) -> Result<Vec<StoredProviderCatalogKey>, DataLayerError> {
|
||||||
|
let mut keys = self.inner.list_keys_by_provider_ids(provider_ids).await?;
|
||||||
|
for key in &mut keys {
|
||||||
|
key.internal_priority = 50;
|
||||||
|
key.global_priority_by_format = None;
|
||||||
|
key.rate_multipliers = None;
|
||||||
|
key.request_count = None;
|
||||||
|
key.success_count = None;
|
||||||
|
key.error_count = None;
|
||||||
|
key.total_response_time_ms = None;
|
||||||
|
key.circuit_breaker_by_format = None;
|
||||||
|
}
|
||||||
|
Ok(keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_keys_page(
|
||||||
|
&self,
|
||||||
|
query: &ProviderCatalogKeyListQuery,
|
||||||
|
) -> Result<StoredProviderCatalogKeyPage, DataLayerError> {
|
||||||
|
self.inner.list_keys_page(query).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn list_key_stats_by_provider_ids(
|
||||||
|
&self,
|
||||||
|
provider_ids: &[String],
|
||||||
|
) -> Result<Vec<StoredProviderCatalogKeyStats>, DataLayerError> {
|
||||||
|
self.inner
|
||||||
|
.list_key_stats_by_provider_ids(provider_ids)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn gateway_handles_admin_provider_keys_locally_with_trusted_admin_principal() {
|
async fn gateway_handles_admin_provider_keys_locally_with_trusted_admin_principal() {
|
||||||
let upstream_hits = Arc::new(Mutex::new(0usize));
|
let upstream_hits = Arc::new(Mutex::new(0usize));
|
||||||
@@ -2195,7 +2297,7 @@ async fn gateway_handles_admin_keys_grouped_by_format_locally_with_trusted_admin
|
|||||||
key_b.created_at_unix_ms = Some(1_711_100_000);
|
key_b.created_at_unix_ms = Some(1_711_100_000);
|
||||||
key_b.updated_at_unix_secs = Some(1_711_100_100);
|
key_b.updated_at_unix_secs = Some(1_711_100_100);
|
||||||
|
|
||||||
let provider_catalog_repository = Arc::new(InMemoryProviderCatalogReadRepository::seed(
|
let provider_catalog_repository = Arc::new(SummaryNullingProviderCatalogReadRepository::seed(
|
||||||
vec![
|
vec![
|
||||||
sample_provider("provider-openai", "openai", 10),
|
sample_provider("provider-openai", "openai", 10),
|
||||||
sample_provider("provider-claude", "claude", 20)
|
sample_provider("provider-claude", "claude", 20)
|
||||||
@@ -2252,6 +2354,12 @@ async fn gateway_handles_admin_keys_grouped_by_format_locally_with_trusted_admin
|
|||||||
"https://api.openai.example"
|
"https://api.openai.example"
|
||||||
);
|
);
|
||||||
assert_eq!(payload["openai:chat"][0]["capabilities"], json!(["1h缓存"]));
|
assert_eq!(payload["openai:chat"][0]["capabilities"], json!(["1h缓存"]));
|
||||||
|
assert_eq!(payload["openai:chat"][0]["format_priority"], 3);
|
||||||
|
assert_eq!(
|
||||||
|
payload["openai:chat"][0]["global_priority_by_format"]["openai:chat"],
|
||||||
|
3
|
||||||
|
);
|
||||||
|
assert_eq!(payload["openai:chat"][0]["internal_priority"], 10);
|
||||||
assert_eq!(payload["claude:messages"][0]["provider_active"], false);
|
assert_eq!(payload["claude:messages"][0]["provider_active"], false);
|
||||||
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user