feat: delete local review comments from CLI

This commit is contained in:
2026-10-18 10:14:26 +02:00
parent a28444a9f2
commit 3c270ed7b5
8 changed files with 200 additions and 9 deletions
+130
View File
@@ -0,0 +1,130 @@
use std::{fs, path::PathBuf, process::Command};
use vamp::{
plugin::TextPosition,
review::{Anchor, Comment, Review, Side, Store, Verdict},
};
struct Directory(PathBuf);
impl Drop for Directory {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
#[test]
fn cli_deletes_only_the_requested_comment_and_preserves_failed_targets() {
let directory = Directory(
std::env::temp_dir().join(format!("vamp-review-cli-delete-{}", std::process::id())),
);
fs::create_dir(&directory.0).unwrap();
let root = directory.0.canonicalize().unwrap();
for name in ["repo", "other"] {
let output = Command::new("jj")
.args(["git", "init"])
.arg(root.join(name))
.env("JJ_CONFIG", "/dev/null")
.output()
.unwrap();
assert!(output.status.success(), "{:?}", output);
}
let config = root.join("config.rb");
fs::write(
&config,
"Vamp.configure { |config| config.review_directory = 'reviews' }",
)
.unwrap();
let storage = root.join("reviews");
let mut store = Store::open(&storage).unwrap();
let mut review = Review::new(root.join("repo"));
review.summary = "Keep summary".into();
review.verdict = Some(Verdict::ChangesRequested);
review.comments = [1, u64::MAX]
.into_iter()
.map(|id| Comment {
id,
body: format!("Feedback {id}"),
resolved: id == u64::MAX,
anchor: Anchor {
revision: "snapshot".into(),
path: "file.rs".into(),
side: Side::After,
start: TextPosition { row: 0, column: 0 },
end: TextPosition { row: 0, column: 1 },
content: "界\n".into(),
},
})
.collect();
store.save(&mut review).unwrap();
let mut other = Review::new(root.join("other"));
other.comments = review.comments.clone();
store.save(&mut other).unwrap();
let id = review.storage_id.unwrap();
let cli = |repo: &str, args: &[&str]| {
Command::new(env!("CARGO_BIN_EXE_vamp"))
.arg("--config")
.arg(&config)
.arg("--review")
.args(args)
.current_dir(root.join(repo))
.output()
.unwrap()
};
let id_text = id.to_string();
for (repo, args) in [
("repo", vec!["delete", "999", "1"]),
("repo", vec!["delete", &id_text, "2"]),
("other", vec!["delete", &id_text, "1"]),
] {
let output = cli(repo, &args);
assert!(!output.status.success(), "{:?}", output);
let loaded = store.load(&review.root, id).unwrap();
assert_eq!(loaded.storage_revision, review.storage_revision);
assert_eq!(loaded.comments, review.comments);
}
let output = cli(
"repo",
&["delete", &id_text, "18446744073709551615", "--json"],
);
assert!(output.status.success(), "{:?}", output);
let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
assert_eq!(json["id"], id);
assert_eq!(
json["counts"],
serde_json::json!({"total": 1, "resolved": 0, "unresolved": 1})
);
let loaded = store.load(&review.root, id).unwrap();
assert_eq!(loaded.storage_revision, review.storage_revision + 1);
assert_eq!(loaded.comments, review.comments[..1]);
assert_eq!(loaded.summary, review.summary);
assert_eq!(loaded.verdict, review.verdict);
assert!(store.save(&mut review).is_err()); // Stale editors cannot resurrect the deletion.
let output = cli("repo", &["delete", &id_text, "18446744073709551615"]);
assert!(!output.status.success());
assert_eq!(
store.load(&review.root, id).unwrap().storage_revision,
loaded.storage_revision
);
let output = cli("repo", &["delete", &id_text, "1"]);
assert!(output.status.success(), "{:?}", output);
assert!(
String::from_utf8(output.stdout)
.unwrap()
.contains("0 total")
);
let empty = store.load(&review.root, id).unwrap();
assert!(empty.comments.is_empty());
assert_eq!(empty.storage_revision, loaded.storage_revision + 1);
assert_eq!(empty.summary, review.summary);
assert_eq!(
store
.load(&other.root, other.storage_id.unwrap())
.unwrap()
.comments,
other.comments
);
drop(store);
fs::remove_dir_all(&storage).unwrap();
let output = cli("repo", &["delete", &id_text, "1"]);
assert!(!output.status.success());
assert!(!storage.exists());
}