~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/testing/httptesting/http.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2014 Canonical Ltd.
 
2
// Licensed under the LGPLv3, see LICENCE file for details.
 
3
 
 
4
package httptesting
 
5
 
 
6
import (
 
7
        "bytes"
 
8
        "encoding/json"
 
9
        "io"
 
10
        "io/ioutil"
 
11
        "net/http"
 
12
        "net/http/httptest"
 
13
        "net/textproto"
 
14
        "net/url"
 
15
        "strings"
 
16
 
 
17
        gc "gopkg.in/check.v1"
 
18
 
 
19
        jc "github.com/juju/testing/checkers"
 
20
)
 
21
 
 
22
// BodyAsserter represents a function that can assert the correctness of
 
23
// a JSON reponse.
 
24
type BodyAsserter func(c *gc.C, body json.RawMessage)
 
25
 
 
26
// JSONCallParams holds parameters for AssertJSONCall.
 
27
// If left empty, some fields will automatically be filled with defaults.
 
28
type JSONCallParams struct {
 
29
        // Do is used to make the HTTP request.
 
30
        // If it is nil, http.DefaultClient.Do will be used.
 
31
        // If the body reader implements io.Seeker,
 
32
        // req.Body will also implement that interface.
 
33
        Do func(req *http.Request) (*http.Response, error)
 
34
 
 
35
        // ExpectError holds the error regexp to match
 
36
        // against the error returned from the HTTP Do
 
37
        // request. If it is empty, the error is expected to be
 
38
        // nil.
 
39
        ExpectError string
 
40
 
 
41
        // Method holds the HTTP method to use for the call.
 
42
        // GET is assumed if this is empty.
 
43
        Method string
 
44
 
 
45
        // URL holds the URL to pass when making the request.
 
46
        // If the URL does not contain a host, a temporary
 
47
        // HTTP server is started running the Handler below
 
48
        // which is used for the host.
 
49
        URL string
 
50
 
 
51
        // Handler holds the handler to use to make the request.
 
52
        // It is ignored if the above URL field has a host part.
 
53
        Handler http.Handler
 
54
 
 
55
        // JSONBody specifies a JSON value to marshal to use
 
56
        // as the body of the request. If this is specified, Body will
 
57
        // be ignored and the Content-Type header will
 
58
        // be set to application/json. The request
 
59
        // body will implement io.Seeker.
 
60
        JSONBody interface{}
 
61
 
 
62
        // Body holds the body to send in the request.
 
63
        Body io.Reader
 
64
 
 
65
        // Header specifies the HTTP headers to use when making
 
66
        // the request.
 
67
        Header http.Header
 
68
 
 
69
        // ContentLength specifies the length of the body.
 
70
        // It may be zero, in which case the default net/http
 
71
        // content-length behaviour will be used.
 
72
        ContentLength int64
 
73
 
 
74
        // Username, if specified, is used for HTTP basic authentication.
 
75
        Username string
 
76
 
 
77
        // Password, if specified, is used for HTTP basic authentication.
 
78
        Password string
 
79
 
 
80
        // ExpectStatus holds the expected HTTP status code.
 
81
        // http.StatusOK is assumed if this is zero.
 
82
        ExpectStatus int
 
83
 
 
84
        // ExpectBody holds the expected JSON body.
 
85
        // This may be a function of type BodyAsserter in which case it
 
86
        // will be called with the http response body to check the
 
87
        // result.
 
88
        ExpectBody interface{}
 
89
 
 
90
        // ExpectHeader holds any HTTP headers that must be present in the response.
 
91
        // Note that the response may also contain headers not in this field.
 
92
        ExpectHeader http.Header
 
93
 
 
94
        // Cookies, if specified, are added to the request.
 
95
        Cookies []*http.Cookie
 
96
}
 
97
 
 
98
// AssertJSONCall asserts that when the given handler is called with
 
99
// the given parameters, the result is as specified.
 
100
func AssertJSONCall(c *gc.C, p JSONCallParams) {
 
101
        c.Logf("JSON call, url %q", p.URL)
 
102
        if p.ExpectStatus == 0 {
 
103
                p.ExpectStatus = http.StatusOK
 
104
        }
 
105
        rec := DoRequest(c, DoRequestParams{
 
106
                Do:            p.Do,
 
107
                ExpectError:   p.ExpectError,
 
108
                Handler:       p.Handler,
 
109
                Method:        p.Method,
 
110
                URL:           p.URL,
 
111
                Body:          p.Body,
 
112
                JSONBody:      p.JSONBody,
 
113
                Header:        p.Header,
 
114
                ContentLength: p.ContentLength,
 
115
                Username:      p.Username,
 
116
                Password:      p.Password,
 
117
                Cookies:       p.Cookies,
 
118
        })
 
119
        if p.ExpectError != "" {
 
120
                return
 
121
        }
 
122
        AssertJSONResponse(c, rec, p.ExpectStatus, p.ExpectBody)
 
123
 
 
124
        for k, v := range p.ExpectHeader {
 
125
                c.Assert(rec.HeaderMap[textproto.CanonicalMIMEHeaderKey(k)], gc.DeepEquals, v, gc.Commentf("header %q", k))
 
126
        }
 
127
}
 
