github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/daemon/errors_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2021 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  	"net/http"
    26  	"net/http/httptest"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/client"
    31  	"github.com/snapcore/snapd/daemon"
    32  	"github.com/snapcore/snapd/overlord/snapstate"
    33  	"github.com/snapcore/snapd/snap"
    34  	"github.com/snapcore/snapd/store"
    35  )
    36  
    37  type errorsSuite struct{}
    38  
    39  var _ = Suite(&errorsSuite{})
    40  
    41  func (s *errorsSuite) TestJSON(c *C) {
    42  	rspe := &daemon.APIError{
    43  		Status:  400,
    44  		Message: "req is wrong",
    45  	}
    46  
    47  	c.Check(rspe.JSON(), DeepEquals, &daemon.RespJSON{
    48  		Status: 400,
    49  		Type:   daemon.ResponseTypeError,
    50  		Result: &daemon.ErrorResult{
    51  			Message: "req is wrong",
    52  		},
    53  	})
    54  
    55  	rspe = &daemon.APIError{
    56  		Status:  404,
    57  		Message: "snap not found",
    58  		Kind:    client.ErrorKindSnapNotFound,
    59  		Value: map[string]string{
    60  			"snap-name": "foo",
    61  		},
    62  	}
    63  	c.Check(rspe.JSON(), DeepEquals, &daemon.RespJSON{
    64  		Status: 404,
    65  		Type:   daemon.ResponseTypeError,
    66  		Result: &daemon.ErrorResult{
    67  			Message: "snap not found",
    68  			Kind:    client.ErrorKindSnapNotFound,
    69  			Value: map[string]string{
    70  				"snap-name": "foo",
    71  			},
    72  		},
    73  	})
    74  }
    75  
    76  func (s *errorsSuite) TestError(c *C) {
    77  	rspe := &daemon.APIError{
    78  		Status:  400,
    79  		Message: "req is wrong",
    80  	}
    81  
    82  	c.Check(rspe.Error(), Equals, `req is wrong (api)`)
    83  
    84  	rspe = &daemon.APIError{
    85  		Status:  404,
    86  		Message: "snap not found",
    87  		Kind:    client.ErrorKindSnapNotFound,
    88  		Value: map[string]string{
    89  			"snap-name": "foo",
    90  		},
    91  	}
    92  
    93  	c.Check(rspe.Error(), Equals, `snap not found (api: snap-not-found)`)
    94  
    95  	rspe = &daemon.APIError{
    96  		Status:  500,
    97  		Message: "internal error",
    98  	}
    99  	c.Check(rspe.Error(), Equals, `internal error (api 500)`)
   100  }
   101  
   102  func (s *errorsSuite) TestThroughSyncResponse(c *C) {
   103  	rspe := &daemon.APIError{
   104  		Status:  400,
   105  		Message: "req is wrong",
   106  	}
   107  
   108  	rsp := daemon.SyncResponse(rspe)
   109  	c.Check(rsp, Equals, rspe)
   110  }
   111  
   112  type fakeNetError struct {
   113  	message   string
   114  	timeout   bool
   115  	temporary bool
   116  }
   117  
   118  func (e fakeNetError) Error() string   { return e.message }
   119  func (e fakeNetError) Timeout() bool   { return e.timeout }
   120  func (e fakeNetError) Temporary() bool { return e.temporary }
   121  
   122  func (s *errorsSuite) TestErrToResponse(c *C) {
   123  	aie := &snap.AlreadyInstalledError{Snap: "foo"}
   124  	nie := &snap.NotInstalledError{Snap: "foo"}
   125  	cce := &snapstate.ChangeConflictError{Snap: "foo"}
   126  	ndme := &snapstate.SnapNeedsDevModeError{Snap: "foo"}
   127  	nc := &snapstate.SnapNotClassicError{Snap: "foo"}
   128  	nce := &snapstate.SnapNeedsClassicError{Snap: "foo"}
   129  	ncse := &snapstate.SnapNeedsClassicSystemError{Snap: "foo"}
   130  	netoe := fakeNetError{message: "other"}
   131  	nettoute := fakeNetError{message: "timeout", timeout: true}
   132  	nettmpe := fakeNetError{message: "temp", temporary: true}
   133  
   134  	e := errors.New("other error")
   135  
   136  	sa1e := &store.SnapActionError{Refresh: map[string]error{"foo": store.ErrSnapNotFound}}
   137  	sa2e := &store.SnapActionError{Refresh: map[string]error{
   138  		"foo": store.ErrSnapNotFound,
   139  		"bar": store.ErrSnapNotFound,
   140  	}}
   141  	saOe := &store.SnapActionError{Other: []error{e}}
   142  	// this one can't happen (but fun to test):
   143  	saXe := &store.SnapActionError{Refresh: map[string]error{"foo": sa1e}}
   144  
   145  	makeErrorRsp := func(kind client.ErrorKind, err error, value interface{}) *daemon.APIError {
   146  		return &daemon.APIError{
   147  			Status:  400,
   148  			Message: err.Error(),
   149  			Kind:    kind,
   150  			Value:   value,
   151  		}
   152  	}
   153  
   154  	tests := []struct {
   155  		err         error
   156  		expectedRsp daemon.Response
   157  	}{
   158  		{store.ErrSnapNotFound, daemon.SnapNotFound("foo", store.ErrSnapNotFound)},
   159  		{store.ErrNoUpdateAvailable, makeErrorRsp(client.ErrorKindSnapNoUpdateAvailable, store.ErrNoUpdateAvailable, "")},
   160  		{store.ErrLocalSnap, makeErrorRsp(client.ErrorKindSnapLocal, store.ErrLocalSnap, "")},
   161  		{aie, makeErrorRsp(client.ErrorKindSnapAlreadyInstalled, aie, "foo")},
   162  		{nie, makeErrorRsp(client.ErrorKindSnapNotInstalled, nie, "foo")},
   163  		{ndme, makeErrorRsp(client.ErrorKindSnapNeedsDevMode, ndme, "foo")},
   164  		{nc, makeErrorRsp(client.ErrorKindSnapNotClassic, nc, "foo")},
   165  		{nce, makeErrorRsp(client.ErrorKindSnapNeedsClassic, nce, "foo")},
   166  		{ncse, makeErrorRsp(client.ErrorKindSnapNeedsClassicSystem, ncse, "foo")},
   167  		{cce, daemon.SnapChangeConflict(cce)},
   168  		{nettoute, makeErrorRsp(client.ErrorKindNetworkTimeout, nettoute, "")},
   169  		{netoe, daemon.BadRequest("ERR: %v", netoe)},
   170  		{nettmpe, daemon.BadRequest("ERR: %v", nettmpe)},
   171  		{e, daemon.BadRequest("ERR: %v", e)},
   172  
   173  		// action error unwrapping:
   174  		{sa1e, daemon.SnapNotFound("foo", store.ErrSnapNotFound)},
   175  		{saXe, daemon.SnapNotFound("foo", store.ErrSnapNotFound)},
   176  		// action errors, unwrapped:
   177  		{sa2e, daemon.BadRequest(`ERR: cannot refresh: snap not found: "bar", "foo"`)},
   178  		{saOe, daemon.BadRequest("ERR: cannot refresh, install, or download: other error")},
   179  	}
   180  
   181  	for _, t := range tests {
   182  		com := Commentf("%v", t.err)
   183  		rspe := daemon.ErrToResponse(t.err, []string{"foo"}, daemon.BadRequest, "%s: %v", "ERR")
   184  		c.Check(rspe, DeepEquals, t.expectedRsp, com)
   185  	}
   186  }
   187  
   188  func (errorsSuite) TestErrorResponderPrintfsWithArgs(c *C) {
   189  	teapot := daemon.MakeErrorResponder(418)
   190  
   191  	rec := httptest.NewRecorder()
   192  	rsp := teapot("system memory below %d%%.", 1)
   193  	req, err := http.NewRequest("GET", "", nil)
   194  	c.Assert(err, IsNil)
   195  	rsp.ServeHTTP(rec, req)
   196  
   197  	var v struct{ Result daemon.ErrorResult }
   198  	c.Assert(json.NewDecoder(rec.Body).Decode(&v), IsNil)
   199  
   200  	c.Check(v.Result.Message, Equals, "system memory below 1%.")
   201  }
   202  
   203  func (errorsSuite) TestErrorResponderDoesNotPrintfAlways(c *C) {
   204  	teapot := daemon.MakeErrorResponder(418)
   205  
   206  	rec := httptest.NewRecorder()
   207  	rsp := teapot("system memory below 1%.")
   208  	req, err := http.NewRequest("GET", "", nil)
   209  	c.Assert(err, IsNil)
   210  	rsp.ServeHTTP(rec, req)
   211  
   212  	var v struct{ Result daemon.ErrorResult }
   213  	c.Assert(json.NewDecoder(rec.Body).Decode(&v), IsNil)
   214  
   215  	c.Check(v.Result.Message, Equals, "system memory below 1%.")
   216  }
   217  
   218  func (s *errorsSuite) TestErrToResponseInsufficentSpace(c *C) {
   219  	err := &snapstate.InsufficientSpaceError{
   220  		Snaps:      []string{"foo", "bar"},
   221  		ChangeKind: "some-change",
   222  		Path:       "/path",
   223  		Message:    "specific error msg",
   224  	}
   225  	rspe := daemon.ErrToResponse(err, nil, daemon.BadRequest, "%s: %v", "ERR")
   226  	c.Check(rspe, DeepEquals, &daemon.APIError{
   227  		Status:  507,
   228  		Message: "specific error msg",
   229  		Kind:    client.ErrorKindInsufficientDiskSpace,
   230  		Value: map[string]interface{}{
   231  			"snap-names":  []string{"foo", "bar"},
   232  			"change-kind": "some-change",
   233  		},
   234  	})
   235  }
   236  
   237  func (s *errorsSuite) TestAuthCancelled(c *C) {
   238  	c.Check(daemon.AuthCancelled("auth cancelled"), DeepEquals, &daemon.APIError{
   239  		Status:  403,
   240  		Message: "auth cancelled",
   241  		Kind:    client.ErrorKindAuthCancelled,
   242  	})
   243  }
   244  
   245  func (s *errorsSuite) TestUnathorized(c *C) {
   246  	c.Check(daemon.Unauthorized("denied"), DeepEquals, &daemon.APIError{
   247  		Status:  401,
   248  		Message: "denied",
   249  		Kind:    client.ErrorKindLoginRequired,
   250  	})
   251  }
   252  
   253  func (s *errorsSuite) TestForbidden(c *C) {
   254  	c.Check(daemon.Forbidden("denied"), DeepEquals, &daemon.APIError{
   255  		Status:  403,
   256  		Message: "denied",
   257  		Kind:    client.ErrorKindLoginRequired,
   258  	})
   259  }