using Xunit; using Moq; using System.Text.Json; using System.Threading.Tasks; namespace OpenHarbor.MCP.Core.Tests; /// /// Unit tests for McpServer following TDD approach. /// Tests MCP protocol method routing and execution. /// public class McpServerTests { [Fact] public void McpServer_ShouldInitializeWithToolRegistry() { // Arrange var registry = new ToolRegistry(); // Act var server = new McpServer(registry); // Assert Assert.NotNull(server); } [Fact] public void McpServer_ShouldThrowOnNullRegistry() { // Act & Assert Assert.Throws(() => new McpServer(null!) ); } [Fact] public async Task McpServer_HandleRequest_WithToolsList_ShouldReturnToolDescriptions() { // Arrange var registry = new ToolRegistry(); var mockTool = CreateMockTool("test_tool", "Test tool description"); registry.AddTool(mockTool.Object); var server = new McpServer(registry); var request = new McpRequest { Id = "1", Method = "tools/list" }; // Act var response = await server.HandleRequestAsync(request); // Assert Assert.NotNull(response); Assert.Equal("1", response.Id); Assert.Null(response.Error); Assert.NotNull(response.Result); var result = response.Result.RootElement; Assert.True(result.TryGetProperty("tools", out var tools)); Assert.Equal(JsonValueKind.Array, tools.ValueKind); Assert.Equal(1, tools.GetArrayLength()); var tool = tools[0]; Assert.True(tool.TryGetProperty("name", out var name)); Assert.Equal("test_tool", name.GetString()); Assert.True(tool.TryGetProperty("description", out var desc)); Assert.Equal("Test tool description", desc.GetString()); } [Fact] public async Task McpServer_HandleRequest_WithToolsList_EmptyRegistry_ShouldReturnEmptyArray() { // Arrange var registry = new ToolRegistry(); var server = new McpServer(registry); var request = new McpRequest { Id = "1", Method = "tools/list" }; // Act var response = await server.HandleRequestAsync(request); // Assert Assert.NotNull(response); Assert.Null(response.Error); Assert.NotNull(response.Result); var result = response.Result.RootElement; Assert.True(result.TryGetProperty("tools", out var tools)); Assert.Equal(0, tools.GetArrayLength()); } [Fact] public async Task McpServer_HandleRequest_WithToolsCall_ShouldExecuteTool() { // Arrange var registry = new ToolRegistry(); var mockTool = CreateMockTool("search_codex", "Search tool"); mockTool.Setup(t => t.ExecuteAsync(It.IsAny())) .ReturnsAsync(JsonDocument.Parse("""{"results": ["doc1", "doc2"]}""")); registry.AddTool(mockTool.Object); var server = new McpServer(registry); var request = new McpRequest { Id = "2", Method = "tools/call", Params = JsonDocument.Parse("""{"name":"search_codex","arguments":{"query":"test"}}""") }; // Act var response = await server.HandleRequestAsync(request); // Assert Assert.NotNull(response); Assert.Equal("2", response.Id); Assert.Null(response.Error); Assert.NotNull(response.Result); var result = response.Result.RootElement; Assert.True(result.TryGetProperty("results", out var results)); mockTool.Verify(t => t.ExecuteAsync(It.IsAny()), Times.Once); } [Fact] public async Task McpServer_HandleRequest_WithToolsCall_NonexistentTool_ShouldReturnError() { // Arrange var registry = new ToolRegistry(); var server = new McpServer(registry); var request = new McpRequest { Id = "3", Method = "tools/call", Params = JsonDocument.Parse("""{"name":"nonexistent_tool","arguments":{}}""") }; // Act var response = await server.HandleRequestAsync(request); // Assert Assert.NotNull(response); Assert.Equal("3", response.Id); Assert.Null(response.Result); Assert.NotNull(response.Error); Assert.Equal(-32601, response.Error.Code); // Method not found Assert.Contains("nonexistent_tool", response.Error.Message); } [Fact] public async Task McpServer_HandleRequest_WithUnknownMethod_ShouldReturnError() { // Arrange var registry = new ToolRegistry(); var server = new McpServer(registry); var request = new McpRequest { Id = "4", Method = "unknown/method" }; // Act var response = await server.HandleRequestAsync(request); // Assert Assert.NotNull(response); Assert.Equal("4", response.Id); Assert.Null(response.Result); Assert.NotNull(response.Error); Assert.Equal(-32601, response.Error.Code); // Method not found Assert.Contains("unknown/method", response.Error.Message); } [Fact] public async Task McpServer_HandleRequest_WithToolsCall_MissingParams_ShouldReturnError() { // Arrange var registry = new ToolRegistry(); var server = new McpServer(registry); var request = new McpRequest { Id = "5", Method = "tools/call" // No Params }; // Act var response = await server.HandleRequestAsync(request); // Assert Assert.NotNull(response); Assert.Equal("5", response.Id); Assert.Null(response.Result); Assert.NotNull(response.Error); Assert.Equal(-32602, response.Error.Code); // Invalid params } [Fact] public async Task McpServer_HandleRequest_WithToolsCall_MissingToolName_ShouldReturnError() { // Arrange var registry = new ToolRegistry(); var server = new McpServer(registry); var request = new McpRequest { Id = "6", Method = "tools/call", Params = JsonDocument.Parse("""{"arguments":{}}""") // Missing "name" }; // Act var response = await server.HandleRequestAsync(request); // Assert Assert.NotNull(response); Assert.Equal("6", response.Id); Assert.Null(response.Result); Assert.NotNull(response.Error); Assert.Equal(-32602, response.Error.Code); // Invalid params } [Fact] public async Task McpServer_HandleRequest_WithNullRequest_ShouldThrow() { // Arrange var registry = new ToolRegistry(); var server = new McpServer(registry); // Act & Assert await Assert.ThrowsAsync(async () => await server.HandleRequestAsync(null!) ); } [Fact] public async Task McpServer_HandleRequest_WithToolsList_MultipleTools_ShouldReturnAll() { // Arrange var registry = new ToolRegistry(); registry.AddTool(CreateMockTool("tool1", "First tool").Object); registry.AddTool(CreateMockTool("tool2", "Second tool").Object); registry.AddTool(CreateMockTool("tool3", "Third tool").Object); var server = new McpServer(registry); var request = new McpRequest { Id = "7", Method = "tools/list" }; // Act var response = await server.HandleRequestAsync(request); // Assert Assert.NotNull(response); Assert.NotNull(response.Result); var result = response.Result.RootElement; Assert.True(result.TryGetProperty("tools", out var tools)); Assert.Equal(3, tools.GetArrayLength()); } // Helper method to create mock tools private Mock CreateMockTool(string name, string description) { var mockTool = new Mock(); mockTool.Setup(t => t.Name).Returns(name); mockTool.Setup(t => t.Description).Returns(description); mockTool.Setup(t => t.Schema).Returns(JsonDocument.Parse("""{"type": "object"}""")); mockTool.Setup(t => t.ExecuteAsync(It.IsAny())) .ReturnsAsync(JsonDocument.Parse("""{"status": "ok"}""")); return mockTool; } }