~juju-qa/ubuntu/xenial/juju/xenial-2.0-beta3

« back to all changes in this revision

Viewing changes to src/gopkg.in/juju/charmstore.v5-unstable/internal/v4/archive_test.go

  • Committer: Martin Packman
  • Date: 2016-03-30 19:31:08 UTC
  • mfrom: (1.1.41)
  • Revision ID: martin.packman@canonical.com-20160330193108-h9iz3ak334uk0z5r
Merge new upstream source 2.0~beta3

Show diffs side-by-side

added added

removed removed

Lines of Context:
43
43
 
44
44
var _ = gc.Suite(&ArchiveSuite{})
45
45
 
 
46
func (s *ArchiveSuite) SetUpSuite(c *gc.C) {
 
47
        s.enableIdentity = true
 
48
        s.commonSuite.SetUpSuite(c)
 
49
}
 
50
 
46
51
func (s *ArchiveSuite) TestGet(c *gc.C) {
47
52
        id := newResolvedURL("cs:~charmers/precise/wordpress-0", -1)
48
 
        wordpress := s.assertUploadCharm(c, "POST", id, "wordpress")
49
 
        err := s.store.SetPerms(&id.URL, "read", params.Everyone, id.URL.User)
50
 
        c.Assert(err, gc.IsNil)
51
 
 
52
 
        archiveBytes, err := ioutil.ReadFile(wordpress.Path)
53
 
        c.Assert(err, gc.IsNil)
54
 
 
55
 
        archiveUrl := storeURL("~charmers/precise/wordpress-0/archive")
56
 
        rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
57
 
                Handler: s.srv,
58
 
                URL:     archiveUrl,
59
 
        })
60
 
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
61
 
        c.Assert(rec.Body.Bytes(), gc.DeepEquals, archiveBytes)
62
 
        c.Assert(rec.Header().Get(params.ContentHashHeader), gc.Equals, hashOfBytes(archiveBytes))
 
53
        ch := storetesting.NewCharm(nil)
 
54
        s.addPublicCharm(c, ch, id)
 
55
 
 
56
        rec := s.assertArchiveDownload(
 
57
                c,
 
58
                "~charmers/precise/wordpress-0",
 
59
                nil,
 
60
                ch.Bytes(),
 
61
        )
63
62
        c.Assert(rec.Header().Get(params.EntityIdHeader), gc.Equals, "cs:~charmers/precise/wordpress-0")
64
63
        assertCacheControl(c, rec.Header(), true)
65
64
 
68
67
        // as net/http is well-tested.
69
68
        rec = httptesting.DoRequest(c, httptesting.DoRequestParams{
70
69
                Handler: s.srv,
71
 
                URL:     archiveUrl,
 
70
                URL:     storeURL("~charmers/precise/wordpress-0/archive"),
72
71
                Header:  http.Header{"Range": {"bytes=10-100"}},
73
72
        })
74
73
        c.Assert(rec.Code, gc.Equals, http.StatusPartialContent, gc.Commentf("body: %q", rec.Body.Bytes()))
75
74
        c.Assert(rec.Body.Bytes(), gc.HasLen, 100-10+1)
76
 
        c.Assert(rec.Body.Bytes(), gc.DeepEquals, archiveBytes[10:101])
77
 
        c.Assert(rec.Header().Get(params.ContentHashHeader), gc.Equals, hashOfBytes(archiveBytes))
 
75
        c.Assert(rec.Body.Bytes(), gc.DeepEquals, ch.Bytes()[10:101])
 
76
        c.Assert(rec.Header().Get(params.ContentHashHeader), gc.Equals, hashOfBytes(ch.Bytes()))
78
77
        c.Assert(rec.Header().Get(params.EntityIdHeader), gc.Equals, "cs:~charmers/precise/wordpress-0")
79
78
        assertCacheControl(c, rec.Header(), true)
80
 
 
81
 
        // The development version of the entity can also be retrieved.
82
 
        err = s.store.SetPerms(id.URL.WithChannel(charm.DevelopmentChannel), "read", params.Everyone, id.URL.User)
83
 
        c.Assert(err, gc.IsNil)
84
 
        rec = httptesting.DoRequest(c, httptesting.DoRequestParams{
85
 
                Handler: s.srv,
86
 
                URL:     storeURL("~charmers/development/precise/wordpress-0/archive"),
87
 
        })
88
 
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
89
 
        c.Assert(rec.Body.Bytes(), gc.DeepEquals, archiveBytes)
90
 
        c.Assert(rec.Header().Get(params.ContentHashHeader), gc.Equals, hashOfBytes(archiveBytes))
91
 
        c.Assert(rec.Header().Get(params.EntityIdHeader), gc.Equals, "cs:~charmers/development/precise/wordpress-0")
92
 
}
93
 
 
94
 
func (s *ArchiveSuite) TestGetDevelopment(c *gc.C) {
95
 
        id := newResolvedURL("cs:~charmers/development/trusty/wordpress-0", -1)
96
 
        wordpress := s.assertUploadCharm(c, "POST", id, "wordpress")
97
 
        url := id.PreferredURL()
98
 
        err := s.store.SetPerms(url, "read", params.Everyone, id.URL.User)
99
 
        c.Assert(err, gc.IsNil)
100
 
 
101
 
        archiveBytes, err := ioutil.ReadFile(wordpress.Path)
102
 
        c.Assert(err, gc.IsNil)
103
 
 
104
 
        rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
105
 
                Handler: s.srv,
106
 
                URL:     storeURL("~charmers/development/trusty/wordpress-0/archive"),
107
 
        })
108
 
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
109
 
        c.Assert(rec.Body.Bytes(), gc.DeepEquals, archiveBytes)
110
 
        c.Assert(rec.Header().Get(params.ContentHashHeader), gc.Equals, hashOfBytes(archiveBytes))
111
 
        c.Assert(rec.Header().Get(params.EntityIdHeader), gc.Equals, "cs:~charmers/development/trusty/wordpress-0")
112
 
 
113
 
        // It is not possible to use the published URL to retrieve the archive,
114
 
        err = s.store.SetPerms(url.WithChannel(""), "read", params.Everyone, id.URL.User)
115
 
        c.Assert(err, gc.IsNil)
116
 
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
117
 
                Handler:      s.srv,
118
 
                URL:          storeURL("~charmers/trusty/wordpress-0/archive"),
119
 
                ExpectStatus: http.StatusNotFound,
120
 
                ExpectBody: params.Error{
121
 
                        Code:    params.ErrNotFound,
122
 
                        Message: `no matching charm or bundle for "cs:~charmers/trusty/wordpress-0"`,
123
 
                },
124
 
        })
125
79
}
126
80
 
127
81
func (s *ArchiveSuite) TestGetWithPartialId(c *gc.C) {
128
 
        id := newResolvedURL("cs:~charmers/utopic/wordpress-42", -1)
129
 
        err := s.store.AddCharmWithArchive(
130
 
                id,
131
 
                storetesting.Charms.CharmArchive(c.MkDir(), "wordpress"))
132
 
        c.Assert(err, gc.IsNil)
133
 
        err = s.store.SetPerms(&id.URL, "read", params.Everyone, id.URL.User)
134
 
        c.Assert(err, gc.IsNil)
135
 
        rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
136
 
                Handler: s.srv,
137
 
                URL:     storeURL("~charmers/wordpress/archive"),
138
 
        })
139
 
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
 
