~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/gopkg.in/juju/charmstore.v5-unstable/internal/legacy/api_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 legacy_test // import "gopkg.in/juju/charmstore.v5-unstable/internal/legacy"
 
5
 
 
6
import (
 
7
        "crypto/sha256"
 
8
        "encoding/json"
 
9
        "fmt"
 
10
        "io"
 
11
        "io/ioutil"
 
12
        "net/http"
 
13
        "net/http/httptest"
 
14
        "os"
 
15
        "time"
 
16
 
 
17
        jujutesting "github.com/juju/testing"
 
18
        jc "github.com/juju/testing/checkers"
 
19
        "github.com/juju/testing/httptesting"
 
20
        gc "gopkg.in/check.v1"
 
21
        "gopkg.in/juju/charm.v6-unstable"
 
22
        "gopkg.in/juju/charmrepo.v2-unstable"
 
23
        "gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
 
24
        "gopkg.in/mgo.v2"
 
25
        "gopkg.in/mgo.v2/bson"
 
26
 
 
27
        "gopkg.in/juju/charmstore.v5-unstable/internal/charmstore"
 
28
        "gopkg.in/juju/charmstore.v5-unstable/internal/legacy"
 
29
        "gopkg.in/juju/charmstore.v5-unstable/internal/router"
 
30
        "gopkg.in/juju/charmstore.v5-unstable/internal/storetesting"
 
31
        "gopkg.in/juju/charmstore.v5-unstable/internal/storetesting/stats"
 
32
)
 
33
 
 
34
var serverParams = charmstore.ServerParams{
 
35
        AuthUsername: "test-user",
 
36
        AuthPassword: "test-password",
 
37
}
 
38
 
 
39
type APISuite struct {
 
40
        jujutesting.IsolatedMgoSuite
 
41
        srv   *charmstore.Server
 
42
        store *charmstore.Store
 
43
}
 
44
 
 
45
var _ = gc.Suite(&APISuite{})
 
46
 
 
47
func (s *APISuite) SetUpTest(c *gc.C) {
 
48
        s.IsolatedMgoSuite.SetUpTest(c)
 
49
        s.srv, s.store = newServer(c, s.Session, serverParams)
 
50
}
 
51
 
 
52
func (s *APISuite) TearDownTest(c *gc.C) {
 
53
        s.store.Close()
 
54
        s.store.Pool().Close()
 
55
        s.srv.Close()
 
56
        s.IsolatedMgoSuite.TearDownTest(c)
 
57
}
 
58
 
 
59
func newServer(c *gc.C, session *mgo.Session, config charmstore.ServerParams) (*charmstore.Server, *charmstore.Store) {
 
60
        db := session.DB("charmstore")
 
61
        pool, err := charmstore.NewPool(db, nil, nil, config)
 
62
        c.Assert(err, gc.IsNil)
 
63
        srv, err := charmstore.NewServer(db, nil, config, map[string]charmstore.NewAPIHandlerFunc{"": legacy.NewAPIHandler})
 
64
        c.Assert(err, gc.IsNil)
 
65
        return srv, pool.Store()
 
66
}
 
67
 
 
68
func (s *APISuite) TestCharmArchive(c *gc.C) {
 
69
        _, wordpress := s.addPublicCharm(c, "wordpress", "cs:precise/wordpress-0")
 
70
        archiveBytes, err := ioutil.ReadFile(wordpress.Path)
 
71
        c.Assert(err, gc.IsNil)
 
72
 
 
73
        rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
 
74
                Handler: s.srv,
 
75
                URL:     "/charm/precise/wordpress-0",
 
76
        })
 
77
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
 
78
        c.Assert(rec.Body.Bytes(), gc.DeepEquals, archiveBytes)
 
79
        c.Assert(rec.Header().Get("Content-Length"), gc.Equals, fmt.Sprint(len(rec.Body.Bytes())))
 
80
 
 
81
        // Test with unresolved URL.
 
82
        rec = httptesting.DoRequest(c, httptesting.DoRequestParams{
 
83
                Handler: s.srv,
 
84
                URL:     "/charm/wordpress",
 
85
        })
 
86
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
 
87
        c.Assert(rec.Body.Bytes(), gc.DeepEquals, archiveBytes)
 
88
        c.Assert(rec.Header().Get("Content-Length"), gc.Equals, fmt.Sprint(len(rec.Body.Bytes())))
 
