registry/internal/api/openapi_compliance_test.go
2025-12-11 09:56:57 +02:00

122 lines
3.5 KiB
Go

package api_test
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"sort"
"testing"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humago"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0"
"github.com/modelcontextprotocol/registry/internal/api/router"
"github.com/modelcontextprotocol/registry/internal/config"
)
// OpenAPISpec represents the minimal structure we need to compare paths
type OpenAPISpec struct {
Paths map[string]interface{} `yaml:"paths"`
}
func TestOpenAPIEndpointCompliance(t *testing.T) {
// Load reference schema from docs
referenceSchemaPath := filepath.Join("..", "..", "docs", "reference", "api", "openapi.yaml")
referenceData, err := os.ReadFile(referenceSchemaPath)
require.NoError(t, err, "Failed to read reference OpenAPI schema at %s", referenceSchemaPath)
var referenceSpec OpenAPISpec
err = yaml.Unmarshal(referenceData, &referenceSpec)
require.NoError(t, err, "Failed to parse reference OpenAPI schema")
// Create test API using the same pattern as other tests
mux := http.NewServeMux()
api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0"))
// Create minimal config for testing
cfg := &config.Config{
JWTPrivateKey: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", // 32-byte hex key
}
// Create version info for testing
versionInfo := &v0.VersionBody{
Version: "test",
GitCommit: "test",
BuildTime: "test",
}
// Register V0 and V0.1 routes exactly like production does
router.RegisterV0Routes(api, cfg, nil, nil, versionInfo) // nil service and metrics for schema testing
router.RegisterV0_1Routes(api, cfg, nil, nil, versionInfo) // Register v0.1 routes for compliance
// Get the OpenAPI schema
req := httptest.NewRequest(http.MethodGet, "/openapi.yaml", nil)
w := httptest.NewRecorder()
mux.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code, "OpenAPI endpoint should return 200")
var servedSpec OpenAPISpec
err = yaml.Unmarshal(w.Body.Bytes(), &servedSpec)
require.NoError(t, err, "Failed to parse served OpenAPI schema")
// Extract and sort paths for comparison
referencePaths := extractAndSortPaths(referenceSpec.Paths)
servedPaths := extractAndSortPaths(servedSpec.Paths)
// Find missing paths
missing := []string{}
for _, refPath := range referencePaths {
found := false
for _, servedPath := range servedPaths {
if refPath == servedPath {
found = true
break
}
}
if !found {
missing = append(missing, refPath)
}
}
// Assert all reference paths are implemented
assert.Empty(t, missing, "All reference endpoints should be implemented. Missing: %v", missing)
// Log success and additional endpoints for visibility
if len(missing) == 0 {
t.Logf("✅ All %d reference endpoints are implemented", len(referencePaths))
// Count extra endpoints
extra := []string{}
for _, servedPath := range servedPaths {
found := false
for _, refPath := range referencePaths {
if servedPath == refPath {
found = true
break
}
}
if !found {
extra = append(extra, servedPath)
}
}
if len(extra) > 0 {
t.Logf("+ %d additional endpoints in served API: %v", len(extra), extra)
}
}
}
func extractAndSortPaths(paths map[string]interface{}) []string {
keys := make([]string, 0, len(paths))
for path := range paths {
keys = append(keys, path)
}
sort.Strings(keys)
return keys
}