mirror of
https://github.com/coder/coder.git
synced 2026-06-03 13:08:25 +00:00
4e08543ace
Chat tests previously constructed a real `openai` provider with a fake API key and no `BaseURL`, so background title generation hit `api.openai.com` and timed out under `-race`. The same root cause produced several distinct flakes: title regeneration races with synchronous `UpdateChat`/`ProposeChatTitle`, and pagination races against `updated_at` bumps from real-network processing. This moves the fake OpenAI-compatible provider and the chat-settle wait into first-class `coderdtest` capabilities. `coderd.Options.ChatProviderAPIKeys` is the new seam tests use to redirect chat traffic to a local `httptest.Server`. `coderdtest.WaitForChatSettled` replaces per-test waiters and drains tracked chat-daemon work after the chat row leaves `pending`/`running`. The `newChatClient*` constructors funnel through one options builder that installs the fake provider before the coderd test server so cleanup ordering is deterministic. Closes https://github.com/coder/internal/issues/1528 & Closes ENG-2659 Closes https://github.com/coder/internal/issues/1480 & Closes CODAGT-359 Closes https://github.com/coder/internal/issues/1507 & Closes CODAGT-368 Relates to https://github.com/coder/internal/issues/1397 & Relates to CODAGT-374
129 lines
3.8 KiB
Go
129 lines
3.8 KiB
Go
package coderdtest
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/coderd"
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
|
"github.com/coder/coder/v2/coderd/x/chatd"
|
|
"github.com/coder/coder/v2/coderd/x/chatd/chatprovider"
|
|
"github.com/coder/coder/v2/coderd/x/chatd/chattest"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/testutil"
|
|
)
|
|
|
|
const (
|
|
// TestChatProviderOpenAICompat is the default provider for chat runtime tests.
|
|
TestChatProviderOpenAICompat = "openai-compat"
|
|
// TestChatProviderAPIKey is a non-secret API key for local chat providers.
|
|
TestChatProviderAPIKey = "test-api-key"
|
|
// TestChatModelOpenAICompat is the default model for chat runtime tests.
|
|
TestChatModelOpenAICompat = "gpt-4o-mini"
|
|
)
|
|
|
|
// OpenAICompatProviderAPIKeys returns provider keys that route OpenAI-compatible
|
|
// chat calls to baseURL.
|
|
func OpenAICompatProviderAPIKeys(baseURL string) chatprovider.ProviderAPIKeys {
|
|
return chatprovider.ProviderAPIKeys{
|
|
ByProvider: map[string]string{
|
|
TestChatProviderOpenAICompat: TestChatProviderAPIKey,
|
|
},
|
|
BaseURLByProvider: map[string]string{
|
|
TestChatProviderOpenAICompat: baseURL,
|
|
},
|
|
}
|
|
}
|
|
|
|
// FakeOpenAICompatProviderAPIKeys starts a fake OpenAI-compatible provider and
|
|
// returns provider keys for coderdtest.Options.
|
|
func FakeOpenAICompatProviderAPIKeys(t testing.TB) chatprovider.ProviderAPIKeys {
|
|
t.Helper()
|
|
return OpenAICompatProviderAPIKeys(chattest.OpenAI(t))
|
|
}
|
|
|
|
// CreateOpenAICompatChatModelConfig creates the default provider and model
|
|
// config used by chat runtime tests. Tests that create chats should also set
|
|
// Options.ChatProviderAPIKeys, usually via FakeOpenAICompatProviderAPIKeys, so
|
|
// background chat work routes to a local provider until coderd closes. baseURL,
|
|
// when non-empty, is stored on the provider config.
|
|
func CreateOpenAICompatChatModelConfig(
|
|
t testing.TB,
|
|
client *codersdk.ExperimentalClient,
|
|
baseURL string,
|
|
) codersdk.ChatModelConfig {
|
|
t.Helper()
|
|
|
|
ctx := testutil.Context(t, testutil.WaitLong)
|
|
_, err := client.CreateChatProvider(ctx, codersdk.CreateChatProviderConfigRequest{
|
|
Provider: TestChatProviderOpenAICompat,
|
|
APIKey: TestChatProviderAPIKey,
|
|
BaseURL: baseURL,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
contextLimit := int64(4096)
|
|
isDefault := true
|
|
modelConfig, err := client.CreateChatModelConfig(ctx, codersdk.CreateChatModelConfigRequest{
|
|
Provider: TestChatProviderOpenAICompat,
|
|
Model: TestChatModelOpenAICompat,
|
|
ContextLimit: &contextLimit,
|
|
IsDefault: &isDefault,
|
|
})
|
|
require.NoError(t, err)
|
|
return modelConfig
|
|
}
|
|
|
|
// WaitForChatSettled waits for a chat to leave active processing and drains
|
|
// tracked chat daemon work before returning the final row.
|
|
func WaitForChatSettled(
|
|
ctx context.Context,
|
|
t testing.TB,
|
|
api *coderd.API,
|
|
chatID uuid.UUID,
|
|
) database.Chat {
|
|
t.Helper()
|
|
|
|
require.NotNil(t, api)
|
|
waitForChatTerminalState(ctx, t, api.Database, chatID)
|
|
|
|
server := api.ChatDaemonForTest()
|
|
require.NotNil(t, server)
|
|
chatd.WaitUntilIdleForTest(server)
|
|
|
|
chat, err := getChatByIDAsSystem(ctx, api.Database, chatID)
|
|
require.NoError(t, err)
|
|
return chat
|
|
}
|
|
|
|
func waitForChatTerminalState(
|
|
ctx context.Context,
|
|
t testing.TB,
|
|
db database.Store,
|
|
chatID uuid.UUID,
|
|
) {
|
|
t.Helper()
|
|
|
|
require.Eventually(t, func() bool {
|
|
chat, err := getChatByIDAsSystem(ctx, db, chatID)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return chat.Status != database.ChatStatusPending && chat.Status != database.ChatStatusRunning
|
|
}, testutil.WaitLong, testutil.IntervalFast)
|
|
}
|
|
|
|
func getChatByIDAsSystem(
|
|
ctx context.Context,
|
|
db database.Store,
|
|
chatID uuid.UUID,
|
|
) (database.Chat, error) {
|
|
// Test helper needs system scope to observe chatd-owned status changes.
|
|
//nolint:gocritic
|
|
return db.GetChatByID(dbauthz.AsSystemRestricted(ctx), chatID)
|
|
}
|