Granian serves the compiled SPA assets directly in Rust: hashed js/wasm/css under /static (the release build uses --public-url /static/) and the card images under /assets, configured with the GRANIAN_STATIC_PATH_ROUTE/MOUNT/ DIR_TO_FILE env vars in the Dockerfile. The Python catch-all now only serves the SPA shell (index.html) at / and for client-side routes. Every module logs through getLogger(__name__): lifecycle and business events at INFO, per-move and store detail at DEBUG. The built-in default writes DEBUG to the console; LOGGING_CONFIG points at a YAML file in the logging.config.dictConfig schema to take over the configuration. PyYAML becomes a direct dependency.
71 lines
2.8 KiB
Python
71 lines
2.8 KiB
Python
"""Tests for the whoami endpoint and the static SPA host."""
|
|
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
from httpx import ASGITransport, AsyncClient
|
|
from pwo import async_test
|
|
|
|
from tavolo.app import app
|
|
from tavolo.config import settings
|
|
from tests.helpers import oidc_user
|
|
|
|
|
|
class MeRouteTest(unittest.TestCase):
|
|
@async_test
|
|
async def test_me_authenticated(self) -> None:
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://127.0.0.1") as client:
|
|
with oidc_user("alice"):
|
|
response = await client.get("/api/me")
|
|
self.assertEqual(200, response.status_code)
|
|
self.assertEqual({"sub": "alice", "name": "alice"}, response.json())
|
|
|
|
@async_test
|
|
async def test_me_unauthenticated(self) -> None:
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://127.0.0.1") as client:
|
|
response = await client.get("/api/me")
|
|
self.assertEqual(401, response.status_code)
|
|
|
|
|
|
class StaticRouteTest(unittest.TestCase):
|
|
"""The app only serves the SPA shell; asset files under /static are
|
|
served by Granian and are not reachable through the ASGI transport."""
|
|
|
|
@async_test
|
|
async def test_serves_shell_and_spa_fallback(self) -> None:
|
|
with tempfile.TemporaryDirectory() as dist:
|
|
(Path(dist) / "index.html").write_text("<html>spa</html>")
|
|
|
|
patched = dataclasses.replace(settings, static_dir=dist)
|
|
with mock.patch("tavolo.routes.static.settings", patched):
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://127.0.0.1") as client:
|
|
index = await client.get("/")
|
|
self.assertEqual(200, index.status_code)
|
|
self.assertEqual("text/html; charset=utf-8", index.headers["content-type"])
|
|
self.assertIn(b"spa", index.content)
|
|
|
|
# Unknown client-side route falls back to the app shell.
|
|
fallback = await client.get("/game/some-id")
|
|
self.assertEqual(200, fallback.status_code)
|
|
self.assertIn(b"spa", fallback.content)
|
|
|
|
@async_test
|
|
async def test_missing_dist_returns_404(self) -> None:
|
|
patched = dataclasses.replace(settings, static_dir="/nonexistent-dist")
|
|
with mock.patch("tavolo.routes.static.settings", patched):
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://127.0.0.1") as client:
|
|
response = await client.get("/")
|
|
self.assertEqual(404, response.status_code)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|