mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(coderd): wire debug logging into chat lifecycle (#23917)
This commit is contained in:
@@ -11524,8 +11524,9 @@ func TestDeleteChatDebugDataAfterMessageIDIncludesTriggeredRuns(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
deletedRows, err := store.DeleteChatDebugDataAfterMessageID(ctx, database.DeleteChatDebugDataAfterMessageIDParams{
|
||||
ChatID: chat.ID,
|
||||
MessageID: cutoff,
|
||||
ChatID: chat.ID,
|
||||
MessageID: cutoff,
|
||||
StartedBefore: time.Now().Add(time.Minute),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 3, deletedRows)
|
||||
@@ -12406,8 +12407,9 @@ func TestDeleteChatDebugDataAfterMessageIDNullMessagesSurvive(t *testing.T) {
|
||||
// Delete with an arbitrary cutoff. The run and its step should
|
||||
// survive because NULL > cutoff evaluates to NULL, not TRUE.
|
||||
deletedRows, err := store.DeleteChatDebugDataAfterMessageID(ctx, database.DeleteChatDebugDataAfterMessageIDParams{
|
||||
ChatID: chat.ID,
|
||||
MessageID: 1,
|
||||
ChatID: chat.ID,
|
||||
MessageID: 1,
|
||||
StartedBefore: time.Now().Add(time.Minute),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, deletedRows, "rows with NULL message IDs must not be deleted")
|
||||
@@ -12424,6 +12426,215 @@ func TestDeleteChatDebugDataAfterMessageIDNullMessagesSurvive(t *testing.T) {
|
||||
require.Equal(t, nullMsgStep.ID, remainingSteps[0].ID)
|
||||
}
|
||||
|
||||
// TestDeleteChatDebugDataAfterMessageIDStartedBeforeFiltersNewerRuns
|
||||
// verifies the started_before bound on DeleteChatDebugDataAfterMessageID.
|
||||
// The bound exists so that retried cleanup (e.g. after edit or archive)
|
||||
// cannot delete runs started by a replacement turn that races ahead of
|
||||
// the retry window. Without this filter, a stale cleanup would wipe
|
||||
// fresh debug rows.
|
||||
func TestDeleteChatDebugDataAfterMessageIDStartedBeforeFiltersNewerRuns(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
store, _ := dbtestutil.NewDB(t)
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
|
||||
org := dbgen.Organization(t, store, database.Organization{})
|
||||
user := dbgen.User(t, store, database.User{})
|
||||
|
||||
providerName := "openai"
|
||||
modelName := "debug-model-started-before-" + uuid.NewString()
|
||||
|
||||
_, err := store.InsertChatProvider(ctx, database.InsertChatProviderParams{
|
||||
Provider: providerName,
|
||||
DisplayName: "Debug Provider",
|
||||
APIKey: "test-key",
|
||||
Enabled: true,
|
||||
CentralApiKeyEnabled: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
modelCfg, err := store.InsertChatModelConfig(ctx, database.InsertChatModelConfigParams{
|
||||
Provider: providerName,
|
||||
Model: modelName,
|
||||
DisplayName: "Debug Model",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
ContextLimit: 128000,
|
||||
CompressionThreshold: 80,
|
||||
Options: json.RawMessage(`{}`),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
chat, err := store.InsertChat(ctx, database.InsertChatParams{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "chat-debug-started-before-" + uuid.NewString(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
const cutoff int64 = 50
|
||||
|
||||
// oldRun started an hour ago: must be deleted because it started
|
||||
// before the bound.
|
||||
oldStartedAt := time.Now().Add(-1 * time.Hour).UTC().
|
||||
Truncate(time.Microsecond)
|
||||
oldRun, err := store.InsertChatDebugRun(ctx, database.InsertChatDebugRunParams{
|
||||
ChatID: chat.ID,
|
||||
ModelConfigID: uuid.NullUUID{UUID: modelCfg.ID, Valid: true},
|
||||
TriggerMessageID: sql.NullInt64{Int64: cutoff + 1, Valid: true},
|
||||
HistoryTipMessageID: sql.NullInt64{Int64: cutoff + 1, Valid: true},
|
||||
Kind: "chat_turn",
|
||||
Status: "in_progress",
|
||||
Provider: sql.NullString{String: providerName, Valid: true},
|
||||
Model: sql.NullString{String: modelName, Valid: true},
|
||||
StartedAt: sql.NullTime{Time: oldStartedAt, Valid: true},
|
||||
UpdatedAt: sql.NullTime{Time: oldStartedAt, Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Bound sits between the two runs. Any run whose started_at is at
|
||||
// or after this instant must survive.
|
||||
cutoffTime := time.Now().Add(-30 * time.Minute).UTC().
|
||||
Truncate(time.Microsecond)
|
||||
|
||||
// newRun started after cutoffTime with identical message_id values
|
||||
// that would otherwise match the delete predicate. It must survive
|
||||
// because started_before excludes it.
|
||||
newStartedAt := time.Now().UTC().Truncate(time.Microsecond)
|
||||
newRun, err := store.InsertChatDebugRun(ctx, database.InsertChatDebugRunParams{
|
||||
ChatID: chat.ID,
|
||||
ModelConfigID: uuid.NullUUID{UUID: modelCfg.ID, Valid: true},
|
||||
TriggerMessageID: sql.NullInt64{Int64: cutoff + 1, Valid: true},
|
||||
HistoryTipMessageID: sql.NullInt64{Int64: cutoff + 1, Valid: true},
|
||||
Kind: "chat_turn",
|
||||
Status: "in_progress",
|
||||
Provider: sql.NullString{String: providerName, Valid: true},
|
||||
Model: sql.NullString{String: modelName, Valid: true},
|
||||
StartedAt: sql.NullTime{Time: newStartedAt, Valid: true},
|
||||
UpdatedAt: sql.NullTime{Time: newStartedAt, Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
deletedRows, err := store.DeleteChatDebugDataAfterMessageID(ctx, database.DeleteChatDebugDataAfterMessageIDParams{
|
||||
ChatID: chat.ID,
|
||||
MessageID: cutoff,
|
||||
StartedBefore: cutoffTime,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, deletedRows,
|
||||
"only the pre-cutoff run should be deleted")
|
||||
|
||||
// oldRun must be gone.
|
||||
_, err = store.GetChatDebugRunByID(ctx, oldRun.ID)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
|
||||
// newRun must survive the retry window.
|
||||
remaining, err := store.GetChatDebugRunByID(ctx, newRun.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newRun.ID, remaining.ID)
|
||||
}
|
||||
|
||||
// TestDeleteChatDebugDataByChatIDStartedBeforeFiltersNewerRuns verifies
|
||||
// the started_before bound on DeleteChatDebugDataByChatID. Archive
|
||||
// cleanup retries rely on this bound to avoid deleting runs created
|
||||
// by a replacement turn that starts after an unarchive races ahead of
|
||||
// the retry window.
|
||||
func TestDeleteChatDebugDataByChatIDStartedBeforeFiltersNewerRuns(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
store, _ := dbtestutil.NewDB(t)
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
|
||||
org := dbgen.Organization(t, store, database.Organization{})
|
||||
user := dbgen.User(t, store, database.User{})
|
||||
|
||||
providerName := "openai"
|
||||
modelName := "debug-model-by-chat-started-before-" + uuid.NewString()
|
||||
|
||||
_, err := store.InsertChatProvider(ctx, database.InsertChatProviderParams{
|
||||
Provider: providerName,
|
||||
DisplayName: "Debug Provider",
|
||||
APIKey: "test-key",
|
||||
Enabled: true,
|
||||
CentralApiKeyEnabled: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
modelCfg, err := store.InsertChatModelConfig(ctx, database.InsertChatModelConfigParams{
|
||||
Provider: providerName,
|
||||
Model: modelName,
|
||||
DisplayName: "Debug Model",
|
||||
CreatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
UpdatedBy: uuid.NullUUID{UUID: user.ID, Valid: true},
|
||||
Enabled: true,
|
||||
IsDefault: true,
|
||||
ContextLimit: 128000,
|
||||
CompressionThreshold: 80,
|
||||
Options: json.RawMessage(`{}`),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
chat, err := store.InsertChat(ctx, database.InsertChatParams{
|
||||
OrganizationID: org.ID,
|
||||
Status: database.ChatStatusWaiting,
|
||||
ClientType: database.ChatClientTypeUi,
|
||||
OwnerID: user.ID,
|
||||
LastModelConfigID: modelCfg.ID,
|
||||
Title: "chat-debug-by-chat-" + uuid.NewString(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
oldStartedAt := time.Now().Add(-1 * time.Hour).UTC().
|
||||
Truncate(time.Microsecond)
|
||||
oldRun, err := store.InsertChatDebugRun(ctx, database.InsertChatDebugRunParams{
|
||||
ChatID: chat.ID,
|
||||
ModelConfigID: uuid.NullUUID{UUID: modelCfg.ID, Valid: true},
|
||||
Kind: "chat_turn",
|
||||
Status: "in_progress",
|
||||
Provider: sql.NullString{String: providerName, Valid: true},
|
||||
Model: sql.NullString{String: modelName, Valid: true},
|
||||
StartedAt: sql.NullTime{Time: oldStartedAt, Valid: true},
|
||||
UpdatedAt: sql.NullTime{Time: oldStartedAt, Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
cutoffTime := time.Now().Add(-30 * time.Minute).UTC().
|
||||
Truncate(time.Microsecond)
|
||||
|
||||
newStartedAt := time.Now().UTC().Truncate(time.Microsecond)
|
||||
newRun, err := store.InsertChatDebugRun(ctx, database.InsertChatDebugRunParams{
|
||||
ChatID: chat.ID,
|
||||
ModelConfigID: uuid.NullUUID{UUID: modelCfg.ID, Valid: true},
|
||||
Kind: "chat_turn",
|
||||
Status: "in_progress",
|
||||
Provider: sql.NullString{String: providerName, Valid: true},
|
||||
Model: sql.NullString{String: modelName, Valid: true},
|
||||
StartedAt: sql.NullTime{Time: newStartedAt, Valid: true},
|
||||
UpdatedAt: sql.NullTime{Time: newStartedAt, Valid: true},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
deletedRows, err := store.DeleteChatDebugDataByChatID(ctx, database.DeleteChatDebugDataByChatIDParams{
|
||||
ChatID: chat.ID,
|
||||
StartedBefore: cutoffTime,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, deletedRows,
|
||||
"only the pre-cutoff run should be deleted")
|
||||
|
||||
_, err = store.GetChatDebugRunByID(ctx, oldRun.ID)
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
|
||||
remaining, err := store.GetChatDebugRunByID(ctx, newRun.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newRun.ID, remaining.ID)
|
||||
}
|
||||
|
||||
func TestChatHasUnread(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user