feat(tests): add comprehensive tests for health and publish handlers
This commit is contained in:
parent
4f89a896ec
commit
d266dd4eb0
4 changed files with 598 additions and 0 deletions
4
go.mod
4
go.mod
|
|
@ -5,6 +5,7 @@ go 1.23.0
|
||||||
require (
|
require (
|
||||||
github.com/caarlos0/env/v11 v11.3.1
|
github.com/caarlos0/env/v11 v11.3.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/swaggo/files v1.0.1
|
github.com/swaggo/files v1.0.1
|
||||||
github.com/swaggo/http-swagger v1.3.4
|
github.com/swaggo/http-swagger v1.3.4
|
||||||
go.mongodb.org/mongo-driver v1.17.3
|
go.mongodb.org/mongo-driver v1.17.3
|
||||||
|
|
@ -12,6 +13,7 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||||
github.com/go-openapi/spec v0.21.0 // indirect
|
github.com/go-openapi/spec v0.21.0 // indirect
|
||||||
|
|
@ -21,6 +23,8 @@ require (
|
||||||
github.com/klauspost/compress v1.16.7 // indirect
|
github.com/klauspost/compress v1.16.7 // indirect
|
||||||
github.com/mailru/easyjson v0.9.0 // indirect
|
github.com/mailru/easyjson v0.9.0 // indirect
|
||||||
github.com/montanaflynn/stats v0.7.1 // indirect
|
github.com/montanaflynn/stats v0.7.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/swaggo/swag v1.16.4 // indirect
|
github.com/swaggo/swag v1.16.4 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
github.com/xdg-go/scram v1.1.2 // indirect
|
github.com/xdg-go/scram v1.1.2 // indirect
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -34,6 +34,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||||
|
|
|
||||||
113
internal/api/handlers/v0/health_test.go
Normal file
113
internal/api/handlers/v0/health_test.go
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
package v0
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/modelcontextprotocol/registry/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHealthHandler(t *testing.T) {
|
||||||
|
// Test cases
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
config *config.Config
|
||||||
|
expectedStatus int
|
||||||
|
expectedBody HealthResponse
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "returns health status with github client id",
|
||||||
|
config: &config.Config{
|
||||||
|
GithubClientID: "test-github-client-id",
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: HealthResponse{
|
||||||
|
Status: "ok",
|
||||||
|
GitHubClientId: "test-github-client-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "works with empty github client id",
|
||||||
|
config: &config.Config{
|
||||||
|
GithubClientID: "",
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedBody: HealthResponse{
|
||||||
|
Status: "ok",
|
||||||
|
GitHubClientId: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
// Create handler with the test config
|
||||||
|
handler := HealthHandler(tc.config)
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
req, err := http.NewRequest("GET", "/health", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create response recorder
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// Call the handler
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
// Check status code
|
||||||
|
assert.Equal(t, tc.expectedStatus, rr.Code)
|
||||||
|
|
||||||
|
// Check content type
|
||||||
|
assert.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
// Parse response body
|
||||||
|
var resp HealthResponse
|
||||||
|
err = json.NewDecoder(rr.Body).Decode(&resp)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Check the response body
|
||||||
|
assert.Equal(t, tc.expectedBody, resp)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHealthHandlerIntegration tests the handler with actual HTTP requests
|
||||||
|
func TestHealthHandlerIntegration(t *testing.T) {
|
||||||
|
// Create test server
|
||||||
|
cfg := &config.Config{
|
||||||
|
GithubClientID: "integration-test-client-id",
|
||||||
|
}
|
||||||
|
|
||||||
|
server := httptest.NewServer(HealthHandler(cfg))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
// Send request to the test server
|
||||||
|
resp, err := http.Get(server.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to send request: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Check status code
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
// Check content type
|
||||||
|
assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
|
||||||
|
|
||||||
|
// Parse response body
|
||||||
|
var healthResp HealthResponse
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&healthResp)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Check the response body
|
||||||
|
expectedResp := HealthResponse{
|
||||||
|
Status: "ok",
|
||||||
|
GitHubClientId: "integration-test-client-id",
|
||||||
|
}
|
||||||
|
assert.Equal(t, expectedResp, healthResp)
|
||||||
|
}
|
||||||
479
internal/api/handlers/v0/publish_test.go
Normal file
479
internal/api/handlers/v0/publish_test.go
Normal file
|
|
@ -0,0 +1,479 @@
|
||||||
|
package v0
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/modelcontextprotocol/registry/internal/auth"
|
||||||
|
"github.com/modelcontextprotocol/registry/internal/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockRegistryService is a mock implementation of the RegistryService interface
|
||||||
|
type MockRegistryService struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRegistryService) List(cursor string, limit int) ([]model.Server, string, error) {
|
||||||
|
args := m.Called(cursor, limit)
|
||||||
|
return args.Get(0).([]model.Server), args.String(1), args.Error(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRegistryService) GetByID(id string) (*model.ServerDetail, error) {
|
||||||
|
args := m.Called(id)
|
||||||
|
return args.Get(0).(*model.ServerDetail), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRegistryService) Publish(serverDetail *model.ServerDetail) error {
|
||||||
|
args := m.Called(serverDetail)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockAuthService is a mock implementation of the auth.Service interface
|
||||||
|
type MockAuthService struct {
|
||||||
|
mock.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockAuthService) StartAuthFlow(ctx context.Context, method model.AuthMethod, repoRef string) (map[string]string, string, error) {
|
||||||
|
args := m.Called(ctx, method, repoRef)
|
||||||
|
return args.Get(0).(map[string]string), args.String(1), args.Error(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockAuthService) CheckAuthStatus(ctx context.Context, statusToken string) (string, error) {
|
||||||
|
args := m.Called(ctx, statusToken)
|
||||||
|
return args.String(0), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockAuthService) ValidateAuth(ctx context.Context, authentication model.Authentication) (bool, error) {
|
||||||
|
args := m.Called(ctx, authentication)
|
||||||
|
return args.Bool(0), args.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublishHandler(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
method string
|
||||||
|
requestBody interface{}
|
||||||
|
authHeader string
|
||||||
|
setupMocks func(*MockRegistryService, *MockAuthService)
|
||||||
|
expectedStatus int
|
||||||
|
expectedResponse map[string]string
|
||||||
|
expectedError string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "successful publish with GitHub auth",
|
||||||
|
method: http.MethodPost,
|
||||||
|
requestBody: model.ServerDetail{
|
||||||
|
Server: model.Server{
|
||||||
|
ID: "test-id",
|
||||||
|
Name: "io.github.example/test-server",
|
||||||
|
Description: "A test server",
|
||||||
|
Repository: model.Repository{
|
||||||
|
URL: "https://github.com/example/test-server",
|
||||||
|
Source: "github",
|
||||||
|
ID: "example/test-server",
|
||||||
|
},
|
||||||
|
VersionDetail: model.VersionDetail{
|
||||||
|
Version: "1.0.0",
|
||||||
|
ReleaseDate: "2025-05-25T00:00:00Z",
|
||||||
|
IsLatest: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authHeader: "Bearer github_token_123",
|
||||||
|
setupMocks: func(registry *MockRegistryService, authSvc *MockAuthService) {
|
||||||
|
authSvc.On("ValidateAuth", mock.Anything, model.Authentication{
|
||||||
|
Method: model.AuthMethodGitHub,
|
||||||
|
Token: "github_token_123",
|
||||||
|
RepoRef: "io.github.example/test-server",
|
||||||
|
}).Return(true, nil)
|
||||||
|
registry.On("Publish", mock.AnythingOfType("*model.ServerDetail")).Return(nil)
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusCreated,
|
||||||
|
expectedResponse: map[string]string{
|
||||||
|
"message": "Server publication successful",
|
||||||
|
"id": "test-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "successful publish with no auth (AuthMethodNone)",
|
||||||
|
method: http.MethodPost,
|
||||||
|
requestBody: model.ServerDetail{
|
||||||
|
Server: model.Server{
|
||||||
|
ID: "test-id-2",
|
||||||
|
Name: "example/test-server",
|
||||||
|
Description: "A test server without auth",
|
||||||
|
Repository: model.Repository{
|
||||||
|
URL: "https://example.com/test-server",
|
||||||
|
Source: "example",
|
||||||
|
ID: "example/test-server",
|
||||||
|
},
|
||||||
|
VersionDetail: model.VersionDetail{
|
||||||
|
Version: "1.0.0",
|
||||||
|
ReleaseDate: "2025-05-25T00:00:00Z",
|
||||||
|
IsLatest: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authHeader: "Bearer some_token",
|
||||||
|
setupMocks: func(registry *MockRegistryService, authSvc *MockAuthService) {
|
||||||
|
authSvc.On("ValidateAuth", mock.Anything, model.Authentication{
|
||||||
|
Method: model.AuthMethodNone,
|
||||||
|
Token: "some_token",
|
||||||
|
RepoRef: "example/test-server",
|
||||||
|
}).Return(true, nil)
|
||||||
|
registry.On("Publish", mock.AnythingOfType("*model.ServerDetail")).Return(nil)
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusCreated,
|
||||||
|
expectedResponse: map[string]string{
|
||||||
|
"message": "Server publication successful",
|
||||||
|
"id": "test-id-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "method not allowed",
|
||||||
|
method: http.MethodGet,
|
||||||
|
requestBody: nil,
|
||||||
|
authHeader: "",
|
||||||
|
setupMocks: func(registry *MockRegistryService, authSvc *MockAuthService) {},
|
||||||
|
expectedStatus: http.StatusMethodNotAllowed,
|
||||||
|
expectedError: "Method not allowed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing request body",
|
||||||
|
method: http.MethodPost,
|
||||||
|
requestBody: "",
|
||||||
|
authHeader: "",
|
||||||
|
setupMocks: func(registry *MockRegistryService, authSvc *MockAuthService) {},
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedError: "Invalid request payload:",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing server name",
|
||||||
|
method: http.MethodPost,
|
||||||
|
requestBody: model.ServerDetail{
|
||||||
|
Server: model.Server{
|
||||||
|
ID: "test-id",
|
||||||
|
Name: "", // Missing name
|
||||||
|
Description: "A test server",
|
||||||
|
VersionDetail: model.VersionDetail{
|
||||||
|
Version: "1.0.0",
|
||||||
|
ReleaseDate: "2025-05-25T00:00:00Z",
|
||||||
|
IsLatest: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authHeader: "",
|
||||||
|
setupMocks: func(registry *MockRegistryService, authSvc *MockAuthService) {},
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedError: "Name is required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing version",
|
||||||
|
method: http.MethodPost,
|
||||||
|
requestBody: model.ServerDetail{
|
||||||
|
Server: model.Server{
|
||||||
|
ID: "test-id",
|
||||||
|
Name: "test-server",
|
||||||
|
Description: "A test server",
|
||||||
|
VersionDetail: model.VersionDetail{
|
||||||
|
Version: "", // Missing version
|
||||||
|
ReleaseDate: "2025-05-25T00:00:00Z",
|
||||||
|
IsLatest: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authHeader: "",
|
||||||
|
setupMocks: func(registry *MockRegistryService, authSvc *MockAuthService) {},
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedError: "Version is required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing authorization header",
|
||||||
|
method: http.MethodPost,
|
||||||
|
requestBody: model.ServerDetail{
|
||||||
|
Server: model.Server{
|
||||||
|
ID: "test-id",
|
||||||
|
Name: "test-server",
|
||||||
|
Description: "A test server",
|
||||||
|
VersionDetail: model.VersionDetail{
|
||||||
|
Version: "1.0.0",
|
||||||
|
ReleaseDate: "2025-05-25T00:00:00Z",
|
||||||
|
IsLatest: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authHeader: "", // Missing auth header
|
||||||
|
setupMocks: func(registry *MockRegistryService, authSvc *MockAuthService) {},
|
||||||
|
expectedStatus: http.StatusUnauthorized,
|
||||||
|
expectedError: "Authorization header is required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication required error",
|
||||||
|
method: http.MethodPost,
|
||||||
|
requestBody: model.ServerDetail{
|
||||||
|
Server: model.Server{
|
||||||
|
ID: "test-id",
|
||||||
|
Name: "test-server",
|
||||||
|
Description: "A test server",
|
||||||
|
VersionDetail: model.VersionDetail{
|
||||||
|
Version: "1.0.0",
|
||||||
|
ReleaseDate: "2025-05-25T00:00:00Z",
|
||||||
|
IsLatest: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authHeader: "Bearer token",
|
||||||
|
setupMocks: func(registry *MockRegistryService, authSvc *MockAuthService) {
|
||||||
|
authSvc.On("ValidateAuth", mock.Anything, mock.Anything).Return(false, auth.ErrAuthRequired)
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusUnauthorized,
|
||||||
|
expectedError: "Authentication is required for publishing",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication failed",
|
||||||
|
method: http.MethodPost,
|
||||||
|
requestBody: model.ServerDetail{
|
||||||
|
Server: model.Server{
|
||||||
|
ID: "test-id",
|
||||||
|
Name: "test-server",
|
||||||
|
Description: "A test server",
|
||||||
|
VersionDetail: model.VersionDetail{
|
||||||
|
Version: "1.0.0",
|
||||||
|
ReleaseDate: "2025-05-25T00:00:00Z",
|
||||||
|
IsLatest: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authHeader: "Bearer invalid_token",
|
||||||
|
setupMocks: func(registry *MockRegistryService, authSvc *MockAuthService) {
|
||||||
|
authSvc.On("ValidateAuth", mock.Anything, mock.Anything).Return(false, nil)
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusUnauthorized,
|
||||||
|
expectedError: "Invalid authentication credentials",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "registry service error",
|
||||||
|
method: http.MethodPost,
|
||||||
|
requestBody: model.ServerDetail{
|
||||||
|
Server: model.Server{
|
||||||
|
ID: "test-id",
|
||||||
|
Name: "test-server",
|
||||||
|
Description: "A test server",
|
||||||
|
VersionDetail: model.VersionDetail{
|
||||||
|
Version: "1.0.0",
|
||||||
|
ReleaseDate: "2025-05-25T00:00:00Z",
|
||||||
|
IsLatest: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
authHeader: "Bearer token",
|
||||||
|
setupMocks: func(registry *MockRegistryService, authSvc *MockAuthService) {
|
||||||
|
authSvc.On("ValidateAuth", mock.Anything, mock.Anything).Return(true, nil)
|
||||||
|
registry.On("Publish", mock.AnythingOfType("*model.ServerDetail")).Return(assert.AnError)
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusInternalServerError,
|
||||||
|
expectedError: "Failed to publish server details:",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
// Create mocks
|
||||||
|
mockRegistry := new(MockRegistryService)
|
||||||
|
mockAuthService := new(MockAuthService)
|
||||||
|
|
||||||
|
// Setup mocks
|
||||||
|
tc.setupMocks(mockRegistry, mockAuthService)
|
||||||
|
|
||||||
|
// Create handler
|
||||||
|
handler := PublishHandler(mockRegistry, mockAuthService)
|
||||||
|
|
||||||
|
// Prepare request body
|
||||||
|
var requestBody []byte
|
||||||
|
if tc.requestBody != nil {
|
||||||
|
var err error
|
||||||
|
requestBody, err = json.Marshal(tc.requestBody)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
req, err := http.NewRequest(tc.method, "/publish", bytes.NewBuffer(requestBody))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Set auth header if provided
|
||||||
|
if tc.authHeader != "" {
|
||||||
|
req.Header.Set("Authorization", tc.authHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create response recorder
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
// Call the handler
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
// Check status code
|
||||||
|
assert.Equal(t, tc.expectedStatus, rr.Code)
|
||||||
|
|
||||||
|
if tc.expectedResponse != nil {
|
||||||
|
// Check content type for successful responses
|
||||||
|
assert.Equal(t, "application/json", rr.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
// Parse and verify response body
|
||||||
|
var response map[string]string
|
||||||
|
err = json.NewDecoder(rr.Body).Decode(&response)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.expectedResponse, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectedError != "" {
|
||||||
|
// Check that the error message is contained in the response
|
||||||
|
assert.Contains(t, rr.Body.String(), tc.expectedError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that all expectations were met
|
||||||
|
mockRegistry.AssertExpectations(t)
|
||||||
|
mockAuthService.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublishHandlerBearerTokenParsing(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
authHeader string
|
||||||
|
expectedToken string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "bearer token with Bearer prefix",
|
||||||
|
authHeader: "Bearer github_token_123",
|
||||||
|
expectedToken: "github_token_123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bearer token with bearer prefix (lowercase)",
|
||||||
|
authHeader: "bearer github_token_123",
|
||||||
|
expectedToken: "github_token_123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token without Bearer prefix",
|
||||||
|
authHeader: "github_token_123",
|
||||||
|
expectedToken: "github_token_123",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mixed case Bearer prefix",
|
||||||
|
authHeader: "BeArEr github_token_123",
|
||||||
|
expectedToken: "github_token_123",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
mockRegistry := new(MockRegistryService)
|
||||||
|
mockAuthService := new(MockAuthService)
|
||||||
|
|
||||||
|
// Setup mock to capture the actual token passed
|
||||||
|
mockAuthService.On("ValidateAuth", mock.Anything, mock.MatchedBy(func(auth model.Authentication) bool {
|
||||||
|
return auth.Token == tc.expectedToken
|
||||||
|
})).Return(true, nil)
|
||||||
|
mockRegistry.On("Publish", mock.AnythingOfType("*model.ServerDetail")).Return(nil)
|
||||||
|
|
||||||
|
handler := PublishHandler(mockRegistry, mockAuthService)
|
||||||
|
|
||||||
|
serverDetail := model.ServerDetail{
|
||||||
|
Server: model.Server{
|
||||||
|
ID: "test-id",
|
||||||
|
Name: "test-server",
|
||||||
|
Description: "A test server",
|
||||||
|
VersionDetail: model.VersionDetail{
|
||||||
|
Version: "1.0.0",
|
||||||
|
ReleaseDate: "2025-05-25T00:00:00Z",
|
||||||
|
IsLatest: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
requestBody, err := json.Marshal(serverDetail)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, "/publish", bytes.NewBuffer(requestBody))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
req.Header.Set("Authorization", tc.authHeader)
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusCreated, rr.Code)
|
||||||
|
mockAuthService.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublishHandlerAuthMethodSelection(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
serverName string
|
||||||
|
expectedAuthMethod model.AuthMethod
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "GitHub prefix triggers GitHub auth",
|
||||||
|
serverName: "io.github.example/test-server",
|
||||||
|
expectedAuthMethod: model.AuthMethodGitHub,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-GitHub prefix uses no auth",
|
||||||
|
serverName: "example.com/test-server",
|
||||||
|
expectedAuthMethod: model.AuthMethodNone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty prefix uses no auth",
|
||||||
|
serverName: "test-server",
|
||||||
|
expectedAuthMethod: model.AuthMethodNone,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
mockRegistry := new(MockRegistryService)
|
||||||
|
mockAuthService := new(MockAuthService)
|
||||||
|
|
||||||
|
// Setup mock to capture the auth method
|
||||||
|
mockAuthService.On("ValidateAuth", mock.Anything, mock.MatchedBy(func(auth model.Authentication) bool {
|
||||||
|
return auth.Method == tc.expectedAuthMethod
|
||||||
|
})).Return(true, nil)
|
||||||
|
mockRegistry.On("Publish", mock.AnythingOfType("*model.ServerDetail")).Return(nil)
|
||||||
|
|
||||||
|
handler := PublishHandler(mockRegistry, mockAuthService)
|
||||||
|
|
||||||
|
serverDetail := model.ServerDetail{
|
||||||
|
Server: model.Server{
|
||||||
|
ID: "test-id",
|
||||||
|
Name: tc.serverName,
|
||||||
|
Description: "A test server",
|
||||||
|
VersionDetail: model.VersionDetail{
|
||||||
|
Version: "1.0.0",
|
||||||
|
ReleaseDate: "2025-05-25T00:00:00Z",
|
||||||
|
IsLatest: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
requestBody, err := json.Marshal(serverDetail)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, "/publish", bytes.NewBuffer(requestBody))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
req.Header.Set("Authorization", "Bearer test_token")
|
||||||
|
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusCreated, rr.Code)
|
||||||
|
mockAuthService.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue