2017-05-26 23:44:53 +00:00
|
|
|
use super::task::Task;
|
2019-06-13 02:41:29 +00:00
|
|
|
|
|
|
|
use rand::{Rng, thread_rng, distributions::Alphanumeric};
|
2017-05-26 23:44:53 +00:00
|
|
|
|
2020-07-11 18:17:43 +00:00
|
|
|
use rocket::local::asynchronous::Client;
|
2017-05-26 23:44:53 +00:00
|
|
|
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.
|
2021-04-08 02:01:48 +00:00
|
|
|
static DB_LOCK: parking_lot::Mutex<()> = parking_lot::const_mutex(());
|
2017-05-26 23:44:53 +00:00
|
|
|
|
|
|
|
macro_rules! run_test {
|
2017-06-06 20:41:04 +00:00
|
|
|
(|$client:ident, $conn:ident| $block:expr) => ({
|
2017-05-26 23:44:53 +00:00
|
|
|
let _lock = DB_LOCK.lock();
|
2018-12-30 21:52:08 +00:00
|
|
|
|
2020-07-11 18:17:43 +00:00
|
|
|
rocket::async_test(async move {
|
Remove 'attach' fairings. Add 'liftoff' fairings.
Launch fairings are now fallible and take the place of attach fairings,
but they are only run, as the name implies, at launch time.
This is is a fundamental shift from eager execution of set-up routines,
including the now defunct attach fairings, to lazy execution,
precipitated by the transition to `async`. The previous functionality,
while simple, caused grave issues:
1. A instance of 'Rocket' with async attach fairings requires an async
runtime to be constructed.
2. The instance is accessible in non-async contexts.
3. The async attach fairings have no runtime in which to be run.
Here's an example:
```rust
let rocket = rocket::ignite()
.attach(AttachFairing::from(|rocket| async {
Ok(rocket.manage(load_from_network::<T>().await))
}));
let state = rocket.state::<T>();
```
This had no real meaning previously yet was accepted by running the
attach fairing future in an isolated runtime. In isolation, this causes
no issue, but when attach fairing futures share reactor state with other
futures in Rocket, panics ensue.
The new Rocket application lifecycle is this:
* Build - A Rocket instance is constructed. No fairings are run.
* Ignition - All launch fairings are run.
* Liftoff - If all launch fairings succeeded, the server is started.
New 'liftoff' fairings are run in this third phase.
2021-04-01 19:32:52 +00:00
|
|
|
let $client = Client::tracked(super::rocket()).await.expect("Rocket client");
|
2020-10-22 10:27:04 +00:00
|
|
|
let db = super::DbConn::get_one($client.rocket()).await;
|
2020-09-03 07:12:00 +00:00
|
|
|
let $conn = db.expect("failed to get database connection for testing");
|
|
|
|
Task::delete_all(&$conn).await.expect("failed to delete all tasks for testing");
|
2019-08-24 17:27:10 +00:00
|
|
|
|
2020-07-11 18:17:43 +00:00
|
|
|
$block
|
|
|
|
})
|
2017-05-26 23:44:53 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
Remove 'attach' fairings. Add 'liftoff' fairings.
Launch fairings are now fallible and take the place of attach fairings,
but they are only run, as the name implies, at launch time.
This is is a fundamental shift from eager execution of set-up routines,
including the now defunct attach fairings, to lazy execution,
precipitated by the transition to `async`. The previous functionality,
while simple, caused grave issues:
1. A instance of 'Rocket' with async attach fairings requires an async
runtime to be constructed.
2. The instance is accessible in non-async contexts.
3. The async attach fairings have no runtime in which to be run.
Here's an example:
```rust
let rocket = rocket::ignite()
.attach(AttachFairing::from(|rocket| async {
Ok(rocket.manage(load_from_network::<T>().await))
}));
let state = rocket.state::<T>();
```
This had no real meaning previously yet was accepted by running the
attach fairing future in an isolated runtime. In isolation, this causes
no issue, but when attach fairing futures share reactor state with other
futures in Rocket, panics ensue.
The new Rocket application lifecycle is this:
* Build - A Rocket instance is constructed. No fairings are run.
* Ignition - All launch fairings are run.
* Liftoff - If all launch fairings succeeded, the server is started.
New 'liftoff' fairings are run in this third phase.
2021-04-01 19:32:52 +00:00
|
|
|
#[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);
|
|
|
|
}
|
|
|
|
|
2017-05-26 23:44:53 +00:00
|
|
|
#[test]
|
|
|
|
fn test_insertion_deletion() {
|
2017-06-06 20:41:04 +00:00
|
|
|
run_test!(|client, conn| {
|
2017-05-26 23:44:53 +00:00
|
|
|
// Get the tasks before making changes.
|
2020-09-03 07:12:00 +00:00
|
|
|
let init_tasks = Task::all(&conn).await.unwrap();
|
2017-05-26 23:44:53 +00:00
|
|
|
|
|
|
|
// Issue a request to insert a new task.
|
2017-06-06 20:41:04 +00:00
|
|
|
client.post("/todo")
|
2017-05-26 23:44:53 +00:00
|
|
|
.header(ContentType::Form)
|
2017-06-06 20:41:04 +00:00
|
|
|
.body("description=My+first+task")
|
2020-07-11 18:17:43 +00:00
|
|
|
.dispatch()
|
|
|
|
.await;
|
2017-05-26 23:44:53 +00:00
|
|
|
|
|
|
|
// Ensure we have one more task in the database.
|
2020-09-03 07:12:00 +00:00
|
|
|
let new_tasks = Task::all(&conn).await.unwrap();
|
2017-05-26 23:44:53 +00:00
|
|
|
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");
|
2024-03-20 07:00:33 +00:00
|
|
|
assert!(!new_tasks[0].completed);
|
2017-05-26 23:44:53 +00:00
|
|
|
|
|
|
|
// Issue a request to delete the task.
|
|
|
|
let id = new_tasks[0].id.unwrap();
|
2020-07-11 18:17:43 +00:00
|
|
|
client.delete(format!("/todo/{}", id)).dispatch().await;
|
2017-05-26 23:44:53 +00:00
|
|
|
|
|
|
|
// Ensure it's gone.
|
2020-09-03 07:12:00 +00:00
|
|
|
let final_tasks = Task::all(&conn).await.unwrap();
|
2017-05-26 23:44:53 +00:00
|
|
|
assert_eq!(final_tasks.len(), init_tasks.len());
|
2024-03-20 07:00:33 +00:00
|
|
|
if !final_tasks.is_empty() {
|
2017-05-27 00:12:57 +00:00
|
|
|
assert_ne!(final_tasks[0].description, "My first task");
|
|
|
|
}
|
2017-05-26 23:44:53 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_toggle() {
|
2017-06-06 20:41:04 +00:00
|
|
|
run_test!(|client, conn| {
|
2017-05-26 23:44:53 +00:00
|
|
|
// Issue a request to insert a new task; ensure it's not yet completed.
|
2017-06-06 20:41:04 +00:00
|
|
|
client.post("/todo")
|
2017-05-26 23:44:53 +00:00
|
|
|
.header(ContentType::Form)
|
2017-06-06 20:41:04 +00:00
|
|
|
.body("description=test_for_completion")
|
2020-07-11 18:17:43 +00:00
|
|
|
.dispatch()
|
|
|
|
.await;
|
2017-05-26 23:44:53 +00:00
|
|
|
|
2020-09-03 07:12:00 +00:00
|
|
|
let task = Task::all(&conn).await.unwrap()[0].clone();
|
2024-03-20 07:00:33 +00:00
|
|
|
assert!(!task.completed);
|
2017-05-26 23:44:53 +00:00
|
|
|
|
|
|
|
// Issue a request to toggle the task; ensure it is completed.
|
2020-07-11 18:17:43 +00:00
|
|
|
client.put(format!("/todo/{}", task.id.unwrap())).dispatch().await;
|
2024-03-20 07:00:33 +00:00
|
|
|
assert!(Task::all(&conn).await.unwrap()[0].completed);
|
2017-05-26 23:44:53 +00:00
|
|
|
|
|
|
|
// Issue a request to toggle the task; ensure it's not completed again.
|
2020-07-11 18:17:43 +00:00
|
|
|
client.put(format!("/todo/{}", task.id.unwrap())).dispatch().await;
|
2024-03-20 07:00:33 +00:00
|
|
|
assert!(!Task::all(&conn).await.unwrap()[0].completed);
|
2017-05-26 23:44:53 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_many_insertions() {
|
|
|
|
const ITER: usize = 100;
|
|
|
|
|
2017-06-06 20:41:04 +00:00
|
|
|
run_test!(|client, conn| {
|
2017-05-26 23:44:53 +00:00
|
|
|
// Get the number of tasks initially.
|
2020-09-03 07:12:00 +00:00
|
|
|
let init_num = Task::all(&conn).await.unwrap().len();
|
2017-05-26 23:44:53 +00:00
|
|
|
let mut descs = Vec::new();
|
|
|
|
|
|
|
|
for i in 0..ITER {
|
|
|
|
// Issue a request to insert a new task with a random description.
|
2021-04-08 02:01:48 +00:00
|
|
|
let desc: String = thread_rng()
|
|
|
|
.sample_iter(&Alphanumeric)
|
|
|
|
.take(12)
|
|
|
|
.map(char::from)
|
|
|
|
.collect();
|
|
|
|
|
2019-11-26 16:25:56 +00:00
|
|
|
client.post("/todo")
|
2017-05-26 23:44:53 +00:00
|
|
|
.header(ContentType::Form)
|
2019-11-26 16:25:56 +00:00
|
|
|
.body(format!("description={}", desc))
|
2020-07-11 18:17:43 +00:00
|
|
|
.dispatch()
|
|
|
|
.await;
|
2017-05-26 23:44:53 +00:00
|
|
|
|
|
|
|
// Record the description we choose for this iteration.
|
|
|
|
descs.insert(0, desc);
|
|
|
|
|
|
|
|
// Ensure the task was inserted properly and all other tasks remain.
|
2020-09-03 07:12:00 +00:00
|
|
|
let tasks = Task::all(&conn).await.unwrap();
|
2017-05-26 23:44:53 +00:00
|
|
|
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() {
|
2017-06-06 20:41:04 +00:00
|
|
|
run_test!(|client, _conn| {
|
2017-05-26 23:44:53 +00:00
|
|
|
// Submit an empty form. We should get a 422 but no flash error.
|
2017-06-06 20:41:04 +00:00
|
|
|
let res = client.post("/todo")
|
|
|
|
.header(ContentType::Form)
|
2020-07-11 18:17:43 +00:00
|
|
|
.dispatch()
|
|
|
|
.await;
|
2017-06-06 20:41:04 +00:00
|
|
|
|
2022-02-23 21:53:31 +00:00
|
|
|
assert!(!res.cookies().iter().any(|c| c.value().contains("error")));
|
2017-06-06 20:41:04 +00:00
|
|
|
assert_eq!(res.status(), Status::UnprocessableEntity);
|
2017-05-26 23:44:53 +00:00
|
|
|
|
2017-06-06 20:41:04 +00:00
|
|
|
// 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")
|
2017-05-26 23:44:53 +00:00
|
|
|
.header(ContentType::Form)
|
2017-06-06 20:41:04 +00:00
|
|
|
.body("description=")
|
2020-07-11 18:17:43 +00:00
|
|
|
.dispatch()
|
|
|
|
.await;
|
2017-05-26 23:44:53 +00:00
|
|
|
|
2022-02-23 21:53:31 +00:00
|
|
|
// 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."));
|
2017-05-26 23:44:53 +00:00
|
|
|
|
|
|
|
// Submit a form without a description. Expect a 422 but no flash error.
|
2017-06-06 20:41:04 +00:00
|
|
|
let res = client.post("/todo")
|
2017-05-26 23:44:53 +00:00
|
|
|
.header(ContentType::Form)
|
2017-06-06 20:41:04 +00:00
|
|
|
.body("evil=smile")
|
2020-07-11 18:17:43 +00:00
|
|
|
.dispatch()
|
|
|
|
.await;
|
2017-06-06 20:41:04 +00:00
|
|
|
|
2022-02-23 21:53:31 +00:00
|
|
|
assert!(!res.cookies().iter().any(|c| c.value().contains("error")));
|
2017-06-06 20:41:04 +00:00
|
|
|
assert_eq!(res.status(), Status::UnprocessableEntity);
|
2017-05-26 23:44:53 +00:00
|
|
|
})
|
|
|
|
}
|