github.com/ubuntu-core/snappy@v0.0.0-20210827154228-9e584df982bb/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  	"encoding/json"
    25  	"reflect"
    26  	"strings"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/jsonutil/safejson"
    31  	"github.com/snapcore/snapd/snap"
    32  	"github.com/snapcore/snapd/testutil"
    33  )
    34  
    35  type detailsV2Suite struct {
    36  	testutil.BaseTest
    37  }
    38  
    39  var _ = Suite(&detailsV2Suite{})
    40  
    41  const (
    42  	coreStoreJSON = `{
    43    "architectures": [
    44      "amd64"
    45    ],
    46    "base": null,
    47    "confinement": "strict",
    48    "contact": "mailto:snappy-canonical-storeaccount@canonical.com",
    49    "created-at": "2018-01-22T07:49:19.440720+00:00",
    50    "description": "The core runtime environment for snapd",
    51    "download": {
    52       "sha3-384": "b691f6dde3d8022e4db563840f0ef82320cb824b6292ffd027dbc838535214dac31c3512c619beaf73f1aeaf35ac62d5",
    53       "size": 85291008,
    54       "url": "https://api.snapcraft.io/api/v1/snaps/download/99T7MUlRhtI3U0QFgl5mXXESAiSwt776_3887.snap",
    55       "deltas": []
    56    },
    57    "epoch": {
    58       "read": [0],
    59       "write": [0]
    60    },
    61    "license": null,
    62    "name": "core",
    63    "prices": {},
    64    "private": false,
    65    "publisher": {
    66       "id": "canonical",
    67       "username": "canonical",
    68       "display-name": "Canonical",
    69       "validation": "verified"
    70    },
    71    "revision": 3887,
    72    "snap-id": "99T7MUlRhtI3U0QFgl5mXXESAiSwt776",
    73    "store-url": "https://snapcraft.io/core",
    74    "summary": "snapd runtime environment",
    75    "title": "core",
    76    "type": "os",
    77    "version": "16-2.30",
    78    "website": "http://example.com/core",
    79    "media": []
    80  }`
    81  
    82  	thingyStoreJSON = `{
    83    "architectures": [
    84      "amd64"
    85    ],
    86    "base": "base-18",
    87    "confinement": "strict",
    88    "contact": "https://thingy.com",
    89    "common-ids": ["org.thingy"],
    90    "created-at": "2018-01-26T11:38:35.536410+00:00",
    91    "description": "Useful thingy for thinging",
    92    "download": {
    93       "sha3-384": "a29f8d894c92ad19bb943764eb845c6bd7300f555ee9b9dbb460599fecf712775c0f3e2117b5c56b08fcb9d78fc8ae4d",
    94       "size": 10000021,
    95       "url": "https://api.snapcraft.io/api/v1/snaps/download/XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV_21.snap",
    96       "deltas": [
    97         {
    98           "format": "xdelta3",
    99           "source": 19,
   100           "target": 21,
   101           "url": "https://api.snapcraft.io/api/v1/snaps/download/XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV_19_21_xdelta3.delta",
   102           "size": 9999,
   103           "sha3-384": "29f8d894c92ad19bb943764eb845c6bd7300f555ee9b9dbb460599fecf712775c0f3e2117b5c56b08fcb9d78fc8ae4df"
   104         }
   105       ]
   106    },
   107    "epoch": {
   108       "read": [0,1],
   109       "write": [1]
   110    },
   111    "license": "Proprietary",
   112    "name": "thingy",
   113    "prices": {"USD": "9.99"},
   114    "private": false,
   115    "publisher": {
   116       "id": "ZvtzsxbsHivZLdvzrt0iqW529riGLfXJ",
   117       "username": "thingyinc",
   118       "display-name": "Thingy Inc.",
   119       "validation": "unproven"
   120    },
   121    "revision": 21,
   122    "snap-id": "XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV",
   123    "snap-yaml": "name: test-snapd-content-plug\nversion: 1.0\nassumes: [snapd2.49]\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",
   124    "store-url": "https://snapcraft.io/thingy",
   125    "summary": "useful thingy",
   126    "title": "This Is The Most Fantastical Snap of Thingy",
   127    "type": "app",
   128    "version": "9.50",
   129    "website": "http://example.com/thingy",
   130    "media": [
   131       {"type": "icon", "url": "https://dashboard.snapcraft.io/site_media/appmedia/2017/12/Thingy.png"},
   132       {"type": "screenshot", "url": "https://dashboard.snapcraft.io/site_media/appmedia/2018/01/Thingy_01.png"},
   133       {"type": "screenshot", "url": "https://dashboard.snapcraft.io/site_media/appmedia/2018/01/Thingy_02.png", "width": 600, "height": 200}
   134    ]
   135  }`
   136  )
   137  
   138  func (s *detailsV2Suite) SetUpTest(c *C) {
   139  	s.BaseTest.SetUpTest(c)
   140  	s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
   141  }
   142  
   143  func (s *detailsV2Suite) TearDownTest(c *C) {
   144  	s.BaseTest.TearDownTest(c)
   145  }
   146  
   147  func (s *detailsV2Suite) TestInfoFromStoreSnapSimple(c *C) {
   148  	var snp storeSnap
   149  	err := json.Unmarshal([]byte(coreStoreJSON), &snp)
   150  	c.Assert(err, IsNil)
   151  
   152  	info, err := infoFromStoreSnap(&snp)
   153  	c.Assert(err, IsNil)
   154  	c.Check(snap.Validate(info), IsNil)
   155  
   156  	c.Check(info, DeepEquals, &snap.Info{
   157  		Architectures: []string{"amd64"},
   158  		SideInfo: snap.SideInfo{
   159  			RealName:          "core",
   160  			SnapID:            "99T7MUlRhtI3U0QFgl5mXXESAiSwt776",
   161  			Revision:          snap.R(3887),
   162  			EditedContact:     "mailto:snappy-canonical-storeaccount@canonical.com",
   163  			EditedTitle:       "core",
   164  			EditedSummary:     "snapd runtime environment",
   165  			EditedDescription: "The core runtime environment for snapd",
   166  			Private:           false,
   167  			Paid:              false,
   168  		},
   169  		Epoch:       snap.E("0"),
   170  		SnapType:    snap.TypeOS,
   171  		Version:     "16-2.30",
   172  		Confinement: snap.StrictConfinement,
   173  		Publisher: snap.StoreAccount{
   174  			ID:          "canonical",
   175  			Username:    "canonical",
   176  			DisplayName: "Canonical",
   177  			Validation:  "verified",
   178  		},
   179  		DownloadInfo: snap.DownloadInfo{
   180  			DownloadURL: "https://api.snapcraft.io/api/v1/snaps/download/99T7MUlRhtI3U0QFgl5mXXESAiSwt776_3887.snap",
   181  			Sha3_384:    "b691f6dde3d8022e4db563840f0ef82320cb824b6292ffd027dbc838535214dac31c3512c619beaf73f1aeaf35ac62d5",
   182  			Size:        85291008,
   183  		},
   184  		Plugs:    make(map[string]*snap.PlugInfo),
   185  		Slots:    make(map[string]*snap.SlotInfo),
   186  		Website:  "http://example.com/core",
   187  		StoreURL: "https://snapcraft.io/core",
   188  	})
   189  }
   190  
   191  func (s *detailsV2Suite) TestInfoFromStoreSnap(c *C) {
   192  	var snp storeSnap
   193  	// base, prices, media
   194  	err := json.Unmarshal([]byte(thingyStoreJSON), &snp)
   195  	c.Assert(err, IsNil)
   196  
   197  	info, err := infoFromStoreSnap(&snp)
   198  	c.Assert(err, IsNil)
   199  	c.Check(snap.Validate(info), IsNil)
   200  
   201  	info2 := *info
   202  	// clear recursive bits
   203  	info2.Plugs = nil
   204  	info2.Slots = nil
   205  	c.Check(&info2, DeepEquals, &snap.Info{
   206  		Architectures: []string{"amd64"},
   207  		Assumes:       []string{"snapd2.49"},
   208  		Base:          "base-18",
   209  		SideInfo: snap.SideInfo{
   210  			RealName:          "thingy",
   211  			SnapID:            "XYZEfjn4WJYnm0FzDKwqqRZZI77awQEV",
   212  			Revision:          snap.R(21),
   213  			EditedContact:     "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  		"OriginalTitle",
   292  		"OriginalSummary",
   293  		"OriginalDescription",
   294  		"OriginalLinks",
   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  		"SideInfo.EditedLinks",         // TODO: take this value from the store
   309  		"DownloadInfo.AnonDownloadURL", // TODO: going away at some point
   310  		"SystemUsernames",
   311  	}
   312  	var checker func(string, reflect.Value)
   313  	checker = func(pfx string, x reflect.Value) {
   314  		t := x.Type()
   315  		for i := 0; i < x.NumField(); i++ {
   316  			f := t.Field(i)
   317  			if f.PkgPath != "" {
   318  				// not exported, ignore
   319  				continue
   320  			}
   321  			v := x.Field(i)
   322  			if f.Anonymous {
   323  				checker(pfx+f.Name+".", v)
   324  				continue
   325  			}
   326  			name := pfx + f.Name
   327  			if reflect.DeepEqual(v.Interface(), reflect.Zero(f.Type).Interface()) {
   328  				c.Check(expectedZeroFields, testutil.Contains, name, Commentf("%s not set", name))
   329  			} else {
   330  				c.Check(expectedZeroFields, Not(testutil.Contains), name, Commentf("%s unexpectedly set", name))
   331  			}
   332  		}
   333  	}
   334  	x := reflect.ValueOf(info).Elem()
   335  	checker("", x)
   336  }
   337  
   338  // arg must be a pointer to a struct
   339  func fillStruct(a interface{}, c *C) {
   340  	if t := reflect.TypeOf(a); t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {
   341  		k := t.Kind()
   342  		if k == reflect.Ptr {
   343  			k = t.Elem().Kind()
   344  		}
   345  		c.Fatalf("first argument must be expected a pointer to a struct, not %s", k)
   346  	}
   347  	va := reflect.ValueOf(a)
   348  	n := va.Elem().NumField()
   349  	for i := 0; i < n; i++ {
   350  		field := va.Elem().Field(i)
   351  		var x interface{}
   352  		switch field.Interface().(type) {
   353  		case string:
   354  			x = "foo"
   355  		case []string:
   356  			x = []string{"foo"}
   357  		case safejson.String:
   358  			var s safejson.String
   359  			c.Assert(json.Unmarshal([]byte(`"foo"`), &s), IsNil)
   360  			x = s
   361  		case safejson.Paragraph:
   362  			var p safejson.Paragraph
   363  			c.Assert(json.Unmarshal([]byte(`"foo"`), &p), IsNil)
   364  			x = p
   365  		case storeSnapDownload:
   366  			x = storeSnapDownload{
   367  				URL:      "http://example.com/foo",
   368  				Size:     42,
   369  				Sha3_384: "foo",
   370  			}
   371  		case snap.Epoch:
   372  			x = snap.E("1")
   373  		case map[string]string:
   374  			x = map[string]string{"foo": "bar"}
   375  		case bool:
   376  			x = true
   377  		case snap.StoreAccount:
   378  			x = snap.StoreAccount{
   379  				ID:          "foo-id",
   380  				Username:    "foo",
   381  				DisplayName: "Foo Bar",
   382  				Validation:  "VALIDATION",
   383  			}
   384  		case int:
   385  			x = 42
   386  		case snap.Type:
   387  			x = snap.Type("invalid")
   388  		case []storeSnapMedia:
   389  			x = []storeSnapMedia{{
   390  				Type: "potato",
   391  				URL:  "http://example.com/foo.pot",
   392  			}}
   393  		default:
   394  			c.Fatalf("unhandled field type %T", field.Interface())
   395  		}
   396  		field.Set(reflect.ValueOf(x))
   397  	}
   398  }
   399  
   400  func (s *detailsV2Suite) TestCopyNonZero(c *C) {
   401  	// a is a storeSnap with everything non-zero
   402  	a := storeSnap{}
   403  	fillStruct(&a, c)
   404  	// b is all zeros
   405  	b := storeSnap{}
   406  
   407  	aCopy := a
   408  	bCopy := b
   409  
   410  	// sanity check
   411  	c.Check(a, DeepEquals, aCopy)
   412  	c.Check(b, DeepEquals, bCopy)
   413  	c.Check(a, Not(DeepEquals), b)
   414  
   415  	// copying from b to a does nothing:
   416  	copyNonZeroFrom(&b, &a)
   417  	c.Check(a, DeepEquals, aCopy)
   418  	c.Check(b, DeepEquals, bCopy)
   419  
   420  	// copying from a to b does its thing:
   421  	copyNonZeroFrom(&a, &b)
   422  	c.Check(a, DeepEquals, b)
   423  	c.Check(b, Not(DeepEquals), bCopy)
   424  }