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  }