@@ -0,0 +1,376 @@
1+package requests
2+
3+import (
4+ "bytes"
5+ "context"
6+ "encoding/base64"
7+ "encoding/json"
8+ "fmt"
9+ "net/http"
10+
11+ "github.com/bitly/go-simplejson"
12+ . "github.com/onsi/ginkgo"
13+ . "github.com/onsi/gomega"
14+)
15+
16+var _ = Describe("Builder suite", func() {
17+ var b Builder
18+ getBuilder := func() Builder { return b }
19+
20+ baseHeaders := http.Header{
21+ "Accept-Encoding": []string{"gzip"},
22+ "User-Agent": []string{"Go-http-client/1.1"},
23+ }
24+
25+ BeforeEach(func() {
26+ // Most tests will request the server address
27+ b = New(serverAddr + "/json/path")
28+ })
29+
30+ Context("with a basic request", func() {
31+ assertSuccessfulRequest(getBuilder, testHTTPRequest{
32+ Method: "GET",
33+ Header: baseHeaders,
34+ Body: []byte{},
35+ RequestURI: "/json/path",
36+ })
37+ })
38+
39+ Context("with a context", func() {
40+ var ctx context.Context
41+ var cancel context.CancelFunc
42+
43+ BeforeEach(func() {
44+ ctx, cancel = context.WithCancel(context.Background())
45+ b = b.WithContext(ctx)
46+ })
47+
48+ AfterEach(func() {
49+ cancel()
50+ })
51+
52+ assertSuccessfulRequest(getBuilder, testHTTPRequest{
53+ Method: "GET",
54+ Header: baseHeaders,
55+ Body: []byte{},
56+ RequestURI: "/json/path",
57+ })
58+
59+ Context("if the context is cancelled", func() {
60+ BeforeEach(func() {
61+ cancel()
62+ })
63+
64+ assertRequestError(getBuilder, "context canceled")
65+ })
66+ })
67+
68+ Context("with a body", func() {
69+ const body = "{\"some\": \"body\"}"
70+ header := baseHeaders.Clone()
71+ header.Set("Content-Length", fmt.Sprintf("%d", len(body)))
72+
73+ BeforeEach(func() {
74+ buf := bytes.NewBuffer([]byte(body))
75+ b = b.WithBody(buf)
76+ })
77+
78+ assertSuccessfulRequest(getBuilder, testHTTPRequest{
79+ Method: "GET",
80+ Header: header,
81+ Body: []byte(body),
82+ RequestURI: "/json/path",
83+ })
84+ })
85+
86+ Context("with a method", func() {
87+ Context("POST with a body", func() {
88+ const body = "{\"some\": \"body\"}"
89+ header := baseHeaders.Clone()
90+ header.Set("Content-Length", fmt.Sprintf("%d", len(body)))
91+
92+ BeforeEach(func() {
93+ buf := bytes.NewBuffer([]byte(body))
94+ b = b.WithMethod("POST").WithBody(buf)
95+ })
96+
97+ assertSuccessfulRequest(getBuilder, testHTTPRequest{
98+ Method: "POST",
99+ Header: header,
100+ Body: []byte(body),
101+ RequestURI: "/json/path",
102+ })
103+ })
104+
105+ Context("POST without a body", func() {
106+ header := baseHeaders.Clone()
107+ header.Set("Content-Length", "0")
108+
109+ BeforeEach(func() {
110+ b = b.WithMethod("POST")
111+ })
112+
113+ assertSuccessfulRequest(getBuilder, testHTTPRequest{
114+ Method: "POST",
115+ Header: header,
116+ Body: []byte{},
117+ RequestURI: "/json/path",
118+ })
119+ })
120+
121+ Context("OPTIONS", func() {
122+ BeforeEach(func() {
123+ b = b.WithMethod("OPTIONS")
124+ })
125+
126+ assertSuccessfulRequest(getBuilder, testHTTPRequest{
127+ Method: "OPTIONS",
128+ Header: baseHeaders,
129+ Body: []byte{},
130+ RequestURI: "/json/path",
131+ })
132+ })
133+
134+ Context("INVALID-\\t-METHOD", func() {
135+ BeforeEach(func() {
136+ b = b.WithMethod("INVALID-\t-METHOD")
137+ })
138+
139+ assertRequestError(getBuilder, "error creating request: net/http: invalid method \"INVALID-\\t-METHOD\"")
140+ })
141+ })
142+
143+ Context("with headers", func() {
144+ Context("setting a header", func() {
145+ header := baseHeaders.Clone()
146+ header.Set("header", "value")
147+
148+ BeforeEach(func() {
149+ b = b.SetHeader("header", "value")
150+ })
151+
152+ assertSuccessfulRequest(getBuilder, testHTTPRequest{
153+ Method: "GET",
154+ Header: header,
155+ Body: []byte{},
156+ RequestURI: "/json/path",
157+ })
158+
159+ Context("then replacing the headers", func() {
160+ replacementHeaders := http.Header{
161+ "Accept-Encoding": []string{"*"},
162+ "User-Agent": []string{"test-agent"},
163+ "Foo": []string{"bar, baz"},
164+ }
165+
166+ BeforeEach(func() {
167+ b = b.WithHeaders(replacementHeaders)
168+ })
169+
170+ assertSuccessfulRequest(getBuilder, testHTTPRequest{
171+ Method: "GET",
172+ Header: replacementHeaders,
173+ Body: []byte{},
174+ RequestURI: "/json/path",
175+ })
176+ })
177+ })
178+
179+ Context("replacing the header", func() {
180+ replacementHeaders := http.Header{
181+ "Accept-Encoding": []string{"*"},
182+ "User-Agent": []string{"test-agent"},
183+ "Foo": []string{"bar, baz"},
184+ }
185+
186+ BeforeEach(func() {
187+ b = b.WithHeaders(replacementHeaders)
188+ })
189+
190+ assertSuccessfulRequest(getBuilder, testHTTPRequest{
191+ Method: "GET",
192+ Header: replacementHeaders,
193+ Body: []byte{},
194+ RequestURI: "/json/path",
195+ })
196+
197+ Context("then setting a header", func() {
198+ header := replacementHeaders.Clone()
199+ header.Set("User-Agent", "different-agent")
200+
201+ BeforeEach(func() {
202+ b = b.SetHeader("User-Agent", "different-agent")
203+ })
204+
205+ assertSuccessfulRequest(getBuilder, testHTTPRequest{
206+ Method: "GET",
207+ Header: header,
208+ Body: []byte{},
209+ RequestURI: "/json/path",
210+ })
211+ })
212+ })
213+ })
214+
215+ Context("if the request has been completed and then modified", func() {
216+ BeforeEach(func() {
217+ result := b.Do()
218+ Expect(result.Error()).ToNot(HaveOccurred())
219+
220+ b.WithMethod("POST")
221+ })
222+
223+ Context("should not redo the request", func() {
224+ assertSuccessfulRequest(getBuilder, testHTTPRequest{
225+ Method: "GET",
226+ Header: baseHeaders,
227+ Body: []byte{},
228+ RequestURI: "/json/path",
229+ })
230+ })
231+ })
232+
233+ Context("when the requested page is not found", func() {
234+ BeforeEach(func() {
235+ b = New(serverAddr + "/not-found")
236+ })
237+
238+ assertJSONError(getBuilder, "404 page not found")
239+ })
240+
241+ Context("when the requested page is not valid JSON", func() {
242+ BeforeEach(func() {
243+ b = New(serverAddr + "/string/path")
244+ })
245+
246+ assertJSONError(getBuilder, "invalid character 'O' looking for beginning of value")
247+ })
248+})
249+
250+func assertSuccessfulRequest(builder func() Builder, expectedRequest testHTTPRequest) {
251+ Context("Do", func() {
252+ var result Result
253+
254+ BeforeEach(func() {
255+ result = builder().Do()
256+ Expect(result.Error()).ToNot(HaveOccurred())
257+ })
258+
259+ It("returns a successful status", func() {
260+ Expect(result.StatusCode()).To(Equal(http.StatusOK))
261+ })
262+
263+ It("made the expected request", func() {
264+ actualRequest := testHTTPRequest{}
265+ Expect(json.Unmarshal(result.Body(), &actualRequest)).To(Succeed())
266+
267+ Expect(actualRequest).To(Equal(expectedRequest))
268+ })
269+ })
270+
271+ Context("UnmarshalInto", func() {
272+ var actualRequest testHTTPRequest
273+
274+ BeforeEach(func() {
275+ Expect(builder().Do().UnmarshalInto(&actualRequest)).To(Succeed())
276+ })
277+
278+ It("made the expected request", func() {
279+ Expect(actualRequest).To(Equal(expectedRequest))
280+ })
281+ })
282+
283+ Context("UnmarshalJSON", func() {
284+ var response *simplejson.Json
285+
286+ BeforeEach(func() {
287+ var err error
288+ response, err = builder().Do().UnmarshalJSON()
289+ Expect(err).ToNot(HaveOccurred())
290+ })
291+
292+ It("made the expected reqest", func() {
293+ header := http.Header{}
294+ for key, value := range response.Get("Header").MustMap() {
295+ vs, ok := value.([]interface{})
296+ Expect(ok).To(BeTrue())
297+ svs := []string{}
298+ for _, v := range vs {
299+ sv, ok := v.(string)
300+ Expect(ok).To(BeTrue())
301+ svs = append(svs, sv)
302+ }
303+ header[key] = svs
304+ }
305+
306+ // Other json unmarhsallers base64 decode byte slices automatically
307+ body, err := base64.StdEncoding.DecodeString(response.Get("Body").MustString())
308+ Expect(err).ToNot(HaveOccurred())
309+
310+ actualRequest := testHTTPRequest{
311+ Method: response.Get("Method").MustString(),
312+ Header: header,
313+ Body: body,
314+ RequestURI: response.Get("RequestURI").MustString(),
315+ }
316+
317+ Expect(actualRequest).To(Equal(expectedRequest))
318+ })
319+ })
320+}
321+
322+func assertRequestError(builder func() Builder, errorMessage string) {
323+ Context("Do", func() {
324+ It("returns an error", func() {
325+ result := builder().Do()
326+ Expect(result.Error()).To(MatchError(ContainSubstring(errorMessage)))
327+ })
328+ })
329+
330+ Context("UnmarshalInto", func() {
331+ It("returns an error", func() {
332+ var actualRequest testHTTPRequest
333+ err := builder().Do().UnmarshalInto(&actualRequest)
334+ Expect(err).To(MatchError(ContainSubstring(errorMessage)))
335+
336+ // Should be empty
337+ Expect(actualRequest).To(Equal(testHTTPRequest{}))
338+ })
339+ })
340+
341+ Context("UnmarshalJSON", func() {
342+ It("returns an error", func() {
343+ resp, err := builder().Do().UnmarshalJSON()
344+ Expect(err).To(MatchError(ContainSubstring(errorMessage)))
345+ Expect(resp).To(BeNil())
346+ })
347+ })
348+}
349+
350+func assertJSONError(builder func() Builder, errorMessage string) {
351+ Context("Do", func() {
352+ It("does not return an error", func() {
353+ result := builder().Do()
354+ Expect(result.Error()).To(BeNil())
355+ })
356+ })
357+
358+ Context("UnmarshalInto", func() {
359+ It("returns an error", func() {
360+ var actualRequest testHTTPRequest
361+ err := builder().Do().UnmarshalInto(&actualRequest)
362+ Expect(err).To(MatchError(ContainSubstring(errorMessage)))
363+
364+ // Should be empty
365+ Expect(actualRequest).To(Equal(testHTTPRequest{}))
366+ })
367+ })
368+
369+ Context("UnmarshalJSON", func() {
370+ It("returns an error", func() {
371+ resp, err := builder().Do().UnmarshalJSON()
372+ Expect(err).To(MatchError(ContainSubstring(errorMessage)))
373+ Expect(resp).To(BeNil())
374+ })
375+ })
376+}