Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
kardolus
GitHub Repository: kardolus/chatgpt-cli
Path: blob/main/cmd/chatgpt/utils/utils_test.go
2649 views
1
package utils_test
2
3
import (
4
"fmt"
5
"github.com/kardolus/chatgpt-cli/cmd/chatgpt/utils"
6
"testing"
7
"time"
8
9
. "github.com/onsi/gomega"
10
"github.com/sclevine/spec"
11
"github.com/sclevine/spec/report"
12
)
13
14
func TestUnitUtils(t *testing.T) {
15
spec.Run(t, "Testing the Utils", testUtils, spec.Report(report.Terminal{}))
16
}
17
18
func testUtils(t *testing.T, when spec.G, it spec.S) {
19
it.Before(func() {
20
RegisterTestingT(t)
21
})
22
23
when("ColorToAnsi()", func() {
24
it("should return an empty color and reset if the input is an empty string", func() {
25
color, reset := utils.ColorToAnsi("")
26
Expect(color).To(Equal(""))
27
Expect(reset).To(Equal(""))
28
})
29
30
it("should return an empty color and reset if the input is an unsupported color", func() {
31
color, reset := utils.ColorToAnsi("unsupported")
32
Expect(color).To(Equal(""))
33
Expect(reset).To(Equal(""))
34
})
35
36
it("should return the correct ANSI code for red", func() {
37
color, reset := utils.ColorToAnsi("red")
38
Expect(color).To(Equal("\033[31m"))
39
Expect(reset).To(Equal("\033[0m"))
40
})
41
42
it("should return the correct ANSI code for green", func() {
43
color, reset := utils.ColorToAnsi("green")
44
Expect(color).To(Equal("\033[32m"))
45
Expect(reset).To(Equal("\033[0m"))
46
})
47
48
it("should return the correct ANSI code for yellow", func() {
49
color, reset := utils.ColorToAnsi("yellow")
50
Expect(color).To(Equal("\033[33m"))
51
Expect(reset).To(Equal("\033[0m"))
52
})
53
54
it("should return the correct ANSI code for blue", func() {
55
color, reset := utils.ColorToAnsi("blue")
56
Expect(color).To(Equal("\033[34m"))
57
Expect(reset).To(Equal("\033[0m"))
58
})
59
60
it("should return the correct ANSI code for magenta", func() {
61
color, reset := utils.ColorToAnsi("magenta")
62
Expect(color).To(Equal("\033[35m"))
63
Expect(reset).To(Equal("\033[0m"))
64
})
65
66
it("should handle case-insensitivity correctly", func() {
67
color, reset := utils.ColorToAnsi("ReD")
68
Expect(color).To(Equal("\033[31m"))
69
Expect(reset).To(Equal("\033[0m"))
70
})
71
72
it("should handle leading and trailing spaces", func() {
73
color, reset := utils.ColorToAnsi(" blue ")
74
Expect(color).To(Equal("\033[34m"))
75
Expect(reset).To(Equal("\033[0m"))
76
})
77
})
78
79
when("FormatPrompt()", func() {
80
const (
81
counter = 1
82
usage = 2
83
)
84
85
now := time.Now()
86
87
it("should add a trailing whitespace", func() {
88
input := "prompt"
89
expected := "prompt "
90
Expect(utils.FormatPrompt(input, counter, usage, now)).To(Equal(expected))
91
})
92
93
it("should handle empty input as expected", func() {
94
input := ""
95
expected := ""
96
Expect(utils.FormatPrompt(input, counter, usage, now)).To(Equal(expected))
97
})
98
99
it("should replace %date with the current date", func() {
100
currentDate := now.Format("2006-01-02")
101
input := "Today's date is %date"
102
expected := "Today's date is " + currentDate + " "
103
Expect(utils.FormatPrompt(input, counter, usage, now)).To(Equal(expected))
104
})
105
106
it("should replace %time with the current time", func() {
107
currentTime := now.Format("15:04:05")
108
input := "Current time is %time"
109
expected := "Current time is " + currentTime + " "
110
Expect(utils.FormatPrompt(input, counter, usage, now)).To(Equal(expected))
111
})
112
113
it("should replace %datetime with the current date and time", func() {
114
currentDatetime := now.Format("2006-01-02 15:04:05")
115
input := "Current date and time is %datetime"
116
expected := "Current date and time is " + currentDatetime + " "
117
Expect(utils.FormatPrompt(input, counter, usage, now)).To(Equal(expected))
118
})
119
120
it("should replace %counter with the current counter value", func() {
121
input := "The counter is %counter"
122
expected := "The counter is 1 "
123
Expect(utils.FormatPrompt(input, counter, usage, now)).To(Equal(expected))
124
})
125
126
it("should replace %usage with the current usage value", func() {
127
input := "The usage is %usage"
128
expected := "The usage is 2 "
129
Expect(utils.FormatPrompt(input, counter, usage, now)).To(Equal(expected))
130
})
131
132
it("should handle complex cases correctly", func() {
133
input := "command_prompt: [%time] [Q%counter]"
134
expected := fmt.Sprintf("command_prompt: [%s] [Q%d] ", now.Format("15:04:05"), counter)
135
Expect(utils.FormatPrompt(input, counter, usage, now)).To(Equal(expected))
136
})
137
138
it("should replace \\n with an actual newline", func() {
139
input := "Line 1\\nLine 2"
140
expected := "Line 1\nLine 2 "
141
Expect(utils.FormatPrompt(input, counter, usage, now)).To(Equal(expected))
142
})
143
})
144
145
when("IsBinary()", func() {
146
it("should return false for a regular string", func() {
147
Expect(utils.IsBinary([]byte("regular string"))).To(BeFalse())
148
})
149
it("should return false for a string containing emojis", func() {
150
Expect(utils.IsBinary([]byte("☮️✅❤️"))).To(BeFalse())
151
})
152
it("should return true for a binary string", func() {
153
Expect(utils.IsBinary([]byte{0xFF, 0xFE, 0xFD, 0xFC, 0xFB})).To(BeTrue())
154
})
155
it("should return false when the data is empty", func() {
156
Expect(utils.IsBinary([]byte{})).To(BeFalse())
157
})
158
it("should handle large text files correctly", func() {
159
// Create a large slice > 512KB with normal text
160
largeText := make([]byte, 1024*1024) // 1MB
161
for i := range largeText {
162
largeText[i] = 'a'
163
}
164
165
Expect(utils.IsBinary(largeText)).To(BeFalse())
166
})
167
it("should return true when data contains null bytes", func() {
168
Expect(utils.IsBinary([]byte{'h', 'e', 'l', 'l', 0x00, 'o'})).To(BeTrue())
169
})
170
171
it("should return true for invalid UTF-8 sequences", func() {
172
// Invalid UTF-8: 0xED 0xA0 0x80 is a surrogate pair which is invalid in UTF-8
173
Expect(utils.IsBinary([]byte{0xED, 0xA0, 0x80})).To(BeTrue())
174
})
175
176
it("should return false for valid UTF-8 special characters", func() {
177
// Testing with Chinese characters, Arabic, and other non-ASCII but valid UTF-8
178
Expect(utils.IsBinary([]byte("你好世界مرحبا"))).To(BeFalse())
179
})
180
181
it("should handle control characters correctly", func() {
182
// Test with allowed control characters (tab, newline, carriage return)
183
Expect(utils.IsBinary([]byte("Hello\tWorld\r\nTest"))).To(BeFalse())
184
185
// Test with other control characters that should trigger binary detection
186
data := []byte{0x01, 0x02, 0x03, 0x04}
187
Expect(utils.IsBinary(data)).To(BeTrue())
188
})
189
190
})
191
192
when("ValidateFlags()", func() {
193
const defaultModel = "mock-model"
194
195
var flags map[string]bool
196
197
it.Before(func() {
198
flags = make(map[string]bool)
199
})
200
201
it("doesn't throw an error when no flags are provided", func() {
202
Expect(utils.ValidateFlags(defaultModel, flags)).To(Succeed())
203
})
204
it("should return an error when --new-thread and --set-thread are both used", func() {
205
flags["new-thread"] = true
206
flags["set-thread"] = true
207
208
err := utils.ValidateFlags(defaultModel, flags)
209
Expect(err).To(HaveOccurred())
210
})
211
it("should return an error when --new-thread and --thread are both used", func() {
212
flags["new-thread"] = true
213
flags["thread"] = true
214
215
err := utils.ValidateFlags(defaultModel, flags)
216
Expect(err).To(HaveOccurred())
217
})
218
it("should return an error when --speak is used but --output is omitted", func() {
219
flags["speak"] = true
220
221
err := utils.ValidateFlags(defaultModel, flags)
222
Expect(err).To(HaveOccurred())
223
})
224
it("should return an error when --draw is used but --output is omitted", func() {
225
flags["draw"] = true
226
227
err := utils.ValidateFlags(defaultModel, flags)
228
Expect(err).To(HaveOccurred())
229
})
230
it("should return an error when --output is used but --speak or --draw are omitted", func() {
231
flags["output"] = true
232
233
err := utils.ValidateFlags(defaultModel, flags)
234
Expect(err).To(HaveOccurred())
235
})
236
it("should return an error when --audio is used with an incompatible model", func() {
237
flags["audio"] = true
238
239
err := utils.ValidateFlags(defaultModel, flags)
240
Expect(err).To(HaveOccurred())
241
})
242
it("should NOT return an error when --audio is used with a compatible model", func() {
243
flags["audio"] = true
244
245
err := utils.ValidateFlags(defaultModel+utils.AudioPattern, flags)
246
Expect(err).NotTo(HaveOccurred())
247
})
248
it("should return an error when --transcribe is used with an incompatible model", func() {
249
flags["transcribe"] = true
250
251
err := utils.ValidateFlags(defaultModel, flags)
252
Expect(err).To(HaveOccurred())
253
})
254
it("should NOT return an error when --transcribe is used with a compatible model", func() {
255
flags["transcribe"] = true
256
257
err := utils.ValidateFlags(defaultModel+utils.TranscribePattern, flags)
258
Expect(err).NotTo(HaveOccurred())
259
})
260
it("should return an error when --speak and --output flags are used with an incompatible model", func() {
261
flags["speak"] = true
262
flags["output"] = true
263
264
err := utils.ValidateFlags(defaultModel, flags)
265
Expect(err).To(HaveOccurred())
266
})
267
it("should NOT return an error when --speak and --output flags are used with a compatible model", func() {
268
flags["speak"] = true
269
flags["output"] = true
270
271
err := utils.ValidateFlags(defaultModel+utils.TTSPattern, flags)
272
Expect(err).NotTo(HaveOccurred())
273
})
274
it("should return an error when --draw and --output flags are used with an incompatible model", func() {
275
flags["draw"] = true
276
flags["output"] = true
277
278
err := utils.ValidateFlags(defaultModel, flags)
279
Expect(err).To(HaveOccurred())
280
})
281
it("should NOT return an error when --draw and --output flags are used with a compatible model", func() {
282
flags["draw"] = true
283
flags["output"] = true
284
285
err := utils.ValidateFlags(defaultModel+utils.ImagePattern, flags)
286
Expect(err).NotTo(HaveOccurred())
287
})
288
it("should NOT return an error when --draw, --image and --output flags are used with a compatible model", func() {
289
flags["draw"] = true
290
flags["output"] = true
291
flags["image"] = true
292
293
err := utils.ValidateFlags(defaultModel+utils.ImagePattern, flags)
294
Expect(err).NotTo(HaveOccurred())
295
})
296
it("should return an error when --voice is used with an incompatible model", func() {
297
flags["voice"] = true
298
299
err := utils.ValidateFlags(defaultModel, flags)
300
Expect(err).To(HaveOccurred())
301
})
302
it("should NOT return an error when --voice is used with a compatible model", func() {
303
flags["voice"] = true
304
305
err := utils.ValidateFlags(defaultModel+utils.TTSPattern, flags)
306
Expect(err).NotTo(HaveOccurred())
307
})
308
it("should return an error when --effort is used with an incompatible model", func() {
309
flags["effort"] = true
310
311
err := utils.ValidateFlags(defaultModel, flags)
312
Expect(err).To(HaveOccurred())
313
})
314
it("should NOT return an error when --effort is used with a compatible model", func() {
315
flags["effort"] = true
316
317
Expect(utils.ValidateFlags(defaultModel+utils.O1ProPattern, flags)).To(Succeed())
318
Expect(utils.ValidateFlags(defaultModel+utils.GPT5Pattern, flags)).To(Succeed())
319
})
320
it("should return an error when the --param flag is used without --mcp", func() {
321
flags["param"] = true
322
err := utils.ValidateFlags(defaultModel, flags)
323
Expect(err).To(HaveOccurred())
324
})
325
it("should return an error when the --params flag is used without --mcp", func() {
326
flags["params"] = true
327
err := utils.ValidateFlags(defaultModel, flags)
328
Expect(err).To(HaveOccurred())
329
})
330
})
331
332
when("ParseMCPPlugin()", func() {
333
const (
334
invalidPattern = "apify-invalid-pattern"
335
function = "user~actor"
336
version = "mock-version"
337
)
338
339
it("throws an error when the pattern does not contain a slash", func() {
340
_, err := utils.ParseMCPPlugin(invalidPattern)
341
Expect(err).To(HaveOccurred())
342
Expect(err).To(MatchError(utils.InvalidMCPPatter))
343
})
344
it("throws an error when the pattern starts with a slash", func() {
345
_, err := utils.ParseMCPPlugin("/" + invalidPattern)
346
Expect(err).To(HaveOccurred())
347
Expect(err).To(MatchError(utils.InvalidMCPPatter))
348
})
349
it("throws an error when the pattern starts ends with a slash", func() {
350
_, err := utils.ParseMCPPlugin(invalidPattern + "/")
351
Expect(err).To(HaveOccurred())
352
Expect(err).To(MatchError(utils.InvalidMCPPatter))
353
})
354
it("throws an error when the pattern contains more than one slash", func() {
355
_, err := utils.ParseMCPPlugin("apify/apify/app")
356
Expect(err).To(HaveOccurred())
357
Expect(err).To(MatchError(utils.InvalidMCPPatter))
358
})
359
it("throws an error when the provider is not apify", func() {
360
_, err := utils.ParseMCPPlugin("unsupported/" + function)
361
Expect(err).To(HaveOccurred())
362
Expect(err).To(MatchError(utils.UnsupportedProvider))
363
})
364
it("is not case dependent when it comes to providers", func() {
365
_, err := utils.ParseMCPPlugin("ApIfY/" + function)
366
Expect(err).NotTo(HaveOccurred())
367
})
368
it("throws an error when the provider is apify and the function is missing a tilde", func() {
369
_, err := utils.ParseMCPPlugin("apify" + "/" + "invalid-function")
370
Expect(err).To(HaveOccurred())
371
Expect(err).To(MatchError(utils.InvalidApifyFunction))
372
})
373
it("throws an error when the provider is apify and the function is missing an actor", func() {
374
_, err := utils.ParseMCPPlugin("apify" + "/" + "user~")
375
Expect(err).To(HaveOccurred())
376
Expect(err).To(MatchError(utils.InvalidApifyFunction))
377
})
378
it("throws an error when the provider is apify and the function is missing a user", func() {
379
_, err := utils.ParseMCPPlugin("apify" + "/" + "~actor")
380
Expect(err).To(HaveOccurred())
381
Expect(err).To(MatchError(utils.InvalidApifyFunction))
382
})
383
it("sets the version to the latest when the version is not specified", func() {
384
result, err := utils.ParseMCPPlugin(utils.ApifyProvider + "/" + function)
385
Expect(err).NotTo(HaveOccurred())
386
Expect(result.Provider).To(Equal(utils.ApifyProvider))
387
Expect(result.Function).To(Equal(function))
388
Expect(result.Version).To(Equal(utils.LatestVersion))
389
})
390
it("sets the correct version when it is specified", func() {
391
result, err := utils.ParseMCPPlugin(utils.ApifyProvider + "/" + function + "@" + version)
392
Expect(err).NotTo(HaveOccurred())
393
Expect(result.Provider).To(Equal(utils.ApifyProvider))
394
Expect(result.Function).To(Equal(function))
395
Expect(result.Version).To(Equal(version))
396
})
397
})
398
399
when("ParseParams()", func() {
400
const (
401
key = "key"
402
value = "value"
403
pair = key + "=" + value
404
)
405
406
it("throws and error when the params are not valid JSON or a valid pair", func() {
407
_, err := utils.ParseParams("invalid-params")
408
Expect(err).To(HaveOccurred())
409
Expect(err).To(MatchError(utils.InvalidParams))
410
})
411
it("parses the input as expected when a valid pair is provided", func() {
412
result, err := utils.ParseParams(pair)
413
Expect(err).NotTo(HaveOccurred())
414
Expect(result).To(HaveLen(1))
415
Expect(result[key]).To(Equal(value))
416
})
417
it("parses the input as expected when a valid json is provided", func() {
418
jsonInput := `{"key": "value"}`
419
420
result, err := utils.ParseParams(jsonInput)
421
Expect(err).NotTo(HaveOccurred())
422
Expect(result).To(HaveLen(1))
423
Expect(result["key"]).To(Equal("value"))
424
})
425
it("does not throw an error when no input is provided", func() {
426
result, err := utils.ParseParams()
427
Expect(err).NotTo(HaveOccurred())
428
Expect(result).To(BeEmpty())
429
})
430
it("throws an error when the 2nd pair is malformed", func() {
431
_, err := utils.ParseParams([]string{pair, "invalid-pair"}...)
432
Expect(err).To(HaveOccurred())
433
Expect(err).To(MatchError(utils.InvalidParams))
434
})
435
it("produces the expected output when multiple pairs are provided", func() {
436
result, err := utils.ParseParams(pair, fmt.Sprintf("%s2=%s2", key, value))
437
Expect(err).NotTo(HaveOccurred())
438
Expect(result).To(HaveLen(2))
439
Expect(result[key]).To(Equal(value))
440
Expect(result[key+"2"]).To(Equal(value + "2"))
441
})
442
it("parses key=value pairs where the value is a JSON array or boolean", func() {
443
result, err := utils.ParseParams(
444
`locations=["Brooklyn","Queens"]`,
445
`forecasts=true`,
446
`language="en"`,
447
)
448
Expect(err).NotTo(HaveOccurred())
449
450
Expect(result).To(HaveLen(3))
451
452
Expect(result["locations"]).To(Equal([]interface{}{"Brooklyn", "Queens"}))
453
Expect(result["forecasts"]).To(Equal(true))
454
Expect(result["language"]).To(Equal("en")) // NOTE: quoted value gets parsed as string
455
})
456
})
457
}
458
459