82
        id := newResolvedURL("cs:~charmers/precise/wordpress-0", -1)
 
83
        ch := storetesting.NewCharm(nil)
 
84
        s.addPublicCharm(c, ch, id)
 
85
 
 
86
        rec := s.assertArchiveDownload(
 
87
                c,
 
88
                "~charmers/wordpress",
 
89
                nil,
 
90
                ch.Bytes(),
 
91
        )
140
92
        // The complete entity id can be retrieved from the response header.
141
93
        c.Assert(rec.Header().Get(params.EntityIdHeader), gc.Equals, id.URL.String())
142
94
}
143
95
 
144
96
func (s *ArchiveSuite) TestGetPromulgatedWithPartialId(c *gc.C) {
145
97
        id := newResolvedURL("cs:~charmers/utopic/wordpress-42", 42)
146
 
        err := s.store.AddCharmWithArchive(
147
 
                id,
148
 
                storetesting.Charms.CharmArchive(c.MkDir(), "wordpress"))
149
 
        c.Assert(err, gc.IsNil)
150
 
        err = s.store.SetPerms(&id.URL, "read", params.Everyone, id.URL.User)
151
 
        c.Assert(err, gc.IsNil)
152
 
        rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
153
 
                Handler: s.srv,
154
 
                URL:     storeURL("wordpress/archive"),
155
 
        })
156
 
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
157
 
        // The complete entity id can be retrieved from the response header.
 
98
        ch := storetesting.NewCharm(nil)
 
99
        s.addPublicCharm(c, ch, id)
 
100
 
 
101
        rec := s.assertArchiveDownload(
 
102
                c,
 
103
                "wordpress",
 
104
                nil,
 
105
                ch.Bytes(),
 
106
        )
158
107
        c.Assert(rec.Header().Get(params.EntityIdHeader), gc.Equals, id.PromulgatedURL().String())
159
108
}
160
109
 
 
110
// V4 SPECIFIC
 
111
func (s *ArchiveSuite) TestGetElidesSeriesFromMultiSeriesCharmMetadata(c *gc.C) {
 
112
        _, ch := s.addPublicCharmFromRepo(c, "multi-series", newResolvedURL("cs:~charmers/multi-series-0", -1))
 
113
        rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
 
114
                Handler: s.srv,
 
115
                URL:     storeURL("~charmers/multi-series/archive"),
 
116
        })
 
117
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
 
118
 
 
119
        gotCh, err := charm.ReadCharmArchiveBytes(rec.Body.Bytes())
 
120
        c.Assert(err, gc.IsNil)
 
121
        c.Assert(gotCh.Meta().Series, gc.HasLen, 0)
 
122
 
 
123
        // Check that the metadata is elided from the metadata file when retrieved
 
124
        // directly too.
 
125
 
 
126
        rec = httptesting.DoRequest(c, httptesting.DoRequestParams{
 
127
                Handler: s.srv,
 
128
                URL:     storeURL("~charmers/multi-series/archive/metadata.yaml"),
 
129
        })
 
130
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
 
131
 
 
132
        gotMeta, err := charm.ReadMeta(bytes.NewReader(rec.Body.Bytes()))
 
133
        c.Assert(err, gc.IsNil)
 
134
        c.Assert(gotMeta.Series, gc.HasLen, 0)
 
135
 
 
136
        chMeta := ch.Meta()
 
137
        chMeta.Series = nil
 
138
 
 
139
        c.Assert(gotMeta, jc.DeepEquals, chMeta)
 
140
}
 
141
 
161
142
func (s *ArchiveSuite) TestGetCounters(c *gc.C) {
162
143
        if !storetesting.MongoJSEnabled() {
163
144
                c.Skip("MongoDB JavaScript not available")
168
149
        } {
169
150
                c.Logf("test %d: %s", i, id)
170
151
 
171
 
                // Add a charm to the database (including the archive).
172
 
                err := s.store.AddCharmWithArchive(id, storetesting.Charms.CharmArchive(c.MkDir(), "mysql"))
173
 
                c.Assert(err, gc.IsNil)
174
 
                err = s.store.SetPerms(&id.URL, "read", params.Everyone, id.URL.User)
175
 
                c.Assert(err, gc.IsNil)
 
152
                ch := storetesting.NewCharm(nil)
 
153
                s.addPublicCharm(c, ch, id)
176
154
 
177
 
                // Download the charm archive using the API.
178
 
                rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
179
 
                        Handler: s.srv,
180
 
                        URL:     storeURL(id.URL.Path() + "/archive"),
181
 
                })
182
 
                c.Assert(rec.Code, gc.Equals, http.StatusOK)
 
155
                // Download the charm archive using the API, which should increment
 
156
                // the download counts.
 
157
                s.assertArchiveDownload(
 
158
                        c,
 
159
                        id.URL.Path(),
 
160
                        nil,
 
161
                        ch.Bytes(),
 
162
                )
183
163
 
184
164
                // Check that the downloads count for the entity has been updated.
185
165
                key := []string{params.StatsArchiveDownload, "utopic", "mysql", id.URL.User, "42"}
191
171
}
192
172
 
193
173
func (s *ArchiveSuite) TestGetCountersDisabled(c *gc.C) {
194
 
        url := newResolvedURL("~charmers/utopic/mysql-42", 42)
195
 
        // Add a charm to the database (including the archive).
196
 
        err := s.store.AddCharmWithArchive(url, storetesting.Charms.CharmArchive(c.MkDir(), "mysql"))
197
 
        c.Assert(err, gc.IsNil)
198
 
        err = s.store.SetPerms(&url.URL, "read", params.Everyone, url.URL.User)
199
 
        c.Assert(err, gc.IsNil)
 
174
        id := newResolvedURL("~charmers/utopic/mysql-42", 42)
 
175
        ch := storetesting.NewCharm(nil)
 
176
        s.addPublicCharm(c, ch, id)
200
177
 
201
178
        // Download the charm archive using the API, passing stats=0.
202
 
        rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
203
 
                Handler: s.srv,
204
 
                URL:     storeURL(url.URL.Path() + "/archive?stats=0"),
205
 
        })
206
 
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
 
179
        s.assertArchiveDownload(
 
180
                c,
 
181
                "",
 
182
                &httptesting.DoRequestParams{URL: storeURL("~charmers/utopic/mysql-42/archive?stats=0")},
 
183
                ch.Bytes(),
 
184
        )
207
185
 
208
186
        // Check that the downloads count for the entity has not been updated.
209
187
        key := []string{params.StatsArchiveDownload, "utopic", "mysql", "", "42"}
212
190
 
