Skip to content

Commit

Permalink
Add simple test request builder
Browse files Browse the repository at this point in the history
  • Loading branch information
raffomania committed Dec 30, 2023
1 parent 412ef3c commit c775aea
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 59 deletions.
22 changes: 22 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ walkdir = "2"

[dev-dependencies]
http-body-util = "0.1.0"
test-log = { version = "0.2.14", features = ["trace"], default-features = false }
22 changes: 9 additions & 13 deletions src/tests/index.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
use axum::{
body::Body,
http::{Request, StatusCode},
};
use axum::http::StatusCode;
use sqlx::{Pool, Postgres};
use tower::ServiceExt; // for `call`, `oneshot`, and `ready`

#[sqlx::test]
async fn index(pool: Pool<Postgres>) -> anyhow::Result<()> {
let app = crate::server::app(pool).await.unwrap();
use super::util::TestApp;

let response = app
.oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
.await
.unwrap();
#[test_log::test(sqlx::test)]
async fn index(pool: Pool<Postgres>) -> anyhow::Result<()> {
let mut app = TestApp::new(pool).await;

assert_eq!(response.status(), StatusCode::SEE_OTHER);
app.req()
.expect_status(StatusCode::SEE_OTHER)
.get("/")
.await;

Ok(())
}
1 change: 1 addition & 0 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
//! for information on why our tests are inside the `src` folder.
mod index;
mod users;
mod util;
64 changes: 18 additions & 46 deletions src/tests/users.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
use axum::{
body::Body,
http::{Request, StatusCode},
response::IntoResponse,
Router,
};
use http_body_util::BodyExt;
use axum::http::StatusCode;
use sqlx::{Pool, Postgres};
use tower::Service;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
use visdom::Vis;

use crate::schemas::users::{CreateUser, Credentials}; // for `call`, `oneshot`, and `ready`
use crate::{
schemas::users::{CreateUser, Credentials},
tests::util::TestApp,
};

#[sqlx::test]
#[test_log::test(sqlx::test)]
async fn can_login(pool: Pool<Postgres>) -> anyhow::Result<()> {
tracing_subscriber::registry()
.with(EnvFilter::from_default_env())
.with(tracing_subscriber::fmt::layer())
.init();

let mut tx = pool.begin().await?;
crate::db::users::create_user_if_not_exists(
&mut tx,
Expand All @@ -30,46 +19,29 @@ async fn can_login(pool: Pool<Postgres>) -> anyhow::Result<()> {
.await?;
tx.commit().await?;

let mut app = crate::server::app(pool).await.unwrap();

let response = <Router as tower::ServiceExt<Request<Body>>>::ready(&mut app)
.await?
.call(Request::builder().uri("/login").body(Body::empty())?)
.await
.unwrap();
let mut app = TestApp::new(pool).await;

assert_eq!(response.status(), StatusCode::OK);
let login_page = app.req().get("/login").await.dom().await;

let body = String::from_utf8(response.into_body().collect().await?.to_bytes().to_vec())?;
let dom = Vis::load(body).expect("Failed to parse HTML");
let form = dom.find("form");
let form = login_page.find("form");
let username = form.find("input[name='username'][type='text'][required]");
assert!(!username.is_empty());
let password = form.find("input[name='password'][type='password'][required]");
assert!(!password.is_empty());

let creds = axum::Form(Credentials {
let creds = Credentials {
username: "test".to_string(),
password: "test".to_string(),
})
.into_response()
.into_body();
};

let response = <Router as tower::ServiceExt<Request<Body>>>::ready(&mut app)
.await?
.call(
Request::builder()
.method("POST")
.uri("/login")
.header("Content-Type", "application/x-www-form-urlencoded")
.body(creds)?,
)
.await
.unwrap();
let login_response = app
.req()
.expect_status(StatusCode::SEE_OTHER)
.post("/login", &creds)
.await;

assert_eq!(response.status(), StatusCode::SEE_OTHER);
let cookie = response.headers().get("Set-Cookie").unwrap();
dbg!(cookie);
let cookie = login_response.headers().get("Set-Cookie").unwrap();
assert!(!cookie.is_empty());

Ok(())
}
23 changes: 23 additions & 0 deletions src/tests/util/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use axum::Router;
use sqlx::{Pool, Postgres};

use crate::server::app;

use self::request_builder::RequestBuilder;

pub mod request_builder;

pub struct TestApp {
router: Router,
}

impl TestApp {
pub async fn new(pool: Pool<Postgres>) -> Self {
TestApp {
router: app(pool).await.unwrap(),
}
}
pub fn req(&mut self) -> RequestBuilder {
RequestBuilder::new(&mut self.router)
}
}
94 changes: 94 additions & 0 deletions src/tests/util/request_builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use askama_axum::IntoResponse;
use axum::{
body::Body,
http::{self, HeaderMap, Request, Response, StatusCode},
Form, Router,
};
use http_body_util::BodyExt;
use mime_guess::mime;
use serde::Serialize;
use tower::{Service, ServiceExt};
use visdom::Vis;

pub struct RequestBuilder<'app> {
router: &'app mut axum::Router,
/// This is the HTTP status that we expect the backend to return.
/// If it returns a different status, we'll panic.
expected_status: StatusCode,
}

impl<'app> RequestBuilder<'app> {
pub fn new(router: &'app mut Router) -> Self {
RequestBuilder {
router: router,
expected_status: StatusCode::OK,
}
}

pub fn expect_status(mut self, expected: StatusCode) -> Self {
self.expected_status = expected;
self
}

pub async fn post<Input>(mut self, url: &str, input: &Input) -> TestResponse
where
Input: Serialize,
{
let request = Request::builder()
.method(http::Method::POST)
.uri(url)
.header(
http::header::CONTENT_TYPE,
mime::APPLICATION_WWW_FORM_URLENCODED.as_ref(),
)
.body(Form(input).into_response().into_body())
.unwrap();

let response = ServiceExt::<Request<Body>>::ready(&mut self.router)
.await
.unwrap()
.call(request)
.await
.unwrap();

assert_eq!(response.status(), self.expected_status);

TestResponse { response }
}

pub async fn get(mut self, url: &str) -> TestResponse {
let request = Request::builder().uri(url).body(Body::empty()).unwrap();

let response = ServiceExt::<Request<Body>>::ready(&mut self.router)
.await
.unwrap()
.call(request)
.await
.unwrap();

assert_eq!(response.status(), self.expected_status);
TestResponse { response: response }
}
}

pub struct TestResponse {
response: Response<Body>,
}

impl TestResponse {
pub async fn dom(self) -> visdom::types::Elements<'static> {
let body = self
.response
.into_body()
.collect()
.await
.unwrap()
.to_bytes()
.to_vec();
Vis::load(String::from_utf8(body).unwrap()).unwrap()
}

pub fn headers(&self) -> &HeaderMap {
self.response.headers()
}
}

0 comments on commit c775aea

Please sign in to comment.