89
 
 
90
        // Check that the HTTP range logic is plugged in OK. If this
 
91
        // is working, we assume that the whole thing is working OK,
 
92
        // as net/http is well-tested.
 
93
        rec = httptesting.DoRequest(c, httptesting.DoRequestParams{
 
94
                Handler: s.srv,
 
95
                URL:     "/charm/precise/wordpress-0",
 
96
                Header:  http.Header{"Range": {"bytes=10-100"}},
 
97
        })
 
98
        c.Assert(rec.Code, gc.Equals, http.StatusPartialContent, gc.Commentf("body: %q", rec.Body.Bytes()))
 
99
        c.Assert(rec.Body.Bytes(), gc.HasLen, 100-10+1)
 
100
        c.Assert(rec.Body.Bytes(), gc.DeepEquals, archiveBytes[10:101])
 
101
}
 
102
 
 
103
func (s *APISuite) TestGetElidesSeriesFromMultiSeriesCharmMetadata(c *gc.C) {
 
104
        _, ch := s.addPublicCharm(c, "multi-series", "cs:~charmers/multi-series-0")
 
105
        rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
 
106
                Handler: s.srv,
 
107
                URL:     "/charm/~charmers/multi-series",
 
108
        })
 
109
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
 
110
 
 
111
        gotCh, err := charm.ReadCharmArchiveBytes(rec.Body.Bytes())
 
112
        c.Assert(err, gc.IsNil)
 
113
 
 
114
        chMeta := ch.Meta()
 
115
        chMeta.Series = nil
 
116
 
 
117
        c.Assert(gotCh.Meta(), jc.DeepEquals, chMeta)
 
118
}
 
119
 
 
120
func (s *APISuite) TestPostNotAllowed(c *gc.C) {
 
121
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
122
                Handler:      s.srv,
 
123
                Method:       "POST",
 
124
                URL:          "/charm/precise/wordpress",
 
125
                ExpectStatus: http.StatusMethodNotAllowed,
 
126
                ExpectBody: params.Error{
 
127
                        Code:    params.ErrMethodNotAllowed,
 
128
                        Message: params.ErrMethodNotAllowed.Error(),
 
129
                },
 
130
        })
 
131
}
 
132
 
 
133
func (s *APISuite) TestCharmArchiveUnresolvedURL(c *gc.C) {
 
134
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
135
                Handler:      s.srv,
 
136
                URL:          "/charm/wordpress",
 
137
                ExpectStatus: http.StatusNotFound,
 
138
                ExpectBody: params.Error{
 
139
                        Code:    params.ErrNotFound,
 
140
                        Message: `no matching charm or bundle for cs:wordpress`,
 
141
                },
 
142
        })
 
143
}
 
144
 
 
145
func (s *APISuite) TestCharmInfoNotFound(c *gc.C) {
 
146
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
147
                Handler:      s.srv,
 
148
                URL:          "/charm-info?charms=cs:precise/something-23",
 
149
                ExpectStatus: http.StatusOK,
 
150
                ExpectBody: map[string]charmrepo.InfoResponse{
 
151
                        "cs:precise/something-23": {
 
152
                                Errors: []string{"entry not found"},
 
153
                        },
 
154
                },
 
155
        })
 
156
}
 
