use super::task::Task; use rand::{Rng, thread_rng, distributions::Alphanumeric}; use rocket::local::asynchronous::Client; use rocket::http::{Status, ContentType}; // 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(()); macro_rules! run_test { (|$client:ident, $conn:ident| $block:expr) => ({ let _lock = DB_LOCK.lock(); rocket::async_test(async move { let $client = Client::tracked(super::rocket()).await.expect("Rocket client"); let db = super::DbConn::get_one($client.rocket()).await; let $conn = db.expect("failed to get database connection for testing"); Task::delete_all(&$conn).await.expect("failed to delete all tasks for testing"); $block }) }) } #[test] fn test_index() { use rocket::local::blocking::Client; let _lock = DB_LOCK.lock(); let client = Client::tracked(super::rocket()).unwrap(); let response = client.get("/").dispatch(); assert_eq!(response.status(), Status::Ok); } #[test] fn test_insertion_deletion() { run_test!(|client, conn| { // Get the tasks before making changes. let init_tasks = Task::all(&conn).await.unwrap(); // Issue a request to insert a new task. client.post("/todo") .header(ContentType::Form) .body("description=My+first+task") .dispatch() .await; // Ensure we have one more task in the database. let new_tasks = Task::all(&conn).await.unwrap(); assert_eq!(new_tasks.len(), init_tasks.len() + 1); // Ensure the task is what we expect. assert_eq!(new_tasks[0].description, "My first task"); assert!(!new_tasks[0].completed); // Issue a request to delete the task. let id = new_tasks[0].id.unwrap(); client.delete(format!("/todo/{}", id)).dispatch().await; // Ensure it's gone. let final_tasks = Task::all(&conn).await.unwrap(); assert_eq!(final_tasks.len(), init_tasks.len()); if !final_tasks.is_empty() { assert_ne!(final_tasks[0].description, "My first task"); } }) } #[test] fn test_toggle() { run_test!(|client, conn| { // Issue a request to insert a new task; ensure it's not yet completed. client.post("/todo") .header(ContentType::Form) .body("description=test_for_completion") .dispatch() .await; let task = Task::all(&conn).await.unwrap()[0].clone(); assert!(!task.completed); // Issue a request to toggle the task; ensure it is completed. client.put(format!("/todo/{}", task.id.unwrap())).dispatch().await; assert!(Task::all(&conn).await.unwrap()[0].completed); // Issue a request to toggle the task; ensure it's not completed again. client.put(format!("/todo/{}", task.id.unwrap())).dispatch().await; assert!(!Task::all(&conn).await.unwrap()[0].completed); }) } #[test] fn test_many_insertions() { const ITER: usize = 100; run_test!(|client, conn| { // Get the number of tasks initially. let init_num = Task::all(&conn).await.unwrap().len(); let mut descs = Vec::new(); for i in 0..ITER { // Issue a request to insert a new task with a random description. let desc: String = thread_rng() .sample_iter(&Alphanumeric) .take(12) .map(char::from) .collect(); client.post("/todo") .header(ContentType::Form) .body(format!("description={}", desc)) .dispatch() .await; // Record the description we choose for this iteration. descs.insert(0, desc); // Ensure the task was inserted properly and all other tasks remain. let tasks = Task::all(&conn).await.unwrap(); assert_eq!(tasks.len(), init_num + i + 1); for j in 0..i { assert_eq!(descs[j], tasks[j].description); } } }) } #[test] fn test_bad_form_submissions() { run_test!(|client, _conn| { // Submit an empty form. We should get a 422 but no flash error. let res = client.post("/todo") .header(ContentType::Form) .dispatch() .await; assert!(!res.cookies().iter().any(|c| c.value().contains("error"))); assert_eq!(res.status(), Status::UnprocessableEntity); // Submit a form with an empty description. We look for 'error' in the // cookies which corresponds to flash message being set as an error. let res = client.post("/todo") .header(ContentType::Form) .body("description=") .dispatch() .await; // Check that the flash cookie set and that we're redirected to index. assert!(res.cookies().iter().any(|c| c.value().contains("error"))); assert_eq!(res.status(), Status::SeeOther); // The flash cookie should still be present and the error message should // be rendered the index. let body = client.get("/").dispatch().await.into_string().await.unwrap(); assert!(body.contains("Description cannot be empty.")); // Check that the flash is cleared upon another visit to the index. let body = client.get("/").dispatch().await.into_string().await.unwrap(); assert!(!body.contains("Description cannot be empty.")); // Submit a form without a description. Expect a 422 but no flash error. let res = client.post("/todo") .header(ContentType::Form) .body("evil=smile") .dispatch() .await; assert!(!res.cookies().iter().any(|c| c.value().contains("error"))); assert_eq!(res.status(), Status::UnprocessableEntity); }) }