mirror of
https://github.com/rwf2/Rocket.git
synced 2024-12-27 04:42:40 +00:00
4e06ee64aa
Prior to this commit, it was not possible to test Rocket crates in production mode without setting a global secret key or bypassing secret key checking - the testing script did the latter. The consequence is that it became impossible to test secret key related failures because the tests passed regardless. This commit undoes this. As a consequence, all tests are now aware of the difference between debug and release configurations, the latter of which validates 'secret_key' by default. New 'Client::debug()' and 'Client::debug_with()' simplify creating an instance of 'Client' with configuration in debug mode to avoid undesired test failures. The summary of changes in this commit are: * Config 'secret_key' success and failure are now tested. * 'secret_key' validation was moved to pre-launch from 'Config:from()'. * 'Config::from()' only extracts the config. * Added 'Config::try_from()' for non-panicking extraction. * 'Config' now knows the profile it was extracted from. * The 'Config' provider sets a profile of 'Config.profile'. * 'Rocket', 'Client', 'Fairings', implement 'Debug'. * 'fairing::Info' implements 'Copy', 'Clone'. * 'Fairings' keeps track of, logs attach fairings. * 'Rocket::reconfigure()' was added to allow modifying a config. Internally, the testing script was refactored to properly test the codebase with the new changes. In particular, it no longer sets a rustc 'cfg' to avoid secret-key checking. Resolves #1543. Fixes #1564.
345 lines
10 KiB
Rust
345 lines
10 KiB
Rust
// Rocket sometimes generates mangled identifiers that activate the
|
|
// non_snake_case lint. We deny the lint in this test to ensure that
|
|
// code generation uses #[allow(non_snake_case)] in the appropriate places.
|
|
#![deny(non_snake_case)]
|
|
|
|
#[macro_use] extern crate rocket;
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use rocket::request::Request;
|
|
use rocket::http::ext::Normalize;
|
|
use rocket::local::blocking::Client;
|
|
use rocket::data::{self, Data, FromData};
|
|
use rocket::http::{Status, RawStr, ContentType};
|
|
|
|
// Use all of the code generation available at once.
|
|
|
|
#[derive(FromForm, UriDisplayQuery)]
|
|
struct Inner<'r> {
|
|
field: &'r str
|
|
}
|
|
|
|
struct Simple(String);
|
|
|
|
#[async_trait]
|
|
impl<'r> FromData<'r> for Simple {
|
|
type Error = std::io::Error;
|
|
|
|
async fn from_data(req: &'r Request<'_>, data: Data) -> data::Outcome<Self, Self::Error> {
|
|
String::from_data(req, data).await.map(Simple)
|
|
}
|
|
}
|
|
|
|
#[post(
|
|
"/<a>/<name>/name/<path..>?sky=blue&<sky>&<query..>",
|
|
format = "json",
|
|
data = "<simple>",
|
|
rank = 138
|
|
)]
|
|
fn post1(
|
|
sky: usize,
|
|
name: &str,
|
|
a: String,
|
|
query: Inner<'_>,
|
|
path: PathBuf,
|
|
simple: Simple,
|
|
) -> String {
|
|
let string = format!("{}, {}, {}, {}, {}, {}",
|
|
sky, name, a, query.field, path.normalized_str(), simple.0);
|
|
|
|
let uri = uri!(post1: a, name, path, sky, query);
|
|
|
|
format!("({}) ({})", string, uri.to_string())
|
|
}
|
|
|
|
#[route(
|
|
POST,
|
|
uri = "/<a>/<name>/name/<path..>?sky=blue&<sky>&<query..>",
|
|
format = "json",
|
|
data = "<simple>",
|
|
rank = 138
|
|
)]
|
|
fn post2(
|
|
sky: usize,
|
|
name: &str,
|
|
a: String,
|
|
query: Inner<'_>,
|
|
path: PathBuf,
|
|
simple: Simple,
|
|
) -> String {
|
|
let string = format!("{}, {}, {}, {}, {}, {}",
|
|
sky, name, a, query.field, path.normalized_str(), simple.0);
|
|
|
|
let uri = uri!(post2: a, name, path, sky, query);
|
|
|
|
format!("({}) ({})", string, uri.to_string())
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[post("/<_unused_param>?<_unused_query>", data="<_unused_data>")]
|
|
fn test_unused_params(_unused_param: String, _unused_query: String, _unused_data: Data) {
|
|
}
|
|
|
|
#[test]
|
|
fn test_full_route() {
|
|
let rocket = rocket::ignite()
|
|
.mount("/1", routes![post1])
|
|
.mount("/2", routes![post2]);
|
|
|
|
let client = Client::debug(rocket).unwrap();
|
|
|
|
let a = RawStr::new("A%20A");
|
|
let name = RawStr::new("Bob%20McDonald");
|
|
let path = "this/path/here";
|
|
let sky = 777;
|
|
let query = "field=inside";
|
|
let simple = "data internals";
|
|
|
|
let path_part = format!("/{}/{}/name/{}", a, name, path);
|
|
let query_part = format!("?sky={}&sky=blue&{}", sky, query);
|
|
let uri = format!("{}{}", path_part, query_part);
|
|
let expected_uri = format!("{}?sky=blue&sky={}&{}", path_part, sky, query);
|
|
|
|
let response = client.post(&uri).body(simple).dispatch();
|
|
assert_eq!(response.status(), Status::NotFound);
|
|
|
|
let response = client.post(format!("/1{}", uri)).body(simple).dispatch();
|
|
assert_eq!(response.status(), Status::NotFound);
|
|
|
|
let response = client
|
|
.post(format!("/1{}", uri))
|
|
.header(ContentType::JSON)
|
|
.body(simple)
|
|
.dispatch();
|
|
|
|
assert_eq!(response.into_string().unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})",
|
|
sky, name.percent_decode().unwrap(), "A A", "inside", path, simple, expected_uri));
|
|
|
|
let response = client.post(format!("/2{}", uri)).body(simple).dispatch();
|
|
assert_eq!(response.status(), Status::NotFound);
|
|
|
|
let response = client
|
|
.post(format!("/2{}", uri))
|
|
.header(ContentType::JSON)
|
|
.body(simple)
|
|
.dispatch();
|
|
|
|
assert_eq!(response.into_string().unwrap(), format!("({}, {}, {}, {}, {}, {}) ({})",
|
|
sky, name.percent_decode().unwrap(), "A A", "inside", path, simple, expected_uri));
|
|
}
|
|
|
|
mod scopes {
|
|
mod other {
|
|
#[get("/world")]
|
|
pub fn world() -> &'static str {
|
|
"Hello, world!"
|
|
}
|
|
}
|
|
|
|
#[get("/hello")]
|
|
pub fn hello() -> &'static str {
|
|
"Hello, outside world!"
|
|
}
|
|
|
|
use other::world;
|
|
|
|
fn _rocket() -> rocket::Rocket {
|
|
rocket::ignite().mount("/", rocket::routes![hello, world])
|
|
}
|
|
}
|
|
|
|
use rocket::form::Contextual;
|
|
|
|
#[derive(Default, Debug, PartialEq, FromForm)]
|
|
struct Filtered<'r> {
|
|
bird: Option<&'r str>,
|
|
color: Option<&'r str>,
|
|
cat: Option<&'r str>,
|
|
rest: Option<&'r str>,
|
|
}
|
|
|
|
#[get("/?bird=1&color=blue&<bird>&<color>&cat=bob&<rest..>")]
|
|
fn filtered_raw_query(bird: usize, color: &str, rest: Contextual<'_, Filtered<'_>>) -> String {
|
|
assert_ne!(bird, 1);
|
|
assert_ne!(color, "blue");
|
|
assert_eq!(rest.value.unwrap(), Filtered::default());
|
|
|
|
format!("{} - {}", bird, color)
|
|
}
|
|
|
|
#[test]
|
|
fn test_filtered_raw_query() {
|
|
let rocket = rocket::ignite().mount("/", routes![filtered_raw_query]);
|
|
let client = Client::debug(rocket).unwrap();
|
|
|
|
#[track_caller]
|
|
fn run(client: &Client, birds: &[&str], colors: &[&str], cats: &[&str]) -> (Status, String) {
|
|
let join = |slice: &[&str], name: &str| slice.iter()
|
|
.map(|v| format!("{}={}", name, v))
|
|
.collect::<Vec<_>>()
|
|
.join("&");
|
|
|
|
let q = format!("{}&{}&{}",
|
|
join(birds, "bird"),
|
|
join(colors, "color"),
|
|
join(cats, "cat"));
|
|
|
|
let response = client.get(format!("/?{}", q)).dispatch();
|
|
let status = response.status();
|
|
let body = response.into_string().unwrap();
|
|
|
|
(status, body)
|
|
}
|
|
|
|
let birds = &["2", "3"];
|
|
let colors = &["red", "blue", "green"];
|
|
let cats = &["bob", "bob"];
|
|
assert_eq!(run(&client, birds, colors, cats).0, Status::NotFound);
|
|
|
|
let birds = &["2", "1", "3"];
|
|
let colors = &["red", "green"];
|
|
let cats = &["bob", "bob"];
|
|
assert_eq!(run(&client, birds, colors, cats).0, Status::NotFound);
|
|
|
|
let birds = &["2", "1", "3"];
|
|
let colors = &["red", "blue", "green"];
|
|
let cats = &[];
|
|
assert_eq!(run(&client, birds, colors, cats).0, Status::NotFound);
|
|
|
|
let birds = &["2", "1", "3"];
|
|
let colors = &["red", "blue", "green"];
|
|
let cats = &["bob", "bob"];
|
|
assert_eq!(run(&client, birds, colors, cats).1, "2 - red");
|
|
|
|
let birds = &["1", "2", "1", "3"];
|
|
let colors = &["blue", "red", "blue", "green"];
|
|
let cats = &["bob"];
|
|
assert_eq!(run(&client, birds, colors, cats).1, "2 - red");
|
|
|
|
let birds = &["5", "1"];
|
|
let colors = &["blue", "orange", "red", "blue", "green"];
|
|
let cats = &["bob"];
|
|
assert_eq!(run(&client, birds, colors, cats).1, "5 - orange");
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, FromForm)]
|
|
struct Dog<'r> {
|
|
name: &'r str,
|
|
age: usize
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, FromForm)]
|
|
struct Q<'r> {
|
|
dog: Dog<'r>
|
|
}
|
|
|
|
#[get("/?<color>&color=red&<q..>")]
|
|
fn query_collection(color: Vec<&str>, q: Q<'_>) -> String {
|
|
format!("{} - {} - {}", color.join("&"), q.dog.name, q.dog.age)
|
|
}
|
|
|
|
#[get("/?<color>&color=red&<dog>")]
|
|
fn query_collection_2(color: Vec<&str>, dog: Dog<'_>) -> String {
|
|
format!("{} - {} - {}", color.join("&"), dog.name, dog.age)
|
|
}
|
|
|
|
#[test]
|
|
fn test_query_collection() {
|
|
#[track_caller]
|
|
fn run(client: &Client, colors: &[&str], dog: &[&str]) -> (Status, String) {
|
|
let join = |slice: &[&str], prefix: &str| slice.iter()
|
|
.map(|v| format!("{}{}", prefix, v))
|
|
.collect::<Vec<_>>()
|
|
.join("&");
|
|
|
|
let q = format!("{}&{}", join(colors, "color="), join(dog, "dog."));
|
|
let response = client.get(format!("/?{}", q)).dispatch();
|
|
(response.status(), response.into_string().unwrap())
|
|
}
|
|
|
|
fn run_tests(rocket: rocket::Rocket) {
|
|
let client = Client::debug(rocket).unwrap();
|
|
|
|
let colors = &["blue", "green"];
|
|
let dog = &["name=Fido", "age=10"];
|
|
assert_eq!(run(&client, colors, dog).0, Status::NotFound);
|
|
|
|
let colors = &["red"];
|
|
let dog = &["name=Fido"];
|
|
assert_eq!(run(&client, colors, dog).0, Status::NotFound);
|
|
|
|
let colors = &["red"];
|
|
let dog = &["name=Fido", "age=2"];
|
|
assert_eq!(run(&client, colors, dog).1, " - Fido - 2");
|
|
|
|
let colors = &["red", "blue", "green"];
|
|
let dog = &["name=Fido", "age=10"];
|
|
assert_eq!(run(&client, colors, dog).1, "blue&green - Fido - 10");
|
|
|
|
let colors = &["red", "blue", "green"];
|
|
let dog = &["name=Fido", "age=10", "toy=yes"];
|
|
assert_eq!(run(&client, colors, dog).1, "blue&green - Fido - 10");
|
|
|
|
let colors = &["blue", "red", "blue"];
|
|
let dog = &["name=Fido", "age=10"];
|
|
assert_eq!(run(&client, colors, dog).1, "blue&blue - Fido - 10");
|
|
|
|
let colors = &["blue", "green", "red", "blue"];
|
|
let dog = &["name=Max+Fido", "age=10"];
|
|
assert_eq!(run(&client, colors, dog).1, "blue&green&blue - Max Fido - 10");
|
|
}
|
|
|
|
let rocket = rocket::ignite().mount("/", routes![query_collection]);
|
|
run_tests(rocket);
|
|
|
|
let rocket = rocket::ignite().mount("/", routes![query_collection_2]);
|
|
run_tests(rocket);
|
|
}
|
|
|
|
use rocket::request::FromSegments;
|
|
use rocket::http::uri::Segments;
|
|
|
|
struct PathString(String);
|
|
|
|
impl FromSegments<'_> for PathString {
|
|
type Error = std::convert::Infallible;
|
|
|
|
fn from_segments(segments: Segments<'_>) -> Result<Self, Self::Error> {
|
|
Ok(PathString(segments.collect::<Vec<_>>().join("/")))
|
|
}
|
|
|
|
}
|
|
|
|
#[get("/<_>/b/<path..>", rank = 1)]
|
|
fn segments(path: PathString) -> String {
|
|
format!("nonempty+{}", path.0)
|
|
}
|
|
|
|
#[get("/<path..>", rank = 2)]
|
|
fn segments_empty(path: PathString) -> String {
|
|
format!("empty+{}", path.0)
|
|
}
|
|
|
|
#[test]
|
|
fn test_inclusive_segments() {
|
|
let rocket = rocket::ignite()
|
|
.mount("/", routes![segments])
|
|
.mount("/", routes![segments_empty]);
|
|
|
|
let client = Client::debug(rocket).unwrap();
|
|
let get = |uri| client.get(uri).dispatch().into_string().unwrap();
|
|
|
|
assert_eq!(get("/"), "empty+");
|
|
assert_eq!(get("//"), "empty+");
|
|
assert_eq!(get("//a/"), "empty+a");
|
|
assert_eq!(get("//a//"), "empty+a");
|
|
assert_eq!(get("//a//c/d"), "empty+a/c/d");
|
|
|
|
assert_eq!(get("//a/b"), "nonempty+");
|
|
assert_eq!(get("//a/b/c"), "nonempty+c");
|
|
assert_eq!(get("//a/b//c"), "nonempty+c");
|
|
assert_eq!(get("//a//b////c"), "nonempty+c");
|
|
assert_eq!(get("//a//b////c/d/e"), "nonempty+c/d/e");
|
|
}
|