fix: correct API key expiry and dashboard savings (#361)

This commit is contained in:
Entropy.Xu
2026-04-30 00:35:32 +08:00
committed by GitHub
parent 9e2faa7e5b
commit 4d59d518d1
11 changed files with 228 additions and 25 deletions

View File

@@ -5839,7 +5839,13 @@ async fn gateway_handles_users_me_api_keys_locally_without_proxying_upstream() {
1.5,
false,
)
.expect("export record should build")]),
.expect("export record should build")
.with_activity_timestamps(
Some(1_711_000_102),
Some(1_711_000_100),
Some(1_711_000_101),
)
.expect("export activity timestamps should build")]),
);
let user_repository = Arc::new(InMemoryUserReadRepository::seed_auth_users(vec![user]));
@@ -5881,6 +5887,8 @@ async fn gateway_handles_users_me_api_keys_locally_without_proxying_upstream() {
assert_eq!(api_keys[0]["key_display"], "sk-user-li...ve-1");
assert_eq!(api_keys[0]["total_requests"], 9);
assert_eq!(api_keys[0]["total_cost_usd"], 1.5);
assert_eq!(api_keys[0]["created_at"], "2024-03-21T05:48:20+00:00");
assert_eq!(api_keys[0]["last_used_at"], "2024-03-21T05:48:22+00:00");
let detail_response = client
.get(format!(
@@ -6212,6 +6220,11 @@ async fn gateway_handles_users_me_api_key_writes_locally_without_proxying_upstre
assert_eq!(create_payload["rate_limit"], 120);
assert_eq!(create_payload["concurrent_limit"], serde_json::Value::Null);
assert_eq!(create_payload["message"], "API密钥创建成功");
let created_at = create_payload["created_at"]
.as_str()
.expect("created_at should be string");
assert!(chrono::DateTime::parse_from_rfc3339(created_at).is_ok());
assert!(!created_at.starts_with("1970-01-01"));
assert!(create_payload["key"]
.as_str()
.unwrap_or_default()
@@ -6317,6 +6330,7 @@ async fn gateway_handles_users_me_api_key_writes_locally_without_proxying_upstre
);
assert_eq!(detail_payload["concurrent_limit"], 4);
assert_eq!(detail_payload["force_capabilities"], json!({}));
assert_eq!(detail_payload["created_at"], created_at);
let delete_response = client
.delete(format!("{gateway_url}/api/users/me/api-keys/{created_id}"))

View File

@@ -173,6 +173,93 @@ async fn gateway_handles_dashboard_stats_locally_without_proxying_upstream() {
upstream_handle.abort();
}
#[tokio::test]
async fn gateway_dashboard_stats_include_end_of_day_boundary() {
let now = stable_dashboard_now();
let user = sample_auth_user(now);
let access_token = build_test_auth_token(
"access",
serde_json::Map::from_iter([
("user_id".to_string(), json!(user.id)),
("role".to_string(), json!(user.role)),
(
"created_at".to_string(),
json!(user.created_at.map(|value| value.to_rfc3339())),
),
("session_id".to_string(), json!("session-dashboard-day-end")),
]),
chrono::Utc::now() + chrono::Duration::hours(1),
);
let session = sample_auth_session(
"user-auth-1",
"session-dashboard-day-end",
"device-dashboard-day-end",
"refresh-dashboard-day-end",
now,
);
let day = now.date_naive();
let end_of_day = day
.and_hms_opt(23, 59, 59)
.expect("end of day should build")
.and_utc();
let next_midnight = day
.checked_add_signed(chrono::Duration::days(1))
.expect("next day should build")
.and_hms_opt(0, 0, 0)
.expect("next midnight should build")
.and_utc();
let usage_repository = Arc::new(InMemoryUsageReadRepository::seed(vec![
sample_user_usage_audit(
"usage-dashboard-day-end",
"req-dashboard-day-end",
"user-auth-1",
"gpt-5",
"openai",
"completed",
end_of_day,
),
sample_user_usage_audit(
"usage-dashboard-next-midnight",
"req-dashboard-next-midnight",
"user-auth-1",
"gpt-5",
"openai",
"completed",
next_midnight,
),
]));
let (gateway_url, upstream_hits, gateway_handle, upstream_handle) =
start_auth_gateway_with_usage_state(
user,
sample_auth_wallet("user-auth-1", now),
[session],
usage_repository,
)
.await;
let response = reqwest::Client::new()
.get(format!(
"{gateway_url}/api/dashboard/stats?start_date={day}&end_date={day}"
))
.header("authorization", format!("Bearer {access_token}"))
.header("x-client-device-id", "device-dashboard-day-end")
.header("user-agent", "AetherTest/1.0")
.send()
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::OK);
let payload: serde_json::Value = response.json().await.expect("json body should parse");
assert_eq!(payload["today"]["requests"], 1);
assert_eq!(payload["monthly_cost"], json!(1.25));
assert_eq!(payload["stats"][1]["value"], json!("1"));
assert_eq!(*upstream_hits.lock().expect("mutex should lock"), 0);
gateway_handle.abort();
upstream_handle.abort();
}
#[tokio::test]
async fn gateway_handles_admin_dashboard_stats_locally_without_proxying_upstream() {
let now = stable_dashboard_now();
@@ -232,6 +319,8 @@ async fn gateway_handles_admin_dashboard_stats_locally_without_proxying_upstream
openai_usage.cache_creation_ephemeral_5m_input_tokens = 600;
openai_usage.cache_creation_ephemeral_1h_input_tokens = 600;
openai_usage.cache_read_input_tokens = 800;
openai_usage.cache_read_cost_usd = 0.01;
openai_usage.output_price_per_1m = Some(100.0);
let mut claude_usage = sample_user_usage_audit(
"usage-dashboard-admin-2",
@@ -251,8 +340,10 @@ async fn gateway_handles_admin_dashboard_stats_locally_without_proxying_upstream
claude_usage.cache_creation_ephemeral_5m_input_tokens = 20;
claude_usage.cache_creation_ephemeral_1h_input_tokens = 30;
claude_usage.cache_read_input_tokens = 200;
claude_usage.cache_read_cost_usd = 0.005;
claude_usage.output_price_per_1m = Some(100.0);
let streaming_usage = sample_user_usage_audit(
let mut streaming_usage = sample_user_usage_audit(
"usage-dashboard-admin-3",
"req-dashboard-admin-3",
"user-auth-3",
@@ -261,6 +352,8 @@ async fn gateway_handles_admin_dashboard_stats_locally_without_proxying_upstream
"streaming",
now - chrono::Duration::minutes(1),
);
streaming_usage.cache_read_input_tokens = 0;
streaming_usage.cache_read_cost_usd = 0.0;
let usage_repository = Arc::new(InMemoryUsageReadRepository::seed(vec![
openai_usage,
@@ -407,6 +500,8 @@ async fn gateway_handles_admin_dashboard_stats_locally_without_proxying_upstream
assert_eq!(payload["today"]["requests"], 2);
assert_eq!(payload["today"]["tokens"], 17_450);
assert_eq!(payload["today"]["cost"], json!(2.5));
assert_eq!(payload["cost_stats"]["cost_savings"], json!(0.085));
assert_eq!(payload["stats"][2]["subValue"], json!("节省 $0.09"));
assert_eq!(payload["stats"][0]["value"], json!("2"));
assert_eq!(payload["stats"][1]["value"], json!("17.4K"));
assert_eq!(