update tests and memory implementation to follow real DB implementations as much as possible. Fix the tests to let the DB generate the server uuids rather than setting them up in the test.

This commit is contained in:
Avinash Sridhar 2025-05-27 10:55:48 -04:00 • committed by Toby Padilla
commit f9ab0bc49b
2 changed files with 118 additions and 125 deletions

View file

@ -8,9 +8,7 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"time"
"github.com/google/uuid"
v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0" v0 "github.com/modelcontextprotocol/registry/internal/api/handlers/v0"
"github.com/modelcontextprotocol/registry/internal/auth" "github.com/modelcontextprotocol/registry/internal/auth"
"github.com/modelcontextprotocol/registry/internal/model" "github.com/modelcontextprotocol/registry/internal/model"
@ -59,53 +57,52 @@ func TestPublishIntegration(t *testing.T) {
handler := v0.PublishHandler(registryService, authService) handler := v0.PublishHandler(registryService, authService)
t.Run("successful publish with GitHub auth", func(t *testing.T) { t.Run("successful publish with GitHub auth", func(t *testing.T) {
serverDetail := &model.ServerDetail{ publishReq := model.PublishRequest{
Server: model.Server{ ServerDetail: model.ServerDetail{
ID: uuid.New().String(), Server: model.Server{
Name: "io.github.testuser/test-mcp-server", Name: "io.github.testuser/test-mcp-server",
Description: "A test MCP server for integration testing", Description: "A test MCP server for integration testing",
Repository: model.Repository{ Repository: model.Repository{
URL: "https://github.com/testuser/test-mcp-server", URL: "https://github.com/testuser/test-mcp-server",
Source: "github", Source: "github",
ID: "testuser/test-mcp-server", ID: "testuser/test-mcp-server",
},
VersionDetail: model.VersionDetail{
Version: "1.0.0",
},
}, },
VersionDetail: model.VersionDetail{ Packages: []model.Package{
Version: "1.0.0", {
ReleaseDate: time.Now().Format(time.RFC3339), RegistryName: "npm",
IsLatest: true, Name: "test-mcp-server",
}, Version: "1.0.0",
}, RunTimeHint: "node",
Packages: []model.Package{ RuntimeArguments: []model.Argument{
{ {
RegistryName: "npm", Type: model.ArgumentTypeNamed,
Name: "test-mcp-server", Name: "config",
Version: "1.0.0", InputWithVariables: model.InputWithVariables{
RunTimeHint: "node", Input: model.Input{
RuntimeArguments: []model.Argument{ Description: "Configuration file path",
{ Format: model.FormatFilePath,
Type: model.ArgumentTypeNamed, IsRequired: true,
Name: "config", },
InputWithVariables: model.InputWithVariables{
Input: model.Input{
Description: "Configuration file path",
Format: model.FormatFilePath,
IsRequired: true,
}, },
}, },
}, },
}, },
}, },
}, Remotes: []model.Remote{
Remotes: []model.Remote{ {
{ TransportType: "http",
TransportType: "http", URL: "http://localhost:3000/mcp",
URL: "http://localhost:3000/mcp", },
}, },
}, },
} }
// Marshal the server detail to JSON // Marshal the server detail to JSON
jsonData, err := json.Marshal(serverDetail) jsonData, err := json.Marshal(publishReq)
require.NoError(t, err) require.NoError(t, err)
// Create a request // Create a request
@ -127,38 +124,37 @@ func TestPublishIntegration(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "Server publication successful", response["message"]) assert.Equal(t, "Server publication successful", response["message"])
assert.Equal(t, serverDetail.ID, response["id"]) assert.NotEmpty(t, response["id"], "Server ID should be generated")
// Verify the server was actually published by retrieving it // Verify the server was actually published by retrieving it
publishedServer, err := registryService.GetByID(serverDetail.ID) publishedServer, err := registryService.GetByID(response["id"])
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, serverDetail.Name, publishedServer.Name) assert.Equal(t, publishReq.ServerDetail.Name, publishedServer.Name)
assert.Equal(t, serverDetail.Description, publishedServer.Description) assert.Equal(t, publishReq.ServerDetail.Description, publishedServer.Description)
assert.Equal(t, serverDetail.VersionDetail.Version, publishedServer.VersionDetail.Version) assert.Equal(t, publishReq.ServerDetail.VersionDetail.Version, publishedServer.VersionDetail.Version)
assert.Len(t, publishedServer.Packages, 1) assert.Len(t, publishedServer.Packages, 1)
assert.Len(t, publishedServer.Remotes, 1) assert.Len(t, publishedServer.Remotes, 1)
}) })
t.Run("successful publish without auth (no prefix)", func(t *testing.T) { t.Run("successful publish without auth (no prefix)", func(t *testing.T) {
serverDetail := &model.ServerDetail{ publishReq := &model.PublishRequest{
Server: model.Server{ ServerDetail: model.ServerDetail{
ID: uuid.New().String(), Server: model.Server{
Name: "custom-mcp-server", Name: "custom-mcp-server",
Description: "A custom MCP server without auth", Description: "A custom MCP server without auth",
Repository: model.Repository{ Repository: model.Repository{
URL: "https://example.com/custom-server", URL: "https://example.com/custom-server",
Source: "custom", Source: "custom",
ID: "custom/custom-server", ID: "custom/custom-server",
}, },
VersionDetail: model.VersionDetail{ VersionDetail: model.VersionDetail{
Version: "2.0.0", Version: "2.0.0",
ReleaseDate: time.Now().Format(time.RFC3339), },
IsLatest: true,
}, },
}, },
} }
jsonData, err := json.Marshal(serverDetail) jsonData, err := json.Marshal(publishReq)
require.NoError(t, err) require.NoError(t, err)
req := httptest.NewRequest(http.MethodPost, "/v0/publish", bytes.NewBuffer(jsonData)) req := httptest.NewRequest(http.MethodPost, "/v0/publish", bytes.NewBuffer(jsonData))
@ -175,24 +171,23 @@ func TestPublishIntegration(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "Server publication successful", response["message"]) assert.Equal(t, "Server publication successful", response["message"])
assert.Equal(t, serverDetail.ID, response["id"]) assert.NotEmpty(t, response["id"], "Server ID should be generated")
}) })
t.Run("publish fails with missing name", func(t *testing.T) { t.Run("publish fails with missing name", func(t *testing.T) {
serverDetail := &model.ServerDetail{ publishReq := &model.PublishRequest{
Server: model.Server{ ServerDetail: model.ServerDetail{
ID: uuid.New().String(), Server: model.Server{
Name: "", // Missing name Name: "", // Missing name
Description: "A test server", Description: "A test server",
VersionDetail: model.VersionDetail{ VersionDetail: model.VersionDetail{
Version: "1.0.0", Version: "1.0.0",
ReleaseDate: time.Now().Format(time.RFC3339), },
IsLatest: true,
}, },
}, },
} }
jsonData, err := json.Marshal(serverDetail) jsonData, err := json.Marshal(publishReq)
require.NoError(t, err) require.NoError(t, err)
req := httptest.NewRequest(http.MethodPost, "/v0/publish", bytes.NewBuffer(jsonData)) req := httptest.NewRequest(http.MethodPost, "/v0/publish", bytes.NewBuffer(jsonData))
@ -209,13 +204,10 @@ func TestPublishIntegration(t *testing.T) {
t.Run("publish fails with missing version", func(t *testing.T) { t.Run("publish fails with missing version", func(t *testing.T) {
serverDetail := &model.ServerDetail{ serverDetail := &model.ServerDetail{
Server: model.Server{ Server: model.Server{
ID: uuid.New().String(),
Name: "test-server", Name: "test-server",
Description: "A test server", Description: "A test server",
VersionDetail: model.VersionDetail{ VersionDetail: model.VersionDetail{
Version: "", // Missing version Version: "", // Missing version
ReleaseDate: time.Now().Format(time.RFC3339),
IsLatest: true,
}, },
}, },
} }
@ -237,13 +229,10 @@ func TestPublishIntegration(t *testing.T) {
t.Run("publish fails with missing authorization header", func(t *testing.T) { t.Run("publish fails with missing authorization header", func(t *testing.T) {
serverDetail := &model.ServerDetail{ serverDetail := &model.ServerDetail{
Server: model.Server{ Server: model.Server{
ID: uuid.New().String(),
Name: "test-server", Name: "test-server",
Description: "A test server", Description: "A test server",
VersionDetail: model.VersionDetail{ VersionDetail: model.VersionDetail{
Version: "1.0.0", Version: "1.0.0",
ReleaseDate: time.Now().Format(time.RFC3339),
IsLatest: true,
}, },
}, },
} }
@ -291,7 +280,6 @@ func TestPublishIntegration(t *testing.T) {
// First, publish a server successfully // First, publish a server successfully
firstServerDetail := &model.ServerDetail{ firstServerDetail := &model.ServerDetail{
Server: model.Server{ Server: model.Server{
ID: uuid.New().String(),
Name: "io.github.duplicate/test-server", Name: "io.github.duplicate/test-server",
Description: "First server for duplicate test", Description: "First server for duplicate test",
Repository: model.Repository{ Repository: model.Repository{
@ -300,9 +288,7 @@ func TestPublishIntegration(t *testing.T) {
ID: "duplicate/test-server", ID: "duplicate/test-server",
}, },
VersionDetail: model.VersionDetail{ VersionDetail: model.VersionDetail{
Version: "1.0.0", Version: "1.0.0",
ReleaseDate: time.Now().Format(time.RFC3339),
IsLatest: true,
}, },
}, },
} }
@ -317,12 +303,17 @@ func TestPublishIntegration(t *testing.T) {
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
handler(recorder, req) handler(recorder, req)
var response map[string]string
err = json.Unmarshal(recorder.Body.Bytes(), &response)
require.NoError(t, err)
assert.Equal(t, http.StatusCreated, recorder.Code, "First publish should succeed") assert.Equal(t, http.StatusCreated, recorder.Code, "First publish should succeed")
firstServerDetail.ID = response["id"] // Store the ID for later verification
// Now try to publish another server with the same name and version // Now try to publish another server with the same name and version
duplicateServerDetail := &model.ServerDetail{ duplicateServerDetail := &model.ServerDetail{
Server: model.Server{ Server: model.Server{
ID: uuid.New().String(), // Different ID
Name: "io.github.duplicate/test-server", // Same name Name: "io.github.duplicate/test-server", // Same name
Description: "Duplicate server attempt", Description: "Duplicate server attempt",
Repository: model.Repository{ Repository: model.Repository{
@ -331,9 +322,7 @@ func TestPublishIntegration(t *testing.T) {
ID: "duplicate/test-server-fork", ID: "duplicate/test-server-fork",
}, },
VersionDetail: model.VersionDetail{ VersionDetail: model.VersionDetail{
Version: "1.0.0", // Same version Version: "1.0.0", // Same version
ReleaseDate: time.Now().Format(time.RFC3339),
IsLatest: true,
}, },
}, },
} }
@ -358,16 +347,12 @@ func TestPublishIntegration(t *testing.T) {
assert.Equal(t, firstServerDetail.Name, retrievedServer.Name) assert.Equal(t, firstServerDetail.Name, retrievedServer.Name)
assert.Equal(t, firstServerDetail.Description, retrievedServer.Description) assert.Equal(t, firstServerDetail.Description, retrievedServer.Description)
// Try to get the duplicate - it should not exist
_, err = registryService.GetByID(duplicateServerDetail.ID)
assert.Error(t, err, "Duplicate server should not have been stored")
}) })
t.Run("publish succeeds with same name but different version", func(t *testing.T) { t.Run("publish succeeds with same name but different version", func(t *testing.T) {
// Publish first version // Publish first version
firstVersionDetail := &model.ServerDetail{ firstVersionDetail := &model.ServerDetail{
Server: model.Server{ Server: model.Server{
ID: uuid.New().String(),
Name: "io.github.versioned/test-server", Name: "io.github.versioned/test-server",
Description: "First version of the server", Description: "First version of the server",
Repository: model.Repository{ Repository: model.Repository{
@ -376,9 +361,7 @@ func TestPublishIntegration(t *testing.T) {
ID: "versioned/test-server", ID: "versioned/test-server",
}, },
VersionDetail: model.VersionDetail{ VersionDetail: model.VersionDetail{
Version: "1.0.0", Version: "1.0.0",
ReleaseDate: time.Now().Format(time.RFC3339),
IsLatest: false,
}, },
}, },
} }
@ -393,12 +376,17 @@ func TestPublishIntegration(t *testing.T) {
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
handler(recorder, req) handler(recorder, req)
var response map[string]string
err = json.Unmarshal(recorder.Body.Bytes(), &response)
require.NoError(t, err)
firstVersionDetail.ID = response["id"] // Store the ID for later verification
assert.Equal(t, http.StatusCreated, recorder.Code, "First version should succeed") assert.Equal(t, http.StatusCreated, recorder.Code, "First version should succeed")
require.NotEmpty(t, firstVersionDetail.ID, "Server ID should be generated")
// Publish second version with same name but different version // Publish second version with same name but different version
secondVersionDetail := &model.ServerDetail{ secondVersionDetail := &model.ServerDetail{
Server: model.Server{ Server: model.Server{
ID: uuid.New().String(),
Name: "io.github.versioned/test-server", // Same name Name: "io.github.versioned/test-server", // Same name
Description: "Second version of the server", Description: "Second version of the server",
Repository: model.Repository{ Repository: model.Repository{
@ -407,9 +395,7 @@ func TestPublishIntegration(t *testing.T) {
ID: "versioned/test-server", ID: "versioned/test-server",
}, },
VersionDetail: model.VersionDetail{ VersionDetail: model.VersionDetail{
Version: "2.0.0", // Different version Version: "2.0.0", // Different version
ReleaseDate: time.Now().Format(time.RFC3339),
IsLatest: true,
}, },
}, },
} }
@ -424,8 +410,14 @@ func TestPublishIntegration(t *testing.T) {
secondRecorder := httptest.NewRecorder() secondRecorder := httptest.NewRecorder()
handler(secondRecorder, secondReq) handler(secondRecorder, secondReq)
var secondResponse map[string]string
err = json.Unmarshal(secondRecorder.Body.Bytes(), &secondResponse)
require.NoError(t, err)
secondVersionDetail.ID = secondResponse["id"] // Store the ID for later verification
// The second version should succeed // The second version should succeed
assert.Equal(t, http.StatusCreated, secondRecorder.Code) assert.Equal(t, http.StatusCreated, secondRecorder.Code)
require.NotEmpty(t, secondVersionDetail.ID, "Server ID for second version should be generated")
// Verify both versions exist // Verify both versions exist
firstRetrieved, err := registryService.GetByID(firstVersionDetail.ID) firstRetrieved, err := registryService.GetByID(firstVersionDetail.ID)
@ -441,7 +433,6 @@ func TestPublishIntegration(t *testing.T) {
// First, publish a newer version (2.0.0) // First, publish a newer version (2.0.0)
newerVersionDetail := &model.ServerDetail{ newerVersionDetail := &model.ServerDetail{
Server: model.Server{ Server: model.Server{
ID: uuid.New().String(),
Name: "io.github.versioning/version-order-test", Name: "io.github.versioning/version-order-test",
Description: "Newer version published first", Description: "Newer version published first",
Repository: model.Repository{ Repository: model.Repository{
@ -450,9 +441,7 @@ func TestPublishIntegration(t *testing.T) {
ID: "versioning/version-order-test", ID: "versioning/version-order-test",
}, },
VersionDetail: model.VersionDetail{ VersionDetail: model.VersionDetail{
Version: "2.0.0", Version: "2.0.0",
ReleaseDate: time.Now().Format(time.RFC3339),
IsLatest: true,
}, },
}, },
} }
@ -467,12 +456,17 @@ func TestPublishIntegration(t *testing.T) {
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
handler(recorder, req) handler(recorder, req)
var response map[string]string
err = json.Unmarshal(recorder.Body.Bytes(), &response)
require.NoError(t, err)
newerVersionDetail.ID = response["id"] // Store the ID for later verification
assert.Equal(t, http.StatusCreated, recorder.Code, "Newer version should be published successfully") assert.Equal(t, http.StatusCreated, recorder.Code, "Newer version should be published successfully")
require.NotEmpty(t, newerVersionDetail.ID, "Server ID for newer version should be generated")
// Now try to publish an older version (1.0.0) of the same package // Now try to publish an older version (1.0.0) of the same package
olderVersionDetail := &model.ServerDetail{ olderVersionDetail := &model.ServerDetail{
Server: model.Server{ Server: model.Server{
ID: uuid.New().String(),
Name: "io.github.versioning/version-order-test", // Same name Name: "io.github.versioning/version-order-test", // Same name
Description: "Older version published after newer", Description: "Older version published after newer",
Repository: model.Repository{ Repository: model.Repository{
@ -481,9 +475,7 @@ func TestPublishIntegration(t *testing.T) {
ID: "versioning/version-order-test", ID: "versioning/version-order-test",
}, },
VersionDetail: model.VersionDetail{ VersionDetail: model.VersionDetail{
Version: "1.0.0", // Older version Version: "1.0.0", // Older version
ReleaseDate: time.Now().Format(time.RFC3339),
IsLatest: false,
}, },
}, },
} }
@ -522,7 +514,6 @@ func TestPublishIntegrationWithComplexPackages(t *testing.T) {
t.Run("publish with complex package configuration", func(t *testing.T) { t.Run("publish with complex package configuration", func(t *testing.T) {
serverDetail := &model.ServerDetail{ serverDetail := &model.ServerDetail{
Server: model.Server{ Server: model.Server{
ID: uuid.New().String(),
Name: "io.github.complex/advanced-mcp-server", Name: "io.github.complex/advanced-mcp-server",
Description: "An advanced MCP server with complex configuration", Description: "An advanced MCP server with complex configuration",
Repository: model.Repository{ Repository: model.Repository{
@ -531,18 +522,20 @@ func TestPublishIntegrationWithComplexPackages(t *testing.T) {
ID: "complex/advanced-mcp-server", ID: "complex/advanced-mcp-server",
}, },
VersionDetail: model.VersionDetail{ VersionDetail: model.VersionDetail{
Version: "2.1.0", Version: "2.1.0",
ReleaseDate: time.Now().Format(time.RFC3339),
IsLatest: true,
}, },
}, },
Packages: []model.Package{ Packages: []model.Package{
{ {
RegistryName: "npm", RegistryName: "npm",
Name: "advanced-mcp-server", Name: "@example/advanced-mcp-server",
Version: "2.1.0", Version: "43.1.0",
RunTimeHint: "node --experimental-modules", RunTimeHint: "node",
RuntimeArguments: []model.Argument{ RuntimeArguments: []model.Argument{
{
Type: model.ArgumentTypeNamed,
Name: "experimental-modules",
},
{ {
Type: model.ArgumentTypeNamed, Type: model.ArgumentTypeNamed,
Name: "config", Name: "config",
@ -620,10 +613,6 @@ func TestPublishIntegrationWithComplexPackages(t *testing.T) {
}, },
}, },
}, },
{
TransportType: "websocket",
URL: "ws://localhost:8081/mcp",
},
}, },
} }
@ -643,8 +632,9 @@ func TestPublishIntegrationWithComplexPackages(t *testing.T) {
err = json.Unmarshal(recorder.Body.Bytes(), &response) err = json.Unmarshal(recorder.Body.Bytes(), &response)
require.NoError(t, err) require.NoError(t, err)
serverDetail.ID = response["id"] // Store the ID for later verification
assert.Equal(t, "Server publication successful", response["message"]) assert.Equal(t, "Server publication successful", response["message"])
assert.Equal(t, serverDetail.ID, response["id"]) assert.NotEmpty(t, response["id"], "Server ID should be generated")
// Verify the complex server was published correctly // Verify the complex server was published correctly
publishedServer, err := registryService.GetByID(serverDetail.ID) publishedServer, err := registryService.GetByID(serverDetail.ID)
@ -654,16 +644,14 @@ func TestPublishIntegrationWithComplexPackages(t *testing.T) {
require.Len(t, publishedServer.Packages, 1) require.Len(t, publishedServer.Packages, 1)
pkg := publishedServer.Packages[0] pkg := publishedServer.Packages[0]
assert.Equal(t, "npm", pkg.RegistryName) assert.Equal(t, "npm", pkg.RegistryName)
assert.Equal(t, "advanced-mcp-server", pkg.Name) assert.Equal(t, "@example/advanced-mcp-server", pkg.Name)
assert.Equal(t, "node --experimental-modules", pkg.RunTimeHint) assert.Len(t, pkg.RuntimeArguments, 3)
assert.Len(t, pkg.RuntimeArguments, 2)
assert.Len(t, pkg.PackageArguments, 1) assert.Len(t, pkg.PackageArguments, 1)
assert.Len(t, pkg.EnvironmentVariables, 2) assert.Len(t, pkg.EnvironmentVariables, 2)
// Verify remotes // Verify remotes
require.Len(t, publishedServer.Remotes, 2) require.Len(t, publishedServer.Remotes, 1)
assert.Equal(t, "http", publishedServer.Remotes[0].TransportType) assert.Equal(t, "http", publishedServer.Remotes[0].TransportType)
assert.Equal(t, "websocket", publishedServer.Remotes[1].TransportType)
assert.Len(t, publishedServer.Remotes[0].Headers, 1) assert.Len(t, publishedServer.Remotes[0].Headers, 1)
}) })
} }
@ -683,7 +671,6 @@ func TestPublishIntegrationEndToEnd(t *testing.T) {
// Step 2: Publish a new server // Step 2: Publish a new server
serverDetail := &model.ServerDetail{ serverDetail := &model.ServerDetail{
Server: model.Server{ Server: model.Server{
ID: uuid.New().String(),
Name: "io.github.e2e/end-to-end-server", Name: "io.github.e2e/end-to-end-server",
Description: "End-to-end test server", Description: "End-to-end test server",
Repository: model.Repository{ Repository: model.Repository{
@ -692,9 +679,7 @@ func TestPublishIntegrationEndToEnd(t *testing.T) {
ID: "e2e/end-to-end-server", ID: "e2e/end-to-end-server",
}, },
VersionDetail: model.VersionDetail{ VersionDetail: model.VersionDetail{
Version: "1.0.0", Version: "1.0.0",
ReleaseDate: time.Now().Format(time.RFC3339),
IsLatest: true,
}, },
}, },
} }
@ -709,6 +694,11 @@ func TestPublishIntegrationEndToEnd(t *testing.T) {
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
handler(recorder, req) handler(recorder, req)
var response map[string]string
err = json.Unmarshal(recorder.Body.Bytes(), &response)
require.NoError(t, err)
serverDetail.ID = response["id"] // Store the ID for later verification
require.Equal(t, http.StatusCreated, recorder.Code) require.Equal(t, http.StatusCreated, recorder.Code)
// Step 3: Verify the count increased // Step 3: Verify the count increased

View file

@ -7,6 +7,7 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/google/uuid"
"github.com/modelcontextprotocol/registry/internal/model" "github.com/modelcontextprotocol/registry/internal/model"
) )
@ -231,6 +232,8 @@ func (db *MemoryDB) Publish(ctx context.Context, serverDetail *model.ServerDetai
return ErrInvalidInput return ErrInvalidInput
} }
// Generate a new ID for the server detail
serverDetail.ID = uuid.New().String()
// Store a copy of the entire ServerDetail // Store a copy of the entire ServerDetail
serverDetailCopy := *serverDetail serverDetailCopy := *serverDetail
db.entries[serverDetail.ID] = &serverDetailCopy db.entries[serverDetail.ID] = &serverDetailCopy