// Copyright (c) 2022 Alibaba Group Holding Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package server import ( "encoding/json" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // MockHttpContext is a mock implementation for testing - skipping interface implementation for now // Tests that require full HttpContext will be tested in integration tests with real host type MockHttpContext struct { responseBody []byte responseStatus int headers map[string]string } // TestMcpProtocolInitialization tests the MCP protocol initialization flow func TestMcpProtocolInitialization(t *testing.T) { // Create proxy server server := NewMcpProxyServer("test-proxy") // Set server fields directly server.SetMcpServerURL("http://mock-backend.example.com/mcp") server.SetTimeout(5000) // Create proxy tool toolConfig := McpProxyToolConfig{ Name: "test-tool", Description: "Test tool for initialization", Args: []ToolArg{ { Name: "input", Description: "Test input", Type: "string", Required: true, }, }, } err := server.AddProxyTool(toolConfig) require.NoError(t, err) tool, exists := server.GetMCPTools()["test-tool"] require.True(t, exists) // Create tool instance with parameters params := map[string]interface{}{ "input": "test value", } paramsBytes, err := json.Marshal(params) require.NoError(t, err) toolInstance := tool.Create(paramsBytes) require.NotNil(t, toolInstance) // Skip HttpContext-dependent test for now - will be tested in integration // mockCtx := &MockHttpContext{} // err = toolInstance.Call(mockCtx, server) // assert.NoError(t, err) // Test the tool creation was successful assert.NotNil(t, toolInstance) } // TestMcpSessionManagement tests temporary session creation and cleanup func TestMcpSessionManagement(t *testing.T) { _ = NewMcpProxyServer("session-test") // Skip session management test until implemented t.Skip("Session management not implemented yet") // Test session creation sessionManager := NewMcpSessionManager() sessionID, err := sessionManager.CreateSession("http://backend.example.com/mcp") // This will fail until session management is implemented assert.NoError(t, err) assert.NotEmpty(t, sessionID) // Test session retrieval session, exists := sessionManager.GetSession(sessionID) assert.True(t, exists) assert.NotNil(t, session) // Test session cleanup sessionManager.CleanupSession(sessionID) _, exists = sessionManager.GetSession(sessionID) assert.False(t, exists) } // TestMcpProtocolVersionNegotiation tests protocol version handling func TestMcpProtocolVersionNegotiation(t *testing.T) { tests := []struct { name string requestedVersion string supportedVersions []string shouldSucceed bool expectedVersion string }{ { name: "supported version 2025-03-26", requestedVersion: "2025-03-26", supportedVersions: []string{"2024-11-05", "2025-03-26"}, shouldSucceed: true, expectedVersion: "2025-03-26", }, { name: "unsupported version", requestedVersion: "2026-01-01", supportedVersions: []string{"2024-11-05", "2025-03-26"}, shouldSucceed: false, expectedVersion: "", }, { name: "fallback to supported version", requestedVersion: "2025-06-18", supportedVersions: []string{"2024-11-05", "2025-03-26"}, shouldSucceed: false, expectedVersion: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Skip until NewMcpVersionNegotiator is implemented t.Skip("Version negotiation not implemented yet") negotiator := NewMcpVersionNegotiator(tt.supportedVersions) version, err := negotiator.NegotiateVersion(tt.requestedVersion) if tt.shouldSucceed { assert.NoError(t, err) assert.Equal(t, tt.expectedVersion, version) } else { assert.Error(t, err) } }) } } // TestMcpInitializeRequest tests the initialize request format and handling func TestMcpInitializeRequest(t *testing.T) { _ = NewMcpProxyServer("init-test") // Skip until CreateInitializeRequest is implemented t.Skip("MCP protocol initialization not implemented yet") // Test initialize request creation initRequest := CreateInitializeRequest() assert.Equal(t, "2.0", initRequest.JsonRPC) assert.Equal(t, "initialize", initRequest.Method) assert.NotNil(t, initRequest.Params) // Validate client info params := initRequest.Params.(map[string]interface{}) clientInfo := params["clientInfo"].(map[string]interface{}) assert.Equal(t, "Higress-mcp-proxy", clientInfo["name"]) assert.Equal(t, "1.0.0", clientInfo["version"]) // Test protocol version assert.Equal(t, "2025-03-26", params["protocolVersion"]) } // TestMcpNotificationsInitialized tests the notifications/initialized message func TestMcpNotificationsInitialized(t *testing.T) { // Skip until CreateInitializedNotification is implemented t.Skip("MCP notifications not implemented yet") // Test notifications/initialized request creation notification := CreateInitializedNotification() assert.Equal(t, "2.0", notification.JsonRPC) assert.Equal(t, "notifications/initialized", notification.Method) assert.Nil(t, notification.ID) // Notifications don't have IDs } // TestMcpErrorHandling tests error response handling and source identification func TestMcpErrorHandling(t *testing.T) { tests := []struct { name string errorType string originalError error expectedSource string expectedCode int }{ { name: "backend connection error", errorType: "connection", originalError: assert.AnError, expectedSource: "mcp-proxy", expectedCode: -32603, }, { name: "backend timeout error", errorType: "timeout", originalError: assert.AnError, expectedSource: "mcp-proxy", expectedCode: -32000, }, { name: "protocol version error", errorType: "version", originalError: assert.AnError, expectedSource: "mcp-proxy", expectedCode: -32602, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Skip until CreateMcpErrorResponse is implemented t.Skip("MCP error handling not implemented yet") errorResponse := CreateMcpErrorResponse(tt.errorType, tt.originalError, "http://backend.example.com/mcp") assert.Equal(t, "2.0", errorResponse.JsonRPC) assert.NotNil(t, errorResponse.Error) assert.Equal(t, tt.expectedCode, errorResponse.Error.Code) assert.Equal(t, tt.expectedSource, errorResponse.Error.Data["source"]) }) } } // Helper types and functions that will fail until implemented type McpSessionManager struct{} func NewMcpSessionManager() *McpSessionManager { panic("McpSessionManager not implemented yet") } func (m *McpSessionManager) CreateSession(backendURL string) (string, error) { panic("CreateSession not implemented yet") } func (m *McpSessionManager) GetSession(sessionID string) (interface{}, bool) { panic("GetSession not implemented yet") } func (m *McpSessionManager) CleanupSession(sessionID string) { panic("CleanupSession not implemented yet") } type McpVersionNegotiator struct { supportedVersions []string } func NewMcpVersionNegotiator(versions []string) *McpVersionNegotiator { panic("McpVersionNegotiator not implemented yet") } func (n *McpVersionNegotiator) NegotiateVersion(requested string) (string, error) { panic("NegotiateVersion not implemented yet") } type McpRequest struct { JsonRPC string `json:"jsonrpc"` ID interface{} `json:"id,omitempty"` Method string `json:"method"` Params interface{} `json:"params,omitempty"` } type McpErrorResponse struct { JsonRPC string `json:"jsonrpc"` ID interface{} `json:"id,omitempty"` Error *McpError `json:"error"` } type McpError struct { Code int `json:"code"` Message string `json:"message"` Data map[string]interface{} `json:"data,omitempty"` } func CreateInitializeRequest() *McpRequest { panic("CreateInitializeRequest not implemented yet") } func CreateInitializedNotification() *McpRequest { panic("CreateInitializedNotification not implemented yet") } func CreateMcpErrorResponse(errorType string, originalError error, backendURL string) *McpErrorResponse { panic("CreateMcpErrorResponse not implemented yet") }