use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; use knot_cob::{CobHome, CobStore}; use knot_cobs::{CollaboratorsChange, CollaboratorsCob, Grant, MembersChange}; use knot_git::Repo; use knot_index::{Coverage, IndexCoverage, IndexError, OfferedKey, Resolved}; mod common; use common::{World, acc, at, grant, meta_home, own, repo_did, rkey}; #[test] fn rebuild_folds_members_and_registry_and_collaborators_fold_on_access() { let world = World::new(); let repo = repo_did("squid"); world.seed_members(); world.seed_registry(&repo); world.seed_collaborator(&repo, "lyna"); let index = world.index(); index.rebuild().unwrap(); assert_eq!(index.is_member(&acc("nel")), Resolved::Ready(true)); assert_eq!(index.is_member(&acc("olaren")), Resolved::Ready(true)); assert_eq!(index.is_member(&acc("teq")), Resolved::Ready(false)); assert_eq!( index.resolve_repo(&own("nel"), &rkey("anemone")), Resolved::Ready(Some(repo.clone())) ); assert_eq!( index.resolve_repo(&own("nel"), &rkey("nautilus")), Resolved::Ready(None) ); assert_eq!( index.is_collaborator(&repo, &acc("lyna")), Resolved::Warming, "rebuild doesn't fold collaborators, so roster reads warming until first access" ); index.ensure_collaborators(&repo).unwrap(); assert_eq!( index.is_collaborator(&repo, &acc("lyna")), Resolved::Ready(true) ); assert_eq!( index.is_collaborator(&repo, &acc("bailey")), Resolved::Ready(false) ); assert_eq!( index.coverage(), IndexCoverage { members: Coverage::Ready, blocklist: Coverage::Ready, collaborators: Coverage::Ready, registry: Coverage::Ready, keys: Coverage::Ready, } ); } #[test] fn every_accessor_fails_closed_while_warming() { let world = World::new(); world.seed_members(); world.seed_collaborator(&repo_did("squid"), "lyna"); let index = world.index(); assert_eq!(index.is_member(&acc("nel")), Resolved::Warming); assert_eq!(index.member_entries(), Resolved::Warming); assert_eq!( index.is_collaborator(&repo_did("squid"), &acc("lyna")), Resolved::Warming ); assert_eq!( index.collaborator_entries(&repo_did("squid")), Resolved::Warming, "roster that has never been folded fails closed" ); assert_eq!( index.resolve_repo(&own("nel"), &rkey("anemone")), Resolved::Warming ); assert_eq!( index.owner_of(&repo_did("squid")), Resolved::Warming, "repo lookup before rebuild fails closed" ); assert_eq!(index.coverage().members, Coverage::Warming); assert_eq!( index.owner_of_key(&OfferedKey::from_bytes(vec![1, 2, 3])), Resolved::Ready(None), "key cache is operational from boot, never warming" ); } #[test] fn member_entries_record_provenance() { let world = World::new(); world.seed_members(); let index = world.index(); index.rebuild().unwrap(); assert_eq!( index.member_entries(), Resolved::Ready(vec![grant("nel", "nel", 1), grant("olaren", "nel", 2)]) ); } #[test] fn a_re_added_member_keeps_the_first_provenance() { let world = World::new(); let members = world.seed_members(); let index = world.index(); index.rebuild().unwrap(); let meta = Repo::open(&world.meta_path).unwrap(); let store = CobStore::new(&meta); store .update( &meta_home(), members, &MembersChange::Add(grant("olaren", "teq", 9)), &world.signer, at(9), ) .unwrap(); index.refresh_members().unwrap(); assert_eq!( index.member_entries(), Resolved::Ready(vec![grant("nel", "nel", 1), grant("olaren", "nel", 2)]), "duplicate add never rewrites original provenance, matching canonical roster" ); } #[test] fn collaborator_entries_match_the_canonical_roster() { let world = World::new(); let repo = repo_did("squid"); world.seed_registry(&repo); let object = world.seed_collaborator(&repo, "lyna"); let git = world.layout.open(&repo).unwrap(); let store = CobStore::new(&git); store .update( &CobHome::from(&repo), object, &CollaboratorsChange::Add(grant("bailey", "olaren", 2)), &world.signer, at(2), ) .unwrap(); world.remove_collaborator(&repo, object, "lyna", 3); store .update( &CobHome::from(&repo), object, &CollaboratorsChange::Add(grant("lyna", "teq", 5)), &world.signer, at(5), ) .unwrap(); let index = world.index(); index.rebuild().unwrap(); index.ensure_collaborators(&repo).unwrap(); let canonical = store.get::(object).unwrap(); let expected: Vec = canonical .state() .entries() .map(|(subject, entry)| Grant { subject: subject.clone(), added_by: entry.added_by.clone(), created_at: entry.created_at, }) .collect(); assert_eq!( index.collaborator_entries(&repo), Resolved::Ready(expected), "projected entries disagree with canonical Evaluate fold" ); } #[test] fn a_folded_repo_serves_while_unaccessed_repos_stay_warming() { let world = World::new(); let present = repo_did("squid"); let absent = repo_did("kelp"); let registry = world.seed_registry(&present); world.register_extra(&absent, "barnacle", registry); world.seed_collaborator(&present, "lyna"); let index = world.index(); index.rebuild().unwrap(); assert_eq!( index.coverage().collaborators, Coverage::Ready, "collaborators projection is operational from boot" ); index.ensure_collaborators(&present).unwrap(); assert_eq!( index.is_collaborator(&present, &acc("lyna")), Resolved::Ready(true) ); assert_eq!( index .collaborator_entries(&present) .map(|grants| grants.len()), Resolved::Ready(1), "folded repo serves its roster" ); assert_eq!( index.collaborator_entries(&absent), Resolved::Warming, "registered repo that was never accessed stays fail-closed until folded" ); assert_eq!( index.is_collaborator(&repo_did("conch"), &acc("lyna")), Resolved::Warming, "repo the index never folded cannot answer, so it fails closed" ); } #[test] fn an_ambiguous_meta_cob_fails_refresh_and_rebuild() { let world = World::new(); let meta = Repo::open(&world.meta_path).unwrap(); let store = CobStore::new(&meta); store .create( &meta_home(), &MembersChange::Add(grant("nel", "nel", 1)), &world.signer, at(1), ) .unwrap(); store .create( &meta_home(), &MembersChange::Add(grant("olaren", "olaren", 2)), &world.signer, at(2), ) .unwrap(); let index = world.index(); assert!(matches!( index.refresh_members(), Err(IndexError::Ambiguous { count: 2, .. }) )); assert!( matches!(index.rebuild(), Err(IndexError::Ambiguous { .. })), "broken meta COB fails whole boot instead of reporting partial one" ); } #[test] fn a_repo_missing_on_disk_does_not_fail_the_boot_and_isolates_its_fold() { let world = World::new(); let present = repo_did("squid"); let absent = repo_did("kelp"); world.seed_members(); let registry = world.seed_registry(&present); world.register_extra(&absent, "barnacle", registry); world.seed_collaborator(&present, "lyna"); let index = world.index(); index .rebuild() .expect("boot folds members and registry only, so missing repo dir never fails it"); index.ensure_collaborators(&present).unwrap(); assert_eq!( index.is_collaborator(&present, &acc("lyna")), Resolved::Ready(true) ); assert!( index.ensure_collaborators(&absent).is_err(), "folding repo with no dir on disk fails for that repo alone" ); assert_eq!( index.is_collaborator(&absent, &acc("lyna")), Resolved::Warming, "repo the index couldn't fold stays fail-closed" ); } #[test] fn concurrent_refreshes_of_distinct_repos_all_land() { let world = World::new(); let repos = ["squid", "clam", "whelk", "conch"]; repos.iter().for_each(|repo| { world.seed_collaborator(&repo_did(repo), "lyna"); }); let index = Arc::new(world.index()); std::thread::scope(|scope| { repos.iter().for_each(|repo| { let index = Arc::clone(&index); let repo = repo_did(repo); scope.spawn(move || index.refresh_collaborators(&repo).unwrap()); }); }); repos.iter().for_each(|repo| { assert_eq!( index.is_collaborator(&repo_did(repo), &acc("lyna")), Resolved::Ready(true) ); }); } #[test] fn a_refresh_is_eventually_consistent_not_an_atomic_snapshot() { let world = World::new(); let members = world.seed_members(); let index = Arc::new(world.index()); index.rebuild().unwrap(); (0..32).for_each(|i| world.add_member(members, &format!("m{i}"), 10 + i as i64)); let done = Arc::new(AtomicBool::new(false)); std::thread::scope(|scope| { let writer = Arc::clone(&index); let writer_done = Arc::clone(&done); scope.spawn(move || { writer.refresh_members().unwrap(); writer_done.store(true, Ordering::Release); }); let reader = Arc::clone(&index); let reader_done = Arc::clone(&done); scope.spawn(move || { while !reader_done.load(Ordering::Acquire) { assert_eq!( reader.is_member(&acc("nel")), Resolved::Ready(true), "stable member stays visible and read never blocks on writer" ); assert!( !reader.is_member(&acc("m0")).is_warming(), "already-ready projection serves reads mid-refresh, it never re-warms" ); } }); }); (0..32).for_each(|i| { assert_eq!( index.is_member(&acc(&format!("m{i}"))), Resolved::Ready(true), "once writer returns, whole delta has converged" ); }); }