This repository has no description
1use std::future::Future;
2use std::pin::Pin;
3use std::sync::Arc;
4
5use axum::body::Body;
6use axum::http::{Request, StatusCode};
7use http_body_util::BodyExt;
8use knot_git::Layout;
9use knot_pack::{HandleResolver, RepoLookup, RepoResolver, RepoTarget};
10use knot_types::{AccountDid, Handle, OwnerDid, RepoDid};
11use tower::ServiceExt;
12
13struct FakeHandles;
14
15impl HandleResolver for FakeHandles {
16 fn resolve(
17 &self,
18 handle: Handle,
19 ) -> Pin<Box<dyn Future<Output = Option<AccountDid>> + Send + '_>> {
20 Box::pin(async move {
21 (handle.as_str() == "nel.pet").then(|| AccountDid::new("did:plc:nel").unwrap())
22 })
23 }
24}
25
26fn repo_resolver() -> Arc<dyn RepoResolver> {
27 let owner = OwnerDid::new("did:plc:nel").unwrap();
28 let repo = RepoDid::new("did:plc:whelk").unwrap();
29 Arc::new(move |target: &RepoTarget| match target {
30 RepoTarget::OwnerPath(o, p)
31 if *o == owner && p.rkeys().any(|rkey| rkey.as_str() == "squid") =>
32 {
33 RepoLookup::Hosted(repo.clone())
34 }
35 RepoTarget::Did(d) if *d == repo => RepoLookup::Hosted(d.clone()),
36 _ => RepoLookup::Unhosted,
37 })
38}
39
40fn build(layout: &Layout, handle_resolver: Option<Arc<dyn HandleResolver>>) -> axum::Router {
41 let (_write, advertisement) = knot_pack::edge_routes(knot_pack::EdgeConfig {
42 handle_resolver,
43 pack_slots: knot_resource::PackSlots::new(4),
44 ..knot_pack::EdgeConfig::serving(
45 layout.clone(),
46 repo_resolver(),
47 Arc::new(knot_runtime::SystemClock),
48 )
49 });
50 advertisement.into_router()
51}
52
53async fn get(router: axum::Router, uri: &str) -> (StatusCode, Vec<u8>) {
54 let response = router
55 .oneshot(Request::get(uri).body(Body::empty()).unwrap())
56 .await
57 .unwrap();
58 let status = response.status();
59 let body = response
60 .into_body()
61 .collect()
62 .await
63 .unwrap()
64 .to_bytes()
65 .to_vec();
66 (status, body)
67}
68
69fn hosted_layout() -> (tempfile::TempDir, Layout) {
70 let scan = tempfile::tempdir().unwrap();
71 let layout = Layout::new(scan.path());
72 layout
73 .create(&RepoDid::new("did:plc:whelk").unwrap())
74 .unwrap();
75 (scan, layout)
76}
77
78#[tokio::test]
79async fn a_handle_owner_serves_the_same_repo_as_its_did() {
80 let (_scan, layout) = hosted_layout();
81 let (handle_status, handle_body) = get(
82 build(&layout, Some(Arc::new(FakeHandles))),
83 "/nel.pet/squid/info/refs?service=git-upload-pack",
84 )
85 .await;
86 let (did_status, did_body) = get(
87 build(&layout, Some(Arc::new(FakeHandles))),
88 "/did:plc:nel/squid/info/refs?service=git-upload-pack",
89 )
90 .await;
91 assert_eq!(handle_status, StatusCode::OK);
92 assert_eq!(did_status, StatusCode::OK);
93 assert_eq!(
94 handle_body, did_body,
95 "handle owner and DID owner must serve the same repository"
96 );
97}
98
99#[tokio::test]
100async fn a_handle_owner_is_not_found_when_unknown_or_unresolvable() {
101 let (_scan, layout) = hosted_layout();
102 let (unknown, _) = get(
103 build(&layout, Some(Arc::new(FakeHandles))),
104 "/olaren.dev/squid/info/refs?service=git-upload-pack",
105 )
106 .await;
107 assert_eq!(
108 unknown,
109 StatusCode::NOT_FOUND,
110 "a handle the resolver rejects isn't found"
111 );
112 let (no_resolver, _) = get(
113 build(&layout, None),
114 "/nel.pet/squid/info/refs?service=git-upload-pack",
115 )
116 .await;
117 assert_eq!(
118 no_resolver,
119 StatusCode::NOT_FOUND,
120 "a handle owner with no resolver configured isn't found"
121 );
122}