github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/daemon/response_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2015 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package daemon_test
    21  
    22  import (
    23  	"encoding/json"
    24  	"errors"
    25  	"fmt"
    26  	"io/ioutil"
    27  	"net/http"
    28  	"net/http/httptest"
    29  	"os"
    30  	"path/filepath"
    31  
    32  	"gopkg.in/check.v1"
    33  
    34  	"github.com/snapcore/snapd/client"
    35  	"github.com/snapcore/snapd/daemon"
    36  	"github.com/snapcore/snapd/overlord/snapstate"
    37  	"github.com/snapcore/snapd/snap"
    38  	"github.com/snapcore/snapd/store"
    39  )
    40  
    41  type responseSuite struct{}
    42  
    43  var _ = check.Suite(&responseSuite{})
    44  
    45  func (s *responseSuite) TestRespSetsLocationIfAccepted(c *check.C) {
    46  	rec := httptest.NewRecorder()
    47  
    48  	rsp := &daemon.Resp{
    49  		Status: 202,
    50  		Result: map[string]interface{}{
    51  			"resource": "foo/bar",
    52  		},
    53  	}
    54  
    55  	rsp.ServeHTTP(rec, nil)
    56  	hdr := rec.Header()
    57  	c.Check(hdr.Get("Location"), check.Equals, "foo/bar")
    58  }
    59  
    60  func (s *responseSuite) TestRespSetsLocationIfCreated(c *check.C) {
    61  	rec := httptest.NewRecorder()
    62  
    63  	rsp := &daemon.Resp{
    64  		Status: 201,
    65  		Result: map[string]interface{}{
    66  			"resource": "foo/bar",
    67  		},
    68  	}
    69  
    70  	rsp.ServeHTTP(rec, nil)
    71  	hdr := rec.Header()
    72  	c.Check(hdr.Get("Location"), check.Equals, "foo/bar")
    73  }
    74  
    75  func (s *responseSuite) TestRespDoesNotSetLocationIfOther(c *check.C) {
    76  	rec := httptest.NewRecorder()
    77  
    78  	rsp := &daemon.Resp{
    79  		Status: 418, // I'm a teapot
    80  		Result: map[string]interface{}{
    81  			"resource": "foo/bar",
    82  		},
    83  	}
    84  
    85  	rsp.ServeHTTP(rec, nil)
    86  	hdr := rec.Header()
    87  	c.Check(hdr.Get("Location"), check.Equals, "")
    88  }
    89  
    90  func (s *responseSuite) TestFileResponseSetsContentDisposition(c *check.C) {
    91  	const filename = "icon.png"
    92  
    93  	path := filepath.Join(c.MkDir(), filename)
    94  	err := ioutil.WriteFile(path, nil, os.ModePerm)
    95  	c.Check(err, check.IsNil)
    96  
    97  	rec := httptest.NewRecorder()
    98  	rsp := daemon.FileResponse(path)
    99  	req, err := http.NewRequest("GET", "", nil)
   100  	c.Check(err, check.IsNil)
   101  
   102  	rsp.ServeHTTP(rec, req)
   103  
   104  	hdr := rec.Header()
   105  	c.Check(hdr.Get("Content-Disposition"), check.Equals,
   106  		fmt.Sprintf("attachment; filename=%s", filename))
   107  }
   108  
   109  // Due to how the protocol was defined the result must be sent, even if it is
   110  // null. Older clients rely on this.
   111  func (s *responseSuite) TestRespJSONWithNullResult(c *check.C) {
   112  	rj := &daemon.RespJSON{Result: nil}
   113  	data, err := json.Marshal(rj)
   114  	c.Assert(err, check.IsNil)
   115  	c.Check(string(data), check.Equals, `{"type":"","status-code":0,"status":"","result":null}`)
   116  }
   117  
   118  func (responseSuite) TestErrorResponderPrintfsWithArgs(c *check.C) {
   119  	teapot := daemon.MakeErrorResponder(418)
   120  
   121  	rec := httptest.NewRecorder()
   122  	rsp := teapot("system memory below %d%%.", 1)
   123  	req, err := http.NewRequest("GET", "", nil)
   124  	c.Assert(err, check.IsNil)
   125  	rsp.ServeHTTP(rec, req)
   126  
   127  	var v struct{ Result daemon.ErrorResult }
   128  	c.Assert(json.NewDecoder(rec.Body).Decode(&v), check.IsNil)
   129  
   130  	c.Check(v.Result.Message, check.Equals, "system memory below 1%.")
   131  }
   132  
   133  func (responseSuite) TestErrorResponderDoesNotPrintfAlways(c *check.C) {
   134  	teapot := daemon.MakeErrorResponder(418)
   135  
   136  	rec := httptest.NewRecorder()
   137  	rsp := teapot("system memory below 1%.")
   138  	req, err := http.NewRequest("GET", "", nil)
   139  	c.Assert(err, check.IsNil)
   140  	rsp.ServeHTTP(rec, req)
   141  
   142  	var v struct{ Result daemon.ErrorResult }
   143  	c.Assert(json.NewDecoder(rec.Body).Decode(&v), check.IsNil)
   144  
   145  	c.Check(v.Result.Message, check.Equals, "system memory below 1%.")
   146  }
   147  
   148  type fakeNetError struct {
   149  	message   string
   150  	timeout   bool
   151  	temporary bool
   152  }
   153  
   154  func (e fakeNetError) Error() string   { return e.message }
   155  func (e fakeNetError) Timeout() bool   { return e.timeout }
   156  func (e fakeNetError) Temporary() bool { return e.temporary }
   157  
   158  func (s *responseSuite) TestErrToResponse(c *check.C) {
   159  	aie := &snap.AlreadyInstalledError{Snap: "foo"}
   160  	nie := &snap.NotInstalledError{Snap: "foo"}
   161  	cce := &snapstate.ChangeConflictError{Snap: "foo"}
   162  	ndme := &snapstate.SnapNeedsDevModeError{Snap: "foo"}
   163  	nc := &snapstate.SnapNotClassicError{Snap: "foo"}
   164  	nce := &snapstate.SnapNeedsClassicError{Snap: "foo"}
   165  	ncse := &snapstate.SnapNeedsClassicSystemError{Snap: "foo"}
   166  	netoe := fakeNetError{message: "other"}
   167  	nettoute := fakeNetError{message: "timeout", timeout: true}
   168  	nettmpe := fakeNetError{message: "temp", temporary: true}
   169  
   170  	e := errors.New("other error")
   171  
   172  	sa1e := &store.SnapActionError{Refresh: map[string]error{"foo": store.ErrSnapNotFound}}
   173  	sa2e := &store.SnapActionError{Refresh: map[string]error{
   174  		"foo": store.ErrSnapNotFound,
   175  		"bar": store.ErrSnapNotFound,
   176  	}}
   177  	saOe := &store.SnapActionError{Other: []error{e}}
   178  	// this one can't happen (but fun to test):
   179  	saXe := &store.SnapActionError{Refresh: map[string]error{"foo": sa1e}}
   180  
   181  	makeErrorRsp := func(kind client.ErrorKind, err error, value interface{}) daemon.Response {
   182  		return daemon.SyncResponse(&daemon.Resp{
   183  			Type:   daemon.ResponseTypeError,
   184  			Result: &daemon.ErrorResult{Message: err.Error(), Kind: kind, Value: value},
   185  			Status: 400,
   186  		}, nil)
   187  	}
   188  
   189  	tests := []struct {
   190  		err         error
   191  		expectedRsp daemon.Response
   192  	}{
   193  		{store.ErrSnapNotFound, daemon.SnapNotFound("foo", store.ErrSnapNotFound)},
   194  		{store.ErrNoUpdateAvailable, makeErrorRsp(client.ErrorKindSnapNoUpdateAvailable, store.ErrNoUpdateAvailable, "")},
   195  		{store.ErrLocalSnap, makeErrorRsp(client.ErrorKindSnapLocal, store.ErrLocalSnap, "")},
   196  		{aie, makeErrorRsp(client.ErrorKindSnapAlreadyInstalled, aie, "foo")},
   197  		{nie, makeErrorRsp(client.ErrorKindSnapNotInstalled, nie, "foo")},
   198  		{ndme, makeErrorRsp(client.ErrorKindSnapNeedsDevMode, ndme, "foo")},
   199  		{nc, makeErrorRsp(client.ErrorKindSnapNotClassic, nc, "foo")},
   200  		{nce, makeErrorRsp(client.ErrorKindSnapNeedsClassic, nce, "foo")},
   201  		{ncse, makeErrorRsp(client.ErrorKindSnapNeedsClassicSystem, ncse, "foo")},
   202  		{cce, daemon.SnapChangeConflict(cce)},
   203  		{nettoute, makeErrorRsp(client.ErrorKindNetworkTimeout, nettoute, "")},
   204  		{netoe, daemon.BadRequest("ERR: %v", netoe)},
   205  		{nettmpe, daemon.BadRequest("ERR: %v", nettmpe)},
   206  		{e, daemon.BadRequest("ERR: %v", e)},
   207  
   208  		// action error unwrapping:
   209  		{sa1e, daemon.SnapNotFound("foo", store.ErrSnapNotFound)},
   210  		{saXe, daemon.SnapNotFound("foo", store.ErrSnapNotFound)},
   211  		// action errors, unwrapped:
   212  		{sa2e, daemon.BadRequest(`ERR: cannot refresh: snap not found: "bar", "foo"`)},
   213  		{saOe, daemon.BadRequest("ERR: cannot refresh, install, or download: other error")},
   214  	}
   215  
   216  	for _, t := range tests {
   217  		com := check.Commentf("%v", t.err)
   218  		rsp := daemon.ErrToResponse(t.err, []string{"foo"}, daemon.BadRequest, "%s: %v", "ERR")
   219  		c.Check(rsp, check.DeepEquals, t.expectedRsp, com)
   220  	}
   221  }
   222  
   223  func (s *responseSuite) TestErrToResponseInsufficentSpace(c *check.C) {
   224  	err := &snapstate.InsufficientSpaceError{
   225  		Snaps:      []string{"foo", "bar"},
   226  		ChangeKind: "some-change",
   227  		Path:       "/path",
   228  		Message:    "specific error msg",
   229  	}
   230  	rsp := daemon.ErrToResponse(err, nil, daemon.BadRequest, "%s: %v", "ERR").(*daemon.Resp)
   231  	c.Check(rsp, check.DeepEquals, &daemon.Resp{
   232  		Status: 507,
   233  		Type:   daemon.ResponseTypeError,
   234  		Result: &daemon.ErrorResult{
   235  			Message: "specific error msg",
   236  			Kind:    client.ErrorKindInsufficientDiskSpace,
   237  			Value: map[string]interface{}{
   238  				"snap-names":  []string{"foo", "bar"},
   239  				"change-kind": "some-change",
   240  			},
   241  		},
   242  	})
   243  }