Now let's take a silly one
1use knot_cob::{CobHome, CobStore};
2use knot_cobs::{
3 CollaboratorsChange, CollaboratorsCob, Grant, MembersChange, MembersCob, Registration,
4 RegistryChange, Removal, Rename, RepoRef, RepoRegistryCob,
5};
6use knot_git::{Layout, Repo};
7use knot_index::{Index, Resolved};
8use knot_runtime::{K256Signer, SeededEntropy};
9use knot_types::{AccountDid, KnotId, OwnerDid, RepoDid, RepoName, RepoRkey, UnixSeconds};
10use proptest::prelude::*;
11
12fn acc(n: u8) -> AccountDid {
13 AccountDid::new(format!("did:plc:s{n}")).unwrap()
14}
15
16fn meta_home() -> CobHome {
17 CobHome::from(&KnotId::new("did:web:knot.nel.pet").unwrap())
18}
19
20fn owner(n: u8) -> OwnerDid {
21 match n {
22 0 => OwnerDid::new("did:plc:nel").unwrap(),
23 _ => OwnerDid::new("did:plc:olaren").unwrap(),
24 }
25}
26
27fn repo_rkey(n: u8) -> RepoRkey {
28 RepoRkey::new(format!("r{n}")).unwrap()
29}
30
31fn repo_did(n: u8) -> RepoDid {
32 RepoDid::new(format!("did:plc:r{n}")).unwrap()
33}
34
35fn grant(subject: u8, t: i64) -> Grant {
36 Grant {
37 subject: acc(subject),
38 added_by: AccountDid::new("did:plc:nel").unwrap(),
39 created_at: UnixSeconds::new(t),
40 }
41}
42
43fn member_change(op: u8, subject: u8, t: i64) -> MembersChange {
44 match op {
45 0 => MembersChange::Add(grant(subject, t)),
46 _ => MembersChange::Remove(Removal {
47 subject: acc(subject),
48 }),
49 }
50}
51
52fn collaborator_change(op: u8, subject: u8, t: i64) -> CollaboratorsChange {
53 match op {
54 0 => CollaboratorsChange::Add(grant(subject, t)),
55 _ => CollaboratorsChange::Remove(Removal {
56 subject: acc(subject),
57 }),
58 }
59}
60
61fn registry_change(op: u8, who: u8, rkey: u8, repo: u8, t: i64) -> RegistryChange {
62 match op {
63 0 => RegistryChange::Register(Registration {
64 owner: owner(who),
65 rkey: repo_rkey(rkey),
66 name: RepoName::new(format!("r{rkey}")).unwrap(),
67 repo: repo_did(repo),
68 created_at: UnixSeconds::new(t),
69 }),
70 1 => RegistryChange::Rename(Rename {
71 owner: owner(who),
72 rkey: repo_rkey(rkey),
73 name: RepoName::new(format!("r{rkey}")).unwrap(),
74 repo: repo_did(repo),
75 }),
76 _ => RegistryChange::Deregister(RepoRef {
77 owner: owner(who),
78 rkey: repo_rkey(rkey),
79 }),
80 }
81}
82
83struct Fixture {
84 _dir: tempfile::TempDir,
85 meta_path: std::path::PathBuf,
86 layout: Layout,
87 signer: K256Signer,
88}
89
90fn fixture(seed: u64) -> Fixture {
91 let dir = tempfile::tempdir().unwrap();
92 let meta_path = dir.path().join("meta");
93 Repo::create(&meta_path).unwrap();
94 let layout = Layout::new(dir.path().join("repos"));
95 let signer = K256Signer::generate(&SeededEntropy::new(seed));
96 Fixture {
97 _dir: dir,
98 meta_path,
99 layout,
100 signer,
101 }
102}
103
104proptest! {
105 #![proptest_config(ProptestConfig { cases: 40, ..ProptestConfig::default() })]
106
107 #[test]
108 fn members_fold_equals_canonical_evaluate(
109 ops in prop::collection::vec((0u8..2, 0u8..4), 1..14)
110 ) {
111 let world = fixture(7);
112 let meta = Repo::open(&world.meta_path).unwrap();
113 let store = CobStore::new(&meta);
114
115 let incremental = Index::new(&world.meta_path, world.layout.clone());
116
117 let (op0, subject0) = ops[0];
118 let object = store
119 .create(&meta_home(), &member_change(op0, subject0, 1), &world.signer, UnixSeconds::new(1))
120 .unwrap()
121 .object;
122 incremental.refresh_members().unwrap();
123
124 ops.iter().enumerate().skip(1).for_each(|(index, (op, subject))| {
125 let t = index as i64 + 1;
126 store
127 .update(&meta_home(), object, &member_change(*op, *subject, t), &world.signer, UnixSeconds::new(t))
128 .unwrap();
129 incremental.refresh_members().unwrap();
130 });
131
132 let full = Index::new(&world.meta_path, world.layout.clone());
133 full.rebuild().unwrap();
134
135 let canonical = store.get::<MembersCob>(object).unwrap();
136 let roster = canonical.state();
137 let expected: Vec<Resolved<bool>> = (0u8..4)
138 .map(|subject| Resolved::Ready(roster.contains(&acc(subject))))
139 .collect();
140 prop_assert_eq!(
141 (0u8..4).map(|s| incremental.is_member(&acc(s))).collect::<Vec<_>>(),
142 expected.clone()
143 );
144 prop_assert_eq!(
145 (0u8..4).map(|s| full.is_member(&acc(s))).collect::<Vec<_>>(),
146 expected
147 );
148 }
149
150 #[test]
151 fn collaborators_fold_equals_canonical_evaluate(
152 ops in prop::collection::vec((0u8..2, 0u8..4), 1..14)
153 ) {
154 let world = fixture(8);
155 let repo = repo_did(0);
156 let git = world.layout.create(&repo).unwrap();
157 let store = CobStore::new(&git);
158
159 let incremental = Index::new(&world.meta_path, world.layout.clone());
160
161 let (op0, subject0) = ops[0];
162 let object = store
163 .create(&CobHome::from(&repo), &collaborator_change(op0, subject0, 1), &world.signer, UnixSeconds::new(1))
164 .unwrap()
165 .object;
166 incremental.rebuild().unwrap();
167 incremental.refresh_collaborators(&repo).unwrap();
168
169 ops.iter().enumerate().skip(1).for_each(|(index, (op, subject))| {
170 let t = index as i64 + 1;
171 store
172 .update(&CobHome::from(&repo), object, &collaborator_change(*op, *subject, t), &world.signer, UnixSeconds::new(t))
173 .unwrap();
174 incremental.refresh_collaborators(&repo).unwrap();
175 });
176
177 let full = Index::new(&world.meta_path, world.layout.clone());
178 full.rebuild().unwrap();
179 full.refresh_collaborators(&repo).unwrap();
180
181 let canonical = store.get::<CollaboratorsCob>(object).unwrap();
182 let roster = canonical.state();
183 let expected: Vec<Resolved<bool>> = (0u8..4)
184 .map(|subject| Resolved::Ready(roster.contains(&acc(subject))))
185 .collect();
186 prop_assert_eq!(
187 (0u8..4).map(|s| incremental.is_collaborator(&repo, &acc(s))).collect::<Vec<_>>(),
188 expected.clone()
189 );
190 prop_assert_eq!(
191 (0u8..4).map(|s| full.is_collaborator(&repo, &acc(s))).collect::<Vec<_>>(),
192 expected
193 );
194 }
195
196 #[test]
197 fn registry_fold_equals_canonical_evaluate(
198 ops in prop::collection::vec((0u8..3, 0u8..2, 0u8..4, 0u8..4), 1..14)
199 ) {
200 let world = fixture(9);
201 let meta = Repo::open(&world.meta_path).unwrap();
202 let store = CobStore::new(&meta);
203
204 let incremental = Index::new(&world.meta_path, world.layout.clone());
205
206 let (op0, who0, name0, repo0) = ops[0];
207 let object = store
208 .create(&meta_home(), ®istry_change(op0, who0, name0, repo0, 1), &world.signer, UnixSeconds::new(1))
209 .unwrap()
210 .object;
211 incremental.refresh_registry().unwrap();
212
213 ops.iter().enumerate().skip(1).for_each(|(index, (op, who, name, repo))| {
214 let t = index as i64 + 1;
215 store
216 .update(&meta_home(), object, ®istry_change(*op, *who, *name, *repo, t), &world.signer, UnixSeconds::new(t))
217 .unwrap();
218 incremental.refresh_registry().unwrap();
219 });
220
221 let full = Index::new(&world.meta_path, world.layout.clone());
222 full.rebuild().unwrap();
223
224 let canonical = store.get::<RepoRegistryCob>(object).unwrap();
225 let registry = canonical.state();
226 let lookups: Vec<(u8, u8)> = (0u8..2)
227 .flat_map(|who| (0u8..4).map(move |rkey| (who, rkey)))
228 .collect();
229 let expected: Vec<Resolved<Option<RepoDid>>> = lookups
230 .iter()
231 .map(|(who, rkey)| {
232 Resolved::Ready(registry.resolve(&owner(*who), &repo_rkey(*rkey)).cloned())
233 })
234 .collect();
235 prop_assert_eq!(
236 lookups
237 .iter()
238 .map(|(who, rkey)| incremental.resolve_repo(&owner(*who), &repo_rkey(*rkey)))
239 .collect::<Vec<_>>(),
240 expected.clone()
241 );
242 prop_assert_eq!(
243 lookups
244 .iter()
245 .map(|(who, rkey)| full.resolve_repo(&owner(*who), &repo_rkey(*rkey)))
246 .collect::<Vec<_>>(),
247 expected
248 );
249 let expected_records: Vec<_> = (0u8..4)
250 .map(|n| {
251 let record = registry.record_of(&repo_did(n));
252 (
253 Resolved::Ready(record.map(|record| record.owner.clone())),
254 Resolved::Ready(record.map(|record| record.rkey.clone())),
255 )
256 })
257 .collect();
258 prop_assert_eq!(
259 (0u8..4)
260 .map(|n| (incremental.owner_of(&repo_did(n)), incremental.rkey_of(&repo_did(n))))
261 .collect::<Vec<_>>(),
262 expected_records.clone()
263 );
264 prop_assert_eq!(
265 (0u8..4)
266 .map(|n| (full.owner_of(&repo_did(n)), full.rkey_of(&repo_did(n))))
267 .collect::<Vec<_>>(),
268 expected_records
269 );
270 }
271
272 #[test]
273 fn two_rebuilds_are_observably_identical(
274 members in prop::collection::vec((0u8..2, 0u8..6), 0..16),
275 registry in prop::collection::vec((0u8..3, 0u8..2, 0u8..4, 0u8..4), 0..10),
276 ) {
277 let world = fixture(10);
278 let meta = Repo::open(&world.meta_path).unwrap();
279 let store = CobStore::new(&meta);
280
281 if let Some(((op, subject), rest)) = members.split_first() {
282 let object = store
283 .create(&meta_home(), &member_change(*op, *subject, 1), &world.signer, UnixSeconds::new(1))
284 .unwrap()
285 .object;
286 rest.iter().enumerate().for_each(|(index, (op, subject))| {
287 let t = index as i64 + 2;
288 store
289 .update(&meta_home(), object, &member_change(*op, *subject, t), &world.signer, UnixSeconds::new(t))
290 .unwrap();
291 });
292 }
293
294 if let Some(((op, who, name, repo), rest)) = registry.split_first() {
295 let object = store
296 .create(&meta_home(), ®istry_change(*op, *who, *name, *repo, 1), &world.signer, UnixSeconds::new(1))
297 .unwrap()
298 .object;
299 rest.iter().enumerate().for_each(|(index, (op, who, name, repo))| {
300 let t = index as i64 + 2;
301 store
302 .update(&meta_home(), object, ®istry_change(*op, *who, *name, *repo, t), &world.signer, UnixSeconds::new(t))
303 .unwrap();
304 });
305 }
306
307 let first = Index::new(&world.meta_path, world.layout.clone());
308 first.rebuild().unwrap();
309 let second = Index::new(&world.meta_path, world.layout.clone());
310 second.rebuild().unwrap();
311
312 prop_assert_eq!(
313 (0u8..6).map(|s| first.is_member(&acc(s))).collect::<Vec<_>>(),
314 (0u8..6).map(|s| second.is_member(&acc(s))).collect::<Vec<_>>()
315 );
316 let lookups: Vec<(u8, u8)> = (0u8..2)
317 .flat_map(|who| (0u8..4).map(move |rkey| (who, rkey)))
318 .collect();
319 prop_assert_eq!(
320 lookups
321 .iter()
322 .map(|(who, rkey)| first.resolve_repo(&owner(*who), &repo_rkey(*rkey)))
323 .collect::<Vec<_>>(),
324 lookups
325 .iter()
326 .map(|(who, rkey)| second.resolve_repo(&owner(*who), &repo_rkey(*rkey)))
327 .collect::<Vec<_>>()
328 );
329 }
330}