213
191
var archivePostErrorsTests = []struct {
214
192
        about           string
215
 
        path            string
 
193
        url             string
216
194
        noContentLength bool
 
195
        noHash          bool
 
196
        entity          charmstore.ArchiverTo
217
197
        expectStatus    int
218
198
        expectMessage   string
219
199
        expectCode      params.ErrorCode
220
200
}{{
221
201
        about:         "revision specified",
222
 
        path:          "~charmers/precise/wordpress-23/archive",
 
202
        url:           "~charmers/precise/wordpress-23",
223
203
        expectStatus:  http.StatusBadRequest,
224
204
        expectMessage: "revision specified, but should not be specified",
225
205
        expectCode:    params.ErrBadRequest,
226
206
}, {
227
207
        about:         "no hash given",
228
 
        path:          "~charmers/precise/wordpress/archive",
 
208
        url:           "~charmers/precise/wordpress",
 
209
        noHash:        true,
229
210
        expectStatus:  http.StatusBadRequest,
230
211
        expectMessage: "hash parameter not specified",
231
212
        expectCode:    params.ErrBadRequest,
232
213
}, {
233
214
        about:           "no content length",
234
 
        path:            "~charmers/precise/wordpress/archive?hash=1234563",
 
215
        url:             "~charmers/precise/wordpress",
235
216
        noContentLength: true,
236
217
        expectStatus:    http.StatusBadRequest,
237
218
        expectMessage:   "Content-Length not specified",
238
219
        expectCode:      params.ErrBadRequest,
239
220
}, {
240
221
        about:         "invalid channel",
241
 
        path:          "~charmers/bad-wolf/trusty/wordpress/archive",
 
222
        url:           "~charmers/bad-wolf/trusty/wordpress",
242
223
        expectStatus:  http.StatusNotFound,
243
224
        expectMessage: "not found",
244
225
        expectCode:    params.ErrNotFound,
 
226
}, {
 
227
        about:         "no series",
 
228
        url:           "~charmers/juju-gui",
 
229
        expectStatus:  http.StatusForbidden,
 
230
        expectMessage: "series not specified in url or charm metadata",
 
231
        expectCode:    params.ErrEntityIdNotAllowed,
 
232
}, {
 
233
        about: "url series not in metadata",
 
234
        url:   "~charmers/precise/juju-gui",
 
235
        entity: storetesting.NewCharm(&charm.Meta{
 
236
                Series: []string{"trusty"},
 
237
        }),
 
238
        expectStatus:  http.StatusForbidden,
 
239
        expectMessage: `"precise" series not listed in charm metadata`,
 
240
        expectCode:    params.ErrEntityIdNotAllowed,
 
241
}, {
 
242
        about: "bad combination of series",
 
243
        url:   "~charmers/juju-gui",
 
244
        entity: storetesting.NewCharm(&charm.Meta{
 
245
                Series: []string{"precise", "win10"},
 
246
        }),
 
247
        expectStatus:  http.StatusBadRequest,
 
248
        expectMessage: `cannot mix series from ubuntu and windows in single charm`,
 
249
        expectCode:    params.ErrInvalidEntity,
 
250
}, {
 
251
        about: "unknown series",
 
252
        url:   "~charmers/juju-gui",
 
253
        entity: storetesting.NewCharm(&charm.Meta{
 
254
                Series: []string{"precise", "nosuchseries"},
 
255
        }),
 
256
        expectStatus:  http.StatusBadRequest,
 
257
        expectMessage: `unrecognized series "nosuchseries" in metadata`,
 
258
        expectCode:    params.ErrInvalidEntity,
245
259
}}
246
260
 
247
261
func (s *ArchiveSuite) TestPostErrors(c *gc.C) {
250
264
        }
251
265
        for i, test := range archivePostErrorsTests {
252
266
                c.Logf("test %d: %s", i, test.about)
253
 
                var body io.Reader = strings.NewReader("bogus")
 
267
                if test.entity == nil {
 
268
                        test.entity = storetesting.NewCharm(nil)
 
269
                }
 
270
                blob, hashSum := getBlob(test.entity)
 
271
                body := io.Reader(blob)
254
272
                if test.noContentLength {
255
273
                        // net/http will automatically add a Content-Length header
256
274
                        // if it sees *strings.Reader, but not if it's a type it doesn't
257
275
                        // know about.
258
276
                        body = exoticReader{body}
259
277
                }
 
278
                path := storeURL(test.url) + "/archive"
 
279
                if !test.noHash {
 
280
                        path += "?hash=" + hashSum
 
281
                }
260
282
                httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
261
283
                        Handler: s.srv,
262
 
                        URL:     storeURL(test.path),
 
284
                        URL:     path,
263
285
                        Method:  "POST",
264
286
                        Header: http.Header{
265
287
                                "Content-Type": {"application/zip"},
388
410
}
389
411
 
390
412
func (s *ArchiveSuite) TestPostCharm(c *gc.C) {
 
413
        s.discharge = dischargeForUser("charmers")
 
414
 
391
415
        // A charm that did not exist before should get revision 0.
392
416
        s.assertUploadCharm(c, "POST", newResolvedURL("~charmers/precise/wordpress-0", -1), "wordpress")
393
417
 
394
418
        // Subsequent charm uploads should increment the revision by 1.
395
419
        s.assertUploadCharm(c, "POST", newResolvedURL("~charmers/precise/wordpress-1", -1), "mysql")
396
420
 
397
 
        // Subsequent development charm uploads should increment the revision by 1.
398
 
        s.assertUploadCharm(c, "POST", newResolvedURL("~charmers/development/precise/wordpress-2", -1), "wordpress")
399
 
 
400
 
        // Retrieving the published version returns the last non-development charm.
401
 
        err := s.store.SetPerms(charm.MustParseURL("~charmers/wordpress"), "read", params.Everyone)
402
 
        c.Assert(err, gc.IsNil)
 
421
        // Retrieving the unpublished version returns the latest charm.
403
422
        rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
404
423
                Handler: s.srv,
405
 
                URL:     storeURL("~charmers/wordpress/archive"),
 
424
                URL:     storeURL("~charmers/wordpress/archive?channel=unpublished"),
 
425
                Do:      bakeryDo(nil),
406
426
        })
407
427
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
408
428
        c.Assert(rec.Header().Get(params.EntityIdHeader), gc.Equals, "cs:~charmers/precise/wordpress-1")
409
429
}
410
430
 
411
431
func (s *ArchiveSuite) TestPostCurrentVersion(c *gc.C) {
412
 
        s.assertUploadCharm(c, "POST", newResolvedURL("~charmers/development/precise/wordpress-0", -1), "wordpress")
 
432
        s.assertUploadCharm(c, "POST", newResolvedURL("~charmers/precise/wordpress-0", -1), "wordpress")
413
433
 
414
434
        // Subsequent charm uploads should not increment the revision by 1.
415
 
        s.assertUploadCharm(c, "POST", newResolvedURL("~charmers/development/precise/wordpress-0", -1), "wordpress")
416
 
}
417
 
 
418
 
func (s *ArchiveSuite) TestPostDevelopmentPromulgated(c *gc.C) {
419
 
        s.assertUploadCharm(c, "PUT", newResolvedURL("~charmers/development/trusty/wordpress-0", 0), "wordpress")
420
 
        s.assertUploadCharm(c, "POST", newResolvedURL("~charmers/development/trusty/wordpress-1", 1), "mysql")
421
 
        s.assertUploadCharm(c, "POST", newResolvedURL("~charmers/development/trusty/wordpress-1", 1), "mysql")
422
 
 
423
 
        // The promulgated charm can be accessed via its development URL.
424
 
        err := s.store.SetPerms(charm.MustParseURL("~charmers/development/wordpress"), "read", params.Everyone)
425
 
        c.Assert(err, gc.IsNil)
426
 
        rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
427
 
                Handler: s.srv,
428
 
                URL:     storeURL("development/wordpress/archive"),
429
 
        })
430
 
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
431
 
        c.Assert(rec.Header().Get(params.EntityIdHeader), gc.Equals, "cs:development/trusty/wordpress-1")
432
 
 
433
 
        // The promulgated charm cannot be retrieved using the published URL.
