diff --git a/marimo/_server/asgi.py b/marimo/_server/asgi.py index 90f9b1a129a..4121d1edd1f 100644 --- a/marimo/_server/asgi.py +++ b/marimo/_server/asgi.py @@ -491,6 +491,8 @@ def build(self) -> "ASGIApp": def create_redirect_to_slash( base_url: str, ) -> Callable[[Request], Response]: + # Strip leading slash + base_url = base_url.lstrip("/") redirect_path = f"{base_url}/" return lambda _: RedirectResponse( url=redirect_path, status_code=301 diff --git a/tests/_server/test_asgi.py b/tests/_server/test_asgi.py index 84bff5d2376..ba957bb0104 100644 --- a/tests/_server/test_asgi.py +++ b/tests/_server/test_asgi.py @@ -1,15 +1,14 @@ +from __future__ import annotations + import os import tempfile import unittest from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any from starlette.applications import Starlette -from starlette.requests import Request from starlette.responses import PlainTextResponse, Response from starlette.testclient import TestClient -from starlette.types import ASGIApp, Message, Receive, Scope, Send -from starlette.websockets import WebSocket from marimo._server.asgi import ( ASGIAppBuilder, @@ -17,6 +16,11 @@ create_asgi_app, ) +if TYPE_CHECKING: + from starlette.requests import Request + from starlette.types import ASGIApp, Message, Receive, Scope, Send + from starlette.websockets import WebSocket + contents = """ import marimo @@ -142,6 +146,50 @@ def test_can_hit_health(self) -> None: response = client.get("/app1/health") assert response.status_code == 200, response.text + def test_mount_at_root(self) -> None: + """Test that assets are correctly served when app is mounted at the root path.""" + builder = create_asgi_app(quiet=True, include_code=True) + builder = builder.with_app(path="/app1", root=self.app1) + app = builder.build() + + base_app = Starlette() + base_app.mount("/", app) + client = TestClient(base_app) + + # Index page + response = client.get("/app1") + assert response.status_code == 200, response.text + # Index page with trailing slash + response = client.get("/app1/") + assert response.status_code == 200, response.text + # Health check + response = client.get("/app1/health") + assert response.status_code == 200, response.text + + def test_mount_at_non_root(self) -> None: + """Test that assets are correctly served when app is mounted at a non-root path.""" + builder = create_asgi_app(quiet=True, include_code=True) + builder = builder.with_app(path="/app1", root=self.app1) + app = builder.build() + + base_app = Starlette() + base_app.mount("/marimo", app) + client = TestClient(base_app) + + # Not at the root + response = client.get("/app1") + assert response.status_code == 404, response.text + + # Index page + response = client.get("/marimo/app1") + assert response.status_code == 200, response.text + # Index page with trailing slash + response = client.get("/marimo/app1/") + assert response.status_code == 200, response.text + # Health check + response = client.get("/marimo/app1/health") + assert response.status_code == 200, response.text + def test_app_with_middleware(self): # Create a simple middleware class TestMiddleware: