@@ -1878,6 +1878,33 @@ mod tests {
18781878 assert_eq ! ( resp. status( ) , StatusCode :: UNAUTHORIZED ) ;
18791879 }
18801880
1881+ struct PinTestFixture {
1882+ owner : gitlawb_core:: identity:: Keypair ,
1883+ owner_did : String ,
1884+ fx : CidFixture ,
1885+ repo : crate :: db:: RepoRecord ,
1886+ }
1887+
1888+ async fn setup_pin_test ( state : & AppState , repo_name : & str ) -> PinTestFixture {
1889+ use gitlawb_core:: identity:: Keypair ;
1890+
1891+ let owner = Keypair :: generate ( ) ;
1892+ let owner_did = owner. did ( ) . to_string ( ) ;
1893+ let fs_slug = owner_did. replace ( [ ':' , '/' ] , "_" ) ;
1894+ let short = owner_did. split ( ':' ) . next_back ( ) . unwrap ( ) . to_string ( ) ;
1895+
1896+ let fx = seed_cid_repos ( & fs_slug, & short, & [ repo_name] ) ;
1897+ let repo = seed_repo ( & owner_did, repo_name) ;
1898+ state. db . create_repo ( & repo) . await . unwrap ( ) ;
1899+
1900+ PinTestFixture {
1901+ owner,
1902+ owner_did,
1903+ fx,
1904+ repo,
1905+ }
1906+ }
1907+
18811908 /// #121: authenticated caller gets 200 from /api/v1/ipfs/pins.
18821909 ///
18831910 /// list_pins only returns pins whose SHA-256 appears in a repo the caller
@@ -1889,41 +1916,20 @@ mod tests {
18891916 /// 3. Records a pin for the real object OID.
18901917 #[ sqlx:: test]
18911918 async fn pins_list_allows_authenticated ( pool : PgPool ) {
1892- use gitlawb_core:: identity:: Keypair ;
1893-
1894- let owner = Keypair :: generate ( ) ;
1895- let owner_did = owner. did ( ) . to_string ( ) ;
1896- // RepoStore maps owner_did to a filesystem slug by replacing ':' and '/' with '_'.
1897- let fs_slug = owner_did. replace ( [ ':' , '/' ] , "_" ) ;
1898- // The short slug is the last ':'-separated segment of the DID (what push writes).
1899- let short = owner_did. split ( ':' ) . next_back ( ) . unwrap ( ) . to_string ( ) ;
1900-
19011919 let state = test_state ( pool) . await ;
1920+ let setup = setup_pin_test ( & state, "pinrepo" ) . await ;
19021921
1903- // Build a real SHA-256 bare git repo under /tmp/<fs_slug>/pinrepo.git so
1904- // list_all_objects can enumerate actual object SHA-256 IDs.
1905- let fx = seed_cid_repos ( & fs_slug, & short, & [ "pinrepo" ] ) ;
1906- // seed_cid_repos uses sha256 object format; fx.public_oid is a real git OID.
1907- let pinned_sha = fx. public_oid . clone ( ) ;
1922+ let pinned_sha = setup. fx . public_oid . clone ( ) ;
19081923 let pinned_cid = cid_for_oid ( & pinned_sha) ;
19091924
1910- // Seed the repo DB record with the same owner_did so list_all_repos returns
1911- // it and visibility_check passes (public repo, no rules).
1912- state
1913- . db
1914- . create_repo ( & seed_repo ( & owner_did, "pinrepo" ) )
1915- . await
1916- . unwrap ( ) ;
1917-
1918- // Record the pin for the real git object SHA.
19191925 state
19201926 . db
19211927 . record_pinned_cid ( & pinned_sha, & pinned_cid)
19221928 . await
19231929 . unwrap ( ) ;
19241930
19251931 let resp = pins_router ( & state)
1926- . oneshot ( signed_get ( & owner, "/api/v1/ipfs/pins" ) )
1932+ . oneshot ( signed_get ( & setup . owner , "/api/v1/ipfs/pins" ) )
19271933 . await
19281934 . unwrap ( ) ;
19291935 assert_eq ! ( resp. status( ) , StatusCode :: OK ) ;
@@ -1940,22 +1946,17 @@ mod tests {
19401946
19411947 #[ sqlx:: test]
19421948 async fn pins_list_excludes_quarantined_repos ( pool : PgPool ) {
1943- use gitlawb_core:: identity:: Keypair ;
1944-
1945- let owner = Keypair :: generate ( ) ;
1946- let owner_did = owner. did ( ) . to_string ( ) ;
1947- let fs_slug = owner_did. replace ( [ ':' , '/' ] , "_" ) ;
1948- let short = owner_did. split ( ':' ) . next_back ( ) . unwrap ( ) . to_string ( ) ;
1949-
19501949 let state = test_state ( pool) . await ;
1950+ let setup = setup_pin_test ( & state, "pinrepo" ) . await ;
19511951
1952- let fx = seed_cid_repos ( & fs_slug, & short, & [ "pinrepo" ] ) ;
1953- let pinned_sha = fx. public_oid . clone ( ) ;
1952+ let pinned_sha = setup. fx . public_oid . clone ( ) ;
19541953 let pinned_cid = cid_for_oid ( & pinned_sha) ;
19551954
1956- let repo = seed_repo ( & owner_did, "pinrepo" ) ;
1957- state. db . create_repo ( & repo) . await . unwrap ( ) ;
1958- state. db . set_repo_quarantine ( & repo. id , true ) . await . unwrap ( ) ;
1955+ state
1956+ . db
1957+ . set_repo_quarantine ( & setup. repo . id , true )
1958+ . await
1959+ . unwrap ( ) ;
19591960
19601961 state
19611962 . db
@@ -1964,7 +1965,7 @@ mod tests {
19641965 . unwrap ( ) ;
19651966
19661967 let resp = pins_router ( & state)
1967- . oneshot ( signed_get ( & owner, "/api/v1/ipfs/pins" ) )
1968+ . oneshot ( signed_get ( & setup . owner , "/api/v1/ipfs/pins" ) )
19681969 . await
19691970 . unwrap ( ) ;
19701971 assert_eq ! ( resp. status( ) , StatusCode :: OK ) ;
@@ -1980,27 +1981,24 @@ mod tests {
19801981 use crate :: db:: VisibilityMode ;
19811982 use gitlawb_core:: identity:: Keypair ;
19821983
1983- let owner = Keypair :: generate ( ) ;
1984- let owner_did = owner. did ( ) . to_string ( ) ;
1985- let fs_slug = owner_did. replace ( [ ':' , '/' ] , "_" ) ;
1986- let short = owner_did. split ( ':' ) . next_back ( ) . unwrap ( ) . to_string ( ) ;
1987-
1988- let stranger = Keypair :: generate ( ) ;
1989-
19901984 let state = test_state ( pool) . await ;
1985+ let setup = setup_pin_test ( & state, "pinrepo" ) . await ;
1986+ let stranger = Keypair :: generate ( ) ;
19911987
1992- let fx = seed_cid_repos ( & fs_slug, & short, & [ "pinrepo" ] ) ;
1993- let public_sha = fx. public_oid . clone ( ) ;
1988+ let public_sha = setup. fx . public_oid . clone ( ) ;
19941989 let public_cid = cid_for_oid ( & public_sha) ;
1995- let secret_sha = fx. secret_oid . clone ( ) ;
1990+ let secret_sha = setup . fx . secret_oid . clone ( ) ;
19961991 let secret_cid = cid_for_oid ( & secret_sha) ;
19971992
1998- let repo = seed_repo ( & owner_did, "pinrepo" ) ;
1999- state. db . create_repo ( & repo) . await . unwrap ( ) ;
2000-
20011993 state
20021994 . db
2003- . set_visibility_rule ( & repo. id , "/secret/**" , VisibilityMode :: B , & [ ] , & owner_did)
1995+ . set_visibility_rule (
1996+ & setup. repo . id ,
1997+ "/secret/**" ,
1998+ VisibilityMode :: B ,
1999+ & [ ] ,
2000+ & setup. owner_did ,
2001+ )
20042002 . await
20052003 . unwrap ( ) ;
20062004
@@ -2069,6 +2067,35 @@ mod tests {
20692067 assert_eq ! ( resp. status( ) , StatusCode :: UNAUTHORIZED ) ;
20702068 }
20712069
2070+ struct AnchorTestFixture {
2071+ owner : gitlawb_core:: identity:: Keypair ,
2072+ owner_did : String ,
2073+ }
2074+
2075+ async fn setup_anchor_test (
2076+ state : & AppState ,
2077+ repo_name : & str ,
2078+ is_public : bool ,
2079+ ) -> AnchorTestFixture {
2080+ use gitlawb_core:: identity:: Keypair ;
2081+
2082+ let owner = Keypair :: generate ( ) ;
2083+ let owner_did = owner. did ( ) . to_string ( ) ;
2084+ let owner_short = owner_did. split ( ':' ) . next_back ( ) . unwrap ( ) . to_string ( ) ;
2085+ let short_slug = format ! ( "{owner_short}/{repo_name}" ) ;
2086+
2087+ let repo = if is_public {
2088+ seed_repo ( & owner_did, repo_name)
2089+ } else {
2090+ seed_private_repo ( & owner_did, repo_name)
2091+ } ;
2092+
2093+ state. db . create_repo ( & repo) . await . unwrap ( ) ;
2094+ seed_anchor ( & state. db , & short_slug, & owner_did) . await ;
2095+
2096+ AnchorTestFixture { owner, owner_did }
2097+ }
2098+
20722099 /// #121: /api/v1/arweave/anchors without ?repo= allows authenticated.
20732100 ///
20742101 /// The global listing filters each anchor through authorize_repo_read, which
@@ -2078,29 +2105,11 @@ mod tests {
20782105 /// LIKE match finds the repo record.
20792106 #[ sqlx:: test]
20802107 async fn anchors_global_allows_authenticated ( pool : PgPool ) {
2081- use gitlawb_core:: identity:: Keypair ;
2082-
20832108 let state = test_state ( pool) . await ;
2084- let kp = Keypair :: generate ( ) ;
2085- let owner_did = kp. did ( ) . to_string ( ) ;
2086- let repo_name = "globalrepo" ;
2087- // Short slug: the last ':'-separated segment of the DID, matching what
2088- // repos.rs writes to the arweave_anchors table on push.
2089- let owner_short = owner_did. split ( ':' ) . next_back ( ) . unwrap ( ) . to_string ( ) ;
2090- let short_slug = format ! ( "{owner_short}/{repo_name}" ) ;
2091-
2092- // Seed a public repo so authorize_repo_read succeeds for this anchor.
2093- state
2094- . db
2095- . create_repo ( & seed_repo ( & owner_did, repo_name) )
2096- . await
2097- . unwrap ( ) ;
2098-
2099- // Record the anchor with the short-form slug that production writes.
2100- seed_anchor ( & state. db , & short_slug, & owner_did) . await ;
2109+ let fx = setup_anchor_test ( & state, "globalrepo" , true ) . await ;
21012110
21022111 let resp = anchors_router ( & state)
2103- . oneshot ( signed_get ( & kp , "/api/v1/arweave/anchors" ) )
2112+ . oneshot ( signed_get ( & fx . owner , "/api/v1/arweave/anchors" ) )
21042113 . await
21052114 . unwrap ( ) ;
21062115 assert_eq ! ( resp. status( ) , StatusCode :: OK ) ;
@@ -2114,22 +2123,9 @@ mod tests {
21142123 #[ sqlx:: test]
21152124 async fn anchors_global_denies_non_reader ( pool : PgPool ) {
21162125 use gitlawb_core:: identity:: Keypair ;
2117-
21182126 let state = test_state ( pool) . await ;
2119- let owner = Keypair :: generate ( ) ;
2120- let owner_did = owner. did ( ) . to_string ( ) ;
2127+ let _fx = setup_anchor_test ( & state, "privaterepo" , false ) . await ;
21212128 let stranger = Keypair :: generate ( ) ;
2122- let repo_name = "privaterepo" ;
2123- let owner_short = owner_did. split ( ':' ) . next_back ( ) . unwrap ( ) . to_string ( ) ;
2124- let short_slug = format ! ( "{owner_short}/{repo_name}" ) ;
2125-
2126- state
2127- . db
2128- . create_repo ( & seed_private_repo ( & owner_did, repo_name) )
2129- . await
2130- . unwrap ( ) ;
2131-
2132- seed_anchor ( & state. db , & short_slug, & owner_did) . await ;
21332129
21342130 let resp = anchors_router ( & state)
21352131 . oneshot ( signed_get ( & stranger, "/api/v1/arweave/anchors" ) )
@@ -2149,31 +2145,42 @@ mod tests {
21492145 /// 404 for anon on private) — no anonymous-rejection guard is needed here.
21502146 #[ sqlx:: test]
21512147 async fn anchors_repo_denies_anonymous_on_private ( pool : PgPool ) {
2152- use gitlawb_core:: identity:: Keypair ;
2153-
21542148 let state = test_state ( pool) . await ;
2155- let owner = Keypair :: generate ( ) ;
2156- let owner_did = owner. did ( ) . to_string ( ) ;
21572149 let repo_name = "private-repo" ;
2158- // Short slug written by the push path.
2159- let owner_short = owner_did. split ( ':' ) . next_back ( ) . unwrap ( ) . to_string ( ) ;
2160- let short_slug = format ! ( "{owner_short}/{repo_name}" ) ;
2150+ let fx = setup_anchor_test ( & state, repo_name, false ) . await ;
21612151
2162- state
2163- . db
2164- . create_repo ( & seed_private_repo ( & owner_did, repo_name) )
2152+ let uri = format ! (
2153+ "/api/v1/arweave/anchors?repo={}/{}" ,
2154+ fx. owner_did, repo_name
2155+ ) ;
2156+ let resp = anchors_router ( & state)
2157+ . oneshot ( anon_get ( & uri) )
21652158 . await
21662159 . unwrap ( ) ;
2167- // Store anchor with the short slug that production writes.
2168- seed_anchor ( & state. db , & short_slug, & owner_did) . await ;
2160+ assert_eq ! ( resp. status( ) , StatusCode :: NOT_FOUND ) ;
2161+ }
2162+
2163+ /// #121: /api/v1/arweave/anchors with ?repo= allows anonymous on public repo.
2164+ #[ sqlx:: test]
2165+ async fn anchors_repo_allows_anonymous_on_public ( pool : PgPool ) {
2166+ let state = test_state ( pool) . await ;
2167+ let repo_name = "public-repo" ;
2168+ let fx = setup_anchor_test ( & state, repo_name, true ) . await ;
21692169
2170- // ?repo= accepts the full DID form; list_anchors normalises it before querying.
2171- let uri = format ! ( "/api/v1/arweave/anchors?repo={owner_did}/{repo_name}" ) ;
2170+ let uri = format ! (
2171+ "/api/v1/arweave/anchors?repo={}/{}" ,
2172+ fx. owner_did, repo_name
2173+ ) ;
21722174 let resp = anchors_router ( & state)
21732175 . oneshot ( anon_get ( & uri) )
21742176 . await
21752177 . unwrap ( ) ;
2176- assert_eq ! ( resp. status( ) , StatusCode :: NOT_FOUND ) ;
2178+ assert_eq ! ( resp. status( ) , StatusCode :: OK ) ;
2179+ let body = json_body ( resp) . await ;
2180+ assert_eq ! (
2181+ body[ "count" ] , 1 ,
2182+ "anonymous caller sees the seeded public anchor"
2183+ ) ;
21772184 }
21782185
21792186 /// #121: /api/v1/arweave/anchors with ?repo= allows repo owner.
@@ -2183,27 +2190,16 @@ mod tests {
21832190 /// slug before issuing the DB query.
21842191 #[ sqlx:: test]
21852192 async fn anchors_repo_allows_owner ( pool : PgPool ) {
2186- use gitlawb_core:: identity:: Keypair ;
2187-
21882193 let state = test_state ( pool) . await ;
2189- let owner = Keypair :: generate ( ) ;
2190- let owner_did = owner. did ( ) . to_string ( ) ;
21912194 let repo_name = "owners-repo" ;
2192- // Short slug matching what repos.rs writes on push.
2193- let owner_short = owner_did. split ( ':' ) . next_back ( ) . unwrap ( ) . to_string ( ) ;
2194- let short_slug = format ! ( "{owner_short}/{repo_name}" ) ;
2195-
2196- state
2197- . db
2198- . create_repo ( & seed_private_repo ( & owner_did, repo_name) )
2199- . await
2200- . unwrap ( ) ;
2201- // Store anchor with the short slug that production writes.
2202- seed_anchor ( & state. db , & short_slug, & owner_did) . await ;
2195+ let fx = setup_anchor_test ( & state, repo_name, false ) . await ;
22032196
2204- let uri = format ! ( "/api/v1/arweave/anchors?repo={owner_did}/{repo_name}" ) ;
2197+ let uri = format ! (
2198+ "/api/v1/arweave/anchors?repo={}/{}" ,
2199+ fx. owner_did, repo_name
2200+ ) ;
22052201 let resp = anchors_router ( & state)
2206- . oneshot ( signed_get ( & owner, & uri) )
2202+ . oneshot ( signed_get ( & fx . owner , & uri) )
22072203 . await
22082204 . unwrap ( ) ;
22092205 assert_eq ! ( resp. status( ) , StatusCode :: OK ) ;
@@ -2215,23 +2211,15 @@ mod tests {
22152211 #[ sqlx:: test]
22162212 async fn anchors_repo_denies_non_reader ( pool : PgPool ) {
22172213 use gitlawb_core:: identity:: Keypair ;
2218-
22192214 let state = test_state ( pool) . await ;
2220- let owner = Keypair :: generate ( ) ;
2221- let owner_did = owner. did ( ) . to_string ( ) ;
2222- let stranger = Keypair :: generate ( ) ;
22232215 let repo_name = "private-repo" ;
2224- let owner_short = owner_did. split ( ':' ) . next_back ( ) . unwrap ( ) . to_string ( ) ;
2225- let short_slug = format ! ( "{owner_short}/{repo_name}" ) ;
2226-
2227- state
2228- . db
2229- . create_repo ( & seed_private_repo ( & owner_did, repo_name) )
2230- . await
2231- . unwrap ( ) ;
2232- seed_anchor ( & state. db , & short_slug, & owner_did) . await ;
2216+ let fx = setup_anchor_test ( & state, repo_name, false ) . await ;
2217+ let stranger = Keypair :: generate ( ) ;
22332218
2234- let uri = format ! ( "/api/v1/arweave/anchors?repo={owner_did}/{repo_name}" ) ;
2219+ let uri = format ! (
2220+ "/api/v1/arweave/anchors?repo={}/{}" ,
2221+ fx. owner_did, repo_name
2222+ ) ;
22352223 let resp = anchors_router ( & state)
22362224 . oneshot ( signed_get ( & stranger, & uri) )
22372225 . await
0 commit comments