~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/httprequest/unmarshal_test.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 2015 Canonical Ltd.
 
2
// Licensed under the LGPLv3, see LICENCE file for details.
 
3
 
 
4
package httprequest_test
 
5
 
 
6
import (
 
7
        "fmt"
 
8
        "io"
 
9
        "io/ioutil"
 
10
        "net/http"
 
11
        "net/url"
 
12
        "reflect"
 
13
        "strings"
 
14
 
 
15
        jc "github.com/juju/testing/checkers"
 
16
        "github.com/julienschmidt/httprouter"
 
17
        gc "gopkg.in/check.v1"
 
18
 
 
19
        "github.com/juju/httprequest"
 
20
)
 
21
 
 
22
type unmarshalSuite struct{}
 
23
 
 
24
var _ = gc.Suite(&unmarshalSuite{})
 
25
 
 
26
var unmarshalTests = []struct {
 
27
        about       string
 
28
        val         interface{}
 
29
        expect      interface{}
 
30
        params      httprequest.Params
 
31
        expectError string
 
32
        // TODO expectErrorCause func(error) bool
 
33
}{{
 
34
        about: "struct with simple fields",
 
35
        val: struct {
 
36
                F1          int    `httprequest:",form"`
 
37
                F2          int    `httprequest:",form"`
 
38
                G1          string `httprequest:",path"`
 
39
                G2          string `httprequest:",path"`
 
40
                H           string `httprequest:",body"`
 
41
                UnknownForm string `httprequest:",form"`
 
42
                UnknownPath string `httprequest:",path"`
 
43
        }{
 
44
                F1: 99,
 
45
                F2: -35,
 
46
                G1: "g1 val",
 
47
                G2: "g2 val",
 
48
                H:  "h val",
 
49
        },
 
50
        params: httprequest.Params{
 
51
                Request: &http.Request{
 
52
                        Header: http.Header{"Content-Type": {"application/json"}},
 
53
                        Form: url.Values{
 
54
                                "F1": {"99"},
 
55
                                "F2": {"-35", "not a number"},
 
56
                        },
 
57
                        Body: body(`"h val"`),
 
58
                },
 
59
                PathVar: httprouter.Params{{
 
60
                        Key:   "G2",
 
61
                        Value: "g2 val",
 
62
                }, {
 
63
                        Key:   "G1",
 
64
                        Value: "g1 val",
 
65
                }, {
 
66
                        Key:   "G1",
 
67
                        Value: "g1 wrong val",
 
68
                }},
 
69
        },
 
70
}, {
 
71
        about: "struct with renamed fields",
 
72
        val: struct {
 
73
                F1 int    `httprequest:"x1,form"`
 
74
                F2 int    `httprequest:"x2,form"`
 
75
                G1 string `httprequest:"g1,path"`
 
76
                G2 string `httprequest:"g2,path"`
 
77
        }{
 
78
                F1: 99,
 
79
                F2: -35,
 
80
                G1: "g1 val",
 
81
                G2: "g2 val",
 
82
        },
 
83
        params: httprequest.Params{
 
84
                Request: &http.Request{
 
85
                        Form: url.Values{
 
86
                                "x1": {"99"},
 
87
                                "x2": {"-35", "not a number"},
 
88
                        },
 
89
                },
 
90
                PathVar: httprouter.Params{{
 
91
                        Key:   "g2",
 
92
                        Value: "g2 val",
 
93
                }, {
 
94
                        Key:   "g1",
 
95
                        Value: "g1 val",
 
96
                }, {
 
97
                        Key:   "g1",
 
98
                        Value: "g1 wrong val",
 
99
                }},
 
100
        },
 
101
}, {
 
102
        about: "unexported fields are ignored",
 
103
        val: struct {
 
104
                f int `httprequest:",form"`
 
105
                G int `httprequest:",form"`
 
106
        }{
 
107
                G: 99,
 
108
        },
 
109
        params: httprequest.Params{
 
110
                Request: &http.Request{
 
111
                        Form: url.Values{
 
112
                                "G": {"99"},
 
113
                                "f": {"100"},
 
114
                        },
 
115
                },
 
116
        },
 
117
}, {
 
118
        about: "unexported embedded type works ok",
 
119
        val: struct {
 
120
                sFG
 
121
        }{
 
122
                sFG: sFG{
 
123
                        F: 99,
 
124
                        G: 100,
 
125
                },
 
126
        },
 
127
        params: httprequest.Params{
 
128
                Request: &http.Request{
 
129
                        Form: url.Values{
 
130
                                "F": {"99"},
 
131
                                "G": {"100"},
 
132
                        },
 
133
                },
 
134
        },
 
135
}, {
 
136
        about: "unexported type for body is ignored",
 
137
        val: struct {
 
138
                foo sFG `httprequest:",body"`
 
139
        }{},
 
140
        params: httprequest.Params{
 
141
                Request: &http.Request{
 
142
                        Header: http.Header{"Content-Type": {"application/json"}},
 
143
                        Body:   body(`{"F": 99, "G": 100}`),
 
144
                },
 
145
        },
 
146
}, {
 
147
        about: "fields without httprequest tags are ignored",
 
148
        val: struct {
 
149
                F int
 
150
        }{},
 
151
        params: httprequest.Params{
 
152
                Request: &http.Request{
 
153
                        Form: url.Values{
 
154
                                "F": {"foo"},
 
155
                        },
 
156
                },
 
157
                PathVar: httprouter.Params{{
 
158
                        Key:   "F",
 
159
                        Value: "foo",
 
160
                }},
 
161
        },
 
162
}, {
 
163
        about: "pointer fields are filled out",
 
164
        val: struct {
 
165
                F *int `httprequest:",form"`
 
166
                *SFG
 
167
                S *string `httprequest:",form"`
 
168
                T *string `httprequest:",form"`
 
169
        }{
 
170
                F: newInt(99),
 
171
                SFG: &SFG{
 
172
                        F: 0,
 
173
                        G: 534,
 
174
                },
 
175
                S: newString("s val"),
 
176
        },
 
177
        params: httprequest.Params{
 
178
                Request: &http.Request{
 
179
                        Form: url.Values{
 
180
                                "F": {"99"},
 
181
                                "G": {"534"},
 
182
                                "S": {"s val"},
 
183
                        },
 
184
                },
 
185
        },
 
186
}, {
 
187
        about: "UnmarshalText called on TextUnmarshalers",
 
188
        val: struct {
 
189
                F  exclamationUnmarshaler  `httprequest:",form"`
 
190
                G  exclamationUnmarshaler  `httprequest:",path"`
 
191
                FP *exclamationUnmarshaler `httprequest:",form"`
 
192
        }{
 
193
                F:  "yes!",
 
194
                G:  "no!",
 
195
                FP: (*exclamationUnmarshaler)(newString("maybe!")),
 
196
        },
 
197
        params: httprequest.Params{
 
198
                Request: &http.Request{
 
199
                        Form: url.Values{
 
200
                                "F":  {"yes"},
 
201
                                "FP": {"maybe"},
 
202
                        },
 
203
                },
 
204
                PathVar: httprouter.Params{{
 
205
                        Key:   "G",
 
206
                        Value: "no",
 
207
                }},
 
208
        },
 
209
}, {
 
210
        about: "UnmarshalText not called on values with a non-TextUnmarshaler UnmarshalText method",
 
211
        val: struct {
 
212
                F notTextUnmarshaler `httprequest:",form"`
 
213
        }{
 
214
                F: "hello",
 
215
        },
 
216
        params: httprequest.Params{
 
217
                Request: &http.Request{
 
218
                        Form: url.Values{
 
219
                                "F": {"hello"},
 
220
                        },
 
221
                },
 
222
        },
 
223
}, {
 
224
        about: "UnmarshalText returning an error",
 
225
        val: struct {
 
226
                F exclamationUnmarshaler `httprequest:",form"`
 
227
        }{},
 
228
        params: httprequest.Params{
 
229
                Request: &http.Request{},
 
230
        },
 
231
        expectError: "cannot unmarshal into field: empty string!",
 
232
}, {
 
233
        about: "all field form values",
 
234
        val: struct {
 
235
                A []string  `httprequest:",form"`
 
236
                B *[]string `httprequest:",form"`
 
237
                C []string  `httprequest:",form"`
 
238
                D *[]string `httprequest:",form"`
 
239
        }{
 
240
                A: []string{"a1", "a2"},
 
241
                B: func() *[]string {
 
242
                        x := []string{"b1", "b2", "b3"}
 
243
                        return &x
 
244
                }(),
 
245
        },
 
246
        params: httprequest.Params{
 
247
                Request: &http.Request{
 
248
                        Form: url.Values{
 
249
                                "A": {"a1", "a2"},
 
250
                                "B": {"b1", "b2", "b3"},
 
251
                        },
 
252
                },
 
253
        },
 
254
}, {
 
255
        about: "invalid scan field",
 
256
        val: struct {
 
257
                A int `httprequest:",form"`
 
258
        }{},
 
259
        params: httprequest.Params{
 
260
                Request: &http.Request{
 
261
                        Form: url.Values{
 
262
                                "A": {"not an int"},
 
263
                        },
 
264
                },
 
265
        },
 
266
        expectError: `cannot unmarshal into field: cannot parse "not an int" into int: expected integer`,
 
267
}, {
 
268
        about: "scan field not present",
 
269
        val: struct {
 
270
                A int `httprequest:",form"`
 
271
        }{},
 
272
        params: httprequest.Params{
 
273
                Request: &http.Request{},
 
274
        },
 
275
}, {
 
276
        about: "invalid JSON body",
 
277
        val: struct {
 
278
                A string `httprequest:",body"`
 
279
        }{},
 
280
        params: httprequest.Params{
 
281
                Request: &http.Request{
 
282
                        Header: http.Header{"Content-Type": {"application/json"}},
 
283
                        Body:   body("invalid JSON"),
 
284
                },
 
285
        },
 
286
        expectError: "cannot unmarshal into field: cannot unmarshal request body: invalid character 'i' looking for beginning of value",
 
287
}, {
 
288
        about: "body with read error",
 
289
        val: struct {
 
290
                A string `httprequest:",body"`
 
291
        }{},
 
292
        params: httprequest.Params{
 
293
                Request: &http.Request{
 
294
                        Header: http.Header{"Content-Type": {"application/json"}},
 
295
                        Body:   errorReader("some error"),
 
296
                },
 
297
        },
 
298
        expectError: "cannot unmarshal into field: cannot read request body: some error",
 
299
}, {
 
300
        about: "[]string not allowed for URL source",
 
301
        val: struct {
 
302
                A []string `httprequest:",path"`
 
303
        }{},
 
304
        expectError: `bad type .*: invalid target type \[]string for path parameter`,
 
305
}, {
 
306
        about: "duplicated body",
 
307
        val: struct {
 
308
                B1 int    `httprequest:",body"`
 
309
                B2 string `httprequest:",body"`
 
310
        }{},
 
311
        expectError: "bad type .*: more than one body field specified",
 
312
}, {
 
313
        about: "body tag name is ignored",
 
314
        val: struct {
 
315
                B string `httprequest:"foo,body"`
 
316
        }{
 
317
                B: "hello",
 
318
        },
 
319
        params: httprequest.Params{
 
320
                Request: &http.Request{
 
321
                        Header: http.Header{"Content-Type": {"application/json"}},
 
322
                        Body:   body(`"hello"`),
 
323
                },
 
324
        },
 
325
}, {
 
326
        about: "tag with invalid source",
 
327
        val: struct {
 
328
                B1 int `httprequest:",xxx"`
 
329
        }{},
 
330
        expectError: `bad type .*: bad tag "httprequest:\\",xxx\\"" in field B1: unknown tag flag "xxx"`,
 
331
}, {
 
332
        about:       "non-struct pointer",
 
333
        val:         0,
 
334
        expectError: `bad type \*int: type is not pointer to struct`,
 
335
}, {
 
336
        about: "unmarshaling with wrong request content type",
 
337
        val: struct {
 
338
                A string `httprequest:",body"`
 
339
        }{},
 
340
        params: httprequest.Params{
 
341
                Request: &http.Request{
 
342
                        Header: http.Header{"Content-Type": {"text/html"}},
 
343
                        Body:   body("invalid JSON"),
 
344
                },
 
345
        },
 
346
        expectError: `cannot unmarshal into field: unexpected content type text/html; want application/json; content: invalid JSON`,
 
347
}, {
 
348
        about: "struct with header fields",
 
349
        val: struct {
 
350
                F1 int    `httprequest:"x1,header"`
 
351
                G1 string `httprequest:"g1,header"`
 
352
        }{
 
353
                F1: 99,
 
354
                G1: "g1 val",
 
355
        },
 
356
        params: httprequest.Params{
 
357
                Request: &http.Request{
 
358
                        Header: http.Header{
 
359
                                "x1": {"99"},
 
360
                                "g1": {"g1 val"},
 
361
                        },
 
362
                },
 
363
        },
 
364
}, {
 
365
        about: "all field header values",
 
366
        val: struct {
 
367
                A []string  `httprequest:",header"`
 
368
                B *[]string `httprequest:",header"`
 
369
                C []string  `httprequest:",header"`
 
370
                D *[]string `httprequest:",header"`
 
371
        }{
 
372
                A: []string{"a1", "a2"},
 
373
                B: func() *[]string {
 
374
                        x := []string{"b1", "b2", "b3"}
 
375
                        return &x
 
376
                }(),
 
377
        },
 
378
        params: httprequest.Params{
 
379
                Request: &http.Request{
 
380
                        Header: http.Header{
 
381
                                "A": {"a1", "a2"},
 
382
                                "B": {"b1", "b2", "b3"},
 
383
                        },
 
384
                },
 
385
        },
 
386
}}
 