434
 
        err = s.store.SetPerms(charm.MustParseURL("~charmers/wordpress"), "read", params.Everyone)
435
 
        c.Assert(err, gc.IsNil)
436
 
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
437
 
                Handler:      s.srv,
438
 
                URL:          storeURL("wordpress/archive"),
439
 
                ExpectStatus: http.StatusNotFound,
440
 
                ExpectBody: params.Error{
441
 
                        Code:    params.ErrNotFound,
442
 
                        Message: `no matching charm or bundle for "cs:wordpress"`,
443
 
                },
444
 
        })
445
 
}
446
 
 
447
 
var uploadAndPublishTests = []struct {
448
 
        about             string
449
 
        existing          string
450
 
        upload            string
451
 
        expectId          string
452
 
        expectDevelopment bool
453
 
}{{
454
 
        about:             "upload same development entity",
455
 
        existing:          "~who/development/django-0",
456
 
        upload:            "~who/development/django",
457
 
        expectId:          "~who/development/django-0",
458
 
        expectDevelopment: true,
459
 
}, {
460
 
        about:    "upload same published entity",
461
 
        existing: "~who/django-0",
462
 
        upload:   "~who/django",
463
 
        expectId: "~who/django-0",
464
 
}, {
465
 
        about:    "existing development, upload published",
466
 
        existing: "~who/development/django-0",
467
 
        upload:   "~who/django",
468
 
        expectId: "~who/django-0",
469
 
}, {
470
 
        about:    "existing published, upload development",
471
 
        existing: "~who/django-0",
472
 
        upload:   "~who/development/django",
473
 
        expectId: "~who/development/django-0",
474
 
}}
475
 
 
476
 
func (s *ArchiveSuite) TestUploadAndPublish(c *gc.C) {
477
 
        for i, test := range uploadAndPublishTests {
478
 
                c.Logf("%d. %s", i, test.about)
479
 
 
480
 
                // Upload the pre-existing entity.
481
 
                rurl := newResolvedURL(test.existing, -1)
482
 
                s.assertUploadCharm(c, "POST", rurl, "multi-series")
483
 
 
484
 
                // Upload the same charm again, using the upload URL.
485
 
                body, hash, size := archiveInfo(c, "multi-series")
486
 
                httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
487
 
                        Handler:       s.srv,
488
 
                        URL:           storeURL(test.upload + "/archive?hash=" + hash),
489
 
                        Method:        "POST",
490
 
                        ContentLength: size,
491
 
                        Header:        http.Header{"Content-Type": {"application/zip"}},
492
 
                        Body:          body,
493
 
                        Username:      testUsername,
494
 
                        Password:      testPassword,
495
 
                        ExpectBody: params.ArchiveUploadResponse{
496
 
                                Id: charm.MustParseURL(test.expectId),
497
 
                        },
498
 
                })
499
 
 
500
 
                // Check the development flag of the entity.
501
 
                entity, err := s.store.FindEntity(rurl, "development")
502
 
                c.Assert(err, gc.IsNil)
503
 
                c.Assert(entity.Development, gc.Equals, test.expectDevelopment)
504
 
 
505
 
                // Remove all entities from the store.
506
 
                _, err = s.store.DB.Entities().RemoveAll(nil)
507
 
                c.Assert(err, gc.IsNil)
508
 
                _, err = s.store.DB.BaseEntities().RemoveAll(nil)
509
 
                c.Assert(err, gc.IsNil)
510
 
        }
 
435
        s.assertUploadCharm(c, "POST", newResolvedURL("~charmers/precise/wordpress-0", -1), "wordpress")
511
436
}
512
437
 
513
438
func (s *ArchiveSuite) TestPostMultiSeriesCharm(c *gc.C) {
515
440
        s.assertUploadCharm(c, "POST", newResolvedURL("~charmers/juju-gui-0", -1), "multi-series")
516
441
}
517
442
 
518
 
func (s *ArchiveSuite) TestPostMultiSeriesDevelopmentCharm(c *gc.C) {
519
 
        // A charm that did not exist before should get revision 0.
520
 
        s.assertUploadCharm(c, "POST", newResolvedURL("~charmers/development/juju-gui-0", -1), "multi-series")
521
 
}
522
 
 
523
 
var charmPostErrorTests = []struct {
524
 
        about        string
525
 
        url          *charm.URL
526
 
        charm        string
527
 
        expectStatus int
528
 
        expectBody   interface{}
529
 
}{{
530
 
        about:        "no series",
531
 
        url:          charm.MustParseURL("~charmers/juju-gui-0"),
532
 
        charm:        "wordpress",
533
 
        expectStatus: http.StatusForbidden,
534
 
        expectBody: params.Error{
535
 
                Message: "series not specified in url or charm metadata",
536
 
                Code:    params.ErrEntityIdNotAllowed,
537
 
        },
538
 
}, {
539
 
        about:        "url series not in metadata",
540
 
        url:          charm.MustParseURL("~charmers/precise/juju-gui-0"),
541
 
        charm:        "multi-series",
542
 
        expectStatus: http.StatusForbidden,
543
 
        expectBody: params.Error{
544
 
                Message: `"precise" series not listed in charm metadata`,
545
 
                Code:    params.ErrEntityIdNotAllowed,
546
 
        },
547
 
}, {
548
 
        about:        "bad combination of series",
549
 
        url:          charm.MustParseURL("~charmers/juju-gui-0"),
550
 
        charm:        "multi-series-bad-combination",
551
 
        expectStatus: http.StatusBadRequest,
552
 
        expectBody: params.Error{
553
 
                Message: `cannot mix series from ubuntu and windows in single charm`,
554
 
                Code:    params.ErrInvalidEntity,
555
 
        },
556
 
}, {
557
 
        about:        "unknown series",
558
 
        url:          charm.MustParseURL("~charmers/juju-gui-0"),
559
 
        charm:        "multi-series-unknown",
560
 
        expectStatus: http.StatusBadRequest,
561
 
        expectBody: params.Error{
562
 
                Message: `unrecognised series "nosuchseries" in metadata`,
563
 
                Code:    params.ErrInvalidEntity,
564
 
        },
565
 
}}
566
 
 
567
 
