github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/backup_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apiserver_test 5 6 import ( 7 "encoding/base64" 8 "encoding/json" 9 "fmt" 10 "io" 11 "net/http" 12 13 "github.com/juju/errors" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils/v3" 16 gc "gopkg.in/check.v1" 17 18 "github.com/juju/juju/apiserver" 19 apitesting "github.com/juju/juju/apiserver/testing" 20 "github.com/juju/juju/rpc/params" 21 "github.com/juju/juju/state" 22 "github.com/juju/juju/state/backups" 23 backupstesting "github.com/juju/juju/state/backups/testing" 24 ) 25 26 var _ = gc.Suite(&backupsSuite{}) 27 28 type backupsSuite struct { 29 apiserverBaseSuite 30 backupURL string 31 fake *backupstesting.FakeBackups 32 } 33 34 func (s *backupsSuite) SetUpTest(c *gc.C) { 35 s.apiserverBaseSuite.SetUpTest(c) 36 37 s.backupURL = s.server.URL + fmt.Sprintf("/model/%s/backups", s.State.ModelUUID()) 38 s.fake = &backupstesting.FakeBackups{} 39 s.PatchValue(apiserver.NewBackups, 40 func(path *backups.Paths) backups.Backups { 41 return s.fake 42 }, 43 ) 44 } 45 46 func (s *backupsSuite) assertErrorResponse(c *gc.C, resp *http.Response, statusCode int, msg string) *params.Error { 47 body, err := io.ReadAll(resp.Body) 48 c.Assert(err, jc.ErrorIsNil) 49 50 c.Assert(resp.StatusCode, gc.Equals, statusCode, gc.Commentf("body: %s", body)) 51 c.Assert(resp.Header.Get("Content-Type"), gc.Equals, params.ContentTypeJSON, gc.Commentf("body: %q", body)) 52 53 var failure params.Error 54 err = json.Unmarshal(body, &failure) 55 c.Assert(err, jc.ErrorIsNil) 56 c.Assert(&failure, gc.ErrorMatches, msg, gc.Commentf("body: %s", body)) 57 return &failure 58 } 59 60 func (s *backupsSuite) TestRequiresAuth(c *gc.C) { 61 resp := apitesting.SendHTTPRequest(c, apitesting.HTTPRequestParams{Method: "GET", URL: s.backupURL}) 62 defer resp.Body.Close() 63 64 c.Assert(resp.StatusCode, gc.Equals, http.StatusUnauthorized) 65 body, err := io.ReadAll(resp.Body) 66 c.Assert(err, jc.ErrorIsNil) 67 c.Assert(string(body), gc.Equals, "authentication failed: no credentials provided\n") 68 } 69 70 func (s *backupsSuite) checkInvalidMethod(c *gc.C, method, url string) { 71 resp := s.sendHTTPRequest(c, apitesting.HTTPRequestParams{Method: method, URL: url}) 72 s.assertErrorResponse(c, resp, http.StatusMethodNotAllowed, `unsupported method: "`+method+`"`) 73 } 74 75 func (s *backupsSuite) TestInvalidHTTPMethods(c *gc.C) { 76 url := s.backupURL 77 for _, method := range []string{"PUT", "POST", "DELETE", "OPTIONS"} { 78 c.Log("testing HTTP method: " + method) 79 s.checkInvalidMethod(c, method, url) 80 } 81 } 82 83 func (s *backupsSuite) TestAuthRequiresClientNotMachine(c *gc.C) { 84 // Add a machine and try to login. 85 machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 86 c.Assert(err, jc.ErrorIsNil) 87 err = machine.SetProvisioned("foo", "", "fake_nonce", nil) 88 c.Assert(err, jc.ErrorIsNil) 89 password, err := utils.RandomPassword() 90 c.Assert(err, jc.ErrorIsNil) 91 err = machine.SetPassword(password) 92 c.Assert(err, jc.ErrorIsNil) 93 94 resp := apitesting.SendHTTPRequest(c, apitesting.HTTPRequestParams{ 95 Tag: machine.Tag().String(), 96 Password: password, 97 Method: "GET", 98 URL: s.backupURL, 99 Nonce: "fake_nonce", 100 }) 101 c.Assert(resp.StatusCode, gc.Equals, http.StatusForbidden) 102 body, err := io.ReadAll(resp.Body) 103 c.Assert(err, jc.ErrorIsNil) 104 c.Assert(string(body), gc.Equals, "authorization failed: machine 0 is not a user\n") 105 106 // Now try a user login. 107 resp = s.sendHTTPRequest(c, apitesting.HTTPRequestParams{Method: "POST", URL: s.backupURL}) 108 s.assertErrorResponse(c, resp, http.StatusMethodNotAllowed, `unsupported method: "POST"`) 109 } 110 111 // sendValid sends a valid GET request to the backups endpoint 112 // and returns the response and the expected contents of the 113 // archive if the request succeeds. 114 func (s *backupsSuite) sendValidGet(c *gc.C) (resp *http.Response, archiveBytes []byte) { 115 meta := backupstesting.NewMetadata() 116 archive, err := backupstesting.NewArchiveBasic(meta) 117 c.Assert(err, jc.ErrorIsNil) 118 archiveBytes = archive.Bytes() 119 s.fake.Meta = meta 120 s.fake.Archive = io.NopCloser(archive) 121 122 return s.sendHTTPRequest(c, apitesting.HTTPRequestParams{ 123 Method: "GET", 124 URL: s.backupURL, 125 ContentType: params.ContentTypeJSON, 126 JSONBody: params.BackupsDownloadArgs{ 127 ID: meta.ID(), 128 }, 129 }), archiveBytes 130 } 131 132 func (s *backupsSuite) TestCalls(c *gc.C) { 133 resp, _ := s.sendValidGet(c) 134 defer resp.Body.Close() 135 136 c.Check(s.fake.Calls, gc.DeepEquals, []string{"Get"}) 137 c.Check(s.fake.IDArg, gc.Equals, s.fake.Meta.ID()) 138 } 139 140 func (s *backupsSuite) TestResponse(c *gc.C) { 141 resp, _ := s.sendValidGet(c) 142 defer resp.Body.Close() 143 meta := s.fake.Meta 144 145 c.Check(resp.StatusCode, gc.Equals, http.StatusOK) 146 expectedChecksum := base64.StdEncoding.EncodeToString([]byte(meta.Checksum())) 147 c.Check(resp.Header.Get("Digest"), gc.Equals, string(params.DigestSHA256)+"="+expectedChecksum) 148 c.Check(resp.Header.Get("Content-Type"), gc.Equals, params.ContentTypeRaw) 149 } 150 151 func (s *backupsSuite) TestBody(c *gc.C) { 152 resp, archiveBytes := s.sendValidGet(c) 153 defer resp.Body.Close() 154 155 body, err := io.ReadAll(resp.Body) 156 c.Assert(err, jc.ErrorIsNil) 157 c.Check(body, jc.DeepEquals, archiveBytes) 158 } 159 160 func (s *backupsSuite) TestErrorWhenGetFails(c *gc.C) { 161 s.fake.Error = errors.New("failed!") 162 resp, _ := s.sendValidGet(c) 163 defer resp.Body.Close() 164 165 s.assertErrorResponse(c, resp, http.StatusInternalServerError, "failed!") 166 }