Files
shopdb-flask/tests/test_api_inventory_parity.py
cproudlock ad335cfc9e api: document the twenty-two routes that were serving traffic in silence
The inventory is hand-maintained, and should stay that way - its value is the
prose, and what an endpoint is FOR cannot be derived from the code. An audit of
all 372 documented operations found zero phantom routes and zero wrong parameter
names, so the maintenance is in good order.

What hand-maintenance cannot do is notice a route somebody added. Twenty-two
were undocumented: the entire backups plugin surface, every one of the dashboard
card endpoints added with contract 0.19.0, the GE-Enforce publish preflight, the
employee SSO resolver, the protocol update verbs, and the four /api/docs routes -
so the spec did not describe how to fetch the spec.

Coverage is now a test. It walks the live url_map and fails when a served route
has no entry, which means adding an endpoint includes describing it, in the same
commit, while the author still knows what it is for. The reverse direction is
checked too: a documented route that no longer exists sends a reader to a 404.

Writing that test found one more thing. The inventory writes multi-verb routes
as "PUT|PATCH", and neither the parity check nor the generator split on the pipe
- so those operations were absent from the published spec entirely, with nothing
reporting it. The spec now carries all 394 operations the code serves, which is
the first time the two numbers have matched.

The generator's own docstring claimed the inventory could be regenerated. It
cannot; nothing generates it. That sentence is why nobody noticed it was falling
behind.
2026-08-14 16:09:47 -04:00

96 lines
3.6 KiB
Python

"""Every route the app serves is described in docs/api-inventory.json.
The inventory is hand-maintained, and deliberately so: its value is the prose -
what each endpoint is FOR, what it accepts, a worked example - and none of that
can be derived from the code. An audit of all 372 documented operations found
zero phantom routes and zero wrong parameter names, so the maintenance itself is
in good order.
What it cannot do is notice a route somebody added. Twenty-two were serving
traffic undocumented, including an entire plugin's surface and every one of the
dashboard card endpoints - each of them added after its surface was written.
So the prose stays hand-written and the COVERAGE is enforced here. Adding an
endpoint now means describing it, in the same commit, which is when the author
still knows what it is for.
"""
import json
import re
from pathlib import Path
import pytest
from shopdb import create_app
REPO = Path(__file__).resolve().parents[1]
INVENTORY = REPO / 'docs' / 'api-inventory.json'
pytestmark = pytest.mark.skipif(
not INVENTORY.is_file(),
reason='docs/ is excluded from publication; nothing to check here')
# Flask's own machinery, not this product's API.
EXEMPT_ENDPOINTS = {'static'}
# Documented as a family rather than per-file: these serve the frontend bundle
# and its assets, not JSON.
EXEMPT_RULE_PREFIXES = ('/static', '/assets')
def normalise(rule):
"""Flask `<int:id>` and inventory `<id>` describe the same path.
Converters are an implementation detail of the route, and the inventory was
written without them. Comparing on the converter would report a difference
that is not one.
"""
return re.sub(r'<(?:[^:>]+:)?([^>]+)>', r'<\1>', rule)
def served_operations():
app = create_app('testing')
served = set()
for rule in app.url_map.iter_rules():
if rule.endpoint in EXEMPT_ENDPOINTS:
continue
if any(rule.rule.startswith(prefix) for prefix in EXEMPT_RULE_PREFIXES):
continue
if not rule.rule.startswith('/api'):
continue
for method in sorted((rule.methods or set()) - {'HEAD', 'OPTIONS'}):
served.add((method, normalise(rule.rule)))
return served
def documented_operations():
documented = set()
for surface in json.loads(INVENTORY.read_text()):
for entry in surface.get('endpoints', []):
path = normalise(entry.get('path') or '')
# The inventory writes multi-verb routes as 'PUT|PATCH'. Splitting
# on comma and slash alone left those undocumented in this check
# AND dropped from the generated spec entirely.
for method in re.split(r'[/,|]', entry.get('method') or 'GET'):
method = method.strip().upper()
if method:
documented.add((method, path))
return documented
def test_every_served_route_is_documented():
missing = sorted(served_operations() - documented_operations())
assert not missing, (
'%d route(s) serve traffic with no entry in docs/api-inventory.json. '
'Describe them there (purpose, auth, params, example) and regenerate '
'the spec with scripts/gen_openapi.py:\n %s'
% (len(missing), '\n '.join('%s %s' % row for row in missing)))
def test_nothing_documented_has_been_removed():
"""A documented route that no longer exists sends a reader to a 404."""
stale = sorted(documented_operations() - served_operations())
assert not stale, (
'%d documented route(s) are no longer served:\n %s'
% (len(stale), '\n '.join('%s %s' % row for row in stale)))