summaryrefslogtreecommitdiff
path: root/internal/lsp/chat_commands_test.go
blob: 0e31c7b9350ef4103cad0127a164566f5b759a54 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package lsp

import (
	"bytes"
	"log"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"codeberg.org/snonux/hexai/internal/appconfig"
	"codeberg.org/snonux/hexai/internal/runtimeconfig"
)

func TestFormatReloadSummary(t *testing.T) {
	changes := []runtimeconfig.Change{
		{Key: "max_tokens", Old: "200", New: "128"},
		{Key: "provider", Old: "openai", New: "ollama"},
	}
	got := runtimeconfig.FormatSummary("Reloaded config", changes)
	if !strings.Contains(got, "Reloaded config (2 changes):") {
		t.Fatalf("expected change count line, got %q", got)
	}
	if !strings.Contains(got, "max_tokens: 200") || !strings.Contains(got, "provider: openai") {
		t.Fatalf("expected formatted entries, got %q", got)
	}
}

func TestHandleHelpCommandListsReload(t *testing.T) {
	s := newTestServer()
	res := s.handleHelpCommand()
	if !strings.Contains(res.message, "/reload?>") {
		t.Fatalf("expected reload command in help output: %q", res.message)
	}
	if !strings.Contains(res.message, "/disable?>") || !strings.Contains(res.message, "/enable?>") {
		t.Fatalf("expected completion toggle commands in help output: %q", res.message)
	}
}

func TestHandleReloadCommandReloadsStore(t *testing.T) {
	tmp := t.TempDir()
	configDir := filepath.Join(tmp, "hexai")
	if err := os.MkdirAll(configDir, 0o755); err != nil {
		t.Fatalf("mkdir: %v", err)
	}
	configPath := filepath.Join(configDir, "config.toml")
	if err := os.WriteFile(configPath, []byte("[general]\nmax_tokens = 64\n"), 0o644); err != nil {
		t.Fatalf("write config: %v", err)
	}

	t.Setenv("XDG_CONFIG_HOME", tmp)
	t.Setenv("HEXAI_MAX_TOKENS", "321")

	var logBuf bytes.Buffer
	logger := log.New(&logBuf, "", 0)

	initial := appconfig.Load(logger)
	if initial.MaxTokens != 321 {
		t.Fatalf("expected env override to win initial load, got %d", initial.MaxTokens)
	}

	store := runtimeconfig.New(initial)

	s := newTestServer()
	s.logger = logger
	s.configStore = store

	if err := os.WriteFile(configPath, []byte("[general]\nmax_tokens = 128\n"), 0o644); err != nil {
		t.Fatalf("update config: %v", err)
	}

	res := s.handleReloadCommand()
	if !strings.Contains(res.message, "Reloaded config (1 changes):") {
		t.Fatalf("unexpected reload summary: %q", res.message)
	}
	if !strings.Contains(res.message, "max_tokens: 321") || !strings.Contains(res.message, "128") {
		t.Fatalf("expected diff for max_tokens: %q", res.message)
	}
	if store.Snapshot().MaxTokens != 128 {
		t.Fatalf("expected snapshot to reflect new value, got %d", store.Snapshot().MaxTokens)
	}
	if !strings.Contains(logBuf.String(), "Reloaded config") {
		t.Fatalf("expected summary logged, got %q", logBuf.String())
	}
}

func TestDetectAndHandleChatExecutesSlashCommand(t *testing.T) {
	tmp := t.TempDir()
	configDir := filepath.Join(tmp, "hexai")
	if err := os.MkdirAll(configDir, 0o755); err != nil {
		t.Fatalf("mkdir: %v", err)
	}
	configPath := filepath.Join(configDir, "config.toml")
	if err := os.WriteFile(configPath, []byte("[general]\nmax_tokens = 128\n"), 0o644); err != nil {
		t.Fatalf("write config: %v", err)
	}
	t.Setenv("XDG_CONFIG_HOME", tmp)
	t.Setenv("HEXAI_MAX_TOKENS", "")

	var logBuf bytes.Buffer
	logger := log.New(&logBuf, "", 0)

	initial := appconfig.Load(logger)
	store := runtimeconfig.New(initial)

	s := newTestServer()
	s.logger = logger
	s.configStore = store
	var out bytes.Buffer
	s.out = &out
	s.setCompletionsDisabled(true) // chat commands should remain available when completions are disabled

	uri := "file:///cmd.go"
	s.setDocument(uri, "/reload>\n")

	s.detectAndHandleChat(uri)

	outStr := out.String()
	if !strings.Contains(outStr, "Reloaded config") {
		t.Fatalf("expected reload summary in applyEdit payload, got %q", outStr)
	}
	if !strings.Contains(logBuf.String(), "Reloaded config") {
		t.Fatalf("expected reload summary logged, got %q", logBuf.String())
	}
}

func TestDisableEnableCommandsToggleCompletions(t *testing.T) {
	s := newTestServer()
	if s.completionDisabled() {
		t.Fatalf("expected completions enabled initially")
	}

	if res, ok := s.chatCommandResponse("file:///x", 0, "/disable>"); !ok {
		t.Fatalf("expected disable command to be handled")
	} else if !strings.Contains(res.message, "disabled") {
		t.Fatalf("unexpected disable message: %q", res.message)
	}
	if !s.completionDisabled() {
		t.Fatalf("expected completions disabled after command")
	}

	if res, ok := s.chatCommandResponse("file:///x", 0, "/disable>"); !ok {
		t.Fatalf("expected repeated disable command to be handled")
	} else if !strings.Contains(res.message, "already disabled") {
		t.Fatalf("expected already-disabled message, got %q", res.message)
	}

	if res, ok := s.chatCommandResponse("file:///x", 0, "/enable>"); !ok {
		t.Fatalf("expected enable command to be handled")
	} else if !strings.Contains(res.message, "enabled") {
		t.Fatalf("unexpected enable message: %q", res.message)
	}
	if s.completionDisabled() {
		t.Fatalf("expected completions enabled after command")
	}

	if res, ok := s.chatCommandResponse("file:///x", 0, "/enable>"); !ok {
		t.Fatalf("expected repeated enable command to be handled")
	} else if !strings.Contains(res.message, "already enabled") {
		t.Fatalf("expected already-enabled message, got %q", res.message)
	}
}