Skip to content

Commit

Permalink
introduce an extension trait for testing servers
Browse files Browse the repository at this point in the history
  • Loading branch information
jbr committed Jul 10, 2020
1 parent dc4f4ac commit b8f2444
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 257 deletions.
84 changes: 17 additions & 67 deletions tests/nested.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use http_types::{Method, Request, Response, Url};
use test_utils::BoxFuture;
use tide::{Middleware, Next};

use test_utils::ServerTestingExt;
mod test_utils;

#[async_std::test]
Expand All @@ -14,80 +11,36 @@ async fn nested() {
// Nest the inner app on /foo
outer.at("/foo").nest(inner);

let req = Request::new(
Method::Get,
Url::parse("http://example.com/foo/foo").unwrap(),
);
let mut res: Response = outer.respond(req).await.unwrap();
assert_eq!(res.status(), 200);
assert_eq!(res.body_string().await.unwrap(), "foo");

let req = Request::new(
Method::Get,
Url::parse("http://example.com/foo/bar").unwrap(),
);
let mut res: Response = outer.respond(req).await.unwrap();
assert_eq!(res.status(), 200);
assert_eq!(res.body_string().await.unwrap(), "bar");
assert_eq!(outer.get_body("/foo/foo").await, "foo");
assert_eq!(outer.get_body("/foo/bar").await, "bar");
}

#[async_std::test]
async fn nested_middleware() {
let echo_path = |req: tide::Request<()>| async move { Ok(req.url().path().to_string()) };

#[derive(Debug, Clone, Default)]
pub struct TestMiddleware;

impl TestMiddleware {
pub fn new() -> Self {
Self {}
}
}

impl<State: Send + Sync + 'static> Middleware<State> for TestMiddleware {
fn handle<'a>(
&'a self,
req: tide::Request<State>,
next: Next<'a, State>,
) -> BoxFuture<'a, tide::Result<tide::Response>> {
Box::pin(async move {
let mut res = next.run(req).await?;
res.insert_header("X-Tide-Test", "1");
Ok(res)
})
}
}

let mut app = tide::new();

let mut inner_app = tide::new();
inner_app.middleware(TestMiddleware::new());
inner_app.middleware(tide::utils::After(|res: tide::Result| async move {
res.map(|mut r| {
r.insert_header("x-tide-test", "1");
r
})
}));
inner_app.at("/echo").get(echo_path);
inner_app.at("/:foo/bar").strip_prefix().get(echo_path);
app.at("/foo").nest(inner_app);

app.at("/bar").get(echo_path);

let req = Request::new(
Method::Get,
Url::parse("http://example.com/foo/echo").unwrap(),
);
let mut res: Response = app.respond(req).await.unwrap();
let mut res = app.get("/foo/echo").await;
assert_eq!(res["X-Tide-Test"], "1");
assert_eq!(res.status(), 200);
assert_eq!(res.body_string().await.unwrap(), "/echo");

let req = Request::new(
Method::Get,
Url::parse("http://example.com/foo/x/bar").unwrap(),
);
let mut res: Response = app.respond(req).await.unwrap();
let mut res = app.get("/foo/x/bar").await;
assert_eq!(res["X-Tide-Test"], "1");
assert_eq!(res.status(), 200);
assert_eq!(res.body_string().await.unwrap(), "/");

let req = Request::new(Method::Get, Url::parse("http://example.com/bar").unwrap());
let mut res: Response = app.respond(req).await.unwrap();
let mut res = app.get("/bar").await;
assert!(res.header("X-Tide-Test").is_none());
assert_eq!(res.status(), 200);
assert_eq!(res.body_string().await.unwrap(), "/bar");
Expand All @@ -104,13 +57,10 @@ async fn nested_with_different_state() {
outer.at("/").get(|_| async { Ok("Hello, world!") });
outer.at("/foo").nest(inner);

let req = Request::new(Method::Get, Url::parse("http://example.com/foo").unwrap());
let mut res: Response = outer.respond(req).await.unwrap();
assert_eq!(res.status(), 200);
assert_eq!(res.body_string().await.unwrap(), "the number is 42");
assert_eq!(outer.get_body("/foo").await, "the number is 42");
assert_eq!(outer.get_body("/").await, "Hello, world!");
}

