API.md ships in the image; GET /api/docs/approved serves it behind api:docs

Moved to app/API.md so the build context carries it. The console renders
it at /leaders/api. tests/smoke_admin.py 162 -> 164.
This commit is contained in:
2026-09-04 20:26:45 -04:00
parent 38bedaea88
commit d1dfa6df90
4 changed files with 22 additions and 3 deletions
View File
+15 -1
View File
@@ -45,7 +45,7 @@ import os
import re import re
from fastapi import APIRouter, Body, Header, HTTPException, Query, Request from fastapi import APIRouter, Body, Header, HTTPException, Query, Request
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse, PlainTextResponse
import auth import auth
import calendar_write import calendar_write
@@ -511,6 +511,20 @@ def whoami(request: Request, x_admin_token: str = Header(None)):
_CAP_RE = re.compile(r"""_(?:auth|people_actor)\([^)]*?["']([a-z_]+:[a-z_]+)["']""") _CAP_RE = re.compile(r"""_(?:auth|people_actor)\([^)]*?["']([a-z_]+:[a-z_]+)["']""")
@docs_router.get("/docs/approved", response_class=PlainTextResponse)
def api_docs_approved(request: Request):
"""API.md: the approved reference, generated from this registry plus a
full HTTP drive of every route (tests/http_drive.py). Markdown; the
console renders it at /leaders/api. Same gate as /api/docs."""
_auth(request, None, "api:docs")
import pathlib
p = pathlib.Path(__file__).with_name("API.md")
if not p.exists():
raise HTTPException(404, "API.md is not in this build")
return p.read_text()
def route_capability(endpoint): def route_capability(endpoint):
try: try:
src = inspect.getsource(endpoint) src = inspect.getsource(endpoint)
+2 -2
View File
@@ -1,9 +1,9 @@
"""Drive every admin API route over real HTTP. NOT a unit test: it needs a running """Drive every admin API route over real HTTP. NOT a unit test: it needs a running
throwaway site on a COPY of the database (container name sw-test on arrstack_arr_net, throwaway site on a COPY of the database (container name sw-test on arrstack_arr_net,
reachable at the URL in /tmp/apitest/base) and sessions for four accounts in reachable at the URL in /tmp/apitest/base) and sessions for four accounts in
/tmp/apitest/setup.json - see projects/scout-website/state.md for the recipe. /tmp/apitest/setup.json - see projects/scout-website/state.md for the recipe. Output goes to app/API.md so it ships in the image and the console can render it.
Calendar checks write 2036 probes to the REAL Radicale store and delete them. Calendar checks write 2036 probes to the REAL Radicale store and delete them.
Writes results to /tmp/apitest/results.json; API.md is generated from that plus the Writes results to /tmp/apitest/results.json; app/API.md is generated from that plus the
router registry. 123 checks on 2026-09-04, all passing.""" router registry. 123 checks on 2026-09-04, all passing."""
import json, urllib.request, urllib.error, urllib.parse, hashlib, struct, zlib, base64, datetime, sys import json, urllib.request, urllib.error, urllib.parse, hashlib, struct, zlib, base64, datetime, sys
+5
View File
@@ -420,6 +420,11 @@ check("fbposts caps: read for leaders, ingest admin-only and scopable", "fbposts
con = S.connect(); con.execute("DELETE FROM fb_posts"); con.commit(); con.close() con = S.connect(); con.execute("DELETE FROM fb_posts"); con.commit(); con.close()
import os as _os; _os.remove(S.image_path(r)) import os as _os; _os.remove(S.image_path(r))
print("\napproved doc")
import pathlib as _pl
check("API.md ships beside the app", (_pl.Path(A.__file__).with_name("API.md")).exists() and "## How to call it" in _pl.Path(A.__file__).with_name("API.md").read_text())
check("/api/docs/approved is gated like /api/docs", any(r.path == "/api/docs/approved" for r in A.docs_router.routes))
print("\napi docs registry") print("\napi docs registry")
import admin_api as A import admin_api as A
reg = A.describe_routes() reg = A.describe_routes()