github.com/rigado/snapd@v2.42.5-go-mod+incompatible/daemon/api_download_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 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 "bytes" 24 "context" 25 "fmt" 26 "io" 27 "io/ioutil" 28 "net/http" 29 "net/http/httptest" 30 "strings" 31 32 "gopkg.in/check.v1" 33 34 "github.com/snapcore/snapd/daemon" 35 "github.com/snapcore/snapd/dirs" 36 "github.com/snapcore/snapd/overlord" 37 "github.com/snapcore/snapd/overlord/auth" 38 "github.com/snapcore/snapd/overlord/snapstate" 39 "github.com/snapcore/snapd/snap" 40 "github.com/snapcore/snapd/store" 41 "github.com/snapcore/snapd/store/storetest" 42 ) 43 44 type fakeStore struct{} 45 46 var _ = check.Suite(&snapDownloadSuite{}) 47 48 type snapDownloadSuite struct { 49 storetest.Store 50 d *daemon.Daemon 51 52 snaps []string 53 } 54 55 func (s *snapDownloadSuite) SetUpTest(c *check.C) { 56 s.snaps = nil 57 58 o := overlord.Mock() 59 s.d = daemon.NewWithOverlord(o) 60 61 st := o.State() 62 st.Lock() 63 defer st.Unlock() 64 snapstate.ReplaceStore(st, s) 65 dirs.SetRootDir(c.MkDir()) 66 } 67 68 var content = "SNAP" 69 70 func (s *snapDownloadSuite) SnapInfo(ctx context.Context, spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) { 71 switch spec.Name { 72 case "bar": 73 return &snap.Info{ 74 DownloadInfo: snap.DownloadInfo{ 75 Size: int64(len(content)), 76 AnonDownloadURL: "http://localhost/bar", 77 }, 78 }, nil 79 case "download-error-trigger-snap": 80 return &snap.Info{ 81 DownloadInfo: snap.DownloadInfo{ 82 Size: 100, 83 AnonDownloadURL: "http://localhost/foo", 84 }, 85 }, nil 86 default: 87 return nil, store.ErrSnapNotFound 88 } 89 } 90 91 func (s *snapDownloadSuite) DownloadStream(ctx context.Context, name string, downloadInfo *snap.DownloadInfo, user *auth.UserState) (io.ReadCloser, error) { 92 if name == "bar" { 93 return ioutil.NopCloser(bytes.NewReader([]byte(content))), nil 94 } 95 return nil, fmt.Errorf("unexpected error") 96 } 97 98 func (s *snapDownloadSuite) TestDownloadSnapErrors(c *check.C) { 99 type scenario struct { 100 dataJSON string 101 status int 102 err string 103 } 104 105 for _, scen := range []scenario{ 106 { 107 dataJSON: `{"action": "download"}`, 108 status: 400, 109 err: "download operation requires one snap name", 110 }, 111 { 112 dataJSON: `{"action": "foo", "snaps": ["foo"]}`, 113 status: 400, 114 err: `unknown download operation "foo"`, 115 }, 116 { 117 dataJSON: `{"snaps": ["foo"]}`, 118 status: 400, 119 err: `download operation requires action`, 120 }, 121 { 122 dataJSON: `{"action": "foo", "snaps": ["foo", "bar"]}`, 123 status: 400, 124 err: `download operation supports only one snap`, 125 }, 126 { 127 dataJSON: `{"}`, 128 status: 400, 129 err: `cannot decode request body into download operation: unexpected EOF`, 130 }, 131 } { 132 var err error 133 data := []byte(scen.dataJSON) 134 135 req, err := http.NewRequest("POST", "/v2/download", bytes.NewBuffer(data)) 136 c.Assert(err, check.IsNil) 137 rsp := daemon.PostSnapDownload(daemon.SnapDownloadCmd, req, nil) 138 139 c.Assert(rsp.(*daemon.Resp).Status, check.Equals, scen.status) 140 if scen.err == "" { 141 c.Errorf("error was expected") 142 } 143 result := rsp.(*daemon.Resp).Result 144 c.Check(result.(*daemon.ErrorResult).Message, check.Matches, scen.err) 145 } 146 } 147 148 func (s *snapDownloadSuite) TestStreamOneSnap(c *check.C) { 149 150 type scenario struct { 151 dataJSON string 152 status int 153 err string 154 } 155 156 for _, s := range []scenario{ 157 { 158 dataJSON: `{"action": "download", "snaps": ["doom"]}`, 159 status: 404, 160 err: "snap not found", 161 }, 162 { 163 dataJSON: `{"action": "download", "snaps": ["download-error-trigger-snap"]}`, 164 status: 500, 165 err: "unexpected error", 166 }, 167 { 168 dataJSON: `{"action": "download", "snaps": ["bar"]}`, 169 status: 200, 170 err: "", 171 }, 172 } { 173 req, err := http.NewRequest("POST", "/v2/download", strings.NewReader(s.dataJSON)) 174 c.Assert(err, check.IsNil) 175 rsp := daemon.SnapDownloadCmd.POST(daemon.SnapDownloadCmd, req, nil) 176 177 if s.err != "" { 178 c.Assert(rsp.(*daemon.Resp).Status, check.Equals, s.status) 179 result := rsp.(*daemon.Resp).Result 180 c.Check(result.(*daemon.ErrorResult).Message, check.Matches, s.err) 181 } else { 182 c.Assert(rsp.(daemon.FileStream).SnapName, check.Equals, "bar") 183 c.Assert(rsp.(daemon.FileStream).Info.Size, check.Equals, int64(len(content))) 184 185 w := httptest.NewRecorder() 186 rsp.(daemon.FileStream).ServeHTTP(w, nil) 187 188 expectedLength := fmt.Sprintf("%d", len(content)) 189 190 c.Assert(w.Code, check.Equals, s.status) 191 c.Assert(w.Header().Get("Content-Length"), check.Equals, expectedLength) 192 c.Assert(w.Header().Get("Content-Type"), check.Equals, "application/octet-stream") 193 c.Assert(w.Header().Get("Content-Disposition"), check.Equals, "attachment; filename=bar") 194 c.Assert(w.Body.String(), check.Equals, "SNAP") 195 } 196 } 197 }