1 Commits
Author SHA1 Message Date
Juan DenisandClaude Opus 5 529e3eaa72 test(proxy): pin what an external reverse proxy depends on
Issue #96's redirect loop came from the nginx site we install, not the app --
but nothing pinned "the app never redirects to HTTPS", so the docs relied on a
property no test kept true.

Covers: no redirect to https from the curated entry routes, the same under
X-Forwarded-Proto: https (the header any force-HTTPS middleware would key on),
and a sweep of every no-argument GET rule in the URL map so new blueprints are
covered without anyone remembering to add them; SERVERKIT_PUBLIC_URL auto-allowed
as an origin, trailing slash normalised, additive to CORS_ORIGINS and never '*';
that value reaching Socket.IO with async_mode='threading'; and the handshake
accepting the public origin while rejecting a stranger.

Mutation-checked rather than merely green: injecting a before_request HTTPS
redirect fails the sweep on all 352 routes it walks, and the handshake's 400
flips to 200 when the same origin is allowlisted.

ProxyFix on/off and HSTS gating are deliberately not duplicated here -- they are
already covered by test_trusted_client_ip.py and test_security_headers.py.

The config fixture loads config.py as a throwaway module rather than calling
importlib.reload. reload looks equivalent and is a trap: it rebinds
config.TestingConfig to a new class object while app/__init__.py still holds the
original config dict from its `from config import config`, so tests that
monkeypatch TestingConfig patch the new class while create_app reads the old one
and ProxyFix silently never turns on. That broke 9 tests in
test_trusted_client_ip.py and test_login_bruteforce.py, in a full run only.

Full suite: 3542 passed, 48 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 23:07:10 -04:00