test: add xUnit test coverage for API controllers and client services
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>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-04-04
|
||||
@@ -0,0 +1,45 @@
|
||||
## Context
|
||||
|
||||
No test projects exist. The solution has 3 projects (Client, Api, Shared) under `src/`. Tests need to cover the API controllers and the client's ChatApiClient service, both of which involve HTTP and SSE streaming.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- Establish test infrastructure (xUnit + Moq)
|
||||
- Test API controllers using WebApplicationFactory (integration-style)
|
||||
- Test ChatApiClient using a mock HttpMessageHandler (unit-style)
|
||||
- All tests runnable via `dotnet test` from solution root
|
||||
|
||||
**Non-Goals:**
|
||||
- Blazor component tests (bUnit) — Chat.razor is UI-heavy, defer to a future phase
|
||||
- End-to-end browser tests (Playwright/Selenium)
|
||||
- Testing the upstream Responses API itself
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: Test framework — xUnit + Moq
|
||||
|
||||
xUnit is the .NET standard. Moq for mocking HttpMessageHandler so we can control HTTP responses in tests without hitting real servers.
|
||||
|
||||
### Decision 2: Test project layout
|
||||
|
||||
```
|
||||
tests/
|
||||
├── ChatAgent.Api.Tests/ → references Api project
|
||||
└── ChatAgent.Client.Tests/ → references Client project + Shared
|
||||
```
|
||||
|
||||
Both added to the `ChatAgent.sln` solution file under a `tests` solution folder.
|
||||
|
||||
### Decision 3: API tests use WebApplicationFactory
|
||||
|
||||
`Microsoft.AspNetCore.Mvc.Testing` provides `WebApplicationFactory<Program>` for integration-style tests. For ChatController, we inject a mock `IHttpClientFactory` that returns a handler with canned SSE responses — no real Responses API needed.
|
||||
|
||||
### Decision 4: Client tests mock HttpMessageHandler
|
||||
|
||||
ChatApiClient takes an HttpClient. Tests create an HttpClient with a custom `DelegatingHandler` that returns canned SSE response streams. This tests the SSE parsing logic in isolation.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [WebApplicationFactory requires Api's Program to be accessible] → Add `InternalsVisibleTo` or use the `public partial class Program {}` trick in Api's Program.cs
|
||||
- [SSE stream mocking is verbose] → Create a small helper method that builds SSE response content from a list of events
|
||||
@@ -0,0 +1,26 @@
|
||||
## Why
|
||||
|
||||
The project has zero test coverage. There are two controllers, a typed HttpClient service, and shared models — all untested. Adding tests now establishes the pattern before the codebase grows, and catches regressions as new phases are added.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Create an xUnit test project for the API (`ChatAgent.Api.Tests`)
|
||||
- Create an xUnit test project for the Client services (`ChatAgent.Client.Tests`)
|
||||
- Add tests for HealthController (GET /api/health)
|
||||
- Add tests for ChatController (POST /api/chat SSE streaming proxy)
|
||||
- Add tests for ChatApiClient (GetHealthAsync, SendChatStreamingAsync)
|
||||
- Add both test projects to the solution
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `test-infrastructure`: Test project setup, test framework choices, and shared test utilities
|
||||
|
||||
### Modified Capabilities
|
||||
<!-- None — adding tests doesn't change existing specs -->
|
||||
|
||||
## Impact
|
||||
|
||||
- `tests/ChatAgent.Api.Tests/`: New xUnit test project
|
||||
- `tests/ChatAgent.Client.Tests/`: New xUnit test project
|
||||
- `ChatAgent.sln`: Add test projects
|
||||
@@ -0,0 +1,56 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: API test project exists
|
||||
|
||||
An xUnit test project SHALL exist at `tests/ChatAgent.Api.Tests/` targeting the API project, with xUnit, Moq, and Microsoft.AspNetCore.Mvc.Testing as dependencies.
|
||||
|
||||
#### Scenario: API tests run
|
||||
|
||||
- **WHEN** `dotnet test` is run from the solution root
|
||||
- **THEN** API tests are discovered and executed
|
||||
|
||||
### Requirement: Client test project exists
|
||||
|
||||
An xUnit test project SHALL exist at `tests/ChatAgent.Client.Tests/` targeting the Client services, with xUnit and Moq as dependencies.
|
||||
|
||||
#### Scenario: Client tests run
|
||||
|
||||
- **WHEN** `dotnet test` is run from the solution root
|
||||
- **THEN** Client service tests are discovered and executed
|
||||
|
||||
### Requirement: HealthController test coverage
|
||||
|
||||
Tests SHALL verify that GET /api/health returns HTTP 200 with a valid HealthResponse containing a non-empty Status and a recent Timestamp.
|
||||
|
||||
#### Scenario: Health endpoint returns 200
|
||||
|
||||
- **WHEN** a GET request is sent to /api/health
|
||||
- **THEN** the response status is 200 and the body contains `status: "healthy"` and a UTC timestamp
|
||||
|
||||
### Requirement: ChatController test coverage
|
||||
|
||||
Tests SHALL verify that POST /api/chat returns a streaming SSE response containing text deltas and a [DONE] terminator. Tests SHALL mock the upstream Responses API.
|
||||
|
||||
#### Scenario: Chat streams text deltas
|
||||
|
||||
- **WHEN** a POST is sent to /api/chat with a valid ChatRequest
|
||||
- **THEN** the response is `text/event-stream` containing `data: {"text":"..."}` events followed by `data: [DONE]`
|
||||
|
||||
#### Scenario: Chat handles upstream error
|
||||
|
||||
- **WHEN** the Responses API is unreachable or returns an error
|
||||
- **THEN** the response contains a `data: {"error":"..."}` event followed by `data: [DONE]`
|
||||
|
||||
### Requirement: ChatApiClient test coverage
|
||||
|
||||
Tests SHALL verify that SendChatStreamingAsync correctly parses SSE events from the backend into text deltas, handles [DONE], and throws on error events.
|
||||
|
||||
#### Scenario: Client parses text deltas
|
||||
|
||||
- **WHEN** the backend returns SSE events with text deltas
|
||||
- **THEN** SendChatStreamingAsync yields each text fragment in order
|
||||
|
||||
#### Scenario: Client handles error event
|
||||
|
||||
- **WHEN** the backend returns an error SSE event
|
||||
- **THEN** SendChatStreamingAsync throws HttpRequestException with the error message
|
||||
@@ -0,0 +1,23 @@
|
||||
## 1. Test Project Setup
|
||||
|
||||
- [x] 1.1 Create xUnit test project at tests/ChatAgent.Api.Tests with xUnit, Moq, Microsoft.AspNetCore.Mvc.Testing
|
||||
- [x] 1.2 Create xUnit test project at tests/ChatAgent.Client.Tests with xUnit, Moq
|
||||
- [x] 1.3 Add both test projects to ChatAgent.sln under a tests solution folder
|
||||
- [x] 1.4 Make Api's Program class accessible to tests (public partial class Program)
|
||||
|
||||
## 2. API Tests
|
||||
|
||||
- [x] 2.1 Test HealthController: GET /api/health returns 200 with valid HealthResponse
|
||||
- [x] 2.2 Test ChatController: POST /api/chat streams SSE text deltas from mocked upstream
|
||||
- [x] 2.3 Test ChatController: POST /api/chat handles upstream error gracefully
|
||||
|
||||
## 3. Client Service Tests
|
||||
|
||||
- [x] 3.1 Create SSE response helper for building mock SSE streams
|
||||
- [x] 3.2 Test ChatApiClient.SendChatStreamingAsync: parses text deltas in order
|
||||
- [x] 3.3 Test ChatApiClient.SendChatStreamingAsync: throws on error event
|
||||
- [x] 3.4 Test ChatApiClient.GetHealthAsync: returns HealthResponse on success
|
||||
|
||||
## 4. Verify
|
||||
|
||||
- [x] 4.1 Run dotnet test from solution root — all tests pass
|
||||
60
openspec/specs/test-infrastructure/spec.md
Normal file
60
openspec/specs/test-infrastructure/spec.md
Normal file
@@ -0,0 +1,60 @@
|
||||
## Purpose
|
||||
|
||||
Define test project setup, framework choices, and required test coverage for the ChatAgent solution.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement: API test project exists
|
||||
|
||||
An xUnit test project SHALL exist at `tests/ChatAgent.Api.Tests/` targeting the API project, with xUnit, Moq, and Microsoft.AspNetCore.Mvc.Testing as dependencies.
|
||||
|
||||
#### Scenario: API tests run
|
||||
|
||||
- **WHEN** `dotnet test` is run from the solution root
|
||||
- **THEN** API tests are discovered and executed
|
||||
|
||||
### Requirement: Client test project exists
|
||||
|
||||
An xUnit test project SHALL exist at `tests/ChatAgent.Client.Tests/` targeting the Client services, with xUnit and Moq as dependencies.
|
||||
|
||||
#### Scenario: Client tests run
|
||||
|
||||
- **WHEN** `dotnet test` is run from the solution root
|
||||
- **THEN** Client service tests are discovered and executed
|
||||
|
||||
### Requirement: HealthController test coverage
|
||||
|
||||
Tests SHALL verify that GET /api/health returns HTTP 200 with a valid HealthResponse containing a non-empty Status and a recent Timestamp.
|
||||
|
||||
#### Scenario: Health endpoint returns 200
|
||||
|
||||
- **WHEN** a GET request is sent to /api/health
|
||||
- **THEN** the response status is 200 and the body contains `status: "healthy"` and a UTC timestamp
|
||||
|
||||
### Requirement: ChatController test coverage
|
||||
|
||||
Tests SHALL verify that POST /api/chat returns a streaming SSE response containing text deltas and a [DONE] terminator. Tests SHALL mock the upstream Responses API.
|
||||
|
||||
#### Scenario: Chat streams text deltas
|
||||
|
||||
- **WHEN** a POST is sent to /api/chat with a valid ChatRequest
|
||||
- **THEN** the response is `text/event-stream` containing `data: {"text":"..."}` events followed by `data: [DONE]`
|
||||
|
||||
#### Scenario: Chat handles upstream error
|
||||
|
||||
- **WHEN** the Responses API is unreachable or returns an error
|
||||
- **THEN** the response contains a `data: {"error":"..."}` event followed by `data: [DONE]`
|
||||
|
||||
### Requirement: ChatApiClient test coverage
|
||||
|
||||
Tests SHALL verify that SendChatStreamingAsync correctly parses SSE events from the backend into text deltas, handles [DONE], and throws on error events.
|
||||
|
||||
#### Scenario: Client parses text deltas
|
||||
|
||||
- **WHEN** the backend returns SSE events with text deltas
|
||||
- **THEN** SendChatStreamingAsync yields each text fragment in order
|
||||
|
||||
#### Scenario: Client handles error event
|
||||
|
||||
- **WHEN** the backend returns an error SSE event
|
||||
- **THEN** SendChatStreamingAsync throws HttpRequestException with the error message
|
||||
Reference in New Issue
Block a user