Files
team-tryouts/tests/test_static_caching.py
T
GGThed d0a9e75fe6 perf: servir les statiques par nginx, et dire ce que le bot n a pas livre
PERF-006. Le bloc location /static/ etait commente : 59 Ko de CSS et de JS
passaient par Waitress a chaque page. L activer tel quel aurait ete une
regression : ces URL ne changent jamais, donc un cache de 30 jours sert une
feuille de style vieille d un mois apres chaque deploiement, sans moyen de
l invalider. url_for('static') estampille maintenant chaque URL du mtime du
fichier ; c est ce qui rend le immutable vrai et pas seulement rapide.

Deux pieges nginx consignes dans le fichier : un add_header dans un location
annule tous les add_header herites du server (nosniff disparaissait du
JavaScript), et un statique manquant doit renvoyer 404 plutot que retomber
sur Flask, sinon un deploiement casse se cache derriere une page qui marche.

PERF-005. Les objets utilisateur Discord sont mis en cache. A etre precis
sur le gain : un envoi coute deux appels reseau, resoudre puis envoyer, et
seul le premier est economise — un premier match a vingt joueurs fait
toujours vingt resolutions. Ce qui est gagne l est entre notifications, la
ou le bot ecrit aux memes personnes soir apres soir.

Chaque message dit desormais ce qu il est devenu, avec le destinataire et
la raison. Les trois echecs ne se ressemblent pas et ne se lisent plus
pareil : une boite fermee est definitive et ne se retente pas, une erreur
HTTP est passagere, un identifiant sans proprietaire est un compte a
corriger. Le lot quotidien annonce son propre deficit.

Piege trouve en ecrivant les tests : configure_logging met propagate=False
sur le logger 'app', et le handler de caplog est sur la racine. Les
assertions sur les journaux passaient seules et echouaient dans la suite
complete, ou une application avait deja ete construite — elles lisaient un
journal vide, pas un bot silencieux.

417 tests.
2026-08-11 11:56:48 -04:00

105 lines
4.1 KiB
Python

"""Static URLs carry a version stamp, so nginx may cache them (PERF-006).
The audit asked for three lines of nginx: serve /static/ from disk with a
30-day expiry. Enabling that alone would have been a regression. The CSS and
the JS are referenced by a fixed URL, so a month-long cache means a
month-old stylesheet after every deploy, with no way to invalidate it short
of asking people to hard-refresh.
The stamp is what makes the caching safe: a changed file gets a new URL, and
the cached copy of the old one is simply never requested again. Delete the
stamp and the nginx block becomes a bug — which is the only reason these
tests exist.
"""
import os
import re
import pytest
from flask import url_for
@pytest.fixture
def nginx_conf():
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
with open(os.path.join(root, 'app', 'nginx.conf'), encoding='utf-8') as handle:
return handle.read()
class TestVersionStamp:
def test_static_urls_carry_a_stamp(self, app):
with app.test_request_context():
url = url_for('static', filename='css/style.css')
assert re.search(r'\?v=\d+$', url), f'no cache-busting stamp in {url}'
def test_a_touched_file_gets_a_new_url(self, app):
"""The whole point: redeploying a file must change its URL.
The stamp is memoised per process — the process restarts on deploy,
which is exactly when a file can have changed — so this drives a
fresh application rather than touching the file under a live one.
"""
from app.app import create_app
path = os.path.join(app.static_folder, 'css/style.css')
original = os.stat(path)
with app.test_request_context():
before = url_for('static', filename='css/style.css')
os.utime(path, (original.st_atime, original.st_mtime + 60))
try:
second = create_app(dict(app.config))
with second.test_request_context():
after = url_for('static', filename='css/style.css')
finally:
os.utime(path, (original.st_atime, original.st_mtime))
assert before != after
def test_a_missing_file_still_builds_a_url(self, app):
"""A template naming a file that is not there must 404, not 500."""
with app.test_request_context():
url = url_for('static', filename='css/does-not-exist.css')
assert url.endswith('does-not-exist.css'), 'no stamp, and no exception either'
def test_other_endpoints_are_untouched(self, app):
with app.test_request_context():
assert '?v=' not in url_for('main.index')
def test_the_stylesheet_and_the_script_are_versioned_in_the_page(self, client, app):
"""The stamp is worthless if the layout bypasses url_for."""
page = client.get('/auth/login').get_data(as_text=True)
assert re.search(r'style\.css\?v=\d+', page)
assert re.search(r'main\.js\?v=\d+', page)
class TestNginx:
def test_the_static_block_is_live(self, nginx_conf):
block = re.search(r'^\s*location /static/ \{', nginx_conf, re.MULTILINE)
assert block, 'the /static/ block is commented out again — 59 KB per page load'
def test_caching_headers_are_present(self, nginx_conf):
static_block = nginx_conf.split('location /static/')[1].split('\n }')[0]
assert 'expires 30d' in static_block
assert 'immutable' in static_block
def test_security_headers_survive_the_block(self, nginx_conf):
"""One add_header in a location drops every inherited one.
nginx only inherits add_header from the enclosing block when the
current block declares none of its own. Setting Cache-Control here
therefore removes nosniff from the JavaScript unless it is repeated.
"""
static_block = nginx_conf.split('location /static/')[1].split('\n }')[0]
assert 'X-Content-Type-Options' in static_block, (
'add_header here cancels the inherited security headers; nosniff '
'has to be repeated inside the block'
)
assert 'Strict-Transport-Security' in static_block