From e2e3aad216861364c4d4f3d4191abe510c51bc7f Mon Sep 17 00:00:00 2001 From: xgopilot Date: Thu, 16 Apr 2026 08:15:15 +0000 Subject: [PATCH] test: increase coverage for config and tui app Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: creatang <165447160+creatang@users.noreply.github.com> --- internal/config/envfile_test.go | 90 +++++++++++++++++ internal/config/loader_test.go | 96 ++++++++++++++++++ internal/tui/core/app/update_test.go | 101 +++++++++++++++++++ internal/tui/core/app/view_test.go | 143 +++++++++++++++++++++++++++ 4 files changed, 430 insertions(+) diff --git a/internal/config/envfile_test.go b/internal/config/envfile_test.go index 4c6110ea..9e0e2b9a 100644 --- a/internal/config/envfile_test.go +++ b/internal/config/envfile_test.go @@ -197,6 +197,96 @@ func TestEncodeEnvValue(t *testing.T) { } } +func TestEnvFilePathFallbackToDefaultBaseDir(t *testing.T) { + got := EnvFilePath(" ") + if got != filepath.Join(defaultBaseDir(), envFileName) { + t.Fatalf("EnvFilePath(blank) = %q", got) + } +} + +func TestPersistEnvVarErrorPaths(t *testing.T) { + t.Run("base dir is file", func(t *testing.T) { + tempRoot := t.TempDir() + fileBase := filepath.Join(tempRoot, "not-a-dir") + if err := os.WriteFile(fileBase, []byte("x"), 0o600); err != nil { + t.Fatalf("WriteFile() error = %v", err) + } + if err := PersistEnvVar(fileBase, "KEY", "value"); err == nil { + t.Fatal("expected mkdir error") + } + }) + + t.Run("read env file failed", func(t *testing.T) { + baseDir := t.TempDir() + envPath := EnvFilePath(baseDir) + if err := os.MkdirAll(envPath, 0o755); err != nil { + t.Fatalf("MkdirAll() error = %v", err) + } + if err := PersistEnvVar(baseDir, "KEY", "value"); err == nil { + t.Fatal("expected read error when env path is a directory") + } + }) + + t.Run("write env file failed", func(t *testing.T) { + baseDir := t.TempDir() + envPath := EnvFilePath(baseDir) + if err := os.WriteFile(envPath, []byte("KEY=old\n"), 0o400); err != nil { + t.Fatalf("WriteFile() error = %v", err) + } + if err := PersistEnvVar(baseDir, "KEY", "new"); err == nil { + t.Fatal("expected write error") + } + }) +} + +func TestLoadPersistedEnvErrorPaths(t *testing.T) { + t.Run("read env file failed", func(t *testing.T) { + baseDir := t.TempDir() + envPath := EnvFilePath(baseDir) + if err := os.MkdirAll(envPath, 0o755); err != nil { + t.Fatalf("MkdirAll() error = %v", err) + } + if err := LoadPersistedEnv(baseDir); err == nil { + t.Fatal("expected read error when env path is a directory") + } + }) + + t.Run("setenv failed on invalid key", func(t *testing.T) { + baseDir := t.TempDir() + envPath := EnvFilePath(baseDir) + if err := os.WriteFile(envPath, []byte("BAD\x00KEY=value\n"), 0o600); err != nil { + t.Fatalf("WriteFile() error = %v", err) + } + if err := LoadPersistedEnv(baseDir); err == nil { + t.Fatal("expected setenv error") + } + }) +} + +func TestRemovePersistedEnvVarErrorPaths(t *testing.T) { + t.Run("read env file failed", func(t *testing.T) { + baseDir := t.TempDir() + envPath := EnvFilePath(baseDir) + if err := os.MkdirAll(envPath, 0o755); err != nil { + t.Fatalf("MkdirAll() error = %v", err) + } + if err := RemovePersistedEnvVar(baseDir, "KEY"); err == nil { + t.Fatal("expected read error") + } + }) + + t.Run("write env file failed", func(t *testing.T) { + baseDir := t.TempDir() + envPath := EnvFilePath(baseDir) + if err := os.WriteFile(envPath, []byte("KEY=value\n"), 0o400); err != nil { + t.Fatalf("WriteFile() error = %v", err) + } + if err := RemovePersistedEnvVar(baseDir, "KEY"); err == nil { + t.Fatal("expected write error") + } + }) +} + func captureEnv(t *testing.T, key string) func() { t.Helper() value, exists := os.LookupEnv(key) diff --git a/internal/config/loader_test.go b/internal/config/loader_test.go index e5c0cca3..2cee0104 100644 --- a/internal/config/loader_test.go +++ b/internal/config/loader_test.go @@ -1042,6 +1042,102 @@ func TestDeleteCustomProviderRemovesProviderDir(t *testing.T) { } } +func TestLoadCustomProvidersReadDirAndStatErrors(t *testing.T) { + t.Run("providers dir read error", func(t *testing.T) { + baseDir := t.TempDir() + providersPath := filepath.Join(baseDir, providersDirName) + if err := os.WriteFile(providersPath, []byte("file"), 0o600); err != nil { + t.Fatalf("WriteFile() error = %v", err) + } + + if _, err := loadCustomProviders(baseDir); err == nil { + t.Fatal("expected read providers dir error") + } + }) + + t.Run("provider yaml stat error", func(t *testing.T) { + baseDir := t.TempDir() + providerDir := filepath.Join(baseDir, providersDirName, "blocked") + if err := os.MkdirAll(providerDir, 0o755); err != nil { + t.Fatalf("MkdirAll() error = %v", err) + } + if err := os.Chmod(providerDir, 0o000); err != nil { + t.Fatalf("Chmod() error = %v", err) + } + defer func() { _ = os.Chmod(providerDir, 0o755) }() + + if _, err := loadCustomProviders(baseDir); err == nil { + t.Fatal("expected stat error") + } + }) +} + +func TestLoadCustomProviderReadErrors(t *testing.T) { + t.Run("missing provider yaml", func(t *testing.T) { + providerDir := t.TempDir() + if _, err := loadCustomProvider(providerDir); err == nil { + t.Fatal("expected missing provider yaml error") + } + }) + + t.Run("provider yaml read error", func(t *testing.T) { + providerDir := t.TempDir() + providerPath := filepath.Join(providerDir, customProviderConfigName) + if err := os.MkdirAll(providerPath, 0o755); err != nil { + t.Fatalf("MkdirAll() error = %v", err) + } + if _, err := loadCustomProvider(providerDir); err == nil { + t.Fatal("expected provider yaml read error") + } + }) +} + +func TestSaveCustomProviderFileSystemErrors(t *testing.T) { + t.Run("mkdir provider dir failed", func(t *testing.T) { + root := t.TempDir() + baseDir := filepath.Join(root, "base-file") + if err := os.WriteFile(baseDir, []byte("x"), 0o600); err != nil { + t.Fatalf("WriteFile() error = %v", err) + } + + err := SaveCustomProvider( + baseDir, + "team-gateway", + provider.DriverOpenAICompat, + "https://llm.example.com/v1", + "TEAM_GATEWAY_API_KEY", + provider.OpenAICompatibleAPIStyleChatCompletions, + "", + "", + ) + if err == nil { + t.Fatal("expected create provider dir error") + } + }) + + t.Run("write provider yaml failed", func(t *testing.T) { + baseDir := t.TempDir() + providerDir := filepath.Join(baseDir, providersDirName, "team-gateway") + if err := os.MkdirAll(filepath.Join(providerDir, customProviderConfigName), 0o755); err != nil { + t.Fatalf("MkdirAll() error = %v", err) + } + + err := SaveCustomProvider( + baseDir, + "team-gateway", + provider.DriverOpenAICompat, + "https://llm.example.com/v1", + "TEAM_GATEWAY_API_KEY", + provider.OpenAICompatibleAPIStyleChatCompletions, + "", + "", + ) + if err == nil { + t.Fatal("expected write provider error") + } + }) +} + func TestLoaderLoadsUnknownCustomProviderDriverUsingTopLevelBaseURL(t *testing.T) { t.Parallel() diff --git a/internal/tui/core/app/update_test.go b/internal/tui/core/app/update_test.go index 9c07856f..36571bbf 100644 --- a/internal/tui/core/app/update_test.go +++ b/internal/tui/core/app/update_test.go @@ -2581,6 +2581,107 @@ func TestUpdateLocalAndWorkspaceCommandResultBranches(t *testing.T) { } } +func TestUpdateCompactFinishedAndRefreshMessagesError(t *testing.T) { + app, runtime := newTestApp(t) + app.state.ActiveSessionID = "session-error" + runtime.loadSessionErr = errors.New("load session failed") + + model, _ := app.Update(compactFinishedMsg{Err: errors.New("compact failed")}) + app = model.(App) + if app.state.IsCompacting { + t.Fatalf("expected compacting state to be cleared") + } + if app.state.ExecutionError != "load session failed" { + t.Fatalf("expected refresh message error to win, got %q", app.state.ExecutionError) + } + if len(app.activeMessages) == 0 || app.activeMessages[len(app.activeMessages)-1].Role != roleError { + t.Fatalf("expected inline error message appended") + } +} + +func TestUpdateLocalCommandProviderChangedRefreshErrors(t *testing.T) { + app, _ := newTestApp(t) + app.providerSvc = errorProviderService{err: errors.New("refresh providers failed")} + + model, _ := app.Update(localCommandResultMsg{ + Notice: "ok", + ProviderChanged: true, + }) + app = model.(App) + if app.state.ExecutionError != "refresh providers failed" { + t.Fatalf("expected provider refresh error, got %q", app.state.ExecutionError) + } + if len(app.activities) == 0 { + t.Fatalf("expected failure activity") + } +} + +func TestUpdateKeyToggleQuitCancelAndPickerClose(t *testing.T) { + app, runtime := newTestApp(t) + + model, _ := app.Update(tea.KeyMsg{Type: tea.KeyCtrlQ}) + app = model.(App) + if !app.state.ShowHelp { + t.Fatalf("expected help to toggle on") + } + + app.state.IsAgentRunning = true + model, _ = app.Update(tea.KeyMsg{Type: tea.KeyCtrlW}) + app = model.(App) + if !runtime.cancelInvoked { + t.Fatalf("expected cancel to be invoked") + } + if app.state.StatusText != statusCanceling { + t.Fatalf("expected canceling status, got %q", app.state.StatusText) + } + + app.openHelpPicker() + model, cmd := app.Update(tea.KeyMsg{Type: tea.KeyEsc}) + app = model.(App) + if cmd != nil { + t.Fatalf("expected nil cmd when closing active picker") + } + if app.state.ActivePicker != pickerNone { + t.Fatalf("expected picker to close on focus input key") + } + + model, cmd = app.Update(tea.KeyMsg{Type: tea.KeyCtrlU}) + if model == nil || cmd == nil { + t.Fatalf("expected quit command") + } +} + +func TestUpdatePickerEnterInvalidSelectionsAndSessionActivationError(t *testing.T) { + app, _ := newTestApp(t) + + app.providerPicker.SetItems([]list.Item{sessionItem{Summary: agentsession.Summary{ID: "s1"}}}) + app.openPicker(pickerProvider, statusChooseProvider, &app.providerPicker, "") + model, cmd := app.updatePicker(tea.KeyMsg{Type: tea.KeyEnter}) + app = model.(App) + if cmd != nil { + t.Fatalf("expected nil cmd when provider picker item type is invalid") + } + + app.modelPicker.SetItems([]list.Item{sessionItem{Summary: agentsession.Summary{ID: "s1"}}}) + app.openPicker(pickerModel, statusChooseModel, &app.modelPicker, "") + model, cmd = app.updatePicker(tea.KeyMsg{Type: tea.KeyEnter}) + app = model.(App) + if cmd != nil { + t.Fatalf("expected nil cmd when model picker item type is invalid") + } + + app.sessionPicker.SetItems([]list.Item{sessionItem{Summary: agentsession.Summary{ID: "missing", Title: "missing"}}}) + app.openPicker(pickerSession, statusChooseSession, &app.sessionPicker, "") + model, cmd = app.updatePicker(tea.KeyMsg{Type: tea.KeyEnter}) + app = model.(App) + if cmd != nil { + t.Fatalf("expected nil cmd for session picker enter") + } + if app.state.ExecutionError == "" { + t.Fatalf("expected session activation error to be recorded") + } +} + func TestUpdateInputPanelSlashAndWorkspaceBranches(t *testing.T) { app, _ := newTestApp(t) diff --git a/internal/tui/core/app/view_test.go b/internal/tui/core/app/view_test.go index 6f291439..b5b64129 100644 --- a/internal/tui/core/app/view_test.go +++ b/internal/tui/core/app/view_test.go @@ -1,6 +1,7 @@ package tui import ( + "errors" "strings" "testing" "time" @@ -8,11 +9,20 @@ import ( "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/lipgloss" + "neo-code/internal/provider" providertypes "neo-code/internal/provider/types" agentsession "neo-code/internal/session" tuistate "neo-code/internal/tui/state" ) +type stubMarkdownRenderer struct { + render func(content string, width int) (string, error) +} + +func (s stubMarkdownRenderer) Render(content string, width int) (string, error) { + return s.render(content, width) +} + func TestRenderPickerHelpMode(t *testing.T) { app, _ := newTestApp(t) app.refreshHelpPicker() @@ -65,6 +75,13 @@ func TestRenderPickerProviderAndFileMode(t *testing.T) { if !strings.Contains(fileView, filePickerTitle) { t.Fatalf("expected file picker title") } + + app.startProviderAddForm() + app.state.ActivePicker = pickerProviderAdd + providerAddView := app.renderPicker(48, 14) + if !strings.Contains(providerAddView, providerAddTitle) { + t.Fatalf("expected provider add title") + } } func TestBuildPickerLayoutExpandsPopupSpace(t *testing.T) { @@ -325,6 +342,33 @@ func TestViewNormalIncludesHeaderAndBody(t *testing.T) { } } +func TestViewAddsSpacerWhenDocIsTallerThanContent(t *testing.T) { + app, _ := newTestApp(t) + app.width = 100 + app.height = 60 + + view := app.View() + if strings.TrimSpace(view) == "" { + t.Fatalf("expected non-empty view") + } +} + +func TestRenderHeaderFallbackAndTrim(t *testing.T) { + app, _ := newTestApp(t) + app.state.IsAgentRunning = true + app.state.StatusText = "custom-running-status" + header := app.renderHeader(20) + if strings.TrimSpace(header) == "" { + t.Fatalf("expected non-empty header") + } + + app.state.CurrentModel = strings.Repeat("very-long-model-name-", 4) + header = app.renderHeader(16) + if strings.TrimSpace(header) == "" { + t.Fatalf("expected trimmed header output") + } +} + func TestRenderPanelAndActivityPreview(t *testing.T) { app, _ := newTestApp(t) panel := app.renderPanel("Title", "Sub", "Body", 60, 8, true) @@ -340,6 +384,15 @@ func TestRenderPanelAndActivityPreview(t *testing.T) { if !strings.Contains(withActivity, activityTitle) { t.Fatalf("expected activity panel title, got %q", withActivity) } + + app.commandMenu.SetItems([]list.Item{ + commandMenuItem{title: "/help", description: "show help"}, + }) + app.commandMenuMeta = tuistate.CommandMenuMeta{Title: commandMenuTitle} + withMenu := app.renderWaterfall(80, 24) + if !strings.Contains(withMenu, commandMenuTitle) { + t.Fatalf("expected command menu to be rendered") + } } func TestRenderMessageContentWithCopyBranches(t *testing.T) { @@ -363,6 +416,96 @@ func TestRenderMessageContentWithCopyBranches(t *testing.T) { if bindings[0].ID != 3 || !strings.Contains(bindings[0].Code, "fmt.Println") { t.Fatalf("unexpected binding: %+v", bindings[0]) } + + app, _ = newTestApp(t) + app.markdownRenderer = stubMarkdownRenderer{ + render: func(content string, width int) (string, error) { + return "", errors.New("render failed") + }, + } + rendered, bindings = app.renderMessageContentWithCopy("plain text", 60, app.styles.messageBody, 1) + if len(bindings) != 0 || strings.TrimSpace(rendered) == "" { + t.Fatalf("expected empty message fallback when markdown render fails") + } +} + +func TestRenderMessageContentWithCopyCodeFallbackAndEmptySegments(t *testing.T) { + app, _ := newTestApp(t) + app.markdownRenderer = stubMarkdownRenderer{ + render: func(content string, width int) (string, error) { + if strings.HasPrefix(strings.TrimSpace(content), "```") { + return "", errors.New("code render failed") + } + return "ok", nil + }, + } + content := " \n```go\nfmt.Println(\"x\")\n```\n" + rendered, bindings := app.renderMessageContentWithCopy(content, 60, app.styles.messageBody, 7) + if strings.TrimSpace(rendered) == "" { + t.Fatalf("expected rendered output") + } + if len(bindings) != 1 || bindings[0].ID != 7 { + t.Fatalf("expected one binding with id 7, got %+v", bindings) + } +} + +func TestRenderMessageBlockWithCopyExtraBranches(t *testing.T) { + app, _ := newTestApp(t) + + eventBlock, _ := app.renderMessageBlockWithCopy(providertypes.Message{Role: roleEvent, Content: "event"}, 50, 1) + if !strings.Contains(eventBlock, "event") { + t.Fatalf("expected event block") + } + + toolBlock, bindings := app.renderMessageBlockWithCopy(providertypes.Message{Role: roleTool, Content: "tool"}, 50, 1) + if toolBlock != "" || bindings != nil { + t.Fatalf("expected tool role to be skipped") + } + + assistantBlock, _ := app.renderMessageBlockWithCopy(providertypes.Message{ + Role: roleAssistant, + ToolCalls: []providertypes.ToolCall{ + {Name: "bash"}, + }, + }, 50, 1) + assistantPlain := copyCodeANSIPattern.ReplaceAllString(assistantBlock, "") + if !strings.Contains(assistantPlain, "bash") { + t.Fatalf("expected tool calls summary in assistant block") + } + + userBlock, _ := app.renderMessageBlockWithCopy(providertypes.Message{ + Role: roleUser, + Content: "hello", + }, 10, 1) + if strings.TrimSpace(userBlock) == "" { + t.Fatalf("expected user message block") + } +} + +func TestRenderProviderAddFormNoFormAndGeminiField(t *testing.T) { + app, _ := newTestApp(t) + if got := app.renderProviderAddForm(); got != "No form active" { + t.Fatalf("unexpected no-form output: %q", got) + } + + app.startProviderAddForm() + app.providerAddForm.Driver = provider.DriverGemini + app.providerAddForm.DeploymentMode = "vertex" + form := app.renderProviderAddForm() + if !strings.Contains(form, "Deployment Mode") { + t.Fatalf("expected deployment mode field for gemini") + } +} + +func TestRenderCommandMenuEmptyBody(t *testing.T) { + app, _ := newTestApp(t) + app.commandMenu.SetItems([]list.Item{ + commandMenuItem{title: "/help", description: "show help"}, + }) + app.state.ActivePicker = pickerHelp + if got := app.renderCommandMenu(50); got != "" { + t.Fatalf("expected empty menu while picker is active") + } } func TestNormalizeAndTrimHelpers(t *testing.T) {