use pretty_assertions::assert_eq;
use rusqlite::Connection;
use super::super::SqliteUsageRecorder;
use crate::compaction_metrics::{
CompactionRecord, CompactionRunOutcome, CompactionTier, CompactionTriggerKind, RecordIdentity,
RereadStats, SummaryRequestPath,
};
fn record(next_prompt_tokens: Option<u64>, reread: Option<RereadStats>) -> CompactionRecord {
CompactionRecord {
identity: RecordIdentity {
event_id: "compaction-1".into(),
occurred_at_ms: 1_000,
session_id: Some("session".into()),
..RecordIdentity::default()
},
outcome: CompactionRunOutcome::Completed,
trigger: CompactionTriggerKind::Automatic,
tier: Some(CompactionTier::TextSummary),
request_path: Some(SummaryRequestPath::SessionHistory),
model: Some("anthropic/claude-test".into()),
elided_tool_results: 2,
context_tokens: 90_000,
prompt_tokens: Some(88_000),
output_tokens: Some(1_200),
cache_read_tokens: Some(80_000),
cost_usd_micros: Some(4_500),
latency_ms: 7_250,
next_prompt_tokens,
reread,
}
}
type Row = (
String,
String,
Option<String>,
Option<String>,
i64,
Option<i64>,
Option<i64>,
i64,
Option<i64>,
Option<i64>,
Option<i64>,
);
fn rows(recorder: &SqliteUsageRecorder) -> Vec<Row> {
let connection = Connection::open(recorder.path()).unwrap();
let mut statement = connection
.prepare(
"SELECT outcome, trigger, tier, request_path, context_tokens, output_tokens,
cache_read_tokens, latency_ms, next_prompt_tokens, reread_tool_calls,
reread_repeated
FROM compaction_events",
)
.unwrap();
statement
.query_map([], |row| {
Ok((
row.get(0)?,
row.get(1)?,
row.get(2)?,
row.get(3)?,
row.get(4)?,
row.get(5)?,
row.get(6)?,
row.get(7)?,
row.get(8)?,
row.get(9)?,
row.get(10)?,
))
})
.unwrap()
.collect::<Result<_, _>>()
.unwrap()
}
#[test]
fn follow_up_write_fills_the_row_in_either_order() {
let reread = RereadStats {
window: 24,
tool_calls: 24,
repeated: 3,
tracked: 5,
};
let initial = record(None, None);
let finished = record(Some(21_000), Some(reread));
let expected = vec![(
"completed".to_string(),
"automatic".to_string(),
Some("text_summary".to_string()),
Some("session_history".to_string()),
90_000,
Some(1_200),
Some(80_000),
7_250,
Some(21_000),
Some(24),
Some(3),
)];
for (case, writes) in [
("in order", [&initial, &finished]),
("reversed", [&finished, &initial]),
] {
let directory = tempfile::tempdir().unwrap();
let recorder = SqliteUsageRecorder::new(directory.path().join("usage.sqlite3")).unwrap();
for write in writes {
recorder.record_compaction(write).unwrap();
}
assert_eq!(rows(&recorder), expected, "{case}");
}
}
#[test]
fn v1_ledger_upgrades_and_keeps_usage_rows() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("usage.sqlite3");
std::fs::copy(
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/usage-v1.sqlite3"),
&path,
)
.unwrap();
let recorder = SqliteUsageRecorder::new(&path).unwrap();
recorder.record_compaction(&record(None, None)).unwrap();
let connection = Connection::open(&path).unwrap();
let count = |table: &str| -> i64 {
connection
.query_row(&format!("SELECT count(*) FROM {table}"), [], |row| {
row.get(0)
})
.unwrap()
};
let version: i64 = connection
.pragma_query_value(None, "user_version", |row| row.get(0))
.unwrap();
assert_eq!(
(version, count("usage_events"), count("compaction_events")),
(2, 1, 1)
);
}