Deduplicate server JSON structs between publisher and registry (#318)
<!-- Provide a brief summary of your changes --> ## Motivation and Context <!-- Why is this change needed? What problem does it solve? --> The following PR eliminates duplicate struct definitions by migrating the publisher tool to use canonical types from pkg/model, creating a single source of truth across the codebase. The publisher tool previously had its own copies of structs that were duplicated, ie. ServerJSON, Package, Repository, VersionDetail, EnvironmentVariable, RuntimeArgument and a few others which was not optimal **Changes in the PR:** - Refactored the structs to single canonical types located at pkg/model - Refactor the implementation to use/build canonical structures directly - Remove all duplicate definitions (5 structs eliminated) - Publisher now uses model.ServerJSON, model.Package, model.Repository, etc. - Renamed some of the structs to better reflect their use case, i.e. ServerJSON instead of ServerDetail - Fixes an issue where the Remote property was missing from the previous publisher copy of the Server **Benefits:** - Single source of truth - Schema changes in one place - Type safety - Compiler catches mismatches - Enhanced validation - Field constraints and bson tags - Zero breaking changes - Same CLI interface and JSON output - Future-proof - New canonical fields automatically available Supersedes: #217 and #240 ## How Has This Been Tested? <!-- Have you tested this in a real application? Which scenarios were tested? --> Locally ## Breaking Changes <!-- Will users need to update their code or configurations? --> No ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [x] 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>
This commit is contained in:
parent
9d04671ee3
commit
099a2d702e
1 changed files with 1280 additions and 1408 deletions
|
|
@ -17,7 +17,8 @@ import (
|
|||
"github.com/modelcontextprotocol/registry/internal/auth"
|
||||
"github.com/modelcontextprotocol/registry/internal/config"
|
||||
"github.com/modelcontextprotocol/registry/internal/database"
|
||||
"github.com/modelcontextprotocol/registry/internal/model"
|
||||
apiv1 "github.com/modelcontextprotocol/registry/pkg/api/v1"
|
||||
"github.com/modelcontextprotocol/registry/pkg/model"
|
||||
)
|
||||
|
||||
func TestEditServerEndpoint(t *testing.T) {
|
||||
|
|
@ -36,7 +37,7 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
AuthMethod: model.AuthMethodGitHubAT,
|
||||
AuthMethod: auth.MethodGitHubAT,
|
||||
AuthMethodSubject: "domdomegg",
|
||||
Permissions: []auth.Permission{
|
||||
{Action: auth.PermissionActionEdit, ResourcePattern: "io.github.domdomegg/*"},
|
||||
|
|
@ -44,11 +45,11 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
})
|
||||
return "Bearer " + token
|
||||
}(),
|
||||
requestBody: model.PublishRequest{
|
||||
Server: model.ServerDetail{
|
||||
requestBody: apiv1.PublishRequest{
|
||||
Server: model.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Updated test server",
|
||||
Status: model.ServerStatusDeprecated,
|
||||
Status: model.StatusDeprecated,
|
||||
Repository: model.Repository{
|
||||
URL: "https://github.com/domdomegg/test-server",
|
||||
Source: "github",
|
||||
|
|
@ -61,23 +62,23 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
},
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
// Current server (not deleted)
|
||||
currentServer := &model.ServerResponse{
|
||||
Server: model.ServerDetail{
|
||||
currentServer := &apiv1.ServerRecord{
|
||||
Server: model.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Original server",
|
||||
Status: model.ServerStatusActive,
|
||||
Status: model.StatusActive,
|
||||
},
|
||||
}
|
||||
registry.On("GetByID", "550e8400-e29b-41d4-a716-446655440001").Return(currentServer, nil)
|
||||
|
||||
expectedResponse := &model.ServerResponse{
|
||||
Server: model.ServerDetail{
|
||||
|
||||
expectedResponse := &apiv1.ServerRecord{
|
||||
Server: model.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Updated test server",
|
||||
Status: model.ServerStatusDeprecated,
|
||||
Status: model.StatusDeprecated,
|
||||
},
|
||||
}
|
||||
registry.On("EditServer", "550e8400-e29b-41d4-a716-446655440001", mock.AnythingOfType("model.PublishRequest")).Return(expectedResponse, nil)
|
||||
registry.On("EditServer", "550e8400-e29b-41d4-a716-446655440001", mock.AnythingOfType("v1.PublishRequest")).Return(expectedResponse, nil)
|
||||
},
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
|
|
@ -85,7 +86,7 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
name: "missing authorization header",
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
authHeader: "",
|
||||
requestBody: model.PublishRequest{},
|
||||
requestBody: apiv1.PublishRequest{},
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
expectedStatus: 422,
|
||||
expectedError: "required header parameter is missing",
|
||||
|
|
@ -94,7 +95,7 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
name: "invalid authorization header format",
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
authHeader: "InvalidFormat token123",
|
||||
requestBody: model.PublishRequest{},
|
||||
requestBody: apiv1.PublishRequest{},
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
expectedError: "Unauthorized",
|
||||
|
|
@ -103,7 +104,7 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
name: "invalid token",
|
||||
serverID: "550e8400-e29b-41d4-a716-446655440001",
|
||||
authHeader: "Bearer invalid-token",
|
||||
requestBody: model.PublishRequest{},
|
||||
requestBody: apiv1.PublishRequest{},
|
||||
setupMocks: func(_ *MockRegistryService) {},
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
expectedError: "Unauthorized",
|
||||
|
|
@ -114,7 +115,7 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
AuthMethod: model.AuthMethodGitHubAT,
|
||||
AuthMethod: auth.MethodGitHubAT,
|
||||
AuthMethodSubject: "domdomegg",
|
||||
Permissions: []auth.Permission{
|
||||
{Action: auth.PermissionActionPublish, ResourcePattern: "io.github.domdomegg/*"},
|
||||
|
|
@ -122,19 +123,19 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
})
|
||||
return "Bearer " + token
|
||||
}(),
|
||||
requestBody: model.PublishRequest{
|
||||
Server: model.ServerDetail{
|
||||
requestBody: apiv1.PublishRequest{
|
||||
Server: model.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 := &model.ServerResponse{
|
||||
Server: model.ServerDetail{
|
||||
currentServer := &apiv1.ServerRecord{
|
||||
Server: model.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Original server",
|
||||
Status: model.ServerStatusActive,
|
||||
Status: model.StatusActive,
|
||||
},
|
||||
}
|
||||
registry.On("GetByID", "550e8400-e29b-41d4-a716-446655440001").Return(currentServer, nil)
|
||||
|
|
@ -148,7 +149,7 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
AuthMethod: model.AuthMethodGitHubAT,
|
||||
AuthMethod: auth.MethodGitHubAT,
|
||||
AuthMethodSubject: "domdomegg",
|
||||
Permissions: []auth.Permission{
|
||||
{Action: auth.PermissionActionEdit, ResourcePattern: "io.github.domdomegg/*"},
|
||||
|
|
@ -156,8 +157,8 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
})
|
||||
return "Bearer " + token
|
||||
}(),
|
||||
requestBody: model.PublishRequest{
|
||||
Server: model.ServerDetail{
|
||||
requestBody: apiv1.PublishRequest{
|
||||
Server: model.ServerJSON{
|
||||
Name: "io.github.other/test-server",
|
||||
Description: "Updated test server",
|
||||
},
|
||||
|
|
@ -165,11 +166,11 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
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 := &model.ServerResponse{
|
||||
Server: model.ServerDetail{
|
||||
currentServer := &apiv1.ServerRecord{
|
||||
Server: model.ServerJSON{
|
||||
Name: "io.github.other/test-server",
|
||||
Description: "Original server",
|
||||
Status: model.ServerStatusActive,
|
||||
Status: model.StatusActive,
|
||||
},
|
||||
}
|
||||
registry.On("GetByID", "550e8400-e29b-41d4-a716-446655440001").Return(currentServer, nil)
|
||||
|
|
@ -183,7 +184,7 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
AuthMethod: model.AuthMethodGitHubAT,
|
||||
AuthMethod: auth.MethodGitHubAT,
|
||||
AuthMethodSubject: "domdomegg",
|
||||
Permissions: []auth.Permission{
|
||||
{Action: auth.PermissionActionEdit, ResourcePattern: "io.github.domdomegg/*"},
|
||||
|
|
@ -191,8 +192,8 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
})
|
||||
return "Bearer " + token
|
||||
}(),
|
||||
requestBody: model.PublishRequest{
|
||||
Server: model.ServerDetail{
|
||||
requestBody: apiv1.PublishRequest{
|
||||
Server: model.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Updated test server",
|
||||
},
|
||||
|
|
@ -209,7 +210,7 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
AuthMethod: model.AuthMethodGitHubAT,
|
||||
AuthMethod: auth.MethodGitHubAT,
|
||||
AuthMethodSubject: "domdomegg",
|
||||
Permissions: []auth.Permission{
|
||||
{Action: auth.PermissionActionEdit, ResourcePattern: "*"},
|
||||
|
|
@ -217,23 +218,23 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
})
|
||||
return "Bearer " + token
|
||||
}(),
|
||||
requestBody: model.PublishRequest{
|
||||
Server: model.ServerDetail{
|
||||
requestBody: apiv1.PublishRequest{
|
||||
Server: model.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Updated test server",
|
||||
},
|
||||
},
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
// Current server (not deleted)
|
||||
currentServer := &model.ServerResponse{
|
||||
Server: model.ServerDetail{
|
||||
currentServer := &apiv1.ServerRecord{
|
||||
Server: model.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Original server",
|
||||
Status: model.ServerStatusActive,
|
||||
Status: model.StatusActive,
|
||||
},
|
||||
}
|
||||
registry.On("GetByID", "550e8400-e29b-41d4-a716-446655440001").Return(currentServer, nil)
|
||||
registry.On("EditServer", "550e8400-e29b-41d4-a716-446655440001", mock.AnythingOfType("model.PublishRequest")).Return(nil, errors.New("database error"))
|
||||
registry.On("EditServer", "550e8400-e29b-41d4-a716-446655440001", mock.AnythingOfType("v1.PublishRequest")).Return(nil, errors.New("database error"))
|
||||
},
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
expectedError: "Bad Request",
|
||||
|
|
@ -244,7 +245,7 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
AuthMethod: model.AuthMethodGitHubAT,
|
||||
AuthMethod: auth.MethodGitHubAT,
|
||||
AuthMethodSubject: "domdomegg",
|
||||
Permissions: []auth.Permission{
|
||||
{Action: auth.PermissionActionEdit, ResourcePattern: "*"},
|
||||
|
|
@ -263,7 +264,7 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
authHeader: func() string {
|
||||
cfg := &config.Config{JWTPrivateKey: "bb2c6b424005acd5df47a9e2c87f446def86dd740c888ea3efb825b23f7ef47c"}
|
||||
token, _ := generateTestJWTToken(cfg, auth.JWTClaims{
|
||||
AuthMethod: model.AuthMethodGitHubAT,
|
||||
AuthMethod: auth.MethodGitHubAT,
|
||||
AuthMethodSubject: "domdomegg",
|
||||
Permissions: []auth.Permission{
|
||||
{Action: auth.PermissionActionEdit, ResourcePattern: "*"},
|
||||
|
|
@ -271,20 +272,20 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
})
|
||||
return "Bearer " + token
|
||||
}(),
|
||||
requestBody: model.PublishRequest{
|
||||
Server: model.ServerDetail{
|
||||
requestBody: apiv1.PublishRequest{
|
||||
Server: model.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Trying to undelete server",
|
||||
Status: model.ServerStatusActive,
|
||||
Status: model.StatusActive,
|
||||
},
|
||||
},
|
||||
setupMocks: func(registry *MockRegistryService) {
|
||||
// Current server is deleted
|
||||
currentServer := &model.ServerResponse{
|
||||
Server: model.ServerDetail{
|
||||
currentServer := &apiv1.ServerRecord{
|
||||
Server: model.ServerJSON{
|
||||
Name: "io.github.domdomegg/test-server",
|
||||
Description: "Original server",
|
||||
Status: model.ServerStatusDeleted,
|
||||
Status: model.StatusDeleted,
|
||||
},
|
||||
}
|
||||
registry.On("GetByID", "550e8400-e29b-41d4-a716-446655440001").Return(currentServer, nil)
|
||||
|
|
@ -345,4 +346,4 @@ func TestEditServerEndpoint(t *testing.T) {
|
|||
mockRegistry.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue