Adds tests for JSON example. Emit warning from JSON FromData.

This also includes a tiny change to the `mk-docs` script to build a
blank index at the root of the docs.
This commit is contained in:
Sergio Benitez 2016-12-21 22:56:58 -08:00
parent b9742c1202
commit 2dc1ba29f0
8 changed files with 118 additions and 22 deletions

View File

@ -4,7 +4,7 @@ extern crate serde_json;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::io::Read; use std::io::Read;
use rocket::outcome::{Outcome, IntoOutcome}; use rocket::outcome::Outcome;
use rocket::request::Request; use rocket::request::Request;
use rocket::data::{self, Data, FromData}; use rocket::data::{self, Data, FromData};
use rocket::response::{self, Responder, content}; use rocket::response::{self, Responder, content};
@ -78,7 +78,13 @@ impl<T: Deserialize> FromData for JSON<T> {
} }
let reader = data.open().take(MAX_SIZE); let reader = data.open().take(MAX_SIZE);
serde_json::from_reader(reader).map(|val| JSON(val)).into_outcome() match serde_json::from_reader(reader).map(|val| JSON(val)) {
Ok(value) => Outcome::Success(value),
Err(e) => {
error_!("Couldn't parse JSON body: {:?}", e);
Outcome::Failure((Status::BadRequest, e))
}
}
} }
} }

View File

@ -6,7 +6,7 @@ extern crate rocket;
#[cfg(test)] mod tests; #[cfg(test)] mod tests;
#[get("/hello/<name>/<age>")] #[get("/hello/<name>/<age>")]
fn hello(name: &str, age: i8) -> String { fn hello(name: &str, age: u8) -> String {
format!("Hello, {} year old named {}!", age, name) format!("Hello, {} year old named {}!", age, name)
} }

View File

