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
92 lines
3.3 KiB
Python
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
|