This repository has no description
0

Configure Feed

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

core / knot2 / crates / knot-index / tests / properties.rs
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(), &registry_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, &registry_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(), &registry_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, &registry_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}