Database implementation cleanup (#335)
## Motivation and Context The database logic was getting a bit unwieldy and fragile - especially because business logic that should really have lived at the registry service level was being pushed into the persistence level. This PR tightens the interface, simplifies the code there, as well as rips out a bunch of mocks etc. we don't need. ## How Has This Been Tested? Locally everything works ## Breaking Changes The list endpoint was surprisingly always filtering for isLatest only, which seems like a bug? I have removed this filter.
This commit is contained in:
parent
07d353ba92
commit
9d9a02c57f
5 changed files with 669 additions and 1280 deletions
|
|
@ -3,7 +3,6 @@ package v0_test
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -11,29 +10,96 @@ import (
|
|||
"github.com/danielgtaylor/huma/v2"
|
||||
"github.com/danielgtaylor/huma/v2/adapters/humago"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0"
|
||||
"github.com/modelcontextprotocol/registry/internal/auth"
|
||||
"github.com/modelcontextprotocol/registry/internal/config"
|
||||
"github.com/modelcontextprotocol/registry/internal/database"
|
||||
"github.com/modelcontextprotocol/registry/internal/service"
|
||||
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
|
||||
"github.com/modelcontextprotocol/registry/pkg/model"
|
||||
)
|
||||
|
||||
func TestEditServerEndpoint(t *testing.T) {
|
||||
// Create registry service and insert a common test server
|
||||
registryService := service.NewRegistryServiceWithDB(database.NewMemoryDB())
|
||||
|
||||
// Publish a test server that will be used across test cases
|
||||
testServer := apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Original test server",
|
||||
Status: model.StatusActive,
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/domdomegg/test-server",
|
||||
Source: "github",
|
||||
ID: "domdomegg/test-server",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.0.0",
|
||||
},
|
||||
}
|
||||
published, err := registryService.Publish(testServer)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, published)
|
||||
assert.NotNil(t, published.Meta)
|
||||
assert.NotNil(t, published.Meta.IOModelContextProtocolRegistry)
|
||||
|
||||
testServerID := published.Meta.IOModelContextProtocolRegistry.ID
|
||||
|
||||
// Publish a second server for permission testing
|
||||
otherServer := apiv0.ServerJSON{
|
||||
Name: "io.github.other/test-server",
|
||||
Description: "Other test server",
|
||||
Status: model.StatusActive,
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/other/test-server",
|
||||
Source: "github",
|
||||
ID: "other/test-server",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.0.0",
|
||||
},
|
||||
}
|
||||
otherPublished, err := registryService.Publish(otherServer)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, otherPublished)
|
||||
assert.NotNil(t, otherPublished.Meta)
|
||||
assert.NotNil(t, otherPublished.Meta.IOModelContextProtocolRegistry)
|
||||
|
||||
otherServerID := otherPublished.Meta.IOModelContextProtocolRegistry.ID
|
||||
|
||||
// Publish a deleted server for undelete testing
|
||||
deletedServer := apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/deleted-server",
|
||||
Description: "Deleted test server",
|
||||
Status: model.StatusDeleted,
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/domdomegg/deleted-server",
|
||||
Source: "github",
|
||||
ID: "domdomegg/deleted-server",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.0.0",
|
||||
},
|
||||
}
|
||||
deletedPublished, err := registryService.Publish(deletedServer)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, deletedPublished)
|
||||
assert.NotNil(t, deletedPublished.Meta)
|
||||
assert.NotNil(t, deletedPublished.Meta.IOModelContextProtocolRegistry)
|
||||
|
||||
deletedServerID := deletedPublished.Meta.IOModelContextProtocolRegistry.ID
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
serverID string
|
||||
authHeader string
|
||||
requestBody interface{}
|
||||
setupMocks func(*MockRegistryService)
|
||||
serverID string
|
||||
expectedStatus int
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "successful edit with valid token and permissions",
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
name: "successful edit with valid token and permissions",
|
||||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
|
|
@ -58,62 +124,43 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
Version: "1.0.1",
|
||||
},
|
||||
},
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
// Current server (not deleted)
|
||||
currentServer := &apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Original server",
|
||||
Status: model.StatusActive,
|
||||
}
|
||||
registry.On("GetByID", "550e8400-e29b-41d4-a716-446655440001").Return(currentServer, nil)
|
||||
|
||||
expectedResponse := &apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Updated test server",
|
||||
Status: model.StatusDeprecated,
|
||||
}
|
||||
registry.On("EditServer", "550e8400-e29b-41d4-a716-446655440001", mock.AnythingOfType("v0.ServerJSON")).Return(expectedResponse, nil)
|
||||
},
|
||||
serverID: testServerID,
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "missing authorization header",
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
authHeader: "",
|
||||
requestBody: apiv0.ServerJSON{},
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
serverID: testServerID,
|
||||
expectedStatus: 422,
|
||||
expectedError: "required header parameter is missing",
|
||||
},
|
||||
{
|
||||
name: "invalid authorization header format",
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
authHeader: "InvalidFormat token123",
|
||||
requestBody: apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Test server",
|
||||
VersionDetail: model.VersionDetail{Version: "1.0.0"},
|
||||
},
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
expectedError: "Unauthorized",
|
||||
serverID: testServerID,
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
expectedError: "Unauthorized",
|
||||
},
|
||||
{
|
||||
name: "invalid token",
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
authHeader: "Bearer invalid-token",
|
||||
requestBody: apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Test server",
|
||||
VersionDetail: model.VersionDetail{Version: "1.0.0"},
|
||||
},
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
expectedError: "Unauthorized",
|
||||
serverID: testServerID,
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
expectedError: "Unauthorized",
|
||||
},
|
||||
{
|
||||
name: "permission denied - no edit permissions",
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
|
|
@ -126,24 +173,16 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
return "Bearer " + token
|
||||
}(),
|
||||
requestBody: apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Updated test server",
|
||||
},
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
// Need to mock GetByID since we check permissions against existing server name
|
||||
currentServer := &apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Original server",
|
||||
Status: model.StatusActive,
|
||||
}
|
||||
registry.On("GetByID", "550e8400-e29b-41d4-a716-446655440001").Return(currentServer, nil)
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Updated test server",
|
||||
VersionDetail: model.VersionDetail{Version: "1.0.0"},
|
||||
},
|
||||
serverID: testServerID,
|
||||
expectedStatus: http.StatusForbidden,
|
||||
expectedError: "Forbidden",
|
||||
},
|
||||
{
|
||||
name: "permission denied - wrong resource",
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
|
|
@ -156,25 +195,16 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
return "Bearer " + token
|
||||
}(),
|
||||
requestBody: apiv0.ServerJSON{
|
||||
Name: "io.github.other/test-server",
|
||||
Description: "Updated test server",
|
||||
},
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
// Need to mock GetByID since we check permissions against existing server name
|
||||
// This test case shows a different scenario: existing server is "other" but user only has perms for "domdomegg"
|
||||
currentServer := &apiv0.ServerJSON{
|
||||
Name: "io.github.other/test-server",
|
||||
Description: "Original server",
|
||||
Status: model.StatusActive,
|
||||
}
|
||||
registry.On("GetByID", "550e8400-e29b-41d4-a716-446655440001").Return(currentServer, nil)
|
||||
Name: "io.github.other/test-server",
|
||||
Description: "Updated test server",
|
||||
VersionDetail: model.VersionDetail{Version: "1.0.0"},
|
||||
},
|
||||
serverID: otherServerID,
|
||||
expectedStatus: http.StatusForbidden,
|
||||
expectedError: "Forbidden",
|
||||
},
|
||||
{
|
||||
name: "server not found",
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
|
|
@ -187,49 +217,16 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
return "Bearer " + token
|
||||
}(),
|
||||
requestBody: apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Updated test server",
|
||||
},
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
registry.On("GetByID", "550e8400-e29b-41d4-a716-446655440001").Return(nil, database.ErrNotFound)
|
||||
Name: "io.github.domdomegg/nonexistent-server",
|
||||
Description: "Updated test server",
|
||||
VersionDetail: model.VersionDetail{Version: "1.0.0"},
|
||||
},
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440999", // Non-existent ID
|
||||
expectedStatus: http.StatusNotFound,
|
||||
expectedError: "Not Found",
|
||||
},
|
||||
{
|
||||
name: "service error",
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
AuthMethod: auth.MethodGitHubAT,
|
||||
AuthMethodSubject: "domdomegg",
|
||||
Permissions: []auth.Permission{
|
||||
{Action: auth.PermissionActionEdit, ResourcePattern: "*"},
|
||||
},
|
||||
})
|
||||
return "Bearer " + token
|
||||
}(),
|
||||
requestBody: apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Updated test server",
|
||||
},
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
// Current server (not deleted)
|
||||
currentServer := &apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Original server",
|
||||
Status: model.StatusActive,
|
||||
}
|
||||
registry.On("GetByID", "550e8400-e29b-41d4-a716-446655440001").Return(currentServer, nil)
|
||||
registry.On("EditServer", "550e8400-e29b-41d4-a716-446655440001", mock.AnythingOfType("v0.ServerJSON")).Return(nil, errors.New("database error"))
|
||||
},
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
expectedError: "Bad Request",
|
||||
},
|
||||
{
|
||||
name: "validation error - invalid server name",
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
|
|
@ -246,20 +243,12 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
Description: "Test server",
|
||||
VersionDetail: model.VersionDetail{Version: "1.0.0"},
|
||||
},
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
currentServer := &apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Original server",
|
||||
Status: model.StatusActive,
|
||||
}
|
||||
registry.On("GetByID", "550e8400-e29b-41d4-a716-446655440001").Return(currentServer, nil)
|
||||
},
|
||||
serverID: testServerID,
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
expectedError: "Bad Request",
|
||||
},
|
||||
{
|
||||
name: "cannot undelete server",
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
|
|
@ -272,19 +261,19 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
return "Bearer " + token
|
||||
}(),
|
||||
requestBody: apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Name: "io.github.domdomegg/deleted-server",
|
||||
Description: "Trying to undelete server",
|
||||
Status: model.StatusActive,
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/domdomegg/deleted-server",
|
||||
Source: "github",
|
||||
ID: "domdomegg/deleted-server",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.0.1",
|
||||
},
|
||||
},
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
// Current server is deleted
|
||||
currentServer := &apiv0.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Original server",
|
||||
Status: model.StatusDeleted,
|
||||
}
|
||||
registry.On("GetByID", "550e8400-e29b-41d4-a716-446655440001").Return(currentServer, nil)
|
||||
},
|
||||
serverID: deletedServerID,
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
expectedError: "Cannot change status of deleted server",
|
||||
},
|
||||
|
|
@ -292,10 +281,6 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Create mock registry
|
||||
mockRegistry := new(MockRegistryService)
|
||||
tc.setupMocks(mockRegistry)
|
||||
|
||||
// Create Huma API
|
||||
mux := http.NewServeMux()
|
||||
humaConfig := huma.DefaultConfig("Test API", "1.0.0")
|
||||
|
|
@ -305,7 +290,7 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
cfg := &config.Config{
|
||||
JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c",
|
||||
}
|
||||
v0.RegisterEditEndpoints(api, mockRegistry, cfg)
|
||||
v0.RegisterEditEndpoints(api, registryService, cfg)
|
||||
|
||||
// Create request body
|
||||
var requestBody []byte
|
||||
|
|
@ -338,7 +323,7 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
assert.Contains(t, w.Body.String(), tc.expectedError)
|
||||
}
|
||||
|
||||
mockRegistry.AssertExpectations(t)
|
||||
// No mock assertions needed with real service
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0"
|
||||
"github.com/modelcontextprotocol/registry/internal/auth"
|
||||
"github.com/modelcontextprotocol/registry/internal/config"
|
||||
"github.com/modelcontextprotocol/registry/internal/database"
|
||||
"github.com/modelcontextprotocol/registry/internal/service"
|
||||
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
|
||||
"github.com/modelcontextprotocol/registry/pkg/model"
|
||||
|
|
@ -36,7 +37,7 @@ func generateIntegrationTestJWTToken(cfg *config.Config, claims auth.JWTClaims)
|
|||
|
||||
func TestPublishIntegration(t *testing.T) {
|
||||
// Setup fake service
|
||||
registryService := service.NewFakeRegistryService()
|
||||
registryService := service.NewRegistryServiceWithDB(database.NewMemoryDB())
|
||||
|
||||
// Create test config with a valid Ed25519 seed
|
||||
testSeed := make([]byte, ed25519.SeedSize)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -17,46 +16,14 @@ import (
|
|||
v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0"
|
||||
"github.com/modelcontextprotocol/registry/internal/auth"
|
||||
"github.com/modelcontextprotocol/registry/internal/config"
|
||||
"github.com/modelcontextprotocol/registry/internal/database"
|
||||
"github.com/modelcontextprotocol/registry/internal/service"
|
||||
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
|
||||
"github.com/modelcontextprotocol/registry/pkg/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// MockRegistryService is a mock implementation of the RegistryService interface
|
||||
type MockRegistryService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockRegistryService) List(cursor string, limit int) ([]apiv0.ServerJSON, string, error) {
|
||||
args := m.Called(cursor, limit)
|
||||
return args.Get(0).([]apiv0.ServerJSON), args.String(1), args.Error(2)
|
||||
}
|
||||
|
||||
func (m *MockRegistryService) GetByID(id string) (*apiv0.ServerJSON, error) {
|
||||
args := m.Called(id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*apiv0.ServerJSON), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockRegistryService) Publish(request apiv0.ServerJSON) (*apiv0.ServerJSON, error) {
|
||||
args := m.Called(request)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*apiv0.ServerJSON), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockRegistryService) EditServer(id string, request apiv0.ServerJSON) (*apiv0.ServerJSON, error) {
|
||||
args := m.Called(id, request)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(*apiv0.ServerJSON), args.Error(1)
|
||||
}
|
||||
|
||||
// Helper function to generate a valid JWT token for testing
|
||||
func generateTestJWTToken(cfg *config.Config, claims auth.JWTClaims) (string, error) {
|
||||
|
|
@ -82,7 +49,7 @@ func TestPublishEndpoint(t *testing.T) {
|
|||
requestBody interface{}
|
||||
tokenClaims *auth.JWTClaims
|
||||
authHeader string
|
||||
setupMocks func(*MockRegistryService)
|
||||
setupRegistryService func(service.RegistryService)
|
||||
expectedStatus int
|
||||
expectedError string
|
||||
}{
|
||||
|
|
@ -107,8 +74,8 @@ func TestPublishEndpoint(t *testing.T) {
|
|||
{Action: auth.PermissionActionPublish, ResourcePattern: "io.github.example/*"},
|
||||
},
|
||||
},
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
registry.On("Publish", mock.AnythingOfType("v0.ServerJSON")).Return(&apiv0.ServerJSON{}, nil)
|
||||
setupRegistryService: func(_ service.RegistryService) {
|
||||
// Empty registry - no setup needed
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
|
|
@ -132,8 +99,8 @@ func TestPublishEndpoint(t *testing.T) {
|
|||
{Action: auth.PermissionActionPublish, ResourcePattern: "example/*"},
|
||||
},
|
||||
},
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
registry.On("Publish", mock.AnythingOfType("v0.ServerJSON")).Return(&apiv0.ServerJSON{}, nil)
|
||||
setupRegistryService: func(_ service.RegistryService) {
|
||||
// Empty registry - no setup needed
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
|
|
@ -141,7 +108,9 @@ func TestPublishEndpoint(t *testing.T) {
|
|||
name: "missing authorization header",
|
||||
requestBody: apiv0.ServerJSON{},
|
||||
authHeader: "", // Empty auth header
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
setupRegistryService: func(_ service.RegistryService) {
|
||||
// Empty registry - no setup needed
|
||||
},
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedError: "required header parameter is missing",
|
||||
},
|
||||
|
|
@ -153,7 +122,9 @@ func TestPublishEndpoint(t *testing.T) {
|
|||
VersionDetail: model.VersionDetail{Version: "1.0.0"},
|
||||
},
|
||||
authHeader: "InvalidFormat",
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
setupRegistryService: func(_ service.RegistryService) {
|
||||
// Empty registry - no setup needed
|
||||
},
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
expectedError: "Invalid Authorization header format",
|
||||
},
|
||||
|
|
@ -167,7 +138,9 @@ func TestPublishEndpoint(t *testing.T) {
|
|||
},
|
||||
},
|
||||
authHeader: "Bearer invalidToken",
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
setupRegistryService: func(_ service.RegistryService) {
|
||||
// Empty registry - no setup needed
|
||||
},
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
expectedError: "Invalid or expired Registry JWT token",
|
||||
},
|
||||
|
|
@ -191,7 +164,9 @@ func TestPublishEndpoint(t *testing.T) {
|
|||
{Action: auth.PermissionActionPublish, ResourcePattern: "io.github.example/*"},
|
||||
},
|
||||
},
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
setupRegistryService: func(_ service.RegistryService) {
|
||||
// Empty registry - no setup needed
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
expectedError: "You do not have permission to publish this server",
|
||||
},
|
||||
|
|
@ -215,28 +190,41 @@ func TestPublishEndpoint(t *testing.T) {
|
|||
{Action: auth.PermissionActionPublish, ResourcePattern: "*"},
|
||||
},
|
||||
},
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
registry.On("Publish", mock.AnythingOfType("v0.ServerJSON")).Return(nil, errors.New("cannot publish duplicate version"))
|
||||
setupRegistryService: func(registry service.RegistryService) {
|
||||
// Pre-publish the same server to cause duplicate version error
|
||||
existingServer := apiv0.ServerJSON{
|
||||
Name: "example/test-server",
|
||||
Description: "Existing test server",
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/test-server-existing",
|
||||
Source: "github",
|
||||
ID: "example/test-server-existing",
|
||||
},
|
||||
}
|
||||
_, _ = registry.Publish(existingServer)
|
||||
},
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
expectedError: "Failed to publish server",
|
||||
expectedError: "invalid version: cannot publish duplicate version",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Create mocks
|
||||
mockRegistry := new(MockRegistryService)
|
||||
|
||||
// Setup mocks
|
||||
tc.setupMocks(mockRegistry)
|
||||
// Create registry service
|
||||
registryService := service.NewRegistryServiceWithDB(database.NewMemoryDB())
|
||||
|
||||
// Setup registry service
|
||||
tc.setupRegistryService(registryService)
|
||||
|
||||
// Create a new ServeMux and Huma API
|
||||
mux := http.NewServeMux()
|
||||
api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0"))
|
||||
|
||||
// Register the endpoint with test config
|
||||
v0.RegisterPublishEndpoint(api, mockRegistry, testConfig)
|
||||
v0.RegisterPublishEndpoint(api, registryService, testConfig)
|
||||
|
||||
// Prepare request body
|
||||
var requestBody []byte
|
||||
|
|
@ -272,8 +260,7 @@ func TestPublishEndpoint(t *testing.T) {
|
|||
assert.Contains(t, rr.Body.String(), tc.expectedError)
|
||||
}
|
||||
|
||||
// Verify mock expectations
|
||||
mockRegistry.AssertExpectations(t)
|
||||
// No mock expectations to verify
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package v0_test
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -12,67 +11,29 @@ import (
|
|||
"github.com/danielgtaylor/huma/v2/adapters/humago"
|
||||
"github.com/google/uuid"
|
||||
v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0"
|
||||
"github.com/modelcontextprotocol/registry/internal/database"
|
||||
"github.com/modelcontextprotocol/registry/internal/service"
|
||||
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
|
||||
"github.com/modelcontextprotocol/registry/pkg/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func TestServersListEndpoint(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
queryParams string
|
||||
setupMocks func(*MockRegistryService)
|
||||
expectedStatus int
|
||||
expectedServers []apiv0.ServerJSON
|
||||
expectedMeta *v0.Metadata
|
||||
expectedError string
|
||||
name string
|
||||
queryParams string
|
||||
setupRegistryService func(service.RegistryService)
|
||||
expectedStatus int
|
||||
expectedServers []apiv0.ServerJSON
|
||||
expectedMeta *v0.Metadata
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "successful list with default parameters",
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
servers := []apiv0.ServerJSON{
|
||||
{
|
||||
Name: "test-server-1",
|
||||
Description: "First test server",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/test-server-1",
|
||||
Source: "github",
|
||||
ID: "example/test-server-1",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Meta: &apiv0.ServerMeta{
|
||||
IOModelContextProtocolRegistry: &apiv0.RegistryExtensions{
|
||||
ID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test-server-2",
|
||||
Description: "Second test server",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/test-server-2",
|
||||
Source: "github",
|
||||
ID: "example/test-server-2",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "2.0.0",
|
||||
},
|
||||
Meta: &apiv0.ServerMeta{
|
||||
IOModelContextProtocolRegistry: &apiv0.RegistryExtensions{
|
||||
ID: "550e8400-e29b-41d4-a716-446655440002",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
registry.Mock.On("List", "", 30).Return(servers, "", nil)
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedServers: []apiv0.ServerJSON{
|
||||
{
|
||||
Name: "test-server-1",
|
||||
setupRegistryService: func(registry service.RegistryService) {
|
||||
// Publish test servers
|
||||
server1 := apiv0.ServerJSON{
|
||||
Name: "com.example/test-server-1",
|
||||
Description: "First test server",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/test-server-1",
|
||||
|
|
@ -82,14 +43,9 @@ func TestServersListEndpoint(t *testing.T) {
|
|||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Meta: &apiv0.ServerMeta{
|
||||
IOModelContextProtocolRegistry: &apiv0.RegistryExtensions{
|
||||
ID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "test-server-2",
|
||||
}
|
||||
server2 := apiv0.ServerJSON{
|
||||
Name: "com.example/test-server-2",
|
||||
Description: "Second test server",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/test-server-2",
|
||||
|
|
@ -99,44 +55,19 @@ func TestServersListEndpoint(t *testing.T) {
|
|||
VersionDetail: model.VersionDetail{
|
||||
Version: "2.0.0",
|
||||
},
|
||||
Meta: &apiv0.ServerMeta{
|
||||
IOModelContextProtocolRegistry: &apiv0.RegistryExtensions{
|
||||
ID: "550e8400-e29b-41d4-a716-446655440002",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, _ = registry.Publish(server1)
|
||||
_, _ = registry.Publish(server2)
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedServers: nil, // Will be verified differently since IDs are dynamic
|
||||
},
|
||||
{
|
||||
name: "successful list with cursor and limit",
|
||||
queryParams: "?cursor=550e8400-e29b-41d4-a716-446655440000&limit=10",
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
servers := []apiv0.ServerJSON{
|
||||
{
|
||||
Name: "test-server-3",
|
||||
Description: "Third test server",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/test-server-3",
|
||||
Source: "github",
|
||||
ID: "example/test-server-3",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.5.0",
|
||||
},
|
||||
Meta: &apiv0.ServerMeta{
|
||||
IOModelContextProtocolRegistry: &apiv0.RegistryExtensions{
|
||||
ID: "550e8400-e29b-41d4-a716-446655440003",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
nextCursor := uuid.New().String()
|
||||
registry.Mock.On("List", mock.AnythingOfType("string"), 10).Return(servers, nextCursor, nil)
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedServers: []apiv0.ServerJSON{
|
||||
{
|
||||
Name: "test-server-3",
|
||||
queryParams: "?limit=10",
|
||||
setupRegistryService: func(registry service.RegistryService) {
|
||||
server := apiv0.ServerJSON{
|
||||
Name: "com.example/test-server-3",
|
||||
Description: "Third test server",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/test-server-3",
|
||||
|
|
@ -146,75 +77,69 @@ func TestServersListEndpoint(t *testing.T) {
|
|||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.5.0",
|
||||
},
|
||||
Meta: &apiv0.ServerMeta{
|
||||
IOModelContextProtocolRegistry: &apiv0.RegistryExtensions{
|
||||
ID: "550e8400-e29b-41d4-a716-446655440003",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, _ = registry.Publish(server)
|
||||
},
|
||||
expectedMeta: &v0.Metadata{
|
||||
NextCursor: "", // This will be dynamically set in the test
|
||||
Count: 1,
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedServers: nil, // Will be verified differently since IDs are dynamic
|
||||
},
|
||||
{
|
||||
name: "successful list with limit capping at 100",
|
||||
queryParams: "?limit=150",
|
||||
setupRegistryService: func(_ service.RegistryService) {},
|
||||
expectedStatus: http.StatusUnprocessableEntity, // Huma rejects values > maximum
|
||||
expectedError: "validation failed",
|
||||
},
|
||||
{
|
||||
name: "invalid cursor parameter",
|
||||
queryParams: "?cursor=invalid-uuid",
|
||||
setupRegistryService: func(_ service.RegistryService) {},
|
||||
expectedStatus: http.StatusUnprocessableEntity, // Huma returns 422 for validation errors
|
||||
expectedError: "validation failed",
|
||||
},
|
||||
{
|
||||
name: "invalid limit parameter - non-numeric",
|
||||
queryParams: "?limit=abc",
|
||||
setupRegistryService: func(_ service.RegistryService) {},
|
||||
expectedStatus: http.StatusUnprocessableEntity, // Huma returns 422 for validation errors
|
||||
expectedError: "validation failed",
|
||||
},
|
||||
{
|
||||
name: "invalid limit parameter - zero",
|
||||
queryParams: "?limit=0",
|
||||
setupRegistryService: func(_ service.RegistryService) {},
|
||||
expectedStatus: http.StatusUnprocessableEntity, // Huma returns 422 for validation errors
|
||||
expectedError: "validation failed",
|
||||
},
|
||||
{
|
||||
name: "invalid limit parameter - negative",
|
||||
queryParams: "?limit=-5",
|
||||
setupRegistryService: func(_ service.RegistryService) {},
|
||||
expectedStatus: http.StatusUnprocessableEntity, // Huma returns 422 for validation errors
|
||||
expectedError: "validation failed",
|
||||
},
|
||||
{
|
||||
name: "empty registry returns success",
|
||||
setupRegistryService: func(_ service.RegistryService) {
|
||||
// Test empty registry - empty setup
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "successful list with limit capping at 100",
|
||||
queryParams: "?limit=150",
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
expectedStatus: http.StatusUnprocessableEntity, // Huma rejects values > maximum
|
||||
expectedError: "validation failed",
|
||||
},
|
||||
{
|
||||
name: "invalid cursor parameter",
|
||||
queryParams: "?cursor=invalid-uuid",
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
expectedStatus: http.StatusUnprocessableEntity, // Huma returns 422 for validation errors
|
||||
expectedError: "validation failed",
|
||||
},
|
||||
{
|
||||
name: "invalid limit parameter - non-numeric",
|
||||
queryParams: "?limit=abc",
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
expectedStatus: http.StatusUnprocessableEntity, // Huma returns 422 for validation errors
|
||||
expectedError: "validation failed",
|
||||
},
|
||||
{
|
||||
name: "invalid limit parameter - zero",
|
||||
queryParams: "?limit=0",
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
expectedStatus: http.StatusUnprocessableEntity, // Huma returns 422 for validation errors
|
||||
expectedError: "validation failed",
|
||||
},
|
||||
{
|
||||
name: "invalid limit parameter - negative",
|
||||
queryParams: "?limit=-5",
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
expectedStatus: http.StatusUnprocessableEntity, // Huma returns 422 for validation errors
|
||||
expectedError: "validation failed",
|
||||
},
|
||||
{
|
||||
name: "registry service error",
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
registry.Mock.On("List", "", 30).Return([]apiv0.ServerJSON{}, "", errors.New("database connection error"))
|
||||
},
|
||||
expectedStatus: http.StatusInternalServerError,
|
||||
expectedError: "Failed to get registry list",
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedServers: []apiv0.ServerJSON{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Create mock registry service
|
||||
mockRegistry := new(MockRegistryService)
|
||||
tc.setupMocks(mockRegistry)
|
||||
registryService := service.NewRegistryServiceWithDB(database.NewMemoryDB())
|
||||
tc.setupRegistryService(registryService)
|
||||
|
||||
// Create a new test API
|
||||
mux := http.NewServeMux()
|
||||
api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0"))
|
||||
|
||||
// Register the servers endpoints
|
||||
v0.RegisterServersEndpoints(api, mockRegistry)
|
||||
v0.RegisterServersEndpoints(api, registryService)
|
||||
|
||||
// Create request
|
||||
url := "/v0/servers" + tc.queryParams
|
||||
|
|
@ -242,13 +167,26 @@ func TestServersListEndpoint(t *testing.T) {
|
|||
// Check the response data
|
||||
if tc.expectedServers != nil {
|
||||
assert.Equal(t, tc.expectedServers, resp.Servers)
|
||||
} else {
|
||||
// For tests with dynamic data, check structure and count
|
||||
assert.NotEmpty(t, resp.Servers, "Expected at least one server")
|
||||
for _, server := range resp.Servers {
|
||||
assert.NotEmpty(t, server.Name)
|
||||
assert.NotEmpty(t, server.Description)
|
||||
assert.NotNil(t, server.Meta)
|
||||
assert.NotNil(t, server.Meta.IOModelContextProtocolRegistry)
|
||||
assert.NotEmpty(t, server.Meta.IOModelContextProtocolRegistry.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Check metadata if expected
|
||||
if tc.expectedMeta != nil {
|
||||
assert.Equal(t, tc.expectedMeta.Count, resp.Metadata.Count)
|
||||
if tc.expectedMeta.NextCursor != "" {
|
||||
assert.NotEmpty(t, resp.Metadata.NextCursor)
|
||||
assert.NotNil(t, resp.Metadata, "Expected metadata to be present")
|
||||
if resp.Metadata != nil {
|
||||
assert.Equal(t, tc.expectedMeta.Count, resp.Metadata.Count)
|
||||
if tc.expectedMeta.NextCursor != "" {
|
||||
assert.NotEmpty(t, resp.Metadata.NextCursor)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if tc.expectedError != "" {
|
||||
|
|
@ -257,84 +195,58 @@ func TestServersListEndpoint(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify mock expectations
|
||||
mockRegistry.AssertExpectations(t)
|
||||
// No expectations to verify with real service
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServersDetailEndpoint(t *testing.T) {
|
||||
// Create mock registry service
|
||||
registryService := service.NewRegistryServiceWithDB(database.NewMemoryDB())
|
||||
|
||||
testServer, err := registryService.Publish(apiv0.ServerJSON{
|
||||
Name: "com.example/test-server",
|
||||
Description: "A test server",
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.0.0",
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
serverID string
|
||||
setupMocks func(*MockRegistryService, string)
|
||||
expectedStatus int
|
||||
expectedServer *apiv0.ServerJSON
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "successful get server detail",
|
||||
serverID: uuid.New().String(),
|
||||
setupMocks: func(registry *MockRegistryService, serverID string) {
|
||||
serverDetail := &apiv0.ServerJSON{
|
||||
Name: "test-server-detail",
|
||||
Description: "Test server detail",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/test-server-detail",
|
||||
Source: "github",
|
||||
ID: "example/test-server-detail",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "2.0.0",
|
||||
},
|
||||
Meta: &apiv0.ServerMeta{
|
||||
IOModelContextProtocolRegistry: &apiv0.RegistryExtensions{
|
||||
ID: serverID,
|
||||
},
|
||||
},
|
||||
}
|
||||
registry.Mock.On("GetByID", serverID).Return(serverDetail, nil)
|
||||
},
|
||||
name: "successful get server detail",
|
||||
serverID: testServer.Meta.IOModelContextProtocolRegistry.ID,
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "invalid server ID format",
|
||||
serverID: "invalid-uuid",
|
||||
setupMocks: func(_ *MockRegistryService, _ string) {},
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedError: "validation failed",
|
||||
},
|
||||
{
|
||||
name: "server not found",
|
||||
serverID: uuid.New().String(),
|
||||
setupMocks: func(registry *MockRegistryService, serverID string) {
|
||||
registry.Mock.On("GetByID", serverID).Return(nil, errors.New("record not found"))
|
||||
},
|
||||
name: "server not found",
|
||||
serverID: uuid.New().String(),
|
||||
expectedStatus: http.StatusNotFound,
|
||||
expectedError: "Server not found",
|
||||
},
|
||||
{
|
||||
name: "registry service error",
|
||||
serverID: uuid.New().String(),
|
||||
setupMocks: func(registry *MockRegistryService, serverID string) {
|
||||
registry.Mock.On("GetByID", serverID).Return(nil, errors.New("database connection error"))
|
||||
},
|
||||
expectedStatus: http.StatusInternalServerError,
|
||||
expectedError: "Failed to get server details",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Create mock registry service
|
||||
mockRegistry := new(MockRegistryService)
|
||||
tc.setupMocks(mockRegistry, tc.serverID)
|
||||
|
||||
// Create a new test API
|
||||
mux := http.NewServeMux()
|
||||
api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0"))
|
||||
|
||||
// Register the servers endpoints
|
||||
v0.RegisterServersEndpoints(api, mockRegistry)
|
||||
v0.RegisterServersEndpoints(api, registryService)
|
||||
|
||||
// Create request
|
||||
url := "/v0/servers/" + tc.serverID
|
||||
|
|
@ -364,7 +276,7 @@ func TestServersDetailEndpoint(t *testing.T) {
|
|||
}
|
||||
|
||||
// Verify mock expectations
|
||||
mockRegistry.AssertExpectations(t)
|
||||
// No expectations to verify with real service
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -372,42 +284,36 @@ func TestServersDetailEndpoint(t *testing.T) {
|
|||
// TestServersEndpointsIntegration tests the servers endpoints with actual HTTP requests
|
||||
func TestServersEndpointsIntegration(t *testing.T) {
|
||||
// Create mock registry service
|
||||
mockRegistry := new(MockRegistryService)
|
||||
registryService := service.NewRegistryServiceWithDB(database.NewMemoryDB())
|
||||
|
||||
// Test data
|
||||
serverID := uuid.New().String()
|
||||
servers := []apiv0.ServerJSON{
|
||||
{
|
||||
Name: "integration-test-server",
|
||||
Description: "Integration test server",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/integration-test",
|
||||
Source: "github",
|
||||
ID: "example/integration-test",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.0.0",
|
||||
},
|
||||
Meta: &apiv0.ServerMeta{
|
||||
IOModelContextProtocolRegistry: &apiv0.RegistryExtensions{
|
||||
ID: serverID,
|
||||
},
|
||||
},
|
||||
// Test data - publish a server and get its actual ID
|
||||
testServer := apiv0.ServerJSON{
|
||||
Name: "com.example/integration-test-server",
|
||||
Description: "Integration test server",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/integration-test",
|
||||
Source: "github",
|
||||
ID: "example/integration-test",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "1.0.0",
|
||||
},
|
||||
}
|
||||
|
||||
serverDetail := &servers[0]
|
||||
published, err := registryService.Publish(testServer)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, published)
|
||||
|
||||
// Setup mocks
|
||||
mockRegistry.Mock.On("List", "", 30).Return(servers, "", nil)
|
||||
mockRegistry.Mock.On("GetByID", serverID).Return(serverDetail, nil)
|
||||
serverID := published.Meta.IOModelContextProtocolRegistry.ID
|
||||
servers := []apiv0.ServerJSON{*published}
|
||||
serverDetail := published
|
||||
|
||||
// Create a new test API
|
||||
mux := http.NewServeMux()
|
||||
api := humago.New(mux, huma.DefaultConfig("Test API", "1.0.0"))
|
||||
|
||||
// Register the servers endpoints
|
||||
v0.RegisterServersEndpoints(api, mockRegistry)
|
||||
v0.RegisterServersEndpoints(api, registryService)
|
||||
|
||||
// Create test server
|
||||
server := httptest.NewServer(mux)
|
||||
|
|
@ -441,8 +347,14 @@ func TestServersEndpointsIntegration(t *testing.T) {
|
|||
err = json.NewDecoder(resp.Body).Decode(&listResp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check the response data
|
||||
assert.Equal(t, servers, listResp.Servers)
|
||||
// Check the response data (excluding timestamps which will be different)
|
||||
assert.Len(t, listResp.Servers, len(servers))
|
||||
if len(listResp.Servers) > 0 {
|
||||
assert.Equal(t, servers[0].Name, listResp.Servers[0].Name)
|
||||
assert.Equal(t, servers[0].Description, listResp.Servers[0].Description)
|
||||
assert.Equal(t, servers[0].Repository, listResp.Servers[0].Repository)
|
||||
assert.Equal(t, servers[0].VersionDetail, listResp.Servers[0].VersionDetail)
|
||||
}
|
||||
})
|
||||
|
||||
// Test get server detail endpoint
|
||||
|
|
@ -471,10 +383,13 @@ func TestServersEndpointsIntegration(t *testing.T) {
|
|||
err = json.NewDecoder(resp.Body).Decode(&serverDetailResp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check the response data
|
||||
assert.Equal(t, *serverDetail, serverDetailResp)
|
||||
// Check the response data (excluding timestamps which will be different)
|
||||
assert.Equal(t, serverDetail.Name, serverDetailResp.Name)
|
||||
assert.Equal(t, serverDetail.Description, serverDetailResp.Description)
|
||||
assert.Equal(t, serverDetail.Repository, serverDetailResp.Repository)
|
||||
assert.Equal(t, serverDetail.VersionDetail, serverDetailResp.VersionDetail)
|
||||
})
|
||||
|
||||
// Verify mock expectations
|
||||
mockRegistry.AssertExpectations(t)
|
||||
// No expectations to verify with real service
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,43 +8,33 @@ import (
|
|||
|
||||
"github.com/danielgtaylor/huma/v2"
|
||||
"github.com/danielgtaylor/huma/v2/adapters/humago"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0"
|
||||
"github.com/modelcontextprotocol/registry/internal/api/router"
|
||||
"github.com/modelcontextprotocol/registry/internal/config"
|
||||
"github.com/modelcontextprotocol/registry/internal/database"
|
||||
"github.com/modelcontextprotocol/registry/internal/service"
|
||||
"github.com/modelcontextprotocol/registry/internal/telemetry"
|
||||
apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0"
|
||||
"github.com/modelcontextprotocol/registry/pkg/model"
|
||||
)
|
||||
|
||||
func mockServerEndpoint(registry *MockRegistryService, serverID string) {
|
||||
serverDetail := &apiv0.ServerJSON{
|
||||
Name: "test-server-detail",
|
||||
func TestPrometheusHandler(t *testing.T) {
|
||||
registryService := service.NewRegistryServiceWithDB(database.NewMemoryDB())
|
||||
server, err := registryService.Publish(apiv0.ServerJSON{
|
||||
Name: "io.github.example/test-server",
|
||||
Description: "Test server detail",
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/example/test-server-detail",
|
||||
URL: "https://github.com/example/test-server",
|
||||
Source: "github",
|
||||
ID: "example/test-server-detail",
|
||||
ID: "example/test-server",
|
||||
},
|
||||
VersionDetail: model.VersionDetail{
|
||||
Version: "2.0.0",
|
||||
},
|
||||
Meta: &apiv0.ServerMeta{
|
||||
IOModelContextProtocolRegistry: &apiv0.RegistryExtensions{
|
||||
ID: serverID,
|
||||
},
|
||||
},
|
||||
}
|
||||
registry.Mock.On("GetByID", serverID).Return(serverDetail, nil)
|
||||
}
|
||||
|
||||
func TestPrometheusHandler(t *testing.T) {
|
||||
mockRegistry := new(MockRegistryService)
|
||||
|
||||
serverID := uuid.New().String()
|
||||
mockServerEndpoint(mockRegistry, serverID)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
cfg := config.NewConfig()
|
||||
shutdownTelemetry, metrics, _ := telemetry.InitMetrics("dev")
|
||||
|
|
@ -57,13 +47,13 @@ func TestPrometheusHandler(t *testing.T) {
|
|||
router.WithSkipPaths("/health", "/metrics", "/ping", "/docs"),
|
||||
))
|
||||
v0.RegisterHealthEndpoint(api, cfg, metrics)
|
||||
v0.RegisterServersEndpoints(api, mockRegistry)
|
||||
v0.RegisterServersEndpoints(api, registryService)
|
||||
|
||||
// Add /metrics for Prometheus metrics using promhttp
|
||||
mux.Handle("/metrics", metrics.PrometheusHandler())
|
||||
|
||||
// Create request
|
||||
url := "/v0/servers/" + serverID
|
||||
url := "/v0/servers/" + server.Meta.IOModelContextProtocolRegistry.ID
|
||||
req := httptest.NewRequest(http.MethodGet, url, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue