tests/e2e/test_base_path_e2e.py
Rama dev · réplica de lectura del repositorio original.
"""The app served from a subpath, driven by a real browser.
The ASGI tests (`test_base_path_app.py`) prove the SERVER routes under a prefix. They cannot prove
the half that runs in the browser: that the rewritten shell's bundle actually loads, that React
Router's `basename` resolves a deep link, and that every request the SPA makes carries the prefix.
Get any of those wrong and the symptom is a blank page in a deployment no other test runs in —
which is precisely how this class of bug reaches someone's server.
No fake Plex here on purpose: an unconfigured app still serves the shell, mounts React, routes, and
calls the API, which is the whole surface under test. Adding a seeded server would test the wizard.
"""
from __future__ import annotations
from collections.abc import Iterator
import pytest
from playwright.sync_api import Browser
from shortlist.server.main import create_app
from tests.e2e.conftest import _free_port
from tests.uvicorn_thread import UvicornThread
pytestmark = pytest.mark.e2e
BASE = "/shortlist"
@pytest.fixture
def prefixed_url(tmp_path, monkeypatch) -> Iterator[str]:
"""A real server that believes it lives at `/shortlist`, as a forwarding proxy would present it."""
monkeypatch.setenv("APP_BASE_PATH", BASE)
server = UvicornThread(create_app(config_dir=tmp_path), _free_port())
server.start()
server.wait_until_up(f"{BASE}/api/system/health")
yield f"http://127.0.0.1:{server.port}"
server.stop()
def _watched_page(browser: Browser, url: str):
"""A page that records everything that would show up as "it just doesn't work"."""
page = browser.new_context().new_page()
page.set_default_timeout(60_000)
console_errors: list[str] = []
failed: list[str] = []
requested: list[str] = []
page.on("console", lambda m: console_errors.append(m.text) if m.type == "error" else None)
page.on("requestfailed", lambda r: failed.append(r.url))
page.on("request", lambda r: requested.append(r.url))
return page, console_errors, failed, requested
def test_the_app_boots_and_talks_to_itself_under_the_prefix(browser: Browser, prefixed_url: str) -> None:
page, console_errors, failed, requested = _watched_page(browser, prefixed_url)
page.goto(f"{prefixed_url}{BASE}/", wait_until="networkidle")
# The shell alone renders an empty <div id="root">. Anything inside it means the bundle was
# found at the rewritten URL, parsed, and mounted — the asset rewrite reaching a real browser.
assert page.locator("#root > *").count() > 0, "React never mounted — the bundle did not load"
# The prefix reached the SPA, not just the server.
assert page.evaluate("window.__SHORTLIST_BASE_PATH__") == BASE
# Asserted on the URL, not the response: the app deliberately answers unprefixed paths too,
# so an asset or API call that skipped the prefix would still come back 200 and leave both
# `failed` and `console_errors` empty. Only the URL shows it.
interesting = [u for u in requested if "/api/" in u or "/assets/" in u]
assert interesting, "the SPA fetched nothing — it never got far enough to be a test"
unprefixed = [u for u in interesting if f"{BASE}/" not in u]
assert unprefixed == [], f"requests bypassed the base path: {unprefixed}"
assert failed == [], f"requests failed under the prefix: {failed}"
assert console_errors == [], f"console errors under the prefix: {console_errors}"
page.context.close()
def test_a_deep_link_survives_a_reload(browser: Browser, prefixed_url: str) -> None:
"""`basename` has to strip the prefix before matching, and put it back when navigating.
Loading a nested route directly is the case a wrong `basename` breaks: the router sees a path
it cannot match and renders nothing, so the page is blank with a 200 and no console error.
"""
page, console_errors, failed, _ = _watched_page(browser, prefixed_url)
page.goto(f"{prefixed_url}{BASE}/settings", wait_until="networkidle")
assert page.locator("#root > *").count() > 0, "the router matched nothing under the prefix"
# Wherever the app decided to send an unconfigured visitor, it must stay inside the prefix.
assert page.url.startswith(f"{prefixed_url}{BASE}/"), f"navigated out of the base path: {page.url}"
assert failed == []
assert console_errors == []
page.context.close()