Files
app/backend/tests/test_spa.py
T
stroblme 75b3a93094
Docs / docs (push) Successful in 33s
Playwright Tests / test-playwright (1, 2) (push) Failing after 1m37s
Playwright Tests / test-playwright (2, 2) (push) Failing after 27s
pre-commit / pre-commit (push) Failing after 2m37s
Test Backend / test-backend (3.12) (push) Successful in 5m7s
Test Backend / test-backend (3.14) (push) Successful in 5m2s
Compose Smoke Test / test-compose (push) Failing after 28s
Playwright Tests / merge-reports (push) Failing after 2m29s
Fix work recovery and reduce engine and chart overhead
2026-10-29 14:24:17 +01:00

92 lines
3.3 KiB
Python

import base64
import gzip
import hashlib
from pathlib import Path
import pytest
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from starlette.testclient import TestClient
from fluksio.spa import Dashboard
THEME = "document.documentElement.classList.add('dark')"
def _client(dist: Path) -> TestClient:
(dist / "assets").mkdir(parents=True)
(dist / "index.html").write_text(f"<html><script>{THEME}</script></html>")
(dist / "assets" / "app-1a2b.js").write_text("console.log(1)")
api = Starlette(routes=[Route("/api/v1/ping", lambda _: PlainTextResponse("api"))])
return TestClient(Dashboard(api, dist))
def test_the_dashboard_and_the_api_share_one_origin(tmp_path: Path) -> None:
client = _client(tmp_path)
# A client-side route survives a reload, with the inline script admitted.
page = client.get("/flows/house")
digest = base64.b64encode(hashlib.sha256(THEME.encode()).digest()).decode()
assert THEME in page.text
assert page.headers["cache-control"] == "no-cache"
assert f"'sha256-{digest}'" in page.headers["content-security-policy"]
asset = client.get("/assets/app-1a2b.js")
assert asset.text == "console.log(1)"
assert "immutable" in asset.headers["cache-control"]
assert client.get("/api/v1/ping").text == "api"
# Nothing outside the build directory is served, only the shell.
assert THEME in client.get("/assets/../../etc/passwd").text
@pytest.mark.parametrize(
("accepted", "compressed"),
[
("gzip", True),
("br, gzip;q=0.8", True),
("gzip ; q=1, identity;q=0", True),
("*", True),
("gzip;q=0, *;q=1", False),
("gzip;q=0.5, identity;q=1", False),
("br", False),
("identity", False),
],
)
def test_precompressed_assets_keep_their_type_and_negotiate_encoding(
tmp_path: Path, accepted: str, compressed: bool
) -> None:
client = _client(tmp_path)
content = b"console.log('built once');\n" * 200
asset = tmp_path / "assets" / "app-1a2b.js"
asset.write_bytes(content)
packed = gzip.compress(content)
asset.with_name(asset.name + ".gz").write_bytes(packed)
response = client.get("/assets/app-1a2b.js", headers={"Accept-Encoding": accepted})
assert response.content == content
assert (response.headers.get("content-encoding") == "gzip") is compressed
assert "javascript" in response.headers["content-type"]
assert response.headers["vary"] == "Accept-Encoding"
assert "immutable" in response.headers["cache-control"]
expected = len(packed) if compressed else len(content)
assert int(response.headers["content-length"]) == expected
head = client.head("/assets/app-1a2b.js", headers={"Accept-Encoding": accepted})
assert head.content == b""
assert head.headers["content-length"] == str(expected)
def test_a_build_without_compressed_assets_still_serves_plain_files(
tmp_path: Path,
) -> None:
client = _client(tmp_path)
content = b"console.log('plain build');\n" * 200
(tmp_path / "assets" / "app-1a2b.js").write_bytes(content)
response = client.get("/assets/app-1a2b.js", headers={"Accept-Encoding": "gzip"})
assert response.content == content
assert "content-encoding" not in response.headers