use std::io::Write;
use std::process::Command;
use tempfile::NamedTempFile;
fn create_test_file(content: &str, extension: &str) -> NamedTempFile {
let mut file = tempfile::Builder::new()
.suffix(extension)
.tempfile()
.unwrap();
file.write_all(content.as_bytes()).unwrap();
file
}
fn run_batless(args: &[&str]) -> std::process::Output {
Command::new(env!("CARGO_BIN_EXE_batless"))
.args(args)
.output()
.expect("Failed to execute batless")
}
#[test]
fn test_help_command() {
let output = run_batless(&["--help"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("A non-blocking, LLM-friendly code viewer"));
assert!(stdout.contains("--mode <MODE>"));
assert!(stdout.contains("--max-lines"));
}
#[test]
fn test_version_command() {
let output = run_batless(&["--version"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("batless"));
}
#[test]
fn test_version_json_command() {
let output = run_batless(&["--version-json"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).expect("valid json");
assert_eq!(json["name"], "batless");
assert_eq!(json["version"], env!("CARGO_PKG_VERSION"));
assert!(json["git_hash"].is_string());
assert_ne!(json["git_hash"].as_str().unwrap_or("unknown"), "unknown");
assert!(json["build_timestamp"].is_string());
assert_ne!(
json["build_timestamp"].as_str().unwrap_or("unknown"),
"unknown"
);
}
#[test]
fn test_plain_mode() {
let content = "fn main() {\n println!(\"Hello, world!\");\n}\n";
let file = create_test_file(content, ".rs");
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=plain"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("fn main()"));
assert!(stdout.contains("println!"));
}
#[test]
fn test_plain_output() {
let content = "fn main() {\n println!(\"Hello, world!\");\n}\n";
let file = create_test_file(content, ".rs");
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=plain"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("main"));
assert!(stdout.contains("println"));
}
#[test]
fn test_json_mode() {
let content = "fn main() {\n println!(\"Hello, world!\");\n}\n";
let file = create_test_file(content, ".rs");
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=json"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(json["mode"], "json");
assert_eq!(json["language"], "Rust");
assert_eq!(
json["processed_lines"]
.as_u64()
.expect("processed_lines should be numeric"),
json["lines"]
.as_array()
.expect("lines should be an array")
.len() as u64
);
assert!(json["lines"].is_array());
assert!(json["total_lines"].is_number());
assert!(json["total_lines_exact"].is_boolean());
assert!(json["total_bytes"].is_number());
assert!(json["identifier_count"].is_number());
assert!(json["identifiers_truncated"].is_boolean());
}
#[test]
fn test_json_pretty_flag_changes_formatting() {
let content = "fn main() { println!(\"Hello\"); }\n";
let file = create_test_file(content, ".rs");
let compact = run_batless(&[file.path().to_str().unwrap(), "--mode=json"]);
assert!(compact.status.success());
let compact_out = String::from_utf8(compact.stdout).unwrap();
assert!(!compact_out.contains("\n \"file\""));
let pretty = run_batless(&[
file.path().to_str().unwrap(),
"--mode=json",
"--json-pretty",
]);
assert!(pretty.status.success());
let pretty_out = String::from_utf8(pretty.stdout).unwrap();
assert!(pretty_out.contains("\n \"file\""));
assert!(pretty_out.len() > compact_out.len());
}
#[test]
fn test_max_lines_limit() {
let content = "line 1\nline 2\nline 3\nline 4\nline 5\n";
let file = create_test_file(content, ".txt");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--mode=plain",
"--max-lines=3",
]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let lines: Vec<&str> = stdout.trim().split('\n').collect();
assert_eq!(lines.len(), 4);
assert!(lines[3].contains("Output truncated after 3 lines"));
}
#[test]
fn test_max_bytes_limit() {
let content = "Short line 1\nShort line 2\nShort line 3\nShort line 4\n";
let file = create_test_file(content, ".txt");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--mode=plain",
"--max-bytes=25",
"--max-lines=100", ]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("Output truncated after") && stdout.contains("bytes"));
}
#[test]
fn test_language_detection() {
let python_content = "def hello():\n print('Hello, world!')\n";
let file = create_test_file(python_content, ".py");
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=json"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(json["language"], "Python");
}
#[test]
fn test_explicit_language() {
let content = "function hello() {\n console.log('Hello');\n}\n";
let file = create_test_file(content, ".unknown");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--language=javascript",
"--mode=json",
]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(json["language"], "javascript");
}
#[test]
fn test_plain_no_ansi() {
let content = "fn main() {\n println!(\"Hello\");\n}\n";
let file = create_test_file(content, ".rs");
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=plain"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(!stdout.contains("\x1b["));
}
#[test]
fn test_strip_ansi() {
let content = "fn main() {\n println!(\"Hello\");\n}\n";
let file = create_test_file(content, ".rs");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--mode=plain",
"--strip-ansi",
]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("fn main()"));
}
#[test]
fn test_empty_file() {
let file = create_test_file("", ".txt");
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=json"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(json["total_lines"], 0);
assert_eq!(json["total_bytes"], 0);
assert_eq!(json["truncated"], false);
}
#[test]
fn test_nonexistent_file() {
let output = run_batless(&["/nonexistent/file.txt", "--mode=plain"]);
assert!(!output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(
stderr.contains("No such file")
|| stderr.contains("not found")
|| stderr.contains("cannot find")
|| stderr.contains("system cannot find")
);
}
#[test]
fn test_multiple_options_combined() {
let content = "def func1():\n pass\n\ndef func2():\n pass\n\ndef func3():\n pass\n";
let file = create_test_file(content, ".py");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--mode=json",
"--max-lines=2",
"--language=python",
]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(json["language"], "python");
assert_eq!(json["processed_lines"], 2);
assert!(json["total_lines"].as_i64().unwrap() >= json["processed_lines"].as_i64().unwrap());
assert_eq!(json["truncated"], true);
assert_eq!(json["lines"].as_array().unwrap().len(), 2);
}
#[test]
fn test_list_languages() {
let output = run_batless(&["--list-languages"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("Rust"));
assert!(stdout.contains("Python"));
assert!(stdout.contains("JavaScript"));
}
#[test]
fn test_summary_mode() {
let content = "import os\nimport sys\n\ndef main():\n print('hello')\n x = 1\n y = 2\n\nclass Test:\n def method(self):\n pass\n";
let file = create_test_file(content, ".py");
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=summary"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("import os"));
assert!(stdout.contains("def main():"));
assert!(stdout.contains("class Test:"));
assert!(!stdout.contains("x = 1")); }
#[test]
fn test_summary_flag() {
let content = "fn main() {\n println!(\"hello\");\n let x = 1;\n}\n\nstruct Test {\n name: String,\n}\n";
let file = create_test_file(content, ".rs");
let output = run_batless(&[file.path().to_str().unwrap(), "--summary", "--mode=plain"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("fn main()"));
assert!(stdout.contains("struct Test"));
assert!(!stdout.contains("let x = 1")); }
#[test]
fn test_json_summary_retains_full_content() {
let content =
"import os\n\nclass Example:\n def method(self):\n pass\n\nSECRET = 42\n";
let file = create_test_file(content, ".py");
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=json", "--summary"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
let lines = json["lines"].as_array().expect("lines array");
assert!(
lines
.iter()
.any(|line| line.as_str() == Some("SECRET = 42")),
"Original content should remain intact"
);
let summary_lines = json["summary_lines"].as_array().expect("summary lines");
assert!(
summary_lines.len() <= lines.len(),
"Summary should not exceed full content"
);
}
#[test]
fn test_total_lines_exact_flag() {
let content = "line1\nline2\nline3\nline4\nline5\n";
let file = create_test_file(content, ".txt");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--mode=json",
"--max-lines=2",
]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(json["truncated_by_lines"], true);
assert_eq!(json["total_lines_exact"], false);
}
#[test]
fn test_token_sampling_reports_truncation() {
let repeated_tokens: Vec<String> = (0..3_000).map(|i| format!("token{}", i)).collect();
let content = repeated_tokens.join(" ");
let file = create_test_file(&content, ".rs");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--mode=json",
"--include-tokens",
]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
let tokens = json["identifiers"].as_array().expect("tokens array");
let token_count = json["identifier_count"].as_u64().expect("token_count");
assert!(
token_count as usize > tokens.len(),
"Reported count should exceed sampled tokens"
);
assert_eq!(json["identifiers_truncated"], true);
}
#[test]
fn test_include_tokens() {
let content = "fn main() {\n println!(\"Hello\");\n}\n";
let file = create_test_file(content, ".rs");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--mode=json",
"--include-tokens",
]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert!(json["identifiers"].is_array());
let tokens = json["identifiers"].as_array().unwrap();
assert!(!tokens.is_empty());
}
#[test]
fn test_enhanced_json_output() {
let content = "def hello():\n print('world')\n";
let file = create_test_file(content, ".py");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--mode=json",
"--include-tokens",
]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert!(json["encoding"].is_string());
assert!(json["syntax_errors"].is_array());
assert!(json["truncated_by_lines"].is_boolean());
assert!(json["truncated_by_bytes"].is_boolean());
assert!(json["processed_lines"].is_number());
assert!(json["total_lines"].is_number());
assert!(json["identifiers"].is_array());
assert!(json["identifier_count"].is_number());
assert!(json["identifiers_truncated"].is_boolean());
}
#[test]
fn test_summary_with_no_important_lines() {
let content = "// Just comments\n// Nothing important\n// More comments\n";
let file = create_test_file(content, ".rs");
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=summary"]);
assert!(output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(stderr.contains("// No summary-worthy code structures found"));
}
#[test]
fn test_ai_profile_claude() {
let test_file = create_test_file("fn main() {\n println!(\"hello\");\n}\n", ".rs");
let output = run_batless(&["--profile", "claude", test_file.path().to_str().unwrap()]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("=== File Summary ==="));
assert!(stdout.contains("Language: Rust"));
}
#[test]
fn test_ai_profile_copilot() {
let test_file = create_test_file("fn main() {\n println!(\"hello\");\n}\n", ".rs");
let output = run_batless(&["--profile", "copilot", test_file.path().to_str().unwrap()]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.contains("\"language\": \"Rust\"") || stdout.contains("\"language\":\"Rust\""),
"Expected language Rust field in JSON output, got: {}",
stdout
);
assert!(stdout.contains("\"identifiers\":"));
}
#[test]
fn test_ai_profile_chatgpt() {
let test_file = create_test_file("def hello():\n print('world')\n", ".py");
let output = run_batless(&["--profile", "chatgpt", test_file.path().to_str().unwrap()]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.contains("\"language\": \"Python\"") || stdout.contains("\"language\":\"Python\""),
"Expected language Python field in JSON output, got: {}",
stdout
);
assert!(stdout.contains("\"identifiers\":"));
}
#[test]
fn test_ai_profile_assistant() {
let test_file = create_test_file("class Test {\n public void run() {}\n}\n", ".java");
let output = run_batless(&["--profile", "assistant", test_file.path().to_str().unwrap()]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("=== File Summary ==="));
}
#[test]
fn test_explicit_mode_overrides_profile() {
let test_file = create_test_file("fn main() {}\n", ".rs");
let output = run_batless(&[
"--profile",
"claude",
"--mode",
"json", test_file.path().to_str().unwrap(),
]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.starts_with("{"));
assert!(!stdout.contains("=== File Summary ==="));
}
#[test]
fn test_profile_default_mode_used_without_explicit_mode() {
let test_file = create_test_file("fn main() {}\n", ".rs");
let output = run_batless(&["--profile", "claude", test_file.path().to_str().unwrap()]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("=== File Summary ==="));
assert!(!stdout.starts_with("{"));
}
#[test]
fn test_shell_completion_generation_bash() {
let output = run_batless(&["--generate-completions", "bash"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("_batless()"));
assert!(stdout.contains("COMPREPLY=()"));
}
#[test]
fn test_shell_completion_generation_zsh() {
let output = run_batless(&["--generate-completions", "zsh"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("#compdef batless"));
}
#[test]
fn test_shell_completion_generation_fish() {
let output = run_batless(&["--generate-completions", "fish"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("complete -c batless"));
}
#[test]
fn test_shell_completion_generation_powershell() {
let output = run_batless(&["--generate-completions", "power-shell"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("Register-ArgumentCompleter"));
}
#[test]
fn test_stdin_processing() {
let output = Command::new("sh")
.arg("-c")
.arg("echo 'test line 1\\ntest line 2' | cargo run -- --mode=json")
.output()
.expect("Failed to execute command");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("test line 1"));
assert!(stdout.contains("test line 2"));
assert!(
stdout.contains("file\": \"-") || stdout.contains("file\":\"-"),
"Expected file path '-' marker in JSON output, got: {}",
stdout
);
}
#[test]
fn test_stdin_processing_with_max_lines() {
let output = Command::new("sh")
.arg("-c")
.arg("printf 'line1\\nline2\\nline3\\nline4\\nline5\\n' | cargo run -- --mode=json --max-lines=3")
.output()
.expect("Failed to execute command");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("line1"));
assert!(stdout.contains("line3"));
assert!(!stdout.contains("line4"), "line4 should be truncated");
assert!(
stdout.contains("\"truncated\": true") || stdout.contains("\"truncated\":true"),
"Expected truncated flag in JSON output"
);
}
#[test]
fn test_stdin_processing_with_max_bytes() {
let output = Command::new("sh")
.arg("-c")
.arg("printf 'short\\nthis is a longer line\\nanother line\\n' | cargo run -- --mode=json --max-bytes=100 --max-lines=1")
.output()
.expect("Failed to execute command");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("short"));
assert!(
!stdout.contains("another line"),
"Content beyond max-lines should be truncated"
);
assert!(
stdout.contains("\"truncated\": true") || stdout.contains("\"truncated\":true"),
"Expected truncated flag in JSON output"
);
}
#[test]
fn test_invalid_language_error() {
let output = run_batless(&["src/main.rs", "--language=invalid-language"]);
assert!(!output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(stderr.contains("Language 'invalid-language' not found"));
assert!(stderr.contains("E203"));
}
#[test]
fn test_summary_mode_different_languages() {
let js_content = "import React from 'react';\n\nfunction Component() {\n console.log('test');\n return <div>Hello</div>;\n}\n\nexport default Component;\n";
let js_file = create_test_file(js_content, ".js");
let output = run_batless(&[
js_file.path().to_str().unwrap(),
"--mode=summary",
"--language=javascript",
]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("import React"));
assert!(stdout.contains("function Component"));
assert!(stdout.contains("export default"));
}
#[test]
fn test_once_lock_performance() {
let content = "fn main() { println!(\"test\"); }";
let file = create_test_file(content, ".rs");
for _ in 0..3 {
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=plain"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("fn main()"));
}
}
#[test]
fn test_error_handling_with_suggestions() {
let output = run_batless(&["nonexistent_file.txt"]);
assert!(!output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(stderr.contains("File not found"));
assert!(stderr.contains("E101"));
}
#[test]
fn test_large_file_processing() {
let large_content = "fn test_function() {\n println!(\"line\");\n}\n".repeat(100);
let file = create_test_file(&large_content, ".rs");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--mode=json",
"--max-lines=50",
]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert!(json["truncated"].as_bool().unwrap_or(false));
}
#[test]
fn test_configuration_validation_edge_cases() {
let content = "test";
let file = create_test_file(content, ".txt");
let output = run_batless(&[file.path().to_str().unwrap(), "--max-lines=0"]);
assert!(!output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(stderr.contains("E302") || stderr.contains("Configuration error"));
}
#[test]
fn test_memory_efficiency_string_handling() {
let content =
"use std::collections::HashMap;\n\nfn main() {\n let map = HashMap::new();\n}\n";
let file = create_test_file(content, ".rs");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--mode=json",
"--include-tokens",
]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert!(json["identifiers"].is_array());
assert!(!json["identifiers"].as_array().unwrap().is_empty());
}
#[test]
fn test_schema_validation() {
let output = run_batless(&["--get-schema", "json_output"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let schema: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert!(schema["type"].as_str().unwrap() == "object");
let properties = schema["properties"]
.as_object()
.expect("properties should be an object");
assert!(
properties.contains_key("identifier_count"),
"schema should expose identifier_count"
);
assert!(
properties.contains_key("identifiers_truncated"),
"schema should expose identifiers_truncated"
);
assert!(
properties.contains_key("total_lines_exact"),
"schema should expose total_lines_exact"
);
}
#[test]
fn test_language_validation() {
let output = run_batless(&["--list-languages"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("Rust"));
assert!(stdout.contains("Python"));
}
#[test]
fn test_compatibility_flags() {
let content = "line1\nline2\nline3\n";
let file = create_test_file(content, ".txt");
let output = run_batless(&[file.path().to_str().unwrap(), "--plain"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert_eq!(stdout.trim(), content.trim());
let output = run_batless(&[file.path().to_str().unwrap(), "--number", "--mode=plain"]);
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("line1") && stdout.contains("line2") && stdout.contains("line3"));
}
#[test]
fn test_encoding_detection_robustness() {
let test_cases = vec![
("ASCII content", ".txt"),
("UTF-8 content with émojis 🦀", ".txt"),
("Binary-like content \x00\x01\x02", ".bin"),
];
for (content, ext) in test_cases {
let file = create_test_file(content, ext);
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=json"]);
if output.status.success() {
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert!(json["encoding"].is_string());
}
}
}
#[test]
fn test_strip_blank_lines() {
let content = "def foo():\n pass\n\n\ndef bar():\n pass\n";
let file = create_test_file(content, ".py");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--strip-blank-lines",
"--mode=plain",
]);
assert!(
output.status.success(),
"batless --strip-blank-lines should succeed"
);
let stdout = String::from_utf8(output.stdout).unwrap();
let has_consecutive_blanks = stdout.contains("\n\n");
assert!(
!has_consecutive_blanks,
"Output should not contain consecutive blank lines after --strip-blank-lines"
);
assert!(
stdout.contains("def foo():"),
"def foo(): should be retained"
);
assert!(
stdout.contains("def bar():"),
"def bar(): should be retained"
);
}
#[test]
fn test_strip_comments() {
let content = "# this is a comment\ndef foo():\n # inline comment\n pass\n";
let file = create_test_file(content, ".py");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--strip-comments",
"--mode=plain",
]);
assert!(
output.status.success(),
"batless --strip-comments should succeed"
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
!stdout.contains("# this is a comment"),
"Top-level comment should be stripped"
);
assert!(
!stdout.contains("# inline comment"),
"Inline comment line should be stripped"
);
assert!(
stdout.contains("def foo():"),
"Function definition should be retained"
);
}
#[test]
fn test_strip_compression_ratio_in_json() {
let content = "# comment\ndef foo():\n pass\n\n# comment2\n";
let file = create_test_file(content, ".py");
let output = run_batless(&[
file.path().to_str().unwrap(),
"--strip-comments",
"--strip-blank-lines",
"--mode=json",
]);
assert!(
output.status.success(),
"batless --strip-comments --strip-blank-lines --mode=json should succeed"
);
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("Output should be valid JSON");
assert!(
json.get("compression_ratio").is_some(),
"JSON output should contain compression_ratio field"
);
let ratio = json["compression_ratio"]
.as_f64()
.expect("compression_ratio should be a float");
assert!(
ratio > 1.0,
"compression_ratio should be > 1.0 when content was stripped, got {}",
ratio
);
}
#[test]
fn test_mode_index_rust() {
let content = "pub fn foo() -> i32 { 1 }\nstruct Bar { x: i32 }\npub fn baz() {}\n";
let file = create_test_file(content, ".rs");
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=index"]);
assert!(
output.status.success(),
"batless --mode=index should succeed for a Rust file"
);
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("--mode=index output should be valid JSON");
assert!(
json["symbols"].is_array(),
"JSON output should contain a symbols array"
);
let symbol_count = json["symbol_count"]
.as_u64()
.expect("symbol_count should be a number");
assert!(symbol_count > 0, "symbol_count should be > 0");
let symbols = json["symbols"].as_array().unwrap();
assert!(
symbols
.iter()
.any(|s| s["kind"].as_str().is_some_and(|k| k == "function")),
"At least one symbol should have kind == function"
);
assert!(
symbols.iter().any(|s| s["name"].as_str() == Some("foo")),
"At least one symbol should have name == foo"
);
assert!(
symbols.iter().all(|s| s.get("line_start").is_some()),
"All symbols should have a line_start field"
);
}
#[test]
fn test_mode_index_has_file_metadata() {
let content = "pub fn main() {}\nstruct Config { value: i32 }\n";
let file = create_test_file(content, ".rs");
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=index"]);
assert!(
output.status.success(),
"batless --mode=index should succeed"
);
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("--mode=index output should be valid JSON");
assert!(
json.get("file").is_some(),
"JSON output should contain file field"
);
assert!(
json.get("language").is_some(),
"JSON output should contain language field"
);
assert!(
json["total_lines"].is_number(),
"JSON output should contain total_lines field"
);
assert!(
json["symbol_count"].is_number(),
"JSON output should contain symbol_count field"
);
assert_eq!(
json["mode"].as_str(),
Some("index"),
"mode field should equal index"
);
}
#[test]
fn test_mode_index_with_hash() {
let content = "pub fn answer() -> i32 { 42 }\n";
let file = create_test_file(content, ".rs");
let output = run_batless(&[file.path().to_str().unwrap(), "--mode=index", "--hash"]);
assert!(
output.status.success(),
"batless --mode=index --hash should succeed"
);
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("--mode=index --hash output should be valid JSON");
assert!(
json.get("file_hash").is_some(),
"JSON output should contain file_hash field when --hash is passed"
);
assert!(
!json["file_hash"].is_null(),
"file_hash should not be null when --hash is passed"
);
}