func (s *ArchiveSuite) TestCharmPostError(c *gc.C) {
568
 
        for i, test := range charmPostErrorTests {
569
 
                c.Logf("%d. %s", i, test.about)
570
 
                s.assertUploadCharmError(
571
 
                        c,
572
 
                        "POST",
573
 
                        test.url,
574
 
                        nil,
575
 
                        test.charm,
576
 
                        test.expectStatus,
577
 
                        test.expectBody,
578
 
                )
579
 
        }
580
 
}
581
 
 
582
443
func (s *ArchiveSuite) TestPostMultiSeriesCharmRevisionAfterAllSingleSeriesOnes(c *gc.C) {
583
444
        // Create some single series versions of the charm
584
445
        s.assertUploadCharm(c, "PUT", newResolvedURL("~charmers/vivid/juju-gui-1", -1), "mysql")
695
556
 
696
557
func (s *ArchiveSuite) TestPostBundle(c *gc.C) {
697
558
        // Upload the required charms.
698
 
        err := s.store.AddCharmWithArchive(
 
559
        for _, rurl := range []*router.ResolvedURL{
699
560
                newResolvedURL("cs:~charmers/utopic/mysql-42", 42),
700
 
                storetesting.Charms.CharmArchive(c.MkDir(), "mysql"))
701
 
        c.Assert(err, gc.IsNil)
702
 
        err = s.store.AddCharmWithArchive(
703
561
                newResolvedURL("cs:~charmers/utopic/wordpress-47", 47),
704
 
                storetesting.Charms.CharmArchive(c.MkDir(), "wordpress"))
705
 
        c.Assert(err, gc.IsNil)
706
 
        err = s.store.AddCharmWithArchive(
707
562
                newResolvedURL("cs:~charmers/utopic/logging-1", 1),
708
 
                storetesting.Charms.CharmArchive(c.MkDir(), "logging"))
709
 
        c.Assert(err, gc.IsNil)
 
563
        } {
 
564
                err := s.store.AddCharmWithArchive(rurl, storetesting.Charms.CharmArchive(c.MkDir(), rurl.URL.Name))
 
565
                c.Assert(err, gc.IsNil)
 
566
                err = s.store.Publish(rurl, params.StableChannel)
 
567
                c.Assert(err, gc.IsNil)
 
568
        }
710
569
 
711
570
        // A bundle that did not exist before should get revision 0.
712
571
        s.assertUploadBundle(c, "POST", newResolvedURL("~charmers/bundle/wordpress-simple-0", -1), "wordpress-simple")
910
769
        stats.CheckCounterSum(c, s.store, key, false, 3)
911
770
}
912
771
 
913
 
func (s *ArchiveSuite) TestPostErrorReadsFully(c *gc.C) {
914
 
        h := s.handler(c)
915
 
        defer h.Close()
916
 
 
917
 
        b := bytes.NewBuffer([]byte("test body"))
918
 
        r, err := http.NewRequest("POST", "/~charmers/trusty/wordpress/archive", b)
919
 
        c.Assert(err, gc.IsNil)
920
 
        r.Header.Set("Content-Type", "application/zip")
921
 
        r.SetBasicAuth(testUsername, testPassword)
922
 
        rec := httptest.NewRecorder()
923
 
        h.ServeHTTP(rec, r)
924
 
        c.Assert(rec.Code, gc.Equals, http.StatusBadRequest)
925
 
        c.Assert(b.Len(), gc.Equals, 0)
926
 
}
927
 
 
928
 
func (s *ArchiveSuite) TestPostAuthErrorReadsFully(c *gc.C) {
929
 
        h := s.handler(c)
930
 
        defer h.Close()
931
 
        b := bytes.NewBuffer([]byte("test body"))
932
 
        r, err := http.NewRequest("POST", "/~charmers/trusty/wordpress/archive", b)
933
 
        c.Assert(err, gc.IsNil)
934
 
        r.Header.Set("Content-Type", "application/zip")
935
 
        rec := httptest.NewRecorder()
936
 
        h.ServeHTTP(rec, r)
937
 
        c.Assert(rec.Code, gc.Equals, http.StatusUnauthorized)
938
 
        c.Assert(b.Len(), gc.Equals, 0)
939
 
}
940
 
 
941
772
func (s *ArchiveSuite) TestUploadOfCurrentCharmReadsFully(c *gc.C) {
942
773
        s.assertUploadCharm(c, "POST", newResolvedURL("~charmers/precise/wordpress-0", -1), "wordpress")
943
774
 
1013
844
                // return charm ids with a series.
1014
845
                id.Series = ch.Meta().Series[0]
1015
846
        }
 
847
        meta := ch.Meta()
 
848
        meta.Series = nil
1016
849
        s.assertEntityInfo(c, entityInfo{
1017
850
                Id: id,
1018
851
                Meta: entityMetaInfo{
1019
852
                        ArchiveSize:  &params.ArchiveSizeResponse{Size: size},
1020
 
                        CharmMeta:    ch.Meta(),
 
853
                        CharmMeta:    meta,
1021
854
                        CharmConfig:  ch.Config(),
1022
855
                        CharmActions: ch.Actions(),
1023
856
                },
1058
891
        _, err = f.Seek(0, 0)
1059
892
        c.Assert(err, gc.IsNil)
1060
893
 
1061
 
        uploadURL := url.UserOwnedURL()
 
894
        uploadURL := url.URL
1062
895
        if method == "POST" {
1063
896
                uploadURL.Revision = -1
1064
897
        }
1086
919
                },
1087
920
        })
1088
921
 
1089
 
        var entity mongodoc.Entity
1090
 
        err = s.store.DB.Entities().FindId(expectId.WithChannel("")).One(&entity)
 
922
        entity, err := s.store.FindEntity(url, nil)
1091
923
        c.Assert(err, gc.IsNil)
1092
924
        c.Assert(entity.BlobHash, gc.Equals, hashSum)
1093
925
        c.Assert(entity.BlobHash256, gc.Equals, hash256Sum)
1094
 
        c.Assert(entity.PromulgatedURL, gc.DeepEquals, expectedPromulgatedId)
1095
 
        c.Assert(entity.Development, gc.Equals, url.Development)
1096
 
        // Test that the expected entry has been created
1097
 
        // in the blob store.
1098
 
        r, _, err := s.store.BlobStore.Open(entity.BlobName)
1099
 
        c.Assert(err, gc.IsNil)
1100
 
        r.Close()
1101
 
 
1102
 
        return expectId, size
 
926
        c.Assert(entity.PreV5BlobHash256, gc.Not(gc.Equals), "")
 
927
        c.Assert(entity.PreV5BlobHash, gc.Not(gc.Equals), "")
 
928
        c.Assert(entity.PreV5BlobSize, gc.Not(gc.Equals), int64(0))
 
929
 
 
930
        c.Assert(entity.PromulgatedURL, gc.DeepEquals, url.PromulgatedURL())
 
931
        c.Assert(entity.Development, gc.Equals, false)
 
932
 
 
933
        return expectId, entity.PreV5BlobSize
1103
934
}
1104
935
 
1105
936
// assertUploadCharmError attempts to upload the testing charm with the
1107
938
// specified error. The URL must hold the expected revision that the
1108
939
// charm will be given when uploaded.
1109
940
func (s *ArchiveSuite) assertUploadCharmError(c *gc.C, method string, url, purl *charm.URL, charmName string, expectStatus int, expectBody interface{}) {
1110
 
        ch := storetesting.Charms.CharmArchive(c.MkDir(), charmName)
1111
 
        s.assertUploadError(c, method, url, purl, ch.Path, expectStatus, expectBody)
 
941
        ch := storetesting.Charms.CharmDir(charmName)
 
942
        s.assertUploadError(c, method, url, purl, ch, expectStatus, expectBody)
1112
943
}
1113
944
 
1114
945
// assertUploadError asserts that we get an error when uploading
1116
947
// The reason this method does not take a *router.ResolvedURL
1117
948
// is so that we can test what happens when an inconsistent promulgated URL
1118
949
// is passed in.
1119
 
