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 }