mod common;
use common::{prepare_work_and_config_dirs, run_llman, write_claude_code_sessions_index};
use serde_json::Value;
use std::fs;
use tempfile::TempDir;
#[test]
fn claude_code_stats_summary_json_includes_sidechain_by_default() {
let temp = TempDir::new().expect("temp dir");
let (work_dir, config_dir) = prepare_work_and_config_dirs(temp.path());
let projects_dir = temp.path().join("projects");
let project = projects_dir.join("p1");
fs::create_dir_all(&project).expect("mkdir project");
let s1 = project.join("s1.jsonl");
let s2 = project.join("s2.jsonl");
let s3 = project.join("s3.jsonl");
fs::write(
&s1,
r#"{"timestamp":"2026-02-01T00:00:00Z","message":{"usage":{"input_tokens":10,"output_tokens":5}}}"#,
)
.expect("write s1");
fs::write(
&s2,
r#"{"timestamp":"2026-02-03T00:00:00Z","message":{"usage":{"input_tokens":1,"output_tokens":1,"cache_read_input_tokens":8}}}"#,
)
.expect("write s2");
fs::write(&s3, r#"{"timestamp":"2026-02-04T00:00:00Z","message":{}}"#).expect("write s3");
write_claude_code_sessions_index(
&project,
&format!(
r#"
[
{{
"sessionId": "s1",
"fullPath": "{}",
"projectPath": "{}",
"created": "2026-02-01T00:00:00Z",
"modified": "2026-02-01T00:00:00Z",
"isSidechain": false
}},
{{
"sessionId": "s2",
"fullPath": "{}",
"projectPath": "{}",
"created": "2026-02-03T00:00:00Z",
"modified": "2026-02-03T00:00:00Z",
"isSidechain": true
}},
{{
"sessionId": "s3",
"fullPath": "{}",
"projectPath": "{}",
"created": "2026-02-04T00:00:00Z",
"modified": "2026-02-04T00:00:00Z",
"isSidechain": false
}}
]
"#,
s1.to_string_lossy(),
work_dir.to_string_lossy(),
s2.to_string_lossy(),
work_dir.to_string_lossy(),
s3.to_string_lossy(),
work_dir.to_string_lossy(),
),
);
let output = run_llman(
&[
"x",
"claude-code",
"stats",
"--projects-dir",
projects_dir.to_str().unwrap(),
"--format",
"json",
],
&work_dir,
&config_dir,
);
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let v: Value = serde_json::from_slice(&output.stdout).expect("parse json");
assert_eq!(v["tool"], "claude-code");
assert_eq!(v["view"], "summary");
assert_eq!(v["result"]["coverage"]["total_sessions"], 3);
assert_eq!(v["result"]["coverage"]["known_token_sessions"], 2);
assert_eq!(v["result"]["totals"]["tokens_total_known"], 25);
assert_eq!(
v["result"]["sidechain_totals"]["primary"]["tokens_total_known"],
15
);
assert_eq!(
v["result"]["sidechain_totals"]["sidechain"]["tokens_total_known"],
10
);
}
#[test]
fn claude_code_stats_no_sidechain_excludes_sidechain_sessions() {
let temp = TempDir::new().expect("temp dir");
let (work_dir, config_dir) = prepare_work_and_config_dirs(temp.path());
let projects_dir = temp.path().join("projects");
let project = projects_dir.join("p1");
fs::create_dir_all(&project).expect("mkdir project");
let s1 = project.join("s1.jsonl");
let s2 = project.join("s2.jsonl");
fs::write(
&s1,
r#"{"timestamp":"2026-02-01T00:00:00Z","message":{"usage":{"input_tokens":10,"output_tokens":5}}}"#,
)
.expect("write s1");
fs::write(
&s2,
r#"{"timestamp":"2026-02-03T00:00:00Z","message":{"usage":{"input_tokens":1,"output_tokens":1}}}"#,
)
.expect("write s2");
write_claude_code_sessions_index(
&project,
&format!(
r#"
[
{{
"sessionId": "s1",
"fullPath": "{}",
"projectPath": "{}",
"created": "2026-02-01T00:00:00Z",
"modified": "2026-02-01T00:00:00Z",
"isSidechain": false
}},
{{
"sessionId": "s2",
"fullPath": "{}",
"projectPath": "{}",
"created": "2026-02-03T00:00:00Z",
"modified": "2026-02-03T00:00:00Z",
"isSidechain": true
}}
]
"#,
s1.to_string_lossy(),
work_dir.to_string_lossy(),
s2.to_string_lossy(),
work_dir.to_string_lossy(),
),
);
let output = run_llman(
&[
"x",
"cc",
"stats",
"--projects-dir",
projects_dir.to_str().unwrap(),
"--no-sidechain",
"--format",
"json",
],
&work_dir,
&config_dir,
);
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let v: Value = serde_json::from_slice(&output.stdout).expect("parse json");
assert_eq!(v["result"]["coverage"]["total_sessions"], 1);
assert_eq!(v["result"]["totals"]["tokens_total_known"], 15);
}
#[test]
fn claude_code_stats_since_filters_by_end_ts() {
let temp = TempDir::new().expect("temp dir");
let (work_dir, config_dir) = prepare_work_and_config_dirs(temp.path());
let projects_dir = temp.path().join("projects");
let project = projects_dir.join("p1");
fs::create_dir_all(&project).expect("mkdir project");
let old = project.join("old.jsonl");
let new = project.join("new.jsonl");
fs::write(
&old,
r#"{"timestamp":"2026-02-01T00:00:00Z","message":{"usage":{"input_tokens":1,"output_tokens":1}}}"#,
)
.expect("write old");
fs::write(
&new,
r#"{"timestamp":"2026-02-10T00:00:00Z","message":{"usage":{"input_tokens":2,"output_tokens":2}}}"#,
)
.expect("write new");
write_claude_code_sessions_index(
&project,
&format!(
r#"
[
{{
"sessionId": "old",
"fullPath": "{}",
"projectPath": "{}",
"created": "2026-02-01T00:00:00Z",
"modified": "2026-02-01T00:00:00Z",
"isSidechain": false
}},
{{
"sessionId": "new",
"fullPath": "{}",
"projectPath": "{}",
"created": "2026-02-10T00:00:00Z",
"modified": "2026-02-10T00:00:00Z",
"isSidechain": false
}}
]
"#,
old.to_string_lossy(),
work_dir.to_string_lossy(),
new.to_string_lossy(),
work_dir.to_string_lossy(),
),
);
let output = run_llman(
&[
"x",
"claude-code",
"stats",
"--projects-dir",
projects_dir.to_str().unwrap(),
"--since",
"2026-02-05T00:00:00Z",
"--format",
"json",
],
&work_dir,
&config_dir,
);
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let v: Value = serde_json::from_slice(&output.stdout).expect("parse json");
assert_eq!(v["result"]["coverage"]["total_sessions"], 1);
assert_eq!(v["result"]["totals"]["tokens_total_known"], 4);
}