128
 
 
129
// AssertJSONResponse asserts that the given response recorder has
 
130
// recorded the given HTTP status, response body and content type. If
 
131
// expectBody is of type BodyAsserter it will be called with the response
 
132
// body to ensure the response is correct.
 
133
func AssertJSONResponse(c *gc.C, rec *httptest.ResponseRecorder, expectStatus int, expectBody interface{}) {
 
134
        c.Assert(rec.Code, gc.Equals, expectStatus, gc.Commentf("body: %s", rec.Body.Bytes()))
 
135
 
 
136
        // Ensure the response includes the expected body.
 
137
        if expectBody == nil {
 
138
                c.Assert(rec.Body.Bytes(), gc.HasLen, 0)
 
139
                return
 
140
        }
 
141
        c.Assert(rec.Header().Get("Content-Type"), gc.Equals, "application/json")
 
142
 
 
143
        if assertBody, ok := expectBody.(BodyAsserter); ok {
 
144
                var data json.RawMessage
 
145
                err := json.Unmarshal(rec.Body.Bytes(), &data)
 
146
                c.Assert(err, jc.ErrorIsNil, gc.Commentf("body: %s", rec.Body.Bytes()))
 
147
                assertBody(c, data)
 
148
                return
 
149
        }
 
150
        c.Assert(rec.Body.String(), jc.JSONEquals, expectBody)
 
151
}
 
152
 
 
153
// DoRequestParams holds parameters for DoRequest.
 
154
// If left empty, some fields will automatically be filled with defaults.
 
155
type DoRequestParams struct {
 
156
        // Do is used to make the HTTP request.
 
157
        // If it is nil, http.DefaultClient.Do will be used.
 
158
        // If the body reader implements io.Seeker,
 
159
        // req.Body will also implement that interface.
 
160
        Do func(req *http.Request) (*http.Response, error)
 
161
 
 
162
        // ExpectError holds the error regexp to match
 
163
        // against the error returned from the HTTP Do
 
164
        // request. If it is empty, the error is expected to be
 
165
        // nil.
 
166
        ExpectError string
 
167
 
 
168
        // Method holds the HTTP method to use for the call.
 
169
        // GET is assumed if this is empty.
 
170
        Method string
 
171
 
 
172
        // URL holds the URL to pass when making the request.
 
173
        // If the URL does not contain a host, a temporary
 
174
        // HTTP server is started running the Handler below
 
175
        // which is used for the host.
 
176
        URL string
 
177
 
 
178
        // Handler holds the handler to use to make the request.
 
179
        // It is ignored if the above URL field has a host part.
 
180
        Handler http.Handler
 
181
 
 
182
        // JSONBody specifies a JSON value to marshal to use
 
183
        // as the body of the request. If this is specified, Body will
 
184
        // be ignored and the Content-Type header will
 
185
        // be set to application/json. The request
 
186
        // body will implement io.Seeker.
 
187
        JSONBody interface{}
 
188
 
 
189
        // Body holds the body to send in the request.
 
190
        Body io.Reader
 
191
 
 
192
        // Header specifies the HTTP headers to use when making
 
193
        // the request.
 
194
        Header http.Header
 
195
 
 
196
        // ContentLength specifies the length of the body.
 
197
        // It may be zero, in which case the default net/http
 
198
        // content-length behaviour will be used.
 
199
        ContentLength int64
 
200
 
 
201
        // Username, if specified, is used for HTTP basic authentication.
 
202
        Username string
 
203
 
 
204
        // Password, if specified, is used for HTTP basic authentication.
 
205
        Password string
 
206
 
 
207
        // Cookies, if specified, are added to the request.
 
208
        Cookies []*http.Cookie
 
209
}
 
210
 
 
211
// DoRequest is the same as Do except that it returns
 
212
// an httptest.ResponseRecorder instead of an http.Response.
 
213
// This function exists for backward compatibility reasons.
 
214
func DoRequest(c *gc.C, p DoRequestParams) *httptest.ResponseRecorder {
 
215
        resp := Do(c, p)
 
216
        if p.ExpectError != "" {
 
217
                return nil
 
218
        }
 
219
        defer resp.Body.Close()
 
220
        rec := httptest.NewRecorder()
 
221
        h := rec.Header()
 
222
        for k, v := range resp.Header {
 
223
                h[k] = v
 
224
        }
 
225
        rec.WriteHeader(resp.StatusCode)
 
226
        _, err := io.Copy(rec.Body, resp.Body)
 
227
        c.Assert(err, jc.ErrorIsNil)
 
228
        return rec
 
229
}
 
230
 
 
231
// Do invokes a request on the given handler with the given
 
232
// parameters and returns the resulting HTTP response.
 
233
// Note that, as with http.Client.Do, the response body
 
234
// must be closed.
 
