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 }