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 }