Files
summercms/cabana/phase09_contract_test.go
Jakub Zych 5f9353841b feat(10-01): serve the embedded admin SPA at backend.uri with cookie login
- backend.uri prefix (default /backend) mounts the admin API at {prefix}/api/v1
  and the embedded SPA shell at {prefix} with an api/ JSON 404 fallback
- cookie transport: an X-Requested-With login sets the HttpOnly summer_admin
  cookie and returns no token; the backend guard reads the cookie after Bearer
- CSRF wrapper refuses cookie-only POST/PUT/DELETE without X-Requested-With
- boardwalk package embeds boardwalk/dist, rewrites index.html once per prefix
  and sets cache and security headers
- framework admin OpenAPI pipeline (swag, swagger2openapi, openapi-typescript)
  with prefix-relative paths and typed envelopes for the tracer routes
- admin/ Vite SPA: login, plugin rail, section panel and read-only list
  through the openapi-fetch client typed by the generated schema
2026-09-27 15:21:48 +02:00

118 lines
2.9 KiB
Go

package cabana
import (
"encoding/json"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)
// TestPhase09ContractInventory fails when the committed framework admin
// OpenAPI document (admin/openapi/admin.json, D-15) drops an admin API route
// or a protected route's 401 response. Paths are prefix-relative (D-03).
func TestPhase09ContractInventory(t *testing.T) {
_, file, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("caller")
}
specPath := filepath.Clean(filepath.Join(filepath.Dir(file), "..", "admin", "openapi", "admin.json"))
raw, err := os.ReadFile(specPath)
if err != nil {
t.Fatalf("read %s: %v", specPath, err)
}
var spec struct {
Paths map[string]map[string]struct {
Responses map[string]json.RawMessage `json:"responses"`
Security []map[string]json.RawMessage `json:"security"`
} `json:"paths"`
Components struct {
SecuritySchemes map[string]json.RawMessage `json:"securitySchemes"`
} `json:"components"`
}
if err := json.Unmarshal(raw, &spec); err != nil {
t.Fatal(err)
}
if _, ok := spec.Components.SecuritySchemes["BackendBearer"]; !ok {
t.Fatal("openapi is missing the BackendBearer scheme")
}
public := map[string]bool{
"POST /auth/login": true,
"POST /auth/refresh": true,
}
seen := map[string]bool{}
apiRoutes := 0
for _, route := range phase09Routes {
if route.spa {
continue
}
apiRoutes++
method, path, ok := splitRoute(route.key)
if !ok {
t.Fatalf("bad route key %s", route.key)
}
method = strings.ToLower(method)
ops, ok := spec.Paths[path]
if !ok {
t.Fatalf("openapi missing %s", path)
}
op, ok := ops[method]
if !ok {
t.Fatalf("openapi missing %s %s", method, path)
}
key := route.key
if seen[key] {
t.Fatalf("duplicate contract route %s", key)
}
seen[key] = true
if _, ok := op.Responses["200"]; !ok {
t.Fatalf("%s has no 200 response", key)
}
if public[key] {
if _, ok := op.Responses["401"]; !ok {
t.Fatalf("%s has no 401 response", key)
}
continue
}
if len(op.Security) == 0 {
t.Fatalf("%s has no BackendBearer security requirement", key)
}
if _, ok := op.Responses["401"]; !ok {
t.Fatalf("%s has no 401 response", key)
}
}
if len(seen) != apiRoutes {
t.Fatalf("contract routes=%d want %d", len(seen), apiRoutes)
}
if len(spec.Paths) != len(pathsOf(phase09Routes)) {
t.Fatalf("openapi lists %d paths, the mounted API has %d", len(spec.Paths), len(pathsOf(phase09Routes)))
}
}
func splitRoute(key string) (method, path string, ok bool) {
for i := 0; i < len(key); i++ {
if key[i] == ' ' {
return key[:i], key[i+1:], key[i+1:] != ""
}
}
return "", "", false
}
func pathsOf(routes []struct {
key string
public bool
spa bool
}) map[string]bool {
out := map[string]bool{}
for _, route := range routes {
if route.spa {
continue
}
if _, path, ok := splitRoute(route.key); ok {
out[path] = true
}
}
return out
}