use chrono::{TimeZone, Utc};
use serde_json::json;
use sqlx::Row;
use super::{
attach_compressed_body_refs, attach_usage_http_audit_body_refs,
attach_usage_routing_snapshot_metadata, attach_usage_settlement_pricing_snapshot_metadata,
clear_previous_request_body_facts, inflate_usage_json_value,
prepare_request_metadata_for_body_storage, prepare_usage_body_storage,
prepare_usage_upsert_context, request_body_capture_replaces_derived_facts,
resolved_read_usage_body_ref, resolved_write_usage_body_ref,
split_dashboard_daily_aggregate_range, split_dashboard_hourly_aggregate_range,
usage_body_capture_state_for_storage, usage_body_ref, usage_capture_update_allowed,
usage_effective_input_tokens, usage_http_audit_body_refs, usage_http_audit_capture_mode,
usage_routing_snapshot_from_usage, usage_settlement_pricing_snapshot_from_usage,
usage_total_input_context, AggregateRangeSplit, SqlxUsageReadRepository, UsageHttpAuditRefs,
UsageRoutingSnapshot, UsageSettlementPricingSnapshot, MAX_INLINE_USAGE_BODY_BYTES,
};
use crate::{PostgresPoolConfig, PostgresPoolFactory};
use aether_data_contracts::repository::usage::{
UpsertUsageRecord, UsageAuditListQuery, UsageBodyCaptureState, UsageBodyField,
UsageCostSavingsSummaryQuery, UsageDashboardDailyBreakdownQuery, UsageDashboardSummaryQuery,
UsageProviderPerformanceQuery, UsageTimeSeriesGranularity,
};
fn fast_clear_usage_record(
request_id: &str,
provider_name: &str,
now_unix_secs: u64,
terminal: bool,
terminal_state: UsageBodyCaptureState,
terminal_service_tier: Option<&str>,
) -> UpsertUsageRecord {
UpsertUsageRecord {
request_id: request_id.to_string(),
user_id: None,
api_key_id: None,
username: None,
api_key_name: None,
provider_name: provider_name.to_string(),
model: "gpt-5".to_string(),
target_model: Some("gpt-5".to_string()),
provider_id: None,
provider_endpoint_id: None,
provider_api_key_id: None,
request_type: Some("chat".to_string()),
api_format: Some("openai:chat".to_string()),
api_family: Some("openai".to_string()),
endpoint_kind: Some("chat".to_string()),
endpoint_api_format: Some("openai:chat".to_string()),
provider_api_family: Some("openai".to_string()),
provider_endpoint_kind: Some("chat".to_string()),
has_format_conversion: Some(false),
is_stream: Some(false),
input_tokens: terminal.then_some(1),
output_tokens: terminal.then_some(1),
total_tokens: terminal.then_some(2),
cache_creation_input_tokens: None,
cache_creation_ephemeral_5m_input_tokens: None,
cache_creation_ephemeral_1h_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: terminal.then_some(200),
error_message: None,
error_category: None,
response_time_ms: terminal.then_some(10),
first_byte_time_ms: None,
status: if terminal { "completed" } else { "pending" }.to_string(),
billing_status: "pending".to_string(),
request_headers: None,
request_body: None,
request_body_ref: None,
request_body_state: None,
provider_request_headers: None,
provider_request_body: (!terminal).then(|| {
json!({
"model": "gpt-5",
"service_tier": "priority"
})
}),
provider_request_body_ref: None,
provider_request_body_state: Some(if terminal {
terminal_state
} else {
UsageBodyCaptureState::Inline
}),
response_headers: None,
response_body: None,
response_body_ref: None,
response_body_state: None,
client_response_headers: None,
client_response_body: None,
client_response_body_ref: None,
client_response_body_state: None,
candidate_id: None,
candidate_index: None,
key_name: None,
planner_kind: None,
route_family: None,
route_kind: None,
execution_path: None,
local_execution_runtime_miss_reason: None,
request_metadata: if terminal {
terminal_service_tier.map(|tier| json!({"provider_service_tier": tier}))
} else {
Some(json!({"provider_service_tier": "priority"}))
},
finalized_at_unix_secs: terminal.then_some(now_unix_secs + 1),
created_at_unix_ms: Some(now_unix_secs),
updated_at_unix_secs: now_unix_secs + u64::from(terminal),
}
}
#[tokio::test]
#[ignore = "requires AETHER_TEST_DATABASE_URL and PostgreSQL migrations"]
async fn live_terminal_none_capture_clears_fast_from_detail_and_lightweight_lists() {
let database_url = std::env::var("AETHER_TEST_DATABASE_URL")
.expect("AETHER_TEST_DATABASE_URL must point at the test database");
let factory = PostgresPoolFactory::new(PostgresPoolConfig {
database_url,
min_connections: 1,
max_connections: 2,
acquire_timeout_ms: 10_000,
idle_timeout_ms: 30_000,
max_lifetime_ms: 60_000,
statement_cache_capacity: 64,
require_ssl: false,
})
.expect("factory should build");
let repository =
SqlxUsageReadRepository::new(factory.connect_lazy().expect("lazy pool should build"));
crate::run_migrations(repository.pool())
.await
.expect("test database migrations should succeed");
let suffix = uuid::Uuid::new_v4().simple().to_string();
let request_id = format!("req-fast-clear-{suffix}");
let provider_name = format!("fast-clear-{suffix}");
let now_unix_secs = Utc::now().timestamp().max(0) as u64;
let mut pending_record = fast_clear_usage_record(
&request_id,
&provider_name,
now_unix_secs,
false,
UsageBodyCaptureState::Inline,
Some("priority"),
);
pending_record.candidate_id = Some("candidate-a".to_string());
pending_record.candidate_index = Some(1);
pending_record.key_name = Some("key-a".to_string());
pending_record.planner_kind = Some("planner-a".to_string());
pending_record.route_family = Some("route-family-a".to_string());
pending_record.route_kind = Some("route-kind-a".to_string());
pending_record.execution_path = Some("path-a".to_string());
pending_record.local_execution_runtime_miss_reason = Some("miss-a".to_string());
pending_record.request_metadata = Some(json!({
"trace_id": "trace-a",
"provider_service_tier": "priority",
"provider_actual_service_tier": "priority",
"billing_snapshot": {
"schema_version": "2.0",
"status": "complete",
"resolved_variables": {
"input_price_per_1m": 30.0,
"output_price_per_1m": 150.0
}
},
"settlement_snapshot": {
"schema_version": "2.0",
"pricing_snapshot": {
"pricing_source": "processing_tier",
"service_tier": "priority"
},
"billing_plan_snapshot": {
"rule_id": "fast-rule",
"rule_version": "1"
}
},
"billing_dimensions": {"service_tier": "priority"},
"rate_multiplier": 2.0,
"input_price_per_1m": 30.0,
"output_price_per_1m": 150.0
}));
let pending = repository
.upsert(pending_record)
.await
.expect("pending usage should persist");
assert_eq!(pending.provider_service_tier().as_deref(), Some("priority"));
let mut terminal_record = fast_clear_usage_record(
&request_id,
&provider_name,
now_unix_secs,
true,
UsageBodyCaptureState::None,
None,
);
terminal_record.provider_id = Some("final-provider-id".to_string());
terminal_record.provider_endpoint_id = Some("final-endpoint-id".to_string());
terminal_record.provider_api_key_id = Some("final-key-id".to_string());
terminal_record.target_model = None;
let terminal = repository
.upsert(terminal_record)
.await
.expect("terminal usage should persist");
assert_eq!(terminal.provider_service_tier(), None);
let stored = repository
.find_by_request_id(&request_id)
.await
.expect("detail lookup should succeed")
.expect("usage should exist");
assert_eq!(
stored.provider_request_body_state,
Some(UsageBodyCaptureState::None)
);
assert_eq!(stored.provider_service_tier(), None);
assert_eq!(stored.provider_actual_service_tier(), None);
let stored_metadata = stored
.request_metadata
.as_ref()
.and_then(serde_json::Value::as_object)
.expect("terminal audit metadata should remain");
assert_eq!(
stored_metadata
.get("trace_id")
.and_then(serde_json::Value::as_str),
Some("trace-a")
);
for stale_key in [
"provider_actual_service_tier",
"billing_snapshot",
"settlement_snapshot",
"billing_dimensions",
"rate_multiplier",
"input_price_per_1m",
"output_price_per_1m",
"candidate_id",
] {
assert!(
stored_metadata.get(stale_key).is_none(),
"terminal metadata retained stale key {stale_key}"
);
}
assert_eq!(stored.settlement_rate_multiplier(), None);
assert_eq!(stored.settlement_input_price_per_1m(), None);
assert_eq!(stored.settlement_output_price_per_1m(), None);
let settlement_row = sqlx::query(
"SELECT settlement_snapshot, billing_dimensions, CAST(rate_multiplier AS DOUBLE PRECISION) AS rate_multiplier, CAST(input_price_per_1m AS DOUBLE PRECISION) AS input_price_per_1m, CAST(output_price_per_1m AS DOUBLE PRECISION) AS output_price_per_1m FROM usage_settlement_snapshots WHERE request_id = $1",
)
.bind(&request_id)
.fetch_one(repository.pool())
.await
.expect("settlement snapshot row should be readable");
assert!(settlement_row
.try_get::