1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
4
package v5_test // import "gopkg.in/juju/charmstore.v5-unstable/internal/v5"
13
"github.com/juju/loggo"
14
jc "github.com/juju/testing/checkers"
15
"github.com/juju/testing/httptesting"
16
gc "gopkg.in/check.v1"
17
"gopkg.in/juju/charm.v6-unstable"
18
"gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
19
"gopkg.in/macaroon-bakery.v1/bakery/checkers"
20
"gopkg.in/macaroon-bakery.v1/httpbakery"
21
"gopkg.in/macaroon.v1"
23
"gopkg.in/juju/charmstore.v5-unstable/internal/router"
24
"gopkg.in/juju/charmstore.v5-unstable/internal/storetesting"
27
type ListSuite struct {
31
var _ = gc.Suite(&ListSuite{})
33
var exportListTestCharms = map[string]*router.ResolvedURL{
34
"wordpress": newResolvedURL("cs:~charmers/precise/wordpress-23", 23),
35
"mysql": newResolvedURL("cs:~openstack-charmers/trusty/mysql-7", 7),
36
"varnish": newResolvedURL("cs:~foo/trusty/varnish-1", -1),
37
"riak": newResolvedURL("cs:~charmers/trusty/riak-67", 67),
40
var exportListTestBundles = map[string]*router.ResolvedURL{
41
"wordpress-simple": newResolvedURL("cs:~charmers/bundle/wordpress-simple-4", 4),
44
func (s *ListSuite) SetUpSuite(c *gc.C) {
45
s.enableIdentity = true
46
s.commonSuite.SetUpSuite(c)
49
func (s *ListSuite) SetUpTest(c *gc.C) {
50
s.commonSuite.SetUpTest(c)
52
// hide the riak charm
53
err := s.store.SetPerms(charm.MustParseURL("cs:~charmers/riak"), "stable.read", "charmers", "test-user")
54
c.Assert(err, gc.IsNil)
57
func (s *ListSuite) addCharmsToStore(c *gc.C) {
58
for name, id := range exportListTestCharms {
59
s.addPublicCharm(c, getListCharm(name), id)
61
for name, id := range exportListTestBundles {
62
s.addPublicBundle(c, getListBundle(name), id, false)
66
func getListCharm(name string) *storetesting.Charm {
67
ca := storetesting.Charms.CharmDir(name)
69
meta.Categories = append(strings.Split(name, "-"), "bar")
70
return storetesting.NewCharm(meta)
73
func getListBundle(name string) *storetesting.Bundle {
74
ba := storetesting.Charms.BundleDir(name)
76
data.Tags = append(strings.Split(name, "-"), "baz")
77
return storetesting.NewBundle(data)
80
func (s *ListSuite) TestSuccessfulList(c *gc.C) {
84
results []*router.ResolvedURL
88
results: []*router.ResolvedURL{
89
exportTestBundles["wordpress-simple"],
90
exportTestCharms["wordpress"],
91
exportTestCharms["varnish"],
92
exportTestCharms["mysql"],
95
about: "name filter list",
97
results: []*router.ResolvedURL{
98
exportTestCharms["mysql"],
101
about: "owner filter list",
103
results: []*router.ResolvedURL{
104
exportTestCharms["varnish"],
107
about: "series filter list",
108
query: "series=trusty",
109
results: []*router.ResolvedURL{
110
exportTestCharms["varnish"],
111
exportTestCharms["mysql"],
114
about: "type filter list",
115
query: "type=bundle",
116
results: []*router.ResolvedURL{
117
exportTestBundles["wordpress-simple"],
120
about: "promulgated",
121
query: "promulgated=1",
122
results: []*router.ResolvedURL{
123
exportTestBundles["wordpress-simple"],
124
exportTestCharms["wordpress"],
125
exportTestCharms["mysql"],
128
about: "not promulgated",
129
query: "promulgated=0",
130
results: []*router.ResolvedURL{
131
exportTestCharms["varnish"],
134
about: "promulgated with owner",
135
query: "promulgated=1&owner=openstack-charmers",
136
results: []*router.ResolvedURL{
137
exportTestCharms["mysql"],
140
for i, test := range tests {
141
c.Logf("test %d. %s", i, test.about)
142
rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
144
URL: storeURL("list?" + test.query),
146
var sr params.ListResponse
147
err := json.Unmarshal(rec.Body.Bytes(), &sr)
148
c.Assert(err, gc.IsNil)
149
c.Assert(sr.Results, gc.HasLen, len(test.results))
150
c.Logf("results: %s", rec.Body.Bytes())
151
for i := range test.results {
152
c.Assert(sr.Results[i].Id.String(), gc.Equals, test.results[i].PreferredURL().String(), gc.Commentf("element %d"))
157
func (s *ListSuite) TestMetadataFields(c *gc.C) {
161
meta map[string]interface{}
163
about: "archive-size",
164
query: "name=mysql&include=archive-size",
165
meta: map[string]interface{}{
166
"archive-size": params.ArchiveSizeResponse{getListCharm("mysql").Size()},
169
about: "bundle-metadata",
170
query: "name=wordpress-simple&type=bundle&include=bundle-metadata",
171
meta: map[string]interface{}{
172
"bundle-metadata": getListBundle("wordpress-simple").Data(),
175
about: "bundle-machine-count",
176
query: "name=wordpress-simple&type=bundle&include=bundle-machine-count",
177
meta: map[string]interface{}{
178
"bundle-machine-count": params.BundleCount{2},
181
about: "bundle-unit-count",
182
query: "name=wordpress-simple&type=bundle&include=bundle-unit-count",
183
meta: map[string]interface{}{
184
"bundle-unit-count": params.BundleCount{2},
187
about: "charm-actions",
188
query: "name=wordpress&type=charm&include=charm-actions",
189
meta: map[string]interface{}{
190
"charm-actions": getListCharm("wordpress").Actions(),
193
about: "charm-config",
194
query: "name=wordpress&type=charm&include=charm-config",
195
meta: map[string]interface{}{
196
"charm-config": getListCharm("wordpress").Config(),
199
about: "charm-related",
200
query: "name=wordpress&type=charm&include=charm-related",
201
meta: map[string]interface{}{
202
"charm-related": params.RelatedResponse{
203
Provides: map[string][]params.EntityResult{
206
Id: exportTestCharms["mysql"].PreferredURL(),
211
Id: exportTestCharms["varnish"].PreferredURL(),
218
about: "multiple values",
219
query: "name=wordpress&type=charm&include=charm-related&include=charm-config",
220
meta: map[string]interface{}{
221
"charm-related": params.RelatedResponse{
222
Provides: map[string][]params.EntityResult{
225
Id: exportTestCharms["mysql"].PreferredURL(),
230
Id: exportTestCharms["varnish"].PreferredURL(),
235
"charm-config": getListCharm("wordpress").Config(),
238
for i, test := range tests {
239
c.Logf("test %d. %s", i, test.about)
240
rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
242
URL: storeURL("list?" + test.query),
244
c.Assert(rec.Code, gc.Equals, http.StatusOK)
250
err := json.Unmarshal(rec.Body.Bytes(), &sr)
251
c.Assert(err, gc.IsNil)
252
c.Assert(sr.Results, gc.HasLen, 1)
253
c.Assert(string(sr.Results[0].Meta), jc.JSONEquals, test.meta)
257
func (s *ListSuite) TestListIncludeError(c *gc.C) {
258
// Perform a list for all charms, including the
259
// manifest, which will try to retrieve all charm
261
rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
263
URL: storeURL("list?type=charm&include=manifest"),
265
c.Assert(rec.Code, gc.Equals, http.StatusOK)
266
var resp params.ListResponse
267
err := json.Unmarshal(rec.Body.Bytes(), &resp)
268
// cs:riak will not be found because it is not visible to "everyone".
269
c.Assert(resp.Results, gc.HasLen, len(exportTestCharms)-1)
271
// Now remove one of the blobs. The list should still
272
// work, but only return a single result.
273
entity, err := s.store.FindEntity(newResolvedURL("~charmers/precise/wordpress-23", 23), nil)
274
c.Assert(err, gc.IsNil)
275
err = s.store.BlobStore.Remove(entity.BlobName)
276
c.Assert(err, gc.IsNil)
278
// Now list again - we should get one result less
279
// (and the error will be logged).
281
// Register a logger that so that we can check the logging output.
282
// It will be automatically removed later because IsolatedMgoESSuite
283
// uses LoggingSuite.
284
var tw loggo.TestWriter
285
err = loggo.RegisterWriter("test-log", &tw, loggo.DEBUG)
286
c.Assert(err, gc.IsNil)
288
rec = httptesting.DoRequest(c, httptesting.DoRequestParams{
290
URL: storeURL("list?type=charm&include=manifest"),
292
c.Assert(rec.Code, gc.Equals, http.StatusOK)
293
resp = params.ListResponse{}
294
err = json.Unmarshal(rec.Body.Bytes(), &resp)
295
// cs:riak will not be found because it is not visible to "everyone".
296
// cs:wordpress will not be found because it has no manifest.
297
c.Assert(resp.Results, gc.HasLen, len(exportTestCharms)-2)
299
c.Assert(tw.Log(), jc.LogMatches, []string{"cannot retrieve metadata for cs:precise/wordpress-23: cannot open archive data for cs:precise/wordpress-23: .*"})
302
func (s *ListSuite) TestSortingList(c *gc.C) {
306
results []*router.ResolvedURL
308
about: "name ascending",
310
results: []*router.ResolvedURL{
311
exportTestCharms["mysql"],
312
exportTestCharms["varnish"],
313
exportTestCharms["wordpress"],
314
exportTestBundles["wordpress-simple"],
317
about: "name descending",
319
results: []*router.ResolvedURL{
320
exportTestBundles["wordpress-simple"],
321
exportTestCharms["wordpress"],
322
exportTestCharms["varnish"],
323
exportTestCharms["mysql"],
326
about: "series ascending",
327
query: "sort=series,name",
328
results: []*router.ResolvedURL{
329
exportTestBundles["wordpress-simple"],
330
exportTestCharms["wordpress"],
331
exportTestCharms["mysql"],
332
exportTestCharms["varnish"],
335
about: "series descending",
336
query: "sort=-series&sort=name",
337
results: []*router.ResolvedURL{
338
exportTestCharms["mysql"],
339
exportTestCharms["varnish"],
340
exportTestCharms["wordpress"],
341
exportTestBundles["wordpress-simple"],
344
about: "owner ascending",
345
query: "sort=owner,name",
346
results: []*router.ResolvedURL{
347
exportTestCharms["wordpress"],
348
exportTestBundles["wordpress-simple"],
349
exportTestCharms["varnish"],
350
exportTestCharms["mysql"],
353
about: "owner descending",
354
query: "sort=-owner&sort=name",
355
results: []*router.ResolvedURL{
356
exportTestCharms["mysql"],
357
exportTestCharms["varnish"],
358
exportTestCharms["wordpress"],
359
exportTestBundles["wordpress-simple"],
362
for i, test := range tests {
363
c.Logf("test %d. %s", i, test.about)
364
rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
366
URL: storeURL("list?" + test.query),
368
var sr params.ListResponse
369
err := json.Unmarshal(rec.Body.Bytes(), &sr)
370
c.Assert(err, gc.IsNil)
371
c.Assert(sr.Results, gc.HasLen, len(test.results), gc.Commentf("expected %#v", test.results))
372
c.Logf("results: %s", rec.Body.Bytes())
373
for i := range test.results {
374
c.Assert(sr.Results[i].Id.String(), gc.Equals, test.results[i].PreferredURL().String(), gc.Commentf("element %d"))
379
func (s *ListSuite) TestSortUnsupportedListField(c *gc.C) {
380
rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
382
URL: storeURL("list?sort=text"),
385
err := json.Unmarshal(rec.Body.Bytes(), &e)
386
c.Assert(err, gc.IsNil)
387
c.Assert(e.Code, gc.Equals, params.ErrBadRequest)
388
c.Assert(e.Message, gc.Equals, "invalid sort field: unrecognized sort parameter \"text\"")
391
func (s *ListSuite) TestGetLatestRevisionOnly(c *gc.C) {
392
id := newResolvedURL("cs:~charmers/precise/wordpress-24", 24)
393
s.addPublicCharm(c, getListCharm("wordpress"), id)
395
testresults := []*router.ResolvedURL{
396
exportTestBundles["wordpress-simple"],
398
exportTestCharms["varnish"],
399
exportTestCharms["mysql"],
402
rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
404
URL: storeURL("list"),
406
var sr params.ListResponse
407
err := json.Unmarshal(rec.Body.Bytes(), &sr)
408
c.Assert(err, gc.IsNil)
409
c.Assert(sr.Results, gc.HasLen, 4, gc.Commentf("expected %#v", testresults))
410
c.Logf("results: %s", rec.Body.Bytes())
411
for i := range testresults {
412
c.Assert(sr.Results[i].Id.String(), gc.Equals, testresults[i].PreferredURL().String(), gc.Commentf("element %d"))
415
testresults = []*router.ResolvedURL{
416
exportTestCharms["mysql"],
417
exportTestCharms["varnish"],
419
exportTestBundles["wordpress-simple"],
421
rec = httptesting.DoRequest(c, httptesting.DoRequestParams{
423
URL: storeURL("list?sort=name"),
425
err = json.Unmarshal(rec.Body.Bytes(), &sr)
426
c.Assert(err, gc.IsNil)
427
c.Assert(sr.Results, gc.HasLen, 4, gc.Commentf("expected %#v", testresults))
428
c.Logf("results: %s", rec.Body.Bytes())
429
for i := range testresults {
430
c.Assert(sr.Results[i].Id.String(), gc.Equals, testresults[i].PreferredURL().String(), gc.Commentf("element %d"))
434
func (s *ListSuite) assertPut(c *gc.C, url string, val interface{}) {
435
body, err := json.Marshal(val)
436
c.Assert(err, gc.IsNil)
437
rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
442
"Content-Type": {"application/json"},
444
Username: testUsername,
445
Password: testPassword,
446
Body: bytes.NewReader(body),
448
c.Assert(rec.Code, gc.Equals, http.StatusOK, gc.Commentf("headers: %v, body: %s", rec.HeaderMap, rec.Body.String()))
449
c.Assert(rec.Body.String(), gc.HasLen, 0)
452
func (s *ListSuite) TestListWithAdminCredentials(c *gc.C) {
453
rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
455
URL: storeURL("list"),
456
Username: testUsername,
457
Password: testPassword,
459
c.Assert(rec.Code, gc.Equals, http.StatusOK)
460
expected := []*router.ResolvedURL{
461
exportTestCharms["mysql"],
462
exportTestCharms["wordpress"],
463
exportTestCharms["riak"],
464
exportTestCharms["varnish"],
465
exportTestBundles["wordpress-simple"],
467
var sr params.ListResponse
468
err := json.Unmarshal(rec.Body.Bytes(), &sr)
469
c.Assert(err, gc.IsNil)
470
assertListResultSet(c, sr, expected)
473
func (s *ListSuite) TestListWithUserMacaroon(c *gc.C) {
474
m, err := s.store.Bakery.NewMacaroon("", nil, []checkers.Caveat{
475
checkers.DeclaredCaveat("username", "test-user"),
477
c.Assert(err, gc.IsNil)
478
macaroonCookie, err := httpbakery.NewCookie(macaroon.Slice{m})
479
c.Assert(err, gc.IsNil)
480
rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
482
URL: storeURL("list"),
483
Cookies: []*http.Cookie{macaroonCookie},
485
c.Assert(rec.Code, gc.Equals, http.StatusOK)
486
expected := []*router.ResolvedURL{
487
exportTestCharms["mysql"],
488
exportTestCharms["wordpress"],
489
exportTestCharms["riak"],
490
exportTestCharms["varnish"],
491
exportTestBundles["wordpress-simple"],
493
var sr params.ListResponse
494
err = json.Unmarshal(rec.Body.Bytes(), &sr)
495
c.Assert(err, gc.IsNil)
496
assertListResultSet(c, sr, expected)
499
func (s *ListSuite) TestSearchWithBadAdminCredentialsAndACookie(c *gc.C) {
500
m, err := s.store.Bakery.NewMacaroon("", nil, []checkers.Caveat{
501
checkers.DeclaredCaveat("username", "test-user"),
503
c.Assert(err, gc.IsNil)
504
macaroonCookie, err := httpbakery.NewCookie(macaroon.Slice{m})
505
c.Assert(err, gc.IsNil)
506
rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
508
URL: storeURL("list"),
509
Cookies: []*http.Cookie{macaroonCookie},
510
Username: testUsername,
511
Password: "bad-password",
513
c.Assert(rec.Code, gc.Equals, http.StatusOK)
514
expected := []*router.ResolvedURL{
515
exportTestCharms["mysql"],
516
exportTestCharms["wordpress"],
517
exportTestCharms["varnish"],
518
exportTestBundles["wordpress-simple"],
520
var sr params.ListResponse
521
err = json.Unmarshal(rec.Body.Bytes(), &sr)
522
c.Assert(err, gc.IsNil)
523
assertListResultSet(c, sr, expected)
526
func assertListResultSet(c *gc.C, sr params.ListResponse, expected []*router.ResolvedURL) {
527
sort.Sort(listResultById(sr.Results))
528
sort.Sort(resolvedURLByPreferredURL(expected))
529
c.Assert(sr.Results, gc.HasLen, len(expected), gc.Commentf("expected %#v", expected))
530
for i := range expected {
531
c.Assert(sr.Results[i].Id.String(), gc.Equals, expected[i].PreferredURL().String(), gc.Commentf("element %d"))
535
type listResultById []params.EntityResult
537
func (s listResultById) Len() int { return len(s) }
538
func (s listResultById) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
539
func (s listResultById) Less(i, j int) bool {
540
return s[i].Id.String() < s[j].Id.String()