fix(coderd): preseed AI providers for title tests (#27564)

This commit is contained in:
Michael Suchacz
2026-08-04 08:55:03 +02:00
committed by GitHub
parent a60f393773
commit 9bab7a8e06
2 changed files with 69 additions and 7 deletions
+17 -1
View File
@@ -15,6 +15,7 @@ import (
"github.com/coder/coder/v2/cli"
"github.com/coder/coder/v2/coderd"
"github.com/coder/coder/v2/coderd/aibridged"
"github.com/coder/coder/v2/coderd/database/pubsub"
)
// StartTestAIBridgeDaemon wires an in-process aibridged daemon onto the
@@ -37,6 +38,21 @@ func StartTestAIBridgeDaemon(
metrics *aibridged.Metrics,
) {
t.Helper()
StartTestAIBridgeDaemonWithPubsub(ctx, t, api, metrics, api.Pubsub)
}
// StartTestAIBridgeDaemonWithPubsub is StartTestAIBridgeDaemon with an
// explicit pubsub for the provider-reload subscription. A pubsub that is
// disconnected from api.Pubsub cuts the daemon off from provider change
// events, leaving the initial synchronous load as its only route source.
func StartTestAIBridgeDaemonWithPubsub(
ctx context.Context,
t testing.TB,
api *coderd.API,
metrics *aibridged.Metrics,
ps pubsub.Pubsub,
) {
t.Helper()
logger := api.Logger.Named("aibridged").Leveled(slog.LevelDebug)
cfg := api.DeploymentValues.AI.BridgeConfig
@@ -63,7 +79,7 @@ func StartTestAIBridgeDaemon(
// The reloader fetches providers from coderd over srv's DRPC client; the
// subscription drives an initial load and refreshes on change events.
reloader := cli.NewPoolRPCReloader(pool, srv.ClientContext, cfg, logger.Named("reloader"), nil, metrics)
unsubscribe, err := aibridged.SubscribeProviderReload(ctx, api.Pubsub, reloader, logger.Named("subscriber"))
unsubscribe, err := aibridged.SubscribeProviderReload(ctx, ps, reloader, logger.Named("subscriber"))
if err != nil {
t.Fatalf("subscribe provider reload: %v", err)
}
+52 -6
View File
@@ -40,6 +40,7 @@ import (
"github.com/coder/coder/v2/coderd/database/dbgen"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/coderd/database/dbtime"
dbpubsub "github.com/coder/coder/v2/coderd/database/pubsub"
"github.com/coder/coder/v2/coderd/externalauth"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/rbac/policy"
@@ -141,6 +142,14 @@ func newChatClientWithAPIAndDatabase(t testing.TB, overrides ...func(*coderdtest
return codersdk.NewExperimentalClient(client), api.Database, api
}
func newChatClientWithoutAIBridge(t testing.TB, overrides ...func(*coderdtest.Options)) (*codersdk.ExperimentalClient, database.Store, *coderd.API) {
t.Helper()
opts := newChatTestOptions(t, coderdtest.DeploymentValues(t), overrides...)
client, _, api := coderdtest.NewWithAPI(t, opts)
return codersdk.NewExperimentalClient(client), api.Database, api
}
func insertTestChatQueuedMessage(
ctx context.Context,
t testing.TB,
@@ -9540,9 +9549,10 @@ func TestRegenerateChatTitle(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, db := newChatClientWithDatabase(t)
client, db, api := newChatClientWithoutAIBridge(t)
user := coderdtest.CreateFirstUser(t, client.Client)
modelConfig := createTitleGenerationModelConfig(t, client)
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
chat := dbgen.Chat(t, db, database.Chat{
OrganizationID: user.OrganizationID,
@@ -9560,13 +9570,45 @@ func TestRegenerateChatTitle(t *testing.T) {
require.Equal(t, "Test Chat", updated.Title)
})
t.Run("NoPubsubDelivery", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, db, api := newChatClientWithoutAIBridge(t)
user := coderdtest.CreateFirstUser(t, client.Client)
modelConfig := createTitleGenerationModelConfig(t, client)
// Wire the daemon's reload subscription to a pubsub coderd never
// publishes to: gateway routes can then only come from the
// synchronous initial load. This guards the invariant the
// create-config-before-daemon pattern above relies on; if the
// initial load is removed or made asynchronous, this fails
// deterministically instead of reintroducing the startup race.
isolated := dbpubsub.NewInMemory()
t.Cleanup(func() { _ = isolated.Close() })
aibridgedtest.StartTestAIBridgeDaemonWithPubsub(t.Context(), t, api, nil, isolated)
chat := dbgen.Chat(t, db, database.Chat{
OrganizationID: user.OrganizationID,
OwnerID: user.UserID,
LastModelConfigID: modelConfig.ID,
Title: "New Chat",
})
seedManualTitleSourceMessage(t, db, chat, modelConfig.ID)
updated, err := client.RegenerateChatTitle(ctx, chat.ID)
require.NoError(t, err)
require.Equal(t, "Test Chat", updated.Title)
})
t.Run("DoesNotBumpHistoryVersion", func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, db := newChatClientWithDatabase(t)
client, db, api := newChatClientWithoutAIBridge(t)
user := coderdtest.CreateFirstUser(t, client.Client)
modelConfig := createTitleGenerationModelConfig(t, client)
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
chat := dbgen.Chat(t, db, database.Chat{
OrganizationID: user.OrganizationID,
@@ -9614,9 +9656,10 @@ func TestRegenerateChatTitle(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, db, api := newChatClientWithAPIAndDatabase(t)
client, db, api := newChatClientWithoutAIBridge(t)
firstUser := coderdtest.CreateFirstUser(t, client.Client)
_ = createChatModelConfigWithTitleFailure(t, client)
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
chat, err := client.CreateChat(ctx, codersdk.CreateChatRequest{
OrganizationID: firstUser.OrganizationID,
@@ -9726,9 +9769,10 @@ func TestProposeChatTitle(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, db := newChatClientWithDatabase(t)
client, db, api := newChatClientWithoutAIBridge(t)
user := coderdtest.CreateFirstUser(t, client.Client)
modelConfig := createTitleGenerationModelConfig(t, client)
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
chat := dbgen.Chat(t, db, database.Chat{
OrganizationID: user.OrganizationID,
@@ -9774,9 +9818,10 @@ func TestProposeChatTitle(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, db := newChatClientWithDatabase(t)
client, db, api := newChatClientWithoutAIBridge(t)
user := coderdtest.CreateFirstUser(t, client.Client)
modelConfig := createTitleGenerationModelConfig(t, client)
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
workspaceBuild := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
OrganizationID: user.OrganizationID,
@@ -9809,9 +9854,10 @@ func TestProposeChatTitle(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitLong)
client, db, api := newChatClientWithAPIAndDatabase(t)
client, db, api := newChatClientWithoutAIBridge(t)
firstUser := coderdtest.CreateFirstUser(t, client.Client)
_ = createChatModelConfigWithTitleFailure(t, client)
aibridgedtest.StartTestAIBridgeDaemon(t.Context(), t, api, nil)
chat, err := client.CreateChat(ctx, codersdk.CreateChatRequest{
OrganizationID: firstUser.OrganizationID,