~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/gopkg.in/juju/charmstore.v5-unstable/internal/v4/log_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 2014 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package v4_test // import "gopkg.in/juju/charmstore.v5-unstable/internal/v4"
 
5
 
 
6
import (
 
7
        "bytes"
 
8
        "encoding/json"
 
9
        "net/http"
 
10
        "time"
 
11
 
 
12
        jc "github.com/juju/testing/checkers"
 
13
        "github.com/juju/testing/httptesting"
 
14
        gc "gopkg.in/check.v1"
 
15
        "gopkg.in/juju/charm.v6-unstable"
 
16
        "gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
 
17
 
 
18
        "gopkg.in/juju/charmstore.v5-unstable/internal/mongodoc"
 
19
)
 
20
 
 
21
type logSuite struct {
 
22
        commonSuite
 
23
}
 
24
 
 
25
var _ = gc.Suite(&logSuite{})
 
26
 
 
27
func (s *logSuite) SetUpSuite(c *gc.C) {
 
28
        s.enableIdentity = true
 
29
        s.commonSuite.SetUpSuite(c)
 
30
}
 
31
 
 
32
var logResponses = map[string]*params.LogResponse{
 
33
        "info1": {
 
34
                Data:  rawMessage("info data 1"),
 
35
                Level: params.InfoLevel,
 
36
                Type:  params.IngestionType,
 
37
        },
 
38
        "error1": {
 
39
                Data:  rawMessage("error data 1"),
 
40
                Level: params.ErrorLevel,
 
41
                Type:  params.IngestionType,
 
42
        },
 
43
        "info2": {
 
44
                Data:  rawMessage("info data 2"),
 
45
                Level: params.InfoLevel,
 
46
                Type:  params.IngestionType,
 
47
                URLs: []*charm.URL{
 
48
                        charm.MustParseURL("precise/django"),
 
49
                        charm.MustParseURL("django"),
 
50
                        charm.MustParseURL("rails"),
 
51
                },
 
52
        },
 
53
        "warning1": {
 
54
                Data:  rawMessage("warning data 1"),
 
55
                Level: params.WarningLevel,
 
56
                Type:  params.IngestionType,
 
57
        },
 
58
        "error2": {
 
59
                Data:  rawMessage("error data 2"),
 
60
                Level: params.ErrorLevel,
 
61
                Type:  params.IngestionType,
 
62
                URLs: []*charm.URL{
 
63
                        charm.MustParseURL("hadoop"),
 
64
                },
 
65
        },
 
66
        "info3": {
 
67
                Data:  rawMessage("info data 3"),
 
68
                Level: params.InfoLevel,
 
69
                Type:  params.IngestionType,
 
70
                URLs: []*charm.URL{
 
71
                        charm.MustParseURL("trusty/django"),
 
72
                        charm.MustParseURL("django"),
 
73
                        charm.MustParseURL("utopic/hadoop"),
 
74
                        charm.MustParseURL("hadoop"),
 
75
                },
 
76
        },
 
77
        "error3": {
 
78
                Data:  rawMessage("error data 3"),
 
79
                Level: params.ErrorLevel,
 
80
                Type:  params.IngestionType,
 
81
                URLs: []*charm.URL{
 
82
                        charm.MustParseURL("utopic/hadoop"),
 
83
                        charm.MustParseURL("hadoop"),
 
84
                        charm.MustParseURL("precise/django"),
 
85
                        charm.MustParseURL("django"),
 
86
                },
 
87
        },
 
88
        "stats": {
 
89
                Data:  rawMessage("statistics info data"),
 
90
                Level: params.InfoLevel,
 
91
                Type:  params.LegacyStatisticsType,
 
92
        },
 
93
}
 
