authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-11 04:01:57-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-12 17:00:32-07:00
loge3d3b5f60eac76bbde6907df41c1133e1f089b13
treea85870138fd7184d9492f384696cb0d4eeda8fcb
parent57e5817759454103af57785fe4a566b34a18c1a7
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

test: preserve dependent conflicts through remote restore review

Verify stale review rejection, separate review of overlapping dependent work, durable identities across reopen, and recovery archive preservation. Assisted-by: gpt-6-astra

1 files changed, 78 insertions(+), 0 deletions(-)

crates/notebook/tests/sync.rs+78
...@@ -917,3 +917,81 @@ fn remote_restore_rebases_unsent_work_but_never_replays_an_uncertain_attempt() {...@@ -917,3 +917,81 @@ fn remote_restore_rebases_unsent_work_but_never_replays_an_uncertain_attempt() {
917 assert_eq!(cache.status(published).unwrap(), Some(receipt));917 assert_eq!(cache.status(published).unwrap(), Some(receipt));
918 }918 }
919}919}
920
921#[test]
922fn restore_conflicts_keep_dependent_work_and_reject_stale_review() {
923 let directory = tempfile::tempdir().unwrap();
924 let path = directory.path().join("cache.sqlite");
925 let source = onestore::create_section("restore.one", "Original", "Fixture").unwrap();
926 let (space, object, _) = text(&source);
927 let cache = Replica::create(&path, &source).unwrap();
928 let mut server = Server::new(&source);
929 let published = save(&cache, object, 0..0, "Published ").unwrap();
930 let (_, receipt) = cache.sync_once(&mut server).unwrap().unwrap();
931 let head = save(&cache, object, 0..9, "Revised").unwrap();
932 server.visible.clone_from(&source);
933 server.durable.clone_from(&source);
934 let conflict = EditStatus::Conflict(ConflictKind::ContentChanged);
935 assert_eq!(
936 cache.sync_once(&mut server).unwrap(),
937 Some((head, conflict))
938 );
939 let end = u32::try_from(text(&cache.snapshot().unwrap()).2.encode_utf16().count()).unwrap();
940 let dependent = save(&cache, object, end..end, " Later").unwrap();
941 assert_ne!(head, dependent);
942 let local = cache.snapshot().unwrap();
943 let pending = cache.pending().unwrap();
944 assert_eq!(pending.len(), 2);
945 cache
946 .export_recovery(directory.path().join("conflicted.sqlite"))
947 .unwrap();
948 drop(cache);
949 let cache = Replica::open(&path).unwrap();
950 assert_eq!(cache.status(head).unwrap(), Some(conflict));
951 assert_eq!(cache.pending().unwrap(), pending);
952 assert_eq!(cache.snapshot().unwrap(), local);
953 let reviewed = edited(&source, object, 0..0, "Revised ");
954 let changed = onestore::replace_text(&source, space, object, 8..8, " Remote").unwrap();
955 server.visible.clone_from(&changed);
956 server.durable.clone_from(&changed);
957 assert_eq!(
958 cache.sync_once(&mut server).unwrap(),
959 Some((head, conflict))
960 );
961 assert!(
962 matches!(cache.review_page(head, &local, &source, &reviewed),
963 Err(Error::Io(error)) if error.kind() == io::ErrorKind::ResourceBusy)
964 );
965 assert_eq!(cache.pending().unwrap(), pending);
966 assert_eq!(server.publications, 1);
967 let reviewed = edited(&changed, object, 0..0, "Revised ");
968 cache
969 .review_page(head, &local, &changed, &reviewed)
970 .unwrap();
971 assert_eq!(cache.pending().unwrap()[1], pending[1]);
972 drop(cache);
973 let cache = Replica::open(&path).unwrap();
974 assert!(matches!(cache.sync_once(&mut server).unwrap(),
975 Some((actual, EditStatus::Published { .. })) if actual == head));
976 assert_eq!(
977 cache.sync_once(&mut server).unwrap(),
978 Some((dependent, conflict))
979 );
980 assert_eq!(server.publications, 2);
981 let dependent_local = cache.snapshot().unwrap();
982 let current = cache.remote_snapshot().unwrap();
983 let reviewed = edited(&current, object, 16..16, " Later");
984 cache
985 .review_page(dependent, &dependent_local, &current, &reviewed)
986 .unwrap();
987 drop(cache);
988 let cache = Replica::open(&path).unwrap();
989 assert!(matches!(cache.sync_once(&mut server).unwrap(),
990 Some((actual, EditStatus::Published { .. })) if actual == dependent));
991 assert_eq!(text(&server.durable).2, "Revised Original Later Remote");
992 assert_eq!(cache.status(published).unwrap(), Some(receipt));
993 let archive = notebook::Recovery::open(directory.path().join("conflicted.sqlite")).unwrap();
994 assert_eq!(archive.snapshot().unwrap(), local);
995 assert_eq!(archive.pending().unwrap(), pending);
996 assert_eq!(archive.status(head).unwrap(), Some(conflict));
997}