<!-- Provide a brief summary of your changes --> ## Motivation and Context <!-- Why is this change needed? What problem does it solve? --> The following PR introduces a `/version` API that serves the build details along with the version of the API server being served. The `/version` endpoint allows users to programmatically query which exact version of the registry is running in production for debugging. ~~auditing, and compatibility checks~~. ## 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 --> Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
121 lines
3.4 KiB
Go
121 lines
3.4 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 routes exactly like production does
|
|
router.RegisterV0Routes(api, cfg, nil, nil, versionInfo) // 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
|
|
}
|