94
 
 
95
var getLogsTests = []struct {
 
96
        about       string
 
97
        querystring string
 
98
        expectBody  []*params.LogResponse
 
99
}{{
 
100
        about: "retrieve logs",
 
101
        expectBody: []*params.LogResponse{
 
102
                logResponses["stats"],
 
103
                logResponses["error3"],
 
104
                logResponses["info3"],
 
105
                logResponses["error2"],
 
106
                logResponses["warning1"],
 
107
                logResponses["info2"],
 
108
                logResponses["error1"],
 
109
                logResponses["info1"],
 
110
        },
 
111
}, {
 
112
        about:       "use limit",
 
113
        querystring: "?limit=2",
 
114
        expectBody: []*params.LogResponse{
 
115
                logResponses["stats"],
 
116
                logResponses["error3"],
 
117
        },
 
118
}, {
 
119
        about:       "use offset",
 
120
        querystring: "?skip=3",
 
121
        expectBody: []*params.LogResponse{
 
122
                logResponses["error2"],
 
123
                logResponses["warning1"],
 
124
                logResponses["info2"],
 
125
                logResponses["error1"],
 
126
                logResponses["info1"],
 
127
        },
 
128
}, {
 
129
        about:       "zero offset",
 
130
        querystring: "?skip=0",
 
131
        expectBody: []*params.LogResponse{
 
132
                logResponses["stats"],
 
133
                logResponses["error3"],
 
134
                logResponses["info3"],
 
135
                logResponses["error2"],
 
136
                logResponses["warning1"],
 
137
                logResponses["info2"],
 
138
                logResponses["error1"],
 
139
                logResponses["info1"],
 
140
        },
 
141
}, {
 
142
        about:       "use both limit and offset",
 
143
        querystring: "?limit=3&skip=1",
 
144
        expectBody: []*params.LogResponse{
 
145
                logResponses["error3"],
 
146
                logResponses["info3"],
 
147
                logResponses["error2"],
 
148
        },
 
149
}, {
 
150
        about:       "filter by level",
 
151
        querystring: "?level=info",
 
152
        expectBody: []*params.LogResponse{
 
153
                logResponses["stats"],
 
154
                logResponses["info3"],
 
155
                logResponses["info2"],
 
156
                logResponses["info1"],
 
157
        },
 
158
}, {
 
159
        about:       "filter by type",
 
160
        querystring: "?type=ingestion",
 
161
        expectBody: []*params.LogResponse{
 
162
                logResponses["error3"],
 
163
                logResponses["info3"],
 
164
                logResponses["error2"],
 
165
                logResponses["warning1"],
 
166
                logResponses["info2"],
 
167
                logResponses["error1"],
 
168
                logResponses["info1"],
 
169
        },
 
170
}, {
 
171
        about:       "filter by level with a limit",
 
172
        querystring: "?level=error&limit=2",
 
173
        expectBody: []*params.LogResponse{
 
174
                logResponses["error3"],
 
175
                logResponses["error2"],
 
176
        },
 
177
}, {
 
178
        about:       "filter by id",
 
179
        querystring: "?id=precise/django",
 
180
        expectBody: []*params.LogResponse{
 
181
                logResponses["error3"],
 
182
                logResponses["info2"],
 
183
        },
 
184
}, {
 
185
        about:       "multiple query",
 
186
        querystring: "?id=utopic/hadoop&limit=1&level=error",
 
187
        expectBody: []*params.LogResponse{
 
188
                logResponses["error3"],
 
189
        },
 
190
}, {
 
191
        about:       "empty response offset",
 
192
        querystring: "?id=utopic/hadoop&skip=10",
 
193
}, {
 
194
        about:       "empty response id not found",
 
195
        querystring: "?id=utopic/mysql",
 
196
}, {
 
197
        about:       "empty response level",
 
198
        querystring: "?id=trusty/rails&level=error",
 
199
}, {
 
200
        about:       "filter by type - legacyStatistics",
 
201
        querystring: "?type=legacyStatistics",
 
202
        expectBody: []*params.LogResponse{
 
203
                logResponses["stats"],
 
204
        },
 
205
}}
 
206
 
 
207
var paramsLogLevels = map[params.LogLevel]mongodoc.LogLevel{
 
208
        params.InfoLevel:    mongodoc.InfoLevel,
 
209
        params.WarningLevel: mongodoc.WarningLevel,
 
210
        params.ErrorLevel:   mongodoc.ErrorLevel,
 
211
}
 
212
 
 
213
// paramsLogTypes maps API params log types to internal mongodoc ones.
 
214
var paramsLogTypes = map[params.LogType]mongodoc.LogType{
 
215
        params.IngestionType:        mongodoc.IngestionType,
 
216
        params.LegacyStatisticsType: mongodoc.LegacyStatisticsType,
 
217
}
 
218
 
 
219
func (s *logSuite) TestGetLogs(c *gc.C) {
 
220
        // Add logs to the database.
 
221
        beforeAdding := time.Now().Add(-time.Second)
 
222
        for _, key := range []string{"info1", "error1", "info2", "warning1", "error2", "info3", "error3", "stats"} {
 
223
                resp := logResponses[key]
 
224
                err := s.store.AddLog(&resp.Data, paramsLogLevels[resp.Level], paramsLogTypes[resp.Type], resp.URLs)
 
225
                c.Assert(err, gc.IsNil)
 
226
        }
 
227
        afterAdding := time.Now().Add(time.Second)
 
228
 
 
229
        // Run the tests.
 
230
        for i, test := range getLogsTests {
 
231
                c.Logf("test %d: %s", i, test.about)
 
232
                rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
 
233
                        Handler:  s.srv,
 
234
                        URL:      storeURL("log" + test.querystring),
 
235
                        Username: testUsername,
 
236
                        Password: testPassword,
 
237
                })
 
