From 5b271ac3f3ef5ab966e4a0952e25edf554cab8dc Mon Sep 17 00:00:00 2001 From: dawn Date: Sat, 17 Oct 2026 02:41:38 +0300 Subject: [PATCH] knot-xrpc: share the private repo test setup between test files Signed-off-by: dawn --- knot2/crates/knot-xrpc/tests/common/mod.rs | 86 +++++++++++------- knot2/crates/knot-xrpc/tests/leases.rs | 89 +++++++++---------- knot2/crates/knot-xrpc/tests/notify.rs | 46 ++++------ knot2/crates/knot-xrpc/tests/private_https.rs | 2 +- knot2/crates/knot-xrpc/tests/space.rs | 72 ++++----------- knot2/crates/knot-xrpc/tests/spindle.rs | 54 ++++------- knot2/crates/knot-xrpc/tests/syncers.rs | 54 ++++------- 7 files changed, 160 insertions(+), 243 deletions(-) diff --git a/knot2/crates/knot-xrpc/tests/common/mod.rs b/knot2/crates/knot-xrpc/tests/common/mod.rs index 7eb3054a0..39a94d1d6 100644 --- a/knot2/crates/knot-xrpc/tests/common/mod.rs +++ b/knot2/crates/knot-xrpc/tests/common/mod.rs @@ -269,6 +269,34 @@ pub fn declared(did: &str, lxm: &[&str]) -> knot_types::Syncer { knot_types::Syncer::new(knot_types::ServiceDid::new(did).unwrap(), lxm).unwrap() } +pub fn bobbin() -> AccountDid { + AccountDid::new("did:web:bobbin.example").unwrap() +} + +pub fn stranger() -> AccountDid { + AccountDid::new("did:plc:urchin").unwrap() +} + +pub fn ghost() -> RepoDid { + RepoDid::new("did:plc:ghostfixture").unwrap() +} + +pub fn world_declaring(lxm: &[&str]) -> World { + let bobbin = declared(bobbin().as_str(), lxm); + World::declaring(Syncers::new(vec![bobbin]).unwrap()) +} + +pub fn bearer_headers(method: &Nsid, actor: &AccountDid, audience: &ServiceDid) -> HeaderMap { + let mut headers = HeaderMap::new(); + headers.insert( + header::AUTHORIZATION, + format!("Bearer {}", service_jwt_for(method, actor, audience)) + .parse() + .unwrap(), + ); + headers +} + pub fn fixture_collection() -> RecordCollection { RecordCollection::new(FIXTURE_COLLECTION).unwrap() } @@ -548,45 +576,24 @@ impl World { } pub fn administered() -> Self { - Self::assemble( - true, - ByteLimits::default(), - ObjectFormat::SHA1, - LimitConfig::default(), - true, - Terms { - admins: BTreeSet::from([AccountDid::new(OWNER).unwrap()]), - ..Terms::default() - }, - ) + Self::with_terms(Terms { + admins: BTreeSet::from([owner()]), + ..Terms::default() + }) } pub fn under(policy: knot_types::ContributionPolicy) -> Self { - Self::assemble( - true, - ByteLimits::default(), - ObjectFormat::SHA1, - LimitConfig::default(), - true, - Terms { - policy, - ..Terms::default() - }, - ) + Self::with_terms(Terms { + policy, + ..Terms::default() + }) } pub fn declaring(syncers: Syncers) -> Self { - Self::assemble( - true, - ByteLimits::default(), - ObjectFormat::SHA1, - LimitConfig::default(), - true, - Terms { - syncers, - ..Terms::default() - }, - ) + Self::with_terms(Terms { + syncers, + ..Terms::default() + }) } pub fn declaring_administered(syncers: Syncers) -> Self { @@ -864,6 +871,19 @@ impl World { } } + pub fn fixture_repo(&self, name: &str, visibility: knot_types::Visibility) -> RepoDid { + let did = RepoDid::new(format!("did:plc:{name}fixture")).unwrap(); + self.layout.create(&did).unwrap(); + self.register_as( + &did, + &RepoName::new(name).unwrap(), + &RepoRkey::new(name).unwrap(), + visibility, + ); + self.state.index.ensure_collaborators(&did).unwrap(); + did + } + pub fn register(&self, did: &RepoDid, name: &RepoName, rkey: &RepoRkey) { self.register_as(did, name, rkey, knot_types::Visibility::default()); } diff --git a/knot2/crates/knot-xrpc/tests/leases.rs b/knot2/crates/knot-xrpc/tests/leases.rs index 06f4e7a9f..a00985b1e 100644 --- a/knot2/crates/knot-xrpc/tests/leases.rs +++ b/knot2/crates/knot-xrpc/tests/leases.rs @@ -6,47 +6,31 @@ use tower::ServiceExt; use knot_git::{RefUpdate, Repo}; use knot_index::{Index, Resolved}; -use knot_types::{AccountDid, RefName, RepoDid, RepoName, RepoRkey, UnixSeconds, Visibility}; +use knot_types::{AccountDid, RefName, RepoDid, UnixSeconds, Visibility}; -use common::{World, git_router, knot_service_did, nsid, owner, post_as, post_authed, service_jwt}; +use common::{ + World, git_router, knot_service_did, nsid, owner, post_as, post_authed, service_jwt, stranger, +}; const RENEW: &str = "org.tangled.knot.renewLease"; const PUSH: &str = "org.tangled.git.receivePack"; -fn stranger() -> AccountDid { - AccountDid::new("did:plc:urchin").unwrap() -} - -fn repo(world: &World, name: &str, visibility: Visibility) -> RepoDid { - let did = RepoDid::new(format!("did:plc:{name}fixture")).unwrap(); - world.layout.create(&did).unwrap(); - world.register_as( - &did, - &RepoName::new(name).unwrap(), - &RepoRkey::new(name).unwrap(), - visibility, - ); - world.state.index.ensure_collaborators(&did).unwrap(); - did -} - -fn now(world: &World) -> UnixSeconds { - world.state.now() -} - #[tokio::test(flavor = "multi_thread")] async fn a_member_renews_and_a_stranger_gets_the_same_answer_and_no_lease() { let world = World::new(); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); let method = nsid(RENEW); let (stranger_status, stranger_body) = post_authed(&world, &method, &stranger(), serde_json::json!({})).await; assert_eq!( - world.state.index.lease_covers(&hidden, now(&world)), + world.state.index.lease_covers(&hidden, world.state.now()), Resolved::Ready(false) ); - assert_eq!(world.state.index.take_unsaved_leases(now(&world)), None); + assert_eq!( + world.state.index.take_unsaved_leases(world.state.now()), + None + ); let (member_status, member_body) = post_authed(&world, &method, &owner(), serde_json::json!({})).await; @@ -57,10 +41,14 @@ async fn a_member_renews_and_a_stranger_gets_the_same_answer_and_no_lease() { (member_status, member_body) ); assert_eq!( - world.state.index.lease_covers(&hidden, now(&world)), + world.state.index.lease_covers(&hidden, world.state.now()), Resolved::Ready(true) ); - let held = world.state.index.take_unsaved_leases(now(&world)).unwrap(); + let held = world + .state + .index + .take_unsaved_leases(world.state.now()) + .unwrap(); assert_eq!(held.len(), 1, "only the member holds a lease"); assert_eq!(held[0].account, owner()); } @@ -68,7 +56,7 @@ async fn a_member_renews_and_a_stranger_gets_the_same_answer_and_no_lease() { #[tokio::test(flavor = "multi_thread")] async fn a_token_for_the_wrong_audience_or_method_is_unauthorized() { let world = World::new(); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); let method = nsid(RENEW); let other_audience = knot_types::ServiceDid::new("did:web:elsewhere.example").unwrap(); @@ -105,7 +93,7 @@ async fn a_token_for_the_wrong_audience_or_method_is_unauthorized() { assert_eq!(response.status(), StatusCode::UNAUTHORIZED, "no token"); assert_eq!( - world.state.index.lease_covers(&hidden, now(&world)), + world.state.index.lease_covers(&hidden, world.state.now()), Resolved::Ready(false), "none of them made a lease" ); @@ -125,9 +113,9 @@ async fn upload_pack_refs(world: &World, did: &RepoDid, actor: &AccountDid) -> S #[tokio::test(flavor = "multi_thread")] async fn a_members_read_of_a_private_repo_renews_their_lease() { let world = World::new(); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); assert_eq!( - world.state.index.lease_covers(&hidden, now(&world)), + world.state.index.lease_covers(&hidden, world.state.now()), Resolved::Ready(false) ); @@ -136,7 +124,7 @@ async fn a_members_read_of_a_private_repo_renews_their_lease() { StatusCode::NOT_FOUND ); assert_eq!( - world.state.index.lease_covers(&hidden, now(&world)), + world.state.index.lease_covers(&hidden, world.state.now()), Resolved::Ready(false), "a refused read renews nothing" ); @@ -146,7 +134,7 @@ async fn a_members_read_of_a_private_repo_renews_their_lease() { StatusCode::OK ); assert_eq!( - world.state.index.lease_covers(&hidden, now(&world)), + world.state.index.lease_covers(&hidden, world.state.now()), Resolved::Ready(true) ); } @@ -154,8 +142,8 @@ async fn a_members_read_of_a_private_repo_renews_their_lease() { #[tokio::test(flavor = "multi_thread")] async fn reading_a_public_repo_creates_no_lease() { let world = World::new(); - let open = repo(&world, "open", Visibility::Public); - let hidden = repo(&world, "hidden", Visibility::Private); + let open = world.fixture_repo("open", Visibility::Public); + let hidden = world.fixture_repo("hidden", Visibility::Private); assert_eq!( upload_pack_refs(&world, &open, &stranger()).await, @@ -166,9 +154,12 @@ async fn reading_a_public_repo_creates_no_lease() { StatusCode::OK ); - assert_eq!(world.state.index.take_unsaved_leases(now(&world)), None); assert_eq!( - world.state.index.lease_covers(&hidden, now(&world)), + world.state.index.take_unsaved_leases(world.state.now()), + None + ); + assert_eq!( + world.state.index.lease_covers(&hidden, world.state.now()), Resolved::Ready(false) ); } @@ -196,8 +187,8 @@ fn load(world: &World, index: &Index, at: UnixSeconds) { #[tokio::test(flavor = "multi_thread")] async fn persisted_leases_load_back_and_expired_ones_are_dropped() { let world = World::new(); - let hidden = repo(&world, "hidden", Visibility::Private); - let started = now(&world); + let hidden = world.fixture_repo("hidden", Visibility::Private); + let started = world.state.now(); world.state.index.renew_lease(&owner(), started); persist(&world, started); @@ -230,28 +221,28 @@ async fn persisted_leases_load_back_and_expired_ones_are_dropped() { #[tokio::test(flavor = "multi_thread")] async fn nothing_is_written_when_nothing_changed() { let world = World::new(); - repo(&world, "hidden", Visibility::Private); + world.fixture_repo("hidden", Visibility::Private); let meta = Repo::open(&world.state.meta_path).unwrap(); let name = RefName::new(knot_xrpc::LEASES_REF).unwrap(); - persist(&world, now(&world)); + persist(&world, world.state.now()); assert_eq!(meta.find_ref(&name).unwrap(), None, "no lease, no blob"); - world.state.index.renew_lease(&owner(), now(&world)); - persist(&world, now(&world)); + world.state.index.renew_lease(&owner(), world.state.now()); + persist(&world, world.state.now()); let written = meta .find_ref(&name) .unwrap() .expect("the renewal is persisted"); - persist(&world, now(&world)); + persist(&world, world.state.now()); assert_eq!(meta.find_ref(&name).unwrap(), Some(written)); } #[tokio::test(flavor = "multi_thread")] async fn an_unreadable_blob_boots_with_no_leases() { let world = World::new(); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); let meta = Repo::open(&world.state.meta_path).unwrap(); let blob = meta.git().write_blob(b"{ not leases").unwrap().detach(); meta.update_ref(&RefUpdate::Create { @@ -261,11 +252,11 @@ async fn an_unreadable_blob_boots_with_no_leases() { .unwrap(); let index = fresh_index(&world); - load(&world, &index, now(&world)); + load(&world, &index, world.state.now()); assert_eq!( - index.lease_covers(&hidden, now(&world)), + index.lease_covers(&hidden, world.state.now()), Resolved::Ready(false) ); - assert_eq!(index.take_unsaved_leases(now(&world)), None); + assert_eq!(index.take_unsaved_leases(world.state.now()), None); } diff --git a/knot2/crates/knot-xrpc/tests/notify.rs b/knot2/crates/knot-xrpc/tests/notify.rs index 9b19b792d..146260fc5 100644 --- a/knot2/crates/knot-xrpc/tests/notify.rs +++ b/knot2/crates/knot-xrpc/tests/notify.rs @@ -7,10 +7,7 @@ use base64::engine::general_purpose::URL_SAFE_NO_PAD; use jacquard_common::types::tid::Tid; use k256::ecdsa::signature::Verifier; use knot_git::{RefUpdate, Repo}; -use knot_types::{ - AccountDid, Did, RecordRkey, RefName, RepoDid, RepoName, RepoRkey, Syncers, UnixSeconds, - Visibility, -}; +use knot_types::{AccountDid, Did, RecordRkey, RefName, RepoDid, Syncers, UnixSeconds, Visibility}; use common::{Sent, World, declared, nsid, owner, post_authed}; use knot_xrpc::notify::SweepMemory; @@ -46,19 +43,6 @@ fn world_with(syncers: &[(&Did, &[&str])]) -> World { world } -fn repo(world: &World, name: &str, visibility: Visibility) -> RepoDid { - let did = RepoDid::new(format!("did:plc:{name}fixture")).unwrap(); - world.layout.create(&did).unwrap(); - world.register_as( - &did, - &RepoName::new(name).unwrap(), - &RepoRkey::new(name).unwrap(), - visibility, - ); - world.state.index.ensure_collaborators(&did).unwrap(); - did -} - fn write(world: &World, did: &RepoDid, rkeys: &[&str], at: i64) { let path = world.layout.repo_path(did).unwrap(); let rkeys: Vec = rkeys @@ -96,7 +80,7 @@ fn covered_owner(world: &World) { async fn a_private_write_goes_to_a_covered_syncer_that_lists_the_nsid() { let (bobbin, deliberi) = (bobbin(), deliberi()); let world = world_with(&[(&bobbin, &[WRITE]), (&deliberi, &[DESCRIBE])]); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); covered_owner(&world); write(&world, &hidden, &["reef"], 1_000); @@ -141,7 +125,7 @@ async fn a_private_write_goes_to_a_covered_syncer_that_lists_the_nsid() { async fn a_delete_op_carries_no_value() { let bobbin = bobbin(); let world = world_with(&[(&bobbin, &[WRITE])]); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); covered_owner(&world); write(&world, &hidden, &["reef"], 1_000); settled(&world, 1).await; @@ -161,7 +145,7 @@ async fn a_delete_op_carries_no_value() { async fn a_write_with_no_covering_lease_is_not_sent() { let bobbin = bobbin(); let world = world_with(&[(&bobbin, &[WRITE])]); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); write(&world, &hidden, &["reef"], 1_000); settled(&world, 1).await; @@ -173,7 +157,7 @@ async fn a_write_with_no_covering_lease_is_not_sent() { async fn a_public_repo_write_is_not_sent() { let bobbin = bobbin(); let world = world_with(&[(&bobbin, &[WRITE])]); - let open = repo(&world, "open", Visibility::Public); + let open = world.fixture_repo("open", Visibility::Public); covered_owner(&world); write(&world, &open, &["reef"], 1_000); @@ -186,7 +170,7 @@ async fn a_public_repo_write_is_not_sent() { async fn a_write_nobody_listens_for_is_not_sent() { let deliberi = deliberi(); let world = world_with(&[(&deliberi, &[DESCRIBE])]); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); covered_owner(&world); write(&world, &hidden, &["reef"], 1_000); @@ -200,7 +184,7 @@ async fn a_syncer_without_an_endpoint_entry_is_skipped() { let declared = [&bobbin, &deliberi].map(|did| declared(did.as_str(), &[WRITE])); let world = World::declaring_administered(Syncers::new(declared.to_vec()).unwrap()); world.publish_service(&bobbin, "#tangled_syncer", "https://bobbin.example"); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); covered_owner(&world); write(&world, &hidden, &["reef"], 1_000); @@ -230,7 +214,7 @@ async fn delete(world: &World, did: &RepoDid) { async fn deleting_a_private_repo_sends_space_deleted_without_asking_for_a_lease() { let (bobbin, deliberi) = (bobbin(), deliberi()); let world = world_with(&[(&bobbin, &[DELETED]), (&deliberi, &[WRITE])]); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); delete(&world, &hidden).await; settled(&world, 1).await; @@ -245,7 +229,7 @@ async fn deleting_a_private_repo_sends_space_deleted_without_asking_for_a_lease( async fn deleting_a_public_repo_notifies_no_syncer() { let bobbin = bobbin(); let world = world_with(&[(&bobbin, &[DELETED])]); - let open = repo(&world, "open", Visibility::Public); + let open = world.fixture_repo("open", Visibility::Public); delete(&world, &open).await; @@ -265,7 +249,7 @@ async fn sweep(world: &World, memory: &mut SweepMemory) { async fn a_lapsed_lease_is_reported_once() { let bobbin = bobbin(); let world = world_with(&[(&bobbin, &[LAPSED])]); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); lease_for_a_collaborator(&world, &hidden); let mut last = SweepMemory::default(); @@ -288,7 +272,7 @@ async fn a_lapsed_lease_is_reported_once() { async fn a_lapse_while_the_knot_was_down_is_still_reported() { let bobbin = bobbin(); let world = world_with(&[(&bobbin, &[LAPSED])]); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); lease_for_a_collaborator(&world, &hidden); sweep(&world, &mut SweepMemory::default()).await; @@ -306,7 +290,7 @@ async fn a_lapse_while_the_knot_was_down_is_still_reported() { async fn a_deleted_repo_is_not_reported_as_lapsed() { let bobbin = bobbin(); let world = world_with(&[(&bobbin, &[DELETED, LAPSED])]); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); lease_for_a_collaborator(&world, &hidden); let mut last = SweepMemory::default(); sweep(&world, &mut last).await; @@ -330,7 +314,7 @@ fn covered_ref() -> RefName { async fn an_unreadable_stored_set_is_kept_and_a_later_read_still_reports_the_lapse() { let bobbin = bobbin(); let world = world_with(&[(&bobbin, &[LAPSED])]); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); lease_for_a_collaborator(&world, &hidden); sweep(&world, &mut SweepMemory::default()).await; @@ -369,7 +353,7 @@ async fn an_unreadable_stored_set_is_kept_and_a_later_read_still_reports_the_lap async fn a_failed_write_is_retried_on_the_next_sweep() { let bobbin = bobbin(); let world = world_with(&[(&bobbin, &[LAPSED])]); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); lease_for_a_collaborator(&world, &hidden); let meta = Repo::open(&world.state.meta_path).unwrap(); let blob = meta.git().write_blob(b"[]").unwrap().detach(); @@ -417,7 +401,7 @@ fn claims_of(sent: &Sent) -> (serde_json::Value, String, Vec) { async fn the_notification_is_a_service_token_from_the_knot_to_the_syncer() { let bobbin = bobbin(); let world = world_with(&[(&bobbin, &[DELETED])]); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); delete(&world, &hidden).await; settled(&world, 1).await; let sent = world.sent(); diff --git a/knot2/crates/knot-xrpc/tests/private_https.rs b/knot2/crates/knot-xrpc/tests/private_https.rs index 629ac5de7..b5b621d36 100644 --- a/knot2/crates/knot-xrpc/tests/private_https.rs +++ b/knot2/crates/knot-xrpc/tests/private_https.rs @@ -78,7 +78,7 @@ fn stand_in(world: World) -> Stand { router, hidden, open, - ghost: RepoDid::new("did:plc:ghostfixture").unwrap(), + ghost: common::ghost(), work, head, } diff --git a/knot2/crates/knot-xrpc/tests/space.rs b/knot2/crates/knot-xrpc/tests/space.rs index 15a9b62cc..cbc088e97 100644 --- a/knot2/crates/knot-xrpc/tests/space.rs +++ b/knot2/crates/knot-xrpc/tests/space.rs @@ -1,49 +1,25 @@ mod common; -use http::{HeaderMap, HeaderValue, StatusCode, header}; +use http::{HeaderMap, StatusCode}; use knot_index::Resolved; use knot_record::chain; -use knot_types::{AccountDid, RepoDid, RepoName, RepoRkey, Syncers, UnixSeconds, Visibility}; +use knot_types::{AccountDid, RepoDid, Visibility}; use common::{ - World, create_ticket, declared, enable_chain, get_with_headers, nsid, owner, service_jwt_for, + World, bearer_headers, bobbin, create_ticket, enable_chain, get_with_headers, ghost, nsid, + owner, stranger, world_declaring, }; const OPS: &str = "org.tangled.space.listRepoOps"; const SPACES: &str = "org.tangled.space.listSpaces"; const ANCIENT: &str = "2222222222222"; -fn bobbin() -> AccountDid { - AccountDid::new("did:web:bobbin.example").unwrap() -} - -fn stranger() -> AccountDid { - AccountDid::new("did:plc:urchin").unwrap() -} - -fn world_declaring(lxm: &[&str]) -> World { - let bobbin = declared(bobbin().as_str(), lxm); - World::declaring(Syncers::new(vec![bobbin]).unwrap()) -} - fn repo(world: &World, name: &str, visibility: Visibility) -> RepoDid { - let did = RepoDid::new(format!("did:plc:{name}fixture")).unwrap(); - world.layout.create(&did).unwrap(); - world.register_as( - &did, - &RepoName::new(name).unwrap(), - &RepoRkey::new(name).unwrap(), - visibility, - ); - world.state.index.ensure_collaborators(&did).unwrap(); + let did = world.fixture_repo(name, visibility); enable_chain(world, &did); did } -fn now(world: &World) -> UnixSeconds { - world.state.now() -} - // aud is the repo for listRepoOps and the knot for listSpaces async fn get_as( world: &World, @@ -52,16 +28,8 @@ async fn get_as( actor: &AccountDid, audience: &str, ) -> (StatusCode, serde_json::Value) { - let token = service_jwt_for( - &nsid(method), - actor, - &knot_types::ServiceDid::new(audience).unwrap(), - ); - let mut headers = HeaderMap::new(); - headers.insert( - header::AUTHORIZATION, - HeaderValue::from_str(&format!("Bearer {token}")).unwrap(), - ); + let audience = knot_types::ServiceDid::new(audience).unwrap(); + let headers = bearer_headers(&nsid(method), actor, &audience); let (status, _, body) = get_with_headers(world, &format!("/xrpc/{method}?{query}"), headers).await; (status, serde_json::from_slice(&body).unwrap_or_default()) @@ -72,15 +40,10 @@ async fn raw_ops( did: &RepoDid, actor: Option<&AccountDid>, ) -> (StatusCode, axum::body::Bytes) { - let mut headers = HeaderMap::new(); - if let Some(actor) = actor { - let audience = knot_types::ServiceDid::new(did.as_str()).unwrap(); - let token = service_jwt_for(&nsid(OPS), actor, &audience); - headers.insert( - header::AUTHORIZATION, - HeaderValue::from_str(&format!("Bearer {token}")).unwrap(), - ); - } + let audience = knot_types::ServiceDid::new(did.as_str()).unwrap(); + let headers = actor.map_or_else(HeaderMap::new, |actor| { + bearer_headers(&nsid(OPS), actor, &audience) + }); let path = format!("/xrpc/{OPS}?space={did}"); let (status, _, body) = get_with_headers(world, &path, headers).await; (status, body) @@ -256,7 +219,7 @@ async fn a_since_the_chain_cant_walk_back_to_is_since_too_old() { async fn a_reader_who_cant_see_the_repo_gets_the_missing_repo_answer_byte_for_byte() { let world = world_declaring(&[OPS]); let hidden = repo(&world, "hidden", Visibility::Private); - let ghost = RepoDid::new("did:plc:ghostfixture").unwrap(); + let ghost = ghost(); let deliberi = AccountDid::new("did:web:deliberi.example").unwrap(); let missing = raw_ops(&world, &ghost, Some(&stranger())).await; @@ -289,7 +252,7 @@ async fn a_collaborator_lists_ops_and_a_public_repo_needs_no_login() { create_ticket(&world, &hidden, "kelp", "").await; create_ticket(&world, &open, "reef", "").await; let mate = AccountDid::new("did:plc:clownfish").unwrap(); - world.add_collaborator(&hidden, &mate, &owner(), now(&world)); + world.add_collaborator(&hidden, &mate, &owner(), world.state.now()); let (status, page) = ops_as(&world, &hidden, "", &mate).await; assert_eq!(status, StatusCode::OK, "{page}"); @@ -314,8 +277,9 @@ async fn list_spaces_for_a_syncer_carries_the_lease_expiry() { assert_eq!(status, StatusCode::OK, "{none}"); assert!(uris(&none).is_empty(), "no member holds a lease yet"); - world.state.index.renew_lease(&owner(), now(&world)); - let Resolved::Ready(covered) = world.state.index.covered_private_repos(now(&world)) else { + world.state.index.renew_lease(&owner(), world.state.now()); + let Resolved::Ready(covered) = world.state.index.covered_private_repos(world.state.now()) + else { panic!("the index is warm"); }; let (status, listed) = spaces_as(&world, "", &bobbin()).await; @@ -330,7 +294,7 @@ async fn list_spaces_for_a_syncer_carries_the_lease_expiry() { async fn list_spaces_for_users_and_anonymous() { let world = World::new(); let hidden = repo(&world, "hidden", Visibility::Private); - world.state.index.renew_lease(&owner(), now(&world)); + world.state.index.renew_lease(&owner(), world.state.now()); let (status, mine) = spaces_as(&world, "", &owner()).await; assert_eq!(status, StatusCode::OK, "{mine}"); @@ -357,7 +321,7 @@ async fn list_spaces_pages_by_repo_did() { .map(|name| repo(&world, name, Visibility::Private).to_string()) .collect(); made.sort(); - world.state.index.renew_lease(&owner(), now(&world)); + world.state.index.renew_lease(&owner(), world.state.now()); let (_, first) = spaces_as(&world, "limit=2", &owner()).await; assert_eq!(uris(&first), made[..2]); diff --git a/knot2/crates/knot-xrpc/tests/spindle.rs b/knot2/crates/knot-xrpc/tests/spindle.rs index 7fb1f066f..8c29122e6 100644 --- a/knot2/crates/knot-xrpc/tests/spindle.rs +++ b/knot2/crates/knot-xrpc/tests/spindle.rs @@ -2,16 +2,17 @@ mod common; use axum::Router; use axum::body::Body; -use http::{HeaderMap, HeaderValue, Request, StatusCode, header}; +use http::{HeaderMap, Request, StatusCode}; use knot_cob::{CobHome, CobStore}; use knot_cobs::{RepoRegistryCob, Spindle, set_repo_spindle}; use knot_git::Repo; -use knot_types::{ - AccountDid, OwnerDid, RepoDid, RepoName, RepoRkey, ServiceDid, Syncers, UnixSeconds, Visibility, -}; +use knot_types::{AccountDid, OwnerDid, RepoDid, ServiceDid, Syncers, UnixSeconds, Visibility}; use tower::ServiceExt; -use common::{World, declared, get_with_headers, git_router, nsid, owner, post_as, service_jwt}; +use common::{ + World, bearer_headers, declared, get_with_headers, ghost, git_router, knot_service_did, nsid, + owner, post_as, +}; const LIST_REFS: &str = "org.tangled.git.listRefs"; const FETCH: &str = "org.tangled.git.uploadPack"; @@ -30,19 +31,6 @@ fn service(did: &AccountDid) -> ServiceDid { ServiceDid::new(did.as_str()).unwrap() } -fn repo(world: &World, name: &str, visibility: Visibility) -> RepoDid { - let did = RepoDid::new(format!("did:plc:{name}fixture")).unwrap(); - world.layout.create(&did).unwrap(); - world.register_as( - &did, - &RepoName::new(name).unwrap(), - &RepoRkey::new(name).unwrap(), - visibility, - ); - world.state.index.ensure_collaborators(&did).unwrap(); - did -} - fn name_spindle(world: &World, repo: &RepoDid, spindle: Option<&AccountDid>) { let meta = Repo::open(&world.state.meta_path).unwrap(); let store = CobStore::new(&meta); @@ -65,13 +53,7 @@ fn name_spindle(world: &World, repo: &RepoDid, spindle: Option<&AccountDid>) { } fn bearer(lxm: &str, actor: &AccountDid) -> HeaderMap { - let mut headers = HeaderMap::new(); - let token = service_jwt(&nsid(lxm), actor); - headers.insert( - header::AUTHORIZATION, - HeaderValue::from_str(&format!("Bearer {token}")).unwrap(), - ); - headers + bearer_headers(&nsid(lxm), actor, &knot_service_did()) } async fn list_refs_as(world: &World, repo: &RepoDid, actor: &AccountDid) -> (StatusCode, String) { @@ -104,7 +86,7 @@ async fn fetch_as(router: &Router, repo: &RepoDid, actor: &AccountDid) -> Status #[tokio::test] async fn the_manifests_spindle_reads_a_private_repo_without_a_lease() { let world = World::new(); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); let router = git_router(&world); name_spindle(&world, &hidden, Some(&ci())); @@ -128,8 +110,8 @@ async fn the_manifests_spindle_reads_a_private_repo_without_a_lease() { #[tokio::test] async fn another_service_gets_the_missing_repo_answer() { let world = World::new(); - let hidden = repo(&world, "hidden", Visibility::Private); - let ghost = RepoDid::new("did:plc:ghostfixture").unwrap(); + let hidden = world.fixture_repo("hidden", Visibility::Private); + let ghost = ghost(); let router = git_router(&world); name_spindle(&world, &hidden, Some(&ci())); @@ -149,15 +131,15 @@ async fn another_service_gets_the_missing_repo_answer() { #[tokio::test] async fn a_spindle_reads_only_the_repo_that_names_it() { let world = World::new(); - let ours = repo(&world, "ours", Visibility::Private); - let theirs = repo(&world, "theirs", Visibility::Private); + let ours = world.fixture_repo("ours", Visibility::Private); + let theirs = world.fixture_repo("theirs", Visibility::Private); let router = git_router(&world); name_spindle(&world, &ours, Some(&ci())); name_spindle(&world, &theirs, Some(&other_ci())); assert_eq!(list_refs_as(&world, &ours, &ci()).await.0, StatusCode::OK); let refused = list_refs_as(&world, &theirs, &ci()).await; - let ghost = RepoDid::new("did:plc:ghostfixture").unwrap(); + let ghost = ghost(); assert_eq!(refused, list_refs_as(&world, &ghost, &ci()).await); assert_ne!(fetch_as(&router, &theirs, &ci()).await, StatusCode::OK); } @@ -165,7 +147,7 @@ async fn a_spindle_reads_only_the_repo_that_names_it() { #[tokio::test] async fn changing_the_manifests_spindle_moves_the_grant_at_once() { let world = World::new(); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); let router = git_router(&world); assert_ne!(fetch_as(&router, &hidden, &ci()).await, StatusCode::OK); @@ -190,7 +172,7 @@ async fn changing_the_manifests_spindle_moves_the_grant_at_once() { #[tokio::test] async fn a_spindle_token_cant_push_or_write() { let world = World::new(); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); let router = git_router(&world); name_spindle(&world, &hidden, Some(&ci())); @@ -231,8 +213,8 @@ async fn a_spindle_token_cant_push_or_write() { async fn a_spindle_that_is_also_a_declared_syncer_keeps_both_rules() { let declared = declared(ci().as_str(), &[FETCH, LIST_REFS]); let world = World::declaring(Syncers::new(vec![declared]).unwrap()); - let named = repo(&world, "named", Visibility::Private); - let leased = repo(&world, "leased", Visibility::Private); + let named = world.fixture_repo("named", Visibility::Private); + let leased = world.fixture_repo("leased", Visibility::Private); let router = git_router(&world); name_spindle(&world, &named, Some(&ci())); @@ -270,7 +252,7 @@ async fn lfs_batch_as(world: &World, uri: String, actor: &AccountDid) -> StatusC #[tokio::test] async fn the_manifests_spindle_reads_lfs_through_every_url_form() { let world = World::new(); - let hidden = repo(&world, "hidden", Visibility::Private); + let hidden = world.fixture_repo("hidden", Visibility::Private); name_spindle(&world, &hidden, Some(&ci())); let urls = [ format!("/{hidden}/info/lfs/objects/batch"), diff --git a/knot2/crates/knot-xrpc/tests/syncers.rs b/knot2/crates/knot-xrpc/tests/syncers.rs index dcbbdff37..45ef6ab28 100644 --- a/knot2/crates/knot-xrpc/tests/syncers.rs +++ b/knot2/crates/knot-xrpc/tests/syncers.rs @@ -1,42 +1,18 @@ mod common; -use http::{HeaderMap, HeaderValue, StatusCode, header}; -use knot_types::{AccountDid, RepoDid, RepoName, RepoRkey, Syncers, Visibility}; +use http::StatusCode; +use knot_types::{AccountDid, RepoDid, Visibility}; -use common::{World, declared, get_with_headers, nsid, owner, post_as, service_jwt}; +use common::{ + World, bearer_headers, bobbin, get_with_headers, ghost, knot_service_did, nsid, owner, post_as, + world_declaring, +}; const DESCRIBE: &str = "com.atproto.repo.describeRepo"; const CREATE: &str = "com.atproto.repo.createRecord"; -fn bobbin() -> AccountDid { - AccountDid::new("did:web:bobbin.example").unwrap() -} - -fn world_declaring(lxm: &[&str]) -> World { - let bobbin = declared(bobbin().as_str(), lxm); - World::declaring(Syncers::new(vec![bobbin]).unwrap()) -} - -fn repo(world: &World, name: &str, visibility: Visibility) -> RepoDid { - let did = RepoDid::new(format!("did:plc:{name}fixture")).unwrap(); - world.layout.create(&did).unwrap(); - world.register_as( - &did, - &RepoName::new(name).unwrap(), - &RepoRkey::new(name).unwrap(), - visibility, - ); - world.state.index.ensure_collaborators(&did).unwrap(); - did -} - async fn describe_as(world: &World, did: &RepoDid, actor: &AccountDid) -> (StatusCode, String) { - let mut headers = HeaderMap::new(); - let token = service_jwt(&nsid(DESCRIBE), actor); - headers.insert( - header::AUTHORIZATION, - HeaderValue::from_str(&format!("Bearer {token}")).unwrap(), - ); + let headers = bearer_headers(&nsid(DESCRIBE), actor, &knot_service_did()); let path = format!("/xrpc/{DESCRIBE}?repo={did}"); let (status, _, body) = get_with_headers(world, &path, headers).await; let body = String::from_utf8_lossy(&body).replace(did.as_str(), ""); @@ -46,7 +22,7 @@ async fn describe_as(world: &World, did: &RepoDid, actor: &AccountDid) -> (Statu #[tokio::test] async fn a_declared_syncer_reads_a_public_repo_like_anyone() { let world = world_declaring(&[DESCRIBE]); - let open = repo(&world, "open", Visibility::Public); + let open = world.fixture_repo("open", Visibility::Public); let (status, body) = describe_as(&world, &open, &bobbin()).await; assert_eq!(status, StatusCode::OK, "{body}"); } @@ -54,8 +30,8 @@ async fn a_declared_syncer_reads_a_public_repo_like_anyone() { #[tokio::test] async fn a_declared_syncer_reads_a_private_repo_only_while_a_member_holds_a_lease() { let world = world_declaring(&[DESCRIBE]); - let hidden = repo(&world, "hidden", Visibility::Private); - let ghost = RepoDid::new("did:plc:ghostfixture").unwrap(); + let hidden = world.fixture_repo("hidden", Visibility::Private); + let ghost = ghost(); let private = describe_as(&world, &hidden, &bobbin()).await; let missing = describe_as(&world, &ghost, &bobbin()).await; @@ -75,7 +51,7 @@ async fn a_declared_syncer_reads_a_private_repo_only_while_a_member_holds_a_leas #[tokio::test] async fn a_declared_syncer_is_refused_a_method_outside_its_list() { let world = world_declaring(&["com.atproto.sync.getRepo"]); - let open = repo(&world, "open", Visibility::Public); + let open = world.fixture_repo("open", Visibility::Public); let (status, body) = describe_as(&world, &open, &bobbin()).await; assert_eq!(status, StatusCode::UNAUTHORIZED, "{body}"); } @@ -83,9 +59,9 @@ async fn a_declared_syncer_is_refused_a_method_outside_its_list() { #[tokio::test] async fn an_undeclared_service_did_is_a_stranger() { let world = world_declaring(&[DESCRIBE]); - let open = repo(&world, "open", Visibility::Public); - let hidden = repo(&world, "hidden", Visibility::Private); - let ghost = RepoDid::new("did:plc:ghostfixture").unwrap(); + let open = world.fixture_repo("open", Visibility::Public); + let hidden = world.fixture_repo("hidden", Visibility::Private); + let ghost = ghost(); let deliberi = AccountDid::new("did:web:deliberi.example").unwrap(); let (status, body) = describe_as(&world, &open, &deliberi).await; @@ -98,7 +74,7 @@ async fn an_undeclared_service_did_is_a_stranger() { #[tokio::test] async fn a_declared_syncer_cant_write_even_when_its_list_names_the_write() { let world = world_declaring(&[DESCRIBE, CREATE]); - let open = repo(&world, "open", Visibility::Public); + let open = world.fixture_repo("open", Visibility::Public); let collection = common::ticket_collection(); let (status, answer) = post_as( &world, -- 2.54.0