svrnty-mcp-server/tests/Svrnty.MCP.Core.Tests/HttpTransportTests.cs
Svrnty 0c27de4162 refactor: rename OpenHarbor.MCP to Svrnty.MCP across all libraries
- Renamed all directories: OpenHarbor.MCP.* → Svrnty.MCP.*
- Updated all namespaces in 179 C# files
- Renamed 20 .csproj files and 3 .sln files
- Updated 193 documentation references
- Updated 33 references in main CODEX codebase
- Updated Codex.sln with new paths
- Build verified: 0 errors

Preparing for extraction to standalone repositories.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-22 21:04:17 -04:00

307 lines
8.9 KiB
C#

using Xunit;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Svrnty.MCP.AspNetCore.Extensions;
using System;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Svrnty.MCP.Core.Tests;
/// <summary>
/// Unit tests for HttpTransport following TDD approach.
/// Tests JSON-RPC 2.0 protocol over HTTP REST endpoints.
/// </summary>
public class HttpTransportTests : IDisposable
{
private readonly WebApplication _app;
private readonly HttpClient _client;
private readonly McpServer _server;
public HttpTransportTests()
{
// Create a test MCP server with a mock tool
var registry = new ToolRegistry();
registry.AddTool(new TestTool());
_server = new McpServer(registry);
// Create test web application
var builder = WebApplication.CreateBuilder();
builder.WebHost.UseTestServer();
builder.Services.AddMcpServer(_server);
_app = builder.Build();
_app.MapMcpEndpoints(_server);
_app.StartAsync().Wait();
_client = _app.GetTestClient();
}
[Fact]
public async Task HttpTransport_HealthEndpoint_ReturnsOk()
{
// Act
var response = await _client.GetAsync("/health");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = await response.Content.ReadAsStringAsync();
Assert.Contains("Healthy", content);
Assert.Contains("MCP Server", content);
}
[Fact]
public async Task HttpTransport_InvokeEndpoint_WithValidRequest_ReturnsSuccess()
{
// Arrange
var request = new
{
jsonrpc = "2.0",
method = "tools/list",
id = "test-1"
};
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("/mcp/invoke", content);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var responseJson = await response.Content.ReadAsStringAsync();
var responseObj = JsonSerializer.Deserialize<JsonDocument>(responseJson);
Assert.NotNull(responseObj);
Assert.Equal("2.0", responseObj.RootElement.GetProperty("jsonrpc").GetString());
Assert.Equal("test-1", responseObj.RootElement.GetProperty("id").GetString());
}
[Fact]
public async Task HttpTransport_InvokeEndpoint_WithEmptyBody_ReturnsBadRequest()
{
// Arrange
var content = new StringContent("", Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("/mcp/invoke", content);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var responseJson = await response.Content.ReadAsStringAsync();
Assert.Contains("error", responseJson);
Assert.Contains("-32700", responseJson); // Parse error code
}
[Fact]
public async Task HttpTransport_InvokeEndpoint_WithInvalidJson_ReturnsBadRequest()
{
// Arrange
var content = new StringContent("{invalid json}", Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("/mcp/invoke", content);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var responseJson = await response.Content.ReadAsStringAsync();
Assert.Contains("error", responseJson);
Assert.Contains("Parse error", responseJson);
}
[Fact]
public async Task HttpTransport_InvokeEndpoint_WithToolsCall_ExecutesTool()
{
// Arrange
var request = new
{
jsonrpc = "2.0",
method = "tools/call",
id = "test-call-1",
@params = new
{
name = "test_tool",
arguments = new
{
query = "test query"
}
}
};
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("/mcp/invoke", content);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var responseJson = await response.Content.ReadAsStringAsync();
var responseObj = JsonSerializer.Deserialize<JsonDocument>(responseJson);
Assert.NotNull(responseObj);
Assert.Equal("test-call-1", responseObj.RootElement.GetProperty("id").GetString());
}
[Fact]
public async Task HttpTransport_ContentTypeHeader_IsApplicationJson()
{
// Arrange
var request = new
{
jsonrpc = "2.0",
method = "tools/list",
id = "test-header"
};
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("/mcp/invoke", content);
// Assert
Assert.Equal("application/json", response.Content.Headers.ContentType?.MediaType);
}
[Fact]
public async Task HttpTransport_MultipleRequests_AllSucceed()
{
// Arrange
var request = new
{
jsonrpc = "2.0",
method = "tools/list",
id = "multi-1"
};
var json = JsonSerializer.Serialize(request);
// Act - Send 5 requests
for (int i = 0; i < 5; i++)
{
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await _client.PostAsync("/mcp/invoke", content);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
[Fact]
public async Task HttpTransport_ToolsListRequest_ReturnsToolInfo()
{
// Arrange
var request = new
{
jsonrpc = "2.0",
method = "tools/list",
id = "list-test"
};
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("/mcp/invoke", content);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var responseJson = await response.Content.ReadAsStringAsync();
Assert.Contains("test_tool", responseJson);
}
[Fact]
public async Task HttpTransport_ServerError_Returns500WithJsonRpcError()
{
// This test would need a tool that throws an exception
// For now, we test the error handling structure
// Arrange
var request = new
{
jsonrpc = "2.0",
method = "invalid/method",
id = "error-test"
};
var json = JsonSerializer.Serialize(request);
var content = new StringContent(json, Encoding.UTF8, "application/json");
// Act
var response = await _client.PostAsync("/mcp/invoke", content);
// Assert - Should handle gracefully
var responseJson = await response.Content.ReadAsStringAsync();
Assert.Contains("jsonrpc", responseJson);
}
[Fact]
public void HttpTransport_AddMcpServer_ThrowsOnNullServices()
{
// Arrange
IServiceCollection? services = null;
var server = new McpServer(new ToolRegistry());
// Act & Assert
Assert.Throws<ArgumentNullException>(() =>
services!.AddMcpServer(server)
);
}
[Fact]
public void HttpTransport_AddMcpServer_ThrowsOnNullServer()
{
// Arrange
var services = new ServiceCollection();
McpServer? server = null;
// Act & Assert
Assert.Throws<ArgumentNullException>(() =>
services.AddMcpServer(server!)
);
}
public void Dispose()
{
_client?.Dispose();
_app?.DisposeAsync().AsTask().Wait();
}
/// <summary>
/// Test tool for HTTP transport tests.
/// </summary>
private class TestTool : IMcpTool
{
public string Name => "test_tool";
public string Description => "Test tool for HTTP transport tests";
public JsonDocument Schema => JsonDocument.Parse("""
{
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Test query parameter"
}
}
}
""");
public async Task<JsonDocument> ExecuteAsync(JsonDocument? arguments)
{
await Task.CompletedTask;
return JsonDocument.Parse("""{"result": "success", "tool": "test_tool"}""");
}
}
}