CI / Build and push docker image (push) Successful in 3m12s
The platform now hosts multiple card games, with scopone scientifico as the first one. Rename the brand wherever it is not a game rule: - move the Python package to server/src/tavolo and update imports - rename the Postgres database/user, OIDC issuer path, client id and Redis key prefixes to tavolo (clean break: existing pgdata volumes and live games are not migrated) - rename the Cargo package to tavolo-web and set the page title to Tavolo - update docs and the Docker image path to woggioni/tavolo The scopa game term (clearing the table) in the engine, state and web UI is intentionally left untouched.
86 lines
3.5 KiB
Python
86 lines
3.5 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):
|
|
@async_test
|
|
async def test_serves_files_and_spa_fallback(self) -> None:
|
|
with tempfile.TemporaryDirectory() as dist:
|
|
root = Path(dist)
|
|
(root / "index.html").write_text("<html>spa</html>")
|
|
(root / "app.js").write_text("console.log(1)")
|
|
cards = root / "assets" / "cards"
|
|
cards.mkdir(parents=True)
|
|
(cards / "07D.svg").write_text("<svg/>")
|
|
|
|
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.assertIn(b"spa", index.content)
|
|
|
|
js = await client.get("/app.js")
|
|
self.assertEqual(200, js.status_code)
|
|
self.assertEqual("text/javascript; charset=utf-8", js.headers["content-type"])
|
|
|
|
svg = await client.get("/assets/cards/07D.svg")
|
|
self.assertEqual(200, svg.status_code)
|
|
self.assertEqual("image/svg+xml", svg.headers["content-type"])
|
|
|
|
# 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)
|
|
|
|
# Traversal attempts never escape the dist directory.
|
|
traversal = await client.get("/..%2F..%2Fetc%2Fpasswd")
|
|
self.assertIn(traversal.status_code, (200, 404))
|
|
if traversal.status_code == 200:
|
|
self.assertIn(b"spa", traversal.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()
|