diff --git a/sanic/blueprints.py b/sanic/blueprints.py index 85b60ddc..2d1e6a1d 100644 --- a/sanic/blueprints.py +++ b/sanic/blueprints.py @@ -85,7 +85,11 @@ class Blueprint(BaseSanic): self.routes: List[Route] = [] self.statics: List[RouteHandler] = [] self.strict_slashes = strict_slashes - self.url_prefix = url_prefix + self.url_prefix = ( + url_prefix[:-1] + if url_prefix and url_prefix.endswith("/") + else url_prefix + ) self.version = version self.websocket_routes: List[Route] = [] diff --git a/sanic/mixins/routes.py b/sanic/mixins/routes.py index f57bf230..27747ab3 100644 --- a/sanic/mixins/routes.py +++ b/sanic/mixins/routes.py @@ -71,7 +71,7 @@ class RouteMixin: # Fix case where the user did not prefix the URL with a / # and will probably get confused as to why it's not working - if not uri.startswith("/"): + if not uri.startswith("/") and (uri or hasattr(self, "router")): uri = "/" + uri if strict_slashes is None: diff --git a/tests/test_routes.py b/tests/test_routes.py index 60c0e760..4b2927ef 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -1175,3 +1175,59 @@ def test_route_with_bad_named_param(app): with pytest.raises(SanicException): app.router.finalize() + + +def test_routes_with_and_without_slash_definitions(app): + bar = Blueprint("bar", url_prefix="bar") + baz = Blueprint("baz", url_prefix="/baz") + fizz = Blueprint("fizz", url_prefix="fizz/") + buzz = Blueprint("buzz", url_prefix="/buzz/") + + instances = ( + (app, "foo"), + (bar, "bar"), + (baz, "baz"), + (fizz, "fizz"), + (buzz, "buzz"), + ) + + for instance, term in instances: + route = f"/{term}" if isinstance(instance, Sanic) else "" + + @instance.get(route, strict_slashes=True) + def get_without(request): + return text(f"{term}_without") + + @instance.get(f"{route}/", strict_slashes=True) + def get_with(request): + return text(f"{term}_with") + + @instance.post(route, strict_slashes=True) + def post_without(request): + return text(f"{term}_without") + + @instance.post(f"{route}/", strict_slashes=True) + def post_with(request): + return text(f"{term}_with") + + app.blueprint(bar) + app.blueprint(baz) + app.blueprint(fizz) + app.blueprint(buzz) + + for _, term in instances: + _, response = app.test_client.get(f"/{term}") + assert response.status == 200 + assert response.text == f"{term}_without" + + _, response = app.test_client.get(f"/{term}/") + assert response.status == 200 + assert response.text == f"{term}_with" + + _, response = app.test_client.post(f"/{term}") + assert response.status == 200 + assert response.text == f"{term}_without" + + _, response = app.test_client.post(f"/{term}/") + assert response.status == 200 + assert response.text == f"{term}_with"