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;
}
}