157
 
 
158
func (s *APISuite) TestServeCharmInfo(c *gc.C) {
 
159
        wordpressURL, wordpress := s.addPublicCharm(c, "wordpress", "cs:precise/wordpress-1")
 
160
        hashSum := fileSHA256(c, wordpress.Path)
 
161
        digest, err := json.Marshal("who@canonical.com-bzr-digest")
 
162
        c.Assert(err, gc.IsNil)
 
163
 
 
164
        tests := []struct {
 
165
                about     string
 
166
                url       string
 
167
                extrainfo map[string][]byte
 
168
                canonical string
 
169
                sha       string
 
170
                digest    string
 
171
                revision  int
 
172
                err       string
 
173
        }{{
 
174
                about: "full charm URL with digest extra info",
 
175
                url:   wordpressURL.String(),
 
176
                extrainfo: map[string][]byte{
 
177
                        params.BzrDigestKey: digest,
 
178
                },
 
179
                canonical: "cs:precise/wordpress-1",
 
180
                sha:       hashSum,
 
181
                digest:    "who@canonical.com-bzr-digest",
 
182
                revision:  1,
 
183
        }, {
 
184
                about:     "full charm URL without digest extra info",
 
185
                url:       wordpressURL.String(),
 
186
                canonical: "cs:precise/wordpress-1",
 
187
                sha:       hashSum,
 
188
                revision:  1,
 
189
        }, {
 
190
                about: "partial charm URL with digest extra info",
 
191
                url:   "cs:wordpress",
 
192
                extrainfo: map[string][]byte{
 
193
                        params.BzrDigestKey: digest,
 
194
                },
 
195
                canonical: "cs:precise/wordpress-1",
 
196
                sha:       hashSum,
 
197
                digest:    "who@canonical.com-bzr-digest",
 
198
                revision:  1,
 
199
        }, {
 
200
                about:     "partial charm URL without extra info",
 
201
                url:       "cs:wordpress",
 
202
                canonical: "cs:precise/wordpress-1",
 
203
                sha:       hashSum,
 
204
                revision:  1,
 
205
        }, {
 
206
                about: "invalid digest extra info",
 
207
                url:   "cs:wordpress",
 
208
                extrainfo: map[string][]byte{
 
209
                        params.BzrDigestKey: []byte("[]"),
 
210
                },
 
211
                canonical: "cs:precise/wordpress-1",
 
212
                sha:       hashSum,
 
213
                revision:  1,
 
214
                err:       `cannot unmarshal digest: json: cannot unmarshal array into Go value of type string`,
 
215
        }, {
 
216
                about: "charm not found",
 
217
                url:   "cs:precise/non-existent",
 
218
                err:   "entry not found",
 
219
        }, {
 
220
                about: "invalid charm URL",
 
221
                url:   "cs:/bad",
 
222
                err:   `entry not found`,
 
223
        }, {
 
224
                about: "invalid charm schema",
 
225
                url:   "gopher:archie-server",
 
226
                err:   `entry not found`,
 
227
        }, {
 
228
                about: "invalid URL",
 
229
                url:   "/charm-info?charms=cs:not-found",
 
230
                err:   "entry not found",
 
231
        }}
 
232
 
 
233
        for i, test := range tests {
 
234
                c.Logf("test %d: %s", i, test.about)
 
235
                err = s.store.UpdateEntity(wordpressURL, bson.D{{
 
236
                        "$set", bson.D{{"extrainfo", test.extrainfo}},
 
237
                }})
 
238
                c.Assert(err, gc.IsNil)
 
239
                expectInfo := charmrepo.InfoResponse{
 
240
                        CanonicalURL: test.canonical,
 
241
                        Sha256:       test.sha,
 
242
                        Revision:     test.revision,
 
243
                        Digest:       test.digest,
 
244
                }
 
245
                if test.err != "" {
 
246
                        expectInfo.Errors = []string{test.err}
 
247
                }
 
248
                httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
249
                        Handler:      s.srv,
 
250
                        URL:          "/charm-info?charms=" + test.url,
 
251
                        ExpectStatus: http.StatusOK,
 
252
                        ExpectBody: map[string]charmrepo.InfoResponse{
 
253
                                test.url: expectInfo,
 
254
                        },
 
255
                })
 
256
        }
 
257
}
 
258
 
 
259
func (s *APISuite) TestCharmInfoCounters(c *gc.C) {
 
260
        if !storetesting.MongoJSEnabled() {
 
261
                c.Skip("MongoDB JavaScript not available")
 
262
        }
 
263
 
 
264
        // Add two charms to the database, a promulgated one and a user owned one.
 
265
        s.addPublicCharm(c, "wordpress", "cs:utopic/wordpress-42")
 
266
        s.addPublicCharm(c, "wordpress", "cs:~who/trusty/wordpress-47")
 
267
 
 
268
        requestInfo := func(id string, times int) {
 
269
                for i := 0; i < times; i++ {
 
270
                        rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
 
271
                                Handler: s.srv,
 
272
                                URL:     "/charm-info?charms=" + id,
 
273
                        })
 
274
                        c.Assert(rec.Code, gc.Equals, http.StatusOK)
 
275
                }
 
276
        }
 
277
 
 
278
        // Request charm info several times for the promulgated charm,
 
279
        // the user owned one and a missing charm.
 
280
        requestInfo("utopic/wordpress-42", 4)
 
281
        requestInfo("~who/trusty/wordpress-47", 3)
 
282
        requestInfo("precise/django-0", 2)
 
283
 
 
284
        // The charm-info count for the promulgated charm has been updated.
 
