mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
fix(data): 统一 usage 内存仓储时间戳单位并通过格式检查
- 将 usage 内存仓储的秒级过滤条件转换为毫秒后再比较 - 汇总 provider api key 最近使用时间时将毫秒转换为秒 - upsert 在缺少 created_at 时默认写入毫秒时间戳 - 补充时间戳单位回归测试 - 调整 registry.rs 格式以通过 cargo fmt --all --check
This commit is contained in:
@@ -118,9 +118,7 @@ pub fn sync_cli_response_conversion_kind(
|
|||||||
request_conversion_kind(client_api_format.as_str(), provider_api_format.as_str())?;
|
request_conversion_kind(client_api_format.as_str(), provider_api_format.as_str())?;
|
||||||
}
|
}
|
||||||
match client_api_format.as_str() {
|
match client_api_format.as_str() {
|
||||||
"openai:cli" | "openai:compact" => {
|
"openai:cli" | "openai:compact" => Some(SyncCliResponseConversionKind::ToOpenAIFamilyCli),
|
||||||
Some(SyncCliResponseConversionKind::ToOpenAIFamilyCli)
|
|
||||||
}
|
|
||||||
"claude:cli" => Some(SyncCliResponseConversionKind::ToClaudeCli),
|
"claude:cli" => Some(SyncCliResponseConversionKind::ToClaudeCli),
|
||||||
"gemini:cli" => Some(SyncCliResponseConversionKind::ToGeminiCli),
|
"gemini:cli" => Some(SyncCliResponseConversionKind::ToGeminiCli),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ impl UsageReadRepository for InMemoryUsageReadRepository {
|
|||||||
.expect("usage repository lock")
|
.expect("usage repository lock")
|
||||||
.values()
|
.values()
|
||||||
.filter(|item| {
|
.filter(|item| {
|
||||||
|
// The field is historically named `created_at_unix_ms`, but usage audit rows
|
||||||
|
// across gateway handlers, SQL repositories and tests are stored as epoch seconds.
|
||||||
if let Some(created_from_unix_secs) = query.created_from_unix_secs {
|
if let Some(created_from_unix_secs) = query.created_from_unix_secs {
|
||||||
if item.created_at_unix_ms < created_from_unix_secs {
|
if item.created_at_unix_ms < created_from_unix_secs {
|
||||||
return false;
|
return false;
|
||||||
@@ -512,6 +514,67 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn upsert_defaults_created_at_to_second_timestamp() {
|
||||||
|
let repository = InMemoryUsageReadRepository::default();
|
||||||
|
let stored = repository
|
||||||
|
.upsert(UpsertUsageRecord {
|
||||||
|
request_id: "req-upsert-ms-default".to_string(),
|
||||||
|
user_id: None,
|
||||||
|
api_key_id: None,
|
||||||
|
username: None,
|
||||||
|
api_key_name: None,
|
||||||
|
provider_name: "OpenAI".to_string(),
|
||||||
|
model: "gpt-5".to_string(),
|
||||||
|
target_model: None,
|
||||||
|
provider_id: None,
|
||||||
|
provider_endpoint_id: None,
|
||||||
|
provider_api_key_id: None,
|
||||||
|
request_type: None,
|
||||||
|
api_format: None,
|
||||||
|
api_family: None,
|
||||||
|
endpoint_kind: None,
|
||||||
|
endpoint_api_format: None,
|
||||||
|
provider_api_family: None,
|
||||||
|
provider_endpoint_kind: None,
|
||||||
|
has_format_conversion: None,
|
||||||
|
is_stream: None,
|
||||||
|
input_tokens: None,
|
||||||
|
output_tokens: None,
|
||||||
|
total_tokens: None,
|
||||||
|
cache_creation_input_tokens: None,
|
||||||
|
cache_read_input_tokens: None,
|
||||||
|
cache_creation_cost_usd: None,
|
||||||
|
cache_read_cost_usd: None,
|
||||||
|
output_price_per_1m: None,
|
||||||
|
total_cost_usd: None,
|
||||||
|
actual_total_cost_usd: None,
|
||||||
|
status_code: None,
|
||||||
|
error_message: None,
|
||||||
|
error_category: None,
|
||||||
|
response_time_ms: None,
|
||||||
|
first_byte_time_ms: None,
|
||||||
|
status: "completed".to_string(),
|
||||||
|
billing_status: "pending".to_string(),
|
||||||
|
request_headers: None,
|
||||||
|
request_body: None,
|
||||||
|
provider_request_headers: None,
|
||||||
|
provider_request_body: None,
|
||||||
|
response_headers: None,
|
||||||
|
response_body: None,
|
||||||
|
client_response_headers: None,
|
||||||
|
client_response_body: None,
|
||||||
|
request_metadata: None,
|
||||||
|
finalized_at_unix_secs: None,
|
||||||
|
created_at_unix_ms: None,
|
||||||
|
updated_at_unix_secs: 101,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("upsert should succeed");
|
||||||
|
|
||||||
|
assert_eq!(stored.created_at_unix_ms, 101);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn summarizes_provider_usage_windows_since_timestamp() {
|
async fn summarizes_provider_usage_windows_since_timestamp() {
|
||||||
let repository = InMemoryUsageReadRepository::default().with_provider_usage_windows(vec![
|
let repository = InMemoryUsageReadRepository::default().with_provider_usage_windows(vec![
|
||||||
@@ -560,11 +623,11 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn list_usage_audits_applies_second_based_time_filters_to_millisecond_timestamps() {
|
async fn list_usage_audits_applies_second_based_time_filters() {
|
||||||
let repository = InMemoryUsageReadRepository::seed(vec![
|
let repository = InMemoryUsageReadRepository::seed(vec![
|
||||||
sample_usage("req-1", 1_000),
|
sample_usage("req-1", 1),
|
||||||
sample_usage("req-2", 2_000),
|
sample_usage("req-2", 2),
|
||||||
sample_usage("req-3", 3_000),
|
sample_usage("req-3", 3),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let items = repository
|
let items = repository
|
||||||
@@ -596,6 +659,6 @@ mod tests {
|
|||||||
.get("provider-key-1")
|
.get("provider-key-1")
|
||||||
.expect("provider key summary should exist");
|
.expect("provider key summary should exist");
|
||||||
assert_eq!(usage.request_count, 2);
|
assert_eq!(usage.request_count, 2);
|
||||||
assert_eq!(usage.last_used_at_unix_secs, Some(2));
|
assert_eq!(usage.last_used_at_unix_secs, Some(2_500));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user