1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the LGPLv3, see LICENCE file for details.
17
gc "gopkg.in/check.v1"
19
jc "github.com/juju/testing/checkers"
22
// BodyAsserter represents a function that can assert the correctness of
24
type BodyAsserter func(c *gc.C, body json.RawMessage)
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)
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
41
// Method holds the HTTP method to use for the call.
42
// GET is assumed if this is empty.
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.
51
// Handler holds the handler to use to make the request.
52
// It is ignored if the above URL field has a host part.
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.
62
// Body holds the body to send in the request.
65
// Header specifies the HTTP headers to use when making
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.
74
// Username, if specified, is used for HTTP basic authentication.
77
// Password, if specified, is used for HTTP basic authentication.
80
// ExpectStatus holds the expected HTTP status code.
81
// http.StatusOK is assumed if this is zero.
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
88
ExpectBody interface{}
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
94
// Cookies, if specified, are added to the request.
95
Cookies []*http.Cookie
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
105
rec := DoRequest(c, DoRequestParams{
107
ExpectError: p.ExpectError,
112
JSONBody: p.JSONBody,
114
ContentLength: p.ContentLength,
115
Username: p.Username,
116
Password: p.Password,
119
if p.ExpectError != "" {
122
AssertJSONResponse(c, rec, p.ExpectStatus, p.ExpectBody)
124
for k, v := range p.ExpectHeader {
125
c.Assert(rec.HeaderMap[textproto.CanonicalMIMEHeaderKey(k)], gc.DeepEquals, v, gc.Commentf("header %q", k))
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()))
136
// Ensure the response includes the expected body.
137
if expectBody == nil {
138
c.Assert(rec.Body.Bytes(), gc.HasLen, 0)
141
c.Assert(rec.Header().Get("Content-Type"), gc.Equals, "application/json")
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()))
150
c.Assert(rec.Body.String(), jc.JSONEquals, expectBody)
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)
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
168
// Method holds the HTTP method to use for the call.
169
// GET is assumed if this is empty.
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.
178
// Handler holds the handler to use to make the request.
179
// It is ignored if the above URL field has a host part.
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.
189
// Body holds the body to send in the request.
192
// Header specifies the HTTP headers to use when making
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.
201
// Username, if specified, is used for HTTP basic authentication.
204
// Password, if specified, is used for HTTP basic authentication.
207
// Cookies, if specified, are added to the request.
208
Cookies []*http.Cookie
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 {
216
if p.ExpectError != "" {
219
defer resp.Body.Close()
220
rec := httptest.NewRecorder()
222
for k, v := range resp.Header {
225
rec.WriteHeader(resp.StatusCode)
226
_, err := io.Copy(rec.Body, resp.Body)
227
c.Assert(err, jc.ErrorIsNil)
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
235
func Do(c *gc.C, p DoRequestParams) *http.Response {
240
p.Do = http.DefaultClient.Do
242
if reqURL, err := url.Parse(p.URL); err == nil && reqURL.Host == "" {
243
srv := httptest.NewServer(p.Handler)
245
p.URL = srv.URL + p.URL
247
if p.JSONBody != nil {
248
data, err := json.Marshal(p.JSONBody)
249
c.Assert(err, jc.ErrorIsNil)
250
p.Body = bytes.NewReader(data)
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")
259
for key, val := range p.Header {
260
req.Header[key] = val
262
if p.ContentLength != 0 {
263
req.ContentLength = p.ContentLength
265
req.ContentLength = bodyContentLength(p.Body)
267
if p.Username != "" || p.Password != "" {
268
req.SetBasicAuth(p.Username, p.Password)
270
for _, cookie := range p.Cookies {
271
req.AddCookie(cookie)
273
resp, err := p.Do(req)
274
if p.ExpectError != "" {
275
c.Assert(err, gc.ErrorMatches, p.ExpectError)
278
c.Assert(err, jc.ErrorIsNil)
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 {
290
switch v := body.(type) {
295
case *strings.Reader:
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 {
308
rc, ok := r.(io.ReadCloser)
312
rs, ok := r.(io.ReadSeeker)
314
return readSeekNopCloser{rs}
316
return ioutil.NopCloser(r)
319
type readSeekNopCloser struct {
323
func (readSeekNopCloser) Close() error {
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.
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 {
339
RoundTripper http.RoundTripper
342
// RoundTrip implements http.RoundTripper.
343
func (t URLRewritingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
346
rt = http.DefaultTransport
348
if !strings.HasPrefix(req.URL.String(), t.MatchPrefix) {
349
return rt.RoundTrip(req)
353
req1.URL, err = url.Parse(t.Replace + strings.TrimPrefix(req.URL.String(), t.MatchPrefix))
357
resp, err := rt.RoundTrip(&req1)