@ -1,7 +1,7 @@
use super::rocket; use super::rocket;
use rocket::testing::MockRequest; use rocket::testing::MockRequest;
use rocket::http::Method::*; use rocket::http::Method::*;
use rocket::http::Status; use rocket::http::Status;
fn test(uri: &str, expected: String) { fn test(uri: &str, expected: String) {
let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]); let rocket = rocket::ignite().mount("/", routes![super::hello, super::hi]);
@ -29,8 +29,8 @@ fn test_hello() {
#[test] #[test]
fn test_failing_hello() { fn test_failing_hello() {
test_404("/hello/Mike/1000"); test_404("/hello/Mike/1000");
test_404("/hello/Mike/128");
test_404("/hello/Mike/-129"); test_404("/hello/Mike/-129");
test_404("/hello/Mike/-1");
} }
#[test] #[test]

View File

@ -16,3 +16,6 @@ lazy_static = "*"
path = "../../contrib" path = "../../contrib"
default-features = false default-features = false
features = ["json"] features = ["json"]
[dev-dependencies]
rocket = { path = "../../lib", features = ["testing"] }

View File

@ -7,6 +7,8 @@ extern crate serde_json;
#[macro_use] extern crate rocket_contrib; #[macro_use] extern crate rocket_contrib;
#[macro_use] extern crate serde_derive; #[macro_use] extern crate serde_derive;
#[cfg(test)] mod tests;
use rocket_contrib::JSON; use rocket_contrib::JSON;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Mutex; use std::sync::Mutex;
@ -27,22 +29,9 @@ struct Message {
} }
// TODO: This example can be improved by using `route` with muliple HTTP verbs. // TODO: This example can be improved by using `route` with muliple HTTP verbs.
// To be precise, put/post could/should look like:
// #[route(PUT, POST, path = "/<id>", format = "application/json")]
// fn f(method: Method, id: ID, message: JSON<Message>) -> Option<JSON<SimpleMap>> {
// let mut hashmap = MAP.lock().unwrap();
// let exists = hashmap.contains_key(&id);
// if method == Method::Put && exists || method == Method::Post && !exists {
// hashmap.insert(id, message.0.contents);
// return Ok(JSON(map!{ "status" => "ok" }))
// }
//
// None
// }
#[post("/<id>", format = "application/json", data = "<message>")] #[post("/<id>", format = "application/json", data = "<message>")]
fn new(id: ID, message: JSON<Message>) -> JSON<SimpleMap> { fn new(id: ID, message: JSON<Message>) -> JSON<SimpleMap> {
let mut hashmap = MAP.lock().unwrap(); let mut hashmap = MAP.lock().expect("map lock.");
if hashmap.contains_key(&id) { if hashmap.contains_key(&id) {
JSON(map!{ JSON(map!{
"status" => "error", "status" => "error",

View File

@ -0,0 +1,95 @@
use rocket;
use rocket::testing::MockRequest;
use rocket::http::Method::*;
use rocket::http::{Status, ContentType};
use rocket::Response;
macro_rules! run_test {
($req:expr, $test_fn:expr) => ({
let rocket = rocket::ignite()
.mount("/message", routes![super::new, super::update, super::get])
.catch(errors![super::not_found]);
$test_fn($req.dispatch_with(&rocket));
})
}
#[test]
fn bad_get_put() {
// Try to get a message with an ID that doesn't exist.
let mut req = MockRequest::new(Get, "/message/99").header(ContentType::JSON);
run_test!(req, |mut response: Response| {
assert_eq!(response.status(), Status::NotFound);
let body = response.body().unwrap().into_string().unwrap();
assert!(body.contains("error"));
assert!(body.contains("Resource was not found."));
});
// Try to get a message with an invalid ID.
let mut req = MockRequest::new(Get, "/message/hi").header(ContentType::JSON);
run_test!(req, |mut response: Response| {
assert_eq!(response.status(), Status::NotFound);
let body = response.body().unwrap().into_string().unwrap();
assert!(body.contains("error"));
});
// Try to put a message without a proper body.
let mut req = MockRequest::new(Put, "/message/80").header(ContentType::JSON);
run_test!(req, |response: Response| {
assert_eq!(response.status(), Status::BadRequest);
});
// Try to put a message for an ID that doesn't exist.
let mut req = MockRequest::new(Put, "/message/80")
.header(ContentType::JSON)
.body(r#"{ "contents": "Bye bye, world!" }"#);
run_test!(req, |response: Response| {
assert_eq!(response.status(), Status::NotFound);
});
}
#[test]
fn post_get_put_get() {
// Check that a message with ID 1 doesn't exist.
let mut req = MockRequest::new(Get, "/message/1").header(ContentType::JSON);
run_test!(req, |response: Response| {
assert_eq!(response.status(), Status::NotFound);
});
// Add a new message with ID 1.
let mut req = MockRequest::new(Post, "/message/1")
.header(ContentType::JSON)
.body(r#"{ "contents": "Hello, world!" }"#);
run_test!(req, |response: Response| {
assert_eq!(response.status(), Status::Ok);
});
// Check that the message exists with the correct contents.
let mut req = MockRequest::new(Get, "/message/1") .header(ContentType::JSON);
run_test!(req, |mut response: Response| {
assert_eq!(response.status(), Status::Ok);
let body = response.body().unwrap().into_string().unwrap();
assert!(body.contains("Hello, world!"));
});
// Change the message contents.
let mut req = MockRequest::new(Put, "/message/1")
.header(ContentType::JSON)
.body(r#"{ "contents": "Bye bye, world!" }"#);
run_test!(req, |response: Response| {
assert_eq!(response.status(), Status::Ok);
});
// Check that the message exists with the updated contents.
let mut req = MockRequest::new(Get, "/message/1") .header(ContentType::JSON);
run_test!(req, |mut response: Response| {
assert_eq!(response.status(), Status::Ok);
let body = response.body().unwrap().into_string().unwrap();
assert!(!body.contains("Hello, world!"));
assert!(body.contains("Bye bye, world!"));
});
}

View File

@ -224,13 +224,13 @@ impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
} }
} }
/// If `self` is `Ok`, responds with the wrapped `Responder`. Otherwise prints a /// If `self` is `Ok`, responds with the wrapped `Responder`. Otherwise prints
/// warning message with the `Err` value returns an `Err` of /// an error message with the `Err` value returns an `Err` of
/// `Status::InternalServerError`. /// `Status::InternalServerError`.
impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result<R, E> { impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result<R, E> {
default fn respond(self) -> Result<Response<'r>, Status> { default fn respond(self) -> Result<Response<'r>, Status> {
self.map(|r| r.respond()).unwrap_or_else(|e| { self.map(|r| r.respond()).unwrap_or_else(|e| {
warn_!("Response was `Err`: {:?}.", e); error_!("Response was `Err`: {:?}.", e);
Err(Status::InternalServerError) Err(Status::InternalServerError)
}) })
} }

View File

@ -25,3 +25,6 @@ cargo clean
mk_doc $LIB_DIR mk_doc $LIB_DIR
mk_doc $CODEGEN_DIR mk_doc $CODEGEN_DIR
mk_doc $CONTRIB_DIR --all-features mk_doc $CONTRIB_DIR --all-features
# Blank index, for redirection.
touch ${DOC_DIR}/index.html