285
        key := []string{params.StatsCharmInfo, "utopic", "wordpress"}
 
286
        stats.CheckCounterSum(c, s.store, key, false, 4)
 
287
 
 
288
        // The charm-info count for the user owned charm has been updated.
 
289
        key = []string{params.StatsCharmInfo, "trusty", "wordpress", "who"}
 
290
        stats.CheckCounterSum(c, s.store, key, false, 3)
 
291
 
 
292
        // The charm-missing count for the missing charm has been updated.
 
293
        key = []string{params.StatsCharmMissing, "precise", "django"}
 
294
        stats.CheckCounterSum(c, s.store, key, false, 2)
 
295
 
 
296
        // The charm-info count for the missing charm is still zero.
 
297
        key = []string{params.StatsCharmInfo, "precise", "django"}
 
298
        stats.CheckCounterSum(c, s.store, key, false, 0)
 
299
}
 
300
 
 
301
func (s *APISuite) TestAPIInfoWithGatedCharm(c *gc.C) {
 
302
        wordpressURL, _ := s.addPublicCharm(c, "wordpress", "cs:precise/wordpress-0")
 
303
        s.store.SetPerms(&wordpressURL.URL, "stable.read", "bob")
 
304
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
305
                Handler:      s.srv,
 
306
                URL:          "/charm-info?charms=" + wordpressURL.URL.String(),
 
307
                ExpectStatus: http.StatusOK,
 
308
                ExpectBody: map[string]charmrepo.InfoResponse{
 
309
                        wordpressURL.URL.String(): {
 
310
                                Errors: []string{"entry not found"},
 
311
                        },
 
312
                },
 
313
        })
 
314
}
 
315
 
 
316
func fileSHA256(c *gc.C, path string) string {
 
317
        f, err := os.Open(path)
 
318
        c.Assert(err, gc.IsNil)
 
319
        hash := sha256.New()
 
320
        _, err = io.Copy(hash, f)
 
321
        c.Assert(err, gc.IsNil)
 
322
        return fmt.Sprintf("%x", hash.Sum(nil))
 
323
}
 
324
 
 
325
func (s *APISuite) TestCharmPackageGet(c *gc.C) {
 
326
        wordpressURL, wordpress := s.addPublicCharm(c, "wordpress", "cs:precise/wordpress-0")
 
327
        archiveBytes, err := ioutil.ReadFile(wordpress.Path)
 
328
        c.Assert(err, gc.IsNil)
 
329
 
 
330
        srv := httptest.NewServer(s.srv)
 
331
        defer srv.Close()
 
332
 
 
333
        s.PatchValue(&charmrepo.CacheDir, c.MkDir())
 
334
        s.PatchValue(&charmrepo.LegacyStore.BaseURL, srv.URL)
 
335
 
 
336
        ch, err := charmrepo.LegacyStore.Get(&wordpressURL.URL)
 
337
        c.Assert(err, gc.IsNil)
 
338
        chArchive := ch.(*charm.CharmArchive)
 
339
 
 
340
        data, err := ioutil.ReadFile(chArchive.Path)
 
341
        c.Assert(err, gc.IsNil)
 
342
        c.Assert(data, gc.DeepEquals, archiveBytes)
 
343
}
 
344
 
 
345
func (s *APISuite) TestCharmPackageCharmInfo(c *gc.C) {
 
346
        wordpressURL, wordpress := s.addPublicCharm(c, "wordpress", "cs:precise/wordpress-0")
 
347
        wordpressSHA256 := fileSHA256(c, wordpress.Path)
 
348
        mysqlURL, mySQL := s.addPublicCharm(c, "wordpress", "cs:precise/mysql-2")
 
349
        mysqlSHA256 := fileSHA256(c, mySQL.Path)
 
350
        notFoundURL := charm.MustParseURL("cs:precise/not-found-3")
 
351
 
 
352
        srv := httptest.NewServer(s.srv)
 
353
        defer srv.Close()
 
354
        s.PatchValue(&charmrepo.LegacyStore.BaseURL, srv.URL)
 
355
 
 
356
        resp, err := charmrepo.LegacyStore.Info(wordpressURL.PreferredURL(), mysqlURL.PreferredURL(), notFoundURL)
 
357
        c.Assert(err, gc.IsNil)
 
358
        c.Assert(resp, gc.HasLen, 3)
 
359
        c.Assert(resp, jc.DeepEquals, []*charmrepo.InfoResponse{{
 
360
                CanonicalURL: wordpressURL.String(),
 
361
                Sha256:       wordpressSHA256,
 
362
        }, {
 
363
                CanonicalURL: mysqlURL.String(),
 
364
                Sha256:       mysqlSHA256,
 
365
                Revision:     2,
 
366
        }, {
 
367
                Errors: []string{"charm not found: " + notFoundURL.String()},
 
368
        }})
 
369
}
 