func (s *ArchiveSuite) assertUploadError(c *gc.C, method string, url, purl *charm.URL, fileName string, expectStatus int, expectBody interface{}) {
1120
 
        f, err := os.Open(fileName)
1121
 
        c.Assert(err, gc.IsNil)
1122
 
        defer f.Close()
1123
 
 
1124
 
        // Calculate blob hashes.
1125
 
        hash := blobstore.NewHash()
1126
 
        size, err := io.Copy(hash, f)
1127
 
        c.Assert(err, gc.IsNil)
1128
 
        hashSum := fmt.Sprintf("%x", hash.Sum(nil))
1129
 
        _, err = f.Seek(0, 0)
1130
 
        c.Assert(err, gc.IsNil)
 
950
func (s *ArchiveSuite) assertUploadError(c *gc.C, method string, url, purl *charm.URL, entity charmstore.ArchiverTo, expectStatus int, expectBody interface{}) {
 
951
        blob, hashSum := getBlob(entity)
1131
952
 
1132
953
        uploadURL := *url
1133
954
        if method == "POST" {
1142
963
                Handler:       s.srv,
1143
964
                URL:           storeURL(path),
1144
965
                Method:        method,
1145
 
                ContentLength: size,
 
966
                ContentLength: int64(blob.Len()),
1146
967
                Header: http.Header{
1147
968
                        "Content-Type": {"application/zip"},
1148
969
                },
1149
 
                Body:         f,
 
970
                Body:         blob,
1150
971
                Username:     testUsername,
1151
972
                Password:     testPassword,
1152
973
                ExpectStatus: expectStatus,
1154
975
        })
1155
976
}
1156
977
 
 
978
// getBlob returns the contents and blob checksum of the given entity.
 
979
func getBlob(entity charmstore.ArchiverTo) (blob *bytes.Buffer, hash string) {
 
980
        blob = new(bytes.Buffer)
 
981
        err := entity.ArchiveTo(blob)
 
982
        if err != nil {
 
983
                panic(err)
 
984
        }
 
985
        h := blobstore.NewHash()
 
986
        h.Write(blob.Bytes())
 
987
        hash = fmt.Sprintf("%x", h.Sum(nil))
 
988
        return blob, hash
 
989
}
 
990
 
1157
991
var archiveFileErrorsTests = []struct {
1158
992
        about         string
1159
993
        path          string
1164
998
        about:         "entity not found",
1165
999
        path:          "~charmers/trusty/no-such-42/archive/icon.svg",
1166
1000
        expectStatus:  http.StatusNotFound,
1167
 
        expectMessage: `no matching charm or bundle for "cs:~charmers/trusty/no-such-42"`,
 
1001
        expectMessage: `no matching charm or bundle for cs:~charmers/trusty/no-such-42`,
1168
1002
        expectCode:    params.ErrNotFound,
1169
1003
}, {
1170
1004
        about:         "directory listing",
1178
1012
        expectStatus:  http.StatusNotFound,
1179
1013
        expectMessage: `file "no-such" not found in the archive`,
1180
1014
        expectCode:    params.ErrNotFound,
 
1015
}, {
 
1016
        about:         "no permissions",
 
1017
        path:          "~charmers/utopic/mysql-0/archive/metadata.yaml",
 
1018
        expectStatus:  http.StatusUnauthorized,
 
1019
        expectMessage: `unauthorized: access denied for user "bob"`,
 
1020
        expectCode:    params.ErrUnauthorized,
1181
1021
}}
1182
1022
 
1183
1023
func (s *ArchiveSuite) TestArchiveFileErrors(c *gc.C) {
1184
 
        wordpress := storetesting.Charms.CharmArchive(c.MkDir(), "wordpress")
1185
 
        url := newResolvedURL("cs:~charmers/utopic/wordpress-0", 0)
1186
 
        err := s.store.AddCharmWithArchive(url, wordpress)
1187
 
        c.Assert(err, gc.IsNil)
1188
 
        err = s.store.SetPerms(&url.URL, "read", params.Everyone, url.URL.User)
1189
 
        c.Assert(err, gc.IsNil)
 
1024
        s.addPublicCharmFromRepo(c, "wordpress", newResolvedURL("cs:~charmers/utopic/wordpress-0", 0))
 
1025
        id, _ := s.addPublicCharmFromRepo(c, "mysql", newResolvedURL("cs:~charmers/utopic/mysql-0", 0))
 
1026
        err := s.store.SetPerms(&id.URL, "stable.read", "no-one")
 
1027
        c.Assert(err, gc.IsNil)
 
1028
        s.discharge = dischargeForUser("bob")
1190
1029
        for i, test := range archiveFileErrorsTests {
1191
1030
                c.Logf("test %d: %s", i, test.about)
1192
1031
                httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
1193
1032
                        Handler:      s.srv,
1194
1033
                        URL:          storeURL(test.path),
 
1034
                        Do:           bakeryDo(nil),
1195
1035
                        Method:       "GET",
1196
1036
                        ExpectStatus: test.expectStatus,
1197
1037
                        ExpectBody: params.Error{
1205
1045
func (s *ArchiveSuite) TestArchiveFileGet(c *gc.C) {
1206
1046
        ch := storetesting.Charms.CharmArchive(c.MkDir(), "all-hooks")
1207
1047
        id := newResolvedURL("cs:~charmers/utopic/all-hooks-0", 0)
1208
 
        err := s.store.AddCharmWithArchive(id, ch)
1209
 
        c.Assert(err, gc.IsNil)
1210
 
        err = s.store.SetPerms(&id.URL, "read", params.Everyone, id.URL.User)
1211
 
        c.Assert(err, gc.IsNil)
 
1048
        s.addPublicCharm(c, ch, id)
1212
1049
        zipFile, err := zip.OpenReader(ch.Path)
1213
1050
        c.Assert(err, gc.IsNil)
1214
1051
        defer zipFile.Close()
1219
1056
        s.assertArchiveFileContents(c, zipFile, "~charmers/utopic/all-hooks-0/archive/hooks/install")
1220
1057
}
1221
1058
 
 
1059
func (s *ArchiveSuite) TestArchiveFileGetMultiSeries(c *gc.C) {
 
1060
        // V4 SPECIFIC:
 
1061
        // Check that the series field of a multi-series charm is omitted.
 
1062
        url := charm.MustParseURL("~charmers/juju-gui-0")
 
1063
        s.assertUploadCharm(c, "POST", newResolvedURL(url.String(), -1), "multi-series")
 
1064
        err := s.store.SetPerms(url, "unpublished.read", params.Everyone)
 
1065
        c.Assert(err, gc.IsNil)
 
1066
 
 
1067
        c.Logf("dorequest %v", storeURL(url.String()+"/archive"))
 
1068
        rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
 
1069
                Handler: s.srv,
 
1070
                URL:     storeURL(url.Path() + "/archive"),
 
1071
        })
 
1072
        c.Assert(rec.Code, gc.Equals, http.StatusOK, gc.Commentf("body: %q", rec.Body.Bytes()))
 
1073
        ch, err := charm.ReadCharmArchiveBytes(rec.Body.Bytes())
 
1074
        c.Assert(err, gc.IsNil)
 
1075
        c.Assert(ch.Meta().Series, gc.HasLen, 0)
 
1076
}
 
1077
 
1222
1078
// assertArchiveFileContents checks that the response returned by the
1223
1079
// serveArchiveFile endpoint is correct for the given archive and URL path.
1224
1080
func (s *ArchiveSuite) assertArchiveFileContents(c *gc.C, zipFile *zip.ReadCloser, path string) {
1275
1131
                Method:       "DELETE",
1276
1132
                Username:     testUsername,
1277
1133
                Password:     testPassword,
1278
 
                ExpectStatus: http.StatusOK,
 
1134
                ExpectStatus: http.StatusMethodNotAllowed,
 
1135
                ExpectBody: params.Error{
 
1136
                        Message: `DELETE not allowed`,
 
1137
                        Code:    params.ErrMethodNotAllowed,
 
1138
                },
1279
1139
        })
