svrnty-mcp-server/tests/Svrnty.MCP.Core.Tests/StdioTransportTests.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

260 lines
7.7 KiB
C#

using Xunit;
using Moq;
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace Svrnty.MCP.Core.Tests;
/// <summary>
/// Unit tests for StdioTransport following TDD approach.
/// Tests JSON-RPC 2.0 protocol over stdin/stdout communication.
/// </summary>
public class StdioTransportTests
{
[Fact]
public void StdioTransport_ShouldInitializeWithStreams()
{
// Arrange
var inputStream = new MemoryStream();
var outputStream = new MemoryStream();
// Act
var transport = new StdioTransport(inputStream, outputStream);
// Assert
Assert.NotNull(transport);
}
[Fact]
public void StdioTransport_ShouldThrowOnNullInputStream()
{
// Arrange
var outputStream = new MemoryStream();
// Act & Assert
Assert.Throws<ArgumentNullException>(() =>
new StdioTransport(null!, outputStream)
);
}
[Fact]
public void StdioTransport_ShouldThrowOnNullOutputStream()
{
// Arrange
var inputStream = new MemoryStream();
// Act & Assert
Assert.Throws<ArgumentNullException>(() =>
new StdioTransport(inputStream, null!)
);
}
[Fact]
public async Task StdioTransport_ReadRequestAsync_ShouldParseValidJsonRpcRequest()
{
// Arrange
var requestJson = """
{"jsonrpc":"2.0","method":"tools/list","id":"1"}
"""; // Newline-delimited
var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(requestJson));
var outputStream = new MemoryStream();
var transport = new StdioTransport(inputStream, outputStream);
// Act
var request = await transport.ReadRequestAsync();
// Assert
Assert.NotNull(request);
Assert.Equal("2.0", request.JsonRpc);
Assert.Equal("tools/list", request.Method);
Assert.Equal("1", request.Id);
}
[Fact]
public async Task StdioTransport_ReadRequestAsync_WithParams_ShouldParseParams()
{
// Arrange
var requestJson = """
{"jsonrpc":"2.0","method":"tools/call","id":"2","params":{"name":"search_codex","arguments":{"query":"test"}}}
""";
var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(requestJson));
var outputStream = new MemoryStream();
var transport = new StdioTransport(inputStream, outputStream);
// Act
var request = await transport.ReadRequestAsync();
// Assert
Assert.NotNull(request);
Assert.NotNull(request.Params);
Assert.True(request.Params.RootElement.TryGetProperty("name", out var name));
Assert.Equal("search_codex", name.GetString());
}
[Fact]
public async Task StdioTransport_ReadRequestAsync_WithEndOfStream_ShouldReturnNull()
{
// Arrange
var inputStream = new MemoryStream(); // Empty stream
var outputStream = new MemoryStream();
var transport = new StdioTransport(inputStream, outputStream);
// Act
var request = await transport.ReadRequestAsync();
// Assert
Assert.Null(request);
}
[Fact]
public async Task StdioTransport_WriteResponseAsync_ShouldWriteValidJsonRpcResponse()
{
// Arrange
var inputStream = new MemoryStream();
var outputStream = new MemoryStream();
var transport = new StdioTransport(inputStream, outputStream);
var response = new McpResponse
{
Id = "1",
Result = JsonDocument.Parse("""{"tools":[]}""")
};
// Act
await transport.WriteResponseAsync(response);
// Assert
outputStream.Position = 0;
var outputText = Encoding.UTF8.GetString(outputStream.ToArray());
Assert.Contains("\"jsonrpc\":\"2.0\"", outputText);
Assert.Contains("\"id\":\"1\"", outputText);
Assert.Contains("\"result\"", outputText);
Assert.EndsWith("\n", outputText); // Should end with newline
}
[Fact]
public async Task StdioTransport_WriteResponseAsync_WithError_ShouldWriteErrorResponse()
{
// Arrange
var inputStream = new MemoryStream();
var outputStream = new MemoryStream();
var transport = new StdioTransport(inputStream, outputStream);
var response = new McpResponse
{
Id = "1",
Error = new McpError
{
Code = -32601,
Message = "Method not found"
}
};
// Act
await transport.WriteResponseAsync(response);
// Assert
outputStream.Position = 0;
var outputText = Encoding.UTF8.GetString(outputStream.ToArray());
Assert.Contains("\"error\"", outputText);
Assert.Contains("\"code\":-32601", outputText);
Assert.Contains("Method not found", outputText);
Assert.DoesNotContain("\"result\"", outputText);
}
[Fact]
public async Task StdioTransport_WriteResponseAsync_ShouldFlushOutput()
{
// Arrange
var inputStream = new MemoryStream();
var outputStream = new MemoryStream();
var transport = new StdioTransport(inputStream, outputStream);
var response = new McpResponse
{
Id = "1",
Result = JsonDocument.Parse("""{"status":"ok"}""")
};
// Act
await transport.WriteResponseAsync(response);
// Assert - data should be immediately available in stream
outputStream.Position = 0;
Assert.True(outputStream.Length > 0);
}
[Fact]
public async Task StdioTransport_ReadRequestAsync_WithInvalidJson_ShouldThrowJsonException()
{
// Arrange
var invalidJson = "not valid json{{{";
var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJson + "\n"));
var outputStream = new MemoryStream();
var transport = new StdioTransport(inputStream, outputStream);
// Act & Assert
await Assert.ThrowsAsync<JsonException>(async () =>
await transport.ReadRequestAsync()
);
}
[Fact]
public async Task StdioTransport_ReadRequestAsync_MultipleRequests_ShouldReadSequentially()
{
// Arrange
var requests = """
{"jsonrpc":"2.0","method":"tools/list","id":"1"}
{"jsonrpc":"2.0","method":"tools/call","id":"2"}
""";
var inputStream = new MemoryStream(Encoding.UTF8.GetBytes(requests));
var outputStream = new MemoryStream();
var transport = new StdioTransport(inputStream, outputStream);
// Act
var request1 = await transport.ReadRequestAsync();
var request2 = await transport.ReadRequestAsync();
// Assert
Assert.NotNull(request1);
Assert.Equal("1", request1.Id);
Assert.Equal("tools/list", request1.Method);
Assert.NotNull(request2);
Assert.Equal("2", request2.Id);
Assert.Equal("tools/call", request2.Method);
}
[Fact]
public void StdioTransport_Dispose_ShouldNotThrow()
{
// Arrange
var inputStream = new MemoryStream();
var outputStream = new MemoryStream();
var transport = new StdioTransport(inputStream, outputStream);
// Act & Assert - should not throw
transport.Dispose();
}
[Fact]
public async Task StdioTransport_WriteResponseAsync_NullResponse_ShouldThrow()
{
// Arrange
var inputStream = new MemoryStream();
var outputStream = new MemoryStream();
var transport = new StdioTransport(inputStream, outputStream);
// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(async () =>
await transport.WriteResponseAsync(null!)
);
}
}