use assert_cmd::Command;
use assert_cmd::cargo_bin_cmd;
use predicates::prelude::*;
fn cship() -> Command {
cargo_bin_cmd!("cship")
}
#[test]
fn test_valid_full_json_exits_zero_with_no_stdout() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cargo_bin_cmd!("cship").write_stdin(json).assert().success();
}
#[test]
fn test_valid_minimal_json_exits_zero() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_minimal.json").unwrap();
cargo_bin_cmd!("cship").write_stdin(json).assert().success();
}
#[test]
fn test_empty_stdin_exits_nonzero_with_no_stdout() {
cargo_bin_cmd!("cship")
.write_stdin("")
.assert()
.failure()
.stdout("")
.stderr(predicate::str::contains(
"failed to parse Claude Code session JSON",
));
}
#[test]
fn test_version_flag_short_prints_version() {
cship()
.arg("-v")
.assert()
.success()
.stdout(predicate::str::contains(env!("CARGO_PKG_VERSION")));
}
#[test]
fn test_version_flag_long_prints_version() {
cship()
.arg("--version")
.assert()
.success()
.stdout(predicate::str::contains(env!("CARGO_PKG_VERSION")));
}
#[test]
fn test_malformed_json_exits_nonzero_with_no_stdout() {
cargo_bin_cmd!("cship")
.write_stdin("not valid json{{{")
.assert()
.failure()
.stdout("")
.stderr(predicate::str::contains(
"failed to parse Claude Code session JSON",
));
}
#[test]
fn test_unknown_fields_silently_ignored() {
let json = r#"{"session_id":"abc","cwd":"/tmp","transcript_path":"/tmp/t.jsonl","version":"1.0","exceeds_200k_tokens":false,"model":{"id":"claude-test","display_name":"Test"},"workspace":{"current_dir":"/tmp","project_dir":"/tmp"},"output_style":{"name":"default"},"cost":{"total_cost_usd":0.0},"unknown_future_field":true,"nested_unknown":{"key":"value"}}"#;
cargo_bin_cmd!("cship").write_stdin(json).assert().success();
}
#[test]
fn test_config_flag_with_valid_toml_exits_zero() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/sample_starship.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("Opus"));
}
#[test]
fn test_config_flag_with_nonexistent_file_exits_nonzero() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "/nonexistent/starship.toml"])
.write_stdin(json)
.assert()
.failure()
.stdout("")
.stderr(predicate::str::contains("failed to load config"));
}
#[test]
fn test_config_flag_with_malformed_toml_exits_nonzero() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/malformed.toml"])
.write_stdin(json)
.assert()
.failure()
.stdout("")
.stderr(predicate::str::contains("failed to load config"));
}
#[test]
fn test_no_local_config_falls_through_to_global_or_default() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_minimal.json").unwrap();
cship().write_stdin(json).assert().success();
}
#[test]
fn test_model_renders_display_name_to_stdout() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/model_only.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("Opus"));
}
#[test]
fn test_model_with_symbol_renders_symbol_and_name() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/model_styled.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("★ Opus"));
}
#[test]
fn test_model_with_style_renders_ansi_codes() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/model_styled.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("\x1b["));
}
#[test]
fn test_disabled_model_produces_empty_stdout() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/model_disabled.toml"])
.write_stdin(json)
.assert()
.success()
.stdout("");
}
#[test]
fn test_two_row_layout_produces_newline_separated_output() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
let output = cship()
.args(["--config", "tests/fixtures/two_rows.toml"])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let lines: Vec<&str> = stdout.trim_end_matches('\n').split('\n').collect();
assert_eq!(lines.len(), 2, "expected 2 lines; got: {stdout:?}");
assert!(lines[0].contains("Opus"), "line 0: {}", lines[0]);
assert!(lines[1].contains("Opus"), "line 1: {}", lines[1]);
}
#[test]
fn test_passthrough_tokens_skipped_silently() {
let json = r#"{"session_id":"test","cwd":"/tmp","transcript_path":"/tmp/t.jsonl","version":"1.0","exceeds_200k_tokens":false,"model":{"id":"test","display_name":"Test"},"workspace":{"current_dir":"/nonexistent_cship_test_path_12345","project_dir":"/tmp"},"output_style":{"name":"default"},"cost":{"total_cost_usd":0.0}}"#;
cship()
.args(["--config", "tests/fixtures/passthrough_only.toml"])
.env("RUST_LOG", "debug")
.write_stdin(json)
.assert()
.success()
.stdout("")
.stderr(predicate::str::contains("error").not())
.stderr(predicate::str::contains("WARN").not());
}
#[test]
fn test_missing_model_logs_warning_to_stderr() {
let json = r#"{"session_id":"test","cwd":"/tmp","transcript_path":"/tmp/t.jsonl","version":"1.0","exceeds_200k_tokens":false,"workspace":{"current_dir":"/tmp"},"output_style":{"name":"default"},"cost":{"total_cost_usd":0.0}}"#;
cship()
.args(["--config", "tests/fixtures/model_only.toml"])
.env("RUST_LOG", "warn")
.write_stdin(json)
.assert()
.success()
.stdout("")
.stderr(predicate::str::contains("cship.model"));
}
#[test]
fn test_no_lines_config_produces_empty_stdout() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/empty_cship.toml"])
.write_stdin(json)
.assert()
.success()
.stdout("");
}
#[test]
fn test_cost_renders_dollar_formatted_value() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/cost_basic.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("$0.01"));
}
#[test]
fn test_cost_warn_threshold_applies_ansi_style() {
let json = std::fs::read_to_string("tests/fixtures/cost_warn_value.json").unwrap();
cship()
.args(["--config", "tests/fixtures/cost_warn.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("\x1b["));
}
#[test]
fn test_cost_critical_threshold_applies_critical_style() {
let json = std::fs::read_to_string("tests/fixtures/cost_high.json").unwrap();
cship()
.args(["--config", "tests/fixtures/cost_critical.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("\x1b["));
}
#[test]
fn test_cost_disabled_produces_no_output() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/cost_disabled.toml"])
.write_stdin(json)
.assert()
.success()
.stdout("");
}
#[test]
fn test_cost_subfields_render_numeric_values() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
let output = cship()
.args(["--config", "tests/fixtures/cost_subfields.toml"])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("0.0123"),
"expected '0.0123' in: {stdout:?}"
);
assert!(stdout.contains("45000"), "expected '45000' in: {stdout:?}");
assert!(stdout.contains("2300"), "expected '2300' in: {stdout:?}");
assert!(stdout.contains("156"), "expected '156' in: {stdout:?}");
assert!(stdout.contains("23"), "expected '23' in: {stdout:?}");
}
#[test]
fn test_context_bar_renders_block_chars_and_percentage() {
let json = std::fs::read_to_string("tests/fixtures/context_high.json").unwrap();
let output = cship()
.args(["--config", "tests/fixtures/context_bar_basic.toml"])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains('█'),
"expected filled bar chars: {stdout:?}"
);
assert!(stdout.contains("90%"), "expected '90%': {stdout:?}");
}
#[test]
fn test_context_bar_warn_threshold_applies_ansi_style() {
let json = std::fs::read_to_string("tests/fixtures/context_warn.json").unwrap();
cship()
.args(["--config", "tests/fixtures/context_bar_warn.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("\x1b["));
}
#[test]
fn test_context_bar_critical_threshold_applies_critical_style() {
let json = std::fs::read_to_string("tests/fixtures/context_high.json").unwrap();
cship()
.args(["--config", "tests/fixtures/context_bar_warn.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("\x1b["));
}
#[test]
fn test_context_bar_disabled_produces_no_output() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/context_bar_disabled.toml"])
.write_stdin(json)
.assert()
.success()
.stdout("");
}
#[test]
fn test_context_bar_custom_width_5() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
let output = cship()
.args(["--config", "tests/fixtures/context_bar_width.toml"])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let total_bar: usize = stdout.chars().filter(|&c| c == '█' || c == '░').count();
assert_eq!(total_bar, 5, "expected bar width 5 in: {stdout:?}");
let filled: usize = stdout.chars().filter(|&c| c == '█').count();
let empty: usize = stdout.chars().filter(|&c| c == '░').count();
assert_eq!(
filled, 0,
"expected 0 filled for 8% with width 5: {stdout:?}"
);
assert_eq!(empty, 5, "expected 5 empty for 8% with width 5: {stdout:?}");
}
#[test]
fn test_context_window_subfields_render_correctly() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
let output = cship()
.args(["--config", "tests/fixtures/context_window_basic.toml"])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let tokens: Vec<&str> = stdout.split_whitespace().collect();
assert_eq!(
tokens.len(),
3,
"expected 3 space-separated tokens: {stdout:?}"
);
assert_eq!(tokens[0], "8", "expected used_pct '8': {stdout:?}");
assert_eq!(tokens[1], "92", "expected remaining_pct '92': {stdout:?}");
assert_eq!(
tokens[2], "200000",
"expected window size '200000': {stdout:?}"
);
}
#[test]
fn test_context_window_exceeds_200k_false_produces_no_output() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/context_window_exceeds.toml"])
.write_stdin(json)
.assert()
.success()
.stdout("");
}
#[test]
fn test_context_window_exceeds_200k_true_renders_marker() {
let json = std::fs::read_to_string("tests/fixtures/context_high.json").unwrap();
cship()
.args(["--config", "tests/fixtures/context_window_exceeds.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains(">200k"));
}
#[test]
fn test_context_window_current_usage_tokens() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
let output = cship()
.args([
"--config",
"tests/fixtures/context_window_current_usage.toml",
])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("8500"),
"expected input_tokens in: {stdout:?}"
);
assert!(
stdout.contains("1200"),
"expected output_tokens in: {stdout:?}"
);
}
#[test]
fn test_context_window_total_tokens_render_correctly() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
let output = cship()
.args(["--config", "tests/fixtures/context_window_totals.toml"])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let tokens: Vec<&str> = stdout.split_whitespace().collect();
assert_eq!(tokens.len(), 2, "expected 2 tokens: {stdout:?}");
assert_eq!(
tokens[0], "15234",
"expected total_input_tokens: {stdout:?}"
);
assert_eq!(
tokens[1], "4521",
"expected total_output_tokens: {stdout:?}"
);
}
#[test]
fn test_vim_renders_mode_string() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/vim_basic.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("NORMAL"));
}
#[test]
fn test_vim_applies_symbol_and_style() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/vim_styled.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("\x1b["))
.stdout(predicate::str::contains("✏ "));
}
#[test]
fn test_vim_disabled_produces_no_output() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/vim_disabled.toml"])
.write_stdin(json)
.assert()
.success()
.stdout("");
}
#[test]
fn test_vim_absent_produces_no_output() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_minimal.json").unwrap();
cship()
.args(["--config", "tests/fixtures/vim_basic.toml"])
.write_stdin(json)
.assert()
.success()
.stdout("");
}
#[test]
fn test_vim_mode_subfield_renders_identically() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/vim_mode_subfield.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("NORMAL"));
}
#[test]
fn test_agent_renders_name_string() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/agent_basic.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("security-reviewer"));
}
#[test]
fn test_agent_name_subfield_renders_identically() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/agent_name_subfield.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("security-reviewer"));
}
#[test]
fn test_agent_disabled_produces_no_output() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/agent_disabled.toml"])
.write_stdin(json)
.assert()
.success()
.stdout("");
}
#[test]
fn test_agent_absent_produces_no_output() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_minimal.json").unwrap();
cship()
.args(["--config", "tests/fixtures/agent_basic.toml"])
.write_stdin(json)
.assert()
.success()
.stdout("");
}
#[test]
fn test_session_cwd_renders_path() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/session_cwd.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("/home/user/projects/myapp"));
}
#[test]
fn test_session_id_renders_string() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/session_id.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("test-session-id"));
}
#[test]
fn test_session_transcript_path_renders_string() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/session_transcript_path.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains(
"/home/user/.claude/projects/myapp/transcript.jsonl",
));
}
#[test]
fn test_session_version_renders_string() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/session_version.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains(env!("CARGO_PKG_VERSION")));
}
#[test]
fn test_session_output_style_renders_name() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/session_output_style.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("default"));
}
#[test]
fn test_workspace_current_dir_renders_path() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/workspace_current_dir.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("/home/user/projects/myapp"));
}
#[test]
fn test_workspace_project_dir_renders_path() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/workspace_project_dir.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("/home/user/projects/myapp"));
}
#[test]
fn test_workspace_absent_produces_no_output() {
let json = r#"{"session_id":"test","cwd":"/tmp","transcript_path":"/t","version":"1.0","exceeds_200k_tokens":false,"model":{"id":"test","display_name":"Test"},"output_style":{"name":"default"},"cost":{"total_cost_usd":0.0}}"#;
cship()
.args(["--config", "tests/fixtures/workspace_current_dir.toml"])
.write_stdin(json)
.assert()
.success()
.stdout("");
}
#[test]
fn test_session_cwd_absent_produces_no_output() {
let json = r#"{"session_id":"test","transcript_path":"/t","version":"1.0","exceeds_200k_tokens":false,"model":{"id":"test","display_name":"Test"},"workspace":{"current_dir":"/tmp","project_dir":"/tmp"},"output_style":{"name":"default"},"cost":{"total_cost_usd":0.0}}"#;
cship()
.args(["--config", "tests/fixtures/session_cwd.toml"])
.write_stdin(json)
.assert()
.success()
.stdout("");
}
#[test]
fn test_explain_with_stdin_json_shows_module_names() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
let output = cship()
.args(["explain"])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("cship.model"),
"expected 'cship.model' in explain output: {stdout}"
);
}
#[test]
fn test_explain_with_stdin_json_shows_config_line() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
let output = cship()
.args(["explain"])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("using config:"),
"expected 'using config:' in explain output: {stdout}"
);
}
#[test]
fn test_explain_with_config_flag() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
let output = cship()
.args(["explain", "--config", "tests/fixtures/minimal.toml"])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("cship.model"),
"expected module names in explain output: {stdout}"
);
}
#[test]
fn test_explain_no_stdin_uses_embedded_fallback() {
let output = cargo_bin_cmd!("cship").args(["explain"]).output().unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("cship.model"),
"expected module names from embedded fallback: {stdout}"
);
assert!(
stdout.contains("using config:"),
"expected config line from embedded fallback: {stdout}"
);
assert!(
stdout.contains("Sonnet"),
"expected 'Sonnet' from embedded sample context: {stdout}"
);
}
#[test]
fn test_explain_shows_warning_for_disabled_module() {
let json = r#"{"model":null}"#;
let output = cargo_bin_cmd!("cship")
.args(["explain", "--config", "tests/fixtures/disabled-model.toml"])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains('⚠'),
"expected '⚠' warning indicator in explain output: {stdout}"
);
assert!(
stdout.contains("disabled"),
"expected 'disabled' in hint section: {stdout}"
);
assert!(
stdout.contains("[cship.model]"),
"expected specific section '[cship.model]' in remediation hint: {stdout}"
);
}
fn starship_available() -> bool {
std::process::Command::new("starship")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
#[test]
fn test_passthrough_directory_renders_when_starship_installed() {
if !starship_available() {
return; }
let json = r#"{"session_id":"test","cwd":"/tmp","transcript_path":"/tmp/t.jsonl","version":"1.0","exceeds_200k_tokens":false,"model":{"id":"claude-opus-4-6","display_name":"Opus"},"workspace":{"current_dir":"/tmp","project_dir":"/tmp"},"output_style":{"name":"default"},"cost":{"total_cost_usd":0.0}}"#;
let output = cship()
.args(["--config", "tests/fixtures/passthrough_directory.toml"])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
!stdout.trim().is_empty(),
"expected non-empty output from $directory passthrough: {stdout:?}"
);
}
#[test]
fn test_native_renders_alongside_passthrough_not_installed() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/sample_starship.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("Opus"));
}
#[cfg(unix)]
#[test]
fn test_passthrough_env_vars_injected_via_cship_model() {
use std::fs;
use std::os::unix::fs::PermissionsExt;
let dir = std::env::temp_dir().join("cship_inttest_cship_env");
fs::create_dir_all(&dir).unwrap();
let script = dir.join("starship");
fs::write(&script, "#!/bin/sh\nprintf '%s' \"$CSHIP_MODEL\"\n").unwrap();
fs::set_permissions(&script, fs::Permissions::from_mode(0o755)).unwrap();
let json = r#"{"session_id":"test","cwd":"/tmp","transcript_path":"/tmp/cship_inttest_tp.jsonl","version":"1.0","exceeds_200k_tokens":false,"model":{"id":"claude-opus-4-6","display_name":"IntTestModel"},"workspace":{"current_dir":"/tmp","project_dir":"/tmp"},"output_style":{"name":"default"},"cost":{"total_cost_usd":0.0}}"#;
let result = cship()
.args(["--config", "tests/fixtures/passthrough_only.toml"])
.env("PATH", dir.to_str().unwrap())
.write_stdin(json)
.output()
.unwrap();
let _ = fs::remove_dir_all(&dir);
let _ = fs::remove_file("/tmp/cship/cship_inttest_tp-starship-git_branch");
let _ = fs::remove_dir("/tmp/cship");
assert!(result.status.success());
let stdout = String::from_utf8_lossy(&result.stdout);
assert!(
stdout.contains("IntTestModel"),
"expected CSHIP_MODEL value 'IntTestModel' in passthrough output: {stdout:?}"
);
}
#[test]
fn test_cache_hit_does_not_spawn_subprocess() {
use std::fs;
let cache_dir = std::path::Path::new("/tmp/cship");
fs::create_dir_all(cache_dir).unwrap();
let cache_file = cache_dir.join("cship_cache_inttest-starship-git_branch");
fs::write(&cache_file, "cached-branch-value").unwrap();
let json = r#"{"session_id":"test","cwd":"/tmp","transcript_path":"/tmp/cship_cache_inttest.jsonl","version":"1.0","exceeds_200k_tokens":false,"model":{"id":"claude-opus-4-6","display_name":"Opus"},"workspace":{"current_dir":"/tmp","project_dir":"/tmp"},"output_style":{"name":"default"},"cost":{"total_cost_usd":0.0}}"#;
let result = cship()
.args(["--config", "tests/fixtures/passthrough_only.toml"])
.env("PATH", "/nonexistent_cship_test_dir_42")
.write_stdin(json)
.output()
.unwrap();
let _ = fs::remove_file(&cache_file);
let _ = fs::remove_dir(cache_dir);
assert!(result.status.success());
let stdout = String::from_utf8_lossy(&result.stdout);
assert!(
stdout.contains("cached-branch-value"),
"expected cached value in output (no subprocess needed): {stdout:?}"
);
}
#[test]
fn test_context_window_format_style_span() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/context_window_format.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("ctx: 8%"));
}
#[test]
fn test_context_bar_format_with_symbol() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/context_bar_format.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("🧠"));
}
#[test]
fn test_vim_format_conditional_absent_produces_no_output() {
let json = r#"{"model":{"id":"claude-opus-4-6","display_name":"Opus"}}"#;
cship()
.args(["--config", "tests/fixtures/vim_format_conditional.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::is_empty());
}
#[test]
fn test_vim_format_conditional_present_renders_value() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/vim_format_conditional.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("NORMAL"));
}
#[test]
fn test_literal_text_in_lines_preserved() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
cship()
.args(["--config", "tests/fixtures/literal_text.toml"])
.write_stdin(json)
.assert()
.success()
.stdout(predicate::str::contains("in: 15234"));
}
#[test]
fn test_usage_limits_stdin_renders_without_transcript_path() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_rate_limits.json").unwrap();
let output = cship()
.args(["--config", "tests/fixtures/usage_limits_stdin.toml"])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("5h:"), "expected 5h prefix: {stdout:?}");
assert!(stdout.contains("7d:"), "expected 7d prefix: {stdout:?}");
assert!(
stdout.contains("42%"),
"expected five_hour_pct 42%: {stdout:?}"
);
assert!(
stdout.contains("75%"),
"expected seven_day_pct 75%: {stdout:?}"
);
}
#[test]
fn test_format_field_line_break_produces_two_rows() {
let json = std::fs::read_to_string("tests/fixtures/sample_input_full.json").unwrap();
let output = cship()
.args(["--config", "tests/fixtures/format_line_break.toml"])
.write_stdin(json)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let lines: Vec<&str> = stdout.trim_end_matches('\n').split('\n').collect();
assert_eq!(
lines.len(),
2,
"expected 2 lines from format with $line_break; got: {stdout:?}"
);
assert!(lines[0].contains("Opus"), "line 0: {}", lines[0]);
assert!(lines[1].contains("Opus"), "line 1: {}", lines[1]);
}