fix(coderd): fix TestPatchChat/Title flake by waiting for chat to settle (#24572)

## Problem

`TestPatchChat/Title/Rename` and `TestPatchChat/Title/TrimsWhitespace`
fail intermittently on `test-go-pg` with:

```
PATCH .../api/experimental/chats/<id>: unexpected status code 409:
Title regeneration already in progress for this chat.
```

`createChat` persists a chat with `ChatStatusPending` and signals the
daemon wake loop. If the `UpdateChat` PATCH arrives before the daemon
transitions the chat past `Pending`/`Running`, the handler's
`acquireManualTitleLock` returns a 409. Whether the PATCH wins the race
is timing-dependent under PG + `-parallel` load.

Sibling subtests `PreservesUpdatedAt` and `NoOpWhenTitleUnchanged`
already wait for the chat to leave `Pending`/`Running` before renaming,
which is why they do not flake.

## Fix

Add a `waitChatSettled` helper closure in `TestPatchChat` that polls
`client.GetChat` until the chat status leaves `Pending`/`Running`.
Call it in the 4 subtests that issue a valid rename immediately after
`createChat`:

- `Title/Rename` (originally reported flake)
- `Title/TrimsWhitespace` (originally reported flake)
- `Title/LengthBoundaries` (latent flake in valid-rename cases)
- `Title/PublishesWatchEvent` (latent flake, goroutine silently 409s)

No handler, daemon, or SDK changes. The 409 is intentional production
behavior; this is a pure test-side timing fix.

Refs coder/internal#1480
This commit is contained in:
Jaayden Halko
2026-04-21 17:10:00 +01:00
committed by GitHub
parent 869168b316
commit 148e56b5d9
+24
View File
@@ -3973,6 +3973,22 @@ func TestPatchChat(t *testing.T) {
require.NoError(t, err)
return db2sdk.Chat(dbChat, nil, nil)
}
// waitChatSettled polls the chat until its background title-generation
// daemon has left the Pending/Running state. Without this, an immediate
// UpdateChat can hit a 409 (title regeneration in progress).
waitChatSettled := func(ctx context.Context, t *testing.T, client *codersdk.ExperimentalClient, chatID uuid.UUID) {
t.Helper()
require.Eventually(t, func() bool {
c, err := client.GetChat(ctx, chatID)
if err != nil {
return false
}
return c.Status != codersdk.ChatStatusPending &&
c.Status != codersdk.ChatStatusRunning
}, testutil.WaitShort, testutil.IntervalFast)
}
t.Run("PlanMode", func(t *testing.T) {
t.Parallel()
@@ -4244,6 +4260,8 @@ func TestPatchChat(t *testing.T) {
chat := createChat(ctx, t, client, firstUser.OrganizationID, "original title")
waitChatSettled(ctx, t, client, chat.ID)
err := client.UpdateChat(ctx, chat.ID, codersdk.UpdateChatRequest{
Title: ptr.Ref("renamed title"),
})
@@ -4263,6 +4281,8 @@ func TestPatchChat(t *testing.T) {
chat := createChat(ctx, t, client, firstUser.OrganizationID, "before trim")
waitChatSettled(ctx, t, client, chat.ID)
err := client.UpdateChat(ctx, chat.ID, codersdk.UpdateChatRequest{
Title: ptr.Ref(" padded title "),
})
@@ -4360,6 +4380,8 @@ func TestPatchChat(t *testing.T) {
_ = createChatModelConfig(t, client)
chat := createChat(ctx, t, client, firstUser.OrganizationID, "boundary baseline")
waitChatSettled(ctx, t, client, chat.ID)
err := client.UpdateChat(ctx, chat.ID, codersdk.UpdateChatRequest{
Title: ptr.Ref(tc.title),
})
@@ -4471,6 +4493,8 @@ func TestPatchChat(t *testing.T) {
chat := createChat(ctx, t, client, firstUser.OrganizationID, "announce me")
waitChatSettled(ctx, t, client, chat.ID)
conn, err := client.Dial(ctx, "/api/experimental/chats/watch", nil)
require.NoError(t, err)
defer conn.Close(websocket.StatusNormalClosure, "done")