let req = Request::new(Method::Get, Url::parse("http://example.com/").unwrap());
let mut res: Response = outer.respond(req).await.unwrap();
assert_eq!(res.status(), 200);
assert_eq!(res.body_string().await.unwrap(), "Hello, world!");
async fn echo_path<State>(req: tide::Request<State>) -> tide::Result {
Ok(req.url().path().to_string().into())
}
17 changes: 6 additions & 11 deletions tests/response.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod test_utils;

use tide::http::{self, headers, mime, StatusCode};
use tide::Response;
use test_utils::ServerTestingExt;
use tide::http::{headers, mime};
use tide::{Response, StatusCode};

#[async_std::test]
async fn test_status() {
Expand Down Expand Up @@ -38,7 +38,6 @@ async fn string_content_type() {
#[async_std::test]
async fn json_content_type() {
use std::collections::BTreeMap;
use tide::http::{Method, Url};
use tide::Body;

let mut app = tide::new();
Expand All @@ -51,14 +50,10 @@ async fn json_content_type() {
resp.set_body(Body::from_json(&map)?);
Ok(resp)
});
let req = http::Request::new(
Method::Get,
Url::parse("http://localhost/json_content_type").unwrap(),
);
let mut resp: http::Response = app.respond(req).await.unwrap();

let mut resp = app.get("/json_content_type").await;
assert_eq!(resp.status(), StatusCode::InternalServerError);
let body = resp.take_body().into_bytes().await.unwrap();
assert_eq!(body, b"");
assert_eq!(resp.body_string().await.unwrap(), "");

let mut resp = async move {
let mut map = BTreeMap::new();
Expand Down
43 changes: 11 additions & 32 deletions tests/route_middleware.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
mod test_utils;
use http_types::headers::HeaderName;
use std::convert::TryInto;
use tide::http::{self, url::Url, Method};
use test_utils::{BoxFuture, ServerTestingExt};
use tide::Middleware;

use test_utils::BoxFuture;

mod test_utils;

#[derive(Debug)]
struct TestMiddleware(HeaderName, &'static str);

Expand Down Expand Up @@ -50,20 +47,11 @@ async fn route_middleware() {
.reset_middleware()
.put(echo_path);

let req = http::Request::new(Method::Get, Url::parse("http://localhost/foo").unwrap());
let res: http::Response = app.respond(req).await.unwrap();
assert_eq!(res["X-Foo"], "foo");

let req = http::Request::new(Method::Post, Url::parse("http://localhost/foo").unwrap());
let res: http::Response = app.respond(req).await.unwrap();
assert_eq!(res["X-Foo"], "foo");
assert_eq!(app.get("/foo").await["X-Foo"], "foo");
assert_eq!(app.post("/foo").await["X-Foo"], "foo");
assert!(app.put("/foo").await.header("X-Foo").is_none());

let req = http::Request::new(Method::Put, Url::parse("http://localhost/foo").unwrap());
let res: http::Response = app.respond(req).await.unwrap();
assert!(res.header("X-Foo").is_none());

let req = http::Request::new(Method::Get, Url::parse("http://localhost/foo/bar").unwrap());
let res: http::Response = app.respond(req).await.unwrap();
let res = app.get("/foo/bar").await;
assert_eq!(res["X-Foo"], "foo");
assert_eq!(res["x-bar"], "bar");
}
Expand All @@ -79,15 +67,12 @@ async fn app_and_route_middleware() {
.middleware(TestMiddleware::with_header_name("X-Bar", "bar"))
.get(echo_path);

let req = http::Request::new(Method::Get, Url::parse("http://localhost/foo").unwrap());
let res: http::Response = app.respond(req).await.unwrap();
let res = app.get("/foo").await;
assert_eq!(res["X-Root"], "root");
assert_eq!(res["x-foo"], "foo");

assert!(res.header("x-bar").is_none());

let req = http::Request::new(Method::Get, Url::parse("http://localhost/bar").unwrap());
let res: http::Response = app.respond(req).await.unwrap();
let res = app.get("/bar").await;
assert_eq!(res["X-Root"], "root");
assert!(res.header("x-foo").is_none());
assert_eq!(res["X-Bar"], "bar");
Expand All @@ -111,16 +96,14 @@ async fn nested_app_with_route_middleware() {
.middleware(TestMiddleware::with_header_name("X-Bar", "bar"))
.nest(inner);

let req = http::Request::new(Method::Get, Url::parse("http://localhost/foo").unwrap());
let res: http::Response = app.respond(req).await.unwrap();
let res = app.get("/foo").await;
assert_eq!(res["X-Root"], "root");
assert!(res.header("X-Inner").is_none());
assert_eq!(res["X-Foo"], "foo");
assert!(res.header("X-Bar").is_none());
assert!(res.header("X-Baz").is_none());

let req = http::Request::new(Method::Get, Url::parse("http://localhost/bar/baz").unwrap());
let res: http::Response = app.respond(req).await.unwrap();
let res = app.get("/bar/baz").await;
assert_eq!(res["X-Root"], "root");
assert_eq!(res["X-Inner"], "inner");
assert!(res.header("X-Foo").is_none());
Expand All @@ -138,11 +121,7 @@ async fn subroute_not_nested() {
.middleware(TestMiddleware::with_header_name("X-Child", "child"))
.get(echo_path);

let req = http::Request::new(
Method::Get,
Url::parse("http://localhost/parent/child").unwrap(),
);
let res: http::Response = app.respond(req).await.unwrap();
let res = app.get("/parent/child").await;
assert!(res.header("X-Parent").is_none());
assert_eq!(res["x-child"], "child");
}
71 changes: 71 additions & 0 deletions tests/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use portpicker::pick_unused_port;

use std::future::Future;
use std::pin::Pin;
use tide::http::{self, url::Url, Method};
use tide::Server;

/// An owned dynamically typed [`Future`] for use in cases where you can't
/// statically type your result or need to add some indirection.
Expand All @@ -13,3 +15,72 @@ pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
pub async fn find_port() -> u16 {
pick_unused_port().expect("No ports free")
}

pub trait ServerTestingExt {
fn request<'a>(
&'a self,
method: Method,
path: &str,
) -> Pin<Box<dyn Future<Output = http::Response> + 'a + Send>>;
fn request_body<'a>(
&'a self,
method: Method,
path: &str,
) -> Pin<Box<dyn Future<Output = String> + 'a + Send>>;
fn get<'a>(&'a self, path: &str) -> Pin<Box<dyn Future<Output = http::Response> + 'a + Send>>;
fn get_body<'a>(&'a self, path: &str) -> Pin<Box<dyn Future<Output = String> + 'a + Send>>;
fn post<'a>(&'a self, path: &str) -> Pin<Box<dyn Future<Output = http::Response> + 'a + Send>>;
fn put<'a>(&'a self, path: &str) -> Pin<Box<dyn Future<Output = http::Response> + 'a + Send>>;
}

