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  }