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 SearchCodexTool following TDD approach. /// Tests integration with CODEX API /api/documents/search endpoint. /// public class SearchCodexToolTests { [Fact] public void SearchCodexTool_ShouldHaveCorrectName() { // Arrange var httpClient = new HttpClient(); var tool = new SearchCodexTool(httpClient); // Act var name = tool.Name; // Assert Assert.Equal("search_codex", name); } [Fact] public void SearchCodexTool_ShouldHaveDescription() { // Arrange var httpClient = new HttpClient(); var tool = new SearchCodexTool(httpClient); // Act var description = tool.Description; // Assert Assert.NotNull(description); Assert.NotEmpty(description); Assert.Contains("search", description.ToLower()); } [Fact] public void SearchCodexTool_ShouldHaveValidSchema() { // Arrange var httpClient = new HttpClient(); var tool = new SearchCodexTool(httpClient); // Act var schema = tool.Schema; // Assert Assert.NotNull(schema); var root = schema.RootElement; Assert.Equal(JsonValueKind.Object, root.ValueKind); // Schema should define required "query" parameter Assert.True(root.TryGetProperty("properties", out var properties)); Assert.True(properties.TryGetProperty("query", out var queryProp)); Assert.True(queryProp.TryGetProperty("type", out var queryType)); Assert.Equal("string", queryType.GetString()); } [Fact] public async Task SearchCodexTool_ExecuteAsync_WithValidQuery_ShouldCallCodexApi() { // Arrange var mockResponse = new { documents = new[] { new { id = "doc1", title = "Test Document", content = "Test content" } }, total = 1 }; var mockHttpMessageHandler = new Mock(); mockHttpMessageHandler .Protected() .Setup>( "SendAsync", ItExpr.Is(req => req.Method == HttpMethod.Post && req.RequestUri.ToString().Contains("/api/documents/search") ), 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 SearchCodexTool(httpClient); var arguments = JsonDocument.Parse("""{"query": "test search"}"""); // Act var result = await tool.ExecuteAsync(arguments); // Assert Assert.NotNull(result); var root = result.RootElement; Assert.True(root.TryGetProperty("documents", out _)); } [Fact] public async Task SearchCodexTool_ExecuteAsync_WithEmptyQuery_ShouldReturnError() { // Arrange var httpClient = new HttpClient(); var tool = new SearchCodexTool(httpClient); var arguments = JsonDocument.Parse("""{"query": ""}"""); // 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("query", error.GetString().ToLower()); } [Fact] public async Task SearchCodexTool_ExecuteAsync_WithNullArguments_ShouldReturnError() { // Arrange var httpClient = new HttpClient(); var tool = new SearchCodexTool(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 SearchCodexTool_ExecuteAsync_WithMissingQueryProperty_ShouldReturnError() { // Arrange var httpClient = new HttpClient(); var tool = new SearchCodexTool(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 SearchCodexTool_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 SearchCodexTool(httpClient); var arguments = JsonDocument.Parse("""{"query": "test"}"""); // 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 SearchCodexTool_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 SearchCodexTool(httpClient); var arguments = JsonDocument.Parse("""{"query": "test"}"""); // Act var result = await tool.ExecuteAsync(arguments); // Assert Assert.NotNull(result); var root = result.RootElement; Assert.True(root.TryGetProperty("error", out _)); } [Fact] public void SearchCodexTool_ShouldImplementIMcpTool() { // Arrange var httpClient = new HttpClient(); // Act var tool = new SearchCodexTool(httpClient); // Assert Assert.IsAssignableFrom(tool); } [Fact] public async Task SearchCodexTool_ExecuteAsync_ShouldIncludeQueryInRequest() { // Arrange string capturedRequestBody = null; var mockHttpMessageHandler = new Mock(); mockHttpMessageHandler .Protected() .Setup>( "SendAsync", ItExpr.IsAny(), ItExpr.IsAny() ) .ReturnsAsync((HttpRequestMessage req, CancellationToken ct) => { capturedRequestBody = req.Content?.ReadAsStringAsync().Result; return new HttpResponseMessage { StatusCode = HttpStatusCode.OK, Content = new StringContent("""{"documents": [], "total": 0}""") }; }); var httpClient = new HttpClient(mockHttpMessageHandler.Object) { BaseAddress = new System.Uri("http://localhost:5050") }; var tool = new SearchCodexTool(httpClient); var arguments = JsonDocument.Parse("""{"query": "test search query"}"""); // Act await tool.ExecuteAsync(arguments); // Assert Assert.NotNull(capturedRequestBody); Assert.Contains("test search query", capturedRequestBody); } }