github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/store/details_v2_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 // not using store_test as this is a very low level test 21 package store 22 23 import ( 24 "reflect" 25 26 "encoding/json" 27 "strings" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/jsonutil/safejson" 32 "github.com/snapcore/snapd/snap" 33 "github.com/snapcore/snapd/testutil" 34 ) 35 36 type detailsV2Suite struct { 37 testutil.BaseTest 38 } 39 40 var _ = Suite(&detailsV2Suite{}) 41 42 const ( 43 coreStoreJSON = `{ 44 "architectures": [ 45 "amd64" 46 ], 47 "base": null, 48 "confinement": "strict", 49 "contact": "mailto:snappy-canonical-storeaccount@canonical.com", 50 "created-at": "2018-01-22T07:49:19.440720+00:00", 51 "description": "The core runtime environment for snapd", 52 "download": { 53 "sha3-384": "b691f6dde3d8022e4db563840f0ef82320cb824b6292ffd027dbc838535214dac31c3512c619beaf73f1aeaf35ac62d5", 54 "size": 85291008, 55 "url": "https://api.snapcraft.io/api/v1/snaps/download/99T7MUlRhtI3U0QFgl5mXXESAiSwt776_3887.snap", 56 "deltas": [] 57 }, 58 "epoch": { 59 "read": [0], 60 "write": [0] 61 }, 62 "license": null, 63 "name": "core", 64 "prices": {}, 65 "private": false, 66 "publisher": { 67 "id": "canonical", 68 "username": "canonical", 69 "display-name": "Canonical", 70 "validation": "verified" 71 }, 72 "revision": 3887, 73 "snap-id": "99T7MUlRhtI3U0QFgl5mXXESAiSwt776", 74 "store-url": "https://snapcraft.io/core", 75 "summary": "snapd runtime environment", 76 "title": "core", 77 "type": "os", 78 "version": "16-2.30", 79 "website": "http://example.com/core", 80 "media": [] 81 }` 82 83 thingyStoreJSON = `{ 84 "architectures": [ 85 "amd64" 86 ], 87 "base": "base-18", 88 "confinement": "strict", 89 "contact": "https://thingy.com", 90 "common-ids": ["org.thingy"], 91 "created-at": "2018-01-26T11:38:35.536410+00:00", 92 "description": "Useful thingy for thinging", 93 "download": { 94 "sha3-384": "a29f8d894c92ad19bb943764eb845c6bd7300f555ee9b9dbb460599fecf712775c0f3e2117b5c56b08fcb9d78fc8ae4d", 95 "size": 10000021, 96 "url": "https://api.snapcraft.io/api/v1/snaps/download/XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV_21.snap", 97 "deltas": [ 98 { 99 "format": "xdelta3", 100 "source": 19, 101 "target": 21, 102 "url": "https://api.snapcraft.io/api/v1/snaps/download/XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV_19_21_xdelta3.delta", 103 "size": 9999, 104 "sha3-384": "29f8d894c92ad19bb943764eb845c6bd7300f555ee9b9dbb460599fecf712775c0f3e2117b5c56b08fcb9d78fc8ae4df" 105 } 106 ] 107 }, 108 "epoch": { 109 "read": [0,1], 110 "write": [1] 111 }, 112 "license": "Proprietary", 113 "name": "thingy", 114 "prices": {"USD": "9.99"}, 115 "private": false, 116 "publisher": { 117 "id": "ZvtzsxbsHivZLdvzrt0iqW529riGLfXJ", 118 "username": "thingyinc", 119 "display-name": "Thingy Inc.", 120 "validation": "unproven" 121 }, 122 "revision": 21, 123 "snap-id": "XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV", 124 "snap-yaml": "name: test-snapd-content-plug\nversion: 1.0\napps:\n content-plug:\n command: bin/content-plug\n plugs: [shared-content-plug]\nplugs:\n shared-content-plug:\n interface: content\n target: import\n content: mylib\n default-provider: test-snapd-content-slot\nslots:\n shared-content-slot:\n interface: content\n content: mylib\n read:\n - /\n", 125 "store-url": "https://snapcraft.io/thingy", 126 "summary": "useful thingy", 127 "title": "This Is The Most Fantastical Snap of Thingy", 128 "type": "app", 129 "version": "9.50", 130 "website": "http://example.com/thingy", 131 "media": [ 132 {"type": "icon", "url": "https://dashboard.snapcraft.io/site_media/appmedia/2017/12/Thingy.png"}, 133 {"type": "screenshot", "url": "https://dashboard.snapcraft.io/site_media/appmedia/2018/01/Thingy_01.png"}, 134 {"type": "screenshot", "url": "https://dashboard.snapcraft.io/site_media/appmedia/2018/01/Thingy_02.png", "width": 600, "height": 200} 135 ] 136 }` 137 ) 138 139 func (s *detailsV2Suite) SetUpTest(c *C) { 140 s.BaseTest.SetUpTest(c) 141 s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) 142 } 143 144 func (s *detailsV2Suite) TearDownTest(c *C) { 145 s.BaseTest.TearDownTest(c) 146 } 147 148 func (s *detailsV2Suite) TestInfoFromStoreSnapSimple(c *C) { 149 var snp storeSnap 150 err := json.Unmarshal([]byte(coreStoreJSON), &snp) 151 c.Assert(err, IsNil) 152 153 info, err := infoFromStoreSnap(&snp) 154 c.Assert(err, IsNil) 155 c.Check(snap.Validate(info), IsNil) 156 157 c.Check(info, DeepEquals, &snap.Info{ 158 Architectures: []string{"amd64"}, 159 SideInfo: snap.SideInfo{ 160 RealName: "core", 161 SnapID: "99T7MUlRhtI3U0QFgl5mXXESAiSwt776", 162 Revision: snap.R(3887), 163 Contact: "mailto:snappy-canonical-storeaccount@canonical.com", 164 EditedTitle: "core", 165 EditedSummary: "snapd runtime environment", 166 EditedDescription: "The core runtime environment for snapd", 167 Private: false, 168 Paid: false, 169 }, 170 Epoch: snap.E("0"), 171 SnapType: snap.TypeOS, 172 Version: "16-2.30", 173 Confinement: snap.StrictConfinement, 174 Publisher: snap.StoreAccount{ 175 ID: "canonical", 176 Username: "canonical", 177 DisplayName: "Canonical", 178 Validation: "verified", 179 }, 180 DownloadInfo: snap.DownloadInfo{ 181 DownloadURL: "https://api.snapcraft.io/api/v1/snaps/download/99T7MUlRhtI3U0QFgl5mXXESAiSwt776_3887.snap", 182 Sha3_384: "b691f6dde3d8022e4db563840f0ef82320cb824b6292ffd027dbc838535214dac31c3512c619beaf73f1aeaf35ac62d5", 183 Size: 85291008, 184 }, 185 Plugs: make(map[string]*snap.PlugInfo), 186 Slots: make(map[string]*snap.SlotInfo), 187 Website: "http://example.com/core", 188 StoreURL: "https://snapcraft.io/core", 189 }) 190 } 191 192 func (s *detailsV2Suite) TestInfoFromStoreSnap(c *C) { 193 var snp storeSnap 194 // base, prices, media 195 err := json.Unmarshal([]byte(thingyStoreJSON), &snp) 196 c.Assert(err, IsNil) 197 198 info, err := infoFromStoreSnap(&snp) 199 c.Assert(err, IsNil) 200 c.Check(snap.Validate(info), IsNil) 201 202 info2 := *info 203 // clear recursive bits 204 info2.Plugs = nil 205 info2.Slots = nil 206 c.Check(&info2, DeepEquals, &snap.Info{ 207 Architectures: []string{"amd64"}, 208 Base: "base-18", 209 SideInfo: snap.SideInfo{ 210 RealName: "thingy", 211 SnapID: "XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV", 212 Revision: snap.R(21), 213 Contact: "https://thingy.com", 214 EditedTitle: "This Is The Most Fantastical Snap of Th…", 215 EditedSummary: "useful thingy", 216 EditedDescription: "Useful thingy for thinging", 217 Private: false, 218 Paid: true, 219 }, 220 Epoch: snap.Epoch{ 221 Read: []uint32{0, 1}, 222 Write: []uint32{1}, 223 }, 224 SnapType: snap.TypeApp, 225 Version: "9.50", 226 Confinement: snap.StrictConfinement, 227 License: "Proprietary", 228 Publisher: snap.StoreAccount{ 229 ID: "ZvtzsxbsHivZLdvzrt0iqW529riGLfXJ", 230 Username: "thingyinc", 231 DisplayName: "Thingy Inc.", 232 Validation: "unproven", 233 }, 234 DownloadInfo: snap.DownloadInfo{ 235 DownloadURL: "https://api.snapcraft.io/api/v1/snaps/download/XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV_21.snap", 236 Sha3_384: "a29f8d894c92ad19bb943764eb845c6bd7300f555ee9b9dbb460599fecf712775c0f3e2117b5c56b08fcb9d78fc8ae4d", 237 Size: 10000021, 238 Deltas: []snap.DeltaInfo{ 239 { 240 Format: "xdelta3", 241 FromRevision: 19, 242 ToRevision: 21, 243 DownloadURL: "https://api.snapcraft.io/api/v1/snaps/download/XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV_19_21_xdelta3.delta", 244 Size: 9999, 245 Sha3_384: "29f8d894c92ad19bb943764eb845c6bd7300f555ee9b9dbb460599fecf712775c0f3e2117b5c56b08fcb9d78fc8ae4df", 246 }, 247 }, 248 }, 249 Prices: map[string]float64{ 250 "USD": 9.99, 251 }, 252 Media: []snap.MediaInfo{ 253 {Type: "icon", URL: "https://dashboard.snapcraft.io/site_media/appmedia/2017/12/Thingy.png"}, 254 {Type: "screenshot", URL: "https://dashboard.snapcraft.io/site_media/appmedia/2018/01/Thingy_01.png"}, 255 {Type: "screenshot", URL: "https://dashboard.snapcraft.io/site_media/appmedia/2018/01/Thingy_02.png", Width: 600, Height: 200}, 256 }, 257 CommonIDs: []string{"org.thingy"}, 258 Website: "http://example.com/thingy", 259 StoreURL: "https://snapcraft.io/thingy", 260 }) 261 262 // validate the plugs/slots 263 c.Assert(info.Plugs, HasLen, 1) 264 plug := info.Plugs["shared-content-plug"] 265 c.Check(plug.Name, Equals, "shared-content-plug") 266 c.Check(plug.Snap, Equals, info) 267 c.Check(plug.Apps, HasLen, 1) 268 c.Check(plug.Apps["content-plug"].Command, Equals, "bin/content-plug") 269 270 c.Assert(info.Slots, HasLen, 1) 271 slot := info.Slots["shared-content-slot"] 272 c.Check(slot.Name, Equals, "shared-content-slot") 273 c.Check(slot.Snap, Equals, info) 274 c.Check(slot.Apps, HasLen, 1) 275 c.Check(slot.Apps["content-plug"].Command, Equals, "bin/content-plug") 276 277 // private 278 err = json.Unmarshal([]byte(strings.Replace(thingyStoreJSON, `"private": false`, `"private": true`, 1)), &snp) 279 c.Assert(err, IsNil) 280 281 info, err = infoFromStoreSnap(&snp) 282 c.Assert(err, IsNil) 283 c.Check(snap.Validate(info), IsNil) 284 285 c.Check(info.Private, Equals, true) 286 287 // check that up to few exceptions info is filled 288 expectedZeroFields := []string{ 289 "SuggestedName", 290 "InstanceKey", 291 "Assumes", 292 "OriginalTitle", 293 "OriginalSummary", 294 "OriginalDescription", 295 "Environment", 296 "LicenseAgreement", // XXX go away? 297 "LicenseVersion", // XXX go away? 298 "Apps", 299 "LegacyAliases", 300 "Hooks", 301 "BadInterfaces", 302 "Broken", 303 "MustBuy", 304 "Channels", // handled at a different level (see TestInfo) 305 "Tracks", // handled at a different level (see TestInfo) 306 "Layout", 307 "SideInfo.Channel", 308 "DownloadInfo.AnonDownloadURL", // TODO: going away at some point 309 "SystemUsernames", 310 } 311 var checker func(string, reflect.Value) 312 checker = func(pfx string, x reflect.Value) { 313 t := x.Type() 314 for i := 0; i < x.NumField(); i++ { 315 f := t.Field(i) 316 if f.PkgPath != "" { 317 // not exported, ignore 318 continue 319 } 320 v := x.Field(i) 321 if f.Anonymous { 322 checker(pfx+f.Name+".", v) 323 continue 324 } 325 if reflect.DeepEqual(v.Interface(), reflect.Zero(f.Type).Interface()) { 326 name := pfx + f.Name 327 c.Check(expectedZeroFields, testutil.Contains, name, Commentf("%s not set", name)) 328 } 329 } 330 } 331 x := reflect.ValueOf(info).Elem() 332 checker("", x) 333 } 334 335 // arg must be a pointer to a struct 336 func fillStruct(a interface{}, c *C) { 337 if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct { 338 k := t.Kind() 339 if k == reflect.Ptr { 340 k = t.Elem().Kind() 341 } 342 c.Fatalf("first argument must be expected a pointer to a struct, not %s", k) 343 } 344 va := reflect.ValueOf(a) 345 n := va.Elem().NumField() 346 for i := 0; i < n; i++ { 347 field := va.Elem().Field(i) 348 var x interface{} 349 switch field.Interface().(type) { 350 case string: 351 x = "foo" 352 case []string: 353 x = []string{"foo"} 354 case safejson.String: 355 var s safejson.String 356 c.Assert(json.Unmarshal([]byte(`"foo"`), &s), IsNil) 357 x = s 358 case safejson.Paragraph: 359 var p safejson.Paragraph 360 c.Assert(json.Unmarshal([]byte(`"foo"`), &p), IsNil) 361 x = p 362 case storeSnapDownload: 363 x = storeSnapDownload{ 364 URL: "http://example.com/foo", 365 Size: 42, 366 Sha3_384: "foo", 367 } 368 case snap.Epoch: 369 x = snap.E("1") 370 case map[string]string: 371 x = map[string]string{"foo": "bar"} 372 case bool: 373 x = true 374 case snap.StoreAccount: 375 x = snap.StoreAccount{ 376 ID: "foo-id", 377 Username: "foo", 378 DisplayName: "Foo Bar", 379 Validation: "VALIDATION", 380 } 381 case int: 382 x = 42 383 case snap.Type: 384 x = snap.Type("invalid") 385 case []storeSnapMedia: 386 x = []storeSnapMedia{{ 387 Type: "potato", 388 URL: "http://example.com/foo.pot", 389 }} 390 default: 391 c.Fatalf("unhandled field type %T", field.Interface()) 392 } 393 field.Set(reflect.ValueOf(x)) 394 } 395 } 396 397 func (s *detailsV2Suite) TestCopyNonZero(c *C) { 398 // a is a storeSnap with everything non-zero 399 a := storeSnap{} 400 fillStruct(&a, c) 401 // b is all zeros 402 b := storeSnap{} 403 404 aCopy := a 405 bCopy := b 406 407 // sanity check 408 c.Check(a, DeepEquals, aCopy) 409 c.Check(b, DeepEquals, bCopy) 410 c.Check(a, Not(DeepEquals), b) 411 412 // copying from b to a does nothing: 413 copyNonZeroFrom(&b, &a) 414 c.Check(a, DeepEquals, aCopy) 415 c.Check(b, DeepEquals, bCopy) 416 417 // copying from a to b does its thing: 418 copyNonZeroFrom(&a, &b) 419 c.Check(a, DeepEquals, b) 420 c.Check(b, Not(DeepEquals), bCopy) 421 }