- Resolve CLI and module-local fixtures from modules/ paths - Keep root admin assets reachable from nested package tests
117 lines
3.0 KiB
Go
117 lines
3.0 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
|
|
_, ok200 := op.Responses["200"]
|
|
_, ok201 := op.Responses["201"]
|
|
if !ok200 && !ok201 {
|
|
t.Fatalf("%s has no 200 or 201 response", key)
|
|
}
|
|
if route.public {
|
|
// Login and refresh answer 401; the public string bundle does not.
|
|
if _, ok := op.Responses["401"]; public[key] && !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 []adminRoute) 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
|
|
}
|