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