oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1use docs_core::{fragment_text, snapshot_bytes, text_content};
2use yrs::updates::decoder::Decode;
3use yrs::{Doc, Options, ReadTxn, StateVector, Text, Transact};
4
5fn doc_with(client_id: u64) -> Doc {
6 Doc::with_options(Options::with_guid_and_client_id(
7 "test-guid".into(),
8 client_id,
9 ))
10}
11
12#[test]
13fn a_snapshot_replayed_into_an_empty_doc_reproduces_the_document() {
14 let source = doc_with(1);
15 let update = std::fs::read("../fixtures/vectors/fragment/simple.bin").unwrap();
16 source
17 .transact_mut()
18 .apply_update(yrs::Update::decode_v1(&update).unwrap())
19 .unwrap();
20
21 let snap = snapshot_bytes(&source);
22
23 let rebuilt = doc_with(2);
24 rebuilt
25 .transact_mut()
26 .apply_update(yrs::Update::decode_v1(&snap).unwrap())
27 .unwrap();
28
29 assert_eq!(fragment_text(&rebuilt), fragment_text(&source));
30 assert!(
31 !fragment_text(&rebuilt).is_empty(),
32 "fixture produced no text"
33 );
34}
35
36#[test]
37fn a_snapshot_of_this_document_verifies() {
38 let source = doc_with(1);
39 let update = std::fs::read("../fixtures/vectors/fragment/simple.bin").unwrap();
40 source
41 .transact_mut()
42 .apply_update(yrs::Update::decode_v1(&update).unwrap())
43 .unwrap();
44 let snap = snapshot_bytes(&source);
45 assert!(docs_core::verify_snapshot(
46 &source,
47 &snap,
48 "verify-guid",
49 99
50 ));
51}
52
53#[test]
54fn a_snapshot_of_a_different_document_does_not_verify() {
55 let source = doc_with(1);
56 source
57 .transact_mut()
58 .apply_update(
59 yrs::Update::decode_v1(
60 &std::fs::read("../fixtures/vectors/fragment/simple.bin").unwrap(),
61 )
62 .unwrap(),
63 )
64 .unwrap();
65
66 let other = doc_with(1);
67 other
68 .transact_mut()
69 .apply_update(
70 yrs::Update::decode_v1(
71 &std::fs::read("../fixtures/vectors/fragment/marks.bin").unwrap(),
72 )
73 .unwrap(),
74 )
75 .unwrap();
76 let wrong = snapshot_bytes(&other);
77
78 assert!(!docs_core::verify_snapshot(
79 &source,
80 &wrong,
81 "verify-guid",
82 99
83 ));
84}
85
86#[test]
87fn garbage_does_not_verify() {
88 let source = doc_with(1);
89 assert!(!docs_core::verify_snapshot(
90 &source,
91 &[0xff, 0x00, 0x13, 0x37],
92 "verify-guid",
93 99
94 ));
95}
96
97/// RULING (Task 2 brief, "beyond the brief" item 1): a test that tells
98/// `snapshot_bytes`'s correct call, `encode_state_as_update_v1`, apart
99/// from its dangerous neighbour `encode_diff_v1`.
100///
101/// Both calls render identically whenever nothing is parked, so no test
102/// that only checks rendered content after a clean replay can catch a
103/// regression to the wrong call. The two calls diverge only when the
104/// source document is holding an update it received out of order — one
105/// whose dependency (an earlier clock range from the same client) has
106/// not arrived yet, so `apply_update` accepted it but yrs parked it in
107/// the pending queue instead of integrating it. `encode_state_as_update_v1`
108/// carries that pending queue along (via yrs's own `merge_pending_v1`);
109/// `encode_diff_v1` drops it (its own doc comment says so).
110///
111/// To construct that scenario without drawing entropy from the CRDT
112/// library (every `Doc` below still takes an explicit guid/client id):
113/// one `origin` document produces two updates in sequence — `dep`
114/// (its first edit) and `dependent` (its second edit, whose blocks'
115/// clocks for `origin`'s client id start exactly where `dep`'s left
116/// off). Handing `dependent` to a fresh document without `dep` first is
117/// what makes yrs park it — the document has never seen client 1 at
118/// all, so `dependent`'s blocks fail their dependency check and go into
119/// the pending queue rather than being rejected outright.
120#[test]
121fn a_snapshot_with_a_parked_update_heals_once_its_dependency_arrives() {
122 // Build `dep` and `dependent` as two genuinely sequential updates
123 // from one document, so `dependent` really does depend on `dep`.
124 let origin = doc_with(1);
125 let origin_text = origin.get_or_insert_text("content");
126 {
127 let mut txn = origin.transact_mut();
128 origin_text.insert(&mut txn, 0, "hello ");
129 }
130 let dep = origin
131 .transact()
132 .encode_state_as_update_v1(&StateVector::default());
133 let state_after_dep = origin.transact().state_vector();
134 {
135 let mut txn = origin.transact_mut();
136 origin_text.insert(&mut txn, 6, "world");
137 }
138 let dependent = origin.transact().encode_diff_v1(&state_after_dep);
139
140 // A document that only ever sees `dependent`: yrs cannot integrate
141 // it (client 1's clock range 0..6 is missing) so it is parked, not
142 // rejected — `apply_update` still returns `Ok`.
143 let parked = doc_with(2);
144 parked
145 .transact_mut()
146 .apply_update(yrs::Update::decode_v1(&dependent).unwrap())
147 .unwrap();
148 assert_eq!(
149 text_content(&parked),
150 "",
151 "a parked update must not be visible yet"
152 );
153
154 // Snapshot the document holding the pending update and replay that
155 // snapshot into a completely fresh document. Under the correct
156 // `encode_state_as_update_v1` call the pending queue rides along;
157 // under `encode_diff_v1` it would be silently dropped, and
158 // `rebuilt` would never heal below no matter what arrives.
159 let snap = snapshot_bytes(&parked);
160 let rebuilt = doc_with(3);
161 rebuilt
162 .transact_mut()
163 .apply_update(yrs::Update::decode_v1(&snap).unwrap())
164 .unwrap();
165 assert_eq!(
166 text_content(&rebuilt),
167 "",
168 "the dependency still hasn't arrived, so this must still be pending, not lost"
169 );
170
171 // Deliver the missing dependency to both. If the pending queue
172 // survived the snapshot/replay, both heal to the same content.
173 parked
174 .transact_mut()
175 .apply_update(yrs::Update::decode_v1(&dep).unwrap())
176 .unwrap();
177 rebuilt
178 .transact_mut()
179 .apply_update(yrs::Update::decode_v1(&dep).unwrap())
180 .unwrap();
181
182 assert_eq!(text_content(&parked), "hello world");
183 assert_eq!(
184 text_content(&rebuilt),
185 text_content(&parked),
186 "the snapshot-and-replay document must converge with the original \
187 once the missing dependency arrives"
188 );
189}
190