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