238
 
 
239
                // Ensure the response is what we expect.
 
240
                c.Assert(rec.Code, gc.Equals, http.StatusOK)
 
241
                c.Assert(rec.Header().Get("Content-Type"), gc.Equals, "application/json")
 
242
 
 
243
                // Decode the response.
 
244
                var logs []*params.LogResponse
 
245
                decoder := json.NewDecoder(rec.Body)
 
246
                err := decoder.Decode(&logs)
 
247
                c.Assert(err, gc.IsNil)
 
248
 
 
249
                // Check and then reset the response time so that the whole body
 
250
                // can be more easily compared later.
 
251
                for _, log := range logs {
 
252
                        c.Assert(log.Time, jc.TimeBetween(beforeAdding, afterAdding))
 
253
                        log.Time = time.Time{}
 
254
                }
 
255
 
 
256
                // Ensure the response includes the expected logs.
 
257
                c.Assert(logs, jc.DeepEquals, test.expectBody)
 
258
        }
 
259
}
 
260
 
 
261
func rawMessage(msg string) json.RawMessage {
 
262
        message, err := json.Marshal(msg)
 
263
        if err != nil {
 
264
                panic(err)
 
265
        }
 
266
        return json.RawMessage(message)
 
267
}
 
268
 
 
269
var getLogsErrorsTests = []struct {
 
270
        about         string
 
271
        querystring   string
 
272
        expectStatus  int
 
273
        expectMessage string
 
274
        expectCode    params.ErrorCode
 
275
}{{
 
276
        about:         "invalid limit (negative number)",
 
277
        querystring:   "?limit=-100",
 
278
        expectStatus:  http.StatusBadRequest,
 
279
        expectMessage: "invalid limit value: value must be >= 1",
 
280
        expectCode:    params.ErrBadRequest,
 
281
}, {
 
282
        about:         "invalid limit (zero value)",
 
283
        querystring:   "?limit=0",
 
284
        expectStatus:  http.StatusBadRequest,
 
285
        expectMessage: "invalid limit value: value must be >= 1",
 
286
        expectCode:    params.ErrBadRequest,
 
287
}, {
 
288
        about:         "invalid limit (not a number)",
 
289
        querystring:   "?limit=foo",
 
290
        expectStatus:  http.StatusBadRequest,
 
291
        expectMessage: "invalid limit value: value must be a number",
 
292
        expectCode:    params.ErrBadRequest,
 
293
}, {
 
294
        about:         "invalid offset (negative number)",
 
295
        querystring:   "?skip=-100",
 
296
        expectStatus:  http.StatusBadRequest,
 
297
        expectMessage: "invalid skip value: value must be >= 0",
 
298
        expectCode:    params.ErrBadRequest,
 
299
}, {
 
300
        about:         "invalid offset (not a number)",
 
301
        querystring:   "?skip=bar",
 
302
        expectStatus:  http.StatusBadRequest,
 
303
        expectMessage: "invalid skip value: value must be a number",
 
304
        expectCode:    params.ErrBadRequest,
 
305
}, {
 
306
        about:         "invalid id",
 
307
        querystring:   "?id=no-such:reference",
 
308
        expectStatus:  http.StatusBadRequest,
 
309
        expectMessage: `invalid id value: charm or bundle URL has invalid schema: "no-such:reference"`,
 
310
        expectCode:    params.ErrBadRequest,
 
311
}, {
 
312
        about:         "invalid log level",
 
313
        querystring:   "?level=bar",
 
314
        expectStatus:  http.StatusBadRequest,
 
315
        expectMessage: "invalid log level value",
 
316
        expectCode:    params.ErrBadRequest,
 
317
}, {
 
318
        about:         "invalid log type",
 
319
        querystring:   "?type=no-such",
 
320
        expectStatus:  http.StatusBadRequest,
 
321
        expectMessage: "invalid log type value",
 
322
        expectCode:    params.ErrBadRequest,
 
323
}}
 