impl<State> ServerTestingExt for Server<State>
where
State: Send + Sync + 'static,
{
fn request<'a>(
&'a self,
method: Method,
path: &str,
) -> Pin<Box<dyn Future<Output = http::Response> + 'a + Send>> {
let url = if path.starts_with("http:") {
Url::parse(path).unwrap()
} else {
Url::parse("http://example.com/")
.unwrap()
.join(path)
.unwrap()
};

let request = http::Request::new(method, url);
let fut = self.respond(request);
Box::pin(async { fut.await.unwrap() })
}

fn request_body<'a>(
&'a self,
method: Method,
path: &str,
) -> Pin<Box<dyn Future<Output = String> + 'a + Send>> {
let response_fut = self.request(method, path);
Box::pin(async move {
let mut response = response_fut.await;
response.body_string().await.unwrap()
})
}

fn get<'a>(&'a self, path: &str) -> Pin<Box<dyn Future<Output = http::Response> + 'a + Send>> {
self.request(Method::Get, path)
}

fn get_body<'a>(&'a self, path: &str) -> Pin<Box<dyn Future<Output = String> + 'a + Send>> {
self.request_body(Method::Get, path)
}

fn post<'a>(&'a self, path: &str) -> Pin<Box<dyn Future<Output = http::Response> + 'a + Send>> {
self.request(Method::Post, path)
}

fn put<'a>(&'a self, path: &str) -> Pin<Box<dyn Future<Output = http::Response> + 'a + Send>> {
self.request(Method::Put, path)
}
}
Loading

0 comments on commit b8f2444

Please sign in to comment.