diff options
Diffstat (limited to 'src/routes.rs')
-rw-r--r-- | src/routes.rs | 380 |
1 files changed, 3 insertions, 377 deletions
diff --git a/src/routes.rs b/src/routes.rs index ed2acad..e4bdee4 100644 --- a/src/routes.rs +++ b/src/routes.rs | |||
@@ -1,10 +1,11 @@ | |||
1 | use gradecoin::schema::Db; | 1 | /// Endpoints and their construction |
2 | use warp::{Filter, Rejection, Reply}; | 2 | use warp::{Filter, Rejection, Reply}; |
3 | 3 | ||
4 | use crate::custom_filters; | 4 | use crate::custom_filters; |
5 | use crate::handlers; | 5 | use crate::handlers; |
6 | use crate::schema::Db; | ||
6 | 7 | ||
7 | /// Root, all routes combined | 8 | /// Every route combined |
8 | pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | 9 | pub fn consensus_routes(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { |
9 | transaction_list(db.clone()) | 10 | transaction_list(db.clone()) |
10 | .or(register_user(db.clone())) | 11 | .or(register_user(db.clone())) |
@@ -39,15 +40,6 @@ pub fn block_list(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection | |||
39 | } | 40 | } |
40 | 41 | ||
41 | /// POST /transaction warp route | 42 | /// POST /transaction warp route |
42 | pub fn transaction_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | ||
43 | warp::path!("transaction") | ||
44 | .and(warp::post()) | ||
45 | .and(custom_filters::transaction_json_body()) | ||
46 | .and(custom_filters::with_db(db)) | ||
47 | .and_then(handlers::propose_transaction) | ||
48 | } | ||
49 | |||
50 | /// POST /transaction warp route | ||
51 | pub fn auth_transaction_propose( | 43 | pub fn auth_transaction_propose( |
52 | db: Db, | 44 | db: Db, |
53 | ) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { | 45 | ) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone { |
@@ -68,369 +60,3 @@ pub fn block_propose(db: Db) -> impl Filter<Extract = impl Reply, Error = Reject | |||
68 | .and_then(handlers::propose_block) | 60 | .and_then(handlers::propose_block) |
69 | } | 61 | } |
70 | 62 | ||
71 | #[cfg(test)] | ||
72 | mod tests { | ||
73 | use gradecoin::schema::{create_database, AuthRequest, Block, MetuId, Transaction, User}; | ||
74 | use handlers::Claims; | ||
75 | // use chrono::prelude::*; | ||
76 | // use parking_lot::RwLock; | ||
77 | // use std::sync::Arc; | ||
78 | use warp::http::StatusCode; | ||
79 | |||
80 | use super::*; | ||
81 | |||
82 | use jsonwebtoken::{Header, encode, EncodingKey, Algorithm}; | ||
83 | const private_key_pem: &str = "-----BEGIN RSA PRIVATE KEY----- | ||
84 | MIIEpAIBAAKCAQEA4nU0G4WjkmcQUx0hq6LQuV5Q+ACmUFL/OjoYMDwC/O/6pCd1 | ||
85 | UZgCfgHN2xEffDPznzcTn8OiFRxr4oWyBinyrUpnY4mhy0SQUwoeCw7YkcHAyhCj | ||
86 | NT74aR/ohX0MCj0qRRdbt5ZQXM/GC3HJuXE1ptSuhFgQxziItamn8maoJ6JUSVEX | ||
87 | VO1NOrrjoM3r7Q+BK2B+sX4/bLZ+VG5g1q2nEbFdTHS6pHqtZNHQndTmEKwRfh0R | ||
88 | YtzEzOXuO6e1gQY42Tujkof40dhGCIU7TeIGGHwdFxy1niLkXwtHNjV7lnIOkTbx | ||
89 | 6+sSPamRfQAlZqUWM2Lf5o+7h3qWP3ENB138sQIDAQABAoIBAD23nYTmrganag6M | ||
90 | wPFrBSGP79c3Lhx0EjUHQjJbGKFgsdltG48qM3ut+DF9ACy0Z+/7bbC7+39vaIOq | ||
91 | 1jLR2d6aiYTaLKseO4s2FawD1sgamvU3BZPsXn0gAhnnU5Gyy8Nas1dccvhoc9wI | ||
92 | neaZUPrvucQ90AzLfo6r9yacDbYHB1lOyomApUvpJxOgHISGEtc9qGPDrdH19aF0 | ||
93 | 8fCv2bbQRh+TChgN3IB0o5w0wXaI7YAyAouAv/AzHCoEMpt7OGjFTkjh/ujlPL9O | ||
94 | +FLuJNsQRHDN0gJo2pcvwGwDCsioMixQ9bZ7ZrUu2BNpEQygyeSbj9ZI1iRvhosO | ||
95 | JU3rwEECgYEA9MppTYA6A9WQbCCwPH1QMpUAmPNVSWVhUVag4lGOEhdCDRcz9ook | ||
96 | DohQMKctiEB1luKuvDokxo0uMOfMO9/YwjsRB7qjQip7Th1zMJIjD+A+juLzHK4r | ||
97 | /RiRtWYGAnF8mptDvE+93JsPb3C/lQLvIhio5GQYWBqPJu6SpeosIskCgYEA7NPi | ||
98 | Gbffzr2UQhW8BNKmctEEh8yFRVojFo3wwwWxSNUVXGSmSm31CL+Q8h817R+2OkPV | ||
99 | 1ZMUOBU4UJiqFt28kIvTDFqbAJlJQGCpY2mY7OLQiD2A+TVLcFrHmoCaPfCAK1Qd | ||
100 | hQ0PmFK7Mf8qClpA3E5chop/WfKQfiu46sZv1qkCgYAhGdXPcw1lQ1W6KVlrdI6J | ||
101 | qHhiNlVMDXdxZkNvFxQdAiQeXQrbxaZGiMw/J/wSNpUwCAsUzM/4QVMDrfSCDCzl | ||
102 | ZtNQtj4pTlFKKNVQthIjrXEIJUw2jp7IJLBfVSJu5iWxSlmId0f3MsiNizN81N69 | ||
103 | P5Rm/doE3+KHoy8VXGsHcQKBgQCkNh62enqjHWypjex6450qS6f6iWN3PRLLVsw0 | ||
104 | TcQpniZblCaBwVCAKmRUnjOEIdL2/4ZLutnwMTaFG/YEOOfAylMiY8jKV38lNmD9 | ||
105 | X4D78CFr9klxgvS2CRwSE03f2NzmLkLxuKaxldvaxPTfjMkgeO1LFMlNExYBhkuH | ||
106 | 7uQpUQKBgQCKX6qMNh2gSdgG7qyxfTFZ4y5EGOBoKe/dE+IcVF3Vnh6DZVbCAbBL | ||
107 | 5EdFWZSrCnDjA4xiKW55mwp95Ud9EZsZAb13L8V9t82eK+UDBoWlb7VRNYpda/x1 | ||
108 | 5/i4qQJ28x2UNJDStpYFpnp4Ba1lvXjKngIbDPkjU+hbBJ+BNGAIeg== | ||
109 | -----END RSA PRIVATE KEY-----"; | ||
110 | /// Create a mock database to be used in tests | ||
111 | fn mocked_db() -> Db { | ||
112 | let db = create_database(); | ||
113 | |||
114 | db.users.write().insert( | ||
115 | "mock_transaction_source".to_owned(), | ||
116 | User { | ||
117 | user_id: MetuId::new("e254275".to_owned()).unwrap(), | ||
118 | public_key: | ||
119 | "-----BEGIN PUBLIC KEY----- | ||
120 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4nU0G4WjkmcQUx0hq6LQ | ||
121 | uV5Q+ACmUFL/OjoYMDwC/O/6pCd1UZgCfgHN2xEffDPznzcTn8OiFRxr4oWyBiny | ||
122 | rUpnY4mhy0SQUwoeCw7YkcHAyhCjNT74aR/ohX0MCj0qRRdbt5ZQXM/GC3HJuXE1 | ||
123 | ptSuhFgQxziItamn8maoJ6JUSVEXVO1NOrrjoM3r7Q+BK2B+sX4/bLZ+VG5g1q2n | ||
124 | EbFdTHS6pHqtZNHQndTmEKwRfh0RYtzEzOXuO6e1gQY42Tujkof40dhGCIU7TeIG | ||
125 | GHwdFxy1niLkXwtHNjV7lnIOkTbx6+sSPamRfQAlZqUWM2Lf5o+7h3qWP3ENB138 | ||
126 | sQIDAQAB | ||
127 | -----END PUBLIC KEY-----" | ||
128 | .to_owned(), | ||
129 | balance: 0, | ||
130 | }, | ||
131 | ); | ||
132 | db.pending_transactions.write().insert( | ||
133 | "hash_value".to_owned(), | ||
134 | Transaction { | ||
135 | by: "source_account".to_owned(), | ||
136 | source: "source_account".to_owned(), | ||
137 | target: "target_account".to_owned(), | ||
138 | amount: 20, | ||
139 | timestamp: chrono::NaiveDate::from_ymd(2021, 04, 09).and_hms(1, 30, 30), | ||
140 | }, | ||
141 | ); | ||
142 | |||
143 | *db.blockchain.write() = Block { | ||
144 | transaction_list: vec![ | ||
145 | "old_transaction_hash_1".to_owned(), | ||
146 | "old_transaction_hash_2".to_owned(), | ||
147 | "old_transaction_hash_3".to_owned(), | ||
148 | ], | ||
149 | nonce: 0, | ||
150 | timestamp: chrono::NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30), | ||
151 | hash: "not_a_thing_yet".to_owned(), | ||
152 | }; | ||
153 | |||
154 | db | ||
155 | } | ||
156 | |||
157 | fn mocked_jwt() -> String { | ||
158 | |||
159 | let claims = Claims { | ||
160 | tha: "6692e774eba7fb92dc0fe6cf7347591e".to_owned(), | ||
161 | iat: 1516239022, | ||
162 | }; | ||
163 | let header = Header::new(Algorithm::RS256); | ||
164 | encode(&header, &claims, &EncodingKey::from_rsa_pem(private_key_pem.as_bytes()).unwrap()).unwrap() | ||
165 | } | ||
166 | /// Create a mock user that is allowed to be in gradecoin to be used in tests | ||
167 | fn priviliged_mocked_user() -> AuthRequest { | ||
168 | AuthRequest { | ||
169 | student_id: String::from("e254275"), | ||
170 | public_key: "NOT IMPLEMENTED".to_owned(), | ||
171 | } | ||
172 | } | ||
173 | |||
174 | /// Create a mock user that is NOT allowed to be in gradecoin to be used in tests | ||
175 | fn unpriviliged_mocked_user() -> AuthRequest { | ||
176 | AuthRequest { | ||
177 | student_id: String::from("foobarbaz"), | ||
178 | public_key: "NOT IMPLEMENTED".to_owned(), | ||
179 | } | ||
180 | } | ||
181 | |||
182 | /// Create a mock transaction to be used in tests | ||
183 | fn mocked_transaction() -> Transaction { | ||
184 | Transaction { | ||
185 | by: "mock_transaction_source".to_owned(), | ||
186 | source: "mock_transaction_source".to_owned(), | ||
187 | target: "mock_transaction_target".to_owned(), | ||
188 | amount: 25, | ||
189 | timestamp: chrono::NaiveDate::from_ymd(2021, 04, 09).and_hms(14, 30, 00), | ||
190 | } | ||
191 | } | ||
192 | |||
193 | /// Create a mock block with a correct mined hash to be used in tests | ||
194 | fn mocked_block() -> Block { | ||
195 | Block { | ||
196 | transaction_list: vec!["hash_value".to_owned()], | ||
197 | nonce: 3831993, | ||
198 | timestamp: chrono::NaiveDate::from_ymd(2021, 04, 08).and_hms(12, 30, 30), | ||
199 | hash: "2b648ffab5d9af1d5d5fc052fc9e51b882fc4fb0c998608c99232f9282000000".to_owned(), | ||
200 | } | ||
201 | } | ||
202 | |||
203 | /// Create a mock block with a wrong hash and nonce | ||
204 | fn mocked_wrong_block() -> Block { | ||
205 | Block { | ||
206 | transaction_list: vec!["foobarbaz".to_owned(), "dazsaz".to_owned()], | ||
207 | nonce: 1000, // can you imagine | ||
208 | timestamp: chrono::NaiveDate::from_ymd(2021, 04, 12).and_hms(05, 29, 30), | ||
209 | hash: "tnarstnarsuthnarsthlarjstk".to_owned(), | ||
210 | } | ||
211 | } | ||
212 | |||
213 | /// Test simple GET request to /transaction, resource that exists | ||
214 | /// https://tools.ietf.org/html/rfc7231#section-6.3.1 | ||
215 | /// We should get the only pending transaction available in the database as json | ||
216 | #[tokio::test] | ||
217 | async fn get_pending_transactions() { | ||
218 | let db = mocked_db(); | ||
219 | |||
220 | let reply = consensus_routes(db); | ||
221 | |||
222 | let res = warp::test::request() | ||
223 | .method("GET") | ||
224 | .path("/transaction") | ||
225 | .reply(&reply) | ||
226 | .await; | ||
227 | |||
228 | assert_eq!(res.status(), StatusCode::OK); | ||
229 | |||
230 | let expected_json_body = r#"[{"by":"source_account","source":"source_account","target":"target_account","amount":20,"timestamp":"2021-04-09T01:30:30"}]"#; | ||
231 | |||
232 | assert_eq!(res.body(), expected_json_body); | ||
233 | } | ||
234 | |||
235 | /// Test simple GET request to /block, resource that exists | ||
236 | /// https://tools.ietf.org/html/rfc7231#section-6.3.1 | ||
237 | /// Should return the single block available in the database as json | ||
238 | #[tokio::test] | ||
239 | async fn get_blockchain() { | ||
240 | let db = mocked_db(); | ||
241 | let filter = consensus_routes(db); | ||
242 | |||
243 | let res = warp::test::request() | ||
244 | .method("GET") | ||
245 | .path("/block") | ||
246 | .reply(&filter) | ||
247 | .await; | ||
248 | |||
249 | assert_eq!(res.status(), StatusCode::OK); | ||
250 | |||
251 | let expected_json_body = r#"{"transaction_list":["old_transaction_hash_1","old_transaction_hash_2","old_transaction_hash_3"],"nonce":0,"timestamp":"2021-04-08T12:30:30","hash":"not_a_thing_yet"}"#; | ||
252 | assert_eq!(res.body(), expected_json_body); | ||
253 | } | ||
254 | |||
255 | /// Test a simple GET request to a nonexisting path | ||
256 | /// https://tools.ietf.org/html/rfc7231#section-6.5.4 | ||
257 | /// Should respond with 404 and stop | ||
258 | #[tokio::test] | ||
259 | async fn get_nonexisting_path_404() { | ||
260 | let db = mocked_db(); | ||
261 | let filter = consensus_routes(db); | ||
262 | |||
263 | let res = warp::test::request() | ||
264 | .method("GET") | ||
265 | .path("/this_path_does_not_exist") | ||
266 | .reply(&filter) | ||
267 | .await; | ||
268 | |||
269 | assert_eq!(res.status(), StatusCode::NOT_FOUND); | ||
270 | } | ||
271 | |||
272 | /// Test a POST request to /transaction, a resource that exists | ||
273 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
274 | /// Should accept the json request, create | ||
275 | /// the transaction and add it to pending transactions in the db | ||
276 | #[tokio::test] | ||
277 | async fn post_json_201() { | ||
278 | let db = mocked_db(); | ||
279 | let filter = consensus_routes(db.clone()); | ||
280 | let res = warp::test::request() | ||
281 | .method("POST") | ||
282 | .json(&mocked_transaction()) | ||
283 | .path("/transaction") | ||
284 | .reply(&filter) | ||
285 | .await; | ||
286 | |||
287 | assert_eq!(res.status(), StatusCode::CREATED); | ||
288 | assert_eq!(db.pending_transactions.read().len(), 2); | ||
289 | } | ||
290 | |||
291 | /// Test a POST request to /transaction, a resource that exists | ||
292 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
293 | /// Should accept the json request, create | ||
294 | /// the transaction and add it to pending transactions in the db | ||
295 | #[tokio::test] | ||
296 | async fn post_auth_json_201() { | ||
297 | let db = mocked_db(); | ||
298 | let filter = consensus_routes(db.clone()); | ||
299 | |||
300 | let res = warp::test::request() | ||
301 | .method("POST") | ||
302 | .json(&mocked_transaction()) | ||
303 | .header("Authorization", format!("Bearer {}", &mocked_jwt())) | ||
304 | .path("/transaction") | ||
305 | .reply(&filter) | ||
306 | .await; | ||
307 | |||
308 | assert_eq!(res.status(), StatusCode::CREATED); | ||
309 | assert_eq!(db.pending_transactions.read().len(), 2); | ||
310 | } | ||
311 | |||
312 | /// Test a POST request to /transaction, a resource that exists | ||
313 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
314 | /// Should accept the json request, create | ||
315 | /// the transaction and add it to pending transactions in the db | ||
316 | #[tokio::test] | ||
317 | async fn post_auth_json_400() { | ||
318 | let db = mocked_db(); | ||
319 | let filter = consensus_routes(db.clone()); | ||
320 | |||
321 | let res = warp::test::request() | ||
322 | .method("POST") | ||
323 | .json(&mocked_transaction()) | ||
324 | .header("Authorization", "Bearer aaaaaaaasdlkjaldkasljdaskjlaaaaaaaaaaaaaa") | ||
325 | .path("/transaction") | ||
326 | .reply(&filter) | ||
327 | .await; | ||
328 | |||
329 | assert_eq!(res.status(), StatusCode::BAD_REQUEST); | ||
330 | assert_eq!(db.pending_transactions.read().len(), 1); | ||
331 | } | ||
332 | |||
333 | /// Test a POST request to /block, a resource that exists | ||
334 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
335 | /// Should accept the json request, create | ||
336 | /// the block | ||
337 | #[tokio::test] | ||
338 | async fn post_block_201() { | ||
339 | let db = mocked_db(); | ||
340 | let filter = consensus_routes(db.clone()); | ||
341 | |||
342 | let res = warp::test::request() | ||
343 | .method("POST") | ||
344 | .json(&mocked_block()) | ||
345 | .path("/block") | ||
346 | .reply(&filter) | ||
347 | .await; | ||
348 | |||
349 | assert_eq!(res.status(), StatusCode::CREATED); | ||
350 | assert_eq!( | ||
351 | *db.blockchain.read().hash, | ||
352 | "2b648ffab5d9af1d5d5fc052fc9e51b882fc4fb0c998608c99232f9282000000".to_owned() | ||
353 | ); | ||
354 | } | ||
355 | |||
356 | /// Test a POST request to /block, a resource that exists | ||
357 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
358 | /// Should reject the block because of the wrong hash | ||
359 | #[tokio::test] | ||
360 | async fn post_block_wrong_hash() { | ||
361 | let db = mocked_db(); | ||
362 | let filter = consensus_routes(db.clone()); | ||
363 | |||
364 | let res = warp::test::request() | ||
365 | .method("POST") | ||
366 | .json(&mocked_wrong_block()) | ||
367 | .path("/block") | ||
368 | .reply(&filter) | ||
369 | .await; | ||
370 | |||
371 | assert_eq!(res.status(), StatusCode::BAD_REQUEST); | ||
372 | } | ||
373 | |||
374 | /// Test a POST request to /register, an endpoint that exists | ||
375 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
376 | /// Should accept the json request, create a new user and | ||
377 | /// add it to the user hashmap in the db | ||
378 | #[tokio::test] | ||
379 | async fn post_register_priviliged_user() { | ||
380 | let db = mocked_db(); | ||
381 | let filter = consensus_routes(db.clone()); | ||
382 | |||
383 | let res = warp::test::request() | ||
384 | .method("POST") | ||
385 | .json(&priviliged_mocked_user()) | ||
386 | .path("/register") | ||
387 | .reply(&filter) | ||
388 | .await; | ||
389 | |||
390 | println!("{:?}", res.body()); | ||
391 | assert_eq!(res.status(), StatusCode::CREATED); | ||
392 | assert_eq!(db.users.read().len(), 2); | ||
393 | } | ||
394 | |||
395 | /// Test a POST request to /transaction, an endpoint that exists | ||
396 | /// https://tools.ietf.org/html/rfc7231#section-6.3.2 | ||
397 | /// Should NOT accept the json request as the user is unpriviliged | ||
398 | #[tokio::test] | ||
399 | async fn post_register_unpriviliged_user() { | ||
400 | let db = mocked_db(); | ||
401 | let filter = consensus_routes(db.clone()); | ||
402 | |||
403 | let res = warp::test::request() | ||
404 | .method("POST") | ||
405 | .json(&unpriviliged_mocked_user()) | ||
406 | .path("/register") | ||
407 | .reply(&filter) | ||
408 | .await; | ||
409 | |||
410 | println!("{:?}", res.body()); | ||
411 | assert_eq!(res.status(), StatusCode::BAD_REQUEST); | ||
412 | assert_eq!(db.users.read().len(), 1); | ||
413 | } | ||
414 | |||
415 | /// Test a POST request to /transaction, a resource that exists with a longer than expected | ||
416 | /// payload | ||
417 | /// https://tools.ietf.org/html/rfc7231#section-6.5.11 | ||
418 | /// Should return 413 to user | ||
419 | #[tokio::test] | ||
420 | async fn post_too_long_content_413() { | ||
421 | let db = mocked_db(); | ||
422 | let filter = consensus_routes(db); | ||
423 | |||
424 | let res = warp::test::request() | ||
425 | .method("POST") | ||
426 | .header("content-length", 1024 * 36) | ||
427 | .path("/transaction") | ||
428 | .reply(&filter) | ||
429 | .await; | ||
430 | |||
431 | assert_eq!(res.status(), StatusCode::PAYLOAD_TOO_LARGE); | ||
432 | } | ||
433 | } | ||
434 | |||
435 | // TODO: POST block without correct transactions test <09-04-21, yigit> // | ||
436 | // TODO: POST transaction while that source has pending transaction test <09-04-21, yigit> // | ||