370
 
 
371
var serverStatusTests = []struct {
 
372
        path string
 
373
        code int
 
374
}{
 
375
        {"/charm-info/any", 404},
 
376
        {"/charm/bad-url", 404},
 
377
        {"/charm/bad-series/wordpress", 404},
 
378
}
 
379
 
 
380
func (s *APISuite) TestServerStatus(c *gc.C) {
 
381
        // TODO(rog) add tests from old TestServerStatus tests
 
382
        // when we implement charm-info.
 
383
        for i, test := range serverStatusTests {
 
384
                c.Logf("test %d: %s", i, test.path)
 
385
                resp := httptesting.DoRequest(c, httptesting.DoRequestParams{
 
386
                        Handler: s.srv,
 
387
                        URL:     test.path,
 
388
                })
 
389
                c.Assert(resp.Code, gc.Equals, test.code, gc.Commentf("body: %s", resp.Body))
 
390
        }
 
391
}
 
392
 
 
393
func (s *APISuite) addPublicCharm(c *gc.C, charmName, curl string) (*router.ResolvedURL, *charm.CharmArchive) {
 
394
        rurl := &router.ResolvedURL{
 
395
                URL:                 *charm.MustParseURL(curl),
 
396
                PromulgatedRevision: -1,
 
397
        }
 
398
        if rurl.URL.User == "" {
 
399
                rurl.URL.User = "charmers"
 
400
                rurl.PromulgatedRevision = rurl.URL.Revision
 
401
        }
 
402
        archive := storetesting.Charms.CharmArchive(c.MkDir(), charmName)
 
403
        err := s.store.AddCharmWithArchive(rurl, archive)
 
404
        c.Assert(err, gc.IsNil)
 
405
        s.setPublic(c, rurl)
 
406
        return rurl, archive
 
407
}
 
408
 
 
409
func (s *APISuite) setPublic(c *gc.C, rurl *router.ResolvedURL) {
 
410
        err := s.store.SetPerms(&rurl.URL, "stable.read", params.Everyone)
 
411
        c.Assert(err, gc.IsNil)
 
412
        err = s.store.Publish(rurl, nil, params.StableChannel)
 
413
        c.Assert(err, gc.IsNil)
 
414
}
 
415
 
 
416
var serveCharmEventErrorsTests = []struct {
 
417
        about       string
 
418
        url         string
 
419
        responseUrl string
 
420
        err         string
 
421
}{{
 
422
        about: "invalid charm URL",
 
423
        url:   "no-such:charm",
 
424
        err:   `invalid charm URL: charm or bundle URL has invalid schema: "no-such:charm"`,
 
425
}, {
 
426
        about: "revision specified",
 
427
        url:   "cs:utopic/django-42",
 
428
        err:   "got charm URL with revision: cs:utopic/django-42",
 
429
}, {
 
430
        about: "charm not found",
 
431
        url:   "cs:trusty/django",
 
432
        err:   "entry not found",
 
433
}, {
 
434
        about:       "ignoring digest",
 
435
        url:         "precise/django-47@a-bzr-digest",
 
436
        responseUrl: "precise/django-47",
 
437
        err:         "got charm URL with revision: cs:precise/django-47",
 
438
}}
 
439
 
 
440
func (s *APISuite) TestServeCharmEventErrors(c *gc.C) {
 
441
        for i, test := range serveCharmEventErrorsTests {
 
442
                c.Logf("test %d: %s", i, test.about)
 
443
                if test.responseUrl == "" {
 
444
                        test.responseUrl = test.url
 
445
                }
 
446
                httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
447
                        Handler:      s.srv,
 
448
                        URL:          "/charm-event?charms=" + test.url,
 
449
                        ExpectStatus: http.StatusOK,
 
450
                        ExpectBody: map[string]charmrepo.EventResponse{
 
451
                                test.responseUrl: {
 
452
                                        Errors: []string{test.err},
 
453
                                },
 
454
                        },
 
455
                })
 
