(t *testing.T)
| 7571 | } |
| 7572 | |
| 7573 | func TestProcessChatPanicRecovery(t *testing.T) { |
| 7574 | t.Parallel() |
| 7575 | |
| 7576 | db, ps := dbtestutil.NewDB(t) |
| 7577 | |
| 7578 | // Wrap the database so we can trigger a panic on the main |
| 7579 | // goroutine of processChat. The chatloop's executeTools has |
| 7580 | // its own recover, so panicking inside a tool goroutine won't |
| 7581 | // reach the processChat-level recovery. Instead, we panic |
| 7582 | // during PersistStep's InTx call, which runs synchronously on |
| 7583 | // the processChat goroutine. |
| 7584 | panicWrapper := &panicOnInTxDB{Store: db} |
| 7585 | |
| 7586 | openAIURL := chattest.NewOpenAI(t, func(req *chattest.OpenAIRequest) chattest.OpenAIResponse { |
| 7587 | if !req.Stream { |
| 7588 | return chattest.OpenAINonStreamingResponse("Panic recovery test") |
| 7589 | } |
| 7590 | return chattest.OpenAIStreamingResponse( |
| 7591 | chattest.OpenAITextChunks("hello")..., |
| 7592 | ) |
| 7593 | }) |
| 7594 | |
| 7595 | ctx := testutil.Context(t, testutil.WaitLong) |
| 7596 | user, org, model := seedChatDependenciesWithProvider(t, db, "openai-compat", openAIURL) |
| 7597 | |
| 7598 | // Pass the panic wrapper to the server, but use the real |
| 7599 | // database for seeding so those operations don't panic. |
| 7600 | server := newActiveTestServer(t, panicWrapper, ps) |
| 7601 | |
| 7602 | chat, err := server.CreateChat(ctx, chatd.CreateOptions{ |
| 7603 | OrganizationID: org.ID, |
| 7604 | OwnerID: user.ID, |
| 7605 | Title: "panic-recovery", |
| 7606 | ModelConfigID: model.ID, |
| 7607 | InitialUserContent: []codersdk.ChatMessagePart{ |
| 7608 | codersdk.ChatMessageText("hello"), |
| 7609 | }, |
| 7610 | }) |
| 7611 | require.NoError(t, err) |
| 7612 | |
| 7613 | // Enable the panic now that CreateChat's InTx has completed. |
| 7614 | // The next InTx call is PersistStep inside the chatloop, |
| 7615 | // running synchronously on the processChat goroutine. |
| 7616 | panicWrapper.enablePanic() |
| 7617 | |
| 7618 | // Wait for the panic to be recovered and the chat to |
| 7619 | // transition to error status. |
| 7620 | var chatResult database.Chat |
| 7621 | require.Eventually(t, func() bool { |
| 7622 | got, getErr := db.GetChatByID(ctx, chat.ID) |
| 7623 | if getErr != nil { |
| 7624 | return false |
| 7625 | } |
| 7626 | chatResult = got |
| 7627 | return got.Status == database.ChatStatusError |
| 7628 | }, testutil.WaitLong, testutil.IntervalFast) |
| 7629 | |
| 7630 | persistedError := requireChatLastErrorPayload(t, chatResult.LastError) |
nothing calls this directly
no test coverage detected