1280
1140
 
1281
 
        // The entity has been deleted.
1282
 
        count, err := s.store.DB.Entities().FindId(url).Count()
1283
 
        c.Assert(err, gc.IsNil)
1284
 
        c.Assert(count, gc.Equals, 0)
1285
 
 
1286
 
        // The blob has been deleted.
1287
 
        _, _, err = s.store.BlobStore.Open(entity.BlobName)
1288
 
        c.Assert(err, gc.ErrorMatches, "resource.*not found")
 
1141
        // TODO(mhilton) reinstate this check when DELETE is re-enabled.
 
1142
        //      // The entity has been deleted.
 
1143
        //      count, err := s.store.DB.Entities().FindId(url).Count()
 
1144
        //      c.Assert(err, gc.IsNil)
 
1145
        //      c.Assert(count, gc.Equals, 0)
 
1146
        //
 
1147
        //      // The blob has been deleted.
 
1148
        //      _, _, err = s.store.BlobStore.Open(entity.BlobName)
 
1149
        //      c.Assert(err, gc.ErrorMatches, "resource.*not found")
1289
1150
}
1290
1151
 
1291
1152
func (s *ArchiveSuite) TestDeleteSpecificCharm(c *gc.C) {
1304
1165
                Method:       "DELETE",
1305
1166
                Username:     testUsername,
1306
1167
                Password:     testPassword,
1307
 
                ExpectStatus: http.StatusOK,
 
1168
                ExpectStatus: http.StatusMethodNotAllowed,
 
1169
                ExpectBody: params.Error{
 
1170
                        Message: `DELETE not allowed`,
 
1171
                        Code:    params.ErrMethodNotAllowed,
 
1172
                },
1308
1173
        })
1309
1174
 
1310
1175
        // The other two charms are still present in the database.
1327
1192
                Method:       "DELETE",
1328
1193
                Username:     testUsername,
1329
1194
                Password:     testPassword,
1330
 
                ExpectStatus: http.StatusNotFound,
1331
 
                ExpectBody: params.Error{
1332
 
                        Message: `no matching charm or bundle for "cs:~charmers/utopic/no-such-0"`,
1333
 
                        Code:    params.ErrNotFound,
1334
 
                },
1335
 
        })
1336
 
}
1337
 
 
1338
 
func (s *ArchiveSuite) TestDeleteError(c *gc.C) {
1339
 
        // Add a charm to the database (not including the archive).
1340
 
        id := "~charmers/utopic/mysql-42"
1341
 
        url := newResolvedURL(id, -1)
1342
 
        err := s.store.AddCharm(storetesting.Charms.CharmArchive(c.MkDir(), "mysql"),
1343
 
                charmstore.AddParams{
1344
 
                        URL:      url,
1345
 
                        BlobName: "no-such-name",
1346
 
                        BlobHash: fakeBlobHash,
1347
 
                        BlobSize: fakeBlobSize,
1348
 
                })
1349
 
        c.Assert(err, gc.IsNil)
1350
 
 
1351
 
        // Try to delete the charm using the API.
1352
 
        httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
1353
 
                Handler:      s.srv,
1354
 
                URL:          storeURL(id + "/archive"),
1355
 
                Method:       "DELETE",
1356
 
                Username:     testUsername,
1357
 
                Password:     testPassword,
1358
 
                ExpectStatus: http.StatusInternalServerError,
1359
 
                ExpectBody: params.Error{
1360
 
                        Message: `cannot remove blob no-such-name: resource at path "global/no-such-name" not found`,
1361
 
                },
1362
 
        })
1363
 
}
1364
 
 
1365
 
func (s *ArchiveSuite) TestDeleteCounters(c *gc.C) {
1366
 
        if !storetesting.MongoJSEnabled() {
1367
 
                c.Skip("MongoDB JavaScript not available")
1368
 
        }
1369
 
 
1370
 
        // Add a charm to the database (including the archive).
1371
 
        id := "~charmers/utopic/mysql-42"
1372
 
        err := s.store.AddCharmWithArchive(
1373
 
                newResolvedURL(id, -1),
1374
 
                storetesting.Charms.CharmArchive(c.MkDir(), "mysql"))
1375
 
        c.Assert(err, gc.IsNil)
1376
 
 
1377
 
        // Delete the charm using the API.
1378
 
        rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
1379
 
                Handler:  s.srv,
1380
 
                Method:   "DELETE",
1381
 
                URL:      storeURL(id + "/archive"),
1382
 
                Username: testUsername,
1383
 
                Password: testPassword,
1384
 
        })
1385
 
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
1386
 
 
1387
 
        // Check that the delete count for the entity has been updated.
1388
 
        key := []string{params.StatsArchiveDelete, "utopic", "mysql", "charmers", "42"}
1389
 
        stats.CheckCounterSum(c, s.store, key, false, 1)
1390
 
}
1391
 
 
1392
 
func (s *ArchiveSuite) TestPostAuthErrors(c *gc.C) {
1393
 
        checkAuthErrors(c, s.srv, "POST", "~charmers/utopic/django/archive")
1394
 
}
1395
 
 
1396
 
func (s *ArchiveSuite) TestDeleteAuthErrors(c *gc.C) {
1397
 
        err := s.store.AddCharmWithArchive(
1398
 
                newResolvedURL("~charmers/utopic/django-42", 42),
1399
 
                storetesting.Charms.CharmArchive(c.MkDir(), "wordpress"),
1400
 
        )
1401
 
        c.Assert(err, gc.IsNil)
1402
 
        checkAuthErrors(c, s.srv, "DELETE", "utopic/django-42/archive")
 
1195
                ExpectStatus: http.StatusMethodNotAllowed,
 
1196
                ExpectBody: params.Error{
 
1197
                        Message: `DELETE not allowed`,
 
1198
                        Code:    params.ErrMethodNotAllowed,
 
1199
                },
 
1200
        })
 
1201
}
 
1202
 
 
1203
// TODO(mhilton) reinstate this test when DELETE is re-enabled.
 
1204
//func (s *ArchiveSuite) TestDeleteError(c *gc.C) {
 
1205
//      // Add a charm to the database (not including the archive).
 
1206
//      id := "~charmers/utopic/mysql-42"
 
1207
//      url := newResolvedURL(id, -1)
 
1208
//      err := s.store.AddCharmWithArchive(url, storetesting.Charms.CharmArchive(c.MkDir(), "mysql"))
 
1209
//      c.Assert(err, gc.IsNil)
 
1210
//
 
1211
//      err = s.store.DB.Entities().UpdateId(&url.URL, bson.M{
 
1212
//              "$set": bson.M{
 
1213
//                      "blobname": "no-such-name",
 
1214
//              },
 
1215
//      })
 
1216
//      c.Assert(err, gc.IsNil)
 
1217
//      // TODO update entity to change BlobName to "no-such-name"
 
1218
//
 
1219
//      // Try to delete the charm using the API.
 
1220
//      httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
 
1221
//              Handler:      s.srv,
 
1222
//              URL:          storeURL(id + "/archive"),
 
1223
//              Method:       "DELETE",
 
1224
//              Username:     testUsername,
 
1225
//              Password:     testPassword,
 
1226
//              ExpectStatus: http.StatusInternalServerError,
 
1227
//              ExpectBody: params.Error{
 
1228
//                      Message: `cannot delete "cs:~charmers/utopic/mysql-42": cannot remove blob no-such-name: resource at path "global/no-such-name" not found`,
 
