Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

introduce an extension trait for testing servers #601

Merged
merged 1 commit into from
Jul 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 12 additions & 66 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(|mut res: tide::Response| async move {
res.insert_header("x-tide-test", "1");
Ok(res)
}));
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,6 @@ 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");

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!");
assert_eq!(outer.get_body("/foo").await, "the number is 42");
assert_eq!(outer.get_body("/").await, "Hello, world!");
}
19 changes: 7 additions & 12 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 All @@ -79,7 +74,7 @@ async fn json_content_type() {
fn from_response() {
let msg = "This is an error";

let error = http::Error::from_str(StatusCode::NotFound, msg);
let error = tide::Error::from_str(StatusCode::NotFound, msg);
let mut res: Response = error.into();

assert!(res.error().is_some());
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