67 lines
2.0 KiB
Go
67 lines
2.0 KiB
Go
package apispec_test
|
|
|
|
import (
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
yaml "gopkg.in/yaml.v3"
|
|
|
|
"github.com/aoagents/agent-orchestrator/backend/internal/config"
|
|
"github.com/aoagents/agent-orchestrator/backend/internal/httpd"
|
|
"github.com/aoagents/agent-orchestrator/backend/internal/httpd/apispec"
|
|
)
|
|
|
|
// TestRouteSpecParity asserts the mounted /api/v1 routes and the OpenAPI
|
|
// operations are in 1:1 correspondence — so a route can't be added without
|
|
// spec coverage, and the spec can't describe a route that isn't served.
|
|
func TestRouteSpecParity(t *testing.T) {
|
|
log := slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
router := httpd.NewRouterWithControl(config.Config{}, log, nil, httpd.APIDeps{}, httpd.ControlDeps{})
|
|
|
|
mounted := map[string]bool{}
|
|
err := chi.Walk(router, func(method, route string, _ http.Handler, _ ...func(http.Handler) http.Handler) error {
|
|
if strings.HasPrefix(route, "/api/v1/") && route != "/api/v1/openapi.yaml" {
|
|
mounted[strings.ToUpper(method)+" "+route] = true
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("walk routes: %v", err)
|
|
}
|
|
if len(mounted) == 0 {
|
|
t.Fatal("no /api/v1 routes mounted — router wiring changed?")
|
|
}
|
|
|
|
// Forward: every mounted route resolves to an operation slice.
|
|
for r := range mounted {
|
|
mp := strings.SplitN(r, " ", 2)
|
|
if apispec.Default().Operation(mp[0], mp[1]) == nil {
|
|
t.Errorf("mounted route %s has no OpenAPI operation", r)
|
|
}
|
|
}
|
|
|
|
// Reverse: every spec operation is a mounted route.
|
|
var doc struct {
|
|
Paths map[string]map[string]yaml.Node `yaml:"paths"`
|
|
}
|
|
if err := yaml.Unmarshal(apispec.Default().YAML(), &doc); err != nil {
|
|
t.Fatalf("parse spec: %v", err)
|
|
}
|
|
httpMethods := map[string]bool{"get": true, "post": true, "put": true, "patch": true, "delete": true}
|
|
for path, item := range doc.Paths {
|
|
for method := range item {
|
|
if !httpMethods[method] {
|
|
continue // skip parameters, summary, etc.
|
|
}
|
|
key := strings.ToUpper(method) + " " + path
|
|
if !mounted[key] {
|
|
t.Errorf("spec operation %s has no mounted route", key)
|
|
}
|
|
}
|
|
}
|
|
}
|