Serve a spec that knows where the instance is mounted

/api/docs on the production site handed out
https://<host>/api/apitokens for an app that lives at /shopdb. Copy that URL
and it 404s; Swagger's try-it fired at the same wrong address.

The mount is a DEPLOYMENT fact and the spec is a BUILD artifact.
gen_openapi.py writes servers: [{"url": "/"}] because at generation time nobody
knows where the app will be hung, and wsgi.py only moves MOUNT_PATH from
PATH_INFO into SCRIPT_NAME when the process starts. Served as written, the file
was right for a root install and wrong for every subpath one - which is how
production runs.

The endpoint now sets `servers` per request from request.script_root: empty at
root, /shopdb under the subpath method, and correct behind a proxy that sets the
prefix. The paths themselves stay mount-free, because the prefix belongs in the
server entry and duplicating it into each path would double it.

The file is about a megabyte, so it is parsed once and cached by mtime, and each
request gets a shallow copy with only `servers` replaced - two requests arriving
on different mounts cannot race on one dict.

A test pins both cases and the fact that paths stay unprefixed. Planting a wrong
server URL fails it.
This commit is contained in:
cproudlock
2026-09-19 12:30:47 -04:00
parent 70f10c6fa0
commit c4d8542888
2 changed files with 72 additions and 2 deletions

View File

@@ -214,3 +214,36 @@ def test_a_vendored_asset_fetches_nothing_from_the_internet(filename):
assert match.startswith('data:'), (
'%s loads %s rather than an inline data: URI'
% (filename, match))
# ---------------------------------------------------------------------------
# The spec has to say where this instance actually lives
# ---------------------------------------------------------------------------
def test_the_spec_declares_the_mount_it_is_served_from(enabled_client):
"""Production runs under /shopdb, and the generated file cannot know that.
gen_openapi.py writes servers: [{"url": "/"}] - correct for a root install
and wrong for every subpath one - and wsgi.py only moves MOUNT_PATH into
SCRIPT_NAME at runtime. Served as written, both renderers built
https://host/api/... for an app mounted at /shopdb: a URL an operator copied
out of the docs page and got a 404 from, and the address Swagger's try-it
fired at.
"""
import json as _json
at_root = _json.loads(
enabled_client.get('/api/docs/openapi.json').get_data(as_text=True))
assert at_root['servers'] == [{'url': '/', 'description': 'this instance'}]
mounted = _json.loads(enabled_client.get(
'/api/docs/openapi.json',
environ_overrides={'SCRIPT_NAME': '/shopdb'}).get_data(as_text=True))
assert mounted['servers'] == [
{'url': '/shopdb', 'description': 'this instance'}]
# The paths themselves stay mount-free: the server prefix is what varies,
# and duplicating it into every path would double it under a subpath.
assert '/api/apitokens' in mounted['paths']
assert '/shopdb/api/apitokens' not in mounted['paths']