mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor: add dbgen chat generators and migrate test boilerplate (#24497)
- Adds chat-related dbgen generators covering defaults, overrides, and message field mapping. - Replaces raw single-row chat, message, provider, and model-config setup in tests with dbgen helpers. - Simplifies chat seed helpers after moving fixture setup into dbgen. > Generated with [Coder Agents](https://coder.com/agents).
This commit is contained in:
@@ -4847,13 +4847,17 @@ func TestAutoPromote_InsertFailureSkipsStatusUpdate(t *testing.T) {
|
||||
heartbeatRegistry: make(map[uuid.UUID]*heartbeatEntry),
|
||||
}
|
||||
|
||||
// Block model resolution until the running status has been
|
||||
// published. Returning ErrInterrupted makes processChat enter the
|
||||
// waiting-state auto-promotion path deterministically.
|
||||
// Hold model resolution until the interrupt has canceled the chat
|
||||
// context. Returning ErrInterrupted keeps processChat on the
|
||||
// interrupted path regardless of whether the cache singleflight sees
|
||||
// the caller cancellation or the DB fetch result first.
|
||||
modelBlocked := make(chan struct{})
|
||||
modelRelease := make(chan struct{})
|
||||
var modelBlockedOnce sync.Once
|
||||
db.EXPECT().GetChatModelConfigByID(gomock.Any(), gomock.Any()).DoAndReturn(
|
||||
func(context.Context, uuid.UUID) (database.ChatModelConfig, error) {
|
||||
<-modelBlocked
|
||||
func(_ context.Context, _ uuid.UUID) (database.ChatModelConfig, error) {
|
||||
modelBlockedOnce.Do(func() { close(modelBlocked) })
|
||||
<-modelRelease
|
||||
return database.ChatModelConfig{}, chatloop.ErrInterrupted
|
||||
},
|
||||
).AnyTimes()
|
||||
@@ -4916,7 +4920,20 @@ func TestAutoPromote_InsertFailureSkipsStatusUpdate(t *testing.T) {
|
||||
t.Fatal("timed out waiting for running status")
|
||||
}
|
||||
|
||||
close(modelBlocked)
|
||||
select {
|
||||
case <-modelBlocked:
|
||||
case <-ctx.Done():
|
||||
t.Fatal("timed out waiting for model resolution")
|
||||
}
|
||||
|
||||
// Publish an interrupt so processChat exits runChat.
|
||||
interruptMsg, err := json.Marshal(coderdpubsub.ChatStreamNotifyMessage{
|
||||
Status: string(database.ChatStatusWaiting),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = ps.Publish(coderdpubsub.ChatStreamNotifyChannel(chatID), interruptMsg)
|
||||
require.NoError(t, err)
|
||||
close(modelRelease)
|
||||
|
||||
select {
|
||||
case <-processDone:
|
||||
|
||||
+278
-566
File diff suppressed because it is too large
Load Diff
@@ -39,7 +39,7 @@ func TestService_IsEnabled(t *testing.T) {
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
db, _, _ := dbtestutil.NewDBWithSQLDB(t)
|
||||
_, owner, chat, model := seedChat(ctx, t, db)
|
||||
_, owner, chat, model := seedChat(t, db)
|
||||
require.NotEqual(t, uuid.Nil, model.ID)
|
||||
|
||||
svc := chatdebug.NewService(db, testutil.Logger(t), nil)
|
||||
@@ -77,7 +77,7 @@ func TestService_IsEnabled_AlwaysEnable(t *testing.T) {
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
db, _, _ := dbtestutil.NewDBWithSQLDB(t)
|
||||
_, owner, chat, model := seedChat(ctx, t, db)
|
||||
_, owner, chat, model := seedChat(t, db)
|
||||
require.NotEqual(t, uuid.Nil, model.ID)
|
||||
|
||||
svc := chatdebug.NewService(db, testutil.Logger(t), nil, chatdebug.WithAlwaysEnable(true))
|
||||
@@ -98,11 +98,11 @@ func TestService_CreateRun(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fixture := newFixture(t)
|
||||
rootChat := insertChat(fixture.ctx, t, fixture.db, fixture.org.ID, fixture.owner.ID, fixture.model.ID)
|
||||
parentChat := insertChat(fixture.ctx, t, fixture.db, fixture.org.ID, fixture.owner.ID, fixture.model.ID)
|
||||
triggerMsg := insertMessage(fixture.ctx, t, fixture.db, fixture.chat.ID,
|
||||
rootChat := insertChat(t, fixture.db, fixture.org.ID, fixture.owner.ID, fixture.model.ID)
|
||||
parentChat := insertChat(t, fixture.db, fixture.org.ID, fixture.owner.ID, fixture.model.ID)
|
||||
triggerMsg := insertMessage(t, fixture.db, fixture.chat.ID,
|
||||
fixture.owner.ID, fixture.model.ID, database.ChatMessageRoleUser, "trigger")
|
||||
historyTipMsg := insertMessage(fixture.ctx, t, fixture.db, fixture.chat.ID,
|
||||
historyTipMsg := insertMessage(t, fixture.db, fixture.chat.ID,
|
||||
fixture.owner.ID, fixture.model.ID, database.ChatMessageRoleAssistant,
|
||||
"history-tip")
|
||||
|
||||
@@ -279,7 +279,7 @@ func TestService_CreateStep(t *testing.T) {
|
||||
|
||||
fixture := newFixture(t)
|
||||
run := createRun(t, fixture)
|
||||
historyTipMsg := insertMessage(fixture.ctx, t, fixture.db, fixture.chat.ID,
|
||||
historyTipMsg := insertMessage(t, fixture.db, fixture.chat.ID,
|
||||
fixture.owner.ID, fixture.model.ID, database.ChatMessageRoleAssistant,
|
||||
"history-tip")
|
||||
|
||||
@@ -424,7 +424,7 @@ func TestService_CreateStep_ChatIDMismatchReportsNotFound(t *testing.T) {
|
||||
// attach a step to the existing run using the wrong chat_id.
|
||||
// The insert's locked_run WHERE fails on chat_id, producing
|
||||
// sql.ErrNoRows; classifyMissingRun must report not-found.
|
||||
otherChat := insertChat(fixture.ctx, t, fixture.db, fixture.org.ID,
|
||||
otherChat := insertChat(t, fixture.db, fixture.org.ID,
|
||||
fixture.owner.ID, fixture.model.ID)
|
||||
|
||||
_, err := fixture.svc.CreateStep(fixture.ctx, chatdebug.CreateStepParams{
|
||||
@@ -454,7 +454,7 @@ func TestService_UpdateStep(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assistantMsg := insertMessage(fixture.ctx, t, fixture.db, fixture.chat.ID,
|
||||
assistantMsg := insertMessage(t, fixture.db, fixture.chat.ID,
|
||||
fixture.owner.ID, fixture.model.ID, database.ChatMessageRoleAssistant,
|
||||
"assistant")
|
||||
finishedAt := time.Now().UTC().Round(time.Microsecond)
|
||||
@@ -598,12 +598,12 @@ func TestService_DeleteAfterMessageID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fixture := newFixture(t)
|
||||
low := insertMessage(fixture.ctx, t, fixture.db, fixture.chat.ID, fixture.owner.ID,
|
||||
low := insertMessage(t, fixture.db, fixture.chat.ID, fixture.owner.ID,
|
||||
fixture.model.ID, database.ChatMessageRoleAssistant, "low")
|
||||
threshold := insertMessage(fixture.ctx, t, fixture.db, fixture.chat.ID,
|
||||
threshold := insertMessage(t, fixture.db, fixture.chat.ID,
|
||||
fixture.owner.ID, fixture.model.ID, database.ChatMessageRoleAssistant,
|
||||
"threshold")
|
||||
high := insertMessage(fixture.ctx, t, fixture.db, fixture.chat.ID, fixture.owner.ID,
|
||||
high := insertMessage(t, fixture.db, fixture.chat.ID, fixture.owner.ID,
|
||||
fixture.model.ID, database.ChatMessageRoleAssistant, "high")
|
||||
require.Less(t, low.ID, threshold.ID)
|
||||
require.Less(t, threshold.ID, high.ID)
|
||||
@@ -685,7 +685,7 @@ func TestService_FinalizeStale(t *testing.T) {
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
_, owner, chat, model := seedChat(ctx, t, db)
|
||||
_, owner, chat, model := seedChat(t, db)
|
||||
require.NotEqual(t, uuid.Nil, owner.ID)
|
||||
|
||||
staleTime := time.Now().Add(-10 * time.Minute).UTC().Round(time.Microsecond)
|
||||
@@ -733,7 +733,7 @@ func TestService_FinalizeStale_BroadcastsFinalizeEvent(t *testing.T) {
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
_, owner, chat, model := seedChat(ctx, t, db)
|
||||
_, owner, chat, model := seedChat(t, db)
|
||||
require.NotEqual(t, uuid.Nil, owner.ID)
|
||||
|
||||
staleTime := time.Now().Add(-10 * time.Minute).UTC().Round(time.Microsecond)
|
||||
@@ -796,7 +796,7 @@ func TestService_FinalizeStale_NoChangesDoesNotBroadcast(t *testing.T) {
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
_, owner, chat, _ := seedChat(ctx, t, db)
|
||||
_, owner, chat, _ := seedChat(t, db)
|
||||
require.NotEqual(t, uuid.Nil, owner.ID)
|
||||
|
||||
memoryPubsub := dbpubsub.NewInMemory()
|
||||
@@ -1018,7 +1018,7 @@ func TestService_PublishesEvents(t *testing.T) {
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
_, owner, chat, model := seedChat(ctx, t, db)
|
||||
_, owner, chat, model := seedChat(t, db)
|
||||
require.NotEqual(t, uuid.Nil, owner.ID)
|
||||
|
||||
memoryPubsub := dbpubsub.NewInMemory()
|
||||
@@ -1069,7 +1069,7 @@ func newFixture(t *testing.T) testFixture {
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
org, owner, chat, model := seedChat(ctx, t, db)
|
||||
org, owner, chat, model := seedChat(t, db)
|
||||
return testFixture{
|
||||
ctx: ctx,
|
||||
db: db,
|
||||
@@ -1082,7 +1082,6 @@ func newFixture(t *testing.T) testFixture {
|
||||
}
|
||||
|
||||
func seedChat(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
) (database.Organization, database.User, database.Chat, database.ChatModelConfig) {
|
||||
@@ -1091,38 +1090,21 @@ func seedChat(
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
owner := dbgen.User(t, db, database.User{})
|
||||
providerName := "openai"
|
||||
_, err := db.InsertChatProvider(ctx, database.InsertChatProviderParams{
|
||||
Provider: providerName,
|
||||
DisplayName: "OpenAI",
|
||||
APIKey: "test-key",
|
||||
CreatedBy: uuid.NullUUID{UUID: owner.ID, Valid: true},
|
||||
Enabled: true,
|
||||
CentralApiKeyEnabled: true,
|
||||
dbgen.ChatProvider(t, db, database.ChatProvider{
|
||||
Provider: providerName,
|
||||
DisplayName: "OpenAI",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
model, err := db.InsertChatModelConfig(ctx,
|
||||
database.InsertChatModelConfigParams{
|
||||
Provider: providerName,
|
||||
Model: "model-" + uuid.NewString(),
|
||||
DisplayName: "Test Model",
|
||||
CreatedBy: uuid.NullUUID{UUID: owner.ID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: owner.ID, Valid: true},
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
ContextLimit: 128000,
|
||||
CompressionThreshold: 70,
|
||||
Options: json.RawMessage(`{}`),
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
model := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
|
||||
Model: "model-" + uuid.NewString(),
|
||||
IsDefault: true,
|
||||
})
|
||||
|
||||
chat := insertChat(ctx, t, db, org.ID, owner.ID, model.ID)
|
||||
chat := insertChat(t, db, org.ID, owner.ID, model.ID)
|
||||
return org, owner, chat, model
|
||||
}
|
||||
|
||||
func insertChat(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
orgID uuid.UUID,
|
||||
@@ -1131,20 +1113,16 @@ func insertChat(
|
||||
) database.Chat {
|
||||
t.Helper()
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: orgID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: ownerID,
|
||||
LastModelConfigID: modelID,
|
||||
Title: "chat-" + uuid.NewString(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return chat
|
||||
}
|
||||
|
||||
func insertMessage(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
chatID uuid.UUID,
|
||||
@@ -1160,29 +1138,16 @@ func insertMessage(
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
messages, err := db.InsertChatMessages(ctx, database.InsertChatMessagesParams{
|
||||
ChatID: chatID,
|
||||
CreatedBy: []uuid.UUID{createdBy},
|
||||
ModelConfigID: []uuid.UUID{modelID},
|
||||
Role: []database.ChatMessageRole{role},
|
||||
Content: []string{string(parts.RawMessage)},
|
||||
ContentVersion: []int16{chatprompt.CurrentContentVersion},
|
||||
Visibility: []database.ChatMessageVisibility{database.ChatMessageVisibilityBoth},
|
||||
InputTokens: []int64{0},
|
||||
OutputTokens: []int64{0},
|
||||
TotalTokens: []int64{0},
|
||||
ReasoningTokens: []int64{0},
|
||||
CacheCreationTokens: []int64{0},
|
||||
CacheReadTokens: []int64{0},
|
||||
ContextLimit: []int64{0},
|
||||
Compressed: []bool{false},
|
||||
TotalCostMicros: []int64{0},
|
||||
RuntimeMs: []int64{0},
|
||||
ProviderResponseID: []string{""},
|
||||
msg := dbgen.ChatMessage(t, db, database.ChatMessage{
|
||||
ChatID: chatID,
|
||||
CreatedBy: uuid.NullUUID{UUID: createdBy, Valid: true},
|
||||
ModelConfigID: uuid.NullUUID{UUID: modelID, Valid: true},
|
||||
Role: role,
|
||||
Content: parts,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
ProviderResponseID: sql.NullString{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, messages, 1)
|
||||
return messages[0]
|
||||
return msg
|
||||
}
|
||||
|
||||
func createRun(t *testing.T, fixture testFixture) database.ChatDebugRun {
|
||||
|
||||
@@ -1815,35 +1815,16 @@ func TestNulEscapeRoundTrip(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
// Seed minimal dependencies for the DB round-trip path:
|
||||
// user, provider, model config, chat.
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
|
||||
_, err := db.InsertChatProvider(ctx, database.InsertChatProviderParams{
|
||||
Provider: "openai",
|
||||
DisplayName: "openai",
|
||||
APIKey: "test-key",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
Enabled: true,
|
||||
CentralApiKeyEnabled: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
dbgen.ChatProvider(t, db, database.ChatProvider{})
|
||||
|
||||
model, err := db.InsertChatModelConfig(ctx, database.InsertChatModelConfigParams{
|
||||
Provider: "openai",
|
||||
Model: "gpt-4o-mini",
|
||||
DisplayName: "Test Model",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
ContextLimit: 128000,
|
||||
CompressionThreshold: 70,
|
||||
Options: json.RawMessage(`{}`),
|
||||
model := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
|
||||
IsDefault: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
@@ -1851,15 +1832,12 @@ func TestNulEscapeRoundTrip(t *testing.T) {
|
||||
OrganizationID: org.ID,
|
||||
})
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
LastModelConfigID: model.ID,
|
||||
Title: "nul-roundtrip-test",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
textTests := []struct {
|
||||
name string
|
||||
@@ -1945,31 +1923,17 @@ func TestNulEscapeRoundTrip(t *testing.T) {
|
||||
// Full DB round-trip: write to PostgreSQL jsonb, read
|
||||
// back, and verify the value survives storage.
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
dbMsgs, err := db.InsertChatMessages(ctx, database.InsertChatMessagesParams{
|
||||
ChatID: chat.ID,
|
||||
CreatedBy: []uuid.UUID{user.ID},
|
||||
ModelConfigID: []uuid.UUID{model.ID},
|
||||
Role: []database.ChatMessageRole{database.ChatMessageRoleAssistant},
|
||||
Content: []string{string(encoded.RawMessage)},
|
||||
ContentVersion: []int16{chatprompt.CurrentContentVersion},
|
||||
Visibility: []database.ChatMessageVisibility{database.ChatMessageVisibilityBoth},
|
||||
InputTokens: []int64{0},
|
||||
OutputTokens: []int64{0},
|
||||
TotalTokens: []int64{0},
|
||||
ReasoningTokens: []int64{0},
|
||||
CacheCreationTokens: []int64{0},
|
||||
CacheReadTokens: []int64{0},
|
||||
ContextLimit: []int64{0},
|
||||
Compressed: []bool{false},
|
||||
TotalCostMicros: []int64{0},
|
||||
RuntimeMs: []int64{0},
|
||||
dbMsg := dbgen.ChatMessage(t, db, database.ChatMessage{
|
||||
ChatID: chat.ID,
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
ModelConfigID: uuid.NullUUID{UUID: model.ID, Valid: true},
|
||||
Role: database.ChatMessageRoleAssistant,
|
||||
Content: encoded,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, dbMsgs, 1)
|
||||
|
||||
readBack, err := db.GetChatMessageByID(ctx, dbMsgs[0].ID)
|
||||
readBack, err := db.GetChatMessageByID(ctx, dbMsg.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
dbDecoded, err := chatprompt.ParseContent(readBack)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, dbDecoded, 1)
|
||||
@@ -2392,29 +2356,16 @@ func TestMediaToolResultRoundTrip(t *testing.T) {
|
||||
OrganizationID: org.ID,
|
||||
})
|
||||
|
||||
_, err := db.InsertChatProvider(ctx, database.InsertChatProviderParams{
|
||||
Provider: "anthropic",
|
||||
DisplayName: "anthropic",
|
||||
APIKey: "test-key",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
Enabled: true,
|
||||
CentralApiKeyEnabled: true,
|
||||
dbgen.ChatProvider(t, db, database.ChatProvider{
|
||||
Provider: "anthropic",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
model, err := db.InsertChatModelConfig(ctx, database.InsertChatModelConfigParams{
|
||||
Provider: "anthropic",
|
||||
Model: "test-model",
|
||||
DisplayName: "Test Model",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
ContextLimit: 200000,
|
||||
CompressionThreshold: 70,
|
||||
Options: json.RawMessage(`{}`),
|
||||
model := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
|
||||
Provider: "anthropic",
|
||||
Model: "test-model",
|
||||
IsDefault: true,
|
||||
ContextLimit: 200000,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Small base64 payload standing in for a real screenshot.
|
||||
const imageData = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQI12NgAAIABQAB"
|
||||
@@ -2429,15 +2380,12 @@ func TestMediaToolResultRoundTrip(t *testing.T) {
|
||||
) database.Chat {
|
||||
t.Helper()
|
||||
|
||||
chat, chatErr := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
LastModelConfigID: model.ID,
|
||||
Title: "media-roundtrip-" + callID,
|
||||
})
|
||||
require.NoError(t, chatErr)
|
||||
|
||||
// Assistant message with the tool call.
|
||||
callPart := codersdk.ChatMessageToolCall(callID, toolName, json.RawMessage(`{}`))
|
||||
@@ -2448,26 +2396,22 @@ func TestMediaToolResultRoundTrip(t *testing.T) {
|
||||
resultEncoded, encErr := chatprompt.MarshalParts(resultParts)
|
||||
require.NoError(t, encErr)
|
||||
|
||||
_, insertErr := db.InsertChatMessages(ctx, database.InsertChatMessagesParams{
|
||||
ChatID: chat.ID,
|
||||
CreatedBy: []uuid.UUID{user.ID, user.ID},
|
||||
ModelConfigID: []uuid.UUID{model.ID, model.ID},
|
||||
Role: []database.ChatMessageRole{database.ChatMessageRoleAssistant, database.ChatMessageRoleTool},
|
||||
Content: []string{string(assistantEncoded.RawMessage), string(resultEncoded.RawMessage)},
|
||||
ContentVersion: []int16{chatprompt.CurrentContentVersion, chatprompt.CurrentContentVersion},
|
||||
Visibility: []database.ChatMessageVisibility{database.ChatMessageVisibilityBoth, database.ChatMessageVisibilityBoth},
|
||||
InputTokens: []int64{0, 0},
|
||||
OutputTokens: []int64{0, 0},
|
||||
TotalTokens: []int64{0, 0},
|
||||
ReasoningTokens: []int64{0, 0},
|
||||
CacheCreationTokens: []int64{0, 0},
|
||||
CacheReadTokens: []int64{0, 0},
|
||||
ContextLimit: []int64{0, 0},
|
||||
Compressed: []bool{false, false},
|
||||
TotalCostMicros: []int64{0, 0},
|
||||
RuntimeMs: []int64{0, 0},
|
||||
_ = dbgen.ChatMessage(t, db, database.ChatMessage{
|
||||
ChatID: chat.ID,
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
ModelConfigID: uuid.NullUUID{UUID: model.ID, Valid: true},
|
||||
Role: database.ChatMessageRoleAssistant,
|
||||
Content: assistantEncoded,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
})
|
||||
_ = dbgen.ChatMessage(t, db, database.ChatMessage{
|
||||
ChatID: chat.ID,
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
ModelConfigID: uuid.NullUUID{UUID: model.ID, Valid: true},
|
||||
Role: database.ChatMessageRoleTool,
|
||||
Content: resultEncoded,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
})
|
||||
require.NoError(t, insertErr)
|
||||
return chat
|
||||
}
|
||||
|
||||
|
||||
@@ -35,22 +35,19 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
OrganizationID: org.ID,
|
||||
})
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-no-workspace",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
tool := chattool.StartWorkspace(chattool.StartWorkspaceOptions{
|
||||
DB: db,
|
||||
@@ -73,7 +70,7 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
@@ -87,16 +84,13 @@ func TestStartWorkspace(t *testing.T) {
|
||||
}).Do()
|
||||
ws := wsResp.Workspace
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true},
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-already-running",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
agentConnFn := func(_ context.Context, _ uuid.UUID) (workspacesdk.AgentConn, func(), error) {
|
||||
return nil, func() {}, nil
|
||||
@@ -132,7 +126,7 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
@@ -170,16 +164,13 @@ func TestStartWorkspace(t *testing.T) {
|
||||
}
|
||||
require.NotEqual(t, uuid.Nil, preferredAgentID)
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true},
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-running-preferred-agent",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var connectedAgentID uuid.UUID
|
||||
agentConnFn := func(_ context.Context, agentID uuid.UUID) (workspacesdk.AgentConn, func(), error) {
|
||||
@@ -216,7 +207,7 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
@@ -232,16 +223,13 @@ func TestStartWorkspace(t *testing.T) {
|
||||
}).Do()
|
||||
ws := wsResp.Workspace
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true},
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-running-no-agent",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
tool := chattool.StartWorkspace(chattool.StartWorkspaceOptions{
|
||||
DB: db,
|
||||
@@ -275,7 +263,7 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
@@ -297,16 +285,13 @@ func TestStartWorkspace(t *testing.T) {
|
||||
}).Do()
|
||||
ws := wsResp.Workspace
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true},
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-running-selection-error",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
tool := chattool.StartWorkspace(chattool.StartWorkspaceOptions{
|
||||
DB: db,
|
||||
@@ -341,7 +326,7 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
@@ -356,16 +341,13 @@ func TestStartWorkspace(t *testing.T) {
|
||||
}).Do()
|
||||
ws := wsResp.Workspace
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true},
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-stopped-workspace",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var startCalled bool
|
||||
var startBuildID uuid.UUID
|
||||
@@ -415,7 +397,7 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
@@ -429,16 +411,13 @@ func TestStartWorkspace(t *testing.T) {
|
||||
}).Do()
|
||||
ws := wsResp.Workspace
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true},
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-stopped-workspace-auto-update",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
startFn := func(_ context.Context, _ uuid.UUID, wsID uuid.UUID, req codersdk.CreateWorkspaceBuildRequest) (codersdk.WorkspaceBuild, error) {
|
||||
require.Equal(t, codersdk.WorkspaceTransitionStart, req.Transition)
|
||||
@@ -480,7 +459,7 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
@@ -494,16 +473,13 @@ func TestStartWorkspace(t *testing.T) {
|
||||
}).Do()
|
||||
ws := wsResp.Workspace
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true},
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-start-workspace-passes-parameters",
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedParams := []codersdk.WorkspaceBuildParameter{
|
||||
{Name: "region", Value: "us-east-1"},
|
||||
@@ -545,7 +521,7 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
@@ -559,16 +535,13 @@ func TestStartWorkspace(t *testing.T) {
|
||||
}).Do()
|
||||
ws := wsResp.Workspace
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true},
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-start-workspace-manual-update-required",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
tool := chattool.StartWorkspace(chattool.StartWorkspaceOptions{
|
||||
DB: db,
|
||||
@@ -615,7 +588,7 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
@@ -629,16 +602,13 @@ func TestStartWorkspace(t *testing.T) {
|
||||
}).Do()
|
||||
ws := wsResp.Workspace
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true},
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-start-workspace-responder-error-without-validations",
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
tool := chattool.StartWorkspace(chattool.StartWorkspaceOptions{
|
||||
DB: db,
|
||||
@@ -671,7 +641,7 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
@@ -686,16 +656,13 @@ func TestStartWorkspace(t *testing.T) {
|
||||
}).Starting().Do()
|
||||
ws := wsResp.Workspace
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true},
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-in-progress-build",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wrap the DB so we know exactly when the tool reads
|
||||
// the job status. The interceptor signals AFTER the
|
||||
@@ -768,7 +735,7 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
@@ -783,16 +750,13 @@ func TestStartWorkspace(t *testing.T) {
|
||||
}).Starting().Do()
|
||||
ws := wsResp.Workspace
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true},
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-failed-build",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
jobRead := make(chan struct{}, 1)
|
||||
wrappedDB := &jobInterceptStore{Store: db, jobRead: jobRead}
|
||||
@@ -851,7 +815,7 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
@@ -866,16 +830,13 @@ func TestStartWorkspace(t *testing.T) {
|
||||
}).Do()
|
||||
ws := wsResp.Workspace
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true},
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-start-triggered-build-failure",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// StartFn creates a real in-progress build via dbfake.
|
||||
var startBuildJobID uuid.UUID
|
||||
@@ -949,7 +910,7 @@ func TestStartWorkspace(t *testing.T) {
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
user := dbgen.User(t, db, database.User{})
|
||||
modelCfg := seedModelConfig(ctx, t, db, user.ID)
|
||||
modelCfg := seedModelConfig(t, db)
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
_ = dbgen.OrganizationMember(t, db, database.OrganizationMember{
|
||||
UserID: user.ID,
|
||||
@@ -965,16 +926,13 @@ func TestStartWorkspace(t *testing.T) {
|
||||
}).Do()
|
||||
ws := wsResp.Workspace
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: ws.ID, Valid: true},
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "test-deleted-workspace",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
tool := chattool.StartWorkspace(chattool.StartWorkspaceOptions{
|
||||
DB: db,
|
||||
@@ -994,39 +952,15 @@ func TestStartWorkspace(t *testing.T) {
|
||||
|
||||
// seedModelConfig inserts a provider and model config for testing.
|
||||
func seedModelConfig(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
userID uuid.UUID,
|
||||
) database.ChatModelConfig {
|
||||
t.Helper()
|
||||
|
||||
_, err := db.InsertChatProvider(ctx, database.InsertChatProviderParams{
|
||||
Provider: "openai",
|
||||
DisplayName: "OpenAI",
|
||||
APIKey: "test-key",
|
||||
BaseUrl: "",
|
||||
ApiKeyKeyID: sql.NullString{},
|
||||
CreatedBy: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
Enabled: true,
|
||||
CentralApiKeyEnabled: true,
|
||||
dbgen.ChatProvider(t, db, database.ChatProvider{})
|
||||
return dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
|
||||
IsDefault: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
model, err := db.InsertChatModelConfig(ctx, database.InsertChatModelConfigParams{
|
||||
Provider: "openai",
|
||||
Model: "gpt-4o-mini",
|
||||
DisplayName: "Test Model",
|
||||
CreatedBy: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
ContextLimit: 128000,
|
||||
CompressionThreshold: 70,
|
||||
Options: json.RawMessage(`{}`),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return model
|
||||
}
|
||||
|
||||
// jobInterceptStore wraps a database.Store and signals a
|
||||
|
||||
@@ -13,7 +13,9 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
dbpubsub "github.com/coder/coder/v2/coderd/database/pubsub"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chatprompt"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chattest"
|
||||
@@ -63,9 +65,9 @@ func TestOpenAIResponsesNoStaleWebSearchReplay(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
user, org, _ := seedChatDependenciesWithProvider(ctx, t, db, "openai", openAIURL)
|
||||
model := insertOpenAIResponsesModelConfig(ctx, t, db, user.ID, false, true)
|
||||
server := newActiveTestServer(t, db, ps)
|
||||
user, org, _ := seedChatDependenciesWithProvider(t, db, "openai", openAIURL)
|
||||
model := insertOpenAIResponsesModelConfig(t, db, user.ID, false, true)
|
||||
server := newOpenAIResponsesTestServer(t, db, ps)
|
||||
|
||||
chat, err := server.CreateChat(ctx, chatd.CreateOptions{
|
||||
OrganizationID: org.ID,
|
||||
@@ -145,10 +147,10 @@ func TestOpenAIResponsesFullReplayPairsReasoningAndWebSearch(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
user, org, _ := seedChatDependenciesWithProvider(ctx, t, db, "openai", openAIURL)
|
||||
firstModel := insertOpenAIResponsesModelConfig(ctx, t, db, user.ID, true, true)
|
||||
secondModel := insertOpenAIResponsesModelConfig(ctx, t, db, user.ID, true, true)
|
||||
server := newActiveTestServer(t, db, ps)
|
||||
user, org, _ := seedChatDependenciesWithProvider(t, db, "openai", openAIURL)
|
||||
firstModel := insertOpenAIResponsesModelConfig(t, db, user.ID, true, true)
|
||||
secondModel := insertOpenAIResponsesModelConfig(t, db, user.ID, true, true)
|
||||
server := newOpenAIResponsesTestServer(t, db, ps)
|
||||
|
||||
chat, err := server.CreateChat(ctx, chatd.CreateOptions{
|
||||
OrganizationID: org.ID,
|
||||
@@ -205,9 +207,9 @@ func TestOpenAIResponsesChainModeSkipsWhenLocalCallPending(t *testing.T) {
|
||||
return resp
|
||||
})
|
||||
|
||||
user, org, _ := seedChatDependenciesWithProvider(ctx, t, db, "openai", openAIURL)
|
||||
model := insertOpenAIResponsesModelConfig(ctx, t, db, user.ID, true, false)
|
||||
chat := insertOpenAIResponsesChat(ctx, t, db, org.ID, user.ID, model.ID, "local-pending")
|
||||
user, org, _ := seedChatDependenciesWithProvider(t, db, "openai", openAIURL)
|
||||
model := insertOpenAIResponsesModelConfig(t, db, user.ID, true, false)
|
||||
chat := insertOpenAIResponsesChat(t, db, org.ID, user.ID, model.ID, "local-pending")
|
||||
|
||||
callID := fmt.Sprintf("call_local_%d", time.Now().UnixNano())
|
||||
localCall := codersdk.ChatMessageToolCall(
|
||||
@@ -229,7 +231,7 @@ func TestOpenAIResponsesChainModeSkipsWhenLocalCallPending(t *testing.T) {
|
||||
},
|
||||
)
|
||||
|
||||
server := newActiveTestServer(t, db, ps)
|
||||
server := newOpenAIResponsesTestServer(t, db, ps)
|
||||
_, err := server.SendMessage(ctx, chatd.SendMessageOptions{
|
||||
ChatID: chat.ID,
|
||||
CreatedBy: user.ID,
|
||||
@@ -272,9 +274,9 @@ func TestOpenAIResponsesChainModeStillFiresForProviderExecutedOnly(t *testing.T)
|
||||
return resp
|
||||
})
|
||||
|
||||
user, org, _ := seedChatDependenciesWithProvider(ctx, t, db, "openai", openAIURL)
|
||||
model := insertOpenAIResponsesModelConfig(ctx, t, db, user.ID, true, true)
|
||||
chat := insertOpenAIResponsesChat(ctx, t, db, org.ID, user.ID, model.ID, "provider-only")
|
||||
user, org, _ := seedChatDependenciesWithProvider(t, db, "openai", openAIURL)
|
||||
model := insertOpenAIResponsesModelConfig(t, db, user.ID, true, true)
|
||||
chat := insertOpenAIResponsesChat(t, db, org.ID, user.ID, model.ID, "provider-only")
|
||||
|
||||
const (
|
||||
previousResponseID = "resp_provider_only_prior"
|
||||
@@ -311,7 +313,7 @@ func TestOpenAIResponsesChainModeStillFiresForProviderExecutedOnly(t *testing.T)
|
||||
},
|
||||
)
|
||||
|
||||
server := newActiveTestServer(t, db, ps)
|
||||
server := newOpenAIResponsesTestServer(t, db, ps)
|
||||
_, err := server.SendMessage(ctx, chatd.SendMessageOptions{
|
||||
ChatID: chat.ID,
|
||||
CreatedBy: user.ID,
|
||||
@@ -382,8 +384,23 @@ type persistedResponsesMessage struct {
|
||||
providerResponseID string
|
||||
}
|
||||
|
||||
func newOpenAIResponsesTestServer(
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
ps dbpubsub.Pubsub,
|
||||
) *chatd.Server {
|
||||
t.Helper()
|
||||
return newActiveTestServer(t, db, ps, func(cfg *chatd.Config) {
|
||||
// Let CreateChat and SendMessage publish their pending status
|
||||
// before wake-driven processing starts. The responses tests are
|
||||
// not exercising periodic polling, and PostgreSQL can otherwise
|
||||
// deliver that stale pending notification after processChat
|
||||
// subscribes to control events.
|
||||
cfg.PendingChatAcquireInterval = testutil.WaitLong
|
||||
})
|
||||
}
|
||||
|
||||
func insertOpenAIResponsesModelConfig(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
userID uuid.UUID,
|
||||
@@ -392,7 +409,6 @@ func insertOpenAIResponsesModelConfig(
|
||||
) database.ChatModelConfig {
|
||||
t.Helper()
|
||||
return insertChatModelConfigWithCallConfig(
|
||||
ctx,
|
||||
t,
|
||||
db,
|
||||
userID,
|
||||
@@ -410,7 +426,6 @@ func insertOpenAIResponsesModelConfig(
|
||||
}
|
||||
|
||||
func insertOpenAIResponsesChat(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
organizationID uuid.UUID,
|
||||
@@ -419,7 +434,7 @@ func insertOpenAIResponsesChat(
|
||||
titlePrefix string,
|
||||
) database.Chat {
|
||||
t.Helper()
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
return dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: organizationID,
|
||||
OwnerID: ownerID,
|
||||
LastModelConfigID: modelConfigID,
|
||||
@@ -428,8 +443,6 @@ func insertOpenAIResponsesChat(
|
||||
MCPServerIDs: []uuid.UUID{},
|
||||
ClientType: database.ChatClientTypeApi,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return chat
|
||||
}
|
||||
|
||||
func insertOpenAIResponsesMessages(
|
||||
@@ -464,6 +477,8 @@ func insertOpenAIResponsesMessages(
|
||||
params.RuntimeMs = append(params.RuntimeMs, 0)
|
||||
params.ProviderResponseID = append(params.ProviderResponseID, message.providerResponseID)
|
||||
}
|
||||
// Keep this raw because dbgen.ChatMessage inserts one message at a time,
|
||||
// while this helper needs to preserve variadic batch insert behavior.
|
||||
_, err := db.InsertChatMessages(ctx, params)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chatprovider"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
@@ -84,7 +85,6 @@ func validRecordingJPEG(extra int, fill byte) []byte {
|
||||
// background processing (which would try to call the LLM and
|
||||
// use the agent connection mock).
|
||||
func createComputerUseParentChild(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
server *Server,
|
||||
user database.User,
|
||||
@@ -98,7 +98,7 @@ func createComputerUseParentChild(
|
||||
|
||||
// Insert the parent chat directly via DB to avoid triggering
|
||||
// the server's background processing.
|
||||
parent, err := server.db.InsertChat(ctx, database.InsertChatParams{
|
||||
parent = dbgen.Chat(t, server.db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: workspace.ID, Valid: true},
|
||||
@@ -106,14 +106,12 @@ func createComputerUseParentChild(
|
||||
LastModelConfigID: model.ID,
|
||||
Title: parentTitle,
|
||||
Status: database.ChatStatusPending,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Insert the child chat directly via DB to avoid triggering
|
||||
// the server's background processing (which would try to run
|
||||
// the chat without an LLM and get stuck).
|
||||
child, err = server.db.InsertChat(ctx, database.InsertChatParams{
|
||||
child = dbgen.Chat(t, server.db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
OwnerID: user.ID,
|
||||
WorkspaceID: uuid.NullUUID{UUID: workspace.ID, Valid: true},
|
||||
@@ -124,9 +122,7 @@ func createComputerUseParentChild(
|
||||
Title: childTitle,
|
||||
Mode: database.NullChatMode{ChatMode: database.ChatModeComputerUse, Valid: true},
|
||||
Status: database.ChatStatusPending,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return parent, child
|
||||
}
|
||||
@@ -178,7 +174,7 @@ func TestWaitAgentComputerUseRecording(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, agent := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
// Create the server WITHOUT agentConnFn so the background
|
||||
@@ -186,7 +182,7 @@ func TestWaitAgentComputerUseRecording(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
parent, child := createComputerUseParentChild(
|
||||
ctx, t, server, user, org, model, workspace, agent,
|
||||
t, server, user, org, model, workspace, agent,
|
||||
"parent-recording", "computer-use-child",
|
||||
)
|
||||
|
||||
@@ -201,7 +197,7 @@ func TestWaitAgentComputerUseRecording(t *testing.T) {
|
||||
}
|
||||
|
||||
// Add an assistant message so the report is extracted.
|
||||
insertAssistantMessage(ctx, t, db, child.ID, model.ID, "I opened Firefox.")
|
||||
insertAssistantMessage(t, db, child.ID, model.ID, "I opened Firefox.")
|
||||
|
||||
// Set child to waiting (terminal success state).
|
||||
setChatStatus(ctx, t, db, child.ID, database.ChatStatusWaiting, "")
|
||||
@@ -268,13 +264,13 @@ func TestWaitAgentComputerUseRecordingWithThumbnail(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, agent := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
parent, child := createComputerUseParentChild(
|
||||
ctx, t, server, user, org, model, workspace, agent,
|
||||
t, server, user, org, model, workspace, agent,
|
||||
"parent-recording-thumb", "computer-use-child-thumb",
|
||||
)
|
||||
|
||||
@@ -285,7 +281,7 @@ func TestWaitAgentComputerUseRecordingWithThumbnail(t *testing.T) {
|
||||
return mockConn, func() {}, nil
|
||||
}
|
||||
|
||||
insertAssistantMessage(ctx, t, db, child.ID, model.ID, "I opened Firefox and took a screenshot.")
|
||||
insertAssistantMessage(t, db, child.ID, model.ID, "I opened Firefox and took a screenshot.")
|
||||
|
||||
setChatStatus(ctx, t, db, child.ID, database.ChatStatusWaiting, "")
|
||||
|
||||
@@ -360,7 +356,7 @@ func TestWaitAgentNonComputerUseNoRecording(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
@@ -368,7 +364,7 @@ func TestWaitAgentNonComputerUseNoRecording(t *testing.T) {
|
||||
parent, child := createParentChildChats(ctx, t, server, user, org, model)
|
||||
|
||||
// Add an assistant message so the report is extracted.
|
||||
insertAssistantMessage(ctx, t, db, child.ID, model.ID, "Done.")
|
||||
insertAssistantMessage(t, db, child.ID, model.ID, "Done.")
|
||||
|
||||
// Wait for background processing triggered by CreateChat to
|
||||
// settle before setting up the mock agent connection.
|
||||
@@ -411,7 +407,7 @@ func TestWaitAgentRecordingStartFails(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, agent := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
// Create the server WITHOUT agentConnFn so the background
|
||||
@@ -420,7 +416,7 @@ func TestWaitAgentRecordingStartFails(t *testing.T) {
|
||||
|
||||
// Create parent + computer_use child.
|
||||
parent, child := createComputerUseParentChild(
|
||||
ctx, t, server, user, org, model, workspace, agent,
|
||||
t, server, user, org, model, workspace, agent,
|
||||
"parent-start-fail", "computer-use-start-fail",
|
||||
)
|
||||
|
||||
@@ -429,7 +425,7 @@ func TestWaitAgentRecordingStartFails(t *testing.T) {
|
||||
return mockConn, func() {}, nil
|
||||
}
|
||||
|
||||
insertAssistantMessage(ctx, t, db, child.ID, model.ID, "Opened the browser.")
|
||||
insertAssistantMessage(t, db, child.ID, model.ID, "Opened the browser.")
|
||||
setChatStatus(ctx, t, db, child.ID, database.ChatStatusWaiting, "")
|
||||
|
||||
// StartDesktopRecording fails. StopDesktopRecording must NOT
|
||||
@@ -465,7 +461,7 @@ func TestWaitAgentRecordingStopFails(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, agent := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
// Create the server WITHOUT agentConnFn so the background
|
||||
@@ -474,7 +470,7 @@ func TestWaitAgentRecordingStopFails(t *testing.T) {
|
||||
|
||||
// Create parent + computer_use child.
|
||||
parent, child := createComputerUseParentChild(
|
||||
ctx, t, server, user, org, model, workspace, agent,
|
||||
t, server, user, org, model, workspace, agent,
|
||||
"parent-stop-fail", "computer-use-stop-fail",
|
||||
)
|
||||
|
||||
@@ -483,7 +479,7 @@ func TestWaitAgentRecordingStopFails(t *testing.T) {
|
||||
return mockConn, func() {}, nil
|
||||
}
|
||||
|
||||
insertAssistantMessage(ctx, t, db, child.ID, model.ID, "Checked settings.")
|
||||
insertAssistantMessage(t, db, child.ID, model.ID, "Checked settings.")
|
||||
setChatStatus(ctx, t, db, child.ID, database.ChatStatusWaiting, "")
|
||||
|
||||
// Start succeeds, stop fails.
|
||||
@@ -526,12 +522,12 @@ func TestWaitAgentTimeoutLeavesRecordingRunning(t *testing.T) {
|
||||
// Use the mock clock server; don't set agentConnFn yet.
|
||||
server := newInternalTestServerWithClock(t, db, ps, chatprovider.ProviderAPIKeys{}, mClock)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, agent := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
// Create parent + computer_use child.
|
||||
_, child := createComputerUseParentChild(
|
||||
ctx, t, server, user, org, model, workspace, agent,
|
||||
t, server, user, org, model, workspace, agent,
|
||||
"parent-timeout", "computer-use-timeout",
|
||||
)
|
||||
|
||||
@@ -610,7 +606,7 @@ func TestStopAndStoreRecording_Oversized(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, _ := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
@@ -659,7 +655,7 @@ func TestStopAndStoreRecording_OversizedThumbnail(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, _ := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
@@ -723,7 +719,7 @@ func TestStopAndStoreRecording_DuplicatePartsIgnored(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, _ := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
@@ -766,7 +762,7 @@ func TestStopAndStoreRecording_Empty(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, _ := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
@@ -796,7 +792,7 @@ func TestStopAndStoreRecording_LinkFailureRollsBackInsert(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, _ := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
@@ -851,7 +847,7 @@ func TestStopAndStoreRecording_WithThumbnail(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, _ := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
@@ -905,7 +901,7 @@ func TestStopAndStoreRecording_VideoOnly(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, _ := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
@@ -947,7 +943,7 @@ func TestStopAndStoreRecording_MismatchedVideoBytesSkipped(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, _ := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
@@ -984,7 +980,7 @@ func TestStopAndStoreRecording_DownloadFailure(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, _ := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
@@ -1017,7 +1013,7 @@ func TestStopAndStoreRecording_UnknownPartIgnored(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, _ := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
@@ -1071,7 +1067,7 @@ func TestStopAndStoreRecording_MalformedContentType(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, _ := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
@@ -1107,7 +1103,7 @@ func TestStopAndStoreRecording_MissingBoundary(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
mockConn := agentconnmock.NewMockAgentConn(ctrl)
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, _ := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"cdr.dev/slog/v3"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chatprompt"
|
||||
"github.com/coder/coder/v2/coderd/x/chatd/chatprovider"
|
||||
@@ -148,7 +149,7 @@ func createParentChatWithInheritedContext(
|
||||
) database.Chat {
|
||||
t.Helper()
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
|
||||
parent, err := server.CreateChat(ctx, CreateOptions{
|
||||
OrganizationID: org.ID,
|
||||
@@ -182,26 +183,14 @@ func createParentChatWithInheritedContext(
|
||||
content, err := json.Marshal(inheritedParts)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.InsertChatMessages(ctx, database.InsertChatMessagesParams{
|
||||
ChatID: parent.ID,
|
||||
CreatedBy: []uuid.UUID{user.ID},
|
||||
ModelConfigID: []uuid.UUID{model.ID},
|
||||
Role: []database.ChatMessageRole{database.ChatMessageRoleUser},
|
||||
Content: []string{string(content)},
|
||||
ContentVersion: []int16{chatprompt.CurrentContentVersion},
|
||||
Visibility: []database.ChatMessageVisibility{database.ChatMessageVisibilityBoth},
|
||||
InputTokens: []int64{0},
|
||||
OutputTokens: []int64{0},
|
||||
TotalTokens: []int64{0},
|
||||
ReasoningTokens: []int64{0},
|
||||
CacheCreationTokens: []int64{0},
|
||||
CacheReadTokens: []int64{0},
|
||||
ContextLimit: []int64{0},
|
||||
Compressed: []bool{false},
|
||||
TotalCostMicros: []int64{0},
|
||||
RuntimeMs: []int64{0},
|
||||
_ = dbgen.ChatMessage(t, db, database.ChatMessage{
|
||||
ChatID: parent.ID,
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
ModelConfigID: uuid.NullUUID{UUID: model.ID, Valid: true},
|
||||
Role: database.ChatMessageRoleUser,
|
||||
Content: pqtype.NullRawMessage{RawMessage: content, Valid: true},
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
parentChat, err := db.GetChatByID(ctx, parent.ID)
|
||||
require.NoError(t, err)
|
||||
@@ -329,7 +318,7 @@ func createParentChatWithRotatedInheritedContext(
|
||||
) database.Chat {
|
||||
t.Helper()
|
||||
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
|
||||
parent, err := server.CreateChat(ctx, CreateOptions{
|
||||
OrganizationID: org.ID,
|
||||
@@ -379,26 +368,22 @@ func createParentChatWithRotatedInheritedContext(
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.InsertChatMessages(ctx, database.InsertChatMessagesParams{
|
||||
ChatID: parent.ID,
|
||||
CreatedBy: []uuid.UUID{user.ID, user.ID},
|
||||
ModelConfigID: []uuid.UUID{model.ID, model.ID},
|
||||
Role: []database.ChatMessageRole{database.ChatMessageRoleUser, database.ChatMessageRoleUser},
|
||||
Content: []string{string(oldContent), string(newContent)},
|
||||
ContentVersion: []int16{chatprompt.CurrentContentVersion, chatprompt.CurrentContentVersion},
|
||||
Visibility: []database.ChatMessageVisibility{database.ChatMessageVisibilityBoth, database.ChatMessageVisibilityBoth},
|
||||
InputTokens: []int64{0, 0},
|
||||
OutputTokens: []int64{0, 0},
|
||||
TotalTokens: []int64{0, 0},
|
||||
ReasoningTokens: []int64{0, 0},
|
||||
CacheCreationTokens: []int64{0, 0},
|
||||
CacheReadTokens: []int64{0, 0},
|
||||
ContextLimit: []int64{0, 0},
|
||||
Compressed: []bool{false, false},
|
||||
TotalCostMicros: []int64{0, 0},
|
||||
RuntimeMs: []int64{0, 0},
|
||||
_ = dbgen.ChatMessage(t, db, database.ChatMessage{
|
||||
ChatID: parent.ID,
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
ModelConfigID: uuid.NullUUID{UUID: model.ID, Valid: true},
|
||||
Role: database.ChatMessageRoleUser,
|
||||
Content: pqtype.NullRawMessage{RawMessage: oldContent, Valid: true},
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
})
|
||||
_ = dbgen.ChatMessage(t, db, database.ChatMessage{
|
||||
ChatID: parent.ID,
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
ModelConfigID: uuid.NullUUID{UUID: model.ID, Valid: true},
|
||||
Role: database.ChatMessageRoleUser,
|
||||
Content: pqtype.NullRawMessage{RawMessage: newContent, Valid: true},
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
parentChat, err := db.GetChatByID(ctx, parent.ID)
|
||||
require.NoError(t, err)
|
||||
@@ -476,7 +461,7 @@ func TestSpawnComputerUseAgentInheritsContext(t *testing.T) {
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
parentChat := createParentChatWithInheritedContext(ctx, t, db, server)
|
||||
insertEnabledAnthropicProvider(ctx, t, db, parentChat.OwnerID)
|
||||
insertEnabledAnthropicProvider(t, db, parentChat.OwnerID)
|
||||
// The direct DB insert above bypasses the pubsub event that
|
||||
// production uses to invalidate the provider cache. Explicitly
|
||||
// invalidate here so the background processing goroutine does
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"charm.land/fantasy"
|
||||
"github.com/google/uuid"
|
||||
"github.com/sqlc-dev/pqtype"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -172,7 +173,6 @@ func (s *subagentTestLogSink) entriesAtLevelWithMessage(
|
||||
// and model. This deliberately does NOT create an Anthropic
|
||||
// provider.
|
||||
func seedInternalChatDeps(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
) (database.User, database.Organization, database.ChatModelConfig) {
|
||||
@@ -184,31 +184,14 @@ func seedInternalChatDeps(
|
||||
UserID: user.ID,
|
||||
OrganizationID: org.ID,
|
||||
})
|
||||
_, err := db.InsertChatProvider(ctx, database.InsertChatProviderParams{
|
||||
Provider: "openai",
|
||||
DisplayName: "OpenAI",
|
||||
APIKey: "test-key",
|
||||
BaseUrl: "",
|
||||
ApiKeyKeyID: sql.NullString{},
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
Enabled: true,
|
||||
CentralApiKeyEnabled: true,
|
||||
dbgen.ChatProvider(t, db, database.ChatProvider{
|
||||
Provider: "openai",
|
||||
DisplayName: "OpenAI",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
model, err := db.InsertChatModelConfig(ctx, database.InsertChatModelConfigParams{
|
||||
Provider: "openai",
|
||||
Model: "gpt-4o-mini",
|
||||
DisplayName: "Test Model",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
ContextLimit: 128000,
|
||||
CompressionThreshold: 70,
|
||||
Options: json.RawMessage(`{}`),
|
||||
model := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
|
||||
IsDefault: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return user, org, model
|
||||
}
|
||||
@@ -217,24 +200,18 @@ func seedInternalChatDeps(
|
||||
// the current test user so computer_use flows keep Anthropic credentials
|
||||
// after provider-key pruning.
|
||||
func insertEnabledAnthropicProvider(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
userID uuid.UUID,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
_, err := db.InsertChatProvider(ctx, database.InsertChatProviderParams{
|
||||
Provider: "anthropic",
|
||||
DisplayName: "Anthropic",
|
||||
APIKey: "test-anthropic-key",
|
||||
BaseUrl: "",
|
||||
ApiKeyKeyID: sql.NullString{},
|
||||
CreatedBy: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
Enabled: true,
|
||||
CentralApiKeyEnabled: true,
|
||||
dbgen.ChatProvider(t, db, database.ChatProvider{
|
||||
Provider: "anthropic",
|
||||
DisplayName: "Anthropic",
|
||||
APIKey: "test-anthropic-key",
|
||||
CreatedBy: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestResolveUserProviderAPIKeys_PreservesAnthropicKeyFromDBProvider(t *testing.T) {
|
||||
@@ -247,8 +224,8 @@ func TestResolveUserProviderAPIKeys_PreservesAnthropicKeyFromDBProvider(t *testi
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, _, _ := seedInternalChatDeps(ctx, t, db)
|
||||
insertEnabledAnthropicProvider(ctx, t, db, user.ID)
|
||||
user, _, _ := seedInternalChatDeps(t, db)
|
||||
insertEnabledAnthropicProvider(t, db, user.ID)
|
||||
|
||||
keys, err := server.resolveUserProviderAPIKeys(ctx, user.ID)
|
||||
require.NoError(t, err)
|
||||
@@ -266,7 +243,7 @@ func TestResolveUserProviderAPIKeys_PreservesAnthropicKeyFromDBProvider(t *testi
|
||||
})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, _, _ := seedInternalChatDeps(ctx, t, db)
|
||||
user, _, _ := seedInternalChatDeps(t, db)
|
||||
|
||||
keys, err := server.resolveUserProviderAPIKeys(ctx, user.ID)
|
||||
require.NoError(t, err)
|
||||
@@ -278,18 +255,14 @@ func TestResolveUserProviderAPIKeys_PreservesAnthropicKeyFromDBProvider(t *testi
|
||||
}
|
||||
|
||||
func insertInternalChatModelConfig(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
userID uuid.UUID,
|
||||
model string,
|
||||
enabled bool,
|
||||
) database.ChatModelConfig {
|
||||
return insertInternalChatModelConfigForProvider(
|
||||
ctx,
|
||||
t,
|
||||
db,
|
||||
userID,
|
||||
"openai",
|
||||
model,
|
||||
enabled,
|
||||
@@ -297,7 +270,6 @@ func insertInternalChatModelConfig(
|
||||
}
|
||||
|
||||
func insertInternalChatProvider(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
userID uuid.UUID,
|
||||
@@ -309,36 +281,31 @@ func insertInternalChatProvider(
|
||||
) database.ChatProvider {
|
||||
t.Helper()
|
||||
|
||||
providerConfig, err := db.InsertChatProvider(ctx, database.InsertChatProviderParams{
|
||||
Provider: provider,
|
||||
DisplayName: provider,
|
||||
APIKey: apiKey,
|
||||
CreatedBy: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
Enabled: true,
|
||||
CentralApiKeyEnabled: centralAPIKeyEnabled,
|
||||
AllowUserApiKey: allowUserAPIKey,
|
||||
AllowCentralApiKeyFallback: allowCentralAPIKeyFallback,
|
||||
providerConfig := dbgen.ChatProvider(t, db, database.ChatProvider{
|
||||
Provider: provider,
|
||||
DisplayName: provider,
|
||||
CreatedBy: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
}, func(p *database.InsertChatProviderParams) {
|
||||
p.APIKey = apiKey
|
||||
p.CentralApiKeyEnabled = centralAPIKeyEnabled
|
||||
p.AllowUserApiKey = allowUserAPIKey
|
||||
p.AllowCentralApiKeyFallback = allowCentralAPIKeyFallback
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return providerConfig
|
||||
}
|
||||
|
||||
func insertInternalChatModelConfigForProvider(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
userID uuid.UUID,
|
||||
provider string,
|
||||
model string,
|
||||
enabled bool,
|
||||
) database.ChatModelConfig {
|
||||
t.Helper()
|
||||
return insertInternalChatModelConfigWithOptions(
|
||||
ctx,
|
||||
t,
|
||||
db,
|
||||
userID,
|
||||
provider,
|
||||
model,
|
||||
enabled,
|
||||
@@ -347,10 +314,8 @@ func insertInternalChatModelConfigForProvider(
|
||||
}
|
||||
|
||||
func insertInternalChatModelConfigWithOptions(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
userID uuid.UUID,
|
||||
provider string,
|
||||
model string,
|
||||
enabled bool,
|
||||
@@ -358,25 +323,19 @@ func insertInternalChatModelConfigWithOptions(
|
||||
) database.ChatModelConfig {
|
||||
t.Helper()
|
||||
|
||||
modelConfig, err := db.InsertChatModelConfig(ctx, database.InsertChatModelConfigParams{
|
||||
Provider: provider,
|
||||
Model: model,
|
||||
DisplayName: model,
|
||||
CreatedBy: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
Enabled: enabled,
|
||||
IsDefault: false,
|
||||
ContextLimit: 128000,
|
||||
CompressionThreshold: 70,
|
||||
Options: options,
|
||||
modelConfig := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
|
||||
Provider: provider,
|
||||
Model: model,
|
||||
DisplayName: model,
|
||||
Options: options,
|
||||
}, func(p *database.InsertChatModelConfigParams) {
|
||||
p.Enabled = enabled
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return modelConfig
|
||||
}
|
||||
|
||||
func insertInternalMCPServerConfig(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
userID uuid.UUID,
|
||||
@@ -385,23 +344,14 @@ func insertInternalMCPServerConfig(
|
||||
) database.MCPServerConfig {
|
||||
t.Helper()
|
||||
|
||||
cfg, err := db.InsertMCPServerConfig(ctx, database.InsertMCPServerConfigParams{
|
||||
return dbgen.MCPServerConfig(t, db, database.MCPServerConfig{
|
||||
DisplayName: slug,
|
||||
Slug: slug,
|
||||
Url: "https://" + slug + ".example.com",
|
||||
Transport: "streamable_http",
|
||||
AuthType: "none",
|
||||
Availability: "default_off",
|
||||
Enabled: true,
|
||||
AllowInPlanMode: allowInPlanMode,
|
||||
ToolAllowList: []string{},
|
||||
ToolDenyList: []string{},
|
||||
CreatedBy: userID,
|
||||
UpdatedBy: userID,
|
||||
CreatedBy: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: userID, Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func seedWorkspaceBinding(
|
||||
@@ -466,7 +416,7 @@ func TestCreateChildSubagentChatInheritsWorkspaceBinding(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, build, agent := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
parent, err := server.CreateChat(ctx, CreateOptions{
|
||||
@@ -634,7 +584,7 @@ func TestCreateChildSubagentChatCopiesPlanMode(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
planMode := database.NullChatPlanMode{
|
||||
ChatPlanMode: database.ChatPlanModePlan,
|
||||
Valid: true,
|
||||
@@ -671,7 +621,7 @@ func TestSpawnAgent_GeneralInheritsParentModelWhenOmitted(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
parentChat := createInternalParentChat(
|
||||
ctx, t, server, db, org.ID, user.ID, model.ID, "parent-inherited-model",
|
||||
)
|
||||
@@ -697,9 +647,9 @@ func TestSpawnAgent_GeneralUsesConfiguredModelOverride(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
overrideModel := insertInternalChatModelConfig(
|
||||
ctx, t, db, user.ID, "general-override-"+uuid.NewString(), true,
|
||||
t, db, "general-override-"+uuid.NewString(), true,
|
||||
)
|
||||
require.NoError(t, db.UpsertChatGeneralModelOverride(ctx, overrideModel.ID.String()))
|
||||
parentChat := createInternalParentChat(
|
||||
@@ -727,9 +677,8 @@ func TestSpawnAgent_GeneralOverrideLogsAndFallsBackWhenCredentialsUnavailable(t
|
||||
server := newInternalTestServerWithLogger(t, db, ps, chatprovider.ProviderAPIKeys{}, logger)
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
insertInternalChatProvider(
|
||||
ctx,
|
||||
t,
|
||||
db,
|
||||
user.ID,
|
||||
@@ -739,11 +688,10 @@ func TestSpawnAgent_GeneralOverrideLogsAndFallsBackWhenCredentialsUnavailable(t
|
||||
true,
|
||||
false,
|
||||
)
|
||||
|
||||
overrideModel := insertInternalChatModelConfigForProvider(
|
||||
ctx,
|
||||
t,
|
||||
db,
|
||||
user.ID,
|
||||
"openai-compat",
|
||||
"gpt-4o-mini",
|
||||
true,
|
||||
@@ -797,23 +745,22 @@ func TestSpawnAgent_GeneralOverrideLogsAndFallsBackWhenProviderDisabled(t *testi
|
||||
)
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
_, err := db.InsertChatProvider(ctx, database.InsertChatProviderParams{
|
||||
Provider: "openai-compat",
|
||||
DisplayName: "openai-compat",
|
||||
APIKey: "",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
Enabled: false,
|
||||
CentralApiKeyEnabled: false,
|
||||
AllowUserApiKey: true,
|
||||
AllowCentralApiKeyFallback: false,
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
dbgen.ChatProvider(t, db, database.ChatProvider{
|
||||
Provider: "openai-compat",
|
||||
DisplayName: "openai-compat",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
}, func(p *database.InsertChatProviderParams) {
|
||||
p.APIKey = ""
|
||||
p.Enabled = false
|
||||
p.CentralApiKeyEnabled = false
|
||||
p.AllowUserApiKey = true
|
||||
p.AllowCentralApiKeyFallback = false
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
overrideModel := insertInternalChatModelConfigForProvider(
|
||||
ctx,
|
||||
t,
|
||||
db,
|
||||
user.ID,
|
||||
"openai-compat",
|
||||
"gpt-4o-mini",
|
||||
true,
|
||||
@@ -904,9 +851,9 @@ func TestCreateChildSubagentChat_OverrideWorksWhenParentHasNoModel(t *testing.T)
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
overrideModel := insertInternalChatModelConfig(
|
||||
ctx, t, db, user.ID, "override-no-parent-model-"+uuid.NewString(), true,
|
||||
t, db, "override-no-parent-model-"+uuid.NewString(), true,
|
||||
)
|
||||
parentChat := createInternalParentChat(
|
||||
ctx, t, server, db, org.ID, user.ID, model.ID, "parent-no-model",
|
||||
@@ -936,9 +883,9 @@ func TestSpawnAgent_ExploreUsesConfiguredModelOverride(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
overrideModel := insertInternalChatModelConfig(
|
||||
ctx, t, db, user.ID, "explore-override-"+uuid.NewString(), true,
|
||||
t, db, "explore-override-"+uuid.NewString(), true,
|
||||
)
|
||||
require.NoError(t, db.UpsertChatExploreModelOverride(ctx, overrideModel.ID.String()))
|
||||
parentChat := createInternalParentChat(
|
||||
@@ -974,9 +921,9 @@ func TestSpawnAgent_ExploreFallsBackToCurrentTurnModel(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, parentModel := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, parentModel := seedInternalChatDeps(t, db)
|
||||
currentTurnModel := insertInternalChatModelConfig(
|
||||
ctx, t, db, user.ID, "explore-current-turn-"+uuid.NewString(), true,
|
||||
t, db, "explore-current-turn-"+uuid.NewString(), true,
|
||||
)
|
||||
parentChat := createInternalParentChat(
|
||||
ctx, t, server, db, org.ID, user.ID, parentModel.ID, "parent-explore-fallback",
|
||||
@@ -1006,7 +953,7 @@ func TestCreateChat_ExploreRootStartsWithoutMCPSnapshot(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
|
||||
root, err := server.CreateChat(ctx, CreateOptions{
|
||||
OrganizationID: org.ID,
|
||||
@@ -1033,12 +980,12 @@ func TestResolveExploreToolSnapshot(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
approvedMCP := insertInternalMCPServerConfig(
|
||||
ctx, t, db, user.ID, "approved-"+uuid.NewString(), true,
|
||||
t, db, user.ID, "approved-"+uuid.NewString(), true,
|
||||
)
|
||||
blockedMCP := insertInternalMCPServerConfig(
|
||||
ctx, t, db, user.ID, "blocked-"+uuid.NewString(), false,
|
||||
t, db, user.ID, "blocked-"+uuid.NewString(), false,
|
||||
)
|
||||
|
||||
askParentRef, err := server.CreateChat(ctx, CreateOptions{
|
||||
@@ -1130,12 +1077,12 @@ func TestCreateChildSubagentChatWithOptions_ExplorePersistsMCPSnapshot(t *testin
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
parentChat := createInternalParentChat(
|
||||
ctx, t, server, db, org.ID, user.ID, model.ID, "parent-explore-snapshot",
|
||||
)
|
||||
mcpCfg := insertInternalMCPServerConfig(
|
||||
ctx, t, db, user.ID, "snapshot-"+uuid.NewString(), false,
|
||||
t, db, user.ID, "snapshot-"+uuid.NewString(), false,
|
||||
)
|
||||
|
||||
child, err := server.createChildSubagentChatWithOptions(
|
||||
@@ -1165,12 +1112,12 @@ func TestSpawnAgent_ExploreSnapshotsTurnStateParentState(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
turnStartConfig := insertInternalMCPServerConfig(
|
||||
ctx, t, db, user.ID, "turn-start-"+uuid.NewString(), false,
|
||||
t, db, user.ID, "turn-start-"+uuid.NewString(), false,
|
||||
)
|
||||
mutatedConfig := insertInternalMCPServerConfig(
|
||||
ctx, t, db, user.ID, "mutated-"+uuid.NewString(), true,
|
||||
t, db, user.ID, "mutated-"+uuid.NewString(), true,
|
||||
)
|
||||
|
||||
parent, err := server.CreateChat(ctx, CreateOptions{
|
||||
@@ -1246,9 +1193,9 @@ func TestSpawnAgent_ExploreFallsBackOnInvalidUUID(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, parentModel := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, parentModel := seedInternalChatDeps(t, db)
|
||||
currentTurnModel := insertInternalChatModelConfig(
|
||||
ctx, t, db, user.ID, "explore-invalid-override-"+uuid.NewString(), true,
|
||||
t, db, "explore-invalid-override-"+uuid.NewString(), true,
|
||||
)
|
||||
require.NoError(t, db.UpsertChatExploreModelOverride(ctx, "not-a-uuid"))
|
||||
parentChat := createInternalParentChat(
|
||||
@@ -1278,12 +1225,12 @@ func TestSpawnAgent_ExploreFallsBackWhenOverrideIsUnavailable(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, parentModel := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, parentModel := seedInternalChatDeps(t, db)
|
||||
currentTurnModel := insertInternalChatModelConfig(
|
||||
ctx, t, db, user.ID, "explore-fallback-current-"+uuid.NewString(), true,
|
||||
t, db, "explore-fallback-current-"+uuid.NewString(), true,
|
||||
)
|
||||
disabledModel := insertInternalChatModelConfig(
|
||||
ctx, t, db, user.ID, "explore-disabled-"+uuid.NewString(), false,
|
||||
t, db, "explore-disabled-"+uuid.NewString(), false,
|
||||
)
|
||||
require.NoError(t, db.UpsertChatExploreModelOverride(ctx, disabledModel.ID.String()))
|
||||
parentChat := createInternalParentChat(
|
||||
@@ -1313,35 +1260,25 @@ func TestSpawnAgent_ExploreFallsBackWhenOverrideCredentialsAreUnavailable(t *tes
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, parentModel := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, parentModel := seedInternalChatDeps(t, db)
|
||||
currentTurnModel := insertInternalChatModelConfig(
|
||||
ctx, t, db, user.ID, "explore-missing-user-key-current-"+uuid.NewString(), true,
|
||||
t, db, "explore-missing-user-key-current-"+uuid.NewString(), true,
|
||||
)
|
||||
_, err := db.InsertChatProvider(ctx, database.InsertChatProviderParams{
|
||||
Provider: "openai-compat",
|
||||
DisplayName: "OpenAI Compat",
|
||||
APIKey: "",
|
||||
BaseUrl: "",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
Enabled: true,
|
||||
CentralApiKeyEnabled: false,
|
||||
AllowUserApiKey: true,
|
||||
AllowCentralApiKeyFallback: false,
|
||||
dbgen.ChatProvider(t, db, database.ChatProvider{
|
||||
Provider: "openai-compat",
|
||||
DisplayName: "OpenAI Compat",
|
||||
}, func(p *database.InsertChatProviderParams) {
|
||||
p.APIKey = ""
|
||||
p.CentralApiKeyEnabled = false
|
||||
p.AllowUserApiKey = true
|
||||
p.AllowCentralApiKeyFallback = false
|
||||
})
|
||||
require.NoError(t, err)
|
||||
overrideModel, err := db.InsertChatModelConfig(ctx, database.InsertChatModelConfigParams{
|
||||
Provider: "openai-compat",
|
||||
Model: "gpt-4o-mini",
|
||||
DisplayName: "Explore Override Missing User Key",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
Enabled: true,
|
||||
IsDefault: false,
|
||||
ContextLimit: 128000,
|
||||
CompressionThreshold: 70,
|
||||
Options: json.RawMessage(`{}`),
|
||||
|
||||
overrideModel := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
|
||||
Provider: "openai-compat",
|
||||
Model: "gpt-4o-mini",
|
||||
DisplayName: "Explore Override Missing User Key",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.UpsertChatExploreModelOverride(ctx, overrideModel.ID.String()))
|
||||
parentChat := createInternalParentChat(
|
||||
ctx, t, server, db, org.ID, user.ID, parentModel.ID, "parent-explore-missing-user-key",
|
||||
@@ -1373,7 +1310,7 @@ func TestSpawnAgent_DescriptionListsAllAvailableTypes(t *testing.T) {
|
||||
})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
parentChat := createInternalParentChat(
|
||||
ctx, t, server, db, org.ID, user.ID, model.ID, "parent-description-all",
|
||||
)
|
||||
@@ -1395,7 +1332,7 @@ func TestSpawnAgent_DescriptionOmitsComputerUseWhenUnavailable(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
parentChat := createInternalParentChat(
|
||||
ctx, t, server, db, org.ID, user.ID, model.ID, "parent-description-unavailable",
|
||||
)
|
||||
@@ -1419,7 +1356,7 @@ func TestSpawnAgent_PlanModeDescriptionOmitsComputerUse(t *testing.T) {
|
||||
})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
parent, err := server.CreateChat(ctx, CreateOptions{
|
||||
OrganizationID: org.ID,
|
||||
OwnerID: user.ID,
|
||||
@@ -1455,7 +1392,7 @@ func TestSpawnAgent_PlanModeRejectsComputerUse(t *testing.T) {
|
||||
})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
parent, err := server.CreateChat(ctx, CreateOptions{
|
||||
OrganizationID: org.ID,
|
||||
OwnerID: user.ID,
|
||||
@@ -1499,7 +1436,7 @@ func TestSpawnAgent_InvalidTypeAndUnavailableTypeAreDistinct(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
parentChat := createInternalParentChat(
|
||||
ctx, t, server, db, org.ID, user.ID, model.ID, "parent-invalid-type",
|
||||
)
|
||||
@@ -1539,7 +1476,7 @@ func TestSpawnAgent_BlankTypeReturnsValidOptions(t *testing.T) {
|
||||
})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
parentChat := createInternalParentChat(
|
||||
ctx, t, server, db, org.ID, user.ID, model.ID, "parent-blank-type",
|
||||
)
|
||||
@@ -1580,7 +1517,7 @@ func TestSpawnAgent_NotAvailableForChildChats(t *testing.T) {
|
||||
})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
_, child := createParentChildChats(ctx, t, server, user, org, model)
|
||||
|
||||
childChat, err := db.GetChatByID(ctx, child.ID)
|
||||
@@ -1608,7 +1545,7 @@ func TestSpawnAgent_NotAvailableForExploreChats(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
exploreChat, err := server.CreateChat(ctx, CreateOptions{
|
||||
OrganizationID: org.ID,
|
||||
OwnerID: user.ID,
|
||||
@@ -1662,9 +1599,9 @@ func TestSubagentLifecycleToolsIncludePersistedSubagentTypeAcrossVariants(t *tes
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
if tt.variant == subagentTypeComputerUse {
|
||||
insertEnabledAnthropicProvider(ctx, t, db, user.ID)
|
||||
insertEnabledAnthropicProvider(t, db, user.ID)
|
||||
}
|
||||
parentChat := createInternalParentChat(
|
||||
ctx,
|
||||
@@ -1687,7 +1624,7 @@ func TestSubagentLifecycleToolsIncludePersistedSubagentTypeAcrossVariants(t *tes
|
||||
require.NoError(t, err)
|
||||
|
||||
setChatStatus(ctx, t, db, childID, database.ChatStatusWaiting, "")
|
||||
insertAssistantMessage(ctx, t, db, childID, model.ID, "task complete")
|
||||
insertAssistantMessage(t, db, childID, model.ID, "task complete")
|
||||
waitResult := requireToolResponseMap(t, runSubagentTool(
|
||||
ctx,
|
||||
t,
|
||||
@@ -1732,7 +1669,7 @@ func TestSubagentLifecycleToolErrorsIncludePersistedSubagentType(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
_, child := createParentChildChats(ctx, t, server, user, org, model)
|
||||
unrelated, err := server.CreateChat(ctx, CreateOptions{
|
||||
OrganizationID: org.ID,
|
||||
@@ -1798,8 +1735,8 @@ func TestSpawnAgent_ComputerUseUsesComputerUseModelNotParent(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
insertEnabledAnthropicProvider(ctx, t, db, user.ID)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
insertEnabledAnthropicProvider(t, db, user.ID)
|
||||
workspace, build, agent := seedWorkspaceBinding(t, db, user.ID)
|
||||
|
||||
require.Equal(t, "openai", model.Provider, "seed helper must create an OpenAI model")
|
||||
@@ -1855,23 +1792,16 @@ func TestSpawnAgent_ComputerUseInheritsMCPServerIDs(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
insertEnabledAnthropicProvider(ctx, t, db, user.ID)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
insertEnabledAnthropicProvider(t, db, user.ID)
|
||||
|
||||
mcpCfg, err := db.InsertMCPServerConfig(ctx, database.InsertMCPServerConfigParams{
|
||||
DisplayName: "MCP Test",
|
||||
Slug: "mcp-test",
|
||||
Url: "https://mcp.example.com",
|
||||
Transport: "streamable_http",
|
||||
AuthType: "none",
|
||||
Availability: "default_off",
|
||||
Enabled: true,
|
||||
ToolAllowList: []string{},
|
||||
ToolDenyList: []string{},
|
||||
CreatedBy: user.ID,
|
||||
UpdatedBy: user.ID,
|
||||
mcpCfg := dbgen.MCPServerConfig(t, db, database.MCPServerConfig{
|
||||
DisplayName: "MCP Test",
|
||||
Slug: "mcp-test",
|
||||
Url: "https://mcp.example.com",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
parentMCPIDs := []uuid.UUID{mcpCfg.ID}
|
||||
|
||||
@@ -1912,39 +1842,25 @@ func TestCreateChildSubagentChat_InheritsMCPServerIDs(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
|
||||
// Insert two MCP server configs so we can verify both are
|
||||
// inherited by the child chat.
|
||||
mcpA, err := db.InsertMCPServerConfig(ctx, database.InsertMCPServerConfigParams{
|
||||
DisplayName: "MCP A",
|
||||
Slug: "mcp-a",
|
||||
Url: "https://mcp-a.example.com",
|
||||
Transport: "streamable_http",
|
||||
AuthType: "none",
|
||||
Availability: "default_off",
|
||||
Enabled: true,
|
||||
ToolAllowList: []string{},
|
||||
ToolDenyList: []string{},
|
||||
CreatedBy: user.ID,
|
||||
UpdatedBy: user.ID,
|
||||
mcpA := dbgen.MCPServerConfig(t, db, database.MCPServerConfig{
|
||||
DisplayName: "MCP A",
|
||||
Slug: "mcp-a",
|
||||
Url: "https://mcp-a.example.com",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
mcpB, err := db.InsertMCPServerConfig(ctx, database.InsertMCPServerConfigParams{
|
||||
DisplayName: "MCP B",
|
||||
Slug: "mcp-b",
|
||||
Url: "https://mcp-b.example.com",
|
||||
Transport: "streamable_http",
|
||||
AuthType: "none",
|
||||
Availability: "default_off",
|
||||
Enabled: true,
|
||||
ToolAllowList: []string{},
|
||||
ToolDenyList: []string{},
|
||||
CreatedBy: user.ID,
|
||||
UpdatedBy: user.ID,
|
||||
mcpB := dbgen.MCPServerConfig(t, db, database.MCPServerConfig{
|
||||
DisplayName: "MCP B",
|
||||
Slug: "mcp-b",
|
||||
Url: "https://mcp-b.example.com",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
parentMCPIDs := []uuid.UUID{mcpA.ID, mcpB.ID}
|
||||
|
||||
@@ -1988,7 +1904,7 @@ func TestCreateChildSubagentChat_NoMCPServersStaysEmpty(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
|
||||
// Create a parent chat without any MCP servers.
|
||||
parent, err := server.CreateChat(ctx, CreateOptions{
|
||||
@@ -2025,7 +1941,7 @@ func TestIsSubagentDescendant(t *testing.T) {
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
|
||||
// Build a chain: root -> child -> grandchild.
|
||||
root, err := server.CreateChat(ctx, CreateOptions{
|
||||
@@ -2225,7 +2141,6 @@ func setChatStatus(
|
||||
// insertAssistantMessage inserts an assistant message with v1 content
|
||||
// into a chat.
|
||||
func insertAssistantMessage(
|
||||
ctx context.Context,
|
||||
t *testing.T,
|
||||
db database.Store,
|
||||
chatID uuid.UUID,
|
||||
@@ -2238,26 +2153,14 @@ func insertAssistantMessage(
|
||||
data, err := json.Marshal(parts)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = db.InsertChatMessages(ctx, database.InsertChatMessagesParams{
|
||||
ChatID: chatID,
|
||||
CreatedBy: []uuid.UUID{uuid.Nil},
|
||||
ModelConfigID: []uuid.UUID{modelID},
|
||||
Role: []database.ChatMessageRole{database.ChatMessageRoleAssistant},
|
||||
Content: []string{string(data)},
|
||||
ContentVersion: []int16{chatprompt.ContentVersionV1},
|
||||
Visibility: []database.ChatMessageVisibility{database.ChatMessageVisibilityBoth},
|
||||
InputTokens: []int64{0},
|
||||
OutputTokens: []int64{0},
|
||||
TotalTokens: []int64{0},
|
||||
ReasoningTokens: []int64{0},
|
||||
CacheCreationTokens: []int64{0},
|
||||
CacheReadTokens: []int64{0},
|
||||
ContextLimit: []int64{0},
|
||||
Compressed: []bool{false},
|
||||
TotalCostMicros: []int64{0},
|
||||
RuntimeMs: []int64{0},
|
||||
_ = dbgen.ChatMessage(t, db, database.ChatMessage{
|
||||
ChatID: chatID,
|
||||
CreatedBy: uuid.NullUUID{},
|
||||
ModelConfigID: uuid.NullUUID{UUID: modelID, Valid: true},
|
||||
Role: database.ChatMessageRoleAssistant,
|
||||
Content: pqtype.NullRawMessage{RawMessage: data, Valid: true},
|
||||
ContentVersion: chatprompt.ContentVersionV1,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func insertLinkedChatFile(
|
||||
@@ -2298,12 +2201,12 @@ func TestWaitAgentDoesNotRelayComputerUseSubagentAttachments(t *testing.T) {
|
||||
|
||||
db, ps := dbtestutil.NewDB(t)
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, agent := seedWorkspaceBinding(t, db, user.ID)
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
parent, child := createComputerUseParentChild(
|
||||
ctx, t, server, user, org, model, workspace, agent,
|
||||
t, server, user, org, model, workspace, agent,
|
||||
"parent-relay", "child-relay",
|
||||
)
|
||||
|
||||
@@ -2318,7 +2221,7 @@ func TestWaitAgentDoesNotRelayComputerUseSubagentAttachments(t *testing.T) {
|
||||
"image/png",
|
||||
[]byte("fake-png"),
|
||||
)
|
||||
insertAssistantMessage(ctx, t, db, child.ID, model.ID, "Shared the screenshot.")
|
||||
insertAssistantMessage(t, db, child.ID, model.ID, "Shared the screenshot.")
|
||||
setChatStatus(ctx, t, db, child.ID, database.ChatStatusWaiting, "")
|
||||
|
||||
resp, err := invokeWaitAgentTool(ctx, t, server, db, parent.ID, child.ID, 5)
|
||||
@@ -2366,7 +2269,7 @@ func TestWaitAgentDoesNotRelayRegularSubagentAttachments(t *testing.T) {
|
||||
|
||||
db, ps := dbtestutil.NewDB(t)
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
workspace, _, _ := seedWorkspaceBinding(t, db, user.ID)
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
|
||||
@@ -2384,7 +2287,7 @@ func TestWaitAgentDoesNotRelayRegularSubagentAttachments(t *testing.T) {
|
||||
"text/plain",
|
||||
[]byte("release notes"),
|
||||
)
|
||||
insertAssistantMessage(ctx, t, db, child.ID, model.ID, "Shared the release notes.")
|
||||
insertAssistantMessage(t, db, child.ID, model.ID, "Shared the release notes.")
|
||||
setChatStatus(ctx, t, db, child.ID, database.ChatStatusWaiting, "")
|
||||
|
||||
resp, err := invokeWaitAgentTool(ctx, t, server, db, parent.ID, child.ID, 5)
|
||||
@@ -2422,8 +2325,7 @@ func TestAwaitSubagentCompletion(t *testing.T) {
|
||||
// also use the mock clock.
|
||||
db, ps := dbtestutil.NewDB(t)
|
||||
server := newInternalTestServer(t, db, ps, chatprovider.ProviderAPIKeys{})
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
|
||||
t.Run("NotDescendant", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
@@ -2453,7 +2355,7 @@ func TestAwaitSubagentCompletion(t *testing.T) {
|
||||
parent, child := createParentChildChats(ctx, t, server, user, org, model)
|
||||
|
||||
setChatStatus(ctx, t, db, child.ID, database.ChatStatusWaiting, "")
|
||||
insertAssistantMessage(ctx, t, db, child.ID, model.ID, "task complete")
|
||||
insertAssistantMessage(t, db, child.ID, model.ID, "task complete")
|
||||
|
||||
gotChat, report, err := server.awaitSubagentCompletion(
|
||||
ctx, parent.ID, child.ID, time.Second,
|
||||
@@ -2471,7 +2373,7 @@ func TestAwaitSubagentCompletion(t *testing.T) {
|
||||
parent, child := createParentChildChats(ctx, t, server, user, org, model)
|
||||
|
||||
setChatStatus(ctx, t, db, child.ID, database.ChatStatusError, "something broke")
|
||||
insertAssistantMessage(ctx, t, db, child.ID, model.ID, "partial work done")
|
||||
insertAssistantMessage(t, db, child.ID, model.ID, "partial work done")
|
||||
|
||||
_, _, err := server.awaitSubagentCompletion(
|
||||
ctx, parent.ID, child.ID, time.Second,
|
||||
@@ -2504,7 +2406,7 @@ func TestAwaitSubagentCompletion(t *testing.T) {
|
||||
mClock := quartz.NewMock(t)
|
||||
server := newInternalTestServerWithClock(t, db, nil, chatprovider.ProviderAPIKeys{}, mClock)
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
|
||||
parent, child := createParentChildChats(ctx, t, server, user, org, model)
|
||||
|
||||
@@ -2534,7 +2436,7 @@ func TestAwaitSubagentCompletion(t *testing.T) {
|
||||
// Now set the state and advance the clock to the next
|
||||
// tick so the poll detects the transition.
|
||||
setChatStatus(ctx, t, db, child.ID, database.ChatStatusWaiting, "")
|
||||
insertAssistantMessage(ctx, t, db, child.ID, model.ID, "poll result")
|
||||
insertAssistantMessage(t, db, child.ID, model.ID, "poll result")
|
||||
mClock.Advance(subagentAwaitPollInterval).MustWait(ctx)
|
||||
|
||||
result := testutil.RequireReceive(ctx, t, resultCh)
|
||||
@@ -2550,7 +2452,7 @@ func TestAwaitSubagentCompletion(t *testing.T) {
|
||||
mClock := quartz.NewMock(t)
|
||||
server := newInternalTestServerWithClock(t, db, ps, chatprovider.ProviderAPIKeys{}, mClock)
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
|
||||
parent, child := createParentChildChats(ctx, t, server, user, org, model)
|
||||
|
||||
@@ -2612,7 +2514,7 @@ func TestAwaitSubagentCompletion(t *testing.T) {
|
||||
// see done=true (Waiting) with an empty report. By
|
||||
// inserting the message first, the report is guaranteed
|
||||
// to be committed before the status makes it visible.
|
||||
insertAssistantMessage(ctx, t, db, child.ID, model.ID, "pubsub result")
|
||||
insertAssistantMessage(t, db, child.ID, model.ID, "pubsub result")
|
||||
setChatStatus(ctx, t, db, child.ID, database.ChatStatusWaiting, "")
|
||||
require.EventuallyWithT(t, func(c *assert.CollectT) {
|
||||
chat, report, done, err := server.checkSubagentCompletion(ctx, child.ID)
|
||||
@@ -2661,7 +2563,7 @@ func TestAwaitSubagentCompletion(t *testing.T) {
|
||||
mClock := quartz.NewMock(t)
|
||||
server := newInternalTestServerWithClock(t, db, ps, chatprovider.ProviderAPIKeys{}, mClock)
|
||||
ctx := chatdTestContext(t)
|
||||
user, org, model := seedInternalChatDeps(ctx, t, db)
|
||||
user, org, model := seedInternalChatDeps(t, db)
|
||||
|
||||
parent, child := createParentChildChats(ctx, t, server, user, org, model)
|
||||
|
||||
@@ -2733,7 +2635,7 @@ func TestAwaitSubagentCompletion(t *testing.T) {
|
||||
|
||||
// Pre-complete the child so it returns immediately.
|
||||
setChatStatus(ctx, t, db, child.ID, database.ChatStatusWaiting, "")
|
||||
insertAssistantMessage(ctx, t, db, child.ID, model.ID, "zero timeout ok")
|
||||
insertAssistantMessage(t, db, child.ID, model.ID, "zero timeout ok")
|
||||
|
||||
gotChat, report, err := server.awaitSubagentCompletion(
|
||||
ctx, parent.ID, child.ID, 0,
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestSpawnComputerUseAgent_CreatesChildWithChatMode(t *testing.T) {
|
||||
db, ps := dbtestutil.NewDB(t)
|
||||
server := newTestServer(t, db, ps, uuid.New())
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
user, org, model := seedChatDependencies(ctx, t, db)
|
||||
user, org, model := seedChatDependencies(t, db)
|
||||
|
||||
// Create a parent chat.
|
||||
parent, err := server.CreateChat(ctx, chatd.CreateOptions{
|
||||
@@ -77,7 +77,7 @@ func TestSpawnComputerUseAgent_SystemPromptFormat(t *testing.T) {
|
||||
db, ps := dbtestutil.NewDB(t)
|
||||
server := newTestServer(t, db, ps, uuid.New())
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
user, org, model := seedChatDependencies(ctx, t, db)
|
||||
user, org, model := seedChatDependencies(t, db)
|
||||
|
||||
parent, err := server.CreateChat(ctx, chatd.CreateOptions{
|
||||
OrganizationID: org.ID,
|
||||
@@ -136,7 +136,7 @@ func TestSpawnComputerUseAgent_ChildIsListedUnderParent(t *testing.T) {
|
||||
db, ps := dbtestutil.NewDB(t)
|
||||
server := newTestServer(t, db, ps, uuid.New())
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
user, org, model := seedChatDependencies(ctx, t, db)
|
||||
user, org, model := seedChatDependencies(t, db)
|
||||
|
||||
parent, err := server.CreateChat(ctx, chatd.CreateOptions{
|
||||
OrganizationID: org.ID,
|
||||
@@ -181,7 +181,7 @@ func TestSpawnComputerUseAgent_RootChatIDPropagation(t *testing.T) {
|
||||
db, ps := dbtestutil.NewDB(t)
|
||||
server := newTestServer(t, db, ps, uuid.New())
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
user, org, model := seedChatDependencies(ctx, t, db)
|
||||
user, org, model := seedChatDependencies(t, db)
|
||||
|
||||
// Create a root parent chat (no parent of its own).
|
||||
parent, err := server.CreateChat(ctx, chatd.CreateOptions{
|
||||
|
||||
@@ -3,7 +3,6 @@ package gitsync_test
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -946,37 +945,22 @@ func TestWorker(t *testing.T) {
|
||||
org := dbgen.Organization(t, db, database.Organization{})
|
||||
|
||||
// 3. Set up FK chain: chat_providers -> chat_model_configs -> chats.
|
||||
_, err := db.InsertChatProvider(ctx, database.InsertChatProviderParams{
|
||||
Provider: "openai",
|
||||
DisplayName: "OpenAI",
|
||||
Enabled: true,
|
||||
CentralApiKeyEnabled: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_ = dbgen.ChatProvider(t, db, database.ChatProvider{})
|
||||
|
||||
modelCfg, err := db.InsertChatModelConfig(ctx, database.InsertChatModelConfigParams{
|
||||
Provider: "openai",
|
||||
Model: "test-model",
|
||||
DisplayName: "Test Model",
|
||||
Enabled: true,
|
||||
ContextLimit: 100000,
|
||||
CompressionThreshold: 70,
|
||||
Options: json.RawMessage("{}"),
|
||||
modelCfg := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{
|
||||
Model: "test-model",
|
||||
ContextLimit: 100000,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
chat, err := db.InsertChat(ctx, database.InsertChatParams{
|
||||
chat := dbgen.Chat(t, db, database.Chat{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "integration-test",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// 4. Seed a stale diff status row so the worker picks it up.
|
||||
_, err = db.UpsertChatDiffStatusReference(ctx, database.UpsertChatDiffStatusReferenceParams{
|
||||
_, err := db.UpsertChatDiffStatusReference(ctx, database.UpsertChatDiffStatusReferenceParams{
|
||||
ChatID: chat.ID,
|
||||
GitBranch: "feature",
|
||||
GitRemoteOrigin: "https://github.com/o/r",
|
||||
|
||||
Reference in New Issue
Block a user