Now let's take a silly one
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 12 kB View raw
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(), &registry_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, &registry_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(), &registry_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, &registry_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}