refactor: normalize review and comment storage
This commit is contained in:
+117
-45
@@ -151,6 +151,10 @@ fn incompatible_database_schemas_are_preserved() {
|
||||
for schema in [
|
||||
"CREATE TABLE other (value TEXT); PRAGMA user_version = 42",
|
||||
"CREATE TABLE other (value TEXT); PRAGMA user_version = 1",
|
||||
"CREATE TABLE reviews (id INTEGER PRIMARY KEY, root TEXT NOT NULL,
|
||||
revision INTEGER NOT NULL, document TEXT NOT NULL) STRICT;
|
||||
INSERT INTO reviews VALUES (1, '/old/repo', 1, '{\"version\":3}');
|
||||
PRAGMA user_version = 1",
|
||||
"",
|
||||
] {
|
||||
let directory = directory();
|
||||
@@ -165,44 +169,73 @@ fn incompatible_database_schemas_are_preserved() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_or_mismatched_database_documents_are_rejected() {
|
||||
let other = directory();
|
||||
fn malformed_database_rows_are_rejected_before_loading_snapshots() {
|
||||
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());
|
||||
review.comments.push(Comment {
|
||||
id: 1,
|
||||
body: "Feedback".into(),
|
||||
resolved: false,
|
||||
anchor: Anchor {
|
||||
revision: "snapshot".into(),
|
||||
path: "file.rs".into(),
|
||||
side: Side::After,
|
||||
start: crate::plugin::TextPosition { row: 0, column: 0 },
|
||||
end: crate::plugin::TextPosition { row: 0, column: 1 },
|
||||
content: "界\n".into(),
|
||||
},
|
||||
});
|
||||
store.save(&mut review).unwrap();
|
||||
let id = review.storage_id.unwrap();
|
||||
let mut wrong_root = serde_json::to_value(&review).unwrap();
|
||||
wrong_root["root"] = serde_json::json!(other.0.canonicalize().unwrap());
|
||||
let mut invalid_version = serde_json::to_value(&review).unwrap();
|
||||
invalid_version["version"] = 1.into();
|
||||
let mut inherited_identity = serde_json::to_value(&review).unwrap();
|
||||
inherited_identity["storage_id"] = id.into();
|
||||
for document in [
|
||||
"{".to_owned(),
|
||||
serde_json::to_string(&wrong_root).unwrap(),
|
||||
serde_json::to_string(&invalid_version).unwrap(),
|
||||
serde_json::to_string(&inherited_identity).unwrap(),
|
||||
for update in [
|
||||
"resolved = 2",
|
||||
"side = 'invalid'",
|
||||
"id = '0'",
|
||||
"id = '18446744073709551616'",
|
||||
"start_row = -1",
|
||||
"end_column = -1",
|
||||
] {
|
||||
store
|
||||
.connection
|
||||
.execute(
|
||||
"UPDATE reviews SET document = ?1 WHERE id = ?2",
|
||||
params![document, id],
|
||||
)
|
||||
.unwrap();
|
||||
assert!(store.load(&root, id).is_err());
|
||||
assert!(store.list(&root).is_err());
|
||||
assert!(
|
||||
store
|
||||
.connection
|
||||
.execute(
|
||||
&format!("UPDATE comments SET {update} WHERE review_id = ?1"),
|
||||
[id],
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(store.load(&root, id).unwrap().comments, review.comments);
|
||||
}
|
||||
store
|
||||
.connection
|
||||
.execute_batch("PRAGMA ignore_check_constraints = ON")
|
||||
.unwrap();
|
||||
for update in [
|
||||
"path = '../outside'",
|
||||
"end_column = 2",
|
||||
"resolved = 2",
|
||||
"side = 'invalid'",
|
||||
"anchor_revision = ''",
|
||||
"id = '0'",
|
||||
"position = 1",
|
||||
] {
|
||||
store
|
||||
.connection
|
||||
.execute(
|
||||
&format!("UPDATE comments SET {update} WHERE review_id = ?1"),
|
||||
[id],
|
||||
)
|
||||
.unwrap();
|
||||
assert!(store.load(&root, id).is_err(), "{update}");
|
||||
assert!(store.list(&root).is_err(), "{update}");
|
||||
store.save(&mut review).unwrap();
|
||||
}
|
||||
store
|
||||
.connection
|
||||
.execute(
|
||||
"UPDATE reviews SET document = CAST(zeroblob(?1) AS TEXT) WHERE id = ?2",
|
||||
"UPDATE comments SET content = CAST(zeroblob(?1) AS TEXT) WHERE review_id = ?2",
|
||||
params![LIMIT as i64 + 1, id],
|
||||
)
|
||||
.unwrap();
|
||||
@@ -406,43 +439,82 @@ fn persistence_validates_anchors_and_preserves_files_on_failure() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stored_version_two_is_rejected_without_rewriting_it() {
|
||||
fn comment_rows_preserve_order_full_ids_and_atomic_replacement() {
|
||||
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());
|
||||
review.comments = [u64::MAX, 7]
|
||||
.into_iter()
|
||||
.map(|id| Comment {
|
||||
id,
|
||||
body: format!("Feedback {id}"),
|
||||
resolved: id == 7,
|
||||
anchor: Anchor {
|
||||
revision: "snapshot".into(),
|
||||
path: "source.rs".into(),
|
||||
side: Side::Before,
|
||||
start: crate::plugin::TextPosition { row: 0, column: 0 },
|
||||
end: crate::plugin::TextPosition { row: 0, column: 1 },
|
||||
content: "界\n".into(),
|
||||
},
|
||||
})
|
||||
.collect();
|
||||
review.summary = "Original".into();
|
||||
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();
|
||||
let id = review.storage_id.unwrap();
|
||||
assert_eq!(store.load(&root, id).unwrap().comments, review.comments);
|
||||
assert!(
|
||||
store
|
||||
.connection
|
||||
.execute(
|
||||
"UPDATE comments SET review_id = ?1 WHERE review_id = ?2",
|
||||
params![id + 1, id],
|
||||
)
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
store
|
||||
.connection
|
||||
.execute("UPDATE comments SET id = '7' WHERE review_id = ?1", [id],)
|
||||
.is_err()
|
||||
);
|
||||
store
|
||||
.connection
|
||||
.execute(
|
||||
"UPDATE reviews SET document = ?1 WHERE id = ?2",
|
||||
params![document, review.storage_id.unwrap()],
|
||||
.execute_batch(
|
||||
"CREATE TEMP TRIGGER reject_comment BEFORE INSERT ON comments
|
||||
WHEN NEW.id = '7' BEGIN SELECT RAISE(ABORT, 'blocked comment'); END",
|
||||
)
|
||||
.unwrap();
|
||||
let revision = review.storage_revision;
|
||||
let original = review.comments.clone();
|
||||
review.summary = "Unsaved".into();
|
||||
review.comments[0].body = "Changed".into();
|
||||
assert!(
|
||||
store
|
||||
.load(&root, review.storage_id.unwrap())
|
||||
.save(&mut review)
|
||||
.unwrap_err()
|
||||
.contains("Unsupported review version 2")
|
||||
.contains("blocked comment")
|
||||
);
|
||||
assert!(
|
||||
store
|
||||
.list(&root)
|
||||
.unwrap_err()
|
||||
.contains("Unsupported review version 2")
|
||||
);
|
||||
let preserved: String = store
|
||||
assert_eq!(review.storage_revision, revision);
|
||||
let loaded = store.load(&root, id).unwrap();
|
||||
assert_eq!(loaded.summary, "Original");
|
||||
assert_eq!(loaded.comments, original);
|
||||
assert_eq!(loaded.storage_revision, revision);
|
||||
store
|
||||
.connection
|
||||
.query_row(
|
||||
"SELECT document FROM reviews WHERE id = ?1",
|
||||
[review.storage_id.unwrap()],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.execute_batch("DROP TRIGGER reject_comment")
|
||||
.unwrap();
|
||||
assert_eq!(preserved, document);
|
||||
review.comments.reverse();
|
||||
store.save(&mut review).unwrap();
|
||||
assert_eq!(store.load(&root, id).unwrap().comments, review.comments);
|
||||
review.comments.pop();
|
||||
store.save(&mut review).unwrap();
|
||||
assert_eq!(store.load(&root, id).unwrap().comments, review.comments);
|
||||
review.comments.clear();
|
||||
store.save(&mut review).unwrap();
|
||||
assert!(store.load(&root, id).unwrap().comments.is_empty());
|
||||
assert_eq!(store.list(&root).unwrap()[0].summary, "Unsaved");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user