This repository has no description
1mod common;
2
3use common::{account, at, did, fixture, grant, home, registration, rename, reopen, rkey, signer};
4use knot_cob::CobStore;
5use knot_cobs::{
6 CollaboratorsChange, MembersChange, MembersCob, RegistryChange, RegistryError, Removal,
7 RepoRegistryCob, register_repo, rename_repo,
8};
9use knot_types::{AccountDid, OwnerDid, RepoDid};
10
11#[test]
12fn members_roundtrip_and_reload_is_identical() {
13 let (_dir, repo) = fixture();
14 let key = signer(1);
15 let store = CobStore::new(&repo);
16
17 let created = store
18 .create(
19 &home(),
20 &MembersChange::Add(grant("nel", "nel", 1)),
21 &key,
22 at(1),
23 )
24 .unwrap();
25 store
26 .update(
27 &home(),
28 created.object,
29 &MembersChange::Add(grant("olaren", "nel", 2)),
30 &key,
31 at(2),
32 )
33 .unwrap();
34 store
35 .update(
36 &home(),
37 created.object,
38 &MembersChange::Add(grant("teq", "nel", 3)),
39 &key,
40 at(3),
41 )
42 .unwrap();
43 store
44 .update(
45 &home(),
46 created.object,
47 &MembersChange::Remove(Removal {
48 subject: account("teq"),
49 }),
50 &key,
51 at(4),
52 )
53 .unwrap();
54
55 let object = store.get::<MembersCob>(created.object).unwrap();
56 let state = object.state();
57 assert!(state.contains(&account("nel")));
58 assert_eq!(
59 state.get(&account("olaren")).unwrap().added_by,
60 account("nel")
61 );
62 assert!(!state.contains(&account("teq")));
63 assert_eq!(state.len(), 2);
64
65 let listed: Vec<&AccountDid> = state.entries().map(|(subject, _)| subject).collect();
66 assert_eq!(listed, vec![&account("nel"), &account("olaren")]);
67
68 let reopened = reopen(repo);
69 let reloaded = CobStore::new(&reopened)
70 .get::<MembersCob>(created.object)
71 .unwrap();
72 assert_eq!(state, reloaded.state());
73
74 let (_dup_dir, dup_repo) = fixture();
75 let dup_store = CobStore::new(&dup_repo);
76 let dup = dup_store
77 .create(
78 &home(),
79 &MembersChange::Add(grant("nel", "olaren", 7)),
80 &key,
81 at(1),
82 )
83 .unwrap();
84 dup_store
85 .update(
86 &home(),
87 dup.object,
88 &MembersChange::Add(grant("nel", "olaren", 7)),
89 &key,
90 at(2),
91 )
92 .unwrap();
93 let replayed = dup_store
94 .get::<MembersCob>(dup.object)
95 .unwrap()
96 .into_state();
97 assert_eq!(replayed.len(), 1, "replaying an identical add is a no-op");
98 let entry = replayed.get(&account("nel")).unwrap();
99 assert_eq!(entry.added_by, account("olaren"));
100 assert_eq!(entry.created_at, at(7));
101
102 let converge = |order: [(&str, &str, i64); 2]| {
103 let (_dir, repo) = fixture();
104 let store = CobStore::new(&repo);
105 let [first, second] = order;
106 let created = store
107 .create(
108 &home(),
109 &MembersChange::Add(grant(first.0, first.1, first.2)),
110 &key,
111 at(1),
112 )
113 .unwrap();
114 store
115 .update(
116 &home(),
117 created.object,
118 &MembersChange::Add(grant(second.0, second.1, second.2)),
119 &key,
120 at(2),
121 )
122 .unwrap();
123 store
124 .get::<MembersCob>(created.object)
125 .unwrap()
126 .into_state()
127 };
128 assert_eq!(
129 converge([("nel", "nel", 10), ("olaren", "nel", 20)]),
130 converge([("olaren", "nel", 20), ("nel", "nel", 10)]),
131 "independent grants converge across write order"
132 );
133}
134
135#[test]
136fn collaborators_live_in_a_per_repo_cob_namespace() {
137 let (_dir, repo) = fixture();
138 let key = signer(2);
139 let store = CobStore::new(&repo);
140 store
141 .create(
142 &home(),
143 &CollaboratorsChange::Add(grant("lyna", "nel", 1)),
144 &key,
145 at(1),
146 )
147 .unwrap();
148
149 let cob_ref = repo.references().unwrap().into_iter().find(|record| {
150 record
151 .name
152 .as_str()
153 .contains("sh.tangled.repo.collaborator")
154 });
155 assert!(cob_ref.is_some(), "collaborators live under refs/cobs");
156 assert!(repo.advertised_refs().unwrap().is_empty());
157}
158
159#[test]
160fn registry_handler_semantics() {
161 let (_dir, repo) = fixture();
162 let key = signer(21);
163 let store = CobStore::new(&repo);
164 let nel = did::<OwnerDid>("nel");
165
166 let created = store
167 .create(
168 &home(),
169 &RegistryChange::Register(registration("nel", "anemone", "squid", 1)),
170 &key,
171 at(1),
172 )
173 .unwrap();
174 let object = created.object;
175
176 let clash = register_repo(
177 &store,
178 &home(),
179 object,
180 registration("nel", "anemone", "whelk", 2),
181 &key,
182 at(2),
183 );
184 assert!(
185 matches!(clash, Err(RegistryError::RkeyTaken { .. })),
186 "register cannot claim the canonical rkey of a live repo under the same owner"
187 );
188 assert_eq!(
189 register_repo(
190 &store,
191 &home(),
192 object,
193 registration("nel", "anemone", "squid", 2),
194 &key,
195 at(2),
196 )
197 .unwrap(),
198 None,
199 "re-registering identical owner, rkey, and repo appends nothing"
200 );
201
202 let renamed = rename_repo(
203 &store,
204 &home(),
205 object,
206 rename("nel", "barnacle", "squid"),
207 &key,
208 at(2),
209 )
210 .unwrap();
211 assert!(renamed.is_some(), "real rename appends a change");
212 let state = store.get::<RepoRegistryCob>(object).unwrap().into_state();
213 assert_eq!(
214 state.resolve(&nel, &rkey("anemone")),
215 Some(&did::<RepoDid>("squid")),
216 "prior rkey keeps resolving as an alias"
217 );
218 assert_eq!(
219 state.resolve(&nel, &rkey("barnacle")),
220 Some(&did::<RepoDid>("squid"))
221 );
222 assert_eq!(
223 state.record_of(&did("squid")).unwrap().rkey,
224 rkey("barnacle")
225 );
226
227 let redundant = register_repo(
228 &store,
229 &home(),
230 object,
231 registration("nel", "barnacle", "squid", 3),
232 &key,
233 at(3),
234 )
235 .unwrap();
236 assert_eq!(
237 redundant, None,
238 "re-register matching canonical owner and rkey appends nothing"
239 );
240 assert_eq!(
241 store
242 .get::<RepoRegistryCob>(object)
243 .unwrap()
244 .into_state()
245 .resolve(&nel, &rkey("anemone")),
246 Some(&did::<RepoDid>("squid")),
247 "retained alias survives a redundant re-register"
248 );
249
250 store
251 .update(
252 &home(),
253 object,
254 &RegistryChange::Register(registration("nel", "kelp", "whelk", 4)),
255 &key,
256 at(4),
257 )
258 .unwrap();
259
260 let taken = rename_repo(
261 &store,
262 &home(),
263 object,
264 rename("nel", "kelp", "squid"),
265 &key,
266 at(5),
267 );
268 assert!(
269 matches!(taken, Err(RegistryError::RkeyTaken { .. })),
270 "rename cannot take canonical rkey of another live repo"
271 );
272
273 let unhosted = rename_repo(
274 &store,
275 &home(),
276 object,
277 rename("nel", "limpet", "conch"),
278 &key,
279 at(6),
280 );
281 assert!(
282 matches!(unhosted, Err(RegistryError::NotHosted { .. })),
283 "rename of a repo with no registration is refused"
284 );
285
286 let moved = rename_repo(
287 &store,
288 &home(),
289 object,
290 rename("olaren", "uni", "squid"),
291 &key,
292 at(7),
293 );
294 assert!(
295 matches!(moved, Err(RegistryError::OwnerMoved { .. })),
296 "rename under an owner the repo no longer belongs to is refused"
297 );
298
299 assert_eq!(
300 store
301 .get::<RepoRegistryCob>(object)
302 .unwrap()
303 .into_state()
304 .record_of(&did("squid"))
305 .unwrap()
306 .rkey,
307 rkey("barnacle"),
308 "refused renames left the canonical rkey untouched"
309 );
310}