mirror of
https://github.com/delibae/claude-prism.git
synced 2026-10-07 02:58:00 +00:00
test: add Rust integration tests for history, latex, claude, and slash_commands
Add 35 integration tests using tempfile for filesystem/git-dependent functions: - history.rs (15): init, snapshot, list, diff, file_at, restore, labels, tag_map, ensure_excludes - latex.rs (6): copy_dir_recursive, sync_source_files - claude.rs (6): extract_first_user_message with JSONL parsing - slash_commands.rs (6): load_command_from_file, find_markdown_files Total Rust tests: 100 (65 existing + 35 new) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7b9474414d
commit
17dcb3f273
4 changed files with 612 additions and 0 deletions
|
|
@ -1128,4 +1128,81 @@ mod tests {
|
|||
// dots and underscores are non-alphanumeric → replaced with '-'
|
||||
assert_eq!(dir_name, "-home-user--my-project-v2");
|
||||
}
|
||||
|
||||
// --- extract_first_user_message integration tests ---
|
||||
|
||||
#[test]
|
||||
fn test_extract_first_user_message_string_content() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("session.jsonl");
|
||||
let line = r#"{"type":"user","timestamp":"2024-01-01T00:00:00Z","message":{"content":"Fix the bibliography"}}"#;
|
||||
std::fs::write(&path, line).unwrap();
|
||||
|
||||
let pb = PathBuf::from(&path);
|
||||
let (title, ts) = extract_first_user_message(&pb);
|
||||
assert_eq!(title.unwrap(), "Fix the bibliography");
|
||||
assert_eq!(ts.unwrap(), "2024-01-01T00:00:00Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_first_user_message_block_array_content() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("session.jsonl");
|
||||
let line = r#"{"type":"user","timestamp":"2024-02-01T00:00:00Z","message":{"content":[{"type":"text","text":"Rewrite the abstract"}]}}"#;
|
||||
std::fs::write(&path, line).unwrap();
|
||||
|
||||
let pb = PathBuf::from(&path);
|
||||
let (title, ts) = extract_first_user_message(&pb);
|
||||
assert_eq!(title.unwrap(), "Rewrite the abstract");
|
||||
assert_eq!(ts.unwrap(), "2024-02-01T00:00:00Z");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_first_user_message_empty_file() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("empty.jsonl");
|
||||
std::fs::write(&path, "").unwrap();
|
||||
|
||||
let pb = PathBuf::from(&path);
|
||||
let (title, ts) = extract_first_user_message(&pb);
|
||||
assert!(title.is_none());
|
||||
assert!(ts.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_first_user_message_no_user_messages() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("session.jsonl");
|
||||
let lines = r#"{"type":"system","subtype":"init","session_id":"abc"}
|
||||
{"type":"assistant","message":{"content":"Hello"}}"#;
|
||||
std::fs::write(&path, lines).unwrap();
|
||||
|
||||
let pb = PathBuf::from(&path);
|
||||
let (title, ts) = extract_first_user_message(&pb);
|
||||
assert!(title.is_none());
|
||||
assert!(ts.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_first_user_message_nonexistent_path() {
|
||||
let pb = PathBuf::from("/tmp/nonexistent_session_file_12345.jsonl");
|
||||
let (title, ts) = extract_first_user_message(&pb);
|
||||
assert!(title.is_none());
|
||||
assert!(ts.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_first_user_message_skips_ide_tags() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let path = dir.path().join("session.jsonl");
|
||||
// First user message is an IDE tag (should skip), second is real
|
||||
let lines = r#"{"type":"user","timestamp":"2024-01-01T00:00:00Z","message":{"content":"<ide_context>data"}}
|
||||
{"type":"user","timestamp":"2024-01-02T00:00:00Z","message":{"content":"Add a new section"}}"#;
|
||||
std::fs::write(&path, lines).unwrap();
|
||||
|
||||
let pb = PathBuf::from(&path);
|
||||
let (title, ts) = extract_first_user_message(&pb);
|
||||
assert_eq!(title.unwrap(), "Add a new section");
|
||||
assert_eq!(ts.unwrap(), "2024-01-02T00:00:00Z");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -524,3 +524,328 @@ pub fn history_remove_label(project_root: String, label: String) -> Result<(), S
|
|||
.map_err(|e| format!("Failed to delete label: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
|
||||
/// Create a temp project dir with the given files.
|
||||
fn setup_project(files: &[(&str, &str)]) -> TempDir {
|
||||
let dir = TempDir::new().unwrap();
|
||||
for (name, content) in files {
|
||||
let path = dir.path().join(name);
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).unwrap();
|
||||
}
|
||||
fs::write(&path, content).unwrap();
|
||||
}
|
||||
dir
|
||||
}
|
||||
|
||||
fn root(dir: &TempDir) -> String {
|
||||
dir.path().to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
// ─── history_init ───
|
||||
|
||||
#[test]
|
||||
fn test_history_init_creates_repo() {
|
||||
let dir = setup_project(&[("main.tex", "\\documentclass{article}")]);
|
||||
history_init(root(&dir)).unwrap();
|
||||
|
||||
let git_dir = dir.path().join(".claudeprism").join("history.git");
|
||||
assert!(git_dir.exists(), "history.git should be created");
|
||||
|
||||
// Should have an initial commit
|
||||
let repo = Repository::open(&git_dir).unwrap();
|
||||
let head = repo.head().unwrap();
|
||||
let commit = head.peel_to_commit().unwrap();
|
||||
assert!(commit.message().unwrap().contains("[init]"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_init_idempotent() {
|
||||
let dir = setup_project(&[("main.tex", "hello")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
// Second call should succeed without error
|
||||
history_init(r).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_init_creates_excludes() {
|
||||
let dir = setup_project(&[("main.tex", "doc")]);
|
||||
history_init(root(&dir)).unwrap();
|
||||
|
||||
let excludes = dir.path().join(".claudeprism").join("history-exclude");
|
||||
assert!(excludes.exists());
|
||||
let content = fs::read_to_string(&excludes).unwrap();
|
||||
assert!(content.contains("*.aux"));
|
||||
assert!(content.contains(".claudeprism/"));
|
||||
assert!(content.contains(".prism/"));
|
||||
}
|
||||
|
||||
// ─── history_snapshot ───
|
||||
|
||||
#[test]
|
||||
fn test_history_snapshot_after_modification() {
|
||||
let dir = setup_project(&[("main.tex", "v1")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
// Modify a file
|
||||
fs::write(dir.path().join("main.tex"), "v2").unwrap();
|
||||
|
||||
let result = history_snapshot(r, "edited main.tex".into()).unwrap();
|
||||
assert!(result.is_some());
|
||||
let snap = result.unwrap();
|
||||
assert_eq!(snap.message, "edited main.tex");
|
||||
assert!(snap.changed_files.contains(&"main.tex".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_snapshot_no_change_returns_none() {
|
||||
let dir = setup_project(&[("main.tex", "same")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
// No modification → None
|
||||
let result = history_snapshot(r, "no-op".into()).unwrap();
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_snapshot_detects_new_file() {
|
||||
let dir = setup_project(&[("main.tex", "doc")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
// Add a new file
|
||||
fs::write(dir.path().join("chapter1.tex"), "new chapter").unwrap();
|
||||
|
||||
let snap = history_snapshot(r, "add chapter".into()).unwrap().unwrap();
|
||||
assert!(snap.changed_files.contains(&"chapter1.tex".to_string()));
|
||||
}
|
||||
|
||||
// ─── history_list ───
|
||||
|
||||
#[test]
|
||||
fn test_history_list_after_snapshots() {
|
||||
let dir = setup_project(&[("main.tex", "v1")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
fs::write(dir.path().join("main.tex"), "v2").unwrap();
|
||||
history_snapshot(r.clone(), "snap 1".into()).unwrap();
|
||||
|
||||
fs::write(dir.path().join("main.tex"), "v3").unwrap();
|
||||
history_snapshot(r.clone(), "snap 2".into()).unwrap();
|
||||
|
||||
let list = history_list(r, 10, 0).unwrap();
|
||||
assert_eq!(list.len(), 3); // init + 2 snapshots
|
||||
let msgs: Vec<&str> = list.iter().map(|s| s.message.as_str()).collect();
|
||||
assert!(msgs.contains(&"snap 1"));
|
||||
assert!(msgs.contains(&"snap 2"));
|
||||
assert!(msgs.iter().any(|m| m.contains("[init]")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_list_pagination() {
|
||||
let dir = setup_project(&[("a.tex", "x")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
fs::write(dir.path().join("a.tex"), "y").unwrap();
|
||||
history_snapshot(r.clone(), "s1".into()).unwrap();
|
||||
|
||||
fs::write(dir.path().join("a.tex"), "z").unwrap();
|
||||
history_snapshot(r.clone(), "s2".into()).unwrap();
|
||||
|
||||
// limit=1 → returns exactly 1 entry
|
||||
let page1 = history_list(r.clone(), 1, 0).unwrap();
|
||||
assert_eq!(page1.len(), 1);
|
||||
|
||||
// offset=1 → returns a different entry
|
||||
let page2 = history_list(r.clone(), 1, 1).unwrap();
|
||||
assert_eq!(page2.len(), 1);
|
||||
assert_ne!(page1[0].id, page2[0].id);
|
||||
|
||||
// All 3 entries accessible
|
||||
let all = history_list(r, 10, 0).unwrap();
|
||||
assert_eq!(all.len(), 3);
|
||||
}
|
||||
|
||||
// ─── history_diff ───
|
||||
|
||||
#[test]
|
||||
fn test_history_diff_shows_changes() {
|
||||
let dir = setup_project(&[("main.tex", "old content")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
fs::write(dir.path().join("main.tex"), "new content").unwrap();
|
||||
let snap = history_snapshot(r.clone(), "update".into()).unwrap().unwrap();
|
||||
|
||||
let list = history_list(r.clone(), 10, 0).unwrap();
|
||||
let from_id = list[1].id.clone(); // init
|
||||
let to_id = snap.id.clone();
|
||||
|
||||
let diffs = history_diff(r, from_id, to_id).unwrap();
|
||||
assert!(!diffs.is_empty());
|
||||
let d = diffs.iter().find(|d| d.file_path == "main.tex").unwrap();
|
||||
assert_eq!(d.status, "modified");
|
||||
assert_eq!(d.old_content.as_deref(), Some("old content"));
|
||||
assert_eq!(d.new_content.as_deref(), Some("new content"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_diff_added_file() {
|
||||
let dir = setup_project(&[("a.tex", "a")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
fs::write(dir.path().join("b.tex"), "new file").unwrap();
|
||||
let snap = history_snapshot(r.clone(), "add b".into()).unwrap().unwrap();
|
||||
|
||||
let list = history_list(r.clone(), 10, 0).unwrap();
|
||||
let from_id = list[1].id.clone(); // init
|
||||
let to_id = snap.id;
|
||||
|
||||
let diffs = history_diff(r, from_id, to_id).unwrap();
|
||||
let d = diffs.iter().find(|d| d.file_path == "b.tex").unwrap();
|
||||
assert_eq!(d.status, "added");
|
||||
assert!(d.old_content.is_none());
|
||||
assert_eq!(d.new_content.as_deref(), Some("new file"));
|
||||
}
|
||||
|
||||
// ─── history_file_at ───
|
||||
|
||||
#[test]
|
||||
fn test_history_file_at_returns_content() {
|
||||
let dir = setup_project(&[("main.tex", "version one")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
let list = history_list(r.clone(), 1, 0).unwrap();
|
||||
let init_id = list[0].id.clone();
|
||||
|
||||
let content = history_file_at(r, init_id, "main.tex".into()).unwrap();
|
||||
assert_eq!(content, "version one");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_file_at_nonexistent_file_errors() {
|
||||
let dir = setup_project(&[("main.tex", "x")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
let list = history_list(r.clone(), 1, 0).unwrap();
|
||||
let id = list[0].id.clone();
|
||||
|
||||
let result = history_file_at(r, id, "nonexistent.tex".into());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
// ─── history_restore ───
|
||||
|
||||
#[test]
|
||||
fn test_history_restore_reverts_content() {
|
||||
let dir = setup_project(&[("main.tex", "original")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
let list = history_list(r.clone(), 1, 0).unwrap();
|
||||
let init_id = list[0].id.clone();
|
||||
|
||||
// Modify
|
||||
fs::write(dir.path().join("main.tex"), "modified").unwrap();
|
||||
history_snapshot(r.clone(), "modify".into()).unwrap();
|
||||
|
||||
// Restore to init
|
||||
let restore_info = history_restore(r.clone(), init_id).unwrap();
|
||||
assert!(restore_info.message.contains("[restore]"));
|
||||
|
||||
// Working directory should have original content
|
||||
let content = fs::read_to_string(dir.path().join("main.tex")).unwrap();
|
||||
assert_eq!(content, "original");
|
||||
}
|
||||
|
||||
// ─── labels ───
|
||||
|
||||
#[test]
|
||||
fn test_history_add_and_remove_label() {
|
||||
let dir = setup_project(&[("main.tex", "doc")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
let list = history_list(r.clone(), 1, 0).unwrap();
|
||||
let id = list[0].id.clone();
|
||||
|
||||
// Add label
|
||||
history_add_label(r.clone(), id.clone(), "v1.0".into()).unwrap();
|
||||
|
||||
// Verify label appears in list
|
||||
let list = history_list(r.clone(), 1, 0).unwrap();
|
||||
assert!(list[0].labels.contains(&"v1.0".to_string()));
|
||||
|
||||
// Remove label
|
||||
history_remove_label(r.clone(), "v1.0".into()).unwrap();
|
||||
|
||||
// Verify label gone
|
||||
let list = history_list(r.clone(), 1, 0).unwrap();
|
||||
assert!(!list[0].labels.contains(&"v1.0".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_remove_nonexistent_label_errors() {
|
||||
let dir = setup_project(&[("main.tex", "x")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
let result = history_remove_label(r, "nope".into());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
// ─── tag_map ───
|
||||
|
||||
#[test]
|
||||
fn test_tag_map_groups_by_oid() {
|
||||
let dir = setup_project(&[("main.tex", "x")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
let list = history_list(r.clone(), 1, 0).unwrap();
|
||||
let id = list[0].id.clone();
|
||||
|
||||
history_add_label(r.clone(), id.clone(), "alpha".into()).unwrap();
|
||||
history_add_label(r.clone(), id.clone(), "beta".into()).unwrap();
|
||||
|
||||
let repo = open_repo(&r).unwrap();
|
||||
let map = tag_map(&repo);
|
||||
let oid = Oid::from_str(&id).unwrap();
|
||||
let labels = map.get(&oid).unwrap();
|
||||
assert!(labels.contains(&"alpha".to_string()));
|
||||
assert!(labels.contains(&"beta".to_string()));
|
||||
}
|
||||
|
||||
// ─── ensure_excludes ───
|
||||
|
||||
#[test]
|
||||
fn test_ensure_excludes_migrates_missing_prism() {
|
||||
let dir = setup_project(&[("main.tex", "x")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
// Write an excludes file WITHOUT .prism/
|
||||
let excludes_path = dir.path().join(".claudeprism").join("history-exclude");
|
||||
fs::write(&excludes_path, "*.aux\n*.log\n.claudeprism/\n").unwrap();
|
||||
|
||||
let repo = open_repo(&r).unwrap();
|
||||
ensure_excludes(&r, &repo);
|
||||
|
||||
let content = fs::read_to_string(&excludes_path).unwrap();
|
||||
assert!(content.contains(".prism/"), "should migrate to include .prism/");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -782,4 +782,115 @@ Postamble:
|
|||
let dir = persistent_build_dir("/project/");
|
||||
assert_eq!(dir, PathBuf::from("/project/.prism/build"));
|
||||
}
|
||||
|
||||
// --- copy_dir_recursive integration tests ---
|
||||
|
||||
#[test]
|
||||
fn test_copy_dir_recursive_nested() {
|
||||
let src = tempfile::tempdir().unwrap();
|
||||
let dst = tempfile::tempdir().unwrap();
|
||||
|
||||
// Create nested structure
|
||||
std::fs::create_dir_all(src.path().join("sub").join("deep")).unwrap();
|
||||
std::fs::write(src.path().join("top.tex"), "top").unwrap();
|
||||
std::fs::write(src.path().join("sub").join("mid.tex"), "mid").unwrap();
|
||||
std::fs::write(src.path().join("sub").join("deep").join("bottom.tex"), "bottom").unwrap();
|
||||
|
||||
copy_dir_recursive(src.path(), dst.path()).unwrap();
|
||||
|
||||
assert_eq!(std::fs::read_to_string(dst.path().join("top.tex")).unwrap(), "top");
|
||||
assert_eq!(std::fs::read_to_string(dst.path().join("sub").join("mid.tex")).unwrap(), "mid");
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(dst.path().join("sub").join("deep").join("bottom.tex")).unwrap(),
|
||||
"bottom"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_dir_recursive_skips_hidden_dirs() {
|
||||
let src = tempfile::tempdir().unwrap();
|
||||
let dst = tempfile::tempdir().unwrap();
|
||||
|
||||
std::fs::create_dir_all(src.path().join(".git")).unwrap();
|
||||
std::fs::write(src.path().join(".git").join("config"), "secret").unwrap();
|
||||
std::fs::write(src.path().join("main.tex"), "doc").unwrap();
|
||||
|
||||
copy_dir_recursive(src.path(), dst.path()).unwrap();
|
||||
|
||||
assert!(dst.path().join("main.tex").exists());
|
||||
assert!(!dst.path().join(".git").exists(), ".git should be skipped");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_copy_dir_recursive_empty_subdir() {
|
||||
let src = tempfile::tempdir().unwrap();
|
||||
let dst = tempfile::tempdir().unwrap();
|
||||
|
||||
std::fs::create_dir_all(src.path().join("empty_sub")).unwrap();
|
||||
std::fs::write(src.path().join("a.tex"), "a").unwrap();
|
||||
|
||||
copy_dir_recursive(src.path(), dst.path()).unwrap();
|
||||
|
||||
assert!(dst.path().join("empty_sub").exists());
|
||||
assert!(dst.path().join("empty_sub").is_dir());
|
||||
}
|
||||
|
||||
// --- sync_source_files integration tests ---
|
||||
|
||||
#[test]
|
||||
fn test_sync_source_files_copies_sources() {
|
||||
let src = tempfile::tempdir().unwrap();
|
||||
let dst = tempfile::tempdir().unwrap();
|
||||
|
||||
std::fs::write(src.path().join("main.tex"), "doc").unwrap();
|
||||
std::fs::write(src.path().join("refs.bib"), "bib").unwrap();
|
||||
std::fs::write(src.path().join("style.sty"), "sty").unwrap();
|
||||
|
||||
sync_source_files(src.path(), dst.path()).unwrap();
|
||||
|
||||
assert_eq!(std::fs::read_to_string(dst.path().join("main.tex")).unwrap(), "doc");
|
||||
assert_eq!(std::fs::read_to_string(dst.path().join("refs.bib")).unwrap(), "bib");
|
||||
assert_eq!(std::fs::read_to_string(dst.path().join("style.sty")).unwrap(), "sty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync_source_files_skips_artifacts() {
|
||||
let src = tempfile::tempdir().unwrap();
|
||||
let dst = tempfile::tempdir().unwrap();
|
||||
|
||||
std::fs::write(src.path().join("main.tex"), "doc").unwrap();
|
||||
std::fs::write(src.path().join("main.aux"), "aux").unwrap();
|
||||
std::fs::write(src.path().join("main.log"), "log").unwrap();
|
||||
std::fs::write(src.path().join("main.pdf"), "pdf").unwrap();
|
||||
std::fs::write(src.path().join("main.synctex.gz"), "sync").unwrap();
|
||||
|
||||
sync_source_files(src.path(), dst.path()).unwrap();
|
||||
|
||||
assert!(dst.path().join("main.tex").exists());
|
||||
assert!(!dst.path().join("main.aux").exists());
|
||||
assert!(!dst.path().join("main.log").exists());
|
||||
assert!(!dst.path().join("main.pdf").exists());
|
||||
assert!(!dst.path().join("main.synctex.gz").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync_source_files_recursive_and_skips_hidden() {
|
||||
let src = tempfile::tempdir().unwrap();
|
||||
let dst = tempfile::tempdir().unwrap();
|
||||
|
||||
std::fs::create_dir_all(src.path().join("chapters")).unwrap();
|
||||
std::fs::create_dir_all(src.path().join(".claudeprism")).unwrap();
|
||||
std::fs::write(src.path().join("chapters").join("ch1.tex"), "ch1").unwrap();
|
||||
std::fs::write(src.path().join("chapters").join("ch1.aux"), "aux").unwrap();
|
||||
std::fs::write(src.path().join(".claudeprism").join("data"), "data").unwrap();
|
||||
|
||||
sync_source_files(src.path(), dst.path()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(dst.path().join("chapters").join("ch1.tex")).unwrap(),
|
||||
"ch1"
|
||||
);
|
||||
assert!(!dst.path().join("chapters").join("ch1.aux").exists());
|
||||
assert!(!dst.path().join(".claudeprism").exists());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -434,4 +434,103 @@ mod tests {
|
|||
let (name, _) = extract_command_info(file, base).unwrap();
|
||||
assert_eq!(name, "my-command");
|
||||
}
|
||||
|
||||
// --- load_command_from_file integration tests ---
|
||||
|
||||
#[test]
|
||||
fn test_load_command_from_file_simple() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let file = dir.path().join("greet.md");
|
||||
fs::write(&file, "Hello $ARGUMENTS, run !`echo hi` and check @main.tex").unwrap();
|
||||
|
||||
let cmd = load_command_from_file(&file, dir.path(), "project").unwrap();
|
||||
assert_eq!(cmd.name, "greet");
|
||||
assert!(cmd.namespace.is_none());
|
||||
assert_eq!(cmd.full_command, "/greet");
|
||||
assert_eq!(cmd.scope, "project");
|
||||
assert!(cmd.accepts_arguments);
|
||||
assert!(cmd.has_bash_commands);
|
||||
assert!(cmd.has_file_references);
|
||||
assert!(cmd.description.is_none());
|
||||
assert!(cmd.allowed_tools.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_command_from_file_with_frontmatter() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let file = dir.path().join("lint.md");
|
||||
let content = "---\ndescription: Run linter\nallowed-tools:\n - Bash\n - Read\n---\nLint the project";
|
||||
fs::write(&file, content).unwrap();
|
||||
|
||||
let cmd = load_command_from_file(&file, dir.path(), "user").unwrap();
|
||||
assert_eq!(cmd.name, "lint");
|
||||
assert_eq!(cmd.description.unwrap(), "Run linter");
|
||||
assert_eq!(cmd.allowed_tools, vec!["Bash", "Read"]);
|
||||
assert_eq!(cmd.content, "Lint the project");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_command_from_file_nested_namespace() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let ns_dir = dir.path().join("tools").join("rust");
|
||||
fs::create_dir_all(&ns_dir).unwrap();
|
||||
let file = ns_dir.join("clippy.md");
|
||||
fs::write(&file, "Run clippy").unwrap();
|
||||
|
||||
let cmd = load_command_from_file(&file, dir.path(), "project").unwrap();
|
||||
assert_eq!(cmd.name, "clippy");
|
||||
assert_eq!(cmd.namespace.unwrap(), "tools:rust");
|
||||
assert_eq!(cmd.full_command, "/tools:rust:clippy");
|
||||
}
|
||||
|
||||
// --- find_markdown_files integration tests ---
|
||||
|
||||
#[test]
|
||||
fn test_find_markdown_files_collects_md_only() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
fs::write(dir.path().join("cmd1.md"), "a").unwrap();
|
||||
fs::write(dir.path().join("cmd2.md"), "b").unwrap();
|
||||
fs::write(dir.path().join("notes.txt"), "c").unwrap();
|
||||
fs::write(dir.path().join("data.json"), "d").unwrap();
|
||||
|
||||
let mut files = Vec::new();
|
||||
find_markdown_files(dir.path(), &mut files);
|
||||
|
||||
assert_eq!(files.len(), 2);
|
||||
let names: Vec<String> = files.iter().map(|f| f.file_name().unwrap().to_string_lossy().to_string()).collect();
|
||||
assert!(names.contains(&"cmd1.md".to_string()));
|
||||
assert!(names.contains(&"cmd2.md".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_markdown_files_skips_hidden_dirs() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
fs::write(dir.path().join("visible.md"), "ok").unwrap();
|
||||
let hidden = dir.path().join(".hidden");
|
||||
fs::create_dir_all(&hidden).unwrap();
|
||||
fs::write(hidden.join("secret.md"), "hidden").unwrap();
|
||||
|
||||
let mut files = Vec::new();
|
||||
find_markdown_files(dir.path(), &mut files);
|
||||
|
||||
assert_eq!(files.len(), 1);
|
||||
assert_eq!(files[0].file_name().unwrap().to_str().unwrap(), "visible.md");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_markdown_files_recursive() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
fs::write(dir.path().join("top.md"), "top").unwrap();
|
||||
let sub = dir.path().join("sub");
|
||||
fs::create_dir_all(&sub).unwrap();
|
||||
fs::write(sub.join("nested.md"), "nested").unwrap();
|
||||
|
||||
let mut files = Vec::new();
|
||||
find_markdown_files(dir.path(), &mut files);
|
||||
|
||||
assert_eq!(files.len(), 2);
|
||||
let names: Vec<String> = files.iter().map(|f| f.file_name().unwrap().to_string_lossy().to_string()).collect();
|
||||
assert!(names.contains(&"top.md".to_string()));
|
||||
assert!(names.contains(&"nested.md".to_string()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue