github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/client/snapshot_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 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 client_test 21 22 import ( 23 "crypto/sha256" 24 "io/ioutil" 25 "net/http" 26 "net/url" 27 "strconv" 28 "strings" 29 "time" 30 31 "gopkg.in/check.v1" 32 33 "github.com/snapcore/snapd/client" 34 "github.com/snapcore/snapd/snap" 35 ) 36 37 func (cs *clientSuite) TestClientSnapshotIsValid(c *check.C) { 38 now := time.Now() 39 revno := snap.R(1) 40 sums := map[string]string{"user/foo.tgz": "some long hash"} 41 c.Check((&client.Snapshot{ 42 SetID: 42, 43 Time: now, 44 Snap: "asnap", 45 Revision: revno, 46 SHA3_384: sums, 47 }).IsValid(), check.Equals, true) 48 49 for desc, snapshot := range map[string]*client.Snapshot{ 50 "nil": nil, 51 "empty": {}, 52 "no id": { /*SetID: 42,*/ Time: now, Snap: "asnap", Revision: revno, SHA3_384: sums}, 53 "no time": {SetID: 42 /*Time: now,*/, Snap: "asnap", Revision: revno, SHA3_384: sums}, 54 "no snap": {SetID: 42, Time: now /*Snap: "asnap",*/, Revision: revno, SHA3_384: sums}, 55 "no rev": {SetID: 42, Time: now, Snap: "asnap" /*Revision: revno,*/, SHA3_384: sums}, 56 "no sums": {SetID: 42, Time: now, Snap: "asnap", Revision: revno /*SHA3_384: sums*/}, 57 } { 58 c.Check(snapshot.IsValid(), check.Equals, false, check.Commentf("%s", desc)) 59 } 60 61 } 62 63 func (cs *clientSuite) TestClientSnapshotSetTime(c *check.C) { 64 // if set is empty, it doesn't explode (and returns the zero time) 65 c.Check(client.SnapshotSet{}.Time().IsZero(), check.Equals, true) 66 // if not empty, returns the earliest one 67 c.Check(client.SnapshotSet{Snapshots: []*client.Snapshot{ 68 {Time: time.Unix(3, 0)}, 69 {Time: time.Unix(1, 0)}, 70 {Time: time.Unix(2, 0)}, 71 }}.Time(), check.DeepEquals, time.Unix(1, 0)) 72 } 73 74 func (cs *clientSuite) TestClientSnapshotSetSize(c *check.C) { 75 // if set is empty, doesn't explode (and returns 0) 76 c.Check(client.SnapshotSet{}.Size(), check.Equals, int64(0)) 77 // if not empty, returns the sum 78 c.Check(client.SnapshotSet{Snapshots: []*client.Snapshot{ 79 {Size: 1}, 80 {Size: 2}, 81 {Size: 3}, 82 }}.Size(), check.DeepEquals, int64(6)) 83 } 84 85 func (cs *clientSuite) TestClientSnapshotSets(c *check.C) { 86 cs.rsp = `{ 87 "type": "sync", 88 "result": [{"id": 1}, {"id":2}] 89 }` 90 sets, err := cs.cli.SnapshotSets(42, []string{"foo", "bar"}) 91 c.Assert(err, check.IsNil) 92 c.Check(sets, check.DeepEquals, []client.SnapshotSet{{ID: 1}, {ID: 2}}) 93 c.Check(cs.req.Method, check.Equals, "GET") 94 c.Check(cs.req.URL.Path, check.Equals, "/v2/snapshots") 95 c.Check(cs.req.URL.Query(), check.DeepEquals, url.Values{ 96 "set": []string{"42"}, 97 "snaps": []string{"foo,bar"}, 98 }) 99 } 100 101 func (cs *clientSuite) testClientSnapshotActionFull(c *check.C, action string, users []string, f func() (string, error)) { 102 cs.status = 202 103 cs.rsp = `{ 104 "status-code": 202, 105 "type": "async", 106 "change": "1too3" 107 }` 108 id, err := f() 109 c.Assert(err, check.IsNil) 110 c.Check(id, check.Equals, "1too3") 111 112 c.Assert(cs.req.Header.Get("Content-Type"), check.Equals, "application/json") 113 114 act, err := client.UnmarshalSnapshotAction(cs.req.Body) 115 c.Assert(err, check.IsNil) 116 c.Check(act.SetID, check.Equals, uint64(42)) 117 c.Check(act.Action, check.Equals, action) 118 c.Check(act.Snaps, check.DeepEquals, []string{"asnap", "bsnap"}) 119 c.Check(act.Users, check.DeepEquals, users) 120 121 c.Check(cs.req.Method, check.Equals, "POST") 122 c.Check(cs.req.URL.Path, check.Equals, "/v2/snapshots") 123 c.Check(cs.req.URL.Query(), check.HasLen, 0) 124 } 125 126 func (cs *clientSuite) TestClientForgetSnapshot(c *check.C) { 127 cs.testClientSnapshotActionFull(c, "forget", nil, func() (string, error) { 128 return cs.cli.ForgetSnapshots(42, []string{"asnap", "bsnap"}) 129 }) 130 } 131 132 func (cs *clientSuite) testClientSnapshotAction(c *check.C, action string, f func(uint64, []string, []string) (string, error)) { 133 cs.testClientSnapshotActionFull(c, action, []string{"auser", "buser"}, func() (string, error) { 134 return f(42, []string{"asnap", "bsnap"}, []string{"auser", "buser"}) 135 }) 136 } 137 138 func (cs *clientSuite) TestClientCheckSnapshots(c *check.C) { 139 cs.testClientSnapshotAction(c, "check", cs.cli.CheckSnapshots) 140 } 141 142 func (cs *clientSuite) TestClientRestoreSnapshots(c *check.C) { 143 cs.testClientSnapshotAction(c, "restore", cs.cli.RestoreSnapshots) 144 } 145 146 func (cs *clientSuite) TestClientExportSnapshot(c *check.C) { 147 type tableT struct { 148 content string 149 contentType string 150 status int 151 } 152 153 table := []tableT{ 154 {"dummy-export", client.SnapshotExportMediaType, 200}, 155 {"dummy-export", "application/x-tar", 400}, 156 {"", "", 400}, 157 } 158 159 for i, t := range table { 160 comm := check.Commentf("%d: %q", i, t.content) 161 162 cs.contentLength = int64(len(t.content)) 163 cs.header = http.Header{"Content-Type": []string{t.contentType}} 164 cs.rsp = t.content 165 cs.status = t.status 166 167 r, size, err := cs.cli.SnapshotExport(42) 168 if t.status == 200 { 169 c.Assert(err, check.IsNil, comm) 170 c.Assert(cs.countingCloser.closeCalled, check.Equals, 0) 171 c.Assert(size, check.Equals, int64(len(t.content)), comm) 172 } else { 173 c.Assert(err.Error(), check.Equals, "unexpected status code: ") 174 c.Assert(cs.countingCloser.closeCalled, check.Equals, 1) 175 } 176 177 if t.status == 200 { 178 buf, err := ioutil.ReadAll(r) 179 c.Assert(err, check.IsNil) 180 c.Assert(string(buf), check.Equals, t.content) 181 } 182 } 183 } 184 185 func (cs *clientSuite) TestClientSnapshotImport(c *check.C) { 186 type tableT struct { 187 rsp string 188 status int 189 setID uint64 190 error string 191 } 192 table := []tableT{ 193 {`{"type": "sync", "result": {"set-id": 42, "snaps": ["baz", "bar", "foo"]}}`, 200, 42, ""}, 194 {`{"type": "error"}`, 400, 0, "server error: \"Bad Request\""}, 195 } 196 197 for i, t := range table { 198 comm := check.Commentf("%d: %s", i, t.rsp) 199 200 cs.rsp = t.rsp 201 cs.status = t.status 202 203 fakeSnapshotData := "fake" 204 r := strings.NewReader(fakeSnapshotData) 205 importSet, err := cs.cli.SnapshotImport(r, int64(len(fakeSnapshotData))) 206 if t.error != "" { 207 c.Assert(err, check.NotNil, comm) 208 c.Check(err.Error(), check.Equals, t.error, comm) 209 continue 210 } 211 c.Assert(err, check.IsNil, comm) 212 c.Assert(cs.req.Header.Get("Content-Type"), check.Equals, client.SnapshotExportMediaType) 213 c.Assert(cs.req.Header.Get("Content-Length"), check.Equals, strconv.Itoa(len(fakeSnapshotData))) 214 c.Check(importSet.ID, check.Equals, t.setID, comm) 215 c.Check(importSet.Snaps, check.DeepEquals, []string{"baz", "bar", "foo"}, comm) 216 d, err := ioutil.ReadAll(cs.req.Body) 217 c.Assert(err, check.IsNil) 218 c.Check(string(d), check.Equals, fakeSnapshotData) 219 } 220 } 221 222 func (cs *clientSuite) TestClientSnapshotContentHash(c *check.C) { 223 now := time.Now() 224 revno := snap.R(1) 225 sums := map[string]string{"user/foo.tgz": "some long hash"} 226 227 sh1 := &client.Snapshot{SetID: 1, Time: now, Snap: "asnap", Revision: revno, SHA3_384: sums} 228 // sh1, sh1_1 are the same except time 229 sh1_1 := &client.Snapshot{SetID: 1, Time: now.Add(10), Snap: "asnap", Revision: revno, SHA3_384: sums} 230 // sh1, sh2 are the same except setID 231 sh2 := &client.Snapshot{SetID: 2, Time: now, Snap: "asnap", Revision: revno, SHA3_384: sums} 232 233 h1, err := sh1.ContentHash() 234 c.Assert(err, check.IsNil) 235 // content hash uses sha256 internally 236 c.Check(h1, check.HasLen, sha256.Size) 237 238 // same except time means same hash 239 h1_1, err := sh1_1.ContentHash() 240 c.Assert(err, check.IsNil) 241 c.Check(h1, check.DeepEquals, h1_1) 242 243 // same except set means same hash 244 h2, err := sh2.ContentHash() 245 c.Assert(err, check.IsNil) 246 c.Check(h1, check.DeepEquals, h2) 247 248 // sh3 is actually different 249 sh3 := &client.Snapshot{SetID: 1, Time: now, Snap: "other-snap", Revision: revno, SHA3_384: sums} 250 h3, err := sh3.ContentHash() 251 c.Assert(err, check.IsNil) 252 c.Check(h1, check.Not(check.DeepEquals), h3) 253 254 // identical to sh1 except for sha3_384 sums 255 sums4 := map[string]string{"user/foo.tgz": "some other hash"} 256 sh4 := &client.Snapshot{SetID: 1, Time: now, Snap: "asnap", Revision: revno, SHA3_384: sums4} 257 // same except sha3_384 means different hash 258 h4, err := sh4.ContentHash() 259 c.Assert(err, check.IsNil) 260 c.Check(h4, check.Not(check.DeepEquals), h1) 261 } 262 263 func (cs *clientSuite) TestClientSnapshotSetContentHash(c *check.C) { 264 sums := map[string]string{"user/foo.tgz": "some long hash"} 265 ss1 := client.SnapshotSet{Snapshots: []*client.Snapshot{ 266 {SetID: 1, Snap: "snap2", Size: 2, SHA3_384: sums}, 267 {SetID: 1, Snap: "snap1", Size: 1, SHA3_384: sums}, 268 {SetID: 1, Snap: "snap3", Size: 3, SHA3_384: sums}, 269 }} 270 // ss2 is the same ss1 but in a different order with different setID 271 // (but that does not matter for the content hash) 272 ss2 := client.SnapshotSet{Snapshots: []*client.Snapshot{ 273 {SetID: 2, Snap: "snap3", Size: 3, SHA3_384: sums}, 274 {SetID: 2, Snap: "snap2", Size: 2, SHA3_384: sums}, 275 {SetID: 2, Snap: "snap1", Size: 1, SHA3_384: sums}, 276 }} 277 278 h1, err := ss1.ContentHash() 279 c.Assert(err, check.IsNil) 280 // content hash uses sha256 internally 281 c.Check(h1, check.HasLen, sha256.Size) 282 283 // h1 and h2 have the same hash 284 h2, err := ss2.ContentHash() 285 c.Assert(err, check.IsNil) 286 c.Check(h2, check.DeepEquals, h1) 287 288 // ss3 is different because the size of snap3 is different 289 ss3 := client.SnapshotSet{Snapshots: []*client.Snapshot{ 290 {SetID: 1, Snap: "snap2", Size: 2}, 291 {SetID: 1, Snap: "snap3", Size: 666666666}, 292 {SetID: 1, Snap: "snap1", Size: 1}, 293 }} 294 // h1 and h3 are different 295 h3, err := ss3.ContentHash() 296 c.Assert(err, check.IsNil) 297 c.Check(h3, check.Not(check.DeepEquals), h1) 298 299 }