huskies: merge 874
This commit is contained in:
@@ -103,6 +103,7 @@ pub async fn handle_delete(
|
|||||||
// Delete from the content store and CRDT.
|
// Delete from the content store and CRDT.
|
||||||
crate::db::delete_content(&story_id);
|
crate::db::delete_content(&story_id);
|
||||||
crate::db::delete_item(&story_id);
|
crate::db::delete_item(&story_id);
|
||||||
|
let _ = crate::crdt_state::evict_item(&story_id);
|
||||||
|
|
||||||
// Build the response.
|
// Build the response.
|
||||||
let stage_label = stage_display_name(&stage);
|
let stage_label = stage_display_name(&stage);
|
||||||
@@ -244,6 +245,53 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn handle_delete_writes_crdt_tombstone() {
|
||||||
|
// Initialise the global CRDT singleton (no-op if already done).
|
||||||
|
crate::crdt_state::init_for_test();
|
||||||
|
|
||||||
|
let story_id = "9977_story_crdt_tombstone_check";
|
||||||
|
let story_number = "9977";
|
||||||
|
|
||||||
|
// Seed in CRDT.
|
||||||
|
crate::crdt_state::write_item(
|
||||||
|
story_id,
|
||||||
|
"1_backlog",
|
||||||
|
Some("CRDT Tombstone Check"),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Seed in content store so find_story_by_number can resolve it.
|
||||||
|
crate::db::ensure_content_store();
|
||||||
|
crate::db::write_item_with_content(
|
||||||
|
story_id,
|
||||||
|
"1_backlog",
|
||||||
|
"---\nname: CRDT Tombstone Check\n---\n\n# Story 9977\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
let project_root = tmp.path();
|
||||||
|
let agents = std::sync::Arc::new(crate::agents::AgentPool::new_test(3002));
|
||||||
|
handle_delete("Timmy", story_number, project_root, &agents).await;
|
||||||
|
|
||||||
|
// The CRDT dump includes tombstoned entries — verify is_deleted = true.
|
||||||
|
let dump = crate::crdt_state::dump_crdt_state(Some(story_id));
|
||||||
|
let deleted = dump
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.any(|i| i.story_id.as_deref() == Some(story_id) && i.is_deleted);
|
||||||
|
assert!(
|
||||||
|
deleted,
|
||||||
|
"CRDT must show is_deleted=true for '{story_id}' after handle_delete"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn handle_delete_removes_story_file_and_confirms() {
|
async fn handle_delete_removes_story_file_and_confirms() {
|
||||||
let tmp = tempfile::tempdir().unwrap();
|
let tmp = tempfile::tempdir().unwrap();
|
||||||
|
|||||||
@@ -249,8 +249,13 @@ pub fn evict_item(story_id: &str) -> Result<(), String> {
|
|||||||
|
|
||||||
// Resolve the item's OpId before the closure (the closure will mutably
|
// Resolve the item's OpId before the closure (the closure will mutably
|
||||||
// borrow `state`, so we can't access `state.crdt.doc.items` from inside).
|
// borrow `state`, so we can't access `state.crdt.doc.items` from inside).
|
||||||
|
//
|
||||||
|
// `rebuild_index` uses `items.iter()` which skips the invisible ROOT
|
||||||
|
// sentinel, so `idx` is a 0-based visible-item position. `id_at` counts
|
||||||
|
// ALL non-deleted ops including the sentinel at position 0, so we must
|
||||||
|
// add 1 to translate from visible-item position to `id_at` position.
|
||||||
let item_id =
|
let item_id =
|
||||||
state.crdt.doc.items.id_at(idx).ok_or_else(|| {
|
state.crdt.doc.items.id_at(idx + 1).ok_or_else(|| {
|
||||||
format!("Item index {idx} for '{story_id}' did not resolve to an OpId")
|
format!("Item index {idx} for '{story_id}' did not resolve to an OpId")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user