test: migrate chatd tests to AI Gateway routing (#26658)

Refs CODAGT-681

Migrates all chatd tests from `AIGatewayRoutingEnabled = false` (direct
routing) to AI Gateway routing using the test helpers extracted in
#26639.

- `coderd/x/chatd/chatd_test.go` — 6 full-server tests migrated to
`NewWithAPI` + daemon, `directChatRoutingDeploymentValues` helper
deleted, 3 bare-chatd tests renamed
- `coderd/x/chatd/context_integration_test.go` — 2 tests migrated
- `coderd/exp_chats_test.go` — `chatDeploymentValues` helper deleted,
all 5 helper functions now use `NewWithAPI` + daemon internally (no call
site changes)
- `coderd/exp_chats_acl_test.go` — stale `chatDeploymentValues`
reference replaced
- `enterprise/coderd/exp_chats_test.go` — 9 sites across 5
`TestChatStreamRelay` subtests migrated
- `cli/exp_scaletest_chat_test.go` — 1 test migrated
- `coderd/x/chatd/model_routing_internal_test.go` — 1 direct-only test
removed
- `coderd/x/chatd/chatd_internal_test.go` — 1 direct-only test removed

> 🤖
This commit is contained in:
Cian Johnston
2026-06-30 12:17:42 +01:00
committed by GitHub
parent 33780758c1
commit e5b7e74847
8 changed files with 210 additions and 182 deletions
+3 -4
View File
@@ -15,6 +15,7 @@ import (
"cdr.dev/slog/v3"
"cdr.dev/slog/v3/sloggers/sloghuman"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/aibridgedtest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/scaletest/llmmock"
@@ -29,13 +30,11 @@ func TestScaleTestChat(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitLong)
values := coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
require.NoError(t, dv.AI.BridgeConfig.Enabled.Set("true"))
// Keep AI Gateway routing disabled so the chat uses the direct model
// route to the mock provider, avoiding the need for an aibridged daemon.
require.NoError(t, dv.AI.Chat.AIGatewayRoutingEnabled.Set("false"))
})
client := coderdtest.New(t, &coderdtest.Options{
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: values,
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
coderdtest.CreateFirstUser(t, client)
server := new(llmmock.Server)
+1 -1
View File
@@ -465,7 +465,7 @@ func TestChatSharingDisabled(t *testing.T) {
})
ctx := testutil.Context(t, testutil.WaitLong)
values := chatDeploymentValues(t)
values := coderdtest.DeploymentValues(t)
values.DisableChatSharing = true
store, pubsub := dbtestutil.NewDB(t)
client := newChatClient(t, func(opts *coderdtest.Options) {
+85 -51
View File
@@ -25,8 +25,10 @@ import (
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
"cdr.dev/slog/v3/sloggers/slogtest"
"github.com/coder/coder/v2/coderd"
"github.com/coder/coder/v2/coderd/aibridge"
"github.com/coder/coder/v2/coderd/aibridgedtest"
"github.com/coder/coder/v2/coderd/audit"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
@@ -56,14 +58,6 @@ const (
missingCentralKeyMessage = "API key is required when central API key is enabled."
)
func chatDeploymentValues(t testing.TB) *codersdk.DeploymentValues {
t.Helper()
values := coderdtest.DeploymentValues(t)
require.NoError(t, values.AI.Chat.AIGatewayRoutingEnabled.Set("false"))
return values
}
// newChatTestOptions builds coderdtest options for chat runtime tests. Unless
// a test sets ChatProviderAPIKeys explicitly, it installs a fake
// OpenAI-compatible provider before coderd starts so background chat work stays
@@ -91,16 +85,18 @@ func newChatTestOptions(
func newChatClient(t testing.TB, overrides ...func(*coderdtest.Options)) *codersdk.ExperimentalClient {
t.Helper()
opts := newChatTestOptions(t, chatDeploymentValues(t), overrides...)
client := coderdtest.New(t, opts)
opts := newChatTestOptions(t, coderdtest.DeploymentValues(t), overrides...)
client, _, api := coderdtest.NewWithAPI(t, opts)
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
return codersdk.NewExperimentalClient(client)
}
func newChatClientWithAPI(t testing.TB, overrides ...func(*coderdtest.Options)) (*codersdk.ExperimentalClient, *coderd.API) {
t.Helper()
opts := newChatTestOptions(t, chatDeploymentValues(t), overrides...)
opts := newChatTestOptions(t, coderdtest.DeploymentValues(t), overrides...)
client, _, api := coderdtest.NewWithAPI(t, opts)
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
return codersdk.NewExperimentalClient(client), api
}
@@ -111,23 +107,26 @@ func newChatClientWithDeploymentValues(
t.Helper()
opts := newChatTestOptions(t, values)
client := coderdtest.New(t, opts)
client, _, api := coderdtest.NewWithAPI(t, opts)
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
return codersdk.NewExperimentalClient(client)
}
func newChatClientWithDatabase(t testing.TB, overrides ...func(*coderdtest.Options)) (*codersdk.ExperimentalClient, database.Store) {
t.Helper()
opts := newChatTestOptions(t, chatDeploymentValues(t), overrides...)
client, db := coderdtest.NewWithDatabase(t, opts)
return codersdk.NewExperimentalClient(client), db
opts := newChatTestOptions(t, coderdtest.DeploymentValues(t), overrides...)
client, _, api := coderdtest.NewWithAPI(t, opts)
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
return codersdk.NewExperimentalClient(client), api.Database
}
func newChatClientWithAPIAndDatabase(t testing.TB, overrides ...func(*coderdtest.Options)) (*codersdk.ExperimentalClient, database.Store, *coderd.API) {
t.Helper()
opts := newChatTestOptions(t, chatDeploymentValues(t), overrides...)
opts := newChatTestOptions(t, coderdtest.DeploymentValues(t), overrides...)
client, _, api := coderdtest.NewWithAPI(t, opts)
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
return codersdk.NewExperimentalClient(client), api.Database, api
}
@@ -1847,7 +1846,7 @@ func TestListChatModels(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
values := chatDeploymentValues(t)
values := coderdtest.DeploymentValues(t)
values.AI.BridgeConfig.LegacyOpenAI.Key = serpent.String("deployment-openai-key")
client := newChatClientWithDeploymentValues(t, values)
_ = coderdtest.CreateFirstUser(t, client.Client)
@@ -1974,8 +1973,9 @@ func TestWatchChats(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitLong)
rawClient, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: chatDeploymentValues(t),
DeploymentValues: coderdtest.DeploymentValues(t),
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
client := codersdk.NewExperimentalClient(rawClient)
db := api.Database
chatDaemon := api.ChatDaemonForTest()
@@ -2312,9 +2312,16 @@ func TestUserAIProviderKeys(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
values := chatDeploymentValues(t)
values := coderdtest.DeploymentValues(t)
values.AI.BridgeConfig.AllowBYOK = serpent.Bool(false)
client := newChatClientWithDeploymentValues(t, values)
// The aibridged reloader logs at error level when it sees a provider
// configured with no API key and BYOK disabled. That state is the
// scenario under test, so suppress its error logs here.
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true})
client := newChatClient(t, func(o *coderdtest.Options) {
o.DeploymentValues = values
o.Logger = &logger
})
_ = coderdtest.CreateFirstUser(t, client.Client)
provider := createOpenAIProvider(t, client, "test-byok-disabled-"+uuid.NewString(), true)
@@ -2364,7 +2371,7 @@ func TestListChatProviders(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
values := chatDeploymentValues(t)
values := coderdtest.DeploymentValues(t)
values.AI.BridgeConfig.LegacyOpenAI.Key = serpent.String("deployment-openai-key")
client := newChatClientWithDeploymentValues(t, values)
_ = coderdtest.CreateFirstUser(t, client.Client)
@@ -2613,7 +2620,7 @@ func TestCreateChatProvider(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
values := chatDeploymentValues(t)
values := coderdtest.DeploymentValues(t)
values.AI.BridgeConfig.LegacyOpenAI.Key = serpent.String("deployment-openai-key")
client := newChatClientWithDeploymentValues(t, values)
_ = coderdtest.CreateFirstUser(t, client.Client)
@@ -2844,7 +2851,7 @@ func TestUpdateChatProvider(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
values := chatDeploymentValues(t)
values := coderdtest.DeploymentValues(t)
values.AI.BridgeConfig.LegacyOpenAI.Key = serpent.String("deployment-openai-key")
client := newChatClientWithDeploymentValues(t, values)
_ = coderdtest.CreateFirstUser(t, client.Client)
@@ -3007,7 +3014,7 @@ func TestChatProviderAPIKeysFromDeploymentValues(t *testing.T) {
t.Run("DoesNotReuseBridgeConfig", func(t *testing.T) {
t.Parallel()
values := chatDeploymentValues(t)
values := coderdtest.DeploymentValues(t)
values.AI.BridgeConfig.LegacyOpenAI.Key = serpent.String("deployment-openai-key")
values.AI.BridgeConfig.LegacyAnthropic.Key = serpent.String("deployment-anthropic-key")
values.AI.BridgeConfig.LegacyOpenAI.BaseURL = serpent.String("https://custom-openai.example.com")
@@ -3213,7 +3220,7 @@ func TestUserChatProviderConfigs(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
values := chatDeploymentValues(t)
values := coderdtest.DeploymentValues(t)
values.AI.BridgeConfig.LegacyOpenAI.Key = serpent.String("deployment-openai-key")
client := newChatClientWithDeploymentValues(t, values)
_ = coderdtest.CreateFirstUser(t, client.Client)
@@ -4259,11 +4266,13 @@ func TestUpdateChatModelConfig(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitLong)
rawDB, pubsub := dbtestutil.NewDB(t)
store := newFailNextUpdateChatModelConfigStore(rawDB)
client := codersdk.NewExperimentalClient(coderdtest.New(t, &coderdtest.Options{
rawClient, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
Database: store,
Pubsub: pubsub,
DeploymentValues: chatDeploymentValues(t),
}))
DeploymentValues: coderdtest.DeploymentValues(t),
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
client := codersdk.NewExperimentalClient(rawClient)
_ = coderdtest.CreateFirstUser(t, client.Client)
modelConfig := createChatModelConfig(t, client)
@@ -4282,11 +4291,13 @@ func TestUpdateChatModelConfig(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitLong)
rawDB, pubsub := dbtestutil.NewDB(t)
store := newFailNextUpdateChatModelConfigStore(rawDB)
client := codersdk.NewExperimentalClient(coderdtest.New(t, &coderdtest.Options{
rawClient, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
Database: store,
Pubsub: pubsub,
DeploymentValues: chatDeploymentValues(t),
}))
DeploymentValues: coderdtest.DeploymentValues(t),
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
client := codersdk.NewExperimentalClient(rawClient)
_ = coderdtest.CreateFirstUser(t, client.Client)
defaultConfig := createChatModelConfig(t, client)
@@ -5549,11 +5560,12 @@ func TestPatchChat(t *testing.T) {
db, ps, sqlDB := dbtestutil.NewDBWithSQLDB(t)
providerKeys := coderdtest.FakeOpenAICompatProviderAPIKeys(t)
clientRaw, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: chatDeploymentValues(t),
DeploymentValues: coderdtest.DeploymentValues(t),
Database: db,
Pubsub: ps,
ChatProviderAPIKeys: &providerKeys,
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
client := codersdk.NewExperimentalClient(clientRaw)
firstUser := coderdtest.CreateFirstUser(t, client.Client)
_ = createChatModelConfig(t, client)
@@ -5586,11 +5598,12 @@ func TestPatchChat(t *testing.T) {
db, ps, sqlDB := dbtestutil.NewDBWithSQLDB(t)
providerKeys := coderdtest.FakeOpenAICompatProviderAPIKeys(t)
clientRaw, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: chatDeploymentValues(t),
DeploymentValues: coderdtest.DeploymentValues(t),
Database: db,
Pubsub: ps,
ChatProviderAPIKeys: &providerKeys,
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
client := codersdk.NewExperimentalClient(clientRaw)
firstUser := coderdtest.CreateFirstUser(t, client.Client)
_ = createChatModelConfig(t, client)
@@ -8382,7 +8395,7 @@ func TestRegenerateChatTitle(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
clientRaw, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
clientRaw, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
Authorizer: &coderdtest.FakeAuthorizer{
ConditionalReturn: func(_ context.Context, _ rbac.Subject, action policy.Action, object rbac.Object) error {
if action == policy.ActionUpdate && object.Type == rbac.ResourceChat.Type {
@@ -8391,8 +8404,10 @@ func TestRegenerateChatTitle(t *testing.T) {
return nil
},
},
DeploymentValues: chatDeploymentValues(t),
DeploymentValues: coderdtest.DeploymentValues(t),
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
db := api.Database
client := codersdk.NewExperimentalClient(clientRaw)
user := coderdtest.CreateFirstUser(t, client.Client)
modelConfig := createChatModelConfig(t, client)
@@ -8651,7 +8666,7 @@ func TestProposeChatTitle(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
clientRaw, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
clientRaw, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
Authorizer: &coderdtest.FakeAuthorizer{
ConditionalReturn: func(_ context.Context, _ rbac.Subject, action policy.Action, object rbac.Object) error {
if action == policy.ActionUpdate && object.Type == rbac.ResourceChat.Type {
@@ -8660,8 +8675,10 @@ func TestProposeChatTitle(t *testing.T) {
return nil
},
},
DeploymentValues: chatDeploymentValues(t),
DeploymentValues: coderdtest.DeploymentValues(t),
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
db := api.Database
client := codersdk.NewExperimentalClient(clientRaw)
user := coderdtest.CreateFirstUser(t, client.Client)
modelConfig := createChatModelConfig(t, client)
@@ -8737,7 +8754,7 @@ func TestManualTitleEndpointsPassCallerAPIKeyToAIGateway(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
values := chatDeploymentValues(t)
values := coderdtest.DeploymentValues(t)
require.NoError(t, values.AI.BridgeConfig.Enabled.Set("true"))
require.NoError(t, values.AI.Chat.AIGatewayRoutingEnabled.Set("true"))
client, db, api := newChatClientWithAPIAndDatabase(t, func(opts *coderdtest.Options) {
@@ -8851,7 +8868,7 @@ func TestGetChatDiffStatus(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitLong)
rawClient, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: chatDeploymentValues(t),
DeploymentValues: coderdtest.DeploymentValues(t),
ExternalAuthConfigs: []*externalauth.Config{
{
ID: "gitlab-test",
@@ -8860,6 +8877,7 @@ func TestGetChatDiffStatus(t *testing.T) {
},
},
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
client := codersdk.NewExperimentalClient(rawClient)
db := api.Database
@@ -8972,7 +8990,7 @@ func TestGetChatDiffContents(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitLong)
rawClient, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: chatDeploymentValues(t),
DeploymentValues: coderdtest.DeploymentValues(t),
ExternalAuthConfigs: []*externalauth.Config{
{
ID: "gitlab-test",
@@ -8981,6 +8999,7 @@ func TestGetChatDiffContents(t *testing.T) {
},
},
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
client := codersdk.NewExperimentalClient(rawClient)
db := api.Database
user := coderdtest.CreateFirstUser(t, client.Client)
@@ -10773,10 +10792,19 @@ func createAIProviderForTest(
t.Helper()
ctx := testutil.Context(t, testutil.WaitLong)
baseURL := aiProviderBaseURLForTest(provider)
// AI Gateway routing uses the provider's BaseURL from the DB row.
// For OpenAI-compatible providers, use a real mock server so the
// daemon can route chat requests. Other provider types (anthropic,
// bedrock, google) are only used for model config CRUD tests that
// never process chats through the daemon.
if provider == "openai" || provider == "openai-compat" {
baseURL = chattest.OpenAI(t)
}
req := codersdk.CreateAIProviderRequest{
Type: codersdk.AIProviderType(provider),
Name: "test-" + provider + "-" + uuid.NewString(),
BaseURL: aiProviderBaseURLForTest(provider),
BaseURL: baseURL,
Enabled: true,
}
if apiKey != "" {
@@ -11023,11 +11051,13 @@ If a workspace is needed, use list_templates before create_workspace and follow
rawDB, pubsub := dbtestutil.NewDB(t)
store := &failNextChatSystemPromptStore{Store: rawDB}
client := codersdk.NewExperimentalClient(coderdtest.New(t, &coderdtest.Options{
rawClient, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
Database: store,
Pubsub: pubsub,
DeploymentValues: chatDeploymentValues(t),
}))
DeploymentValues: coderdtest.DeploymentValues(t),
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
client := codersdk.NewExperimentalClient(rawClient)
_ = coderdtest.CreateFirstUser(t, client.Client)
_ = createChatModelConfig(t, client)
@@ -11191,11 +11221,13 @@ If a workspace is needed, use list_templates before create_workspace and follow
rawDB, pubsub := dbtestutil.NewDB(t)
store := &failNextChatSystemPromptStore{Store: rawDB}
client := codersdk.NewExperimentalClient(coderdtest.New(t, &coderdtest.Options{
rawClient, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
Database: store,
Pubsub: pubsub,
DeploymentValues: chatDeploymentValues(t),
}))
DeploymentValues: coderdtest.DeploymentValues(t),
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
client := codersdk.NewExperimentalClient(rawClient)
firstUser := coderdtest.CreateFirstUser(t, client.Client)
_ = createChatModelConfig(t, client)
@@ -11238,11 +11270,13 @@ If a workspace is needed, use list_templates before create_workspace and follow
rawDB, pubsub := dbtestutil.NewDB(t)
store := &failNextChatSystemPromptStore{Store: rawDB}
client := codersdk.NewExperimentalClient(coderdtest.New(t, &coderdtest.Options{
rawClient, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
Database: store,
Pubsub: pubsub,
DeploymentValues: chatDeploymentValues(t),
}))
DeploymentValues: coderdtest.DeploymentValues(t),
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
client := codersdk.NewExperimentalClient(rawClient)
firstUser := coderdtest.CreateFirstUser(t, client.Client)
_ = createChatModelConfig(t, client)
@@ -12569,7 +12603,7 @@ func TestChatDebugLoggingSettings(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
values := chatDeploymentValues(t)
values := coderdtest.DeploymentValues(t)
values.AI.Chat.DebugLoggingEnabled = serpent.Bool(true)
adminClient := newChatClientWithDeploymentValues(t, values)
firstUser := coderdtest.CreateFirstUser(t, adminClient.Client)
-27
View File
@@ -141,33 +141,6 @@ func TestComputerUseProviderAndModelFromConfig(t *testing.T) {
}
}
func TestResolveComputerUseModel_OpenAIMissingCredentials(t *testing.T) {
t.Parallel()
server := &Server{}
provider := chattool.ComputerUseProviderOpenAI
modelProvider, modelName, ok := chattool.DefaultComputerUseModel(provider)
require.True(t, ok)
model, debugEnabled, resolvedProvider, resolvedModel, err := server.resolveComputerUseModel(
context.Background(),
database.Chat{ID: uuid.New(), OwnerID: uuid.New()},
newDirectModelRoute(modelProvider, chatprovider.ProviderAPIKeys{}),
provider,
modelProvider,
modelName,
modelBuildOptions{},
)
require.Error(t, err)
require.Nil(t, model)
require.False(t, debugEnabled)
require.Empty(t, resolvedProvider)
require.Empty(t, resolvedModel)
require.Contains(t, err.Error(), `provider "openai" model "gpt-5.5"`)
require.Contains(t, err.Error(), "OPENAI_API_KEY is not set")
require.NotContains(t, err.Error(), "ANTHROPIC_API_KEY")
}
func TestResolveUserProviderAPIKeysAndProviderForProviderTypeProviderMatch(t *testing.T) {
t.Parallel()
+24 -29
View File
@@ -37,6 +37,7 @@ import (
"github.com/coder/coder/v2/agent/agentcontextconfig"
"github.com/coder/coder/v2/agent/agenttest"
"github.com/coder/coder/v2/coderd/aibridge"
"github.com/coder/coder/v2/coderd/aibridgedtest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/db2sdk"
@@ -83,14 +84,6 @@ func chatAIGatewayTransportFactoryPointer(factory aibridge.TransportFactory) *at
return &ptr
}
func directChatRoutingDeploymentValues(t testing.TB) *codersdk.DeploymentValues {
t.Helper()
values := coderdtest.DeploymentValues(t)
require.NoError(t, values.AI.Chat.AIGatewayRoutingEnabled.Set("false"))
return values
}
func openAIToolName(tool chattest.OpenAITool) string {
return cmp.Or(tool.Function.Name, tool.Name, tool.Type)
}
@@ -202,11 +195,11 @@ func TestSubagentChatExcludesWorkspaceProvisioningTools(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
deploymentValues := directChatRoutingDeploymentValues(t)
client := coderdtest.New(t, &coderdtest.Options{
DeploymentValues: deploymentValues,
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
IncludeProvisionerDaemon: true,
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
user := coderdtest.CreateFirstUser(t, client)
expClient := codersdk.NewExperimentalClient(client)
@@ -359,11 +352,11 @@ func TestPlanModeSubagentChatExcludesAskUserQuestion(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
deploymentValues := directChatRoutingDeploymentValues(t)
client := coderdtest.New(t, &coderdtest.Options{
DeploymentValues: deploymentValues,
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
IncludeProvisionerDaemon: true,
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
user := coderdtest.CreateFirstUser(t, client)
expClient := codersdk.NewExperimentalClient(client)
@@ -526,11 +519,12 @@ func TestExploreSubagentIsReadOnly(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
deploymentValues := directChatRoutingDeploymentValues(t)
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: deploymentValues,
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
IncludeProvisionerDaemon: true,
})
db := api.Database
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
user := coderdtest.CreateFirstUser(t, client)
expClient := codersdk.NewExperimentalClient(client)
@@ -4650,11 +4644,11 @@ func TestCreateWorkspaceTool_EndToEnd(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
deploymentValues := directChatRoutingDeploymentValues(t)
client := coderdtest.New(t, &coderdtest.Options{
DeploymentValues: deploymentValues,
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
IncludeProvisionerDaemon: true,
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
user := coderdtest.CreateFirstUser(t, client)
expClient := codersdk.NewExperimentalClient(client)
@@ -4815,11 +4809,11 @@ func TestStartWorkspaceTool_EndToEnd(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitSuperLong)
deploymentValues := directChatRoutingDeploymentValues(t)
client := coderdtest.New(t, &coderdtest.Options{
DeploymentValues: deploymentValues,
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
IncludeProvisionerDaemon: true,
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
user := coderdtest.CreateFirstUser(t, client)
expClient := codersdk.NewExperimentalClient(client)
@@ -5284,7 +5278,7 @@ func highUsageReadFileResponse(path string) chattest.AnthropicResponse {
return chattest.AnthropicStreamingResponse(chunks...)
}
func TestActiveServer_AIGatewayRoutingPreservesAPIKeyAfterCompaction(t *testing.T) {
func TestActiveServer_RoutingPreservesAPIKeyAfterCompaction(t *testing.T) {
t.Parallel()
const (
@@ -9694,7 +9688,7 @@ func seedAIGatewayOpenAITestDependencies(
return user, org, provider, model, apiKey
}
func TestProcessChat_AIGatewayRoutingUsesDelegatedAPIKey(t *testing.T) {
func TestProcessChat_RoutingUsesDelegatedAPIKey(t *testing.T) {
t.Parallel()
db, ps := dbtestutil.NewDB(t)
@@ -9758,7 +9752,7 @@ func TestProcessChat_AIGatewayRoutingUsesDelegatedAPIKey(t *testing.T) {
}
}
func TestProcessChat_AIGatewayRoutingPreservesAPIKeyAfterWorkspaceContext(t *testing.T) {
func TestProcessChat_RoutingPreservesAPIKeyAfterWorkspaceContext(t *testing.T) {
t.Parallel()
db, ps := dbtestutil.NewDB(t)
@@ -11264,12 +11258,13 @@ func TestAgentContextFilesAndSkillsLoadedIntoChat(t *testing.T) {
))
ctx := testutil.Context(t, testutil.WaitSuperLong)
deploymentValues := directChatRoutingDeploymentValues(t)
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: deploymentValues,
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
IncludeProvisionerDaemon: true,
ChatdInstructionLookupTimeout: testutil.WaitLong,
})
db := api.Database
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
user := coderdtest.CreateFirstUser(t, client)
expClient := codersdk.NewExperimentalClient(client)
+9 -4
View File
@@ -10,6 +10,7 @@ import (
"google.golang.org/protobuf/types/known/structpb"
agentproto "github.com/coder/coder/v2/agent/proto"
"github.com/coder/coder/v2/coderd/aibridgedtest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
@@ -32,10 +33,12 @@ func TestChatContextDirtyFromAgentPush(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: directChatRoutingDeploymentValues(t),
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
IncludeProvisionerDaemon: true,
})
db := api.Database
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
user := coderdtest.CreateFirstUser(t, client)
expClient := codersdk.NewExperimentalClient(client)
@@ -284,10 +287,12 @@ func TestChatContextRefreshFromAgentToken(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: directChatRoutingDeploymentValues(t),
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
DeploymentValues: coderdtest.DeploymentValues(t),
IncludeProvisionerDaemon: true,
})
db := api.Database
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
user := coderdtest.CreateFirstUser(t, client)
expClient := codersdk.NewExperimentalClient(client)
+35 -13
View File
@@ -779,19 +779,6 @@ func TestAIBridgeGatewayProviderTypesPreserveSlashModelID(t *testing.T) {
}
}
func TestDirectModelBuildDoesNotRequireActiveAPIKeyID(t *testing.T) {
t.Parallel()
server := &Server{}
model, err := server.newModel(t.Context(), modelClientRequest{
Chat: database.Chat{ID: uuid.New(), OwnerID: uuid.New()},
ModelName: "gpt-4",
UserAgent: chatprovider.UserAgent(),
}, newDirectModelRoute("openai", chatprovider.ProviderAPIKeys{OpenAI: "sk-test"}), modelBuildOptions{})
require.NoError(t, err)
require.NotNil(t, model)
}
func TestAIBridgeComputerUseModelUsesRoute(t *testing.T) {
t.Parallel()
@@ -829,6 +816,41 @@ func TestAIBridgeComputerUseModelUsesRoute(t *testing.T) {
require.Equal(t, aibridge.SourceAgents, factory.source)
}
func TestResolveComputerUseModel_AIGatewayMissingAPIKeyID(t *testing.T) {
t.Parallel()
providerID := uuid.New()
factory := &aibridgeTestFactory{rt: roundTripFunc(func(*http.Request) (*http.Response, error) {
t.Fatal("transport must not be used without an API key ID")
return nil, xerrors.New("unreachable")
})}
chat := database.Chat{ID: uuid.New(), OwnerID: uuid.New()}
server := &Server{
aiGatewayRoutingEnabled: true,
aibridgeTransportFactory: aibridgeTestFactoryPointer(factory),
}
provider := chattool.ComputerUseProviderOpenAI
modelProvider, modelName, ok := chattool.DefaultComputerUseModel(provider)
require.True(t, ok)
model, debugEnabled, resolvedProvider, resolvedModel, err := server.resolveComputerUseModel(
t.Context(),
chat,
aibridgeTestRoute(aibridgeTestAIProvider(providerID, "primary-openai", database.AIProviderTypeOpenai)),
provider,
modelProvider,
modelName,
modelBuildOptions{}, // no ActiveAPIKeyID
)
require.Error(t, err)
require.Nil(t, model)
require.False(t, debugEnabled)
require.Empty(t, resolvedProvider)
require.Empty(t, resolvedModel)
require.Contains(t, err.Error(), `resolve computer use model for provider "openai" model "gpt-5.5"`)
require.Contains(t, err.Error(), "active turn API key ID")
}
func TestAIBridgeDelegatedContextPropagation(t *testing.T) {
t.Parallel()
+53 -53
View File
@@ -11,6 +11,7 @@ import (
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/aibridgedtest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
@@ -66,20 +67,24 @@ func createOpenAIModelConfigForTest(
}
func TestChatStreamRelay(t *testing.T) {
// OpenAI Responses streaming events are buffered (not relayed) under AI
// Gateway routing while the agentic inner loop exists; see
// https://github.com/coder/aibridge/issues/223. Unskip once the
// reverse-proxy refactor lands and the follow-up tracking ticket is
// resolved.
t.Parallel()
t.Skip("chat stream relay buffers events under AI Gateway routing; see CODAGT-734 and https://github.com/coder/aibridge/issues/223")
t.Run("RelayMessagePartsAcrossReplicas", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
db, pubsub := dbtestutil.NewDB(t)
firstClient, firstUser := coderdenttest.New(t, &coderdenttest.Options{
firstClient, _, firstAPI, firstUser := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
require.NoError(t, dv.AI.Chat.AIGatewayRoutingEnabled.Set("false"))
}),
Database: db,
Pubsub: pubsub,
DeploymentValues: coderdtest.DeploymentValues(t),
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
@@ -87,18 +92,18 @@ func TestChatStreamRelay(t *testing.T) {
},
},
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, firstAPI.AGPL, nil)
secondClient, _ := coderdenttest.New(t, &coderdenttest.Options{
secondClient, _, secondAPI, _ := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
require.NoError(t, dv.AI.Chat.AIGatewayRoutingEnabled.Set("false"))
}),
Database: db,
Pubsub: pubsub,
DeploymentValues: coderdtest.DeploymentValues(t),
},
DontAddLicense: true,
DontAddFirstUser: true,
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, secondAPI.AGPL, nil)
secondClient.SetSessionToken(firstClient.SessionToken())
// Verify we have two replicas
@@ -220,14 +225,12 @@ func TestChatStreamRelay(t *testing.T) {
certificates := []tls.Certificate{testutil.GenerateTLSCertificate(t, "localhost")}
db, pubsub := dbtestutil.NewDB(t)
firstClient, firstUser := coderdenttest.New(t, &coderdenttest.Options{
firstClient, _, firstAPI, firstUser := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
TLSCertificates: certificates,
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
require.NoError(t, dv.AI.Chat.AIGatewayRoutingEnabled.Set("false"))
}),
Database: db,
Pubsub: pubsub,
TLSCertificates: certificates,
DeploymentValues: coderdtest.DeploymentValues(t),
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
@@ -235,19 +238,19 @@ func TestChatStreamRelay(t *testing.T) {
},
},
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, firstAPI.AGPL, nil)
secondClient, _ := coderdenttest.New(t, &coderdenttest.Options{
secondClient, _, secondAPI, _ := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
TLSCertificates: certificates,
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
require.NoError(t, dv.AI.Chat.AIGatewayRoutingEnabled.Set("false"))
}),
Database: db,
Pubsub: pubsub,
TLSCertificates: certificates,
DeploymentValues: coderdtest.DeploymentValues(t),
},
DontAddLicense: true,
DontAddFirstUser: true,
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, secondAPI.AGPL, nil)
// Authenticate the second client using cookies only, simulating
// browser WebSocket behavior. Browsers cannot set custom
@@ -406,13 +409,11 @@ func TestChatStreamRelay(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitLong)
db, pubsub := dbtestutil.NewDB(t)
firstClient, firstUser := coderdenttest.New(t, &coderdenttest.Options{
firstClient, _, firstAPI, firstUser := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
require.NoError(t, dv.AI.Chat.AIGatewayRoutingEnabled.Set("false"))
}),
Database: db,
Pubsub: pubsub,
DeploymentValues: coderdtest.DeploymentValues(t),
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
@@ -420,18 +421,18 @@ func TestChatStreamRelay(t *testing.T) {
},
},
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, firstAPI.AGPL, nil)
secondClient, _ := coderdenttest.New(t, &coderdenttest.Options{
secondClient, _, secondAPI, _ := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
require.NoError(t, dv.AI.Chat.AIGatewayRoutingEnabled.Set("false"))
}),
Database: db,
Pubsub: pubsub,
DeploymentValues: coderdtest.DeploymentValues(t),
},
DontAddLicense: true,
DontAddFirstUser: true,
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, secondAPI.AGPL, nil)
//nolint:gocritic // Test uses owner client session token for cookie-based relay auth.
sessionToken := firstClient.SessionToken()
@@ -562,11 +563,10 @@ func TestChatStreamRelay(t *testing.T) {
db, pubsub := dbtestutil.NewDB(t)
hostPrefixValues := coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
require.NoError(t, dv.AI.Chat.AIGatewayRoutingEnabled.Set("false"))
dv.HTTPCookies.EnableHostPrefix = true
dv.HTTPCookies.Secure = true
})
firstClient, firstUser := coderdenttest.New(t, &coderdenttest.Options{
firstClient, _, firstAPI, firstUser := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
@@ -578,8 +578,9 @@ func TestChatStreamRelay(t *testing.T) {
},
},
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, firstAPI.AGPL, nil)
secondClient, _ := coderdenttest.New(t, &coderdenttest.Options{
secondClient, _, secondAPI, _ := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
@@ -588,6 +589,7 @@ func TestChatStreamRelay(t *testing.T) {
DontAddLicense: true,
DontAddFirstUser: true,
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, secondAPI.AGPL, nil)
//nolint:gocritic // Test uses owner client session token for cookie-based relay auth.
sessionToken := firstClient.SessionToken()
@@ -711,13 +713,11 @@ func TestChatStreamRelay(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitLong)
db, pubsub := dbtestutil.NewDB(t)
firstClient, firstUser := coderdenttest.New(t, &coderdenttest.Options{
firstClient, _, firstAPI, firstUser := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
require.NoError(t, dv.AI.Chat.AIGatewayRoutingEnabled.Set("false"))
}),
Database: db,
Pubsub: pubsub,
DeploymentValues: coderdtest.DeploymentValues(t),
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
@@ -725,18 +725,18 @@ func TestChatStreamRelay(t *testing.T) {
},
},
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, firstAPI.AGPL, nil)
secondClient, _ := coderdenttest.New(t, &coderdenttest.Options{
secondClient, _, secondAPI, _ := coderdenttest.NewWithAPI(t, &coderdenttest.Options{
Options: &coderdtest.Options{
Database: db,
Pubsub: pubsub,
DeploymentValues: coderdtest.DeploymentValues(t, func(dv *codersdk.DeploymentValues) {
require.NoError(t, dv.AI.Chat.AIGatewayRoutingEnabled.Set("false"))
}),
Database: db,
Pubsub: pubsub,
DeploymentValues: coderdtest.DeploymentValues(t),
},
DontAddLicense: true,
DontAddFirstUser: true,
})
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, secondAPI.AGPL, nil)
secondClient.SetSessionToken(firstClient.SessionToken())
// Verify we have two replicas.