github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/apiserver/charms_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver_test 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "net/http" 13 "net/url" 14 "os" 15 "path/filepath" 16 "strings" 17 18 jc "github.com/juju/testing/checkers" 19 "github.com/juju/utils" 20 gc "launchpad.net/gocheck" 21 22 "github.com/juju/juju/charm" 23 charmtesting "github.com/juju/juju/charm/testing" 24 "github.com/juju/juju/environs" 25 jujutesting "github.com/juju/juju/juju/testing" 26 "github.com/juju/juju/state" 27 "github.com/juju/juju/state/api/params" 28 ) 29 30 type authHttpSuite struct { 31 jujutesting.JujuConnSuite 32 userTag string 33 password string 34 archiveContentType string 35 } 36 37 func (s *authHttpSuite) SetUpTest(c *gc.C) { 38 s.JujuConnSuite.SetUpTest(c) 39 user := s.AddUser(c, "joe") 40 s.userTag = user.Tag() 41 s.password = "password" 42 } 43 44 func (s *authHttpSuite) sendRequest(c *gc.C, tag, password, method, uri, contentType string, body io.Reader) (*http.Response, error) { 45 req, err := http.NewRequest(method, uri, body) 46 c.Assert(err, gc.IsNil) 47 if tag != "" && password != "" { 48 req.SetBasicAuth(tag, password) 49 } 50 if contentType != "" { 51 req.Header.Set("Content-Type", contentType) 52 } 53 return utils.GetNonValidatingHTTPClient().Do(req) 54 } 55 56 func (s *authHttpSuite) baseURL(c *gc.C) *url.URL { 57 _, info, err := s.APIConn.Environ.StateInfo() 58 c.Assert(err, gc.IsNil) 59 return &url.URL{ 60 Scheme: "https", 61 Host: info.Addrs[0], 62 Path: "", 63 } 64 } 65 66 func (s *authHttpSuite) authRequest(c *gc.C, method, uri, contentType string, body io.Reader) (*http.Response, error) { 67 return s.sendRequest(c, s.userTag, s.password, method, uri, contentType, body) 68 } 69 70 func (s *authHttpSuite) uploadRequest(c *gc.C, uri string, asZip bool, path string) (*http.Response, error) { 71 contentType := "application/octet-stream" 72 if asZip { 73 contentType = s.archiveContentType 74 } 75 76 if path == "" { 77 return s.authRequest(c, "POST", uri, contentType, nil) 78 } 79 80 file, err := os.Open(path) 81 c.Assert(err, gc.IsNil) 82 defer file.Close() 83 return s.authRequest(c, "POST", uri, contentType, file) 84 } 85 86 type charmsSuite struct { 87 authHttpSuite 88 } 89 90 var _ = gc.Suite(&charmsSuite{}) 91 92 func (s *charmsSuite) SetUpSuite(c *gc.C) { 93 s.JujuConnSuite.SetUpSuite(c) 94 s.archiveContentType = "application/zip" 95 } 96 97 func (s *charmsSuite) TestCharmsServedSecurely(c *gc.C) { 98 _, info, err := s.APIConn.Environ.StateInfo() 99 c.Assert(err, gc.IsNil) 100 uri := "http://" + info.Addrs[0] + "/charms" 101 _, err = s.sendRequest(c, "", "", "GET", uri, "", nil) 102 c.Assert(err, gc.ErrorMatches, `.*malformed HTTP response.*`) 103 } 104 105 func (s *charmsSuite) TestRequiresAuth(c *gc.C) { 106 resp, err := s.sendRequest(c, "", "", "GET", s.charmsURI(c, ""), "", nil) 107 c.Assert(err, gc.IsNil) 108 s.assertErrorResponse(c, resp, http.StatusUnauthorized, "unauthorized") 109 } 110 111 func (s *charmsSuite) TestRequiresPOSTorGET(c *gc.C) { 112 resp, err := s.authRequest(c, "PUT", s.charmsURI(c, ""), "", nil) 113 c.Assert(err, gc.IsNil) 114 s.assertErrorResponse(c, resp, http.StatusMethodNotAllowed, `unsupported method: "PUT"`) 115 } 116 117 func (s *charmsSuite) TestAuthRequiresUser(c *gc.C) { 118 // Add a machine and try to login. 119 machine, err := s.State.AddMachine("quantal", state.JobHostUnits) 120 c.Assert(err, gc.IsNil) 121 err = machine.SetProvisioned("foo", "fake_nonce", nil) 122 c.Assert(err, gc.IsNil) 123 password, err := utils.RandomPassword() 124 c.Assert(err, gc.IsNil) 125 err = machine.SetPassword(password) 126 c.Assert(err, gc.IsNil) 127 128 resp, err := s.sendRequest(c, machine.Tag(), password, "GET", s.charmsURI(c, ""), "", nil) 129 c.Assert(err, gc.IsNil) 130 s.assertErrorResponse(c, resp, http.StatusUnauthorized, "unauthorized") 131 132 // Now try a user login. 133 resp, err = s.authRequest(c, "GET", s.charmsURI(c, ""), "", nil) 134 c.Assert(err, gc.IsNil) 135 s.assertErrorResponse(c, resp, http.StatusBadRequest, "expected url=CharmURL query argument") 136 } 137 138 func (s *charmsSuite) TestUploadRequiresSeries(c *gc.C) { 139 resp, err := s.authRequest(c, "POST", s.charmsURI(c, ""), "", nil) 140 c.Assert(err, gc.IsNil) 141 s.assertErrorResponse(c, resp, http.StatusBadRequest, "expected series=URL argument") 142 } 143 144 func (s *charmsSuite) TestUploadFailsWithInvalidZip(c *gc.C) { 145 // Create an empty file. 146 tempFile, err := ioutil.TempFile(c.MkDir(), "charm") 147 c.Assert(err, gc.IsNil) 148 149 // Pretend we upload a zip by setting the Content-Type, so we can 150 // check the error at extraction time later. 151 resp, err := s.uploadRequest(c, s.charmsURI(c, "?series=quantal"), true, tempFile.Name()) 152 c.Assert(err, gc.IsNil) 153 s.assertErrorResponse(c, resp, http.StatusBadRequest, "cannot open charm archive: zip: not a valid zip file") 154 155 // Now try with the default Content-Type. 156 resp, err = s.uploadRequest(c, s.charmsURI(c, "?series=quantal"), false, tempFile.Name()) 157 c.Assert(err, gc.IsNil) 158 s.assertErrorResponse(c, resp, http.StatusBadRequest, "expected Content-Type: application/zip, got: application/octet-stream") 159 } 160 161 func (s *charmsSuite) TestUploadBumpsRevision(c *gc.C) { 162 // Add the dummy charm with revision 1. 163 ch := charmtesting.Charms.Bundle(c.MkDir(), "dummy") 164 curl := charm.MustParseURL( 165 fmt.Sprintf("local:quantal/%s-%d", ch.Meta().Name, ch.Revision()), 166 ) 167 bundleURL, err := url.Parse("http://bundles.testing.invalid/dummy-1") 168 c.Assert(err, gc.IsNil) 169 _, err = s.State.AddCharm(ch, curl, bundleURL, "dummy-1-sha256") 170 c.Assert(err, gc.IsNil) 171 172 // Now try uploading the same revision and verify it gets bumped, 173 // and the BundleURL and BundleSha256 are calculated. 174 resp, err := s.uploadRequest(c, s.charmsURI(c, "?series=quantal"), true, ch.Path) 175 c.Assert(err, gc.IsNil) 176 expectedURL := charm.MustParseURL("local:quantal/dummy-2") 177 s.assertUploadResponse(c, resp, expectedURL.String()) 178 sch, err := s.State.Charm(expectedURL) 179 c.Assert(err, gc.IsNil) 180 c.Assert(sch.URL(), gc.DeepEquals, expectedURL) 181 c.Assert(sch.Revision(), gc.Equals, 2) 182 c.Assert(sch.IsUploaded(), jc.IsTrue) 183 // No more checks for these two here, because they 184 // are verified in TestUploadRespectsLocalRevision. 185 c.Assert(sch.BundleURL(), gc.Not(gc.Equals), "") 186 c.Assert(sch.BundleSha256(), gc.Not(gc.Equals), "") 187 } 188 189 func (s *charmsSuite) TestUploadRespectsLocalRevision(c *gc.C) { 190 // Make a dummy charm dir with revision 123. 191 dir := charmtesting.Charms.ClonedDir(c.MkDir(), "dummy") 192 dir.SetDiskRevision(123) 193 // Now bundle the dir. 194 tempFile, err := ioutil.TempFile(c.MkDir(), "charm") 195 c.Assert(err, gc.IsNil) 196 defer tempFile.Close() 197 defer os.Remove(tempFile.Name()) 198 err = dir.BundleTo(tempFile) 199 c.Assert(err, gc.IsNil) 200 201 // Now try uploading it and ensure the revision persists. 202 resp, err := s.uploadRequest(c, s.charmsURI(c, "?series=quantal"), true, tempFile.Name()) 203 c.Assert(err, gc.IsNil) 204 expectedURL := charm.MustParseURL("local:quantal/dummy-123") 205 s.assertUploadResponse(c, resp, expectedURL.String()) 206 sch, err := s.State.Charm(expectedURL) 207 c.Assert(err, gc.IsNil) 208 c.Assert(sch.URL(), gc.DeepEquals, expectedURL) 209 c.Assert(sch.Revision(), gc.Equals, 123) 210 c.Assert(sch.IsUploaded(), jc.IsTrue) 211 212 // First rewind the reader, which was reset but BundleTo() above. 213 _, err = tempFile.Seek(0, 0) 214 c.Assert(err, gc.IsNil) 215 216 // Finally, verify the SHA256 and uploaded URL. 217 expectedSHA256, _, err := utils.ReadSHA256(tempFile) 218 c.Assert(err, gc.IsNil) 219 name := charm.Quote(expectedURL.String()) 220 storage, err := environs.GetStorage(s.State) 221 c.Assert(err, gc.IsNil) 222 expectedUploadURL, err := storage.URL(name) 223 c.Assert(err, gc.IsNil) 224 225 c.Assert(sch.BundleURL().String(), gc.Equals, expectedUploadURL) 226 c.Assert(sch.BundleSha256(), gc.Equals, expectedSHA256) 227 228 reader, err := storage.Get(name) 229 c.Assert(err, gc.IsNil) 230 defer reader.Close() 231 downloadedSHA256, _, err := utils.ReadSHA256(reader) 232 c.Assert(err, gc.IsNil) 233 c.Assert(downloadedSHA256, gc.Equals, expectedSHA256) 234 } 235 236 func (s *charmsSuite) TestUploadAllowsTopLevelPath(c *gc.C) { 237 ch := charmtesting.Charms.Bundle(c.MkDir(), "dummy") 238 // Backwards compatibility check, that we can upload charms to 239 // https://host:port/charms 240 url := s.charmsURL(c, "series=quantal") 241 url.Path = "/charms" 242 resp, err := s.uploadRequest(c, url.String(), true, ch.Path) 243 c.Assert(err, gc.IsNil) 244 expectedURL := charm.MustParseURL("local:quantal/dummy-1") 245 s.assertUploadResponse(c, resp, expectedURL.String()) 246 } 247 248 func (s *charmsSuite) TestUploadAllowsEnvUUIDPath(c *gc.C) { 249 // Check that we can upload charms to https://host:port/ENVUUID/charms 250 ch := charmtesting.Charms.Bundle(c.MkDir(), "dummy") 251 environ, err := s.State.Environment() 252 c.Assert(err, gc.IsNil) 253 url := s.charmsURL(c, "series=quantal") 254 url.Path = fmt.Sprintf("/environment/%s/charms", environ.UUID()) 255 resp, err := s.uploadRequest(c, url.String(), true, ch.Path) 256 c.Assert(err, gc.IsNil) 257 expectedURL := charm.MustParseURL("local:quantal/dummy-1") 258 s.assertUploadResponse(c, resp, expectedURL.String()) 259 } 260 261 func (s *charmsSuite) TestUploadRejectsWrongEnvUUIDPath(c *gc.C) { 262 // Check that we cannot upload charms to https://host:port/BADENVUUID/charms 263 url := s.charmsURL(c, "series=quantal") 264 url.Path = "/environment/dead-beef-123456/charms" 265 resp, err := s.authRequest(c, "POST", url.String(), "", nil) 266 c.Assert(err, gc.IsNil) 267 s.assertErrorResponse(c, resp, http.StatusNotFound, `unknown environment: "dead-beef-123456"`) 268 } 269 270 func (s *charmsSuite) TestUploadRepackagesNestedArchives(c *gc.C) { 271 // Make a clone of the dummy charm in a nested directory. 272 rootDir := c.MkDir() 273 dirPath := filepath.Join(rootDir, "subdir1", "subdir2") 274 err := os.MkdirAll(dirPath, 0755) 275 c.Assert(err, gc.IsNil) 276 dir := charmtesting.Charms.ClonedDir(dirPath, "dummy") 277 // Now tweak the path the dir thinks it is in and bundle it. 278 dir.Path = rootDir 279 tempFile, err := ioutil.TempFile(c.MkDir(), "charm") 280 c.Assert(err, gc.IsNil) 281 defer tempFile.Close() 282 defer os.Remove(tempFile.Name()) 283 err = dir.BundleTo(tempFile) 284 c.Assert(err, gc.IsNil) 285 286 // Try reading it as a bundle - should fail due to nested dirs. 287 _, err = charm.ReadBundle(tempFile.Name()) 288 c.Assert(err, gc.ErrorMatches, "bundle file not found: metadata.yaml") 289 290 // Now try uploading it - should succeeed and be repackaged. 291 resp, err := s.uploadRequest(c, s.charmsURI(c, "?series=quantal"), true, tempFile.Name()) 292 c.Assert(err, gc.IsNil) 293 expectedURL := charm.MustParseURL("local:quantal/dummy-1") 294 s.assertUploadResponse(c, resp, expectedURL.String()) 295 sch, err := s.State.Charm(expectedURL) 296 c.Assert(err, gc.IsNil) 297 c.Assert(sch.URL(), gc.DeepEquals, expectedURL) 298 c.Assert(sch.Revision(), gc.Equals, 1) 299 c.Assert(sch.IsUploaded(), jc.IsTrue) 300 301 // Get it from the storage and try to read it as a bundle - it 302 // should succeed, because it was repackaged during upload to 303 // strip nested dirs. 304 archiveName := strings.TrimPrefix(sch.BundleURL().RequestURI(), "/dummyenv/private/") 305 storage, err := environs.GetStorage(s.State) 306 c.Assert(err, gc.IsNil) 307 reader, err := storage.Get(archiveName) 308 c.Assert(err, gc.IsNil) 309 defer reader.Close() 310 311 data, err := ioutil.ReadAll(reader) 312 c.Assert(err, gc.IsNil) 313 downloadedFile, err := ioutil.TempFile(c.MkDir(), "downloaded") 314 c.Assert(err, gc.IsNil) 315 defer downloadedFile.Close() 316 defer os.Remove(downloadedFile.Name()) 317 err = ioutil.WriteFile(downloadedFile.Name(), data, 0644) 318 c.Assert(err, gc.IsNil) 319 320 bundle, err := charm.ReadBundle(downloadedFile.Name()) 321 c.Assert(err, gc.IsNil) 322 c.Assert(bundle.Revision(), jc.DeepEquals, sch.Revision()) 323 c.Assert(bundle.Meta(), jc.DeepEquals, sch.Meta()) 324 c.Assert(bundle.Config(), jc.DeepEquals, sch.Config()) 325 } 326 327 func (s *charmsSuite) TestGetRequiresCharmURL(c *gc.C) { 328 uri := s.charmsURI(c, "?file=hooks/install") 329 resp, err := s.authRequest(c, "GET", uri, "", nil) 330 c.Assert(err, gc.IsNil) 331 s.assertErrorResponse( 332 c, resp, http.StatusBadRequest, 333 "expected url=CharmURL query argument", 334 ) 335 } 336 337 func (s *charmsSuite) TestGetFailsWithInvalidCharmURL(c *gc.C) { 338 uri := s.charmsURI(c, "?url=local:precise/no-such") 339 resp, err := s.authRequest(c, "GET", uri, "", nil) 340 c.Assert(err, gc.IsNil) 341 s.assertErrorResponse( 342 c, resp, http.StatusBadRequest, 343 "unable to retrieve and save the charm: charm not found in the provider storage: .*", 344 ) 345 } 346 347 func (s *charmsSuite) TestGetReturnsNotFoundWhenMissing(c *gc.C) { 348 // Add the dummy charm. 349 ch := charmtesting.Charms.Bundle(c.MkDir(), "dummy") 350 _, err := s.uploadRequest( 351 c, s.charmsURI(c, "?series=quantal"), true, ch.Path) 352 c.Assert(err, gc.IsNil) 353 354 // Ensure a 404 is returned for files not included in the charm. 355 for i, file := range []string{ 356 "no-such-file", "..", "../../../etc/passwd", "hooks/delete", 357 } { 358 c.Logf("test %d: %s", i, file) 359 uri := s.charmsURI(c, "?url=local:quantal/dummy-1&file="+file) 360 resp, err := s.authRequest(c, "GET", uri, "", nil) 361 c.Assert(err, gc.IsNil) 362 c.Assert(resp.StatusCode, gc.Equals, http.StatusNotFound) 363 } 364 } 365 366 func (s *charmsSuite) TestGetReturnsForbiddenWithDirectory(c *gc.C) { 367 // Add the dummy charm. 368 ch := charmtesting.Charms.Bundle(c.MkDir(), "dummy") 369 _, err := s.uploadRequest( 370 c, s.charmsURI(c, "?series=quantal"), true, ch.Path) 371 c.Assert(err, gc.IsNil) 372 373 // Ensure a 403 is returned if the requested file is a directory. 374 uri := s.charmsURI(c, "?url=local:quantal/dummy-1&file=hooks") 375 resp, err := s.authRequest(c, "GET", uri, "", nil) 376 c.Assert(err, gc.IsNil) 377 c.Assert(resp.StatusCode, gc.Equals, http.StatusForbidden) 378 } 379 380 func (s *charmsSuite) TestGetReturnsFileContents(c *gc.C) { 381 // Add the dummy charm. 382 ch := charmtesting.Charms.Bundle(c.MkDir(), "dummy") 383 _, err := s.uploadRequest( 384 c, s.charmsURI(c, "?series=quantal"), true, ch.Path) 385 c.Assert(err, gc.IsNil) 386 387 // Ensure the file contents are properly returned. 388 for i, t := range []struct { 389 summary string 390 file string 391 response string 392 }{{ 393 summary: "relative path", 394 file: "revision", 395 response: "1", 396 }, { 397 summary: "exotic path", 398 file: "./hooks/../revision", 399 response: "1", 400 }, { 401 summary: "sub-directory path", 402 file: "hooks/install", 403 response: "#!/bin/bash\necho \"Done!\"\n", 404 }, 405 } { 406 c.Logf("test %d: %s", i, t.summary) 407 uri := s.charmsURI(c, "?url=local:quantal/dummy-1&file="+t.file) 408 resp, err := s.authRequest(c, "GET", uri, "", nil) 409 c.Assert(err, gc.IsNil) 410 s.assertGetFileResponse(c, resp, t.response, "text/plain; charset=utf-8") 411 } 412 } 413 414 func (s *charmsSuite) TestGetAllowsTopLevelPath(c *gc.C) { 415 ch := charmtesting.Charms.Bundle(c.MkDir(), "dummy") 416 _, err := s.uploadRequest( 417 c, s.charmsURI(c, "?series=quantal"), true, ch.Path) 418 c.Assert(err, gc.IsNil) 419 // Backwards compatibility check, that we can GET from charms at 420 // https://host:port/charms 421 url := s.charmsURL(c, "url=local:quantal/dummy-1&file=revision") 422 url.Path = "/charms" 423 resp, err := s.authRequest(c, "GET", url.String(), "", nil) 424 c.Assert(err, gc.IsNil) 425 s.assertGetFileResponse(c, resp, "1", "text/plain; charset=utf-8") 426 } 427 428 func (s *charmsSuite) TestGetAllowsEnvUUIDPath(c *gc.C) { 429 ch := charmtesting.Charms.Bundle(c.MkDir(), "dummy") 430 _, err := s.uploadRequest( 431 c, s.charmsURI(c, "?series=quantal"), true, ch.Path) 432 c.Assert(err, gc.IsNil) 433 // Check that we can GET from charms at https://host:port/ENVUUID/charms 434 environ, err := s.State.Environment() 435 c.Assert(err, gc.IsNil) 436 url := s.charmsURL(c, "url=local:quantal/dummy-1&file=revision") 437 url.Path = fmt.Sprintf("/environment/%s/charms", environ.UUID()) 438 resp, err := s.authRequest(c, "GET", url.String(), "", nil) 439 c.Assert(err, gc.IsNil) 440 s.assertGetFileResponse(c, resp, "1", "text/plain; charset=utf-8") 441 } 442 443 func (s *charmsSuite) TestGetRejectsWrongEnvUUIDPath(c *gc.C) { 444 // Check that we cannot upload charms to https://host:port/BADENVUUID/charms 445 url := s.charmsURL(c, "url=local:quantal/dummy-1&file=revision") 446 url.Path = "/environment/dead-beef-123456/charms" 447 resp, err := s.authRequest(c, "GET", url.String(), "", nil) 448 c.Assert(err, gc.IsNil) 449 s.assertErrorResponse(c, resp, http.StatusNotFound, `unknown environment: "dead-beef-123456"`) 450 } 451 452 func (s *charmsSuite) TestGetReturnsManifest(c *gc.C) { 453 // Add the dummy charm. 454 ch := charmtesting.Charms.Bundle(c.MkDir(), "dummy") 455 _, err := s.uploadRequest( 456 c, s.charmsURI(c, "?series=quantal"), true, ch.Path) 457 c.Assert(err, gc.IsNil) 458 459 // Ensure charm files are properly listed. 460 uri := s.charmsURI(c, "?url=local:quantal/dummy-1") 461 resp, err := s.authRequest(c, "GET", uri, "", nil) 462 c.Assert(err, gc.IsNil) 463 manifest, err := ch.Manifest() 464 c.Assert(err, gc.IsNil) 465 expectedFiles := manifest.SortedValues() 466 s.assertGetFileListResponse(c, resp, expectedFiles) 467 ctype := resp.Header.Get("content-type") 468 c.Assert(ctype, gc.Equals, "application/json") 469 } 470 471 func (s *charmsSuite) TestGetUsesCache(c *gc.C) { 472 // Add a fake charm archive in the cache directory. 473 cacheDir := filepath.Join(s.DataDir(), "charm-get-cache") 474 err := os.MkdirAll(cacheDir, 0755) 475 c.Assert(err, gc.IsNil) 476 477 // Create and save a bundle in it. 478 charmDir := charmtesting.Charms.ClonedDir(c.MkDir(), "dummy") 479 testPath := filepath.Join(charmDir.Path, "utils.js") 480 contents := "// blah blah" 481 err = ioutil.WriteFile(testPath, []byte(contents), 0755) 482 c.Assert(err, gc.IsNil) 483 var buffer bytes.Buffer 484 err = charmDir.BundleTo(&buffer) 485 c.Assert(err, gc.IsNil) 486 charmArchivePath := filepath.Join( 487 cacheDir, charm.Quote("local:trusty/django-42")+".zip") 488 err = ioutil.WriteFile(charmArchivePath, buffer.Bytes(), 0644) 489 c.Assert(err, gc.IsNil) 490 491 // Ensure the cached contents are properly retrieved. 492 uri := s.charmsURI(c, "?url=local:trusty/django-42&file=utils.js") 493 resp, err := s.authRequest(c, "GET", uri, "", nil) 494 c.Assert(err, gc.IsNil) 495 s.assertGetFileResponse(c, resp, contents, "application/javascript") 496 } 497 498 func (s *charmsSuite) charmsURL(c *gc.C, query string) *url.URL { 499 uri := s.baseURL(c) 500 uri.Path += "/charms" 501 uri.RawQuery = query 502 return uri 503 } 504 505 func (s *charmsSuite) charmsURI(c *gc.C, query string) string { 506 if query != "" && query[0] == '?' { 507 query = query[1:] 508 } 509 return s.charmsURL(c, query).String() 510 } 511 512 func (s *charmsSuite) assertUploadResponse(c *gc.C, resp *http.Response, expCharmURL string) { 513 body := assertResponse(c, resp, http.StatusOK, "application/json") 514 charmResponse := jsonResponse(c, body) 515 c.Check(charmResponse.Error, gc.Equals, "") 516 c.Check(charmResponse.CharmURL, gc.Equals, expCharmURL) 517 } 518 519 func (s *charmsSuite) assertGetFileResponse(c *gc.C, resp *http.Response, expBody, expContentType string) { 520 body := assertResponse(c, resp, http.StatusOK, expContentType) 521 c.Check(string(body), gc.Equals, expBody) 522 } 523 524 func (s *charmsSuite) assertGetFileListResponse(c *gc.C, resp *http.Response, expFiles []string) { 525 body := assertResponse(c, resp, http.StatusOK, "application/json") 526 charmResponse := jsonResponse(c, body) 527 c.Check(charmResponse.Error, gc.Equals, "") 528 c.Check(charmResponse.Files, gc.DeepEquals, expFiles) 529 } 530 531 func (s *charmsSuite) assertErrorResponse(c *gc.C, resp *http.Response, expCode int, expError string) { 532 body := assertResponse(c, resp, expCode, "application/json") 533 c.Check(jsonResponse(c, body).Error, gc.Matches, expError) 534 } 535 536 func assertResponse(c *gc.C, resp *http.Response, expCode int, expContentType string) []byte { 537 c.Check(resp.StatusCode, gc.Equals, expCode) 538 body, err := ioutil.ReadAll(resp.Body) 539 defer resp.Body.Close() 540 c.Assert(err, gc.IsNil) 541 ctype := resp.Header.Get("Content-Type") 542 c.Assert(ctype, gc.Equals, expContentType) 543 return body 544 } 545 546 func jsonResponse(c *gc.C, body []byte) (jsonResponse params.CharmsResponse) { 547 err := json.Unmarshal(body, &jsonResponse) 548 c.Assert(err, gc.IsNil) 549 return 550 }