324
 
 
325
func (s *logSuite) TestGetLogsErrors(c *gc.C) {
 
326
        for i, test := range getLogsErrorsTests {
 
327
                c.Logf("test %d: %s", i, test.about)
 
328
                httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
329
                        Handler:      s.srv,
 
330
                        URL:          storeURL("log" + test.querystring),
 
331
                        Username:     testUsername,
 
332
                        Password:     testPassword,
 
333
                        ExpectStatus: test.expectStatus,
 
334
                        ExpectBody: params.Error{
 
335
                                Message: test.expectMessage,
 
336
                                Code:    test.expectCode,
 
337
                        },
 
338
                })
 
339
        }
 
340
}
 
341
 
 
342
func (s *logSuite) TestGetLogsErrorInvalidLog(c *gc.C) {
 
343
        // Add a non-parsable log message to the db directly.
 
344
        err := s.store.DB.Logs().Insert(mongodoc.Log{
 
345
                Data:  []byte("!"),
 
346
                Level: mongodoc.InfoLevel,
 
347
                Type:  mongodoc.IngestionType,
 
348
                Time:  time.Now(),
 
349
        })
 
350
        c.Assert(err, gc.IsNil)
 
351
        // The log is just ignored.
 
352
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
353
                Handler:      s.srv,
 
354
                URL:          storeURL("log"),
 
355
                Username:     testUsername,
 
356
                Password:     testPassword,
 
357
                ExpectStatus: http.StatusOK,
 
358
                ExpectBody:   []params.LogResponse{},
 
359
        })
 
360
}
 
361
 
 
362
func (s *logSuite) TestPostLogs(c *gc.C) {
 
363
        // Prepare the request body.
 
364
        body := makeByteLogs(rawMessage("info data"), params.InfoLevel, params.IngestionType, []*charm.URL{
 
365
                charm.MustParseURL("trusty/django"),
 
366
                charm.MustParseURL("utopic/rails"),
 
367
        })
 
368
 
 
369
        // Send the request.
 
370
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
371
                Handler:  s.srv,
 
372
                URL:      storeURL("log"),
 
373
                Method:   "POST",
 
374
                Username: testUsername,
 
375
                Password: testPassword,
 
376
                Header: http.Header{
 
377
                        "Content-Type": {"application/json"},
 
378
                },
 
379
                Body:         bytes.NewReader(body),
 
380
                ExpectStatus: http.StatusOK,
 
381
        })
 
382
 
 
383
        // Ensure the log message has been added to the database.
 
384
        var doc mongodoc.Log
 
385
        err := s.store.DB.Logs().Find(nil).One(&doc)
 
386
        c.Assert(err, gc.IsNil)
 
387
        c.Assert(string(doc.Data), gc.Equals, `"info data"`)
 
388
        c.Assert(doc.Level, gc.Equals, mongodoc.InfoLevel)
 
389
        c.Assert(doc.Type, gc.Equals, mongodoc.IngestionType)
 
390
        c.Assert(doc.URLs, jc.DeepEquals, []*charm.URL{
 
391
                charm.MustParseURL("trusty/django"),
 
392
                charm.MustParseURL("django"),
 
393
                charm.MustParseURL("utopic/rails"),
 
394
                charm.MustParseURL("rails"),
 
395
        })
 
396
}
 
397
 
 
398
func (s *logSuite) TestPostLogsMultipleEntries(c *gc.C) {
 
399
        // Prepare the request body.
 
400
        infoData := rawMessage("info data")
 
401
        warningData := rawMessage("warning data")
 
402
        logs := []params.Log{{
 
403
                Data:  &infoData,
 
404
                Level: params.InfoLevel,
 
405
                Type:  params.IngestionType,
 
406
        }, {
 
407
                Data:  &warningData,
 
408
                Level: params.WarningLevel,
 
409
                Type:  params.IngestionType,
 
410
        }}
 
411
        body, err := json.Marshal(logs)
 
412
        c.Assert(err, gc.IsNil)
 
413
 
 
414
        // Send the request.
 
415
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
416
                Handler:  s.srv,
 
417
                URL:      storeURL("log"),
 
418
                Method:   "POST",
 
419
                Username: testUsername,
 
420
                Password: testPassword,
 
421
                Header: http.Header{
 
422
                        "Content-Type": {"application/json"},
 
423
                },
 
424
                Body:         bytes.NewReader(body),
 
425
                ExpectStatus: http.StatusOK,
 
426
        })
 
427
 
 
428
        // Ensure the log messages has been added to the database.
 
429
        var docs []mongodoc.Log
 
430
        err = s.store.DB.Logs().Find(nil).Sort("id").All(&docs)
 
431
        c.Assert(err, gc.IsNil)
 
432
        c.Assert(docs, gc.HasLen, 2)
 
