using Xunit; using Moq; using Moq.Protected; using System.Net; using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Svrnty.MCP.Core; using CodexMcpServer.Tools; namespace CodexMcpServer.Tests; /// /// Unit tests for GetDocumentTool following TDD approach. /// Tests integration with CODEX API /api/documents/{id} endpoint. /// public class GetDocumentToolTests { [Fact] public void GetDocumentTool_ShouldHaveCorrectName() { // Arrange var httpClient = new HttpClient(); var tool = new GetDocumentTool(httpClient); // Act var name = tool.Name; // Assert Assert.Equal("get_document", name); } [Fact] public void GetDocumentTool_ShouldHaveDescription() { // Arrange var httpClient = new HttpClient(); var tool = new GetDocumentTool(httpClient); // Act var description = tool.Description; // Assert Assert.NotNull(description); Assert.NotEmpty(description); Assert.Contains("document", description.ToLower()); } [Fact] public void GetDocumentTool_ShouldHaveValidSchema() { // Arrange var httpClient = new HttpClient(); var tool = new GetDocumentTool(httpClient); // Act var schema = tool.Schema; // Assert Assert.NotNull(schema); var root = schema.RootElement; Assert.Equal(JsonValueKind.Object, root.ValueKind); // Schema should define required "id" parameter Assert.True(root.TryGetProperty("properties", out var properties)); Assert.True(properties.TryGetProperty("id", out var idProp)); Assert.True(idProp.TryGetProperty("type", out var idType)); Assert.Equal("string", idType.GetString()); } [Fact] public async Task GetDocumentTool_ExecuteAsync_WithValidId_ShouldCallCodexApi() { // Arrange var mockResponse = new { id = "doc123", title = "Test Document", content = "Document content", metadata = new { author = "Test Author" } }; var mockHttpMessageHandler = new Mock(); mockHttpMessageHandler .Protected() .Setup>( "SendAsync", ItExpr.Is(req => req.Method == HttpMethod.Get && req.RequestUri.ToString().Contains("/api/documents/doc123") ), ItExpr.IsAny() ) .ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent(JsonSerializer.Serialize(mockResponse)) }); var httpClient = new HttpClient(mockHttpMessageHandler.Object) { BaseAddress = new System.Uri("http://localhost:5050") }; var tool = new GetDocumentTool(httpClient); var arguments = JsonDocument.Parse("""{"id": "doc123"}"""); // Act var result = await tool.ExecuteAsync(arguments); // Assert Assert.NotNull(result); var root = result.RootElement; Assert.True(root.TryGetProperty("id", out var id)); Assert.Equal("doc123", id.GetString()); } [Fact] public async Task GetDocumentTool_ExecuteAsync_WithEmptyId_ShouldReturnError() { // Arrange var httpClient = new HttpClient(); var tool = new GetDocumentTool(httpClient); var arguments = JsonDocument.Parse("""{"id": ""}"""); // Act var result = await tool.ExecuteAsync(arguments); // Assert Assert.NotNull(result); var root = result.RootElement; Assert.True(root.TryGetProperty("error", out var error)); Assert.Contains("id", error.GetString().ToLower()); } [Fact] public async Task GetDocumentTool_ExecuteAsync_WithNullArguments_ShouldReturnError() { // Arrange var httpClient = new HttpClient(); var tool = new GetDocumentTool(httpClient); // Act var result = await tool.ExecuteAsync(null); // Assert Assert.NotNull(result); var root = result.RootElement; Assert.True(root.TryGetProperty("error", out _)); } [Fact] public async Task GetDocumentTool_ExecuteAsync_WithMissingIdProperty_ShouldReturnError() { // Arrange var httpClient = new HttpClient(); var tool = new GetDocumentTool(httpClient); var arguments = JsonDocument.Parse("""{"other": "value"}"""); // Act var result = await tool.ExecuteAsync(arguments); // Assert Assert.NotNull(result); var root = result.RootElement; Assert.True(root.TryGetProperty("error", out _)); } [Fact] public async Task GetDocumentTool_ExecuteAsync_WithNotFound_ShouldReturnError() { // Arrange var mockHttpMessageHandler = new Mock(); mockHttpMessageHandler .Protected() .Setup>( "SendAsync", ItExpr.IsAny(), ItExpr.IsAny() ) .ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.NotFound, Content = new StringContent("Document not found") }); var httpClient = new HttpClient(mockHttpMessageHandler.Object) { BaseAddress = new System.Uri("http://localhost:5050") }; var tool = new GetDocumentTool(httpClient); var arguments = JsonDocument.Parse("""{"id": "nonexistent"}"""); // Act var result = await tool.ExecuteAsync(arguments); // Assert Assert.NotNull(result); var root = result.RootElement; Assert.True(root.TryGetProperty("error", out _)); } [Fact] public async Task GetDocumentTool_ExecuteAsync_WithHttpError_ShouldReturnErrorResponse() { // Arrange var mockHttpMessageHandler = new Mock(); mockHttpMessageHandler .Protected() .Setup>( "SendAsync", ItExpr.IsAny(), ItExpr.IsAny() ) .ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.InternalServerError, Content = new StringContent("Internal server error") }); var httpClient = new HttpClient(mockHttpMessageHandler.Object) { BaseAddress = new System.Uri("http://localhost:5050") }; var tool = new GetDocumentTool(httpClient); var arguments = JsonDocument.Parse("""{"id": "doc123"}"""); // Act var result = await tool.ExecuteAsync(arguments); // Assert Assert.NotNull(result); var root = result.RootElement; Assert.True(root.TryGetProperty("error", out _)); } [Fact] public async Task GetDocumentTool_ExecuteAsync_WithInvalidJson_ShouldReturnError() { // Arrange var mockHttpMessageHandler = new Mock(); mockHttpMessageHandler .Protected() .Setup>( "SendAsync", ItExpr.IsAny(), ItExpr.IsAny() ) .ReturnsAsync(new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent("invalid json{{{") }); var httpClient = new HttpClient(mockHttpMessageHandler.Object) { BaseAddress = new System.Uri("http://localhost:5050") }; var tool = new GetDocumentTool(httpClient); var arguments = JsonDocument.Parse("""{"id": "doc123"}"""); // Act var result = await tool.ExecuteAsync(arguments); // Assert Assert.NotNull(result); var root = result.RootElement; Assert.True(root.TryGetProperty("error", out _)); } [Fact] public void GetDocumentTool_ShouldImplementIMcpTool() { // Arrange var httpClient = new HttpClient(); // Act var tool = new GetDocumentTool(httpClient); // Assert Assert.IsAssignableFrom(tool); } [Fact] public async Task GetDocumentTool_ExecuteAsync_ShouldUseCorrectEndpoint() { // Arrange string capturedUri = null; var mockHttpMessageHandler = new Mock(); mockHttpMessageHandler .Protected() .Setup>( "SendAsync", ItExpr.IsAny(), ItExpr.IsAny() ) .ReturnsAsync((HttpRequestMessage req, CancellationToken ct) => { capturedUri = req.RequestUri?.ToString(); return new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent("""{"id": "test123"}""") }; }); var httpClient = new HttpClient(mockHttpMessageHandler.Object) { BaseAddress = new System.Uri("http://localhost:5050") }; var tool = new GetDocumentTool(httpClient); var arguments = JsonDocument.Parse("""{"id": "test123"}"""); // Act await tool.ExecuteAsync(arguments); // Assert Assert.NotNull(capturedUri); Assert.Contains("/api/documents/test123", capturedUri); Assert.DoesNotContain("POST", capturedUri); // Should be GET } }