mirror of
https://github.com/nearai/ironclaw.git
synced 2026-09-02 23:56:24 +08:00
fix(libsql): parse scientific notation cost aggregates (#3296)
This commit is contained in:
@@ -1091,6 +1091,50 @@ mod admin_api_contracts {
|
||||
assert_rfc3339(user.last_active_at.as_deref().unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_admin_user_list_handles_tiny_costs_from_libsql() {
|
||||
let (db, _dir) = test_db().await;
|
||||
db.create_user(&test_user(
|
||||
"carol",
|
||||
"Carol",
|
||||
Some("carol@example.com"),
|
||||
"active",
|
||||
"member",
|
||||
serde_json::json!({}),
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
let job_id = db.create_system_job("carol", "test").await.unwrap();
|
||||
db.record_llm_call(&crate::history::LlmCallRecord {
|
||||
job_id: Some(job_id),
|
||||
conversation_id: None,
|
||||
provider: "test",
|
||||
model: "tiny-cost-model",
|
||||
input_tokens: 1,
|
||||
output_tokens: 1,
|
||||
cost: rust_decimal::Decimal::from_str_exact("0.000075").unwrap(),
|
||||
purpose: Some("test"),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let state = build_state(Some(db), None);
|
||||
let app = admin_router(state, two_user_auth());
|
||||
|
||||
let req = Request::builder()
|
||||
.uri("/api/admin/users")
|
||||
.header("Authorization", "Bearer tok-alice")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let resp = app.oneshot(req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let body: AdminUserListResponse = parse_json(resp).await;
|
||||
|
||||
let user = body.users.iter().find(|u| u.id == "carol").unwrap();
|
||||
assert_eq!(user.job_count, 1);
|
||||
assert_eq!(user.total_cost, "0.000075");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_admin_user_detail_response_contract() {
|
||||
let (db, _dir) = test_db().await;
|
||||
|
||||
@@ -45,6 +45,17 @@ fn row_to_api_token(row: &libsql::Row) -> Result<ApiTokenRecord, DatabaseError>
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_libsql_decimal_text(
|
||||
value: &str,
|
||||
field_name: &str,
|
||||
) -> Result<rust_decimal::Decimal, DatabaseError> {
|
||||
rust_decimal::Decimal::from_str_exact(value)
|
||||
.or_else(|_| rust_decimal::Decimal::from_scientific(value))
|
||||
.map_err(|e| {
|
||||
DatabaseError::Query(format!("invalid {} value '{}': {}", field_name, value, e))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn seed_initial_assistant_thread(
|
||||
conn: &libsql::Connection,
|
||||
user_id: &str,
|
||||
@@ -658,9 +669,7 @@ impl UserStore for LibSqlBackend {
|
||||
.map_err(|e| DatabaseError::Query(e.to_string()))?
|
||||
{
|
||||
let cost_str = get_text(&row, 5);
|
||||
let total_cost = rust_decimal::Decimal::from_str_exact(&cost_str).map_err(|e| {
|
||||
DatabaseError::Query(format!("invalid cost value '{}': {}", cost_str, e))
|
||||
})?;
|
||||
let total_cost = parse_libsql_decimal_text(&cost_str, "cost")?;
|
||||
stats.push(crate::db::UserUsageStats {
|
||||
user_id: get_text(&row, 0),
|
||||
model: get_text(&row, 1),
|
||||
@@ -836,9 +845,7 @@ impl UserStore for LibSqlBackend {
|
||||
.map_err(|e| DatabaseError::Query(e.to_string()))?
|
||||
{
|
||||
let cost_str = get_text(&row, 2);
|
||||
let total_cost = rust_decimal::Decimal::from_str_exact(&cost_str).map_err(|e| {
|
||||
DatabaseError::Query(format!("invalid cost value '{}': {}", cost_str, e))
|
||||
})?;
|
||||
let total_cost = parse_libsql_decimal_text(&cost_str, "cost")?;
|
||||
stats.push(crate::db::UserSummaryStats {
|
||||
user_id: get_text(&row, 0),
|
||||
job_count: row
|
||||
@@ -896,12 +903,7 @@ impl UserStore for LibSqlBackend {
|
||||
})?;
|
||||
|
||||
let usage_cost_str = get_text(&row, 8);
|
||||
let usage_cost = rust_decimal::Decimal::from_str_exact(&usage_cost_str).map_err(|e| {
|
||||
DatabaseError::Query(format!(
|
||||
"invalid usage_cost value '{}': {}",
|
||||
usage_cost_str, e
|
||||
))
|
||||
})?;
|
||||
let usage_cost = parse_libsql_decimal_text(&usage_cost_str, "usage_cost")?;
|
||||
|
||||
Ok(AdminUsageSummary {
|
||||
total_users: row
|
||||
|
||||
Reference in New Issue
Block a user