387
 
 
388
type SFG struct {
 
389
        F int `httprequest:",form"`
 
390
        G int `httprequest:",form"`
 
391
}
 
392
 
 
393
type sFG struct {
 
394
        F int `httprequest:",form"`
 
395
        G int `httprequest:",form"`
 
396
}
 
397
 
 
398
func (*unmarshalSuite) TestUnmarshal(c *gc.C) {
 
399
        for i, test := range unmarshalTests {
 
400
                c.Logf("%d: %s", i, test.about)
 
401
                t := reflect.TypeOf(test.val)
 
402
                fillv := reflect.New(t)
 
403
                err := httprequest.Unmarshal(test.params, fillv.Interface())
 
404
                if test.expectError != "" {
 
405
                        c.Assert(err, gc.ErrorMatches, test.expectError)
 
406
                        continue
 
407
                }
 
408
                c.Assert(fillv.Elem().Interface(), jc.DeepEquals, test.val)
 
409
        }
 
410
}
 
411
 
 
412
// TODO non-pointer struct
 
413
 
 
414
type notTextUnmarshaler string
 
415
 
 
416
// UnmarshalText does *not* implement encoding.TextUnmarshaler
 
417
// (it has no arguments or error return value)
 
418
func (t *notTextUnmarshaler) UnmarshalText() {
 
419
        panic("unexpected call")
 
420
}
 
421
 
 
422
type exclamationUnmarshaler string
 
423
 
 
424
func (t *exclamationUnmarshaler) UnmarshalText(b []byte) error {
 
425
        if len(b) == 0 {
 
426
                return fmt.Errorf("empty string!")
 
427
        }
 
428
        *t = exclamationUnmarshaler(b) + "!"
 
429
        return nil
 
430
}
 
431
 
 
432
func newInt(i int) *int {
 
433
        return &i
 
434
}
 
435
 
 
436
func newString(s string) *string {
 
437
        return &s
 
438
}
 
439
 
 
440
type errorReader string
 
441
 
 
442
func (r errorReader) Read([]byte) (int, error) {
 
443
        return 0, fmt.Errorf("%s", r)
 
444
}
 
445
 
 
446
func (r errorReader) Close() error {
 
447
        return nil
 
448
}
 
449
 
 
450
func body(s string) io.ReadCloser {
 
451
        return ioutil.NopCloser(strings.NewReader(s))
 
452
}