github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/asserts/snapasserts/validation_sets_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 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 snapasserts_test
    21  
    22  import (
    23  	"fmt"
    24  	"sort"
    25  
    26  	. "gopkg.in/check.v1"
    27  
    28  	"github.com/snapcore/snapd/asserts"
    29  	"github.com/snapcore/snapd/asserts/assertstest"
    30  	"github.com/snapcore/snapd/asserts/snapasserts"
    31  	"github.com/snapcore/snapd/snap"
    32  	"github.com/snapcore/snapd/snap/naming"
    33  )
    34  
    35  type validationSetsSuite struct{}
    36  
    37  var _ = Suite(&validationSetsSuite{})
    38  
    39  func (s *validationSetsSuite) TestAddFromSameSequence(c *C) {
    40  	mySnapAt7Valset := assertstest.FakeAssertion(map[string]interface{}{
    41  		"type":         "validation-set",
    42  		"authority-id": "account-id",
    43  		"series":       "16",
    44  		"account-id":   "account-id",
    45  		"name":         "my-snap-ctl",
    46  		"sequence":     "1",
    47  		"snaps": []interface{}{
    48  			map[string]interface{}{
    49  				"name":     "my-snap",
    50  				"id":       "mysnapididididididididididididid",
    51  				"presence": "required",
    52  				"revision": "7",
    53  			},
    54  		},
    55  	}).(*asserts.ValidationSet)
    56  
    57  	mySnapAt8Valset := assertstest.FakeAssertion(map[string]interface{}{
    58  		"type":         "validation-set",
    59  		"authority-id": "account-id",
    60  		"series":       "16",
    61  		"account-id":   "account-id",
    62  		"name":         "my-snap-ctl",
    63  		"sequence":     "2",
    64  		"snaps": []interface{}{
    65  			map[string]interface{}{
    66  				"name":     "my-snap",
    67  				"id":       "mysnapididididididididididididid",
    68  				"presence": "required",
    69  				"revision": "8",
    70  			},
    71  		},
    72  	}).(*asserts.ValidationSet)
    73  
    74  	valsets := snapasserts.NewValidationSets()
    75  	err := valsets.Add(mySnapAt7Valset)
    76  	c.Assert(err, IsNil)
    77  	err = valsets.Add(mySnapAt8Valset)
    78  	c.Check(err, ErrorMatches, `cannot add a second validation-set under "account-id/my-snap-ctl"`)
    79  }
    80  
    81  func (s *validationSetsSuite) TestIntersections(c *C) {
    82  	mySnapAt7Valset := assertstest.FakeAssertion(map[string]interface{}{
    83  		"type":         "validation-set",
    84  		"authority-id": "account-id",
    85  		"series":       "16",
    86  		"account-id":   "account-id",
    87  		"name":         "my-snap-ctl",
    88  		"sequence":     "1",
    89  		"snaps": []interface{}{
    90  			map[string]interface{}{
    91  				"name":     "my-snap",
    92  				"id":       "mysnapididididididididididididid",
    93  				"presence": "required",
    94  				"revision": "7",
    95  			},
    96  		},
    97  	}).(*asserts.ValidationSet)
    98  
    99  	mySnapAt7Valset2 := assertstest.FakeAssertion(map[string]interface{}{
   100  		"type":         "validation-set",
   101  		"authority-id": "account-id",
   102  		"series":       "16",
   103  		"account-id":   "account-id",
   104  		"name":         "my-snap-ctl2",
   105  		"sequence":     "2",
   106  		"snaps": []interface{}{
   107  			map[string]interface{}{
   108  				"name":     "my-snap",
   109  				"id":       "mysnapididididididididididididid",
   110  				"presence": "required",
   111  				"revision": "7",
   112  			},
   113  		},
   114  	}).(*asserts.ValidationSet)
   115  
   116  	mySnapAt8Valset := assertstest.FakeAssertion(map[string]interface{}{
   117  		"type":         "validation-set",
   118  		"authority-id": "account-id",
   119  		"series":       "16",
   120  		"account-id":   "account-id",
   121  		"name":         "my-snap-ctl-other",
   122  		"sequence":     "1",
   123  		"snaps": []interface{}{
   124  			map[string]interface{}{
   125  				"name":     "my-snap",
   126  				"id":       "mysnapididididididididididididid",
   127  				"presence": "required",
   128  				"revision": "8",
   129  			},
   130  		},
   131  	}).(*asserts.ValidationSet)
   132  
   133  	mySnapAt8OptValset := assertstest.FakeAssertion(map[string]interface{}{
   134  		"type":         "validation-set",
   135  		"authority-id": "account-id",
   136  		"series":       "16",
   137  		"account-id":   "account-id",
   138  		"name":         "my-snap-ctl-opt",
   139  		"sequence":     "1",
   140  		"snaps": []interface{}{
   141  			map[string]interface{}{
   142  				"name":     "my-snap",
   143  				"id":       "mysnapididididididididididididid",
   144  				"presence": "optional",
   145  				"revision": "8",
   146  			},
   147  		},
   148  	}).(*asserts.ValidationSet)
   149  
   150  	mySnapInvalidValset := assertstest.FakeAssertion(map[string]interface{}{
   151  		"type":         "validation-set",
   152  		"authority-id": "account-id",
   153  		"series":       "16",
   154  		"account-id":   "account-id",
   155  		"name":         "my-snap-ctl-inv",
   156  		"sequence":     "1",
   157  		"snaps": []interface{}{
   158  			map[string]interface{}{
   159  				"name":     "my-snap",
   160  				"id":       "mysnapididididididididididididid",
   161  				"presence": "invalid",
   162  			},
   163  		},
   164  	}).(*asserts.ValidationSet)
   165  
   166  	mySnapAt7OptValset := assertstest.FakeAssertion(map[string]interface{}{
   167  		"type":         "validation-set",
   168  		"authority-id": "account-id",
   169  		"series":       "16",
   170  		"account-id":   "account-id",
   171  		"name":         "my-snap-ctl-opt2",
   172  		"sequence":     "1",
   173  		"snaps": []interface{}{
   174  			map[string]interface{}{
   175  				"name":     "my-snap",
   176  				"id":       "mysnapididididididididididididid",
   177  				"presence": "optional",
   178  				"revision": "7",
   179  			},
   180  		},
   181  	}).(*asserts.ValidationSet)
   182  
   183  	mySnapReqValset := assertstest.FakeAssertion(map[string]interface{}{
   184  		"type":         "validation-set",
   185  		"authority-id": "account-id",
   186  		"series":       "16",
   187  		"account-id":   "account-id",
   188  		"name":         "my-snap-ctl-req-only",
   189  		"sequence":     "1",
   190  		"snaps": []interface{}{
   191  			map[string]interface{}{
   192  				"name":     "my-snap",
   193  				"id":       "mysnapididididididididididididid",
   194  				"presence": "required",
   195  			},
   196  		},
   197  	}).(*asserts.ValidationSet)
   198  
   199  	mySnapOptValset := assertstest.FakeAssertion(map[string]interface{}{
   200  		"type":         "validation-set",
   201  		"authority-id": "account-id",
   202  		"series":       "16",
   203  		"account-id":   "account-id",
   204  		"name":         "my-snap-ctl-opt-only",
   205  		"sequence":     "1",
   206  		"snaps": []interface{}{
   207  			map[string]interface{}{
   208  				"name":     "my-snap",
   209  				"id":       "mysnapididididididididididididid",
   210  				"presence": "optional",
   211  			},
   212  		},
   213  	}).(*asserts.ValidationSet)
   214  
   215  	tests := []struct {
   216  		sets        []*asserts.ValidationSet
   217  		conflictErr string
   218  	}{
   219  		{[]*asserts.ValidationSet{mySnapAt7Valset}, ""},
   220  		{[]*asserts.ValidationSet{mySnapAt7Valset, mySnapAt7Valset2}, ""},
   221  		{[]*asserts.ValidationSet{mySnapAt7Valset, mySnapAt8Valset}, `(?ms)validation sets are in conflict:.*cannot constrain snap "my-snap" at different revisions 7 \(account-id/my-snap-ctl\), 8 \(account-id/my-snap-ctl-other\)`},
   222  		{[]*asserts.ValidationSet{mySnapAt8Valset, mySnapAt8OptValset}, ""},
   223  		{[]*asserts.ValidationSet{mySnapAt7Valset, mySnapAt8OptValset}, `(?ms)validation sets are in conflict:.*cannot constrain snap "my-snap" at different revisions 7 \(account-id/my-snap-ctl\), 8 \(account-id/my-snap-ctl-opt\)`},
   224  		{[]*asserts.ValidationSet{mySnapAt7Valset, mySnapInvalidValset}, `(?ms)validation sets are in conflict:.*cannot constrain snap "my-snap" as both invalid \(account-id/my-snap-ctl-inv\) and required at revision 7 \(account-id/my-snap-ctl\)`},
   225  		{[]*asserts.ValidationSet{mySnapInvalidValset, mySnapAt7Valset}, `(?ms)validation sets are in conflict:.*cannot constrain snap "my-snap" as both invalid \(account-id/my-snap-ctl-inv\) and required at revision 7 \(account-id/my-snap-ctl\)`},
   226  		{[]*asserts.ValidationSet{mySnapAt8OptValset, mySnapInvalidValset}, ""},
   227  		{[]*asserts.ValidationSet{mySnapInvalidValset, mySnapAt8OptValset}, ""},
   228  		{[]*asserts.ValidationSet{mySnapAt7OptValset, mySnapAt8OptValset}, ""}, // no conflict but interpreted as invalid
   229  		{[]*asserts.ValidationSet{mySnapAt7OptValset, mySnapAt8OptValset, mySnapAt7Valset}, `(?ms)validation sets are in conflict:.*cannot constrain snap "my-snap" at different revisions 7 \(account-id/my-snap-ctl,account-id/my-snap-ctl-opt2\), 8 \(account-id/my-snap-ctl-opt\)`},
   230  		{[]*asserts.ValidationSet{mySnapAt8OptValset, mySnapInvalidValset, mySnapAt7Valset}, `(?ms)validation sets are in conflict:.*cannot constrain snap "my-snap" as both invalid \(account-id/my-snap-ctl-inv\) and required at different revisions 7 \(account-id/my-snap-ctl\), 8 \(account-id/my-snap-ctl-opt\)`},
   231  		{[]*asserts.ValidationSet{mySnapAt7Valset, mySnapReqValset}, ""},
   232  		{[]*asserts.ValidationSet{mySnapReqValset, mySnapAt7Valset}, ""},
   233  		{[]*asserts.ValidationSet{mySnapAt8OptValset, mySnapReqValset}, ""},
   234  		{[]*asserts.ValidationSet{mySnapAt8OptValset, mySnapReqValset, mySnapAt7OptValset}, `(?ms)validation sets are in conflict:.*cannot constrain snap "my-snap" at different revisions 7 \(account-id/my-snap-ctl-opt2\), 8 \(account-id/my-snap-ctl-opt\) or required at any revision \(account-id/my-snap-ctl-req-only\)`},
   235  		{[]*asserts.ValidationSet{mySnapAt8OptValset, mySnapAt7OptValset, mySnapReqValset}, `(?ms)validation sets are in conflict:.*cannot constrain snap "my-snap" at different revisions 7 \(account-id/my-snap-ctl-opt2\), 8 \(account-id/my-snap-ctl-opt\) or required at any revision \(account-id/my-snap-ctl-req-only\)`},
   236  		{[]*asserts.ValidationSet{mySnapReqValset, mySnapInvalidValset}, `(?ms)validation sets are in conflict:.*cannot constrain snap "my-snap" as both invalid \(account-id/my-snap-ctl-inv\) and required at any revision \(account-id/my-snap-ctl-req-only\)`},
   237  		{[]*asserts.ValidationSet{mySnapInvalidValset, mySnapReqValset}, `(?ms)validation sets are in conflict:.*cannot constrain snap "my-snap" as both invalid \(account-id/my-snap-ctl-inv\) and required at any revision \(account-id/my-snap-ctl-req-only\)`},
   238  		{[]*asserts.ValidationSet{mySnapAt7Valset, mySnapAt8Valset, mySnapOptValset}, `(?ms)validation sets are in conflict:.*cannot constrain snap "my-snap" at different revisions 7 \(account-id/my-snap-ctl\), 8 \(account-id/my-snap-ctl-other\)`},
   239  		{[]*asserts.ValidationSet{mySnapAt7Valset, mySnapOptValset}, ""},
   240  		{[]*asserts.ValidationSet{mySnapOptValset, mySnapAt7Valset}, ""},
   241  		{[]*asserts.ValidationSet{mySnapAt8OptValset, mySnapOptValset}, ""},
   242  		{[]*asserts.ValidationSet{mySnapAt8OptValset, mySnapOptValset, mySnapAt7OptValset}, ""}, // no conflict but interpreted as invalid
   243  		{[]*asserts.ValidationSet{mySnapInvalidValset, mySnapOptValset}, ""},
   244  		{[]*asserts.ValidationSet{mySnapOptValset, mySnapInvalidValset}, ""},
   245  		{[]*asserts.ValidationSet{mySnapAt7Valset, mySnapAt8Valset, mySnapReqValset, mySnapInvalidValset}, `(?ms)validation sets are in conflict:.*cannot constrain snap "my-snap" as both invalid \(account-id/my-snap-ctl-inv\) and required at different revisions 7 \(account-id/my-snap-ctl\), 8 \(account-id/my-snap-ctl-other\) or at any revision \(account-id/my-snap-ctl-req-only\)`},
   246  	}
   247  
   248  	for _, t := range tests {
   249  		valsets := snapasserts.NewValidationSets()
   250  		cSets := make(map[string]*asserts.ValidationSet)
   251  		for _, valset := range t.sets {
   252  			err := valsets.Add(valset)
   253  			c.Assert(err, IsNil)
   254  			// mySnapOptValset never influcens an outcome
   255  			if valset != mySnapOptValset {
   256  				cSets[fmt.Sprintf("%s/%s", valset.AccountID(), valset.Name())] = valset
   257  			}
   258  		}
   259  		err := valsets.Conflict()
   260  		if t.conflictErr == "" {
   261  			c.Check(err, IsNil)
   262  		} else {
   263  			c.Check(err, ErrorMatches, t.conflictErr)
   264  			ce := err.(*snapasserts.ValidationSetsConflictError)
   265  			c.Check(ce.Sets, DeepEquals, cSets)
   266  		}
   267  	}
   268  }
   269  
   270  func (s *validationSetsSuite) TestCheckInstalledSnapsNoValidationSets(c *C) {
   271  	valsets := snapasserts.NewValidationSets()
   272  	snaps := []*snapasserts.InstalledSnap{
   273  		snapasserts.NewInstalledSnap("snap-a", "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa", snap.R(1)),
   274  	}
   275  	err := valsets.CheckInstalledSnaps(snaps)
   276  	c.Assert(err, IsNil)
   277  }
   278  
   279  func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) {
   280  	// require: snapB rev 3, snapC rev 2.
   281  	// invalid: snapA
   282  	vs1 := assertstest.FakeAssertion(map[string]interface{}{
   283  		"type":         "validation-set",
   284  		"authority-id": "acme",
   285  		"series":       "16",
   286  		"account-id":   "acme",
   287  		"name":         "fooname",
   288  		"sequence":     "1",
   289  		"snaps": []interface{}{
   290  			map[string]interface{}{
   291  				"name":     "snap-a",
   292  				"id":       "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa",
   293  				"presence": "invalid",
   294  			},
   295  			map[string]interface{}{
   296  				"name":     "snap-b",
   297  				"id":       "mysnapbbbbbbbbbbbbbbbbbbbbbbbbbb",
   298  				"revision": "3",
   299  				"presence": "required",
   300  			},
   301  			map[string]interface{}{
   302  				"name":     "snap-c",
   303  				"id":       "mysnapcccccccccccccccccccccccccc",
   304  				"revision": "2",
   305  				"presence": "optional",
   306  			},
   307  		},
   308  	}).(*asserts.ValidationSet)
   309  
   310  	// require: snapD any rev
   311  	// optional: snapE any rev
   312  	vs2 := assertstest.FakeAssertion(map[string]interface{}{
   313  		"type":         "validation-set",
   314  		"authority-id": "acme",
   315  		"series":       "16",
   316  		"account-id":   "acme",
   317  		"name":         "barname",
   318  		"sequence":     "3",
   319  		"snaps": []interface{}{
   320  			map[string]interface{}{
   321  				"name":     "snap-d",
   322  				"id":       "mysnapdddddddddddddddddddddddddd",
   323  				"presence": "required",
   324  			},
   325  			map[string]interface{}{
   326  				"name":     "snap-e",
   327  				"id":       "mysnapeeeeeeeeeeeeeeeeeeeeeeeeee",
   328  				"presence": "optional",
   329  			},
   330  		},
   331  	}).(*asserts.ValidationSet)
   332  
   333  	// optional: snapE any rev
   334  	// note: since it only has an optional snap, acme/bazname is not expected
   335  	// not be invalid by any of the checks.
   336  	vs3 := assertstest.FakeAssertion(map[string]interface{}{
   337  		"type":         "validation-set",
   338  		"authority-id": "acme",
   339  		"series":       "16",
   340  		"account-id":   "acme",
   341  		"name":         "bazname",
   342  		"sequence":     "2",
   343  		"snaps": []interface{}{
   344  			map[string]interface{}{
   345  				"name":     "snap-e",
   346  				"id":       "mysnapeeeeeeeeeeeeeeeeeeeeeeeeee",
   347  				"presence": "optional",
   348  			},
   349  		},
   350  	}).(*asserts.ValidationSet)
   351  
   352  	// invalid: snapA
   353  	vs4 := assertstest.FakeAssertion(map[string]interface{}{
   354  		"type":         "validation-set",
   355  		"authority-id": "acme",
   356  		"series":       "16",
   357  		"account-id":   "acme",
   358  		"name":         "booname",
   359  		"sequence":     "1",
   360  		"snaps": []interface{}{
   361  			map[string]interface{}{
   362  				"name":     "snap-a",
   363  				"id":       "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa",
   364  				"presence": "invalid",
   365  			},
   366  		},
   367  	}).(*asserts.ValidationSet)
   368  
   369  	valsets := snapasserts.NewValidationSets()
   370  	c.Assert(valsets.Add(vs1), IsNil)
   371  	c.Assert(valsets.Add(vs2), IsNil)
   372  	c.Assert(valsets.Add(vs3), IsNil)
   373  	c.Assert(valsets.Add(vs4), IsNil)
   374  
   375  	snapA := snapasserts.NewInstalledSnap("snap-a", "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa", snap.R(1))
   376  	snapAlocal := snapasserts.NewInstalledSnap("snap-a", "", snap.R("x2"))
   377  	snapB := snapasserts.NewInstalledSnap("snap-b", "mysnapbbbbbbbbbbbbbbbbbbbbbbbbbb", snap.R(3))
   378  	snapBinvRev := snapasserts.NewInstalledSnap("snap-b", "mysnapbbbbbbbbbbbbbbbbbbbbbbbbbb", snap.R(8))
   379  	snapBlocal := snapasserts.NewInstalledSnap("snap-b", "", snap.R("x3"))
   380  	snapC := snapasserts.NewInstalledSnap("snap-c", "mysnapcccccccccccccccccccccccccc", snap.R(2))
   381  	snapCinvRev := snapasserts.NewInstalledSnap("snap-c", "mysnapcccccccccccccccccccccccccc", snap.R(99))
   382  	snapD := snapasserts.NewInstalledSnap("snap-d", "mysnapdddddddddddddddddddddddddd", snap.R(2))
   383  	snapDrev99 := snapasserts.NewInstalledSnap("snap-d", "mysnapdddddddddddddddddddddddddd", snap.R(99))
   384  	snapDlocal := snapasserts.NewInstalledSnap("snap-d", "", snap.R("x3"))
   385  	snapE := snapasserts.NewInstalledSnap("snap-e", "mysnapeeeeeeeeeeeeeeeeeeeeeeeeee", snap.R(2))
   386  	// extra snap, not referenced by any validation set
   387  	snapZ := snapasserts.NewInstalledSnap("snap-z", "mysnapzzzzzzzzzzzzzzzzzzzzzzzzzz", snap.R(1))
   388  
   389  	tests := []struct {
   390  		snaps            []*snapasserts.InstalledSnap
   391  		expectedInvalid  map[string][]string
   392  		expectedMissing  map[string][]string
   393  		expectedWrongRev map[string]map[snap.Revision][]string
   394  	}{
   395  		{
   396  			// required snaps not installed
   397  			snaps: nil,
   398  			expectedMissing: map[string][]string{
   399  				"snap-b": {"acme/fooname"},
   400  				"snap-d": {"acme/barname"},
   401  			},
   402  		},
   403  		{
   404  			// required snaps not installed
   405  			snaps: []*snapasserts.InstalledSnap{
   406  				snapZ,
   407  			},
   408  			expectedMissing: map[string][]string{
   409  				"snap-b": {"acme/fooname"},
   410  				"snap-d": {"acme/barname"},
   411  			},
   412  		},
   413  		{
   414  			snaps: []*snapasserts.InstalledSnap{
   415  				// covered by acme/fooname validation-set
   416  				snapB,
   417  				// covered by acme/barname validation-set. snap-e not installed but optional
   418  				snapDrev99},
   419  			// ale fine
   420  		},
   421  		{
   422  			snaps: []*snapasserts.InstalledSnap{
   423  				// covered by acme/fooname validation-set and acme/booname, snap-a presence is invalid
   424  				snapA,
   425  				snapB,
   426  				// covered by acme/barname validation-set. snap-e not installed but optional
   427  				snapDrev99},
   428  			expectedInvalid: map[string][]string{
   429  				"snap-a": {"acme/booname", "acme/fooname"},
   430  			},
   431  		},
   432  		{
   433  			snaps: []*snapasserts.InstalledSnap{
   434  				// covered by acme/fooname and acme/booname validation-sets, snapB missing, snap-a presence is invalid
   435  				snapA,
   436  				// covered by acme/barname validation-set. snap-e not installed but optional
   437  				snapDrev99},
   438  			expectedInvalid: map[string][]string{
   439  				"snap-a": {"acme/booname", "acme/fooname"},
   440  			},
   441  			expectedMissing: map[string][]string{
   442  				"snap-b": {"acme/fooname"},
   443  			},
   444  		},
   445  		{
   446  			snaps: []*snapasserts.InstalledSnap{
   447  				// covered by acme/fooname validation-set
   448  				snapB,
   449  				snapC,
   450  				// covered by acme/barname validation-set. snap-e not installed but optional
   451  				snapD},
   452  			// ale fine
   453  		},
   454  		{
   455  			snaps: []*snapasserts.InstalledSnap{
   456  				// covered by acme/fooname validation-set, snap-c optional but wrong revision
   457  				snapB,
   458  				snapCinvRev,
   459  				// covered by acme/barname validation-set. snap-e not installed but optional
   460  				snapD},
   461  			expectedWrongRev: map[string]map[snap.Revision][]string{
   462  				"snap-c": {
   463  					snap.R(2): {"acme/fooname"},
   464  				},
   465  			},
   466  		},
   467  		{
   468  			snaps: []*snapasserts.InstalledSnap{
   469  				// covered by acme/fooname validation-set but wrong revision
   470  				snapBinvRev,
   471  				// covered by acme/barname validation-set.
   472  				snapD},
   473  			expectedWrongRev: map[string]map[snap.Revision][]string{
   474  				"snap-b": {
   475  					snap.R(3): {"acme/fooname"},
   476  				},
   477  			},
   478  		},
   479  		{
   480  			snaps: []*snapasserts.InstalledSnap{
   481  				// covered by acme/fooname validation-set
   482  				snapB,
   483  				// covered by acme/barname validation-set. snap-d not installed.
   484  				snapE},
   485  			expectedMissing: map[string][]string{
   486  				"snap-d": {"acme/barname"},
   487  			},
   488  		},
   489  		{
   490  			snaps: []*snapasserts.InstalledSnap{
   491  				// required snaps from acme/fooname are not installed.
   492  				// covered by acme/barname validation-set
   493  				snapDrev99,
   494  				snapE},
   495  			expectedMissing: map[string][]string{
   496  				"snap-b": {"acme/fooname"},
   497  			},
   498  		},
   499  		{
   500  			snaps: []*snapasserts.InstalledSnap{
   501  				// covered by acme/fooname validation-set, required missing.
   502  				snapC,
   503  				// covered by acme/barname validation-set, required missing.
   504  				snapE},
   505  			expectedMissing: map[string][]string{
   506  				"snap-b": {"acme/fooname"},
   507  				"snap-d": {"acme/barname"},
   508  			},
   509  		},
   510  		// local snaps
   511  		{
   512  			snaps: []*snapasserts.InstalledSnap{
   513  				// covered by acme/fooname validation-set.
   514  				snapB,
   515  				// covered by acme/barname validation-set, local snap-d.
   516  				snapDlocal},
   517  			// all fine
   518  		},
   519  		{
   520  			snaps: []*snapasserts.InstalledSnap{
   521  				// covered by acme/fooname validation-set, snap-a is invalid.
   522  				snapAlocal,
   523  				snapB,
   524  				// covered by acme/barname validation-set.
   525  				snapD},
   526  			expectedInvalid: map[string][]string{
   527  				"snap-a": {"acme/booname", "acme/fooname"},
   528  			},
   529  		},
   530  		{
   531  			snaps: []*snapasserts.InstalledSnap{
   532  				// covered by acme/fooname validation-set, snap-b is wrong rev (local).
   533  				snapBlocal,
   534  				// covered by acme/barname validation-set.
   535  				snapD},
   536  			expectedWrongRev: map[string]map[snap.Revision][]string{
   537  				"snap-b": {
   538  					snap.R(3): {"acme/fooname"},
   539  				},
   540  			},
   541  		},
   542  	}
   543  
   544  	checkSets := func(snapsToValidationSets map[string][]string, vs map[string]*asserts.ValidationSet) {
   545  		for _, vsetKeys := range snapsToValidationSets {
   546  			for _, key := range vsetKeys {
   547  				vset, ok := vs[key]
   548  				c.Assert(ok, Equals, true)
   549  				c.Assert(vset.AccountID()+"/"+vset.Name(), Equals, key)
   550  			}
   551  		}
   552  	}
   553  
   554  	for i, tc := range tests {
   555  		err := valsets.CheckInstalledSnaps(tc.snaps)
   556  		if err == nil {
   557  			c.Assert(tc.expectedInvalid, IsNil)
   558  			c.Assert(tc.expectedMissing, IsNil)
   559  			c.Assert(tc.expectedWrongRev, IsNil)
   560  			continue
   561  		}
   562  		verr, ok := err.(*snapasserts.ValidationSetsValidationError)
   563  		c.Assert(ok, Equals, true, Commentf("#%d", i))
   564  		c.Assert(tc.expectedInvalid, DeepEquals, verr.InvalidSnaps, Commentf("#%d", i))
   565  		c.Assert(tc.expectedMissing, DeepEquals, verr.MissingSnaps, Commentf("#%d", i))
   566  		c.Assert(tc.expectedWrongRev, DeepEquals, verr.WrongRevisionSnaps, Commentf("#%d", i))
   567  		checkSets(verr.InvalidSnaps, verr.Sets)
   568  	}
   569  }
   570  
   571  func (s *validationSetsSuite) TestCheckInstalledSnapsErrorFormat(c *C) {
   572  	vs1 := assertstest.FakeAssertion(map[string]interface{}{
   573  		"type":         "validation-set",
   574  		"authority-id": "acme",
   575  		"series":       "16",
   576  		"account-id":   "acme",
   577  		"name":         "fooname",
   578  		"sequence":     "1",
   579  		"snaps": []interface{}{
   580  			map[string]interface{}{
   581  				"name":     "snap-a",
   582  				"id":       "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa",
   583  				"presence": "invalid",
   584  			},
   585  			map[string]interface{}{
   586  				"name":     "snap-b",
   587  				"id":       "mysnapbbbbbbbbbbbbbbbbbbbbbbbbbb",
   588  				"revision": "3",
   589  				"presence": "required",
   590  			},
   591  		},
   592  	}).(*asserts.ValidationSet)
   593  	vs2 := assertstest.FakeAssertion(map[string]interface{}{
   594  		"type":         "validation-set",
   595  		"authority-id": "acme",
   596  		"series":       "16",
   597  		"account-id":   "acme",
   598  		"name":         "barname",
   599  		"sequence":     "2",
   600  		"snaps": []interface{}{
   601  			map[string]interface{}{
   602  				"name":     "snap-b",
   603  				"id":       "mysnapbbbbbbbbbbbbbbbbbbbbbbbbbb",
   604  				"revision": "5",
   605  				"presence": "required",
   606  			},
   607  		},
   608  	}).(*asserts.ValidationSet)
   609  
   610  	valsets := snapasserts.NewValidationSets()
   611  	c.Assert(valsets.Add(vs1), IsNil)
   612  	c.Assert(valsets.Add(vs2), IsNil)
   613  
   614  	snapA := snapasserts.NewInstalledSnap("snap-a", "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa", snap.R(1))
   615  	snapBlocal := snapasserts.NewInstalledSnap("snap-b", "", snap.R("x3"))
   616  
   617  	tests := []struct {
   618  		snaps    []*snapasserts.InstalledSnap
   619  		errorMsg string
   620  	}{
   621  		{
   622  			nil,
   623  			"validation sets assertions are not met:\n" +
   624  				"- missing required snaps:\n" +
   625  				"  - snap-b \\(required by sets acme/barname,acme/fooname\\)",
   626  		},
   627  		{
   628  			[]*snapasserts.InstalledSnap{snapA},
   629  			"validation sets assertions are not met:\n" +
   630  				"- missing required snaps:\n" +
   631  				"  - snap-b \\(required by sets acme/barname,acme/fooname\\)\n" +
   632  				"- invalid snaps:\n" +
   633  				"  - snap-a \\(invalid for sets acme/fooname\\)",
   634  		},
   635  		{
   636  			[]*snapasserts.InstalledSnap{snapBlocal},
   637  			"validation sets assertions are not met:\n" +
   638  				"- snaps at wrong revisions:\n" +
   639  				"  - snap-b \\(required at revision 3 by sets acme/fooname, at revision 5 by sets acme/barname\\)",
   640  		},
   641  	}
   642  
   643  	for i, tc := range tests {
   644  		err := valsets.CheckInstalledSnaps(tc.snaps)
   645  		c.Assert(err, NotNil, Commentf("#%d", i))
   646  		c.Assert(err, ErrorMatches, tc.errorMsg, Commentf("#%d: ", i))
   647  	}
   648  }
   649  
   650  func (s *validationSetsSuite) TestSortByRevision(c *C) {
   651  	revs := []snap.Revision{snap.R(10), snap.R(4), snap.R(5), snap.R(-1)}
   652  
   653  	sort.Sort(snapasserts.ByRevision(revs))
   654  	c.Assert(revs, DeepEquals, []snap.Revision{snap.R(-1), snap.R(4), snap.R(5), snap.R(10)})
   655  }
   656  
   657  func (s *validationSetsSuite) TestCheckPresenceRequired(c *C) {
   658  	valset1 := assertstest.FakeAssertion(map[string]interface{}{
   659  		"type":         "validation-set",
   660  		"authority-id": "account-id",
   661  		"series":       "16",
   662  		"account-id":   "account-id",
   663  		"name":         "my-snap-ctl",
   664  		"sequence":     "1",
   665  		"snaps": []interface{}{
   666  			map[string]interface{}{
   667  				"name":     "my-snap",
   668  				"id":       "mysnapididididididididididididid",
   669  				"presence": "required",
   670  				"revision": "7",
   671  			},
   672  			map[string]interface{}{
   673  				"name":     "other-snap",
   674  				"id":       "123456ididididididididididididid",
   675  				"presence": "optional",
   676  			},
   677  		},
   678  	}).(*asserts.ValidationSet)
   679  
   680  	valset2 := assertstest.FakeAssertion(map[string]interface{}{
   681  		"type":         "validation-set",
   682  		"authority-id": "account-id",
   683  		"series":       "16",
   684  		"account-id":   "account-id",
   685  		"name":         "my-snap-ctl2",
   686  		"sequence":     "2",
   687  		"snaps": []interface{}{
   688  			map[string]interface{}{
   689  				"name":     "my-snap",
   690  				"id":       "mysnapididididididididididididid",
   691  				"presence": "required",
   692  				"revision": "7",
   693  			},
   694  			map[string]interface{}{
   695  				"name":     "other-snap",
   696  				"id":       "123456ididididididididididididid",
   697  				"presence": "invalid",
   698  			},
   699  		},
   700  	}).(*asserts.ValidationSet)
   701  
   702  	// my-snap required but no specific revision set.
   703  	valset3 := assertstest.FakeAssertion(map[string]interface{}{
   704  		"type":         "validation-set",
   705  		"authority-id": "account-id",
   706  		"series":       "16",
   707  		"account-id":   "account-id",
   708  		"name":         "my-snap-ctl3",
   709  		"sequence":     "1",
   710  		"snaps": []interface{}{
   711  			map[string]interface{}{
   712  				"name":     "my-snap",
   713  				"id":       "mysnapididididididididididididid",
   714  				"presence": "required",
   715  			},
   716  		},
   717  	}).(*asserts.ValidationSet)
   718  
   719  	valsets := snapasserts.NewValidationSets()
   720  
   721  	// no validation sets
   722  	vsKeys, _, err := valsets.CheckPresenceRequired(naming.Snap("my-snap"))
   723  	c.Assert(err, IsNil)
   724  	c.Check(vsKeys, HasLen, 0)
   725  
   726  	c.Assert(valsets.Add(valset1), IsNil)
   727  	c.Assert(valsets.Add(valset2), IsNil)
   728  	c.Assert(valsets.Add(valset3), IsNil)
   729  
   730  	// sanity
   731  	c.Assert(valsets.Conflict(), IsNil)
   732  
   733  	vsKeys, rev, err := valsets.CheckPresenceRequired(naming.Snap("my-snap"))
   734  	c.Assert(err, IsNil)
   735  	c.Check(rev, DeepEquals, snap.Revision{N: 7})
   736  	c.Check(vsKeys, DeepEquals, []string{"account-id/my-snap-ctl", "account-id/my-snap-ctl2", "account-id/my-snap-ctl3"})
   737  
   738  	vsKeys, rev, err = valsets.CheckPresenceRequired(naming.NewSnapRef("my-snap", "mysnapididididididididididididid"))
   739  	c.Assert(err, IsNil)
   740  	c.Check(rev, DeepEquals, snap.Revision{N: 7})
   741  	c.Check(vsKeys, DeepEquals, []string{"account-id/my-snap-ctl", "account-id/my-snap-ctl2", "account-id/my-snap-ctl3"})
   742  
   743  	// other-snap is not required
   744  	vsKeys, rev, err = valsets.CheckPresenceRequired(naming.Snap("other-snap"))
   745  	c.Assert(err, ErrorMatches, `unexpected presence "invalid" for snap "other-snap"`)
   746  	pr, ok := err.(*snapasserts.PresenceConstraintError)
   747  	c.Assert(ok, Equals, true)
   748  	c.Check(pr.SnapName, Equals, "other-snap")
   749  	c.Check(pr.Presence, Equals, asserts.PresenceInvalid)
   750  	c.Check(rev, DeepEquals, snap.Revision{N: 0})
   751  	c.Check(vsKeys, HasLen, 0)
   752  
   753  	// unknown snap is not required
   754  	vsKeys, rev, err = valsets.CheckPresenceRequired(naming.NewSnapRef("unknown-snap", "00000000idididididididididididid"))
   755  	c.Assert(err, IsNil)
   756  	c.Check(rev, DeepEquals, snap.Revision{N: 0})
   757  	c.Check(vsKeys, HasLen, 0)
   758  
   759  	// just one set, required but no revision specified
   760  	valsets = snapasserts.NewValidationSets()
   761  	c.Assert(valsets.Add(valset3), IsNil)
   762  	vsKeys, rev, err = valsets.CheckPresenceRequired(naming.Snap("my-snap"))
   763  	c.Assert(err, IsNil)
   764  	c.Check(rev, DeepEquals, snap.Revision{N: 0})
   765  	c.Check(vsKeys, DeepEquals, []string{"account-id/my-snap-ctl3"})
   766  }
   767  
   768  func (s *validationSetsSuite) TestIsPresenceInvalid(c *C) {
   769  	valset1 := assertstest.FakeAssertion(map[string]interface{}{
   770  		"type":         "validation-set",
   771  		"authority-id": "account-id",
   772  		"series":       "16",
   773  		"account-id":   "account-id",
   774  		"name":         "my-snap-ctl",
   775  		"sequence":     "1",
   776  		"snaps": []interface{}{
   777  			map[string]interface{}{
   778  				"name":     "my-snap",
   779  				"id":       "mysnapididididididididididididid",
   780  				"presence": "invalid",
   781  			},
   782  			map[string]interface{}{
   783  				"name":     "other-snap",
   784  				"id":       "123456ididididididididididididid",
   785  				"presence": "optional",
   786  			},
   787  		},
   788  	}).(*asserts.ValidationSet)
   789  
   790  	valset2 := assertstest.FakeAssertion(map[string]interface{}{
   791  		"type":         "validation-set",
   792  		"authority-id": "account-id",
   793  		"series":       "16",
   794  		"account-id":   "account-id",
   795  		"name":         "my-snap-ctl2",
   796  		"sequence":     "2",
   797  		"snaps": []interface{}{
   798  			map[string]interface{}{
   799  				"name":     "my-snap",
   800  				"id":       "mysnapididididididididididididid",
   801  				"presence": "invalid",
   802  			},
   803  		},
   804  	}).(*asserts.ValidationSet)
   805  
   806  	valsets := snapasserts.NewValidationSets()
   807  
   808  	// no validation sets
   809  	vsKeys, err := valsets.CheckPresenceInvalid(naming.Snap("my-snap"))
   810  	c.Assert(err, IsNil)
   811  	c.Check(vsKeys, HasLen, 0)
   812  
   813  	c.Assert(valsets.Add(valset1), IsNil)
   814  	c.Assert(valsets.Add(valset2), IsNil)
   815  
   816  	// sanity
   817  	c.Assert(valsets.Conflict(), IsNil)
   818  
   819  	// invalid in two sets
   820  	vsKeys, err = valsets.CheckPresenceInvalid(naming.Snap("my-snap"))
   821  	c.Assert(err, IsNil)
   822  	c.Check(vsKeys, DeepEquals, []string{"account-id/my-snap-ctl", "account-id/my-snap-ctl2"})
   823  
   824  	vsKeys, err = valsets.CheckPresenceInvalid(naming.NewSnapRef("my-snap", "mysnapididididididididididididid"))
   825  	c.Assert(err, IsNil)
   826  	c.Check(vsKeys, DeepEquals, []string{"account-id/my-snap-ctl", "account-id/my-snap-ctl2"})
   827  
   828  	// other-snap isn't invalid
   829  	vsKeys, err = valsets.CheckPresenceInvalid(naming.Snap("other-snap"))
   830  	c.Assert(err, ErrorMatches, `unexpected presence "optional" for snap "other-snap"`)
   831  	pr, ok := err.(*snapasserts.PresenceConstraintError)
   832  	c.Assert(ok, Equals, true)
   833  	c.Check(pr.SnapName, Equals, "other-snap")
   834  	c.Check(pr.Presence, Equals, asserts.PresenceOptional)
   835  	c.Check(vsKeys, HasLen, 0)
   836  
   837  	vsKeys, err = valsets.CheckPresenceInvalid(naming.NewSnapRef("other-snap", "123456ididididididididididididid"))
   838  	c.Assert(err, ErrorMatches, `unexpected presence "optional" for snap "other-snap"`)
   839  	c.Check(vsKeys, HasLen, 0)
   840  
   841  	// unknown snap isn't invalid
   842  	vsKeys, err = valsets.CheckPresenceInvalid(naming.NewSnapRef("unknown-snap", "00000000idididididididididididid"))
   843  	c.Assert(err, IsNil)
   844  	c.Check(vsKeys, HasLen, 0)
   845  }