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 OpenHarbor.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
}
}