use code2prompt_core::{
configuration::Code2PromptConfig,
path::{EntryMetadata, FileEntry, traverse_directory},
};
use git2::Repository;
use rstest::*;
use std::{
fs::{self},
path::Path,
};
use tempfile::{TempDir, tempdir};
#[fixture]
fn git_repo_with_files() -> TempDir {
let dir = tempdir().expect("Failed to create temp dir");
let _repo = Repository::init(dir.path()).expect("Failed to init git repo");
let files = vec![
("src/main.rs", "// Main file"),
("target/debug/app", "// Binary in target/"),
(".gitignore", "target/\n*.log"),
("README.md", "# Project Code2prompt"),
];
for (path, content) in files {
let full_path = dir.path().join(path);
if let Some(parent) = full_path.parent() {
fs::create_dir_all(parent).expect("Failed to create dir");
}
fs::write(full_path, content).expect("Failed to write file");
}
dir
}
#[fixture]
fn simple_dir_structure() -> TempDir {
let dir = tempdir().expect("Failed to create temp dir");
let files = vec![
("file1.txt", "Content 1"),
("subdir/file2.txt", "Content 2"),
("subdir/nested/file3.txt", "Content 3"),
];
for (path, content) in files {
let full_path = dir.path().join(path);
if let Some(parent) = full_path.parent() {
fs::create_dir_all(parent).expect("Failed to create dir");
}
fs::write(full_path, content).expect("Failed to write file");
}
dir
}
fn base_config(path: &Path) -> Code2PromptConfig {
Code2PromptConfig::builder()
.path(path.to_path_buf())
.build()
.expect("Failed to build config")
}
fn file_exists(files: &[FileEntry], path: &str) -> bool {
files.iter().any(|file| file.path.contains(path))
}
fn get_metadata(files: &[FileEntry], path: &str) -> Option<EntryMetadata> {
files
.iter()
.find(|file| file.path.contains(path))
.map(|file| file.metadata)
}
#[cfg(test)]
mod tests {
use super::*;
#[rstest]
fn test_basic_traversal(simple_dir_structure: TempDir) {
let config = base_config(simple_dir_structure.path());
let (tree_str, files) = traverse_directory(&config, None).unwrap();
assert!(tree_str.contains("file1.txt"));
assert!(tree_str.contains("subdir"));
assert!(tree_str.contains("file2.txt"));
assert_eq!(files.len(), 3);
assert!(file_exists(&files, "file1.txt"));
assert!(file_exists(&files, "file2.txt"));
assert!(file_exists(&files, "file3.txt"));
}
#[rstest]
fn test_respects_gitignore(git_repo_with_files: TempDir) {
let config = Code2PromptConfig::builder()
.path(git_repo_with_files.path().to_path_buf())
.no_ignore(false) .build()
.unwrap();
let (_, files) = traverse_directory(&config, None).unwrap();
assert!(!file_exists(&files, "target/debug/app"));
assert!(file_exists(&files, "src/main.rs"));
assert!(file_exists(&files, "README.md"));
}
#[rstest]
fn test_ignores_gitignore_when_disabled(git_repo_with_files: TempDir) {
let config = Code2PromptConfig::builder()
.path(git_repo_with_files.path().to_path_buf())
.no_ignore(true)
.build()
.unwrap();
let (_, files) = traverse_directory(&config, None).unwrap();
assert!(file_exists(&files, "src/main.rs"));
assert!(file_exists(&files, "README.md"));
assert!(file_exists(&files, "target/debug/app"));
}
#[rstest]
fn test_excludes_hidden_files_by_default(simple_dir_structure: TempDir) {
fs::write(simple_dir_structure.path().join(".hidden"), "secret").unwrap();
let config = base_config(simple_dir_structure.path());
let (tree_str, files) = traverse_directory(&config, None).unwrap();
assert!(!tree_str.contains(".hidden"));
assert!(!file_exists(&files, ".hidden"));
}
#[rstest]
fn test_includes_hidden_files_when_enabled(simple_dir_structure: TempDir) {
fs::write(simple_dir_structure.path().join(".hidden"), "secret").unwrap();
let config = Code2PromptConfig::builder()
.path(simple_dir_structure.path().to_path_buf())
.hidden(true)
.build()
.unwrap();
let (tree_str, files) = traverse_directory(&config, None).unwrap();
assert!(tree_str.contains(".hidden"));
assert!(file_exists(&files, ".hidden"));
}
#[rstest]
fn test_file_content_processing(simple_dir_structure: TempDir) {
let config = Code2PromptConfig::builder()
.path(simple_dir_structure.path().to_path_buf())
.line_numbers(true)
.build()
.unwrap();
let (_, files) = traverse_directory(&config, None).unwrap();
if let Some(file) = files.iter().find(|f| f.path.contains("file1.txt")) {
let code = &file.code;
assert!(code.contains("Content 1"));
assert!(code.contains("1 |")); } else {
panic!("file1.txt not found in output");
}
}
#[rstest]
fn test_file_metadata(simple_dir_structure: TempDir) {
let config = base_config(simple_dir_structure.path());
let (_, files) = traverse_directory(&config, None).unwrap();
if let Some(metadata) = get_metadata(&files, "file1.txt") {
assert!(!metadata.is_dir);
assert!(!metadata.is_symlink);
} else {
panic!("Metadata not found for file1.txt");
}
}
#[rstest]
fn test_relative_paths_by_default(simple_dir_structure: TempDir) {
let config = base_config(simple_dir_structure.path());
let (_, files) = traverse_directory(&config, None).unwrap();
assert!(files.iter().all(|file| !file.path.starts_with('/')));
}
#[rstest]
fn test_absolute_paths_when_enabled(simple_dir_structure: TempDir) {
let config = Code2PromptConfig::builder()
.path(simple_dir_structure.path().to_path_buf())
.absolute_path(true)
.build()
.unwrap();
let (_, files) = traverse_directory(&config, None).unwrap();
let abs_path = simple_dir_structure.path().canonicalize().unwrap();
assert!(
files
.iter()
.all(|file| file.path.starts_with(abs_path.to_str().unwrap()))
);
}
#[rstest]
fn test_symlink_following_when_enabled(simple_dir_structure: TempDir) {
let link_path = simple_dir_structure.path().join("link_to_file");
#[cfg(unix)]
{
std::os::unix::fs::symlink(simple_dir_structure.path().join("file1.txt"), &link_path)
.unwrap();
}
let config = Code2PromptConfig::builder()
.path(simple_dir_structure.path().to_path_buf())
.follow_symlinks(true)
.build()
.unwrap();
let (tree_str, _) = traverse_directory(&config, None).unwrap();
#[cfg(unix)]
assert!(tree_str.contains("link_to_file"));
}
}