Skip to content

Commit 6844e26

Browse files
authored
test(cli): add missing offline report coverage (#402)
* test(cli): add missing offline report coverage from PR #375 review Add totalCost assertions to existing offline-no-cache tests, stale-cache tests for monthly and graph commands, and a test proving submit still fails offline with strict pricing guard. Constraint: embedded costs from OpenCode fixtures (0.05+0.03+0.02=0.10) are preserved when pricing is None Constraint: stale-cache pricing overrides embedded costs (totalCost=0.0209) Confidence: high Scope-risk: narrow * fix(test): assert submit offline test fails due to pricing, not bad args The test was passing because --no-spinner is not a valid submit flag, causing an argument parse error instead of a pricing failure. Removed --no-spinner (submit doesn't have it), added stderr assertions to verify failure is from pricing/network error path, not auth or args. Confidence: high Scope-risk: narrow
1 parent 82cd4cf commit 6844e26

1 file changed

Lines changed: 100 additions & 0 deletions

File tree

crates/tokscale-cli/tests/cli_tests.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,16 @@ fn write_pricing_cache(base: &Path, timestamp: u64) {
298298
}
299299
}
300300

301+
fn write_fake_credentials(base: &Path) {
302+
let creds_dir = base.join(".config/tokscale");
303+
fs::create_dir_all(&creds_dir).unwrap();
304+
fs::write(
305+
creds_dir.join("credentials.json"),
306+
r#"{"token":"fake","username":"testuser","createdAt":"2024-01-01T00:00:00Z"}"#,
307+
)
308+
.unwrap();
309+
}
310+
301311
// ── Existing tests ─────────────────────────────────────────────────────────
302312

303313
#[test]
@@ -900,6 +910,12 @@ fn test_models_json_offline_without_pricing_cache_still_succeeds() {
900910
assert_eq!(json["totalOutput"].as_i64().unwrap(), 1000);
901911
assert_eq!(json["totalMessages"].as_i64().unwrap(), 3);
902912
assert_eq!(json["entries"].as_array().unwrap().len(), 2);
913+
// Without pricing, embedded source costs are preserved (0.05 + 0.03 + 0.02)
914+
let total_cost = json["totalCost"].as_f64().unwrap();
915+
assert!(
916+
(total_cost - 0.10).abs() < 1e-9,
917+
"unexpected totalCost without pricing: {total_cost}"
918+
);
903919
}
904920

905921
#[test]
@@ -920,6 +936,12 @@ fn test_monthly_json_offline_without_pricing_cache_still_succeeds() {
920936
assert_eq!(entries.len(), 2);
921937
assert_eq!(entries[0]["month"].as_str().unwrap(), "2024-06");
922938
assert_eq!(entries[1]["month"].as_str().unwrap(), "2025-01");
939+
// Without pricing, embedded source costs are preserved
940+
let total_cost = json["totalCost"].as_f64().unwrap();
941+
assert!(
942+
(total_cost - 0.10).abs() < 1e-9,
943+
"unexpected totalCost without pricing: {total_cost}"
944+
);
923945
}
924946

925947
#[test]
@@ -939,6 +961,12 @@ fn test_graph_offline_without_pricing_cache_still_succeeds() {
939961
assert_eq!(json["summary"]["totalTokens"].as_i64().unwrap(), 3950);
940962
assert_eq!(json["summary"]["activeDays"].as_i64().unwrap(), 2);
941963
assert_eq!(json["contributions"].as_array().unwrap().len(), 2);
964+
// Without pricing, embedded source costs are preserved
965+
let total_cost = json["summary"]["totalCost"].as_f64().unwrap();
966+
assert!(
967+
(total_cost - 0.10).abs() < 1e-9,
968+
"unexpected totalCost without pricing: {total_cost}"
969+
);
942970
}
943971

944972
#[test]
@@ -964,6 +992,52 @@ fn test_models_json_offline_uses_stale_pricing_cache_when_available() {
964992
);
965993
}
966994

995+
#[test]
996+
fn test_monthly_json_offline_uses_stale_pricing_cache_when_available() {
997+
let tmp = create_temp_fixture_dir_without_pricing_cache();
998+
write_pricing_cache(tmp.path(), 1);
999+
1000+
let output = offline_cmd_with_home(tmp.path())
1001+
.args(["monthly", "--json", "--opencode", "--no-spinner"])
1002+
.output()
1003+
.unwrap();
1004+
assert!(
1005+
output.status.success(),
1006+
"stderr: {}",
1007+
String::from_utf8_lossy(&output.stderr)
1008+
);
1009+
1010+
let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
1011+
let total_cost = json["totalCost"].as_f64().unwrap();
1012+
assert!(
1013+
(total_cost - 0.0209).abs() < 1e-9,
1014+
"unexpected totalCost: {total_cost}"
1015+
);
1016+
}
1017+
1018+
#[test]
1019+
fn test_graph_offline_uses_stale_pricing_cache_when_available() {
1020+
let tmp = create_temp_fixture_dir_without_pricing_cache();
1021+
write_pricing_cache(tmp.path(), 1);
1022+
1023+
let output = offline_cmd_with_home(tmp.path())
1024+
.args(["graph", "--opencode", "--no-spinner"])
1025+
.output()
1026+
.unwrap();
1027+
assert!(
1028+
output.status.success(),
1029+
"stderr: {}",
1030+
String::from_utf8_lossy(&output.stderr)
1031+
);
1032+
1033+
let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
1034+
let total_cost = json["summary"]["totalCost"].as_f64().unwrap();
1035+
assert!(
1036+
(total_cost - 0.0209).abs() < 1e-9,
1037+
"unexpected totalCost: {total_cost}"
1038+
);
1039+
}
1040+
9671041
#[test]
9681042
fn test_models_json_total_consistency() {
9691043
let tmp = create_temp_fixture_dir();
@@ -1582,3 +1656,29 @@ fn test_root_with_group_by() {
15821656
let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
15831657
assert_eq!(json["groupBy"].as_str().unwrap(), "model");
15841658
}
1659+
1660+
#[test]
1661+
fn test_submit_offline_without_pricing_cache_fails() {
1662+
let tmp = create_temp_fixture_dir_without_pricing_cache();
1663+
write_fake_credentials(tmp.path());
1664+
1665+
let output = offline_cmd_with_home(tmp.path())
1666+
.args(["submit", "--opencode", "--dry-run"])
1667+
.output()
1668+
.unwrap();
1669+
let stderr = String::from_utf8_lossy(&output.stderr);
1670+
assert!(
1671+
!output.status.success(),
1672+
"submit should fail when pricing is unavailable; stdout: {}",
1673+
String::from_utf8_lossy(&output.stdout)
1674+
);
1675+
// Verify failure is from pricing fetch, not from auth or argument errors
1676+
assert!(
1677+
!stderr.contains("Not logged in"),
1678+
"submit failed due to auth, not pricing: {stderr}"
1679+
);
1680+
assert!(
1681+
stderr.contains("error") || stderr.contains("Error"),
1682+
"stderr should contain a pricing/network error: {stderr}"
1683+
);
1684+
}

0 commit comments

Comments
 (0)