235
func Do(c *gc.C, p DoRequestParams) *http.Response {
 
236
        if p.Method == "" {
 
237
                p.Method = "GET"
 
238
        }
 
239
        if p.Do == nil {
 
240
                p.Do = http.DefaultClient.Do
 
241
        }
 
242
        if reqURL, err := url.Parse(p.URL); err == nil && reqURL.Host == "" {
 
243
                srv := httptest.NewServer(p.Handler)
 
244
                defer srv.Close()
 
245
                p.URL = srv.URL + p.URL
 
246
        }
 
247
        if p.JSONBody != nil {
 
248
                data, err := json.Marshal(p.JSONBody)
 
249
                c.Assert(err, jc.ErrorIsNil)
 
250
                p.Body = bytes.NewReader(data)
 
251
        }
 
252
        // Note: we avoid NewRequest's odious reader wrapping by using
 
253
        // a custom nopCloser function.
 
254
        req, err := http.NewRequest(p.Method, p.URL, nopCloser(p.Body))
 
255
        c.Assert(err, jc.ErrorIsNil)
 
256
        if p.JSONBody != nil {
 
257
                req.Header.Set("Content-Type", "application/json")
 
258
        }
 
259
        for key, val := range p.Header {
 
260
                req.Header[key] = val
 
261
        }
 
262
        if p.ContentLength != 0 {
 
263
                req.ContentLength = p.ContentLength
 
264
        } else {
 
265
                req.ContentLength = bodyContentLength(p.Body)
 
266
        }
 
267
        if p.Username != "" || p.Password != "" {
 
268
                req.SetBasicAuth(p.Username, p.Password)
 
269
        }
 
270
        for _, cookie := range p.Cookies {
 
271
                req.AddCookie(cookie)
 
272
        }
 
273
        resp, err := p.Do(req)
 
274
        if p.ExpectError != "" {
 
275
                c.Assert(err, gc.ErrorMatches, p.ExpectError)
 
276
                return nil
 
277
        }
 
278
        c.Assert(err, jc.ErrorIsNil)
 
279
        return resp
 
280
}
 
281
 
 
282
// bodyContentLength returns the Content-Length
 
283
// to use for the given body. Usually http.NewRequest
 
284
// would infer this (and the cases here come directly
 
285
// from the logic in that function) but unfortunately
 
286
// there's no way to avoid the NopCloser wrapping
 
287
// for any of the types mentioned here.
 
288
func bodyContentLength(body io.Reader) int64 {
 
289
        n := 0
 
290
        switch v := body.(type) {
 
291
        case *bytes.Buffer:
 
292
                n = v.Len()
 
293
        case *bytes.Reader:
 
294
                n = v.Len()
 
295
        case *strings.Reader:
 
296
                n = v.Len()
 
297
        }
 
298
        return int64(n)
 
299
}
 
300
 
 
301
// nopCloser is like ioutil.NopCloser except that
 
302
// the returned value implements io.Seeker if
 
303
// r implements io.Seeker
 
304
func nopCloser(r io.Reader) io.ReadCloser {
 
305
        if r == nil {
 
306
                return nil
 
307
        }
 
308
        rc, ok := r.(io.ReadCloser)
 
309
        if ok {
 
310
                return rc
 
311
        }
 
312
        rs, ok := r.(io.ReadSeeker)
 
313
        if ok {
 
314
                return readSeekNopCloser{rs}
 
315
        }
 
316
        return ioutil.NopCloser(r)
 
317
}
 
318
 
 
319
type readSeekNopCloser struct {
 
320
        io.ReadSeeker
 
321
}
 
322
 
 
323
func (readSeekNopCloser) Close() error {
 
324
        return nil
 
325
}
 
326
 
 
327
// URLRewritingTransport is an http.RoundTripper that can rewrite request
 
328
// URLs. If the request URL has the prefix specified in Match that part
 
329
// will be changed to the value specified in Replace. RoundTripper will
 
330
// then be used to perform the resulting request. If RoundTripper is nil
 
331
// http.DefaultTransport will be used.
 
332
//
 
333
// This can be used in tests that, for whatever reason, need to make a
 
334
// call to a URL that's not in our control but we want to control the
 
335
// results of HTTP requests to that URL.
 
336
type URLRewritingTransport struct {
 
337
        MatchPrefix  string
 
338
        Replace      string
 
339
        RoundTripper http.RoundTripper
 
340
}
 
341
 
 
342
// RoundTrip implements http.RoundTripper.
 
343
func (t URLRewritingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
 
344
        rt := t.RoundTripper
 
345
        if rt == nil {
 
346
                rt = http.DefaultTransport
 
347
        }
 
348
        if !strings.HasPrefix(req.URL.String(), t.MatchPrefix) {
 
349
                return rt.RoundTrip(req)
 
350
        }
 
351
        req1 := *req
 
352
        var err error
 
353
        req1.URL, err = url.Parse(t.Replace + strings.TrimPrefix(req.URL.String(), t.MatchPrefix))
 
354
        if err != nil {
 
355
                panic(err)
 
356
        }
 
357
        resp, err := rt.RoundTrip(&req1)
 
358
        if resp != nil {
 
359
                resp.Request = req
 
360
        }
 
361
        return resp, err
 
362
}