using System.Net; using System.Net.Http.Json; using System.Text; using System.Text.Json; using ColaFlow.Modules.Mcp.Contracts.JsonRpc; using ColaFlow.Modules.Mcp.Contracts.Mcp; using FluentAssertions; using Microsoft.AspNetCore.Mvc.Testing; namespace ColaFlow.IntegrationTests.Mcp; /// /// Integration tests for MCP Protocol endpoint /// public class McpProtocolIntegrationTests : IClassFixture> { private readonly HttpClient _client; public McpProtocolIntegrationTests(WebApplicationFactory factory) { _client = factory.CreateClient(); } [Fact] public async Task McpEndpoint_WithInitializeRequest_ReturnsSuccess() { // Arrange var request = new JsonRpcRequest { JsonRpc = "2.0", Method = "initialize", Params = new { protocolVersion = "1.0", clientInfo = new { name = "Test Client", version = "1.0.0" } }, Id = 1 }; var json = JsonSerializer.Serialize(request, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/mcp", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); response.Content.Headers.ContentType?.MediaType.Should().Be("application/json"); var responseJson = await response.Content.ReadAsStringAsync(); var rpcResponse = JsonSerializer.Deserialize(responseJson); rpcResponse.Should().NotBeNull(); rpcResponse!.JsonRpc.Should().Be("2.0"); rpcResponse.Id.Should().NotBeNull(); rpcResponse.Error.Should().BeNull(); rpcResponse.Result.Should().NotBeNull(); } [Fact] public async Task McpEndpoint_InitializeResponse_ContainsServerInfo() { // Arrange var request = new JsonRpcRequest { JsonRpc = "2.0", Method = "initialize", Params = new { protocolVersion = "1.0", clientInfo = new { name = "Test", version = "1.0" } }, Id = 1 }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/mcp", content); var responseJson = await response.Content.ReadAsStringAsync(); var rpcResponse = JsonSerializer.Deserialize(responseJson); // Assert rpcResponse.GetProperty("result").TryGetProperty("serverInfo", out var serverInfo).Should().BeTrue(); serverInfo.GetProperty("name").GetString().Should().Be("ColaFlow MCP Server"); serverInfo.GetProperty("version").GetString().Should().NotBeNullOrEmpty(); } [Fact] public async Task McpEndpoint_InitializeResponse_ContainsCapabilities() { // Arrange var request = new JsonRpcRequest { JsonRpc = "2.0", Method = "initialize", Params = new { protocolVersion = "1.0", clientInfo = new { name = "Test", version = "1.0" } }, Id = 1 }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/mcp", content); var responseJson = await response.Content.ReadAsStringAsync(); var rpcResponse = JsonSerializer.Deserialize(responseJson); // Assert rpcResponse.GetProperty("result").TryGetProperty("capabilities", out var capabilities).Should().BeTrue(); capabilities.TryGetProperty("resources", out var resources).Should().BeTrue(); resources.GetProperty("supported").GetBoolean().Should().BeTrue(); capabilities.TryGetProperty("tools", out var tools).Should().BeTrue(); tools.GetProperty("supported").GetBoolean().Should().BeTrue(); capabilities.TryGetProperty("prompts", out var prompts).Should().BeTrue(); prompts.GetProperty("supported").GetBoolean().Should().BeTrue(); } [Fact] public async Task McpEndpoint_WithInvalidJson_ReturnsParseError() { // Arrange var invalidJson = "{ invalid json }"; var content = new StringContent(invalidJson, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/mcp", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var responseJson = await response.Content.ReadAsStringAsync(); var rpcResponse = JsonSerializer.Deserialize(responseJson); rpcResponse.Should().NotBeNull(); rpcResponse!.Error.Should().NotBeNull(); rpcResponse.Error!.Code.Should().Be((int)JsonRpcErrorCode.ParseError); } [Fact] public async Task McpEndpoint_WithUnknownMethod_ReturnsMethodNotFound() { // Arrange var request = new JsonRpcRequest { JsonRpc = "2.0", Method = "unknown_method", Id = 1 }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/mcp", content); var responseJson = await response.Content.ReadAsStringAsync(); var rpcResponse = JsonSerializer.Deserialize(responseJson); // Assert rpcResponse.Should().NotBeNull(); rpcResponse!.Error.Should().NotBeNull(); rpcResponse.Error!.Code.Should().Be((int)JsonRpcErrorCode.MethodNotFound); rpcResponse.Error.Message.Should().Contain("unknown_method"); } [Fact] public async Task McpEndpoint_WithResourcesList_ReturnsEmptyList() { // Arrange var request = new JsonRpcRequest { JsonRpc = "2.0", Method = "resources/list", Id = 1 }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/mcp", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var responseJson = await response.Content.ReadAsStringAsync(); var rpcResponse = JsonSerializer.Deserialize(responseJson); rpcResponse.TryGetProperty("result", out var result).Should().BeTrue(); result.TryGetProperty("resources", out var resources).Should().BeTrue(); resources.GetArrayLength().Should().Be(0); } [Fact] public async Task McpEndpoint_WithToolsList_ReturnsEmptyList() { // Arrange var request = new JsonRpcRequest { JsonRpc = "2.0", Method = "tools/list", Id = 1 }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/mcp", content); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); var responseJson = await response.Content.ReadAsStringAsync(); var rpcResponse = JsonSerializer.Deserialize(responseJson); rpcResponse.TryGetProperty("result", out var result).Should().BeTrue(); result.TryGetProperty("tools", out var tools).Should().BeTrue(); tools.GetArrayLength().Should().Be(0); } [Fact] public async Task McpEndpoint_WithNotification_Returns204NoContent() { // Arrange - Notification has no "id" field var request = new { jsonrpc = "2.0", method = "notification_method", @params = new { test = "value" } // No "id" field = notification }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Act var response = await _client.PostAsync("/mcp", content); // Assert // Notifications should return 204 or not return a response // For now, check that it doesn't fail response.StatusCode.Should().BeOneOf(HttpStatusCode.NoContent, HttpStatusCode.OK); } [Fact] public async Task McpEndpoint_ProtocolOverhead_IsLessThan5Milliseconds() { // Arrange var request = new JsonRpcRequest { JsonRpc = "2.0", Method = "initialize", Params = new { protocolVersion = "1.0", clientInfo = new { name = "Test", version = "1.0" } }, Id = 1 }; var json = JsonSerializer.Serialize(request); var content = new StringContent(json, Encoding.UTF8, "application/json"); // Warmup await _client.PostAsync("/mcp", content); // Act var stopwatch = System.Diagnostics.Stopwatch.StartNew(); var response = await _client.PostAsync("/mcp", new StringContent(json, Encoding.UTF8, "application/json")); stopwatch.Stop(); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); // Note: This includes network overhead in tests, actual protocol overhead will be much less stopwatch.ElapsedMilliseconds.Should().BeLessThan(100); // Generous for integration test } }