Ensure registry API implements the reference openapi schema (#568)
<!-- Provide a brief summary of your changes --> ## Motivation and Context <!-- Why is this change needed? What problem does it solve? --> The following PR adds a simple unit test to ensure the served API handlers are a superset of the reference OpenAPI schema. ## How Has This Been Tested? <!-- Have you tested this in a real application? Which scenarios were tested? --> ## Breaking Changes <!-- Will users need to update their code or configurations? --> ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context <!-- Add any other context, implementation notes, or design decisions --> Fixes: https://github.com/modelcontextprotocol/registry/issues/567 --------- Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
This commit is contained in:
parent
5a584b86f4
commit
3762542ddc
1 changed files with 113 additions and 0 deletions
113
internal/api/openapi_compliance_test.go
Normal file
113
internal/api/openapi_compliance_test.go
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
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"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
// Register V0 routes exactly like production does
|
||||
router.RegisterV0Routes(api, cfg, nil, nil) // nil service and metrics for schema testing
|
||||
|
||||
// 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
|
||||
}
|
||||
Loading…
Reference in a new issue