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::{CacheConfig, 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(
42 layout.clone(),
43 repo_resolver(),
44 None,
45 handle_resolver,
46 knot_resource::PackSlots::new(4),
47 CacheConfig::default(),
48 Arc::new(knot_messages::Catalog::defaults()),
49 knot_pack::default_hostname().clone(),
50 Arc::new(knot_runtime::SystemClock),
51 );
52 advertisement.into_router()
53}
54
55async fn get(router: axum::Router, uri: &str) -> (StatusCode, Vec<u8>) {
56 let response = router
57 .oneshot(Request::get(uri).body(Body::empty()).unwrap())
58 .await
59 .unwrap();
60 let status = response.status();
61 let body = response
62 .into_body()
63 .collect()
64 .await
65 .unwrap()
66 .to_bytes()
67 .to_vec();
68 (status, body)
69}
70
71fn hosted_layout() -> (tempfile::TempDir, Layout) {
72 let scan = tempfile::tempdir().unwrap();
73 let layout = Layout::new(scan.path());
74 layout
75 .create(&RepoDid::new("did:plc:whelk").unwrap())
76 .unwrap();
77 (scan, layout)
78}
79
80#[tokio::test]
81async fn a_handle_owner_serves_the_same_repo_as_its_did() {
82 let (_scan, layout) = hosted_layout();
83 let (handle_status, handle_body) = get(
84 build(&layout, Some(Arc::new(FakeHandles))),
85 "/nel.pet/squid/info/refs?service=git-upload-pack",
86 )
87 .await;
88 let (did_status, did_body) = get(
89 build(&layout, Some(Arc::new(FakeHandles))),
90 "/did:plc:nel/squid/info/refs?service=git-upload-pack",
91 )
92 .await;
93 assert_eq!(handle_status, StatusCode::OK);
94 assert_eq!(did_status, StatusCode::OK);
95 assert_eq!(
96 handle_body, did_body,
97 "handle owner and DID owner must serve the same repository"
98 );
99}
100
101#[tokio::test]
102async fn a_handle_owner_is_not_found_when_unknown_or_unresolvable() {
103 let (_scan, layout) = hosted_layout();
104 let (unknown, _) = get(
105 build(&layout, Some(Arc::new(FakeHandles))),
106 "/olaren.dev/squid/info/refs?service=git-upload-pack",
107 )
108 .await;
109 assert_eq!(
110 unknown,
111 StatusCode::NOT_FOUND,
112 "a handle the resolver rejects isn't found"
113 );
114 let (no_resolver, _) = get(
115 build(&layout, None),
116 "/nel.pet/squid/info/refs?service=git-upload-pack",
117 )
118 .await;
119 assert_eq!(
120 no_resolver,
121 StatusCode::NOT_FOUND,
122 "a handle owner with no resolver configured isn't found"
123 );
124}