feat: resolve local review comments manually
This commit is contained in:
+81
-8
@@ -87,6 +87,7 @@ pub struct Comment {
|
||||
pub id: u64,
|
||||
pub anchor: Anchor,
|
||||
pub body: String,
|
||||
pub resolved: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@@ -130,7 +131,7 @@ impl Review {
|
||||
fs::canonicalize(&root).unwrap_or(root)
|
||||
};
|
||||
Self {
|
||||
version: 2,
|
||||
version: 3,
|
||||
root,
|
||||
summary: String::new(),
|
||||
verdict: None,
|
||||
@@ -142,7 +143,7 @@ impl Review {
|
||||
}
|
||||
|
||||
fn validate(&self) -> Result<(), String> {
|
||||
if self.version != 2 {
|
||||
if self.version != 3 {
|
||||
return Err(format!("Unsupported review version {}", self.version));
|
||||
}
|
||||
if !self.root.is_absolute() || !self.root.is_dir() {
|
||||
@@ -188,10 +189,22 @@ impl Review {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn from_json(bytes: &[u8]) -> Result<Self, String> {
|
||||
#[derive(Deserialize)]
|
||||
struct Header {
|
||||
version: u32,
|
||||
}
|
||||
let header: Header = serde_json::from_slice(bytes)
|
||||
.map_err(|error| format!("Invalid review JSON: {error}"))?;
|
||||
if header.version != 3 {
|
||||
return Err(format!("Unsupported review version {}", header.version));
|
||||
}
|
||||
serde_json::from_slice(bytes).map_err(|error| format!("Invalid review JSON: {error}"))
|
||||
}
|
||||
|
||||
pub fn load(path: &Path, root: &Path) -> Result<Self, String> {
|
||||
let bytes = read_file(path)?.ok_or("Review file does not exist")?;
|
||||
let mut review: Self = serde_json::from_slice(&bytes)
|
||||
.map_err(|error| format!("Invalid review JSON: {error}"))?;
|
||||
let mut review = Self::from_json(&bytes)?;
|
||||
review.validate()?;
|
||||
let expected = fs::canonicalize(root).map_err(|error| error.to_string())?;
|
||||
let actual = fs::canonicalize(&review.root).map_err(|error| error.to_string())?;
|
||||
@@ -483,8 +496,7 @@ fn stored_review(row: &rusqlite::Row<'_>, root: &Path) -> Result<Review, String>
|
||||
if bytes.len() > LIMIT {
|
||||
return Err("Review exceeds the 64 MiB limit".into());
|
||||
}
|
||||
let mut review: Review = serde_json::from_slice(bytes)
|
||||
.map_err(|error| format!("Invalid stored review JSON: {error}"))?;
|
||||
let mut review = Review::from_json(bytes)?;
|
||||
review.validate()?;
|
||||
if review.root != root {
|
||||
return Err("Stored review belongs to a different repository".into());
|
||||
@@ -578,6 +590,7 @@ mod tests {
|
||||
content: "a👩🔬e\u{301}\r\n界x\n".into(),
|
||||
},
|
||||
body: "Please preserve 👩🔬".into(),
|
||||
resolved: true,
|
||||
});
|
||||
{
|
||||
let mut store = Store::open(&storage).unwrap();
|
||||
@@ -606,7 +619,8 @@ mod tests {
|
||||
loaded.save(&path).unwrap();
|
||||
let exported = fs::read(&path).unwrap();
|
||||
let json: serde_json::Value = serde_json::from_slice(&exported).unwrap();
|
||||
assert_eq!(json["version"], 2);
|
||||
assert_eq!(json["comments"][0]["resolved"], true);
|
||||
assert_eq!(json["version"], 3);
|
||||
assert!(json.get("storage_id").is_none());
|
||||
assert!(json.get("storage_revision").is_none());
|
||||
loaded.summary = "Database only".into();
|
||||
@@ -857,6 +871,7 @@ mod tests {
|
||||
content: "first\nlast\n".into(),
|
||||
},
|
||||
body: "Please adjust this".into(),
|
||||
resolved: false,
|
||||
});
|
||||
review.save(&path).unwrap();
|
||||
let mut loaded = Review::load(&path, &root).unwrap();
|
||||
@@ -895,7 +910,7 @@ mod tests {
|
||||
assert!(Review::new(root.clone()).save(&path).is_err());
|
||||
let valid = fs::read(&path).unwrap();
|
||||
for (field, value) in [
|
||||
("version", serde_json::json!(3)),
|
||||
("version", serde_json::json!(2)),
|
||||
("root", serde_json::json!(root.join("other"))),
|
||||
(
|
||||
"comments",
|
||||
@@ -948,6 +963,46 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stored_version_two_is_rejected_without_rewriting_it() {
|
||||
let directory = directory();
|
||||
let root = directory.0.canonicalize().unwrap();
|
||||
let mut store = Store::open(&root.join("storage")).unwrap();
|
||||
let mut review = Review::new(root.clone());
|
||||
store.save(&mut review).unwrap();
|
||||
let mut old = serde_json::to_value(&review).unwrap();
|
||||
old["version"] = 2.into();
|
||||
let document = serde_json::to_string(&old).unwrap();
|
||||
store
|
||||
.connection
|
||||
.execute(
|
||||
"UPDATE reviews SET document = ?1 WHERE id = ?2",
|
||||
params![document, review.storage_id.unwrap()],
|
||||
)
|
||||
.unwrap();
|
||||
assert!(
|
||||
store
|
||||
.load(&root, review.storage_id.unwrap())
|
||||
.unwrap_err()
|
||||
.contains("Unsupported review version 2")
|
||||
);
|
||||
assert!(
|
||||
store
|
||||
.list(&root)
|
||||
.unwrap_err()
|
||||
.contains("Unsupported review version 2")
|
||||
);
|
||||
let preserved: String = store
|
||||
.connection
|
||||
.query_row(
|
||||
"SELECT document FROM reviews WHERE id = ?1",
|
||||
[review.storage_id.unwrap()],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(preserved, document);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_grapheme_ranges_roundtrip_and_reject_invalid_coordinates() {
|
||||
use crate::plugin::TextPosition;
|
||||
@@ -966,12 +1021,30 @@ mod tests {
|
||||
content: "a👩🔬e\u{301}\n界x\n".into(),
|
||||
},
|
||||
body: "Exact text".into(),
|
||||
resolved: false,
|
||||
});
|
||||
review.save(&path).unwrap();
|
||||
let mut loaded = Review::load(&path, &root).unwrap();
|
||||
assert_eq!(loaded.comments, review.comments);
|
||||
assert_eq!(loaded.comments[0].anchor.rows(), 0..=1);
|
||||
let valid = serde_json::to_value(&review).unwrap();
|
||||
let mut missing_resolution = valid.clone();
|
||||
missing_resolution["comments"][0]
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.remove("resolved");
|
||||
for version in [2, 3] {
|
||||
missing_resolution["version"] = version.into();
|
||||
let bytes = serde_json::to_vec(&missing_resolution).unwrap();
|
||||
fs::write(&path, &bytes).unwrap();
|
||||
let error = Review::load(&path, &root).unwrap_err();
|
||||
assert!(error.contains(if version == 2 {
|
||||
"Unsupported review version 2"
|
||||
} else {
|
||||
"missing field `resolved`"
|
||||
}));
|
||||
assert_eq!(fs::read(&path).unwrap(), bytes);
|
||||
}
|
||||
for (field, value) in [
|
||||
("start", serde_json::json!({"row": 0, "column": 4})),
|
||||
("end", serde_json::json!({"row": 1, "column": 3})),
|
||||
|
||||
Reference in New Issue
Block a user