From 1fa005eb8a7aa33f4fc7f6f57052fcc51b931069 Mon Sep 17 00:00:00 2001 From: "benjamin.baker" Date: Sun, 30 Aug 2026 11:59:01 -0400 Subject: [PATCH] Green up Tracking docs/openapi.json (so the app can serve the spec at runtime) makes docs/ exist on the published GitHub mirror, which defeated the skipif(not DOCS.is_dir()) guard in the docs publish/version tests: they then ran against a prose-less checkout and failed on CI. Key the skip off docs/*.md prose instead, matching the canonical published-checkout definition in scripts/check-gates-ran.sh. Also: the OpenAPI generator emitted a trailing-slash path (/api/docs/), which a strict proxy like IIS treats as a redirecting resource a subpath-mounted client cannot follow; normalise trailing slashes in _norm_path and correct the committed spec. And allow docx (python-docx) as an undeclared import: it is used only by scripts/docs/generate_docx.py, a developer tool that never runs on a deployed site. --- docs/openapi.json | 2 +- scripts/gen_openapi.py | 8 +++++++- tests/test_docs_publishable.py | 10 ++++++++-- tests/test_docs_versions.py | 9 +++++++-- tests/test_runtime_dependencies.py | 7 +++++++ 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/docs/openapi.json b/docs/openapi.json index b72ab67c..077980da 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -20109,7 +20109,7 @@ ] } }, - "/api/docs/": { + "/api/docs": { "get": { "tags": [ "core-docs" diff --git a/scripts/gen_openapi.py b/scripts/gen_openapi.py index d86ffa06..c0692c37 100644 --- a/scripts/gen_openapi.py +++ b/scripts/gen_openapi.py @@ -59,7 +59,13 @@ def _product_version(): def _norm_path(p): # Flask / -> OpenAPI {id} - return re.sub(r'<(?:[^:>]+:)?([^>]+)>', r'{\1}', p or '') + normalised = re.sub(r'<(?:[^:>]+:)?([^>]+)>', r'{\1}', p or '') + # A trailing slash in a published path is a distinct resource to a strict + # proxy (IIS), which answers the unslashed URL with a redirect a client + # behind a subpath mount cannot follow. Emit the canonical, slashless form. + if len(normalised) > 1: + normalised = normalised.rstrip('/') + return normalised def _security(auth): diff --git a/tests/test_docs_publishable.py b/tests/test_docs_publishable.py index ddaffeca..2598a99f 100644 --- a/tests/test_docs_publishable.py +++ b/tests/test_docs_publishable.py @@ -28,9 +28,15 @@ DOCS = REPO / 'docs' # test_there_are_docs_to_check exists to catch. Distinguishing the two matters: # collapsing them either breaks CI on the published mirror (this module failed # there for exactly this reason) or quietly disables the guard everywhere. +# +# The test is docs/*.md PROSE, not docs/ existing. docs/openapi.json is tracked +# so the app can serve it at runtime, which means docs/ exists even on the +# published mirror - keying off the directory made this module run there against +# a prose-less tree and fail. A published checkout is one with no authored +# prose, matching the canonical definition in scripts/check-gates-ran.sh. pytestmark = pytest.mark.skipif( - not DOCS.is_dir(), - reason='no docs/ in this checkout - it is excluded from publication and lives in the wiki') + not any(DOCS.glob('*.md')), + reason='no docs/ prose in this checkout - it is excluded from publication and lives in the wiki') # Kept in step with the scrub list in tools/export-github.sh. Two mechanisms for # one rule is not ideal, but the export scrubs a tree it is about to commit while diff --git a/tests/test_docs_versions.py b/tests/test_docs_versions.py index 8b43d858..4b001c01 100644 --- a/tests/test_docs_versions.py +++ b/tests/test_docs_versions.py @@ -23,9 +23,14 @@ import pytest REPO = Path(__file__).resolve().parents[1] DOCS = REPO / 'docs' +# docs/openapi.json is tracked so the app can serve it at runtime, so docs/ +# exists even on the published mirror where the authored prose is not. Keying +# the skip off the directory made this module run on the mirror against a +# prose-less tree and fail. A published checkout is one with no docs/*.md prose, +# matching the canonical definition in scripts/check-gates-ran.sh. pytestmark = pytest.mark.skipif( - not DOCS.is_dir(), - reason='no docs/ in this checkout - it is excluded from publication') + not any(DOCS.glob('*.md')), + reason='no docs/ prose in this checkout - it is excluded from publication') # The map is generated and the changelog records history; both are allowed to # carry literal versions. CONTRACT-STABILITY's table is a history of what each diff --git a/tests/test_runtime_dependencies.py b/tests/test_runtime_dependencies.py index b381f6b2..061df171 100644 --- a/tests/test_runtime_dependencies.py +++ b/tests/test_runtime_dependencies.py @@ -38,6 +38,13 @@ ALLOWED_UNDECLARED = { 'pip', 'setuptools', 'pkg_resources', + # python-docx, used ONLY by scripts/docs/generate_docx.py to turn local + # authored Markdown into DOCX deliverables in exports/. That script is a + # developer convenience: the app never imports it, no deployed site runs it, + # and the Markdown it reads is itself gitignored. Declaring it in the + # production requirements would ship a doc-generation library to every + # customer venv for no runtime purpose. + 'docx', }