falconry / falcon

The no-magic web data plane API and microservices framework for Python developers, with a focus on reliability, correctness, and performance at scale.
https://falcon.readthedocs.io/en/stable/
Apache License 2.0
9.51k stars 937 forks source link

TestClient doesn't work for HEAD and HTTPRouteNotFound error handler #2154

Closed liborjelinek closed 1 year ago

liborjelinek commented 1 year ago

I have a custom error handler for HTTPRouteNotFound to provide friendlier error message "route not found" then generic "something not found".

import falcon
import falcon.testing

def route_not_found_handler(req, resp, ex, params):
    raise falcon.HTTPRouteNotFound(
        title=route_not_found_handler.title,
        description=route_not_found_handler.description,
    )

route_not_found_handler.title = "No such route"
route_not_found_handler.description = "The API doesn't know endpoint with specified URL."

For testing, I create simple resource:

class HelloResource:
    def on_get(self, req, resp):
        resp.text = "Hello"

app = falcon.App()
app.add_route('/hello', HelloResource())

and add route_not_found_handler()

app.add_error_handler(falcon.HTTPRouteNotFound, route_not_found_handler)

Testing it with GET to non-existing route works well:

client = falcon.testing.TestClient(app)

# For GET it's ok
result = client.get("/non-existing")
assert result.status == falcon.HTTP_404
assert result.json == {
                "title": route_not_found_handler.title,
                "description": route_not_found_handler.description }

However, if method is HEAD, response code is 404 but body is empty

# For HEAD it fails
result = client.head("/non-existing")
assert result.status == falcon.HTTP_404
# result.json is None
assert result.json == {
                "title": route_not_found_handler.title,
                "description": route_not_found_handler.description }
vytas7 commented 1 year ago

Hey @mattwarrick! I believe the test client is working as intended -- it is the framework itself that does not render any content; and that is by design.

Falcon conforms to the HTTP semantics, see, e.g. RFC 9110, 9.3.2. HEAD:

The HEAD method is identical to GET except that the server MUST NOT send content in the response.

(emphasis mine)

liborjelinek commented 1 year ago

Ooo, you are correct. I didn't realize HEAD responses have no bodies. Thank you for making Falcon so great.