Files
TieMeasureFlow/src/frontend/flask_app/tests/test_offline.py
T
2026-10-07 13:46:27 +02:00

209 lines
8.1 KiB
Python

"""Point 12: the install must work on a network with no way out.
The factory floor at Tràfilo has no internet. A page that fetches a library from
a CDN there does not load slowly, it does not load: the interface never starts.
The libraries were brought in-house; this is what keeps them there. The failure
mode this guards against is not dramatic - someone adds a `<script src="https://
cdn...">` because it is the quickest way to try something, it works at their desk,
and nobody finds out until the machine is on the shop floor.
"""
import re
import subprocess
from pathlib import Path
import pytest
APP_ROOT = Path(__file__).resolve().parents[1]
TEMPLATES = APP_ROOT / "templates"
STATIC = APP_ROOT / "static"
# Anything loaded by the browser: src=, href=, url(), importScripts, workerSrc.
_EXTERNAL_ASSET_RX = re.compile(
r"""(?:src|href)\s*=\s*["']https?://""", re.I,
)
_EXTERNAL_URL_RX = re.compile(r"""https?://[^\s"'<>)]+""")
# Hosts a comment may legitimately mention: the check is about what gets fetched,
# not about what gets written down. Only documentation links live here.
_DOC_HOSTS = (
"tailwindcss.com", "github.com", "bugzilla.mozilla.org",
"bugs.chromium.org", "bugs.webkit.org", "www.w3.org", "schema.org",
"developer.mozilla.org",
)
def _tracked_files():
"""The files git knows about, or None when git cannot answer.
Being outside `vendor/` is not the same as being ours. A scratch copy of a
library dropped into `static/js/` to debug something looks first-party and is
not: it is not in the repository, it never reaches the shop floor, and its
comments are not our comments. Trackedness is the honest test for that.
Trackedness, and not `.gitignore`: an ignore pattern says nothing about a
file that is already tracked, so matching patterns would be the wrong
question.
When git cannot answer we scan everything, as before. A guard test that goes
quiet the moment it loses its footing is worse than one that cries wolf.
"""
try:
listed = subprocess.run(
["git", "ls-files", "-z"],
cwd=APP_ROOT, capture_output=True, text=True, check=True,
).stdout
except (OSError, subprocess.SubprocessError):
return None
return {(APP_ROOT / name).resolve() for name in listed.split("\0") if name}
def _first_party_files(suffixes):
tracked = _tracked_files()
def is_ours(path):
return tracked is None or path.resolve() in tracked
for path in sorted(STATIC.rglob("*")):
if path.suffix in suffixes and "vendor" not in path.parts and is_ours(path):
yield path
for path in sorted(TEMPLATES.rglob("*.html")):
if is_ours(path):
yield path
def test_no_template_loads_anything_from_the_network():
"""A src= or href= pointing outside is the whole bug, in one line."""
offenders = []
for path in sorted(TEMPLATES.rglob("*.html")):
for n, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
if _EXTERNAL_ASSET_RX.search(line):
offenders.append(f"{path.relative_to(APP_ROOT)}:{n}")
assert not offenders, f"asset caricati dalla rete: {offenders}"
def test_no_first_party_script_calls_out():
"""Not only tags: a fetch() or a workerSrc reaches the network just the same."""
offenders = []
for path in _first_party_files({".js", ".css"}):
for n, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
for url in _EXTERNAL_URL_RX.findall(line):
if any(host in url for host in _DOC_HOSTS):
continue
offenders.append(f"{path.relative_to(APP_ROOT)}:{n} {url}")
assert not offenders, f"riferimenti esterni: {offenders}"
def test_the_libraries_are_actually_in_the_box():
"""Repointing the tags without shipping the files is the same white screen."""
vendor = STATIC / "vendor"
missing = [
name for name in (
"alpinejs-3.15.12.min.js",
"plotly-2.32.0.min.js",
"pdf-3.11.174.min.js",
"pdf.worker-3.11.174.min.js",
"fabric-5.3.1.min.js",
"fonts.css",
)
if not (vendor / name).is_file()
]
assert not missing, f"librerie dichiarate ma non presenti: {missing}"
assert list((vendor / "fonts").glob("*.woff2")), "i font non sono nel pacchetto"
def test_the_pdf_worker_is_local_everywhere_it_is_set():
"""The trap of this point: the library loads from disk and its worker does not.
Everything looks right until someone opens a drawing.
"""
setters = []
for path in sorted(TEMPLATES.rglob("*.html")):
text = path.read_text(encoding="utf-8")
if "workerSrc" not in text:
continue
setters.append(path.relative_to(APP_ROOT).as_posix())
for n, line in enumerate(text.splitlines(), 1):
if "workerSrc" in line:
assert "vendor/pdf.worker" in line, (
f"{path.relative_to(APP_ROOT)}:{n} punta il worker fuori casa"
)
# Five templates render PDFs; if that number changes the new one needs the
# worker set too, and this is where you find out.
# #65: da quattro a cinque, task_execute.html mostra PDF nelle note e negli
# allegati dell'esecuzione (#55).
assert len(setters) == 5,f"template che impostano il worker: {setters}"
def test_the_shipped_libraries_are_the_ones_that_were_checked():
"""The fingerprints in VERSIONS.md are the record of what was validated.
A library swapped in place - a hurried patch, a bad merge, something worse -
keeps the same filename and the same version in the table. The hash is the
only part that notices.
"""
vendor = STATIC / "vendor"
recorded = dict(
(name, digest)
for digest, name in (
line.split(maxsplit=1)
for line in (
l.strip() for l in
(vendor / "VERSIONS.md").read_text(encoding="utf-8").splitlines()
)
if re.fullmatch(r"[0-9a-f]{64}\s+\S+", line)
)
)
assert recorded, "VERSIONS.md non elenca più le impronte"
import hashlib
wrong = []
for name, digest in recorded.items():
path = vendor / name
if not path.is_file():
wrong.append(f"{name}: assente")
continue
actual = hashlib.sha256(path.read_bytes()).hexdigest()
if actual != digest:
wrong.append(f"{name}: {actual} != {digest}")
assert not wrong, f"librerie diverse da quelle validate: {wrong}"
def test_the_versions_are_pinned():
"""No `3.x.x`: a library that updates itself changes an audit system without
anyone validating it. The filenames on disk carry the version."""
vendor = STATIC / "vendor"
unpinned = [
p.name for p in vendor.glob("*.js")
if not re.search(r"-\d+\.\d+\.\d+(\.min)?\.js$", p.name)
]
assert not unpinned, f"librerie senza versione nel nome: {unpinned}"
# ---------------------------------------------------------------------------
# The policy that keeps it that way
# ---------------------------------------------------------------------------
PAGES = ["/auth/login", "/"]
@pytest.mark.parametrize("path", PAGES)
def test_every_page_carries_the_policy(client, path):
resp = client.get(path, follow_redirects=False)
csp = resp.headers.get("Content-Security-Policy", "")
assert "default-src 'self'" in csp
assert "connect-src 'self'" in csp
# No CDN host may be allowed back in through the policy itself.
assert "http://" not in csp and "https://" not in csp
def test_the_policy_allows_what_the_app_actually_needs(client):
"""A policy that breaks the interface gets removed, and then it protects
nothing. These are the four allowances the libraries genuinely require."""
csp = client.get("/auth/login").headers["Content-Security-Policy"]
assert "'unsafe-eval'" in csp # Alpine 3, Plotly
assert "'unsafe-inline'" in csp # componenti Alpine nei template
assert "worker-src 'self' blob:" in csp # worker di PDF.js
assert "img-src 'self' data: blob:" in csp # canvas Fabric, miniature