fix(brief): make GET /brief a pure query, 404 when absent (RB-23)

GET /brief allocated a row on first call (BriefStore.GetOrCreate) — the
one endpoint in the backend where a read performed a persisted write.
The FE retries GETs automatically, so a transient failure could enter
the create path more than once; a lock prevented a duplicate row, but
the safety depended on the lock, not on the endpoint being a query.

Split GetOrCreate into Get (a pure query) and the already-existing
ResetAndCreate (POST /brief/reset owns creation). GET /brief now 404s
when the owner has no brief yet. GET /brief/preview used GetOrCreate
too, so it gets the same Get + 404 treatment, forced by the split.

RB-22 already made BriefStore.load() on the FE tolerate a 404 by
calling reset() once; this ticket is what makes that branch live.

Updated the brief/preview/org-template backend tests that assumed
GET seeded a brief on first call to create one explicitly first, and
added a test that GET 404s and writes no row without the fix (verified
red beforehand). Regenerated the API client (npm run gen:api).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-08-27 19:01:06 +02:00
co-authored by Claude Opus 5
parent 05dff974bf
commit d0fda08bcc
13 changed files with 305 additions and 78 deletions
@@ -41,7 +41,7 @@ public class PreviewEndpointTests(TestWebApplicationFactory factory) : IClassFix
public async Task Preview_of_an_unsent_brief_renders_live_with_a_watermark()
{
ResetStores();
await _client.GetAsync("/api/v1/brief"); // GetOrCreate the demo draft
await _client.PostAsync("/api/v1/brief/reset", null); // RB-23: GET no longer seeds — create explicitly
var res = await _client.GetAsync("/api/v1/brief/preview");
res.EnsureSuccessStatusCode();
@@ -54,7 +54,8 @@ public class PreviewEndpointTests(TestWebApplicationFactory factory) : IClassFix
public async Task Preview_of_a_sent_brief_serves_the_archive_unchanged_after_a_republish()
{
ResetStores();
var brief = (await _client.GetFromJsonAsync<BriefViewDto>("/api/v1/brief"))!.Brief;
var resetRes = await _client.PostAsync("/api/v1/brief/reset", null); // RB-23: create explicitly
var brief = (await resetRes.Content.ReadFromJsonAsync<BriefViewDto>())!.Brief;
await _client.PutAsJsonAsync("/api/v1/brief", FilledFrom(brief));
await _client.SendAsync(Req(HttpMethod.Post, "/api/v1/brief/submit"));
await _client.SendAsync(Req(HttpMethod.Post, "/api/v1/brief/approve", role: "approver"));