From 57e5817759454103af57785fe4a566b34a18c1a7 Mon Sep 17 00:00:00 2001 From: clover caruso Date: Fri, 11 Sep 2026 04:01:57 -0700 Subject: [PATCH] test: retain recovery evidence across remote restores Exercise empty, unsent, and uncertain queues against an older remote image across cache reopen. Verify that historical receipts do not trigger replay and recovery archives retain the original branch. Assisted-by: gpt-6-astra --- crates/notebook/src/sync.rs | 2 +- crates/notebook/tests/sync.rs | 92 +++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/crates/notebook/src/sync.rs b/crates/notebook/src/sync.rs index 9b00a867c58fe906751cb53126a0e176bb704da6..9c9426dbc8536bcde85cb7ff97a593a16beef255 100644 --- a/crates/notebook/src/sync.rs +++ b/crates/notebook/src/sync.rs @@ -35,7 +35,7 @@ pub enum EditStatus { revision: ExGuid, }, Conflict(ConflictKind), - /// Revision of the intent's space when its complete effect was confirmed. + /// Historical confirmation; later remote edits or restores may remove the effect. Published { revision: ExGuid, }, diff --git a/crates/notebook/tests/sync.rs b/crates/notebook/tests/sync.rs index 2549340e4f8052529011cb71b350316ed0963fcf..8702b0b6c6ec67e1308a0373e4983df9990348f9 100644 --- a/crates/notebook/tests/sync.rs +++ b/crates/notebook/tests/sync.rs @@ -825,3 +825,95 @@ fn remote_changes_after_review_cannot_be_overwritten_by_the_reviewed_page() { ); } } + +#[test] +fn remote_restore_retains_historical_receipts_without_replaying_them() { + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("cache.sqlite"); + let source = onestore::create_section("restore.one", "Original", "Fixture").unwrap(); + let (_, object, _) = text(&source); + let cache = Replica::create(&path, &source).unwrap(); + let mut server = Server::new(&source); + let published = save(&cache, object, 0..0, "Published ").unwrap(); + let (_, receipt) = cache.sync_once(&mut server).unwrap().unwrap(); + assert!(matches!(receipt, EditStatus::Published { .. })); + let published_image = cache.snapshot().unwrap(); + cache + .export_recovery(directory.path().join("published.sqlite")) + .unwrap(); + drop(cache); + + server.visible.clone_from(&source); + server.durable.clone_from(&source); + let cache = Replica::open(&path).unwrap(); + assert_eq!(cache.sync_once(&mut server).unwrap(), None); + assert_eq!(cache.snapshot().unwrap(), source); + assert_eq!(cache.remote_snapshot().unwrap(), source); + assert_eq!(cache.status(published).unwrap(), Some(receipt)); + assert_eq!(server.publications, 1); + assert!(cache.pending().unwrap().is_empty()); + let archive = notebook::Recovery::open(directory.path().join("published.sqlite")).unwrap(); + assert_eq!(archive.snapshot().unwrap(), published_image); + assert_eq!(archive.status(published).unwrap(), Some(receipt)); + assert_eq!(text(&archive.snapshot().unwrap()).2, "Published Original"); + drop(cache); + let cache = Replica::open(path).unwrap(); + assert_eq!(text(&cache.snapshot().unwrap()).2, "Original"); + assert_eq!(cache.status(published).unwrap(), Some(receipt)); +} + +#[test] +fn remote_restore_rebases_unsent_work_but_never_replays_an_uncertain_attempt() { + for fault in [Fault::None, Fault::UnknownBefore, Fault::UnknownAfter] { + let uncertain = !matches!(fault, Fault::None); + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("cache.sqlite"); + let source = onestore::create_section("restore.one", "Original", "Fixture").unwrap(); + let (_, object, _) = text(&source); + let cache = Replica::create(&path, &source).unwrap(); + let mut server = Server::new(&source); + let published = save(&cache, object, 0..0, "Published ").unwrap(); + let (_, receipt) = cache.sync_once(&mut server).unwrap().unwrap(); + let end = u32::try_from(text(&cache.snapshot().unwrap()).2.encode_utf16().count()).unwrap(); + let queued = save(&cache, object, end..end, " Local").unwrap(); + if uncertain { + server.fault = fault; + assert!(cache.sync_once(&mut server).is_err()); + } + let prior_status = cache.status(queued).unwrap(); + let local = cache.snapshot().unwrap(); + let pending = cache.pending().unwrap(); + cache + .export_recovery(directory.path().join("before-restore.sqlite")) + .unwrap(); + drop(cache); + server.visible.clone_from(&source); + server.durable.clone_from(&source); + let attempts = server.publications; + let cache = Replica::open(&path).unwrap(); + let (_, status) = cache.sync_once(&mut server).unwrap().unwrap(); + assert_eq!(cache.status(published).unwrap(), Some(receipt)); + if uncertain { + assert!(matches!(status, EditStatus::AwaitingConfirmation { .. })); + assert_eq!(Some(status), prior_status); + assert_eq!(server.publications, attempts); + assert_eq!(server.visible, source); + assert_eq!(cache.snapshot().unwrap(), local); + assert_eq!(cache.pending().unwrap(), pending); + } else { + assert!(matches!(status, EditStatus::Published { .. })); + assert_eq!(server.publications, attempts + 1); + assert_eq!(text(&server.visible).2, "Original Local"); + assert!(cache.pending().unwrap().is_empty()); + } + let archive = + notebook::Recovery::open(directory.path().join("before-restore.sqlite")).unwrap(); + assert_eq!(archive.snapshot().unwrap(), local); + assert_eq!(archive.pending().unwrap(), pending); + assert_eq!(archive.status(queued).unwrap(), prior_status); + drop(cache); + let cache = Replica::open(&path).unwrap(); + assert_eq!(cache.status(queued).unwrap(), Some(status)); + assert_eq!(cache.status(published).unwrap(), Some(receipt)); + } +} -- 2.54.0