Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
test(zcode): snapshot focused report output
Cover daily, monthly, and session JSON and table serialization with report headings and cache buckets. Include ZCODE in the environment diagnostic regex so ZCODE_HOME is visible.
  • Loading branch information
ryoppippi committed Aug 31, 2026
commit 5540d450da85aa2120af0da0553af1efacad4154
2 changes: 1 addition & 1 deletion docs/guide/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ To see which environment variables are being used:

```bash
# Show all environment variables
env | grep -E "CLAUDE|CODEX|OPENCODE|AMP|DROID|CODEBUFF|HERMES|PI_AGENT|GOOSE|OPENCLAW|KILO|KIMI|QWEN|COPILOT|GEMINI|GROK|CCUSAGE|LOG_LEVEL"
env | grep -E "CLAUDE|CODEX|OPENCODE|AMP|DROID|CODEBUFF|HERMES|PI_AGENT|GOOSE|OPENCLAW|KILO|KIMI|QWEN|COPILOT|GEMINI|GROK|ZCODE|CCUSAGE|LOG_LEVEL"

# Debug mode shows environment variable usage
LOG_LEVEL=4 ccusage daily --debug
Expand Down
1 change: 1 addition & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/adapters/zcode/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ sqlite.workspace = true

[dev-dependencies]
ccusage-test-support.workspace = true
insta.workspace = true
220 changes: 219 additions & 1 deletion rust/adapters/zcode/src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ fn rows_key(kind: AgentReportKind) -> &'static str {
#[cfg(test)]
mod tests {
use super::*;
use crate::{TimestampMs, TokenUsageRaw, UsageEntry, UsageMessage};
use crate::{
TimestampMs, TokenUsageRaw, UsageEntry, UsageMessage, first_column, format_currency,
format_models_multiline, format_number, format_rfc3339_millis, parse_ts_timestamp,
summary_period,
};
use std::sync::Arc;

fn entry(session_id: &str, date: &str, millis: i64) -> LoadedEntry {
Expand Down Expand Up @@ -127,4 +131,218 @@ mod tests {
assert_eq!(sessions[0].session_id.as_deref(), Some("session-1"));
assert_eq!(sessions[0].project_path.as_deref(), Some("/project"));
}

#[test]
fn snapshots_focused_zcode_cli_reports_for_daily_monthly_and_session() {
let entries = snapshot_entries();
let daily = summarize_entries(&entries, AgentReportKind::Daily).unwrap();
let monthly = summarize_entries(&entries, AgentReportKind::Monthly).unwrap();
let session = summarize_entries(&entries, AgentReportKind::Session).unwrap();

insta::assert_json_snapshot!(
"focused_zcode_daily_json",
report_from_rows(&daily, AgentReportKind::Daily)
);
insta::assert_json_snapshot!(
"focused_zcode_monthly_json",
report_from_rows(&monthly, AgentReportKind::Monthly)
);
insta::assert_json_snapshot!(
"focused_zcode_session_json",
report_from_rows(&session, AgentReportKind::Session)
);
insta::assert_snapshot!(
"focused_zcode_daily_table",
serde_json::to_string_pretty(&table_snapshot(&daily, AgentReportKind::Daily)).unwrap()
);
insta::assert_snapshot!(
"focused_zcode_monthly_table",
serde_json::to_string_pretty(&table_snapshot(&monthly, AgentReportKind::Monthly))
.unwrap()
);
insta::assert_snapshot!(
"focused_zcode_session_table",
serde_json::to_string_pretty(&table_snapshot(&session, AgentReportKind::Session))
.unwrap()
);
}

fn snapshot_entries() -> Vec<LoadedEntry> {
[
(
"usage-52",
"session-a",
"2099-01-02T00:00:00.000Z",
"GLM-5.2",
60,
10,
15,
25,
0.00015549999999999999,
"/workspace/project-a",
),
(
"usage-53",
"session-a",
"2099-01-15T12:00:00.000Z",
"GLM-5.3",
130,
20,
30,
40,
0.0003224,
"/workspace/project-a",
),
(
"usage-53-b",
"session-b",
"2099-02-01T00:00:00.000Z",
"GLM-5.3",
40,
5,
0,
10,
0.0000806,
"/workspace/project-b",
),
]
.into_iter()
.map(
|(
id,
session_id,
timestamp,
model,
input_tokens,
output_tokens,
cache_creation_tokens,
cache_read_tokens,
cost,
project_path,
)| {
let date = timestamp.get(..10).unwrap().to_string();
let timestamp = parse_ts_timestamp(timestamp).unwrap();
LoadedEntry {
data: UsageEntry {
session_id: Some(session_id.to_string()),
timestamp: format_rfc3339_millis(timestamp),
version: Some("0.16.3".to_string()),
message: UsageMessage {
usage: TokenUsageRaw {
input_tokens,
output_tokens,
cache_creation_input_tokens: cache_creation_tokens,
cache_read_input_tokens: cache_read_tokens,
speed: None,
cache_creation: None,
},
model: Some(model.to_string()),
id: Some(id.to_string()),
},
cost_usd: None,
request_id: None,
is_api_error_message: None,
is_sidechain: None,
},
timestamp,
date,
project: Arc::from("zcode"),
session_id: Arc::from(session_id),
project_path: Arc::from(project_path),
cost,
extra_total_tokens: 0,
credits: None,
message_count: None,
model: Some(model.to_string()),
usage_limit_reset_time: None,
missing_pricing_model: None,
}
},
)
.collect()
}

fn table_snapshot(rows: &[crate::UsageSummary], kind: AgentReportKind) -> Value {
let show_cache_creation = rows.iter().any(|row| row.cache_creation_tokens > 0);
let include_last_activity = rows.iter().any(|row| row.last_activity.is_some());
let mut headers = vec![first_column(kind), "Models", "Input", "Output"];
if show_cache_creation {
headers.push("Cache Create");
}
headers.extend(["Cache Read", "Total Tokens", "Cost (USD)"]);
if include_last_activity {
headers.push("Last Activity");
}

let mut rendered_rows = rows
.iter()
.map(|row| {
json!({
"kind": "row",
"cells": table_row(row, show_cache_creation, include_last_activity),
})
})
.collect::<Vec<_>>();
let totals = report_from_rows(rows, kind)["totals"].clone();
let mut total_cells = vec![
"Total".to_string(),
String::new(),
format_number(totals["inputTokens"].as_u64().unwrap()),
format_number(totals["outputTokens"].as_u64().unwrap()),
];
if show_cache_creation {
total_cells.push(format_number(
totals["cacheCreationTokens"].as_u64().unwrap(),
));
}
total_cells.extend([
format_number(totals["cacheReadTokens"].as_u64().unwrap()),
format_number(totals["totalTokens"].as_u64().unwrap()),
format_currency(totals["totalCost"].as_f64().unwrap()),
]);
if include_last_activity {
total_cells.push(String::new());
}
rendered_rows.push(json!({
"kind": "total",
"cells": total_cells,
}));

json!({
"title": "ZCode Token Usage Report",
"headers": headers,
"rows": rendered_rows,
})
}

fn table_row(
row: &crate::UsageSummary,
show_cache_creation: bool,
include_last_activity: bool,
) -> Vec<String> {
let mut cells = vec![
summary_period(row).to_string(),
format_models_multiline(&row.models_used),
format_number(row.input_tokens),
format_number(row.output_tokens),
];
if show_cache_creation {
cells.push(format_number(row.cache_creation_tokens));
}
cells.extend([
format_number(row.cache_read_tokens),
format_number(row.total_tokens()),
format_currency(row.total_cost),
]);
if include_last_activity {
cells.push(
row.last_activity
.as_deref()
.and_then(|value| value.get(..10))
.unwrap_or_default()
.to_string(),
);
}
cells
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
source: adapters/zcode/src/report.rs
expression: "report_from_rows(&daily, AgentReportKind::Daily)"
---
{
"daily": [
{
"cacheCreationTokens": 15,
"cacheReadTokens": 25,
"date": "2099-01-02",
"inputTokens": 60,
"modelBreakdowns": [
{
"cacheCreationTokens": 15,
"cacheReadTokens": 25,
"cost": 0.00015549999999999999,
"inputTokens": 60,
"modelName": "GLM-5.2",
"outputTokens": 10
}
],
"modelsUsed": [
"GLM-5.2"
],
"outputTokens": 10,
"totalCost": 0.00015549999999999999,
"totalTokens": 110
},
{
"cacheCreationTokens": 30,
"cacheReadTokens": 40,
"date": "2099-01-15",
"inputTokens": 130,
"modelBreakdowns": [
{
"cacheCreationTokens": 30,
"cacheReadTokens": 40,
"cost": 0.0003224,
"inputTokens": 130,
"modelName": "GLM-5.3",
"outputTokens": 20
}
],
"modelsUsed": [
"GLM-5.3"
],
"outputTokens": 20,
"totalCost": 0.0003224,
"totalTokens": 220
},
{
"cacheCreationTokens": 0,
"cacheReadTokens": 10,
"date": "2099-02-01",
"inputTokens": 40,
"modelBreakdowns": [
{
"cacheCreationTokens": 0,
"cacheReadTokens": 10,
"cost": 0.0000806,
"inputTokens": 40,
"modelName": "GLM-5.3",
"outputTokens": 5
}
],
"modelsUsed": [
"GLM-5.3"
],
"outputTokens": 5,
"totalCost": 0.0000806,
"totalTokens": 55
}
],
"totals": {
"cacheCreationTokens": 45,
"cacheReadTokens": 75,
"inputTokens": 230,
"outputTokens": 35,
"totalCost": 0.0005585,
"totalTokens": 385
}
}
Loading