456
        }
 
457
}
 
458
 
 
459
func (s *APISuite) TestServeCharmEvent(c *gc.C) {
 
460
        // Add three charms to the charm store.
 
461
        mysqlUrl, _ := s.addPublicCharm(c, "mysql", "cs:trusty/mysql-2")
 
462
        riakUrl, _ := s.addPublicCharm(c, "riak", "cs:utopic/riak-3")
 
463
 
 
464
        // Update the mysql charm with a valid digest extra-info.
 
465
        s.addExtraInfoDigest(c, mysqlUrl, "who@canonical.com-bzr-digest")
 
466
 
 
467
        // Update the riak charm with an invalid digest extra-info.
 
468
        err := s.store.UpdateEntity(riakUrl, bson.D{{
 
469
                "$set", bson.D{{"extrainfo", map[string][]byte{
 
470
                        params.BzrDigestKey: []byte(":"),
 
471
                }}},
 
472
        }})
 
473
        c.Assert(err, gc.IsNil)
 
474
 
 
475
        // Retrieve the entities.
 
476
        mysql, err := s.store.FindEntity(mysqlUrl, nil)
 
477
        c.Assert(err, gc.IsNil)
 
478
        riak, err := s.store.FindEntity(riakUrl, nil)
 
479
        c.Assert(err, gc.IsNil)
 
480
 
 
481
        tests := []struct {
 
482
                about  string
 
483
                query  string
 
484
                expect map[string]*charmrepo.EventResponse
 
485
        }{{
 
486
                about: "valid digest",
 
487
                query: "?charms=cs:trusty/mysql",
 
488
                expect: map[string]*charmrepo.EventResponse{
 
489
                        "cs:trusty/mysql": {
 
490
                                Kind:     "published",
 
491
                                Revision: mysql.Revision,
 
492
                                Time:     mysql.UploadTime.UTC().Format(time.RFC3339),
 
493
                                Digest:   "who@canonical.com-bzr-digest",
 
494
                        },
 
495
                },
 
496
        }, {
 
497
                about: "invalid digest",
 
498
                query: "?charms=cs:utopic/riak",
 
499
                expect: map[string]*charmrepo.EventResponse{
 
500
                        "cs:utopic/riak": {
 
501
                                Kind:     "published",
 
502
                                Revision: riak.Revision,
 
503
                                Time:     riak.UploadTime.UTC().Format(time.RFC3339),
 
504
                                Errors:   []string{"cannot unmarshal digest: invalid character ':' looking for beginning of value"},
 
505
                        },
 
506
                },
 
507
        }, {
 
508
                about: "partial charm URL",
 
509
                query: "?charms=cs:mysql",
 
510
                expect: map[string]*charmrepo.EventResponse{
 
511
                        "cs:mysql": {
 
512
                                Kind:     "published",
 
513
                                Revision: mysql.Revision,
 
514
                                Time:     mysql.UploadTime.UTC().Format(time.RFC3339),
 
515
                                Digest:   "who@canonical.com-bzr-digest",
 
516
                        },
 
517
                },
 
518
        }, {
 
519
                about: "digest in request",
 
520
                query: "?charms=cs:trusty/mysql@my-digest",
 
521
                expect: map[string]*charmrepo.EventResponse{
 
522
                        "cs:trusty/mysql": {
 
523
                                Kind:     "published",
 
524
                                Revision: mysql.Revision,
 
525
                                Time:     mysql.UploadTime.UTC().Format(time.RFC3339),
 
526
                                Digest:   "who@canonical.com-bzr-digest",
 
527
                        },
 
528
                },
 
529
        }, {
 
530
                about: "multiple charms",
 
531
                query: "?charms=cs:mysql&charms=utopic/riak",
 
532
                expect: map[string]*charmrepo.EventResponse{
 
533
                        "cs:mysql": {
 
534
                                Kind:     "published",
 
535
                                Revision: mysql.Revision,
 
536
                                Time:     mysql.UploadTime.UTC().Format(time.RFC3339),
 
537
                                Digest:   "who@canonical.com-bzr-digest",
 
538
                        },
 
539
                        "utopic/riak": {
 
540
                                Kind:     "published",
 
541
                                Revision: riak.Revision,
 
542
                                Time:     riak.UploadTime.UTC().Format(time.RFC3339),
 
543
                                Errors:   []string{"cannot unmarshal digest: invalid character ':' looking for beginning of value"},
 
544
                        },
 
545
                },
 
546
        }}
 
547
 
 
548
        for i, test := range tests {
 
549
                c.Logf("test %d: %s", i, test.about)
 
550
                httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
551
                        Handler:      s.srv,
 
552
                        URL:          "/charm-event" + test.query,
 
553
                        ExpectStatus: http.StatusOK,
 
554
                        ExpectBody:   test.expect,
 
555
                })
 
