TST-36: Golden path integration test for capture-to-board pipeline#735
TST-36: Golden path integration test for capture-to-board pipeline#735Chris0Jeky merged 3 commits intomainfrom
Conversation
Exercises the full capture -> triage -> proposal -> review -> board mutation pipeline with 7 test scenarios: - Happy path: single capture creates card on board - Multi-operation: checklist capture creates multiple cards - Rejection: rejected proposal leaves board unchanged - Cross-user isolation: users cannot see/approve other users' proposals - Audit trail: card creation is recorded in audit log - Provenance integrity: backward-traceable chain from card to capture - Triage failure: deterministic failure when board is missing
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Adversarial Self-ReviewDoes the test actually prove the full pipeline?Yes, with caveats. The happy path test ( Gaps identified
Is the setup realistic?Yes. The test uses the same Edge cases covered
VerdictThe core pipeline coverage is solid. The missing expiry and retry scenarios are already tested elsewhere and would add complexity without meaningful new coverage. No action items. |
There was a problem hiding this comment.
Code Review
This pull request introduces a new integration test suite, CaptureToBoardGoldenPathIntegrationTests, which validates the end-to-end pipeline from item capture to board mutation. The tests cover various scenarios including successful conversions, multi-card creation, proposal rejections, and security isolation. Review feedback suggests refactoring the test setup logic into helper methods to reduce code duplication and refining audit log assertions for better precision.
| var user = await ApiTestHarness.AuthenticateAsync(client, "golden-happy"); | ||
| var board = await ApiTestHarness.CreateBoardAsync(client, "golden-happy-board"); | ||
|
|
||
| var columnResponse = await client.PostAsJsonAsync( | ||
| $"/api/boards/{board.Id}/columns", | ||
| new CreateColumnDto(board.Id, "Backlog", null, null)); | ||
| columnResponse.StatusCode.Should().Be(HttpStatusCode.Created); | ||
|
|
There was a problem hiding this comment.
There is a lot of repeated code for setting up a user, board, and column across multiple tests in this file (e.g., HappyPath..., MultiOperation..., Rejection...). To improve maintainability and reduce duplication, consider extracting this setup logic into a private helper method.
For example, you could create a helper method that takes an HttpClient and returns the created BoardDto:
private async Task<BoardDto> CreateBoardWithColumnAsync(HttpClient client, string boardName)
{
var board = await ApiTestHarness.CreateBoardAsync(client, boardName);
var columnResponse = await client.PostAsJsonAsync(
$`/api/boards/{board.Id}/columns`,
new CreateColumnDto(board.Id, "Backlog", null, null));
columnResponse.StatusCode.Should().Be(HttpStatusCode.Created);
return board;
}Then, the setup in each test would be simplified to:
using var client = _factory.CreateClient();
await ApiTestHarness.AuthenticateAsync(client, "golden-happy");
var board = await CreateBoardWithColumnAsync(client, "golden-happy-board");| string.Equals(log.EntityType, "card", StringComparison.OrdinalIgnoreCase) && | ||
| log.Action == AuditAction.Created); | ||
| } |
There was a problem hiding this comment.
The current assertion Should().Contain(...) checks that at least one audit log for card creation exists. Since this test creates exactly one card, the assertion can be made more specific to ensure that exactly one such audit log is created. This will make the test more robust against regressions that might cause duplicate logs.
Using ContainSingle from FluentAssertions would be more precise here.
auditLogs!.Should().ContainSingle(log =>
string.Equals(log.EntityType, "card", StringComparison.OrdinalIgnoreCase) &&
log.Action == AuditAction.Created);There was a problem hiding this comment.
Pull request overview
Adds a new “golden path” integration test suite that exercises the full capture → triage → proposal → review/approval → execution → board mutation workflow end-to-end using TestWebApplicationFactory with SQLite + Mock LLM for deterministic behavior.
Changes:
- Introduces
CaptureToBoardGoldenPathIntegrationTestswith 7 end-to-end integration tests covering happy path, multi-op execution, rejection, cross-user isolation, audit logging, provenance integrity, and deterministic triage failure. - Adds polling helper usage to reliably await async worker processing and capture state transitions.
- Validates persisted provenance linkage by reading
TaskdeckDbContextpayloads for consistency.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| using System.Net; | ||
| using System.Net.Http.Json; | ||
| using FluentAssertions; | ||
| using Microsoft.EntityFrameworkCore; | ||
| using Microsoft.Extensions.DependencyInjection; |
There was a problem hiding this comment.
System.Text.Json is not used in this test file. Removing the unused using will avoid compiler warnings and keep imports minimal.
| captureResponse.StatusCode.Should().Be(HttpStatusCode.Created); | ||
| var capture = await captureResponse.Content.ReadFromJsonAsync<CaptureItemDto>(); | ||
|
|
||
| await client.PostAsync($"/api/capture/items/{capture!.Id}/triage", null); |
There was a problem hiding this comment.
The triage request response is not asserted here. If the endpoint returns a non-2xx status (auth issue, validation change, etc.), the subsequent poll will time out and the failure will be harder to diagnose. Consider asserting the triage response status code (and optionally body) like the other integration tests do.
| await client.PostAsync($"/api/capture/items/{capture!.Id}/triage", null); | |
| var triageResponse = await client.PostAsync($"/api/capture/items/{capture!.Id}/triage", null); | |
| triageResponse.IsSuccessStatusCode.Should().BeTrue(); |
| await client.PostAsync($"/api/automation/proposals/{proposalId}/approve", null); | ||
|
|
||
| var executeRequest = new HttpRequestMessage(HttpMethod.Post, $"/api/automation/proposals/{proposalId}/execute"); | ||
| executeRequest.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString()); | ||
| await client.SendAsync(executeRequest); |
There was a problem hiding this comment.
The approve/execute calls aren’t asserting their HTTP status codes or parsing the response. Adding explicit status assertions (and optionally validating the returned proposal status) will make failures more actionable and consistent with the rest of the test suite.
| await client.PostAsync($"/api/automation/proposals/{proposalId}/approve", null); | |
| var executeRequest = new HttpRequestMessage(HttpMethod.Post, $"/api/automation/proposals/{proposalId}/execute"); | |
| executeRequest.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString()); | |
| await client.SendAsync(executeRequest); | |
| var approveResponse = await client.PostAsync($"/api/automation/proposals/{proposalId}/approve", null); | |
| approveResponse.StatusCode.Should().Be(HttpStatusCode.OK); | |
| var executeRequest = new HttpRequestMessage(HttpMethod.Post, $"/api/automation/proposals/{proposalId}/execute"); | |
| executeRequest.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString()); | |
| var executeResponse = await client.SendAsync(executeRequest); | |
| executeResponse.StatusCode.Should().Be(HttpStatusCode.OK); |
Adversarial Review (Round 2)1. Missing column placement assertion (medium)File:
cards[0].Title.Should().Be("Fix login bug");
cards[0].BoardId.Should().Be(board.Id);It never checks that the card was placed in the expected column (the "Backlog" column created at the start). This is a meaningful gap: the mock LLM provider could produce a proposal targeting a non-existent column or the wrong column, and the test would still pass. The column assertion should be present for both the happy path and the multi-operation test. 2. ProvenanceIntegrity test queries internal persistence layer (low-medium)File: The using var scope = _factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<TaskdeckDbContext>();
var persistedItem = await db.LlmRequests.SingleAsync(r => r.Id == capture.Id);While this proves persistence-level provenance integrity, it couples the test to the internal DB schema. If 3. IClassFixture sharing means shared database across all 7 tests (low)All 7 tests share a single
This is not a bug, but the shared state deserves a brief class-level comment noting that test isolation relies on per-test unique users/boards. 4. Multi-operation test does not verify card-to-column assignment or card ordering (low)File: cards.Select(c => c.Title).Should().BeEquivalentTo(
new[] { "Implement user authentication", "Write API integration tests", "Update deployment docs" });
5. WaitForCaptureStatusAsync early-exit on Failed status (acceptable but notable)File: item => item.Status == expectedStatus ||
(item.Status == CaptureStatus.Failed && expectedStatus != CaptureStatus.Failed),This early-exits polling if the status becomes 6. Rejection test does not verify capture item status after rejection (low)File: The 7. No double-execute idempotency assertion (nice-to-have)The happy path test sends a single execute request with an SummaryThe test suite is well-structured and covers the core pipeline thoroughly. The most actionable finding is #1 (missing column assertion) -- without it, the test proves a card was created but not that it was placed correctly on the board. Findings #2-#7 range from structural observations to nice-to-have improvements. CI is green. The self-review comment already acknowledged the absence of expiry and retry tests with reasonable justification. |
The happy path and multi-operation tests verified card title and board but not which column the card was placed in. Since the triage service deterministically targets the first column by position, the column assertion strengthens coverage of the full pipeline.
Adversarial Review - Final StatusAction takenPushed commit
This is meaningful because the triage service deterministically selects the first column by position. Without this assertion, a bug in column targeting would pass silently. Findings not acted on (acceptable as-is)
Bot comments reviewed
CI statusCI triggered on new commit, awaiting results. Previous run was all green. |
Summary
CaptureToBoardGoldenPathIntegrationTests— 7 integration tests exercising the full capture → triage → proposal → review → board mutation pipelineTestWebApplicationFactorywith real SQLite and Mock LLM provider for deterministic behaviorCloses #703
Test plan
TestWebApplicationFactoryinfrastructure as existing API testsPollUntilAsynchelper with 40 attempts x 250ms