Show the API reference with Redoc
Replace the hand-built reference page with Redoc rendering the generated OpenAPI document. The bundle is fetched by package.nix (pinned hash) and served from the UI's own address; only that page gets a style nonce and worker-src blob: in its CSP. The spec now explains authentication, saved-not-applied, ETags (If-Match, 409) and errors, documents the ETag header, gives each operation a curl sample, and no longer names MCP tools. Area GETs no longer emit "parameters": null, which Redoc could not render. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeFqV9dZrK6nipHkPfyM4F
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -247,12 +248,37 @@ func TestAPIReference(t *testing.T) {
|
||||
if rec.Code != 200 {
|
||||
t.Fatalf("reference: %d", rec.Code)
|
||||
}
|
||||
for _, rt := range apiRoutes {
|
||||
if !strings.Contains(body, rt.path) {
|
||||
t.Errorf("%s is not on the page", rt.path)
|
||||
}
|
||||
csp := rec.Header().Get("Content-Security-Policy")
|
||||
m := regexp.MustCompile(`data-nonce="([^"]+)"`).FindStringSubmatch(body)
|
||||
if m == nil || !strings.Contains(csp, "'nonce-"+m[1]+"'") {
|
||||
t.Errorf("the page's nonce is not in its CSP: %q", csp)
|
||||
}
|
||||
if !strings.Contains(body, "curl -sk") || !strings.Contains(body, "If-Match") || !strings.Contains(body, "unit=router-pppoe") {
|
||||
t.Errorf("examples missing")
|
||||
if strings.Contains(csp, "unsafe-inline") || strings.Contains(csp, "unsafe-eval") {
|
||||
t.Errorf("CSP too loose: %q", csp)
|
||||
}
|
||||
if !strings.Contains(body, `data-spec="/api/openapi.json"`) || !strings.Contains(body, "/static/redoc.standalone.js") {
|
||||
t.Errorf("the page does not load Redoc with the spec:\n%s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpecExplainsETags(t *testing.T) {
|
||||
spec := openAPISpec("1.2.3", nil, "router.example")
|
||||
info := spec["info"].(map[string]any)["description"].(string)
|
||||
for _, want := range []string{"ETag", "If-Match", "409"} {
|
||||
if !strings.Contains(info, want) {
|
||||
t.Errorf("%q is not explained in the spec's description", want)
|
||||
}
|
||||
}
|
||||
patch := spec["paths"].(map[string]any)["/api/v1/settings"].(map[string]any)["patch"].(map[string]any)
|
||||
if strings.Contains(patch["description"].(string), "update_settings") || strings.Contains(patch["description"].(string), "check_settings") {
|
||||
t.Errorf("REST description names an MCP tool: %v", patch["description"])
|
||||
}
|
||||
src := patch["x-codeSamples"].([]any)[0].(map[string]any)["source"].(string)
|
||||
if !strings.Contains(src, "https://router.example/api/v1/settings") || !strings.Contains(src, "If-Match") {
|
||||
t.Errorf("curl sample: %s", src)
|
||||
}
|
||||
get := spec["paths"].(map[string]any)["/api/v1/settings"].(map[string]any)["get"].(map[string]any)
|
||||
if _, ok := get["responses"].(map[string]any)["200"].(map[string]any)["headers"].(map[string]any)["ETag"]; !ok {
|
||||
t.Errorf("GET settings does not document its ETag header")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user