mirror of
https://github.com/delibae/claude-prism.git
synced 2026-10-07 02:58:00 +00:00
test: add additional Rust integration tests for history and slash_commands
history.rs (+5): deleted file snapshot/diff, non-adjacent diff, duplicate label error, restore commit verification slash_commands.rs (+8): remove_empty_dirs (3), create_default_commands (1), slash_command_save project scope (5: basic, frontmatter, namespace, empty name error, invalid scope error) Total Rust tests: 114 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
17dcb3f273
commit
b6a31ebcc7
2 changed files with 260 additions and 0 deletions
|
|
@ -848,4 +848,95 @@ mod tests {
|
|||
let content = fs::read_to_string(&excludes_path).unwrap();
|
||||
assert!(content.contains(".prism/"), "should migrate to include .prism/");
|
||||
}
|
||||
|
||||
// ─── edge cases ───
|
||||
|
||||
#[test]
|
||||
fn test_history_snapshot_deleted_file() {
|
||||
let dir = setup_project(&[("a.tex", "aaa"), ("b.tex", "bbb")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
// Delete a file
|
||||
fs::remove_file(dir.path().join("b.tex")).unwrap();
|
||||
|
||||
let snap = history_snapshot(r.clone(), "delete b".into()).unwrap().unwrap();
|
||||
assert!(!snap.changed_files.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_diff_deleted_file() {
|
||||
let dir = setup_project(&[("a.tex", "keep"), ("b.tex", "remove me")]);
|
||||
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();
|
||||
|
||||
fs::remove_file(dir.path().join("b.tex")).unwrap();
|
||||
let snap = history_snapshot(r.clone(), "delete b".into()).unwrap().unwrap();
|
||||
|
||||
let diffs = history_diff(r, init_id, snap.id).unwrap();
|
||||
let d = diffs.iter().find(|d| d.file_path == "b.tex").unwrap();
|
||||
assert_eq!(d.status, "deleted");
|
||||
assert_eq!(d.old_content.as_deref(), Some("remove me"));
|
||||
assert!(d.new_content.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_diff_nonadjacent_snapshots() {
|
||||
let dir = setup_project(&[("a.tex", "v1")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
let list0 = history_list(r.clone(), 1, 0).unwrap();
|
||||
let init_id = list0[0].id.clone();
|
||||
|
||||
fs::write(dir.path().join("a.tex"), "v2").unwrap();
|
||||
history_snapshot(r.clone(), "s1".into()).unwrap();
|
||||
|
||||
fs::write(dir.path().join("a.tex"), "v3").unwrap();
|
||||
let snap3 = history_snapshot(r.clone(), "s2".into()).unwrap().unwrap();
|
||||
|
||||
// Diff from init directly to s2 (skipping s1)
|
||||
let diffs = history_diff(r, init_id, snap3.id).unwrap();
|
||||
let d = diffs.iter().find(|d| d.file_path == "a.tex").unwrap();
|
||||
assert_eq!(d.old_content.as_deref(), Some("v1"));
|
||||
assert_eq!(d.new_content.as_deref(), Some("v3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_add_duplicate_label_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();
|
||||
|
||||
history_add_label(r.clone(), id.clone(), "dup".into()).unwrap();
|
||||
// Adding same label again should error
|
||||
let result = history_add_label(r, id, "dup".into());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_restore_creates_restore_commit() {
|
||||
let dir = setup_project(&[("main.tex", "original")]);
|
||||
let r = root(&dir);
|
||||
history_init(r.clone()).unwrap();
|
||||
|
||||
let init_list = history_list(r.clone(), 1, 0).unwrap();
|
||||
let init_id = init_list[0].id.clone();
|
||||
|
||||
fs::write(dir.path().join("main.tex"), "changed").unwrap();
|
||||
history_snapshot(r.clone(), "change".into()).unwrap();
|
||||
|
||||
history_restore(r.clone(), init_id).unwrap();
|
||||
|
||||
// Should now have 4 entries: init, change, restore
|
||||
let list = history_list(r, 10, 0).unwrap();
|
||||
assert_eq!(list.len(), 3);
|
||||
assert!(list.iter().any(|s| s.message.contains("[restore]")));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -533,4 +533,173 @@ mod tests {
|
|||
assert!(names.contains(&"top.md".to_string()));
|
||||
assert!(names.contains(&"nested.md".to_string()));
|
||||
}
|
||||
|
||||
// --- remove_empty_dirs integration tests ---
|
||||
|
||||
#[test]
|
||||
fn test_remove_empty_dirs_removes_nested_empty() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let deep = dir.path().join("a").join("b").join("c");
|
||||
fs::create_dir_all(&deep).unwrap();
|
||||
|
||||
remove_empty_dirs(&deep);
|
||||
|
||||
assert!(!dir.path().join("a").exists(), "entire empty chain should be removed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_empty_dirs_stops_at_nonempty() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let parent = dir.path().join("a");
|
||||
let child = parent.join("b");
|
||||
fs::create_dir_all(&child).unwrap();
|
||||
fs::write(parent.join("keep.txt"), "x").unwrap();
|
||||
|
||||
remove_empty_dirs(&child);
|
||||
|
||||
assert!(!child.exists(), "empty child should be removed");
|
||||
assert!(parent.exists(), "parent with file should be kept");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_empty_dirs_nonexistent_is_noop() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let fake = dir.path().join("nonexistent");
|
||||
// Should not panic
|
||||
remove_empty_dirs(&fake);
|
||||
}
|
||||
|
||||
// --- create_default_commands ---
|
||||
|
||||
#[test]
|
||||
fn test_create_default_commands_structure() {
|
||||
let cmds = create_default_commands();
|
||||
assert_eq!(cmds.len(), 3);
|
||||
let names: Vec<&str> = cmds.iter().map(|c| c.name.as_str()).collect();
|
||||
assert!(names.contains(&"add-dir"));
|
||||
assert!(names.contains(&"init"));
|
||||
assert!(names.contains(&"review"));
|
||||
for cmd in &cmds {
|
||||
assert_eq!(cmd.scope, "default");
|
||||
assert!(cmd.full_command.starts_with('/'));
|
||||
}
|
||||
}
|
||||
|
||||
// --- slash_command_save integration tests ---
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_slash_command_save_project_scope() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let project_path = dir.path().to_string_lossy().to_string();
|
||||
|
||||
let result = slash_command_save(
|
||||
"project".into(),
|
||||
"test-cmd".into(),
|
||||
None,
|
||||
"Do something".into(),
|
||||
None,
|
||||
vec![],
|
||||
Some(project_path.clone()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let cmd = result.unwrap();
|
||||
assert_eq!(cmd.name, "test-cmd");
|
||||
assert_eq!(cmd.full_command, "/test-cmd");
|
||||
assert_eq!(cmd.content, "Do something");
|
||||
|
||||
// Verify file was created
|
||||
let file = dir.path().join(".claude").join("commands").join("test-cmd.md");
|
||||
assert!(file.exists());
|
||||
assert_eq!(fs::read_to_string(&file).unwrap(), "Do something");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_slash_command_save_with_frontmatter() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let project_path = dir.path().to_string_lossy().to_string();
|
||||
|
||||
let cmd = slash_command_save(
|
||||
"project".into(),
|
||||
"lint".into(),
|
||||
None,
|
||||
"Run the linter".into(),
|
||||
Some("Lint all files".into()),
|
||||
vec!["Bash".into(), "Read".into()],
|
||||
Some(project_path),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(cmd.description.unwrap(), "Lint all files");
|
||||
assert_eq!(cmd.allowed_tools, vec!["Bash", "Read"]);
|
||||
|
||||
// Verify frontmatter in file
|
||||
let file = dir.path().join(".claude").join("commands").join("lint.md");
|
||||
let content = fs::read_to_string(&file).unwrap();
|
||||
assert!(content.starts_with("---\n"));
|
||||
assert!(content.contains("description: Lint all files"));
|
||||
assert!(content.contains("- Bash"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_slash_command_save_with_namespace() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let project_path = dir.path().to_string_lossy().to_string();
|
||||
|
||||
let cmd = slash_command_save(
|
||||
"project".into(),
|
||||
"clippy".into(),
|
||||
Some("tools:rust".into()),
|
||||
"Run clippy".into(),
|
||||
None,
|
||||
vec![],
|
||||
Some(project_path),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(cmd.name, "clippy");
|
||||
assert_eq!(cmd.namespace.unwrap(), "tools:rust");
|
||||
assert_eq!(cmd.full_command, "/tools:rust:clippy");
|
||||
|
||||
// Verify nested directory structure
|
||||
let file = dir.path().join(".claude").join("commands").join("tools").join("rust").join("clippy.md");
|
||||
assert!(file.exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_slash_command_save_empty_name_errors() {
|
||||
let result = slash_command_save(
|
||||
"project".into(),
|
||||
"".into(),
|
||||
None,
|
||||
"content".into(),
|
||||
None,
|
||||
vec![],
|
||||
Some("/tmp".into()),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("empty"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_slash_command_save_invalid_scope_errors() {
|
||||
let result = slash_command_save(
|
||||
"global".into(),
|
||||
"test".into(),
|
||||
None,
|
||||
"content".into(),
|
||||
None,
|
||||
vec![],
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("Invalid scope"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue