1 use super::task::Task;
2 
3 use parking_lot::Mutex;
4 use rand::{Rng, thread_rng, distributions::Alphanumeric};
5 
6 use rocket::local::Client;
7 use rocket::http::{Status, ContentType};
8 
9 // We use a lock to synchronize between tests so DB operations don't collide.
10 // For now. In the future, we'll have a nice way to run each test in a DB
11 // transaction so we can regain concurrency.
12 static DB_LOCK: Mutex<()> = Mutex::new(());
13 
14 macro_rules! run_test {
15     (|$client:ident, $conn:ident| $block:expr) => ({
16         let _lock = DB_LOCK.lock();
17         let rocket = super::rocket();
18         let db = super::DbConn::get_one(&rocket);
19         let $client = Client::new(rocket).expect("Rocket client");
20         let $conn = db.expect("failed to get database connection for testing");
21         Task::delete_all(&$conn).expect("failed to delete all tasks for testing");
22 
23         $block
24     })
25 }
26 
27 #[test]
test_insertion_deletion()28 fn test_insertion_deletion() {
29     run_test!(|client, conn| {
30         // Get the tasks before making changes.
31         let init_tasks = Task::all(&conn).unwrap();
32 
33         // Issue a request to insert a new task.
34         client.post("/todo")
35             .header(ContentType::Form)
36             .body("description=My+first+task")
37             .dispatch();
38 
39         // Ensure we have one more task in the database.
40         let new_tasks = Task::all(&conn).unwrap();
41         assert_eq!(new_tasks.len(), init_tasks.len() + 1);
42 
43         // Ensure the task is what we expect.
44         assert_eq!(new_tasks[0].description, "My first task");
45         assert_eq!(new_tasks[0].completed, false);
46 
47         // Issue a request to delete the task.
48         let id = new_tasks[0].id.unwrap();
49         client.delete(format!("/todo/{}", id)).dispatch();
50 
51         // Ensure it's gone.
52         let final_tasks = Task::all(&conn).unwrap();
53         assert_eq!(final_tasks.len(), init_tasks.len());
54         if final_tasks.len() > 0 {
55             assert_ne!(final_tasks[0].description, "My first task");
56         }
57     })
58 }
59 
60 #[test]
test_toggle()61 fn test_toggle() {
62     run_test!(|client, conn| {
63         // Issue a request to insert a new task; ensure it's not yet completed.
64         client.post("/todo")
65             .header(ContentType::Form)
66             .body("description=test_for_completion")
67             .dispatch();
68 
69         let task = Task::all(&conn).unwrap()[0].clone();
70         assert_eq!(task.completed, false);
71 
72         // Issue a request to toggle the task; ensure it is completed.
73         client.put(format!("/todo/{}", task.id.unwrap())).dispatch();
74         assert_eq!(Task::all(&conn).unwrap()[0].completed, true);
75 
76         // Issue a request to toggle the task; ensure it's not completed again.
77         client.put(format!("/todo/{}", task.id.unwrap())).dispatch();
78         assert_eq!(Task::all(&conn).unwrap()[0].completed, false);
79     })
80 }
81 
82 #[test]
test_many_insertions()83 fn test_many_insertions() {
84     const ITER: usize = 100;
85 
86     let rng = thread_rng();
87     run_test!(|client, conn| {
88         // Get the number of tasks initially.
89         let init_num = Task::all(&conn).unwrap().len();
90         let mut descs = Vec::new();
91 
92         for i in 0..ITER {
93             // Issue a request to insert a new task with a random description.
94             let desc: String = rng.sample_iter(&Alphanumeric).take(12).collect();
95             client.post("/todo")
96                 .header(ContentType::Form)
97                 .body(format!("description={}", desc))
98                 .dispatch();
99 
100             // Record the description we choose for this iteration.
101             descs.insert(0, desc);
102 
103             // Ensure the task was inserted properly and all other tasks remain.
104             let tasks = Task::all(&conn).unwrap();
105             assert_eq!(tasks.len(), init_num + i + 1);
106 
107             for j in 0..i {
108                 assert_eq!(descs[j], tasks[j].description);
109             }
110         }
111     })
112 }
113 
114 #[test]
test_bad_form_submissions()115 fn test_bad_form_submissions() {
116     run_test!(|client, _conn| {
117         // Submit an empty form. We should get a 422 but no flash error.
118         let res = client.post("/todo")
119             .header(ContentType::Form)
120             .dispatch();
121 
122         let mut cookies = res.headers().get("Set-Cookie");
123         assert_eq!(res.status(), Status::UnprocessableEntity);
124         assert!(!cookies.any(|value| value.contains("error")));
125 
126         // Submit a form with an empty description. We look for 'error' in the
127         // cookies which corresponds to flash message being set as an error.
128         let res = client.post("/todo")
129             .header(ContentType::Form)
130             .body("description=")
131             .dispatch();
132 
133         let mut cookies = res.headers().get("Set-Cookie");
134         assert!(cookies.any(|value| value.contains("error")));
135 
136         // Submit a form without a description. Expect a 422 but no flash error.
137         let res = client.post("/todo")
138             .header(ContentType::Form)
139             .body("evil=smile")
140             .dispatch();
141 
142         let mut cookies = res.headers().get("Set-Cookie");
143         assert_eq!(res.status(), Status::UnprocessableEntity);
144         assert!(!cookies.any(|value| value.contains("error")));
145     })
146 }
147