Files
tutti/services/tuttid/data/workspace/sqlite_preferences_test.go
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

794 lines
29 KiB
Go

package workspace
import (
"context"
"errors"
"reflect"
"runtime"
"sync"
"testing"
"time"
preferencesbiz "github.com/tutti-os/tutti/services/tuttid/biz/preferences"
)
func TestSQLiteStoreGetDesktopPreferencesDefaultsWhenUnset(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
preferences, err := store.GetDesktopPreferences(context.Background())
if err != nil {
t.Fatalf("GetDesktopPreferences() error = %v", err)
}
if preferences.Initialized {
t.Fatal("GetDesktopPreferences() initialized = true, want false")
}
if preferences.Locale != "en" {
t.Fatalf("GetDesktopPreferences() locale = %q, want en", preferences.Locale)
}
if preferences.DockPlacement != "bottom" {
t.Fatalf("GetDesktopPreferences() dockPlacement = %q, want bottom", preferences.DockPlacement)
}
if preferences.DockIconStyle != "default" {
t.Fatalf("GetDesktopPreferences() dockIconStyle = %q, want default", preferences.DockIconStyle)
}
if preferences.DeletedAgentConversationRetentionDays != 30 {
t.Fatalf("GetDesktopPreferences() retention days = %d, want 30", preferences.DeletedAgentConversationRetentionDays)
}
if preferences.DefaultAgentProvider != "tutti-agent" {
t.Fatalf("GetDesktopPreferences() defaultAgentProvider = %q, want tutti-agent", preferences.DefaultAgentProvider)
}
if preferences.AgentConversationDetailMode != "coding" {
t.Fatalf("GetDesktopPreferences() agentConversationDetailMode = %q, want coding", preferences.AgentConversationDetailMode)
}
if preferences.AgentDockLayout != "unified" {
t.Fatalf("GetDesktopPreferences() agentDockLayout = %q, want unified", preferences.AgentDockLayout)
}
if preferences.ThemeSource != "dark" {
t.Fatalf("GetDesktopPreferences() themeSource = %q, want dark", preferences.ThemeSource)
}
if preferences.SleepPreventionMode != "never" {
t.Fatalf("GetDesktopPreferences() sleepPreventionMode = %q, want never", preferences.SleepPreventionMode)
}
if preferences.BrowserUseConnectionMode != "isolated" {
t.Fatalf("GetDesktopPreferences() browserUseConnectionMode = %q, want isolated", preferences.BrowserUseConnectionMode)
}
if preferences.AppCatalogChannel != "production" {
t.Fatalf("GetDesktopPreferences() appCatalogChannel = %q, want production", preferences.AppCatalogChannel)
}
if preferences.FileDefaultOpenersByExtension["html"] != "appBrowser" {
t.Fatalf("GetDesktopPreferences() html opener = %q, want appBrowser", preferences.FileDefaultOpenersByExtension["html"])
}
if len(preferences.AgentGUIConversationRailCollapsedByProvider) != 0 {
t.Fatalf("GetDesktopPreferences() rail collapsed preferences = %#v, want empty", preferences.AgentGUIConversationRailCollapsedByProvider)
}
if len(preferences.AgentSessionLaunchModesByWorkspace) != 0 {
t.Fatalf("GetDesktopPreferences() agent session launch modes = %#v, want empty", preferences.AgentSessionLaunchModesByWorkspace)
}
if preferences.UpdatePolicy != "prompt" {
t.Fatalf("GetDesktopPreferences() updatePolicy = %q, want prompt", preferences.UpdatePolicy)
}
if preferences.UpdateChannel != "stable" {
t.Fatalf("GetDesktopPreferences() updateChannel = %q, want stable", preferences.UpdateChannel)
}
}
func TestSQLiteStorePutDesktopPreferencesPersistsValue(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
ctx := context.Background()
saved, err := store.PutDesktopPreferences(ctx, preferencesbiz.DesktopPreferences{
AgentCLIUpdateCheckEnabled: false,
AgentComposerDefaultsByProvider: map[string]preferencesbiz.AgentComposerDefaults{
"codex": {
Model: "gpt-5",
PermissionModeID: "full-access",
ReasoningEffort: "high",
},
},
AgentGUIConversationRailCollapsedByProvider: map[string]bool{
"codex": true,
"claude-code": false,
},
AgentSessionLaunchModesByWorkspace: map[string]map[string]string{
"workspace-1": {
"project-1": "worktree",
"project-2": "local",
},
},
AgentConversationDetailMode: "general",
AgentDockLayout: "unified",
DefaultAgentProvider: "claude-code",
BrowserUseConnectionMode: "autoConnect",
AppCatalogChannel: "staging",
DockIconStyle: "default",
DockPlacement: "left",
DeletedAgentConversationRetentionDays: 15,
FileDefaultOpenersByExtension: map[string]string{
"html": "fileViewer",
"pdf": "defaultBrowser",
},
Initialized: true,
Locale: "zh-CN",
MinimizeAnimation: "scale",
SleepPreventionMode: "whileAgentRunning",
ThemeSource: "dark",
UpdateChannel: "rc",
UpdatePolicy: "auto",
})
if err != nil {
t.Fatalf("PutDesktopPreferences() error = %v", err)
}
if !saved.Initialized {
t.Fatal("PutDesktopPreferences() initialized = false, want true")
}
if saved.AgentCLIUpdateCheckEnabled {
t.Fatal("PutDesktopPreferences() agent CLI update check = true, want false")
}
reloaded, err := store.GetDesktopPreferences(ctx)
if err != nil {
t.Fatalf("GetDesktopPreferences() error = %v", err)
}
if !reloaded.Initialized {
t.Fatal("GetDesktopPreferences() initialized = false, want true")
}
if reloaded.AgentCLIUpdateCheckEnabled {
t.Fatal("GetDesktopPreferences() agent CLI update check = true, want false")
}
if reloaded.Locale != "zh-CN" {
t.Fatalf("GetDesktopPreferences() locale = %q, want zh-CN", reloaded.Locale)
}
if reloaded.DockPlacement != "left" {
t.Fatalf("GetDesktopPreferences() dockPlacement = %q, want left", reloaded.DockPlacement)
}
if reloaded.DeletedAgentConversationRetentionDays != 15 {
t.Fatalf("GetDesktopPreferences() retention days = %d, want 15", reloaded.DeletedAgentConversationRetentionDays)
}
if reloaded.DefaultAgentProvider != "claude-code" {
t.Fatalf("GetDesktopPreferences() defaultAgentProvider = %q, want claude-code", reloaded.DefaultAgentProvider)
}
if reloaded.AgentConversationDetailMode != "general" {
t.Fatalf("GetDesktopPreferences() agentConversationDetailMode = %q, want general", reloaded.AgentConversationDetailMode)
}
if reloaded.AgentDockLayout != "unified" {
t.Fatalf("GetDesktopPreferences() agentDockLayout = %q, want unified", reloaded.AgentDockLayout)
}
if reloaded.ThemeSource != "dark" {
t.Fatalf("GetDesktopPreferences() themeSource = %q, want dark", reloaded.ThemeSource)
}
if reloaded.SleepPreventionMode != "whileAgentRunning" {
t.Fatalf("GetDesktopPreferences() sleepPreventionMode = %q, want whileAgentRunning", reloaded.SleepPreventionMode)
}
if reloaded.BrowserUseConnectionMode != "autoConnect" {
t.Fatalf("GetDesktopPreferences() browserUseConnectionMode = %q, want autoConnect", reloaded.BrowserUseConnectionMode)
}
if reloaded.AppCatalogChannel != "staging" {
t.Fatalf("GetDesktopPreferences() appCatalogChannel = %q, want staging", reloaded.AppCatalogChannel)
}
if reloaded.FileDefaultOpenersByExtension["html"] != "fileViewer" || reloaded.FileDefaultOpenersByExtension["pdf"] != "defaultBrowser" {
t.Fatalf("GetDesktopPreferences() file default openers = %#v, want html/pdf", reloaded.FileDefaultOpenersByExtension)
}
if !reloaded.AgentGUIConversationRailCollapsedByProvider["codex"] {
t.Fatalf("GetDesktopPreferences() codex rail collapsed = false, want true")
}
if collapsed, ok := reloaded.AgentGUIConversationRailCollapsedByProvider["claude-code"]; !ok || collapsed {
t.Fatalf("GetDesktopPreferences() claude rail collapsed = %v/%v, want present false", collapsed, ok)
}
workspaceLaunchModes := reloaded.AgentSessionLaunchModesByWorkspace["workspace-1"]
if workspaceLaunchModes["project-1"] != "worktree" || workspaceLaunchModes["project-2"] != "local" {
t.Fatalf("GetDesktopPreferences() agent session launch modes = %#v, want project-1/worktree and project-2/local", reloaded.AgentSessionLaunchModesByWorkspace)
}
if reloaded.UpdatePolicy != "auto" {
t.Fatalf("GetDesktopPreferences() updatePolicy = %q, want auto", reloaded.UpdatePolicy)
}
if reloaded.UpdateChannel != "rc" {
t.Fatalf("GetDesktopPreferences() updateChannel = %q, want rc", reloaded.UpdateChannel)
}
codexDefaults := reloaded.AgentComposerDefaultsByProvider["codex"]
if codexDefaults.Model != "gpt-5" ||
codexDefaults.PermissionModeID != "full-access" ||
codexDefaults.ReasoningEffort != "high" {
t.Fatalf("GetDesktopPreferences() codex composer defaults = %#v, want gpt-5/full-access/high", codexDefaults)
}
}
func TestSQLiteStoreInitializeDesktopPreferencesCreatesMissingRow(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
candidate := preferencesbiz.DefaultDesktopPreferences()
candidate.Locale = "zh-CN"
stored, created, err := store.InitializeDesktopPreferences(context.Background(), candidate)
if err != nil {
t.Fatalf("InitializeDesktopPreferences() error = %v", err)
}
if !created {
t.Fatal("InitializeDesktopPreferences() created = false, want true")
}
if !stored.Initialized || stored.Locale != "zh-CN" {
t.Fatalf("stored preferences = %#v", stored)
}
if !stored.FeatureFlags[preferencesbiz.DesktopStandaloneAgentModeFeatureFlag] {
t.Fatalf("stored feature flags = %#v, want standalone Agent mode enabled", stored.FeatureFlags)
}
}
func TestSQLiteStoreInitializeDesktopPreferencesPreservesExistingLegacyRow(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
ctx := context.Background()
existing := preferencesbiz.DefaultDesktopPreferences()
existing.FeatureFlags = map[string]bool{}
existing.Locale = "en"
existing.ThemeSource = "light"
if _, err := store.PutDesktopPreferences(ctx, existing); err != nil {
t.Fatalf("PutDesktopPreferences() error = %v", err)
}
candidate := preferencesbiz.DefaultDesktopPreferences()
candidate.Locale = "zh-CN"
candidate.ThemeSource = "dark"
stored, created, err := store.InitializeDesktopPreferences(ctx, candidate)
if err != nil {
t.Fatalf("InitializeDesktopPreferences() error = %v", err)
}
if created {
t.Fatal("InitializeDesktopPreferences() created = true, want false")
}
if stored.Locale != "en" || stored.ThemeSource != "light" {
t.Fatalf("stored locale/theme = %q/%q, want en/light", stored.Locale, stored.ThemeSource)
}
if len(stored.FeatureFlags) != 0 {
t.Fatalf("stored feature flags = %#v, want legacy empty flags", stored.FeatureFlags)
}
}
func TestSQLiteStoreInitializeDesktopPreferencesCommitsExactlyOneCompleteCandidateUnderConcurrentWriters(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
writerGate, err := store.writeDB.Conn(ctx)
if err != nil {
t.Fatalf("hold SQLite writer connection: %v", err)
}
gateReleased := false
releaseGate := func() {
if gateReleased {
return
}
gateReleased = true
if err := writerGate.Close(); err != nil {
t.Errorf("release SQLite writer connection: %v", err)
}
}
defer releaseGate()
candidateA := preferencesbiz.DefaultDesktopPreferences()
candidateA.Initialized = true
candidateA.Locale = "zh-CN"
candidateA.ThemeSource = "dark"
candidateA.FeatureFlags = map[string]bool{
preferencesbiz.DesktopStandaloneAgentModeFeatureFlag: true,
"test.candidate-a": true,
}
candidateB := preferencesbiz.DefaultDesktopPreferences()
candidateB.Initialized = true
candidateB.DockPlacement = "left"
candidateB.Locale = "en"
candidateB.ThemeSource = "light"
candidateB.FeatureFlags = map[string]bool{
preferencesbiz.DesktopStandaloneAgentModeFeatureFlag: false,
"test.candidate-b": true,
}
candidates := []preferencesbiz.DesktopPreferences{candidateA, candidateB}
type initializationResult struct {
created bool
err error
preferences preferencesbiz.DesktopPreferences
}
results := make(chan initializationResult, len(candidates))
var writers sync.WaitGroup
baselineWaitCount := store.writeDB.Stats().WaitCount
for _, candidate := range candidates {
candidate := candidate
writers.Add(1)
go func() {
defer writers.Done()
stored, created, err := store.InitializeDesktopPreferences(ctx, candidate)
results <- initializationResult{
created: created,
err: err,
preferences: stored,
}
}()
}
wantWaitCount := baselineWaitCount + int64(len(candidates))
for store.writeDB.Stats().WaitCount < wantWaitCount && ctx.Err() == nil {
runtime.Gosched()
}
queuedWaitCount := store.writeDB.Stats().WaitCount
releaseGate()
writers.Wait()
close(results)
if queuedWaitCount < wantWaitCount {
t.Fatalf("SQLite writer wait count = %d, want at least %d before gate release: %v", queuedWaitCount, wantWaitCount, ctx.Err())
}
createdCount := 0
var returned []preferencesbiz.DesktopPreferences
for result := range results {
if result.err != nil {
t.Fatalf("InitializeDesktopPreferences() error = %v", result.err)
}
if result.created {
createdCount++
}
returned = append(returned, result.preferences)
}
if createdCount != 1 {
t.Fatalf("created results = %d, want exactly 1", createdCount)
}
if len(returned) != len(candidates) || !reflect.DeepEqual(returned[0], returned[1]) {
t.Fatalf("returned preferences = %#v, want both writers to observe one canonical row", returned)
}
final, err := store.GetDesktopPreferences(context.Background())
if err != nil {
t.Fatalf("GetDesktopPreferences() error = %v", err)
}
if !reflect.DeepEqual(returned[0], final) {
t.Fatalf("returned preferences = %#v, final = %#v", returned[0], final)
}
if !reflect.DeepEqual(final, candidateA) && !reflect.DeepEqual(final, candidateB) {
t.Fatalf("final preferences = %#v, want one complete candidate without merging", final)
}
}
func TestSQLiteStoreDesktopPreferencesAgentConversationDetailModeMigrationAndNormalize(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
ctx := context.Background()
hasAgentConversationDetailMode, err := store.hasColumn(ctx, "desktop_preferences", "agent_conversation_detail_mode")
if err != nil {
t.Fatalf("hasColumn() error = %v", err)
}
if !hasAgentConversationDetailMode {
t.Fatal("desktop_preferences.agent_conversation_detail_mode column missing after migration")
}
_, err = store.writeDB.ExecContext(ctx, `
INSERT INTO desktop_preferences (
id,
default_agent_provider,
agent_conversation_detail_mode,
agent_dock_layout,
dock_icon_style,
dock_placement,
locale,
theme_source,
sleep_prevention_mode,
update_channel,
update_policy,
agent_composer_defaults_by_provider_json,
agent_gui_conversation_rail_collapsed_by_provider_json,
file_default_openers_by_extension_json,
app_catalog_channel,
browser_use_connection_mode,
minimize_animation,
show_app_developer_sources,
workbench_window_snapping_enabled,
workbench_window_snapping_shortcut_preset,
updated_at_unix_ms
) VALUES (
'desktop',
'codex',
'daily',
'sideBySide',
'default',
'bottom',
'en',
'dark',
'never',
'rc',
'prompt',
'{}',
'{}',
'{}',
'production',
'isolated',
'scale',
0,
0,
'commandArrows',
1
)`)
if err != nil {
t.Fatalf("insert desktop preferences with invalid conversation detail mode: %v", err)
}
preferences, err := store.GetDesktopPreferences(ctx)
if err != nil {
t.Fatalf("GetDesktopPreferences() error = %v", err)
}
if preferences.AgentConversationDetailMode != "coding" {
t.Fatalf("GetDesktopPreferences() agentConversationDetailMode = %q, want coding", preferences.AgentConversationDetailMode)
}
if preferences.AgentDockLayout != "unified" {
t.Fatalf("GetDesktopPreferences() agentDockLayout = %q, want unified", preferences.AgentDockLayout)
}
}
func TestSQLiteStorePutDesktopPreferencesPersistsAgentComposerDefaultsByAgentTarget(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
input := preferencesbiz.DefaultDesktopPreferences()
input.AgentComposerDefaultsByProvider = map[string]preferencesbiz.AgentComposerDefaults{
"codex": {Model: "gpt-5"},
}
input.AgentComposerDefaultsByAgentTarget = map[string]preferencesbiz.AgentComposerDefaults{
"local:codex": {
Model: "gpt-5-codex",
PermissionModeID: "full-access",
ReasoningEffort: "high",
Speed: "fast",
},
}
if _, err := store.PutDesktopPreferences(context.Background(), input); err != nil {
t.Fatalf("PutDesktopPreferences() error = %v", err)
}
preferences, err := store.GetDesktopPreferences(context.Background())
if err != nil {
t.Fatalf("GetDesktopPreferences() error = %v", err)
}
codexDefaults := preferences.AgentComposerDefaultsByAgentTarget["local:codex"]
if codexDefaults.Model != "gpt-5-codex" ||
codexDefaults.PermissionModeID != "full-access" ||
codexDefaults.ReasoningEffort != "high" ||
codexDefaults.Speed != "fast" {
t.Fatalf("agent target defaults = %#v, want persisted round-trip", codexDefaults)
}
if preferences.AgentComposerDefaultsByProvider["codex"].Model != "gpt-5" {
t.Fatalf("legacy provider defaults = %#v, want preserved", preferences.AgentComposerDefaultsByProvider)
}
}
func TestSQLiteStoreMigrationBackfillsAgentComposerDefaultsByAgentTarget(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
// Simulate a pre-migration database: legacy provider-keyed defaults
// exist, the agent-target column is empty, and the data migration marker
// is absent.
legacy := preferencesbiz.DefaultDesktopPreferences()
legacy.AgentComposerDefaultsByProvider = map[string]preferencesbiz.AgentComposerDefaults{
"codex": {Model: "gpt-5", PermissionModeID: "full-access"},
"gemini": {Model: "legacy-gemini-pro"},
}
if _, err := store.PutDesktopPreferences(context.Background(), legacy); err != nil {
t.Fatalf("PutDesktopPreferences() error = %v", err)
}
if _, err := store.writeDB.ExecContext(context.Background(), `
DELETE FROM tuttid_schema_migrations WHERE id = ?
`, schemaMigrationDesktopPreferencesAgentComposerDefaultsByAgentTargetV1); err != nil {
t.Fatalf("reset migration marker: %v", err)
}
if err := store.applyDesktopPreferencesAgentComposerDefaultsByAgentTargetV1(context.Background()); err != nil {
t.Fatalf("applyDesktopPreferencesAgentComposerDefaultsByAgentTargetV1() error = %v", err)
}
preferences, err := store.GetDesktopPreferences(context.Background())
if err != nil {
t.Fatalf("GetDesktopPreferences() error = %v", err)
}
codexDefaults := preferences.AgentComposerDefaultsByAgentTarget["local:codex"]
if codexDefaults.Model != "gpt-5" || codexDefaults.PermissionModeID != "full-access" {
t.Fatalf("backfilled codex defaults = %#v, want legacy values", codexDefaults)
}
if _, ok := preferences.AgentComposerDefaultsByAgentTarget["local:gemini"]; ok {
t.Fatalf("local:gemini defaults were backfilled: %#v", preferences.AgentComposerDefaultsByAgentTarget)
}
// Re-running the backfill must not clobber newer agent-target data.
model := "gpt-5-codex"
if _, err := store.PatchAgentComposerDefaultsForTarget(context.Background(), "local:codex", preferencesbiz.AgentComposerDefaultsPatch{
preferencesbiz.AgentComposerDefaultsFieldModel: &model,
preferencesbiz.AgentComposerDefaultsFieldPermissionModeID: nil,
}); err != nil {
t.Fatalf("PatchAgentComposerDefaultsForTarget() error = %v", err)
}
if err := store.backfillAgentComposerDefaultsByAgentTarget(context.Background()); err != nil {
t.Fatalf("backfillAgentComposerDefaultsByAgentTarget() error = %v", err)
}
preferences, err = store.GetDesktopPreferences(context.Background())
if err != nil {
t.Fatalf("GetDesktopPreferences() error = %v", err)
}
if preferences.AgentComposerDefaultsByAgentTarget["local:codex"].Model != "gpt-5-codex" {
t.Fatalf("agent target defaults = %#v, want newer data preserved", preferences.AgentComposerDefaultsByAgentTarget)
}
}
func TestSQLiteStorePatchAgentComposerDefaultsForTargetMergesLatestFieldsAndPreservesPreferences(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
ctx := context.Background()
stale := preferencesbiz.DefaultDesktopPreferences()
stale.Locale = "zh-CN"
stale.ThemeSource = "light"
if _, err := store.PutDesktopPreferences(ctx, stale); err != nil {
t.Fatalf("PutDesktopPreferences() error = %v", err)
}
permission := "full-access"
if _, err := store.PatchAgentComposerDefaultsForTarget(ctx, "local:opencode", preferencesbiz.AgentComposerDefaultsPatch{
preferencesbiz.AgentComposerDefaultsFieldPermissionModeID: &permission,
}); err != nil {
t.Fatalf("patch permission: %v", err)
}
model := "openai/gpt-5"
reasoning := "high"
speed := "fast"
if _, err := store.PatchAgentComposerDefaultsForTarget(ctx, "local:opencode", preferencesbiz.AgentComposerDefaultsPatch{
preferencesbiz.AgentComposerDefaultsFieldModel: &model,
preferencesbiz.AgentComposerDefaultsFieldReasoningEffort: &reasoning,
preferencesbiz.AgentComposerDefaultsFieldSpeed: &speed,
}); err != nil {
t.Fatalf("patch remaining fields: %v", err)
}
otherModel := "claude-sonnet-4"
if _, err := store.PatchAgentComposerDefaultsForTarget(ctx, "local:claude-code", preferencesbiz.AgentComposerDefaultsPatch{
preferencesbiz.AgentComposerDefaultsFieldModel: &otherModel,
}); err != nil {
t.Fatalf("patch other target: %v", err)
}
// A full preference update based on an older snapshot must not overwrite
// any target defaults committed after that snapshot was read.
stale.Locale = "en"
if _, err := store.PutDesktopPreferences(ctx, stale); err != nil {
t.Fatalf("put stale full preferences: %v", err)
}
got, err := store.GetDesktopPreferences(ctx)
if err != nil {
t.Fatalf("GetDesktopPreferences() error = %v", err)
}
opencode := got.AgentComposerDefaultsByAgentTarget["local:opencode"]
if opencode.Model != model || opencode.PermissionModeID != permission || opencode.ReasoningEffort != reasoning || opencode.Speed != speed {
t.Fatalf("opencode defaults = %#v", opencode)
}
if got.AgentComposerDefaultsByAgentTarget["local:claude-code"].Model != otherModel {
t.Fatalf("target defaults = %#v", got.AgentComposerDefaultsByAgentTarget)
}
if got.Locale != "en" || got.ThemeSource != "light" {
t.Fatalf("unrelated preferences locale=%q theme=%q", got.Locale, got.ThemeSource)
}
// Repeating a SET is naturally idempotent.
if _, err := store.PatchAgentComposerDefaultsForTarget(ctx, "local:opencode", preferencesbiz.AgentComposerDefaultsPatch{
preferencesbiz.AgentComposerDefaultsFieldPermissionModeID: &permission,
}); err != nil {
t.Fatalf("repeat permission patch: %v", err)
}
}
func TestSQLiteStorePatchAgentComposerDefaultsForTargetRejectsMissingPreferencesRow(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
model := "gpt-5"
_, err := store.PatchAgentComposerDefaultsForTarget(context.Background(), "local:codex", preferencesbiz.AgentComposerDefaultsPatch{
preferencesbiz.AgentComposerDefaultsFieldModel: &model,
})
if !errors.Is(err, ErrDesktopPreferencesNotInitialized) {
t.Fatalf("PatchAgentComposerDefaultsForTarget() error = %v, want %v", err, ErrDesktopPreferencesNotInitialized)
}
var rows int
if err := store.readDB.QueryRowContext(context.Background(), `SELECT COUNT(*) FROM desktop_preferences`).Scan(&rows); err != nil {
t.Fatalf("count desktop preferences rows: %v", err)
}
if rows != 0 {
t.Fatalf("desktop preferences rows = %d, want 0", rows)
}
}
func TestSQLiteStorePatchAgentComposerDefaultsForTargetPersistsCodexSaverMode(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
if _, err := store.PutDesktopPreferences(context.Background(), preferencesbiz.DefaultDesktopPreferences()); err != nil {
t.Fatalf("PutDesktopPreferences() error = %v", err)
}
if _, err := store.PatchAgentComposerDefaultsForTarget(context.Background(), "local:codex", preferencesbiz.AgentComposerDefaultsPatch{
preferencesbiz.AgentComposerDefaultsFieldCodexSaverMode: true,
}); err != nil {
t.Fatalf("enable saver mode: %v", err)
}
got, err := store.GetDesktopPreferences(context.Background())
if err != nil {
t.Fatalf("GetDesktopPreferences() error = %v", err)
}
if !got.AgentComposerDefaultsByAgentTarget["local:codex"].CodexSaverMode {
t.Fatalf("defaults = %#v", got.AgentComposerDefaultsByAgentTarget)
}
if _, err := store.PatchAgentComposerDefaultsForTarget(context.Background(), "local:codex", preferencesbiz.AgentComposerDefaultsPatch{
preferencesbiz.AgentComposerDefaultsFieldCodexSaverMode: false,
}); err != nil {
t.Fatalf("disable saver mode: %v", err)
}
got, err = store.GetDesktopPreferences(context.Background())
if err != nil {
t.Fatalf("GetDesktopPreferences() error = %v", err)
}
if got.AgentComposerDefaultsByAgentTarget["local:codex"].CodexSaverMode {
t.Fatalf("defaults = %#v, want saver mode cleared", got.AgentComposerDefaultsByAgentTarget)
}
}
func TestSQLiteStorePatchAgentComposerDefaultsForTargetSerializesConcurrentFields(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
if _, err := store.PutDesktopPreferences(context.Background(), preferencesbiz.DefaultDesktopPreferences()); err != nil {
t.Fatalf("PutDesktopPreferences() error = %v", err)
}
permission := "full-access"
model := "gpt-5"
patches := []preferencesbiz.AgentComposerDefaultsPatch{
{preferencesbiz.AgentComposerDefaultsFieldPermissionModeID: &permission},
{preferencesbiz.AgentComposerDefaultsFieldModel: &model},
}
var wait sync.WaitGroup
errorsByPatch := make([]error, len(patches))
for index, patch := range patches {
wait.Add(1)
go func() {
defer wait.Done()
_, errorsByPatch[index] = store.PatchAgentComposerDefaultsForTarget(context.Background(), "local:codex", patch)
}()
}
wait.Wait()
for _, err := range errorsByPatch {
if err != nil {
t.Fatalf("concurrent patch error = %v", err)
}
}
got, err := store.GetDesktopPreferences(context.Background())
if err != nil {
t.Fatalf("GetDesktopPreferences() error = %v", err)
}
defaults := got.AgentComposerDefaultsByAgentTarget["local:codex"]
if defaults.Model != model || defaults.PermissionModeID != permission {
t.Fatalf("defaults = %#v", defaults)
}
}
func TestSQLiteStorePatchAgentSessionLaunchModeSerializesConcurrentProjectsAndRejectsStaleReplacement(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
ctx := context.Background()
stale := preferencesbiz.DefaultDesktopPreferences()
stale.Locale = "en"
if _, err := store.PutDesktopPreferences(ctx, stale); err != nil {
t.Fatal(err)
}
patches := []struct {
workspaceID string
projectSectionKey string
mode string
}{
{workspaceID: "workspace-a", projectSectionKey: "project:/alpha", mode: "worktree"},
{workspaceID: "workspace-b", projectSectionKey: "project:/beta", mode: "local"},
}
var wait sync.WaitGroup
errorsByPatch := make([]error, len(patches))
for index, patch := range patches {
wait.Add(1)
go func() {
defer wait.Done()
_, errorsByPatch[index] = store.PatchAgentSessionLaunchMode(
context.Background(),
patch.workspaceID,
patch.projectSectionKey,
patch.mode,
)
}()
}
wait.Wait()
for _, err := range errorsByPatch {
if err != nil {
t.Fatalf("concurrent launch mode patch: %v", err)
}
}
stale.Locale = "zh-CN"
stale.AgentSessionLaunchModesByWorkspace = map[string]map[string]string{
"workspace-stale": {"project:/stale": "worktree"},
}
if _, err := store.PutDesktopPreferences(ctx, stale); err != nil {
t.Fatalf("put stale full preferences: %v", err)
}
got, err := store.GetDesktopPreferences(ctx)
if err != nil {
t.Fatal(err)
}
if got.AgentSessionLaunchModesByWorkspace["workspace-a"]["project:/alpha"] != "worktree" ||
got.AgentSessionLaunchModesByWorkspace["workspace-b"]["project:/beta"] != "local" ||
len(got.AgentSessionLaunchModesByWorkspace) != 2 {
t.Fatalf("launch modes = %#v", got.AgentSessionLaunchModesByWorkspace)
}
if got.Locale != "zh-CN" {
t.Fatalf("locale = %q, want zh-CN", got.Locale)
}
}
func TestSQLiteStorePatchAgentSessionLaunchModeRejectsMissingPreferencesRow(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
_, err := store.PatchAgentSessionLaunchMode(
context.Background(),
"workspace-a",
"project:/alpha",
"worktree",
)
if !errors.Is(err, ErrDesktopPreferencesNotInitialized) {
t.Fatalf("PatchAgentSessionLaunchMode() error = %v, want %v", err, ErrDesktopPreferencesNotInitialized)
}
var rows int
if err := store.readDB.QueryRowContext(context.Background(), `SELECT COUNT(*) FROM desktop_preferences`).Scan(&rows); err != nil {
t.Fatalf("count desktop preferences rows: %v", err)
}
if rows != 0 {
t.Fatalf("desktop preferences rows = %d, want 0", rows)
}
}
func TestDesktopPreferencesFeatureFlagsRoundtrip(t *testing.T) {
t.Parallel()
store := openTestSQLiteStore(t)
ctx := context.Background()
in := preferencesbiz.DefaultDesktopPreferences()
in.FeatureFlags = map[string]bool{"lab.enabled": true, "lab.workbenchShortcuts": true}
in.WorkbenchShortcuts = preferencesbiz.DesktopWorkbenchShortcuts{
NewAgentConversation: "Meta+K",
CaptureScreenshot: "Meta+Shift+P",
}
if _, err := store.PutDesktopPreferences(ctx, in); err != nil {
t.Fatal(err)
}
got, err := store.GetDesktopPreferences(ctx)
if err != nil {
t.Fatal(err)
}
if !got.FeatureFlags["lab.enabled"] || !got.FeatureFlags["lab.workbenchShortcuts"] {
t.Fatalf("flags not persisted: %v", got.FeatureFlags)
}
if got.WorkbenchShortcuts.NewAgentConversation != "Meta+K" ||
got.WorkbenchShortcuts.NewSameTypeWindow != "" ||
got.WorkbenchShortcuts.CaptureScreenshot != "Meta+Shift+P" {
t.Fatalf("shortcuts wrong: %+v", got.WorkbenchShortcuts)
}
}