use std::path::{Path, PathBuf};
use tempfile::TempDir;
use tldr_core::{detect_or_parse_language, validate_file_path};
use tldr_core::{Language, TldrError};
#[cfg(test)]
mod validate_file_path_tests {
use super::*;
#[test]
fn test_validate_file_path_relative_with_project() {
let temp = TempDir::new().unwrap();
let src_dir = temp.path().join("src");
std::fs::create_dir(&src_dir).unwrap();
let main_rs = src_dir.join("main.rs");
std::fs::write(&main_rs, "fn main() {}").unwrap();
let result = validate_file_path("src/main.rs", Some(temp.path()));
assert!(result.is_ok(), "Expected Ok, got {:?}", result);
assert!(result.unwrap().ends_with("src/main.rs"));
}
#[test]
fn test_validate_file_path_traversal_blocked() {
let temp = TempDir::new().unwrap();
let project_dir = temp.path().join("project");
std::fs::create_dir(&project_dir).unwrap();
let escape_file = temp.path().join("escape.rs");
std::fs::write(&escape_file, "// escaped").unwrap();
let result = validate_file_path("../escape.rs", Some(&project_dir));
assert!(
matches!(result, Err(TldrError::PathTraversal(_))),
"Expected PathTraversal error, got {:?}",
result
);
}
#[test]
fn test_validate_file_path_not_found() {
let result = validate_file_path("/nonexistent/path/file.rs", None);
assert!(
matches!(result, Err(TldrError::PathNotFound(_))),
"Expected PathNotFound error, got {:?}",
result
);
}
}
#[cfg(test)]
mod detect_or_parse_language_tests {
use super::*;
#[test]
fn test_parse_explicit_python() {
let result = detect_or_parse_language(Some("python"), Path::new("any.xyz"));
assert_eq!(result.unwrap(), Language::Python);
}
#[test]
fn test_detect_python_extension() {
let result = detect_or_parse_language(None, Path::new("script.py"));
assert_eq!(result.unwrap(), Language::Python);
}
#[test]
fn test_parse_invalid_language() {
let result = detect_or_parse_language(Some("invalid_lang"), Path::new("any.xyz"));
assert!(
matches!(result, Err(TldrError::UnsupportedLanguage(_))),
"Expected UnsupportedLanguage error, got {:?}",
result
);
}
#[test]
fn test_detect_unknown_extension() {
let result = detect_or_parse_language(None, Path::new("file.xyz"));
assert!(
matches!(result, Err(TldrError::UnsupportedLanguage(_))),
"Expected UnsupportedLanguage error, got {:?}",
result
);
}
#[test]
fn test_language_from_extension_py() {
let lang = Language::from_extension(".py");
assert_eq!(lang, Some(Language::Python));
}
#[test]
fn test_language_from_extension_rs() {
let lang = Language::from_extension(".rs");
assert_eq!(lang, Some(Language::Rust));
}
#[test]
fn test_language_from_extension_ts() {
let lang = Language::from_extension(".ts");
assert_eq!(lang, Some(Language::TypeScript));
}
#[test]
fn test_language_from_extension_go() {
let lang = Language::from_extension(".go");
assert_eq!(lang, Some(Language::Go));
}
#[test]
fn test_language_from_extension_unknown() {
let lang = Language::from_extension(".xyz");
assert_eq!(lang, None);
}
}
#[cfg(test)]
mod error_exit_codes_tests {
use super::*;
#[test]
fn test_exit_code_path_not_found() {
let err = TldrError::PathNotFound(PathBuf::from("/nonexistent"));
assert_eq!(err.exit_code(), 2, "PathNotFound should have exit code 2");
}
#[test]
fn test_exit_code_path_traversal() {
let err = TldrError::PathTraversal(PathBuf::from("../escape"));
assert_eq!(err.exit_code(), 3, "PathTraversal should have exit code 3");
}
#[test]
fn test_exit_code_symlink_cycle() {
let err = TldrError::SymlinkCycle(PathBuf::from("/cycle"));
assert_eq!(err.exit_code(), 4, "SymlinkCycle should have exit code 4");
}
#[test]
fn test_exit_code_permission_denied() {
let err = TldrError::PermissionDenied(PathBuf::from("/secret"));
assert_eq!(
err.exit_code(),
5,
"PermissionDenied should have exit code 5"
);
}
#[test]
fn test_exit_code_parse_error() {
let err = TldrError::parse_error(PathBuf::from("file.py"), Some(10), "syntax error");
assert_eq!(err.exit_code(), 10, "ParseError should have exit code 10");
}
#[test]
fn test_exit_code_unsupported_language() {
let err = TldrError::UnsupportedLanguage("unknown".to_string());
assert_eq!(
err.exit_code(),
11,
"UnsupportedLanguage should have exit code 11"
);
}
#[test]
fn test_exit_code_function_not_found() {
let err = TldrError::function_not_found("missing_fn");
assert_eq!(
err.exit_code(),
20,
"FunctionNotFound should have exit code 20"
);
}
#[test]
fn test_exit_code_invalid_direction() {
let err = TldrError::InvalidDirection("sideways".to_string());
assert_eq!(
err.exit_code(),
21,
"InvalidDirection should have exit code 21"
);
}
#[test]
fn test_exit_code_line_not_in_function() {
let err = TldrError::LineNotInFunction(999);
assert_eq!(
err.exit_code(),
22,
"LineNotInFunction should have exit code 22"
);
}
#[test]
fn test_exit_code_daemon_error() {
let err = TldrError::DaemonError("connection failed".to_string());
assert_eq!(err.exit_code(), 30, "DaemonError should have exit code 30");
}
#[test]
fn test_exit_code_connection_failed() {
let err = TldrError::ConnectionFailed("timeout".to_string());
assert_eq!(
err.exit_code(),
31,
"ConnectionFailed should have exit code 31"
);
}
#[test]
fn test_exit_code_timeout() {
let err = TldrError::Timeout("5s".to_string());
assert_eq!(err.exit_code(), 32, "Timeout should have exit code 32");
}
#[test]
fn test_exit_code_mcp_error() {
let err = TldrError::McpError("protocol error".to_string());
assert_eq!(err.exit_code(), 33, "McpError should have exit code 33");
}
#[test]
fn test_exit_code_serialization_error() {
let err = TldrError::SerializationError("invalid json".to_string());
assert_eq!(
err.exit_code(),
40,
"SerializationError should have exit code 40"
);
}
#[test]
fn test_exit_code_io_error() {
let io_err = std::io::Error::other("io error");
let err = TldrError::IoError(io_err);
assert_eq!(err.exit_code(), 1, "IoError should have exit code 1");
}
}
#[cfg(test)]
mod error_message_tests {
use super::*;
#[test]
fn test_error_message_path_not_found() {
let err = TldrError::PathNotFound(PathBuf::from("/missing/file.rs"));
let msg = err.to_string();
assert!(
msg.contains("Path not found") && msg.contains("/missing/file.rs"),
"Error message should contain 'Path not found: /missing/file.rs', got: {}",
msg
);
}
#[test]
fn test_error_message_path_traversal() {
let err = TldrError::PathTraversal(PathBuf::from("../escape.txt"));
let msg = err.to_string();
assert!(
msg.contains("traversal") && msg.contains("../escape.txt"),
"Error message should mention traversal and path, got: {}",
msg
);
}
#[test]
fn test_error_message_function_not_found_with_suggestions() {
let err = TldrError::function_not_found_with_suggestions(
"proces_data",
Some(PathBuf::from("main.py")),
vec!["process_data".to_string(), "process".to_string()],
);
let msg = err.to_string();
assert!(
msg.contains("proces_data"),
"Should contain function name: {}",
msg
);
assert!(
msg.contains("Did you mean"),
"Should contain suggestions: {}",
msg
);
assert!(
msg.contains("process_data"),
"Should list suggested name: {}",
msg
);
}
#[test]
fn test_error_message_parse_error_with_line() {
let err = TldrError::parse_error(PathBuf::from("broken.py"), Some(42), "unexpected token");
let msg = err.to_string();
assert!(
msg.contains("broken.py"),
"Should contain filename: {}",
msg
);
assert!(msg.contains("42"), "Should contain line number: {}", msg);
assert!(
msg.contains("unexpected token"),
"Should contain message: {}",
msg
);
}
#[test]
fn test_error_message_unsupported_language() {
let err = TldrError::UnsupportedLanguage("brainfuck".to_string());
let msg = err.to_string();
assert!(
msg.contains("Unsupported language") && msg.contains("brainfuck"),
"Error message should contain 'Unsupported language: brainfuck', got: {}",
msg
);
}
}