433
        c.Assert(string(docs[0].Data), gc.Equals, string(infoData))
 
434
        c.Assert(docs[0].Level, gc.Equals, mongodoc.InfoLevel)
 
435
        c.Assert(string(docs[1].Data), gc.Equals, string(warningData))
 
436
        c.Assert(docs[1].Level, gc.Equals, mongodoc.WarningLevel)
 
437
}
 
438
 
 
439
var postLogsErrorsTests = []struct {
 
440
        about         string
 
441
        contentType   string
 
442
        body          []byte
 
443
        expectStatus  int
 
444
        expectMessage string
 
445
        expectCode    params.ErrorCode
 
446
}{{
 
447
        about:         "invalid content type",
 
448
        contentType:   "application/zip",
 
449
        expectStatus:  http.StatusBadRequest,
 
450
        expectMessage: `unexpected Content-Type "application/zip"; expected 'application/json'`,
 
451
        expectCode:    params.ErrBadRequest,
 
452
}, {
 
453
        about:         "invalid body",
 
454
        body:          []byte("!"),
 
455
        expectStatus:  http.StatusBadRequest,
 
456
        expectMessage: "cannot unmarshal body: invalid character '!' looking for beginning of value",
 
457
        expectCode:    params.ErrBadRequest,
 
458
}, {
 
459
        about:         "invalid log level",
 
460
        body:          makeByteLogs(rawMessage("message"), params.LogLevel(42), params.IngestionType, nil),
 
461
        expectStatus:  http.StatusBadRequest,
 
462
        expectMessage: "invalid log level",
 
463
        expectCode:    params.ErrBadRequest,
 
464
}, {
 
465
        about:         "invalid log type",
 
466
        body:          makeByteLogs(rawMessage("message"), params.WarningLevel, params.LogType(42), nil),
 
467
        expectStatus:  http.StatusBadRequest,
 
468
        expectMessage: "invalid log type",
 
469
        expectCode:    params.ErrBadRequest,
 
470
}}
 
471
 
 
472
func (s *logSuite) TestPostLogsErrors(c *gc.C) {
 
473
        url := storeURL("log")
 
474
        for i, test := range postLogsErrorsTests {
 
475
                c.Logf("test %d: %s", i, test.about)
 
476
                if test.contentType == "" {
 
477
                        test.contentType = "application/json"
 
478
                }
 
479
                httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
480
                        Handler: s.srv,
 
481
                        URL:     url,
 
482
                        Method:  "POST",
 
483
                        Header: http.Header{
 
484
                                "Content-Type": {test.contentType},
 
485
                        },
 
486
                        Body:         bytes.NewReader(test.body),
 
487
                        Username:     testUsername,
 
488
                        Password:     testPassword,
 
489
                        ExpectStatus: test.expectStatus,
 
490
                        ExpectBody: params.Error{
 
491
                                Message: test.expectMessage,
 
492
                                Code:    test.expectCode,
 
493
                        },
 
494
                })
 
495
        }
 
496
}
 
497
 
 
498
func (s *logSuite) TestGetLogsUnauthorizedError(c *gc.C) {
 
499
        s.AssertEndpointAuth(c, httptesting.JSONCallParams{
 
500
                URL:          storeURL("log"),
 
501
                ExpectStatus: http.StatusOK,
 
502
                ExpectBody:   []params.LogResponse{},
 
503
        })
 
504
}
 
505
 
 
506
func (s *logSuite) TestPostLogsUnauthorizedError(c *gc.C) {
 
507
        // Add a non-parsable log message to the db.
 
508
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
509
                Handler: s.noMacaroonSrv,
 
510
                URL:     storeURL("log"),
 
511
                Method:  "POST",
 
512
                Header: http.Header{
 
513
                        "Content-Type": {"application/json"},
 
514
                },
 
515
                ExpectStatus: http.StatusUnauthorized,
 
516
                ExpectBody: params.Error{
 
517
                        Message: "authentication failed: missing HTTP auth header",
 
518
                        Code:    params.ErrUnauthorized,
 
519
                },
 
520
        })
 
521
}
 
522
 
 
523
func makeByteLogs(data json.RawMessage, logLevel params.LogLevel, logType params.LogType, urls []*charm.URL) []byte {
 
524
        logs := []params.Log{{
 
525
                Data:  &data,
 
526
                Level: logLevel,
 
527
                Type:  logType,
 
528
                URLs:  urls,
 
529
        }}
 
530
        b, err := json.Marshal(logs)
 
531
        if err != nil {
 
532
                panic(err)
 
533
        }
 
534
        return b
 
535
}