Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
kardolus
GitHub Repository: kardolus/chatgpt-cli
Path: blob/main/config/manager_test.go
2649 views
1
package config_test
2
3
import (
4
"errors"
5
"github.com/kardolus/chatgpt-cli/config"
6
"os"
7
"strings"
8
"testing"
9
10
"github.com/golang/mock/gomock"
11
. "github.com/onsi/gomega"
12
"github.com/sclevine/spec"
13
"github.com/sclevine/spec/report"
14
)
15
16
//go:generate mockgen -destination=configmocks_test.go -package=config_test github.com/kardolus/chatgpt-cli/config Store
17
18
func TestUnitConfigManager(t *testing.T) {
19
spec.Run(t, "Config Manager", testConfig, spec.Report(report.Terminal{}))
20
}
21
22
func testConfig(t *testing.T, when spec.G, it spec.S) {
23
const (
24
defaultMaxTokens = 10
25
defaultContextWindow = 20
26
defaultName = "default-name"
27
defaultURL = "default-url"
28
defaultModel = "default-model"
29
defaultRole = "default-role"
30
defaultApiKey = "default-api-key"
31
defaultThread = "default-thread"
32
defaultCompletionsPath = "default-completions-path"
33
defaultModelsPath = "default-models-path"
34
defaultAuthHeader = "default-auth-header"
35
defaultAuthTokenPrefix = "default-auth-token-prefix"
36
defaultOmitHistory = false
37
defaultAutoCreateNewThread = false
38
defaultTrackTokenUsage = false
39
defaultSkipTLSVerify = false
40
defaultTemperature = 1.1
41
defaultTopP = 2.2
42
defaultFrequencyPenalty = 3.3
43
defaultPresencePenalty = 4.4
44
defaultCommandPrompt = "default-command-prompt"
45
defaultOutputPrompt = "default-output-prompt"
46
)
47
48
var (
49
mockCtrl *gomock.Controller
50
mockConfigStore *MockStore
51
defaultConfig config.Config
52
envPrefix string
53
)
54
55
it.Before(func() {
56
RegisterTestingT(t)
57
mockCtrl = gomock.NewController(t)
58
mockConfigStore = NewMockStore(mockCtrl)
59
60
defaultConfig = config.Config{
61
Name: defaultName,
62
APIKey: defaultApiKey,
63
Model: defaultModel,
64
MaxTokens: defaultMaxTokens,
65
ContextWindow: defaultContextWindow,
66
URL: defaultURL,
67
CompletionsPath: defaultCompletionsPath,
68
ModelsPath: defaultModelsPath,
69
AuthHeader: defaultAuthHeader,
70
AuthTokenPrefix: defaultAuthTokenPrefix,
71
OmitHistory: defaultOmitHistory,
72
Role: defaultRole,
73
Thread: defaultThread,
74
Temperature: defaultTemperature,
75
TopP: defaultTopP,
76
FrequencyPenalty: defaultFrequencyPenalty,
77
PresencePenalty: defaultPresencePenalty,
78
CommandPrompt: defaultCommandPrompt,
79
OutputPrompt: defaultOutputPrompt,
80
AutoCreateNewThread: defaultAutoCreateNewThread,
81
TrackTokenUsage: defaultTrackTokenUsage,
82
SkipTLSVerify: defaultSkipTLSVerify,
83
}
84
85
envPrefix = strings.ToUpper(defaultConfig.Name) + "_"
86
87
unsetEnvironmentVariables(envPrefix)
88
})
89
90
it.After(func() {
91
mockCtrl.Finish()
92
})
93
94
it("should use default values when no environment variables or user config are provided", func() {
95
mockConfigStore.EXPECT().ReadDefaults().Return(defaultConfig).Times(1)
96
mockConfigStore.EXPECT().Read().Return(config.Config{}, errors.New("no user config")).Times(1)
97
98
subject := config.NewManager(mockConfigStore).WithEnvironment()
99
100
Expect(subject.Config.MaxTokens).To(Equal(defaultMaxTokens))
101
Expect(subject.Config.ContextWindow).To(Equal(defaultContextWindow))
102
Expect(subject.Config.Name).To(Equal(defaultName))
103
Expect(subject.Config.Model).To(Equal(defaultModel))
104
Expect(subject.Config.URL).To(Equal(defaultURL))
105
Expect(subject.Config.CompletionsPath).To(Equal(defaultCompletionsPath))
106
Expect(subject.Config.ModelsPath).To(Equal(defaultModelsPath))
107
Expect(subject.Config.AuthHeader).To(Equal(defaultAuthHeader))
108
Expect(subject.Config.AuthTokenPrefix).To(Equal(defaultAuthTokenPrefix))
109
Expect(subject.Config.OmitHistory).To(Equal(defaultOmitHistory))
110
Expect(subject.Config.Role).To(Equal(defaultRole))
111
Expect(subject.Config.Thread).To(Equal(defaultThread))
112
Expect(subject.Config.Temperature).To(Equal(defaultTemperature))
113
Expect(subject.Config.TopP).To(Equal(defaultTopP))
114
Expect(subject.Config.FrequencyPenalty).To(Equal(defaultFrequencyPenalty))
115
Expect(subject.Config.PresencePenalty).To(Equal(defaultPresencePenalty))
116
Expect(subject.Config.CommandPrompt).To(Equal(defaultCommandPrompt))
117
Expect(subject.Config.OutputPrompt).To(Equal(defaultOutputPrompt))
118
Expect(subject.Config.AutoCreateNewThread).To(Equal(defaultAutoCreateNewThread))
119
Expect(subject.Config.TrackTokenUsage).To(Equal(defaultTrackTokenUsage))
120
Expect(subject.Config.SkipTLSVerify).To(Equal(defaultSkipTLSVerify))
121
})
122
123
it("should prioritize user-provided config over defaults", func() {
124
userConfig := config.Config{
125
Model: "user-model",
126
MaxTokens: 20,
127
ContextWindow: 30,
128
URL: "user-url",
129
CompletionsPath: "user-completions-path",
130
ModelsPath: "user-models-path",
131
AuthHeader: "user-auth-header",
132
AuthTokenPrefix: "user-auth-token-prefix",
133
OmitHistory: true,
134
Role: "user-role",
135
Thread: "user-thread",
136
Temperature: 2.5,
137
TopP: 3.5,
138
FrequencyPenalty: 4.5,
139
PresencePenalty: 5.5,
140
CommandPrompt: "user-command-prompt",
141
OutputPrompt: "user-output-prompt",
142
AutoCreateNewThread: true,
143
TrackTokenUsage: true,
144
SkipTLSVerify: true,
145
}
146
147
mockConfigStore.EXPECT().ReadDefaults().Return(defaultConfig).Times(1)
148
mockConfigStore.EXPECT().Read().Return(userConfig, nil).Times(1)
149
150
subject := config.NewManager(mockConfigStore).WithEnvironment()
151
152
Expect(subject.Config.Model).To(Equal("user-model"))
153
Expect(subject.Config.MaxTokens).To(Equal(20))
154
Expect(subject.Config.ContextWindow).To(Equal(30))
155
Expect(subject.Config.URL).To(Equal("user-url"))
156
Expect(subject.Config.CompletionsPath).To(Equal("user-completions-path"))
157
Expect(subject.Config.ModelsPath).To(Equal("user-models-path"))
158
Expect(subject.Config.AuthHeader).To(Equal("user-auth-header"))
159
Expect(subject.Config.AuthTokenPrefix).To(Equal("user-auth-token-prefix"))
160
Expect(subject.Config.OmitHistory).To(BeTrue())
161
Expect(subject.Config.AutoCreateNewThread).To(BeTrue())
162
Expect(subject.Config.TrackTokenUsage).To(BeTrue())
163
Expect(subject.Config.SkipTLSVerify).To(BeTrue())
164
Expect(subject.Config.Role).To(Equal("user-role"))
165
Expect(subject.Config.Thread).To(Equal("user-thread"))
166
Expect(subject.Config.Temperature).To(Equal(2.5))
167
Expect(subject.Config.TopP).To(Equal(3.5))
168
Expect(subject.Config.FrequencyPenalty).To(Equal(4.5))
169
Expect(subject.Config.PresencePenalty).To(Equal(5.5))
170
Expect(subject.Config.CommandPrompt).To(Equal("user-command-prompt"))
171
Expect(subject.Config.OutputPrompt).To(Equal("user-output-prompt"))
172
})
173
174
it("should prioritize environment variables over default config", func() {
175
Expect(os.Setenv(envPrefix+"API_KEY", "env-api-key")).To(Succeed())
176
Expect(os.Setenv(envPrefix+"MODEL", "env-model")).To(Succeed())
177
Expect(os.Setenv(envPrefix+"MAX_TOKENS", "15")).To(Succeed())
178
Expect(os.Setenv(envPrefix+"CONTEXT_WINDOW", "25")).To(Succeed())
179
Expect(os.Setenv(envPrefix+"URL", "env-url")).To(Succeed())
180
Expect(os.Setenv(envPrefix+"COMPLETIONS_PATH", "env-completions-path")).To(Succeed())
181
Expect(os.Setenv(envPrefix+"MODELS_PATH", "env-models-path")).To(Succeed())
182
Expect(os.Setenv(envPrefix+"AUTH_HEADER", "env-auth-header")).To(Succeed())
183
Expect(os.Setenv(envPrefix+"AUTH_TOKEN_PREFIX", "env-auth-token-prefix")).To(Succeed())
184
Expect(os.Setenv(envPrefix+"OMIT_HISTORY", "true")).To(Succeed())
185
Expect(os.Setenv(envPrefix+"AUTO_CREATE_NEW_THREAD", "true")).To(Succeed())
186
Expect(os.Setenv(envPrefix+"TRACK_TOKEN_USAGE", "true")).To(Succeed())
187
Expect(os.Setenv(envPrefix+"DEBUG", "true")).To(Succeed())
188
Expect(os.Setenv(envPrefix+"SKIP_TLS_VERIFY", "true")).To(Succeed())
189
Expect(os.Setenv(envPrefix+"ROLE", "env-role")).To(Succeed())
190
Expect(os.Setenv(envPrefix+"THREAD", "env-thread")).To(Succeed())
191
Expect(os.Setenv(envPrefix+"TEMPERATURE", "2.2")).To(Succeed())
192
Expect(os.Setenv(envPrefix+"TOP_P", "3.3")).To(Succeed())
193
Expect(os.Setenv(envPrefix+"FREQUENCY_PENALTY", "4.4")).To(Succeed())
194
Expect(os.Setenv(envPrefix+"PRESENCE_PENALTY", "5.5")).To(Succeed())
195
Expect(os.Setenv(envPrefix+"COMMAND_PROMPT", "env-command-prompt")).To(Succeed())
196
Expect(os.Setenv(envPrefix+"OUTPUT_PROMPT", "env-output-prompt")).To(Succeed())
197
198
mockConfigStore.EXPECT().ReadDefaults().Return(defaultConfig).Times(1)
199
mockConfigStore.EXPECT().Read().Return(config.Config{}, errors.New("config error")).Times(1)
200
201
subject := config.NewManager(mockConfigStore).WithEnvironment()
202
203
Expect(subject.Config.APIKey).To(Equal("env-api-key"))
204
Expect(subject.Config.Model).To(Equal("env-model"))
205
Expect(subject.Config.MaxTokens).To(Equal(15))
206
Expect(subject.Config.ContextWindow).To(Equal(25))
207
Expect(subject.Config.URL).To(Equal("env-url"))
208
Expect(subject.Config.CompletionsPath).To(Equal("env-completions-path"))
209
Expect(subject.Config.ModelsPath).To(Equal("env-models-path"))
210
Expect(subject.Config.AuthHeader).To(Equal("env-auth-header"))
211
Expect(subject.Config.AuthTokenPrefix).To(Equal("env-auth-token-prefix"))
212
Expect(subject.Config.OmitHistory).To(BeTrue())
213
Expect(subject.Config.AutoCreateNewThread).To(BeTrue())
214
Expect(subject.Config.TrackTokenUsage).To(BeTrue())
215
Expect(subject.Config.SkipTLSVerify).To(BeTrue())
216
Expect(subject.Config.Role).To(Equal("env-role"))
217
Expect(subject.Config.Thread).To(Equal("env-thread"))
218
Expect(subject.Config.Temperature).To(Equal(2.2))
219
Expect(subject.Config.TopP).To(Equal(3.3))
220
Expect(subject.Config.FrequencyPenalty).To(Equal(4.4))
221
Expect(subject.Config.PresencePenalty).To(Equal(5.5))
222
Expect(subject.Config.CommandPrompt).To(Equal("env-command-prompt"))
223
Expect(subject.Config.OutputPrompt).To(Equal("env-output-prompt"))
224
})
225
226
it("should prioritize environment variables over user-provided config", func() {
227
Expect(os.Setenv(envPrefix+"API_KEY", "env-api-key")).To(Succeed())
228
Expect(os.Setenv(envPrefix+"MODEL", "env-model")).To(Succeed())
229
Expect(os.Setenv(envPrefix+"MAX_TOKENS", "15")).To(Succeed())
230
Expect(os.Setenv(envPrefix+"CONTEXT_WINDOW", "25")).To(Succeed())
231
Expect(os.Setenv(envPrefix+"URL", "env-url")).To(Succeed())
232
Expect(os.Setenv(envPrefix+"COMPLETIONS_PATH", "env-completions-path")).To(Succeed())
233
Expect(os.Setenv(envPrefix+"MODELS_PATH", "env-models-path")).To(Succeed())
234
Expect(os.Setenv(envPrefix+"AUTH_HEADER", "env-auth-header")).To(Succeed())
235
Expect(os.Setenv(envPrefix+"AUTH_TOKEN_PREFIX", "env-auth-token-prefix")).To(Succeed())
236
Expect(os.Setenv(envPrefix+"OMIT_HISTORY", "true")).To(Succeed())
237
Expect(os.Setenv(envPrefix+"AUTO_CREATE_NEW_THREAD", "true")).To(Succeed())
238
Expect(os.Setenv(envPrefix+"TRACK_TOKEN_USAGE", "true")).To(Succeed())
239
Expect(os.Setenv(envPrefix+"SKIP_TLS_VERIFY", "true")).To(Succeed())
240
Expect(os.Setenv(envPrefix+"DEBUG", "false")).To(Succeed())
241
Expect(os.Setenv(envPrefix+"ROLE", "env-role")).To(Succeed())
242
Expect(os.Setenv(envPrefix+"THREAD", "env-thread")).To(Succeed())
243
Expect(os.Setenv(envPrefix+"TEMPERATURE", "2.2")).To(Succeed())
244
Expect(os.Setenv(envPrefix+"TOP_P", "3.3")).To(Succeed())
245
Expect(os.Setenv(envPrefix+"FREQUENCY_PENALTY", "4.4")).To(Succeed())
246
Expect(os.Setenv(envPrefix+"PRESENCE_PENALTY", "5.5")).To(Succeed())
247
Expect(os.Setenv(envPrefix+"COMMAND_PROMPT", "env-command-prompt")).To(Succeed())
248
Expect(os.Setenv(envPrefix+"OUTPUT_PROMPT", "env-output-prompt")).To(Succeed())
249
250
userConfig := config.Config{
251
APIKey: "user-api-key",
252
Model: "user-model",
253
MaxTokens: 20,
254
ContextWindow: 30,
255
URL: "user-url",
256
CompletionsPath: "user-completions-path",
257
ModelsPath: "user-models-path",
258
AuthHeader: "user-auth-header",
259
AuthTokenPrefix: "user-auth-token-prefix",
260
OmitHistory: false,
261
AutoCreateNewThread: false,
262
TrackTokenUsage: false,
263
SkipTLSVerify: false,
264
Role: "user-role",
265
Thread: "user-thread",
266
Temperature: 1.5,
267
TopP: 2.5,
268
FrequencyPenalty: 3.5,
269
PresencePenalty: 4.5,
270
CommandPrompt: "user-command-prompt",
271
OutputPrompt: "user-output-prompt",
272
}
273
274
mockConfigStore.EXPECT().ReadDefaults().Return(defaultConfig).Times(1)
275
mockConfigStore.EXPECT().Read().Return(userConfig, nil).Times(1)
276
277
subject := config.NewManager(mockConfigStore).WithEnvironment()
278
279
Expect(subject.Config.APIKey).To(Equal("env-api-key"))
280
Expect(subject.Config.Model).To(Equal("env-model"))
281
Expect(subject.Config.MaxTokens).To(Equal(15))
282
Expect(subject.Config.ContextWindow).To(Equal(25))
283
Expect(subject.Config.URL).To(Equal("env-url"))
284
Expect(subject.Config.CompletionsPath).To(Equal("env-completions-path"))
285
Expect(subject.Config.ModelsPath).To(Equal("env-models-path"))
286
Expect(subject.Config.AuthHeader).To(Equal("env-auth-header"))
287
Expect(subject.Config.AuthTokenPrefix).To(Equal("env-auth-token-prefix"))
288
Expect(subject.Config.OmitHistory).To(BeTrue())
289
Expect(subject.Config.AutoCreateNewThread).To(BeTrue())
290
Expect(subject.Config.TrackTokenUsage).To(BeTrue())
291
Expect(subject.Config.SkipTLSVerify).To(BeTrue())
292
Expect(subject.Config.Role).To(Equal("env-role"))
293
Expect(subject.Config.Thread).To(Equal("env-thread"))
294
Expect(subject.Config.Temperature).To(Equal(2.2))
295
Expect(subject.Config.TopP).To(Equal(3.3))
296
Expect(subject.Config.FrequencyPenalty).To(Equal(4.4))
297
Expect(subject.Config.PresencePenalty).To(Equal(5.5))
298
Expect(subject.Config.CommandPrompt).To(Equal("env-command-prompt"))
299
Expect(subject.Config.OutputPrompt).To(Equal("env-output-prompt"))
300
})
301
302
when("DeleteThread()", func() {
303
var subject *config.Manager
304
305
threadName := "non-active-thread"
306
307
it.Before(func() {
308
userConfig := config.Config{Thread: threadName}
309
310
mockConfigStore.EXPECT().ReadDefaults().Return(defaultConfig).Times(1)
311
mockConfigStore.EXPECT().Read().Return(userConfig, nil).Times(1)
312
313
subject = config.NewManager(mockConfigStore).WithEnvironment()
314
})
315
316
it("propagates the error from the config store", func() {
317
expectedMsg := "expected-error"
318
319
mockConfigStore.EXPECT().Delete(threadName).Return(errors.New(expectedMsg)).Times(1)
320
321
err := subject.DeleteThread(threadName)
322
323
Expect(err).To(HaveOccurred())
324
Expect(err).To(MatchError(expectedMsg))
325
})
326
it("completes successfully the config store throws no error", func() {
327
mockConfigStore.EXPECT().Delete(threadName).Return(nil).Times(1)
328
329
err := subject.DeleteThread(threadName)
330
331
Expect(err).NotTo(HaveOccurred())
332
})
333
})
334
335
when("ListThreads()", func() {
336
activeThread := "active-thread"
337
338
it.Before(func() {
339
userConfig := config.Config{Thread: activeThread}
340
341
mockConfigStore.EXPECT().ReadDefaults().Return(defaultConfig).Times(1)
342
mockConfigStore.EXPECT().Read().Return(userConfig, nil).Times(1)
343
})
344
345
it("throws an error when the List call fails", func() {
346
subject := config.NewManager(mockConfigStore).WithEnvironment()
347
348
errorInstance := errors.New("an error occurred")
349
mockConfigStore.EXPECT().List().Return(nil, errorInstance).Times(1)
350
351
_, err := subject.ListThreads()
352
Expect(err).To(HaveOccurred())
353
Expect(err).To(MatchError(errorInstance))
354
})
355
356
it("returns the expected threads", func() {
357
subject := config.NewManager(mockConfigStore).WithEnvironment()
358
359
threads := []string{"thread1.json", "thread2.json", activeThread + ".json"}
360
mockConfigStore.EXPECT().List().Return(threads, nil).Times(1)
361
362
result, err := subject.ListThreads()
363
Expect(err).NotTo(HaveOccurred())
364
Expect(result).To(HaveLen(3))
365
Expect(result[0]).NotTo(ContainSubstring("current"))
366
Expect(result[0]).NotTo(ContainSubstring("json"))
367
Expect(result[1]).NotTo(ContainSubstring("current"))
368
Expect(result[1]).NotTo(ContainSubstring("json"))
369
Expect(result[2]).To(ContainSubstring("current"))
370
Expect(result[2]).NotTo(ContainSubstring("json"))
371
})
372
})
373
374
when("ShowConfig()", func() {
375
it("returns the expected config", func() {
376
mockConfigStore.EXPECT().ReadDefaults().Return(defaultConfig).Times(1)
377
mockConfigStore.EXPECT().Read().Return(config.Config{
378
APIKey: "user-api-key",
379
}, nil).Times(1)
380
381
subject := config.NewManager(mockConfigStore).WithEnvironment()
382
383
result, err := subject.ShowConfig()
384
Expect(err).NotTo(HaveOccurred())
385
Expect(result).To(ContainSubstring("user-api-key"))
386
})
387
})
388
}
389
390
func unsetEnvironmentVariables(envPrefix string) {
391
variables := []string{
392
"API_KEY",
393
"MODEL",
394
"MAX_TOKENS",
395
"CONTEXT_WINDOW",
396
"URL",
397
"COMPLETIONS_PATH",
398
"MODELS_PATH",
399
"AUTH_HEADER",
400
"AUTH_TOKEN_PREFIX",
401
"OMIT_HISTORY",
402
"ROLE",
403
"THREAD",
404
"TEMPERATURE",
405
"TOP_P",
406
"FREQUENCY_PENALTY",
407
"PRESENCE_PENALTY",
408
"COMMAND_PROMPT",
409
"OUTPUT_PROMPT",
410
"AUTO_CREATE_NEW_THREAD",
411
"TRACK_TOKEN_USAGE",
412
"DEBUG",
413
"SKIP_TLS_VERIFY",
414
}
415
416
for _, variable := range variables {
417
Expect(os.Unsetenv(envPrefix + variable)).To(Succeed())
418
}
419
}
420
421