1229
//              },
 
1230
//      })
 
1231
//}
 
1232
 
 
1233
// TODO(mhilton) reinstate this test when DELETE is re-enabled
 
1234
//.func (s *ArchiveSuite) TestDeleteCounters(c *gc.C) {
 
1235
//      if !storetesting.MongoJSEnabled() {
 
1236
//              c.Skip("MongoDB JavaScript not available")
 
1237
//      }
 
1238
//
 
1239
//      // Add a charm to the database (including the archive).
 
1240
//      id := "~charmers/utopic/mysql-42"
 
1241
//      err := s.store.AddCharmWithArchive(
 
1242
//              newResolvedURL(id, -1),
 
1243
//              storetesting.Charms.CharmArchive(c.MkDir(), "mysql"))
 
1244
//      c.Assert(err, gc.IsNil)
 
1245
//
 
1246
//      // Delete the charm using the API.
 
1247
//      rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
 
1248
//              Handler:  s.srv,
 
1249
//              Method:   "DELETE",
 
1250
//              URL:      storeURL(id + "/archive"),
 
1251
//              Username: testUsername,
 
1252
//              Password: testPassword,
 
1253
//      })
 
1254
//      c.Assert(rec.Code, gc.Equals, http.StatusOK)
 
1255
//
 
1256
//      // Check that the delete count for the entity has been updated.
 
1257
//      key := []string{params.StatsArchiveDelete, "utopic", "mysql", "charmers", "42"}
 
1258
//      stats.CheckCounterSum(c, s.store, key, false, 1)
 
1259
//}
 
1260
 
 
1261
type basicAuthArchiveSuite struct {
 
1262
        commonSuite
 
1263
}
 
1264
 
 
1265
var _ = gc.Suite(&basicAuthArchiveSuite{})
 
1266
 
 
1267
func (s *basicAuthArchiveSuite) TestPostAuthErrors(c *gc.C) {
 
1268
        s.checkAuthErrors(c, "POST", "~charmers/utopic/django/archive")
 
1269
}
 
1270
 
 
1271
// TODO(mhilton) reinstate this test when DELETE is re-enabled.
 
1272
//func (s *basicAuthArchiveSuite) TestDeleteAuthErrors(c *gc.C) {
 
1273
//      err := s.store.AddCharmWithArchive(
 
1274
//              newResolvedURL("~charmers/utopic/django-42", 42),
 
1275
//              storetesting.Charms.CharmArchive(c.MkDir(), "wordpress"),
 
1276
//      )
 
1277
//      c.Assert(err, gc.IsNil)
 
1278
//      s.checkAuthErrors(c, "DELETE", "utopic/django-42/archive")
 
1279
//}
 
1280
 
 
1281
func (s *basicAuthArchiveSuite) TestPostErrorReadsFully(c *gc.C) {
 
1282
        h := s.handler(c)
 
1283
        defer h.Close()
 
1284
 
 
1285
        b := strings.NewReader("test body")
 
1286
        r, err := http.NewRequest("POST", "/~charmers/trusty/wordpress/archive", b)
 
1287
        c.Assert(err, gc.IsNil)
 
1288
        r.Header.Set("Content-Type", "application/zip")
 
1289
        r.SetBasicAuth(testUsername, testPassword)
 
1290
        rec := httptest.NewRecorder()
 
1291
        h.ServeHTTP(rec, r)
 
1292
        c.Assert(rec.Code, gc.Equals, http.StatusBadRequest)
 
1293
        c.Assert(b.Len(), gc.Equals, 0)
 
1294
}
 
1295
 
 
1296
func (s *basicAuthArchiveSuite) TestPostAuthErrorReadsFully(c *gc.C) {
 
1297
        h := s.handler(c)
 
1298
        defer h.Close()
 
1299
        b := strings.NewReader("test body")
 
1300
        r, err := http.NewRequest("POST", "/~charmers/trusty/wordpress/archive", b)
 
1301
        c.Assert(err, gc.IsNil)
 
1302
        r.Header.Set("Content-Type", "application/zip")
 
1303
        rec := httptest.NewRecorder()
 
1304
        h.ServeHTTP(rec, r)
 
1305
        c.Assert(rec.Code, gc.Equals, http.StatusUnauthorized)
 
1306
        c.Assert(b.Len(), gc.Equals, 0)
1403
1307
}
1404
1308
 
1405
1309
var archiveAuthErrorsTests = []struct {
1430
1334
        expectMessage: "invalid user name or password",
1431
1335
}}
1432
1336
 
1433
 
func checkAuthErrors(c *gc.C, handler http.Handler, method, url string) {
1434
 
        archiveURL := storeURL(url)
 
1337
func (s *basicAuthArchiveSuite) checkAuthErrors(c *gc.C, method, url string) {
1435
1338
        for i, test := range archiveAuthErrorsTests {
1436
1339
                c.Logf("test %d: %s", i, test.about)
1437
1340
                if test.header == nil {
1441
1344
                        test.header.Add("Content-Type", "application/zip")
1442
1345
                }
1443
1346
                httptesting.AssertJSONCall(c, httptesting.JSONCallParams{
1444
 
                        Handler:      handler,
1445
 
                        URL:          archiveURL,
 
1347
                        Handler:      s.srv,
 
1348
                        URL:          storeURL(url),
1446
1349
                        Method:       method,
1447
1350
                        Header:       test.header,
1448
1351
                        Username:     test.username,
1556
1459
                c.Logf("test %d: %s", i, id)
1557
1460
                url := newResolvedURL(id, -1)
1558
1461
 
1559
 
                // Add a charm to the database (including the archive).
1560
 
                err := s.store.AddCharmWithArchive(url, storetesting.Charms.CharmArchive(c.MkDir(), "mysql"))
1561
 
                c.Assert(err, gc.IsNil)
1562
 
                err = s.store.SetPerms(&url.URL, "read", params.Everyone, url.URL.User)
1563
 
                c.Assert(err, gc.IsNil)
 
1462
                // Add a charm to the database.
 
1463
                s.addPublicCharm(c, storetesting.NewCharm(nil), url)
1564
1464
 
1565
1465
                // Download the charm archive using the API.
1566
1466
                rec := httptesting.DoRequest(c, httptesting.DoRequestParams{
1573
1473
                stats.CheckSearchTotalDownloads(c, s.store, &url.URL, 1)
1574
1474
        }
1575
1475
}
 
1476
 
 
1477
func (s *commonSuite) assertArchiveDownload(c *gc.C, id string, extraParams *httptesting.DoRequestParams, archiveBytes []byte) *httptest.ResponseRecorder {
 
1478
        doParams := httptesting.DoRequestParams{}
 
1479
        if extraParams != nil {
 
1480
                doParams = *extraParams
 
1481
        }
 
1482
        doParams.Handler = s.srv
 
1483
        if doParams.URL == "" {
 
1484
                doParams.URL = storeURL(id + "/archive")
 
1485
        }
 
1486
        rec := httptesting.DoRequest(c, doParams)
 
1487
        c.Assert(rec.Code, gc.Equals, http.StatusOK)
 
1488
 
 
1489
        c.Assert(rec.Body.Bytes(), gc.DeepEquals, archiveBytes)
 
1490
        c.Assert(rec.Header().Get(params.ContentHashHeader), gc.Equals, hashOfBytes(archiveBytes))
 
1491
        return rec
 
1492
}