-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Route Tracing #1878
Comments
Why do you want to test that a URI + method resolves to a specific route? I'm not sure your code does what you might want it to. Importantly, it completely ignores guards and #[get("/foo/<bar>")]
fn foo(bar: u8) { .. }
#[get("/foo/<bar>", rank = 2)]
fn bar(bar: usize) { .. } Your approach is also quite brittle as it checks that the route's name is a given string, but you could have two routes with the same name with no way to disambiguate as the code stands, and changing the route's name would break your tests. What's more, assuming no rank-resolved conflicts, you're effectively just checking that you put the right attribute on the right function. Why not issue a request (in testing) and see if you get the response you expect? This has none of the downsides and is actually checking something meaningful. |
I think the desire here would be to test things similar to the routing It could be useful to test the guards, formats, fairings, and routing Another reason I would like routing tests is because routing logic is One nice thing from the routing specs is testing that a given request is Some reasons to not need routing tests:
|
Looking into this, it actually seems fairly trivial to add support for testing which route handled a given request. The actual mechanics are somewhat complicated, but the use rocket::local::asynchronous::Client;
#[rocket::async_test]
async fn test_routing() {
let client = Client::untracked(super::rocket()).await.expect("Failed to launch rocket");
let res = client.get(uri!("/")).dispatch().await;
assert_routed_with!(res, super::index);
} The macro would then be expanded to something like: {
let route = res.route().expect("Request was not routed");// Route run for local client
let expected_route = super::index {}.into_info();// Route info from user
assert_eq!(route.name, expected_route.name);
assert_eq!(route.method, expected_route.method);
// etc.
} However, there are a few issues. First, we acutally can't tell certain routes apart, since we only know the name of the function, which method, and the uri defined in the route macro. To actually handle this, we likely need to add some support for the route to hold onto which route it was called from - I think it may be trivial to implement this using an I'm not sure if this should be implemented, but this does also take care of a few issues that were expressed. First, this requires running the full routing machinary, including request guards, and the actual route itself. This also makes it easy to check the response from the request (since the full response was generated), or simply add it as an additional check to an existing test. There are still some issues. First, if a request guard fails, and the request is forwarded to a catcher, this will still show the route as being executed, since the route was the last one attempted. This also only shows the last route attempted (which will be the one that responded, if one did), so this may not work for testing routes with authentication, if there are backup routes (i.e. a redirect to the login page). Another interesting option for testing whether a given route gets called would be to create a partial Rocket instance, where rather than mounting everything, you just mount the route in question and check whether it gets routed at all. These tests should also be run against your 'full' Rocket instance, to verify that another route doesn't take precedence. For testing, it may be useful to provide a use rocket::local::asynchronous::Client;
#[rocket::async_test]
async fn test_routing() {
let full_client = Client::untracked(super::rocket()).await.expect("Failed to launch rocket");
let full_res = full_client.get(uri!("/")).dispatch().await;
assert_eq!(full_res.status(), Status::Ok, "No route responded");
let index_client = Client::untracked(super::rocket().clear_routes().mount("/", routes![super::index])).await.expect("Failed to launch rocket");
let index_res = index_client.get(uri!("/")).dispatch().await;
assert_eq!(index_res.status(), Status::Ok, "Index route did not respond");
assert_eq!(index_res.into_string(), full_res.into_string(), "Index route did not return the same reponse with the full rocket instance");
} For testing authenticated routes, a partial client could be used to see if the authenticated route matches an unauthenticated request. Unlike a full client, if the route doesn't match it will return a 404, making the test potentially a lot shorter. Right now, I would recommend testing by placing some kind of key in the authenticated page (i.e. maybe the user's email or some other information that the page should only return to an authenticated user), and checking for it's presence in the response. This also catches potential information leaks in the fallback methods, since they shouldn't return this sensitive information. The only real issue I see with this is that it is non-trivial to check whether the route returned a 404 or the default responder returned a 404. This could be trivially solved by mounting a default route that responds with a different status by default, but it's not an obvious solution. A better alternative may be to create a |
I'll reopen this as I'm quite intrigued by the ideas proposed by @the10thWiz above. Generalizing from there, I think it would be phenomenal if we could ask LocalResponse, unambiguously, to tell us what generated the response, whether it be a user's route, something from a library, a catcher, or whatever. Even better would be to know the chain of calls to get to the response, including forwards and so on. That would subsume the request in this issue and likely make for even better logging. I would accept such a proposal and implementation. P.S: As an aside, I've long envisioned (at the suggestion of a colleague) a UI tool for Rocket that visually shows you the chain from request to response through routes, catchers, fairings, etc. The suggestion I've made above would enable such a tool. |
Just an idea... Have you thought about using OpenTelemetry to instrument your code? This would give you all the info you need. The advantage is it's not limited to routes but you can add trace info to all parts of the code (if wanted). |
Note that with #1202 (comment), our trace messages now include all of the information necessary to understand the complete flow of a request. It doesn't give you a nice AP to ask questions, but all of the information is there. |
I want to be be able to test that routes resolve correctly, given the HTTP method and URI.
Existing Functionality
Router
Suggested Changes
The above code works if
Router
is made public #1873 and the route name is settable #1872.Alternatives Considered
Additional Context
The text was updated successfully, but these errors were encountered: