diff options
-rw-r--r-- | Cargo.lock | 63 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | src/main.rs | 39 | ||||
-rw-r--r-- | src/tests.rs | 104 |
4 files changed, 198 insertions, 11 deletions
@@ -136,6 +136,12 @@ dependencies = [ ] [[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + +[[package]] name = "futures-channel" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -223,6 +229,7 @@ dependencies = [ "pulldown-cmark", "serde", "sputnik", + "tempdir", "tokio", "toml", "url", @@ -633,6 +640,52 @@ dependencies = [ ] [[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] name = "ryu" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -728,6 +781,16 @@ dependencies = [ ] [[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand", + "remove_dir_all", +] + +[[package]] name = "termcolor" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -37,3 +37,6 @@ camino = "1" [target.'cfg(unix)'.dependencies] hyperlocal = { version = "0.8", features = ["server"] } + +[dev-dependencies] +tempdir = "0.3.7" diff --git a/src/main.rs b/src/main.rs index 9251003..f1f9caa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,6 +49,7 @@ mod get_routes; mod origins; mod post_routes; mod shares; +mod tests; pub enum Response { Raw(HyperResponse), @@ -94,17 +95,22 @@ struct Args { #[tokio::main] async fn main() { let args = Args::parse(); - let repo = Repository::open_bare(env::current_dir().unwrap()) + let repo_path: &'static _ = Box::leak(Box::new(env::current_dir().unwrap())); + let repo = Repository::open_bare(repo_path) .expect("expected current directory to be a bare Git repository"); if args.multiuser { - serve(MultiUserController::new(&repo), args).await; + serve(repo_path, MultiUserController::new(&repo), args).await; } else { - serve(SoloController::new(&repo), args).await; + serve(repo_path, SoloController::new(&repo), args).await; } } -async fn serve<C: Controller + Send + Sync + 'static>(controller: C, args: Args) { +async fn serve<C: Controller + Send + Sync + 'static>( + repo_path: &'static Path, + controller: C, + args: Args, +) { let controller = Arc::new(controller); #[cfg(unix)] @@ -121,7 +127,7 @@ async fn serve<C: Controller + Send + Sync + 'static>(controller: C, args: Args) async move { Ok::<_, hyper::Error>(service_fn(move |req| { - service(origin, controller.clone(), req) + service_wrapper(repo_path, origin, controller.clone(), req) })) } }); @@ -173,7 +179,7 @@ async fn serve<C: Controller + Send + Sync + 'static>(controller: C, args: Args) async move { Ok::<_, hyper::Error>(service_fn(move |req| { - service(origin, controller.clone(), req) + service_wrapper(repo_path, origin, controller.clone(), req) })) } }); @@ -182,17 +188,27 @@ async fn serve<C: Controller + Send + Sync + 'static>(controller: C, args: Args) server.await.expect("server error"); } -async fn service<C: Controller>( +async fn service_wrapper<C: Controller>( + repo_path: &Path, origin: &HttpOrigin, controller: Arc<C>, request: Request, ) -> Result<HyperResponse, Infallible> { + Ok(service(repo_path, origin, &*controller, request).await) +} + +async fn service<C: Controller>( + repo_path: &Path, + origin: &HttpOrigin, + controller: &C, + request: Request, +) -> HyperResponse { let (mut parts, body) = request.into_parts(); let mut script_csp = "'none'".into(); let mut frame_csp = "'none'"; - let mut resp = build_response(origin, &*controller, &mut parts, body) + let mut resp = build_response(repo_path, origin, controller, &mut parts, body) .await .map(|resp| match resp { Response::Raw(resp) => resp, @@ -205,7 +221,7 @@ async fn service<C: Controller>( } Builder::new() .content_type(mime::TEXT_HTML) - .body(render_page(&page, &*controller, &parts).into()) + .body(render_page(&page, controller, &parts).into()) .unwrap() } }) @@ -224,7 +240,7 @@ async fn service<C: Controller>( .parse() .unwrap() }); - Ok(resp) + resp } #[derive(Default)] @@ -284,6 +300,7 @@ impl Branch { } async fn build_response<C: Controller>( + repo_path: &Path, origin: &HttpOrigin, controller: &C, parts: &mut Parts, @@ -309,7 +326,7 @@ async fn build_response<C: Controller>( .map_err(|_| Error::BadRequest("failed to percent-decode path as UTF-8".into()))? .into_owned(); - let repo = Repository::open_bare(env::current_dir().unwrap()).unwrap(); + let repo = Repository::open_bare(repo_path).unwrap(); let (rev, unsanitized_path) = match controller.parse_url_path(&unsanitized_path, parts, &repo) { Ok(parsed) => parsed, diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..3eb71ec --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,104 @@ +#![cfg(test)] +use std::{future::Future, path::PathBuf, pin::Pin}; + +use crate::controller::Controller; + +use super::{ + controller::{MultiUserController, SoloController}, + origins::HttpOrigin, +}; +use git2::Repository; +use hyper::{body, http::request::Builder, Body, Request, Response}; +use tempdir::TempDir; + +fn temp_repo() -> (PathBuf, Repository) { + let path = TempDir::new("gitpad-test").unwrap().into_path(); + let repo = Repository::init_bare(&path).unwrap(); + (path, repo) +} + +fn origin() -> HttpOrigin { + "http://pad.example.com".parse().unwrap() +} + +struct Server<C> { + repo_path: PathBuf, + repo: Repository, + origin: HttpOrigin, + controller: C, +} + +impl Server<SoloController> { + fn new() -> Self { + let (repo_path, repo) = temp_repo(); + let origin = origin(); + Self { + repo_path, + origin, + controller: SoloController::new(&repo), + repo, + } + } +} + +impl Server<MultiUserController> { + fn new() -> Self { + let (repo_path, repo) = temp_repo(); + let origin = origin(); + Self { + repo_path, + origin, + controller: MultiUserController::new(&repo), + repo, + } + } +} + +impl<C: Controller> Server<C> { + async fn serve(&self, req: Request<Body>) -> Response<Body> { + super::service(&self.repo_path, &self.origin, &self.controller, req).await + } +} + +trait IntoText { + fn into_text(self) -> Pin<Box<dyn Future<Output = String>>>; +} + +impl IntoText for Body { + fn into_text(self) -> Pin<Box<dyn Future<Output = String>>> { + Box::pin(async move { + std::str::from_utf8(&body::to_bytes(self).await.unwrap()) + .unwrap() + .to_string() + }) + } +} + +#[tokio::test] +async fn test_no_host_header() { + let server = Server::<SoloController>::new(); + let res = server.serve(Builder::new().body("".into()).unwrap()).await; + assert_eq!(res.status(), 400); + assert!(res + .into_body() + .into_text() + .await + .contains("Host header required")); +} + +#[tokio::test] +async fn test_missing_branch() { + let server = Server::<SoloController>::new(); + + let res = server + .serve( + Builder::new() + .header("host", server.origin.host()) + .body("".into()) + .unwrap(), + ) + .await; + assert_eq!(res.status(), 404); // branch not found + + // TODO: create commit with file and test that it is retrievable +} |