556
        }
 
557
}
 
558
 
 
559
func (s *APISuite) TestServeCharmEventDigestNotFound(c *gc.C) {
 
560
        // Add a charm without a Bazaar digest.
 
561
        url, _ := s.addPublicCharm(c, "wordpress", "cs:trusty/wordpress-42")
 
562
 
 
563
        // Pretend the entity has been uploaded right now, and assume the test does
 
564
        // not take more than two minutes to run.
 
565
        s.updateUploadTime(c, url, time.Now())
 
566
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
567
                Handler:      s.srv,
 
568
                URL:          "/charm-event?charms=cs:trusty/wordpress",
 
569
                ExpectStatus: http.StatusOK,
 
570
                ExpectBody: map[string]charmrepo.EventResponse{
 
571
                        "cs:trusty/wordpress": {
 
572
                                Errors: []string{"entry not found"},
 
573
                        },
 
574
                },
 
575
        })
 
576
 
 
577
        // Now change the entity upload time to be more than 2 minutes ago.
 
578
        s.updateUploadTime(c, url, time.Now().Add(-121*time.Second))
 
579
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
580
                Handler:      s.srv,
 
581
                URL:          "/charm-event?charms=cs:trusty/wordpress",
 
582
                ExpectStatus: http.StatusOK,
 
583
                ExpectBody: map[string]charmrepo.EventResponse{
 
584
                        "cs:trusty/wordpress": {
 
585
                                Errors: []string{"digest not found: this can be due to an error while ingesting the entity"},
 
586
                        },
 
587
                },
 
588
        })
 
589
}
 
590
 
 
591
func (s *APISuite) TestServeCharmEventLastRevision(c *gc.C) {
 
592
        // Add two revisions of the same charm.
 
593
        url1, _ := s.addPublicCharm(c, "wordpress", "cs:trusty/wordpress-1")
 
594
        url2, _ := s.addPublicCharm(c, "wordpress", "cs:trusty/wordpress-2")
 
595
 
 
596
        // Update the resulting entities with Bazaar digests.
 
597
        s.addExtraInfoDigest(c, url1, "digest-1")
 
598
        s.addExtraInfoDigest(c, url2, "digest-2")
 
599
 
 
600
        // Retrieve the most recent revision of the entity.
 
601
        entity, err := s.store.FindEntity(url2, nil)
 
602
        c.Assert(err, gc.IsNil)
 
603
 
 
604
        // Ensure the last revision is correctly returned.
 
605
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
606
                Handler:      s.srv,
 
607
                URL:          "/charm-event?charms=wordpress",
 
608
                ExpectStatus: http.StatusOK,
 
609
                ExpectBody: map[string]*charmrepo.EventResponse{
 
610
                        "wordpress": {
 
611
                                Kind:     "published",
 
612
                                Revision: 2,
 
613
                                Time:     entity.UploadTime.UTC().Format(time.RFC3339),
 
614
                                Digest:   "digest-2",
 
615
                        },
 
616
                },
 
617
        })
 
618
}
 
619
 
 
620
func (s *APISuite) addExtraInfoDigest(c *gc.C, id *router.ResolvedURL, digest string) {
 
621
        b, err := json.Marshal(digest)
 
622
        c.Assert(err, gc.IsNil)
 
623
        err = s.store.UpdateEntity(id, bson.D{{
 
624
                "$set", bson.D{{"extrainfo", map[string][]byte{
 
625
                        params.BzrDigestKey: b,
 
626
                }}},
 
627
        }})
 
628
        c.Assert(err, gc.IsNil)
 
629
}
 
630
 
 
631
func (s *APISuite) updateUploadTime(c *gc.C, id *router.ResolvedURL, uploadTime time.Time) {
 
632
        err := s.store.UpdateEntity(id, bson.D{{
 
633
                "$set", bson.D{{"uploadtime", uploadTime}},
 
634
        }})
 
635
        c.Assert(err, gc.IsNil)
 
636
}