Create ChatAgent.Api.Tests and ChatAgent.Client.Tests projects with xUnit and Moq. Test HealthController (200 + valid response), ChatController (SSE streaming with mocked upstream, error handling), and ChatApiClient (delta parsing, error events, health endpoint). 6 tests, all passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
142 lines
4.1 KiB
C#
142 lines
4.1 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Text;
|
|
using ChatAgent.Client.Services;
|
|
using ChatAgent.Shared.Models;
|
|
|
|
namespace ChatAgent.Client.Tests;
|
|
|
|
public class ChatApiClientTests
|
|
{
|
|
[Fact]
|
|
public async Task SendChatStreamingAsync_ParsesTextDeltas_InOrder()
|
|
{
|
|
// Arrange: build a canned SSE response with two text deltas
|
|
var sse = BuildSSE(
|
|
("text", "Hello"),
|
|
("text", " world")
|
|
);
|
|
|
|
var handler = new FakeHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent(sse, Encoding.UTF8, "text/event-stream")
|
|
});
|
|
|
|
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
|
var client = new ChatApiClient(httpClient);
|
|
|
|
var request = new ChatRequest
|
|
{
|
|
Messages = new List<ChatMessage>
|
|
{
|
|
new() { Role = "user", Content = "Hi" }
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var deltas = new List<string>();
|
|
await foreach (var delta in client.SendChatStreamingAsync(request))
|
|
{
|
|
deltas.Add(delta);
|
|
}
|
|
|
|
// Assert
|
|
Assert.Equal(2, deltas.Count);
|
|
Assert.Equal("Hello", deltas[0]);
|
|
Assert.Equal(" world", deltas[1]);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SendChatStreamingAsync_ThrowsOnErrorEvent()
|
|
{
|
|
// Arrange
|
|
var sse = "data: {\"error\":\"Something went wrong\"}\n\n" +
|
|
"data: [DONE]\n\n";
|
|
|
|
var handler = new FakeHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent(sse, Encoding.UTF8, "text/event-stream")
|
|
});
|
|
|
|
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
|
var client = new ChatApiClient(httpClient);
|
|
|
|
var request = new ChatRequest
|
|
{
|
|
Messages = new List<ChatMessage>
|
|
{
|
|
new() { Role = "user", Content = "Hi" }
|
|
}
|
|
};
|
|
|
|
// Act & Assert
|
|
var ex = await Assert.ThrowsAsync<HttpRequestException>(async () =>
|
|
{
|
|
await foreach (var _ in client.SendChatStreamingAsync(request))
|
|
{
|
|
}
|
|
});
|
|
|
|
Assert.Contains("Something went wrong", ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetHealthAsync_ReturnsHealthResponse()
|
|
{
|
|
// Arrange
|
|
var healthJson = "{\"status\":\"healthy\",\"timestamp\":\"2026-04-04T12:00:00Z\"}";
|
|
var handler = new FakeHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent(healthJson, Encoding.UTF8, "application/json")
|
|
});
|
|
|
|
var httpClient = new HttpClient(handler) { BaseAddress = new Uri("http://localhost") };
|
|
var client = new ChatApiClient(httpClient);
|
|
|
|
// Act
|
|
var result = await client.GetHealthAsync();
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.Equal("healthy", result.Status);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds simplified SSE content from (type, value) pairs.
|
|
/// Each pair becomes: data: {"text":"value"}\n\n
|
|
/// Ends with data: [DONE]\n\n
|
|
/// </summary>
|
|
private static string BuildSSE(params (string key, string value)[] events)
|
|
{
|
|
var sb = new StringBuilder();
|
|
foreach (var (key, value) in events)
|
|
{
|
|
sb.AppendLine($"data: {{\"{key}\":\"{value}\"}}");
|
|
sb.AppendLine();
|
|
}
|
|
sb.AppendLine("data: [DONE]");
|
|
sb.AppendLine();
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Simple fake HttpMessageHandler for unit testing HttpClient-based services.
|
|
/// Returns a canned response for any request.
|
|
/// </summary>
|
|
public class FakeHttpMessageHandler : HttpMessageHandler
|
|
{
|
|
private readonly HttpResponseMessage _response;
|
|
|
|
public FakeHttpMessageHandler(HttpResponseMessage response)
|
|
{
|
|
_response = response;
|
|
}
|
|
|
|
protected override Task<HttpResponseMessage> SendAsync(
|
|
HttpRequestMessage request, CancellationToken cancellationToken)
|
|
{
|
|
return Task.FromResult(_response);
|
|
}
|
|
}
|