diff --git a/backend/Cargo.toml b/backend/Cargo.toml index b44b5ad..cd70bc0 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -20,4 +20,7 @@ base64 = "0.13.0" [dependencies.rocket_sync_db_pools] version = "0.1.0-rc.1" -features = ["diesel_postgres_pool"] \ No newline at end of file +features = ["diesel_postgres_pool"] + +[dev-dependencies] +parking_lot = "0.11" \ No newline at end of file diff --git a/backend/migrations/2021-12-13-145111_users/down.sql b/backend/migrations/2021-12-13-145111_users/down.sql index 441087a..49285a1 100644 --- a/backend/migrations/2021-12-13-145111_users/down.sql +++ b/backend/migrations/2021-12-13-145111_users/down.sql @@ -1 +1,2 @@ +DROP INDEX users_username_index DROP TABLE users; \ No newline at end of file diff --git a/backend/migrations/2021-12-13-145111_users/up.sql b/backend/migrations/2021-12-13-145111_users/up.sql index 6ec7e01..f35e718 100644 --- a/backend/migrations/2021-12-13-145111_users/up.sql +++ b/backend/migrations/2021-12-13-145111_users/up.sql @@ -3,4 +3,6 @@ CREATE TABLE users( username VARCHAR(52) NOT NULL, password_salt BYTEA NOT NULL, password_hash BYTEA NOT NULL -); \ No newline at end of file +); + +CREATE UNIQUE INDEX users_username_index ON users(username); \ No newline at end of file diff --git a/backend/src/lib.rs b/backend/src/lib.rs new file mode 100644 index 0000000..0a21850 --- /dev/null +++ b/backend/src/lib.rs @@ -0,0 +1,35 @@ +#![feature(proc_macro_hygiene, decl_macro)] + +use rocket::{Build, Rocket}; +use rocket_sync_db_pools::database; + +#[macro_use] +extern crate rocket; +#[macro_use] +extern crate diesel; + +mod db; +mod routes; +mod schema; + +#[database("postgresql_database")] +pub struct DbConn(diesel::PgConnection); + +#[get("/")] +fn index() -> &'static str { + "Hello, world!" +} + +pub fn rocket() -> Rocket { + rocket::build() + .mount( + "/", + routes![ + index, + routes::users::register, + routes::users::login, + routes::users::current_user, + ], + ) + .attach(DbConn::fairing()) +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 6ee54ec..65be48d 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,36 +1,8 @@ -#![feature(proc_macro_hygiene, decl_macro)] - -use rocket::{Build, Rocket}; -use rocket_sync_db_pools::database; - #[macro_use] extern crate rocket; -#[macro_use] -extern crate diesel; - -mod db; -mod routes; -mod schema; - -#[database("postgresql_database")] -pub struct DbConn(diesel::PgConnection); - -#[get("/")] -fn index() -> &'static str { - "Hello, world!" -} +extern crate mozaic4_backend; #[launch] -fn rocket() -> Rocket { - rocket::build() - .mount( - "/", - routes![ - index, - routes::users::register, - routes::users::login, - routes::users::current_user, - ], - ) - .attach(DbConn::fairing()) +fn launch() -> _ { + mozaic4_backend::rocket() } diff --git a/backend/tests/common.rs b/backend/tests/common.rs index e69de29..8ab68a1 100644 --- a/backend/tests/common.rs +++ b/backend/tests/common.rs @@ -0,0 +1,75 @@ +extern crate mozaic4_backend; + +use diesel; +use diesel::prelude::*; +use mozaic4_backend::DbConn; +use rocket::http::{ContentType, Header, Status}; +use rocket::local::asynchronous::Client; + +// We use a lock to synchronize between tests so DB operations don't collide. +// For now. In the future, we'll have a nice way to run each test in a DB +// transaction so we can regain concurrency. +static DB_LOCK: parking_lot::Mutex<()> = parking_lot::const_mutex(()); + +async fn reset_db(db: &DbConn) { + db.run(|conn| { + diesel::sql_query("TRUNCATE TABLE users, sessions") + .execute(conn) + .expect("drop all tables"); + }) + .await +} + +macro_rules! run_test { + (|$client:ident, $conn:ident| $block:expr) => {{ + let _lock = DB_LOCK.lock(); + + rocket::async_test(async move { + let $client = Client::tracked(mozaic4_backend::rocket()) + .await + .expect("Rocket client"); + let db = mozaic4_backend::DbConn::get_one($client.rocket()).await; + let $conn = db.expect("failed to get database connection for testing"); + reset_db(&$conn).await; + + $block + }) + }}; +} + +#[test] +fn test_registration() { + run_test!(|client, _conn| { + let response = client + .post("/register") + .header(ContentType::JSON) + .body(r#"{"username": "piepkonijn", "password": "geheim123"}"#) + .dispatch() + .await; + + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.content_type(), Some(ContentType::JSON)); + + let response = client + .post("/login") + .header(ContentType::JSON) + .body(r#"{"username": "piepkonijn", "password": "geheim123"}"#) + .dispatch() + .await; + + assert_eq!(response.status(), Status::Ok); + let token = response.into_string().await.unwrap(); + + let response = client + .get("/users/me") + .header(Header::new("Authorization", token)) + .dispatch() + .await; + + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.content_type(), Some(ContentType::JSON)); + let resp = response.into_string().await.unwrap(); + let json: serde_json::Value = serde_json::from_str(&resp).unwrap(); + assert_eq!(json["username"], "piepkonijn"); + }); +}