Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
kardolus
GitHub Repository: kardolus/chatgpt-cli
Path: blob/main/api/http/http_test.go
2865 views
1
package http_test
2
3
import (
4
"bytes"
5
stdhttp "net/http"
6
"net/http/httptest"
7
"strings"
8
"testing"
9
10
"github.com/kardolus/chatgpt-cli/api/http"
11
chatgpthttp "github.com/kardolus/chatgpt-cli/api/http"
12
"github.com/kardolus/chatgpt-cli/config"
13
. "github.com/onsi/gomega"
14
"github.com/sclevine/spec"
15
"github.com/sclevine/spec/report"
16
)
17
18
func TestUnitHTTP(t *testing.T) {
19
spec.Run(t, "Testing the HTTP Client", testHTTP, spec.Report(report.Terminal{}))
20
}
21
22
func testHTTP(t *testing.T, when spec.G, it spec.S) {
23
var subject http.RestCaller
24
25
const responsesPath = "/v1/responses" // use http.ResponsesPath if you export it
26
27
it.Before(func() {
28
RegisterTestingT(t)
29
subject = http.RestCaller{}
30
})
31
32
when("ProcessResponse()", func() {
33
it("parses a legacy stream as expected (any endpoint)", func() {
34
buf := &bytes.Buffer{}
35
// legacy works via both branches; use a non-responses endpoint to
36
// ensure we exercise the original/legacy code path.
37
subject.ProcessResponse(strings.NewReader(legacyStream), buf, "/v1/chat/completions")
38
output := buf.String()
39
Expect(output).To(Equal("a b c\n"))
40
})
41
42
it("parses a GPT-5 SSE stream when endpoint is /v1/responses", func() {
43
buf := &bytes.Buffer{}
44
subject.ProcessResponse(strings.NewReader(gpt5Stream), buf, responsesPath)
45
output := buf.String()
46
// deltas are "a", " b", " c" then response.completed -> newline
47
Expect(output).To(Equal("a b c\n"))
48
})
49
50
it("throws an error when the legacy json is invalid", func() {
51
input := `data: {"invalid":"json"` // missing closing brace
52
expectedOutput := "Error: unexpected end of JSON input\n"
53
54
var buf bytes.Buffer
55
subject.ProcessResponse(strings.NewReader(input), &buf, "/v1/chat/completions")
56
output := buf.String()
57
Expect(output).To(Equal(expectedOutput))
58
})
59
})
60
}
61
62
const legacyStream = `
63
data: {"id":"id-1","object":"chat.completion.chunk","created":1,"model":"model-1","choices":[{"delta":{"role":"assistant"},"index":0,"finish_reason":null}]}
64
65
data: {"id":"id-2","object":"chat.completion.chunk","created":2,"model":"model-1","choices":[{"delta":{"content":"a"},"index":0,"finish_reason":null}]}
66
67
data: {"id":"id-3","object":"chat.completion.chunk","created":3,"model":"model-1","choices":[{"delta":{"content":" b"},"index":0,"finish_reason":null}]}
68
69
data: {"id":"id-4","object":"chat.completion.chunk","created":4,"model":"model-1","choices":[{"delta":{"content":" c"},"index":0,"finish_reason":null}]}
70
71
data: {"id":"id-5","object":"chat.completion.chunk","created":5,"model":"model-1","choices":[{"delta":{},"index":0,"finish_reason":"stop"}]}
72
73
data: [DONE]
74
`
75
76
// Minimal GPT-5 SSE that your new parser should handle
77
const gpt5Stream = `
78
event: response.created
79
data: {"type":"response.created"}
80
81
event: response.output_item.added
82
data: {"type":"response.output_item.added","output_index":0,"item":{"id":"msg_1","type":"message","status":"in_progress","content":[],"role":"assistant"}}
83
84
event: response.content_part.added
85
data: {"type":"response.content_part.added","item_id":"msg_1","output_index":0,"content_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""}}
86
87
event: response.output_text.delta
88
data: {"type":"response.output_text.delta","item_id":"msg_1","output_index":0,"content_index":0,"delta":"a"}
89
90
event: response.output_text.delta
91
data: {"type":"response.output_text.delta","item_id":"msg_1","output_index":0,"content_index":0,"delta":" b"}
92
93
event: response.output_text.delta
94
data: {"type":"response.output_text.delta","item_id":"msg_1","output_index":0,"content_index":0,"delta":" c"}
95
96
event: response.completed
97
data: {"type":"response.completed","response":{"status":"completed"}}
98
`
99
100
func TestUnitCustomHeaders(t *testing.T) {
101
spec.Run(t, "Testing Custom Headers", testCustomHeaders, spec.Report(report.Terminal{}))
102
}
103
104
func testCustomHeaders(t *testing.T, when spec.G, it spec.S) {
105
it.Before(func() {
106
RegisterTestingT(t)
107
})
108
109
when("custom headers are configured", func() {
110
it("attaches custom headers to POST requests", func() {
111
t.Parallel()
112
113
var receivedHeaders stdhttp.Header
114
server := httptest.NewServer(stdhttp.HandlerFunc(func(w stdhttp.ResponseWriter, r *stdhttp.Request) {
115
receivedHeaders = r.Header
116
w.WriteHeader(stdhttp.StatusOK)
117
_, _ = w.Write([]byte(`{"success": true}`))
118
}))
119
defer server.Close()
120
121
cfg := config.Config{
122
CustomHeaders: map[string]string{
123
"X-Custom-Header": "custom-value",
124
"X-Another-Header": "another-value",
125
},
126
}
127
128
subject := chatgpthttp.New(cfg)
129
_, err := subject.Post(server.URL, []byte(`{"test": "data"}`), false)
130
131
Expect(err).ToNot(HaveOccurred())
132
Expect(receivedHeaders.Get("X-Custom-Header")).To(Equal("custom-value"))
133
Expect(receivedHeaders.Get("X-Another-Header")).To(Equal("another-value"))
134
})
135
136
it("attaches custom headers to GET requests", func() {
137
t.Parallel()
138
139
var receivedHeaders stdhttp.Header
140
server := httptest.NewServer(stdhttp.HandlerFunc(func(w stdhttp.ResponseWriter, r *stdhttp.Request) {
141
receivedHeaders = r.Header
142
w.WriteHeader(stdhttp.StatusOK)
143
_, _ = w.Write([]byte(`{"success": true}`))
144
}))
145
defer server.Close()
146
147
cfg := config.Config{
148
CustomHeaders: map[string]string{
149
"X-API-Version": "v2",
150
"X-Client-ID": "test-client",
151
},
152
}
153
154
subject := chatgpthttp.New(cfg)
155
_, err := subject.Get(server.URL)
156
157
Expect(err).ToNot(HaveOccurred())
158
Expect(receivedHeaders.Get("X-API-Version")).To(Equal("v2"))
159
Expect(receivedHeaders.Get("X-Client-ID")).To(Equal("test-client"))
160
})
161
162
it("works with empty custom headers map", func() {
163
t.Parallel()
164
165
var receivedHeaders stdhttp.Header
166
server := httptest.NewServer(stdhttp.HandlerFunc(func(w stdhttp.ResponseWriter, r *stdhttp.Request) {
167
receivedHeaders = r.Header
168
w.WriteHeader(stdhttp.StatusOK)
169
_, _ = w.Write([]byte(`{"success": true}`))
170
}))
171
defer server.Close()
172
173
cfg := config.Config{
174
CustomHeaders: map[string]string{},
175
}
176
177
subject := chatgpthttp.New(cfg)
178
_, err := subject.Post(server.URL, []byte(`{"test": "data"}`), false)
179
180
Expect(err).ToNot(HaveOccurred())
181
Expect(receivedHeaders).ToNot(BeNil())
182
})
183
184
it("works with nil custom headers map", func() {
185
t.Parallel()
186
187
var receivedHeaders stdhttp.Header
188
server := httptest.NewServer(stdhttp.HandlerFunc(func(w stdhttp.ResponseWriter, r *stdhttp.Request) {
189
receivedHeaders = r.Header
190
w.WriteHeader(stdhttp.StatusOK)
191
_, _ = w.Write([]byte(`{"success": true}`))
192
}))
193
defer server.Close()
194
195
cfg := config.Config{
196
CustomHeaders: nil,
197
}
198
199
subject := chatgpthttp.New(cfg)
200
_, err := subject.Post(server.URL, []byte(`{"test": "data"}`), false)
201
202
Expect(err).ToNot(HaveOccurred())
203
Expect(receivedHeaders).ToNot(BeNil())
204
})
205
206
it("does not override standard headers with custom headers", func() {
207
t.Parallel()
208
209
var receivedHeaders stdhttp.Header
210
server := httptest.NewServer(stdhttp.HandlerFunc(func(w stdhttp.ResponseWriter, r *stdhttp.Request) {
211
receivedHeaders = r.Header
212
w.WriteHeader(stdhttp.StatusOK)
213
_, _ = w.Write([]byte(`{"success": true}`))
214
}))
215
defer server.Close()
216
217
cfg := config.Config{
218
APIKey: "test-key",
219
AuthHeader: "Authorization",
220
AuthTokenPrefix: "Bearer ",
221
UserAgent: "TestAgent/1.0",
222
CustomHeaders: map[string]string{
223
"X-Custom-Header": "custom-value",
224
},
225
}
226
227
subject := chatgpthttp.New(cfg)
228
_, err := subject.Post(server.URL, []byte(`{"test": "data"}`), false)
229
230
Expect(err).ToNot(HaveOccurred())
231
Expect(receivedHeaders.Get("Authorization")).To(Equal("Bearer test-key"))
232
Expect(receivedHeaders.Get("User-Agent")).To(Equal("TestAgent/1.0"))
233
Expect(receivedHeaders.Get("X-Custom-Header")).To(Equal("custom-value"))
234
})
235
})
236
}
237
238