github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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  )
    33  
    34  type validationSetsSuite struct{}
    35  
    36  var _ = Suite(&validationSetsSuite{})
    37  
    38  func (s *validationSetsSuite) TestAddFromSameSequence(c *C) {
    39  	mySnapAt7Valset := assertstest.FakeAssertion(map[string]interface{}{
    40  		"type":         "validation-set",
    41  		"authority-id": "account-id",
    42  		"series":       "16",
    43  		"account-id":   "account-id",
    44  		"name":         "my-snap-ctl",
    45  		"sequence":     "1",
    46  		"snaps": []interface{}{
    47  			map[string]interface{}{
    48  				"name":     "my-snap",
    49  				"id":       "mysnapididididididididididididid",
    50  				"presence": "required",
    51  				"revision": "7",
    52  			},
    53  		},
    54  	}).(*asserts.ValidationSet)
    55  
    56  	mySnapAt8Valset := assertstest.FakeAssertion(map[string]interface{}{
    57  		"type":         "validation-set",
    58  		"authority-id": "account-id",
    59  		"series":       "16",
    60  		"account-id":   "account-id",
    61  		"name":         "my-snap-ctl",
    62  		"sequence":     "2",
    63  		"snaps": []interface{}{
    64  			map[string]interface{}{
    65  				"name":     "my-snap",
    66  				"id":       "mysnapididididididididididididid",
    67  				"presence": "required",
    68  				"revision": "8",
    69  			},
    70  		},
    71  	}).(*asserts.ValidationSet)
    72  
    73  	valsets := snapasserts.NewValidationSets()
    74  	err := valsets.Add(mySnapAt7Valset)
    75  	c.Assert(err, IsNil)
    76  	err = valsets.Add(mySnapAt8Valset)
    77  	c.Check(err, ErrorMatches, `cannot add a second validation-set under "account-id/my-snap-ctl"`)
    78  }
    79  
    80  func (s *validationSetsSuite) TestIntersections(c *C) {
    81  	mySnapAt7Valset := assertstest.FakeAssertion(map[string]interface{}{
    82  		"type":         "validation-set",
    83  		"authority-id": "account-id",
    84  		"series":       "16",
    85  		"account-id":   "account-id",
    86  		"name":         "my-snap-ctl",
    87  		"sequence":     "1",
    88  		"snaps": []interface{}{
    89  			map[string]interface{}{
    90  				"name":     "my-snap",
    91  				"id":       "mysnapididididididididididididid",
    92  				"presence": "required",
    93  				"revision": "7",
    94  			},
    95  		},
    96  	}).(*asserts.ValidationSet)
    97  
    98  	mySnapAt7Valset2 := assertstest.FakeAssertion(map[string]interface{}{
    99  		"type":         "validation-set",
   100  		"authority-id": "account-id",
   101  		"series":       "16",
   102  		"account-id":   "account-id",
   103  		"name":         "my-snap-ctl2",
   104  		"sequence":     "2",
   105  		"snaps": []interface{}{
   106  			map[string]interface{}{
   107  				"name":     "my-snap",
   108  				"id":       "mysnapididididididididididididid",
   109  				"presence": "required",
   110  				"revision": "7",
   111  			},
   112  		},
   113  	}).(*asserts.ValidationSet)
   114  
   115  	mySnapAt8Valset := assertstest.FakeAssertion(map[string]interface{}{
   116  		"type":         "validation-set",
   117  		"authority-id": "account-id",
   118  		"series":       "16",
   119  		"account-id":   "account-id",
   120  		"name":         "my-snap-ctl-other",
   121  		"sequence":     "1",
   122  		"snaps": []interface{}{
   123  			map[string]interface{}{
   124  				"name":     "my-snap",
   125  				"id":       "mysnapididididididididididididid",
   126  				"presence": "required",
   127  				"revision": "8",
   128  			},
   129  		},
   130  	}).(*asserts.ValidationSet)
   131  
   132  	mySnapAt8OptValset := assertstest.FakeAssertion(map[string]interface{}{
   133  		"type":         "validation-set",
   134  		"authority-id": "account-id",
   135  		"series":       "16",
   136  		"account-id":   "account-id",
   137  		"name":         "my-snap-ctl-opt",
   138  		"sequence":     "1",
   139  		"snaps": []interface{}{
   140  			map[string]interface{}{
   141  				"name":     "my-snap",
   142  				"id":       "mysnapididididididididididididid",
   143  				"presence": "optional",
   144  				"revision": "8",
   145  			},
   146  		},
   147  	}).(*asserts.ValidationSet)
   148  
   149  	mySnapInvalidValset := assertstest.FakeAssertion(map[string]interface{}{
   150  		"type":         "validation-set",
   151  		"authority-id": "account-id",
   152  		"series":       "16",
   153  		"account-id":   "account-id",
   154  		"name":         "my-snap-ctl-inv",
   155  		"sequence":     "1",
   156  		"snaps": []interface{}{
   157  			map[string]interface{}{
   158  				"name":     "my-snap",
   159  				"id":       "mysnapididididididididididididid",
   160  				"presence": "invalid",
   161  			},
   162  		},
   163  	}).(*asserts.ValidationSet)
   164  
   165  	mySnapAt7OptValset := assertstest.FakeAssertion(map[string]interface{}{
   166  		"type":         "validation-set",
   167  		"authority-id": "account-id",
   168  		"series":       "16",
   169  		"account-id":   "account-id",
   170  		"name":         "my-snap-ctl-opt2",
   171  		"sequence":     "1",
   172  		"snaps": []interface{}{
   173  			map[string]interface{}{
   174  				"name":     "my-snap",
   175  				"id":       "mysnapididididididididididididid",
   176  				"presence": "optional",
   177  				"revision": "7",
   178  			},
   179  		},
   180  	}).(*asserts.ValidationSet)
   181  
   182  	mySnapReqValset := assertstest.FakeAssertion(map[string]interface{}{
   183  		"type":         "validation-set",
   184  		"authority-id": "account-id",
   185  		"series":       "16",
   186  		"account-id":   "account-id",
   187  		"name":         "my-snap-ctl-req-only",
   188  		"sequence":     "1",
   189  		"snaps": []interface{}{
   190  			map[string]interface{}{
   191  				"name":     "my-snap",
   192  				"id":       "mysnapididididididididididididid",
   193  				"presence": "required",
   194  			},
   195  		},
   196  	}).(*asserts.ValidationSet)
   197  
   198  	mySnapOptValset := assertstest.FakeAssertion(map[string]interface{}{
   199  		"type":         "validation-set",
   200  		"authority-id": "account-id",
   201  		"series":       "16",
   202  		"account-id":   "account-id",
   203  		"name":         "my-snap-ctl-opt-only",
   204  		"sequence":     "1",
   205  		"snaps": []interface{}{
   206  			map[string]interface{}{
   207  				"name":     "my-snap",
   208  				"id":       "mysnapididididididididididididid",
   209  				"presence": "optional",
   210  			},
   211  		},
   212  	}).(*asserts.ValidationSet)
   213  
   214  	tests := []struct {
   215  		sets        []*asserts.ValidationSet
   216  		conflictErr string
   217  	}{
   218  		{[]*asserts.ValidationSet{mySnapAt7Valset}, ""},
   219  		{[]*asserts.ValidationSet{mySnapAt7Valset, mySnapAt7Valset2}, ""},
   220  		{[]*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\)`},
   221  		{[]*asserts.ValidationSet{mySnapAt8Valset, mySnapAt8OptValset}, ""},
   222  		{[]*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\)`},
   223  		{[]*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\)`},
   224  		{[]*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\)`},
   225  		{[]*asserts.ValidationSet{mySnapAt8OptValset, mySnapInvalidValset}, ""},
   226  		{[]*asserts.ValidationSet{mySnapInvalidValset, mySnapAt8OptValset}, ""},
   227  		{[]*asserts.ValidationSet{mySnapAt7OptValset, mySnapAt8OptValset}, ""}, // no conflict but interpreted as invalid
   228  		{[]*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\)`},
   229  		{[]*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\)`},
   230  		{[]*asserts.ValidationSet{mySnapAt7Valset, mySnapReqValset}, ""},
   231  		{[]*asserts.ValidationSet{mySnapReqValset, mySnapAt7Valset}, ""},
   232  		{[]*asserts.ValidationSet{mySnapAt8OptValset, mySnapReqValset}, ""},
   233  		{[]*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\)`},
   234  		{[]*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\)`},
   235  		{[]*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\)`},
   236  		{[]*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\)`},
   237  		{[]*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\)`},
   238  		{[]*asserts.ValidationSet{mySnapAt7Valset, mySnapOptValset}, ""},
   239  		{[]*asserts.ValidationSet{mySnapOptValset, mySnapAt7Valset}, ""},
   240  		{[]*asserts.ValidationSet{mySnapAt8OptValset, mySnapOptValset}, ""},
   241  		{[]*asserts.ValidationSet{mySnapAt8OptValset, mySnapOptValset, mySnapAt7OptValset}, ""}, // no conflict but interpreted as invalid
   242  		{[]*asserts.ValidationSet{mySnapInvalidValset, mySnapOptValset}, ""},
   243  		{[]*asserts.ValidationSet{mySnapOptValset, mySnapInvalidValset}, ""},
   244  		{[]*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\)`},
   245  	}
   246  
   247  	for _, t := range tests {
   248  		valsets := snapasserts.NewValidationSets()
   249  		cSets := make(map[string]*asserts.ValidationSet)
   250  		for _, valset := range t.sets {
   251  			err := valsets.Add(valset)
   252  			c.Assert(err, IsNil)
   253  			// mySnapOptValset never influcens an outcome
   254  			if valset != mySnapOptValset {
   255  				cSets[fmt.Sprintf("%s/%s", valset.AccountID(), valset.Name())] = valset
   256  			}
   257  		}
   258  		err := valsets.Conflict()
   259  		if t.conflictErr == "" {
   260  			c.Check(err, IsNil)
   261  		} else {
   262  			c.Check(err, ErrorMatches, t.conflictErr)
   263  			ce := err.(*snapasserts.ValidationSetsConflictError)
   264  			c.Check(ce.Sets, DeepEquals, cSets)
   265  		}
   266  	}
   267  }
   268  
   269  func (s *validationSetsSuite) TestCheckInstalledSnapsNoValidationSets(c *C) {
   270  	valsets := snapasserts.NewValidationSets()
   271  	snaps := []*snapasserts.InstalledSnap{
   272  		snapasserts.NewInstalledSnap("snap-a", "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa", snap.R(1)),
   273  	}
   274  	err := valsets.CheckInstalledSnaps(snaps)
   275  	c.Assert(err, IsNil)
   276  }
   277  
   278  func (s *validationSetsSuite) TestCheckInstalledSnaps(c *C) {
   279  	// require: snapB rev 3, snapC rev 2.
   280  	// invalid: snapA
   281  	vs1 := assertstest.FakeAssertion(map[string]interface{}{
   282  		"type":         "validation-set",
   283  		"authority-id": "acme",
   284  		"series":       "16",
   285  		"account-id":   "acme",
   286  		"name":         "fooname",
   287  		"sequence":     "1",
   288  		"snaps": []interface{}{
   289  			map[string]interface{}{
   290  				"name":     "snap-a",
   291  				"id":       "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa",
   292  				"presence": "invalid",
   293  			},
   294  			map[string]interface{}{
   295  				"name":     "snap-b",
   296  				"id":       "mysnapbbbbbbbbbbbbbbbbbbbbbbbbbb",
   297  				"revision": "3",
   298  				"presence": "required",
   299  			},
   300  			map[string]interface{}{
   301  				"name":     "snap-c",
   302  				"id":       "mysnapcccccccccccccccccccccccccc",
   303  				"revision": "2",
   304  				"presence": "optional",
   305  			},
   306  		},
   307  	}).(*asserts.ValidationSet)
   308  
   309  	// require: snapD any rev
   310  	// optional: snapE any rev
   311  	vs2 := assertstest.FakeAssertion(map[string]interface{}{
   312  		"type":         "validation-set",
   313  		"authority-id": "acme",
   314  		"series":       "16",
   315  		"account-id":   "acme",
   316  		"name":         "barname",
   317  		"sequence":     "3",
   318  		"snaps": []interface{}{
   319  			map[string]interface{}{
   320  				"name":     "snap-d",
   321  				"id":       "mysnapdddddddddddddddddddddddddd",
   322  				"presence": "required",
   323  			},
   324  			map[string]interface{}{
   325  				"name":     "snap-e",
   326  				"id":       "mysnapeeeeeeeeeeeeeeeeeeeeeeeeee",
   327  				"presence": "optional",
   328  			},
   329  		},
   330  	}).(*asserts.ValidationSet)
   331  
   332  	// optional: snapE any rev
   333  	// note: since it only has an optional snap, acme/bazname is not expected
   334  	// not be invalid by any of the checks.
   335  	vs3 := assertstest.FakeAssertion(map[string]interface{}{
   336  		"type":         "validation-set",
   337  		"authority-id": "acme",
   338  		"series":       "16",
   339  		"account-id":   "acme",
   340  		"name":         "bazname",
   341  		"sequence":     "2",
   342  		"snaps": []interface{}{
   343  			map[string]interface{}{
   344  				"name":     "snap-e",
   345  				"id":       "mysnapeeeeeeeeeeeeeeeeeeeeeeeeee",
   346  				"presence": "optional",
   347  			},
   348  		},
   349  	}).(*asserts.ValidationSet)
   350  
   351  	// invalid: snapA
   352  	vs4 := assertstest.FakeAssertion(map[string]interface{}{
   353  		"type":         "validation-set",
   354  		"authority-id": "acme",
   355  		"series":       "16",
   356  		"account-id":   "acme",
   357  		"name":         "booname",
   358  		"sequence":     "1",
   359  		"snaps": []interface{}{
   360  			map[string]interface{}{
   361  				"name":     "snap-a",
   362  				"id":       "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa",
   363  				"presence": "invalid",
   364  			},
   365  		},
   366  	}).(*asserts.ValidationSet)
   367  
   368  	valsets := snapasserts.NewValidationSets()
   369  	c.Assert(valsets.Add(vs1), IsNil)
   370  	c.Assert(valsets.Add(vs2), IsNil)
   371  	c.Assert(valsets.Add(vs3), IsNil)
   372  	c.Assert(valsets.Add(vs4), IsNil)
   373  
   374  	snapA := snapasserts.NewInstalledSnap("snap-a", "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa", snap.R(1))
   375  	snapAlocal := snapasserts.NewInstalledSnap("snap-a", "", snap.R("x2"))
   376  	snapB := snapasserts.NewInstalledSnap("snap-b", "mysnapbbbbbbbbbbbbbbbbbbbbbbbbbb", snap.R(3))
   377  	snapBinvRev := snapasserts.NewInstalledSnap("snap-b", "mysnapbbbbbbbbbbbbbbbbbbbbbbbbbb", snap.R(8))
   378  	snapBlocal := snapasserts.NewInstalledSnap("snap-b", "", snap.R("x3"))
   379  	snapC := snapasserts.NewInstalledSnap("snap-c", "mysnapcccccccccccccccccccccccccc", snap.R(2))
   380  	snapCinvRev := snapasserts.NewInstalledSnap("snap-c", "mysnapcccccccccccccccccccccccccc", snap.R(99))
   381  	snapD := snapasserts.NewInstalledSnap("snap-d", "mysnapdddddddddddddddddddddddddd", snap.R(2))
   382  	snapDrev99 := snapasserts.NewInstalledSnap("snap-d", "mysnapdddddddddddddddddddddddddd", snap.R(99))
   383  	snapDlocal := snapasserts.NewInstalledSnap("snap-d", "", snap.R("x3"))
   384  	snapE := snapasserts.NewInstalledSnap("snap-e", "mysnapeeeeeeeeeeeeeeeeeeeeeeeeee", snap.R(2))
   385  	// extra snap, not referenced by any validation set
   386  	snapZ := snapasserts.NewInstalledSnap("snap-z", "mysnapzzzzzzzzzzzzzzzzzzzzzzzzzz", snap.R(1))
   387  
   388  	tests := []struct {
   389  		snaps            []*snapasserts.InstalledSnap
   390  		expectedInvalid  map[string][]string
   391  		expectedMissing  map[string][]string
   392  		expectedWrongRev map[string]map[snap.Revision][]string
   393  	}{
   394  		{
   395  			// required snaps not installed
   396  			snaps: nil,
   397  			expectedMissing: map[string][]string{
   398  				"snap-b": {"acme/fooname"},
   399  				"snap-d": {"acme/barname"},
   400  			},
   401  		},
   402  		{
   403  			// required snaps not installed
   404  			snaps: []*snapasserts.InstalledSnap{
   405  				snapZ,
   406  			},
   407  			expectedMissing: map[string][]string{
   408  				"snap-b": {"acme/fooname"},
   409  				"snap-d": {"acme/barname"},
   410  			},
   411  		},
   412  		{
   413  			snaps: []*snapasserts.InstalledSnap{
   414  				// covered by acme/fooname validation-set
   415  				snapB,
   416  				// covered by acme/barname validation-set. snap-e not installed but optional
   417  				snapDrev99},
   418  			// ale fine
   419  		},
   420  		{
   421  			snaps: []*snapasserts.InstalledSnap{
   422  				// covered by acme/fooname validation-set and acme/booname, snap-a presence is invalid
   423  				snapA,
   424  				snapB,
   425  				// covered by acme/barname validation-set. snap-e not installed but optional
   426  				snapDrev99},
   427  			expectedInvalid: map[string][]string{
   428  				"snap-a": {"acme/booname", "acme/fooname"},
   429  			},
   430  		},
   431  		{
   432  			snaps: []*snapasserts.InstalledSnap{
   433  				// covered by acme/fooname and acme/booname validation-sets, snapB missing, snap-a presence is invalid
   434  				snapA,
   435  				// covered by acme/barname validation-set. snap-e not installed but optional
   436  				snapDrev99},
   437  			expectedInvalid: map[string][]string{
   438  				"snap-a": {"acme/booname", "acme/fooname"},
   439  			},
   440  			expectedMissing: map[string][]string{
   441  				"snap-b": {"acme/fooname"},
   442  			},
   443  		},
   444  		{
   445  			snaps: []*snapasserts.InstalledSnap{
   446  				// covered by acme/fooname validation-set
   447  				snapB,
   448  				snapC,
   449  				// covered by acme/barname validation-set. snap-e not installed but optional
   450  				snapD},
   451  			// ale fine
   452  		},
   453  		{
   454  			snaps: []*snapasserts.InstalledSnap{
   455  				// covered by acme/fooname validation-set, snap-c optional but wrong revision
   456  				snapB,
   457  				snapCinvRev,
   458  				// covered by acme/barname validation-set. snap-e not installed but optional
   459  				snapD},
   460  			expectedWrongRev: map[string]map[snap.Revision][]string{
   461  				"snap-c": {
   462  					snap.R(2): {"acme/fooname"},
   463  				},
   464  			},
   465  		},
   466  		{
   467  			snaps: []*snapasserts.InstalledSnap{
   468  				// covered by acme/fooname validation-set but wrong revision
   469  				snapBinvRev,
   470  				// covered by acme/barname validation-set.
   471  				snapD},
   472  			expectedWrongRev: map[string]map[snap.Revision][]string{
   473  				"snap-b": {
   474  					snap.R(3): {"acme/fooname"},
   475  				},
   476  			},
   477  		},
   478  		{
   479  			snaps: []*snapasserts.InstalledSnap{
   480  				// covered by acme/fooname validation-set
   481  				snapB,
   482  				// covered by acme/barname validation-set. snap-d not installed.
   483  				snapE},
   484  			expectedMissing: map[string][]string{
   485  				"snap-d": {"acme/barname"},
   486  			},
   487  		},
   488  		{
   489  			snaps: []*snapasserts.InstalledSnap{
   490  				// required snaps from acme/fooname are not installed.
   491  				// covered by acme/barname validation-set
   492  				snapDrev99,
   493  				snapE},
   494  			expectedMissing: map[string][]string{
   495  				"snap-b": {"acme/fooname"},
   496  			},
   497  		},
   498  		{
   499  			snaps: []*snapasserts.InstalledSnap{
   500  				// covered by acme/fooname validation-set, required missing.
   501  				snapC,
   502  				// covered by acme/barname validation-set, required missing.
   503  				snapE},
   504  			expectedMissing: map[string][]string{
   505  				"snap-b": {"acme/fooname"},
   506  				"snap-d": {"acme/barname"},
   507  			},
   508  		},
   509  		// local snaps
   510  		{
   511  			snaps: []*snapasserts.InstalledSnap{
   512  				// covered by acme/fooname validation-set.
   513  				snapB,
   514  				// covered by acme/barname validation-set, local snap-d.
   515  				snapDlocal},
   516  			// all fine
   517  		},
   518  		{
   519  			snaps: []*snapasserts.InstalledSnap{
   520  				// covered by acme/fooname validation-set, snap-a is invalid.
   521  				snapAlocal,
   522  				snapB,
   523  				// covered by acme/barname validation-set.
   524  				snapD},
   525  			expectedInvalid: map[string][]string{
   526  				"snap-a": {"acme/booname", "acme/fooname"},
   527  			},
   528  		},
   529  		{
   530  			snaps: []*snapasserts.InstalledSnap{
   531  				// covered by acme/fooname validation-set, snap-b is wrong rev (local).
   532  				snapBlocal,
   533  				// covered by acme/barname validation-set.
   534  				snapD},
   535  			expectedWrongRev: map[string]map[snap.Revision][]string{
   536  				"snap-b": {
   537  					snap.R(3): {"acme/fooname"},
   538  				},
   539  			},
   540  		},
   541  	}
   542  
   543  	checkSets := func(snapsToValidationSets map[string][]string, vs map[string]*asserts.ValidationSet) {
   544  		for _, vsetKeys := range snapsToValidationSets {
   545  			for _, key := range vsetKeys {
   546  				vset, ok := vs[key]
   547  				c.Assert(ok, Equals, true)
   548  				c.Assert(vset.AccountID()+"/"+vset.Name(), Equals, key)
   549  			}
   550  		}
   551  	}
   552  
   553  	for i, tc := range tests {
   554  		err := valsets.CheckInstalledSnaps(tc.snaps)
   555  		if err == nil {
   556  			c.Assert(tc.expectedInvalid, IsNil)
   557  			c.Assert(tc.expectedMissing, IsNil)
   558  			c.Assert(tc.expectedWrongRev, IsNil)
   559  			continue
   560  		}
   561  		verr, ok := err.(*snapasserts.ValidationSetsValidationError)
   562  		c.Assert(ok, Equals, true, Commentf("#%d", i))
   563  		c.Assert(tc.expectedInvalid, DeepEquals, verr.InvalidSnaps, Commentf("#%d", i))
   564  		c.Assert(tc.expectedMissing, DeepEquals, verr.MissingSnaps, Commentf("#%d", i))
   565  		c.Assert(tc.expectedWrongRev, DeepEquals, verr.WrongRevisionSnaps, Commentf("#%d", i))
   566  		checkSets(verr.InvalidSnaps, verr.Sets)
   567  	}
   568  }
   569  
   570  func (s *validationSetsSuite) TestCheckInstalledSnapsErrorFormat(c *C) {
   571  	vs1 := assertstest.FakeAssertion(map[string]interface{}{
   572  		"type":         "validation-set",
   573  		"authority-id": "acme",
   574  		"series":       "16",
   575  		"account-id":   "acme",
   576  		"name":         "fooname",
   577  		"sequence":     "1",
   578  		"snaps": []interface{}{
   579  			map[string]interface{}{
   580  				"name":     "snap-a",
   581  				"id":       "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa",
   582  				"presence": "invalid",
   583  			},
   584  			map[string]interface{}{
   585  				"name":     "snap-b",
   586  				"id":       "mysnapbbbbbbbbbbbbbbbbbbbbbbbbbb",
   587  				"revision": "3",
   588  				"presence": "required",
   589  			},
   590  		},
   591  	}).(*asserts.ValidationSet)
   592  	vs2 := assertstest.FakeAssertion(map[string]interface{}{
   593  		"type":         "validation-set",
   594  		"authority-id": "acme",
   595  		"series":       "16",
   596  		"account-id":   "acme",
   597  		"name":         "barname",
   598  		"sequence":     "2",
   599  		"snaps": []interface{}{
   600  			map[string]interface{}{
   601  				"name":     "snap-b",
   602  				"id":       "mysnapbbbbbbbbbbbbbbbbbbbbbbbbbb",
   603  				"revision": "5",
   604  				"presence": "required",
   605  			},
   606  		},
   607  	}).(*asserts.ValidationSet)
   608  
   609  	valsets := snapasserts.NewValidationSets()
   610  	c.Assert(valsets.Add(vs1), IsNil)
   611  	c.Assert(valsets.Add(vs2), IsNil)
   612  
   613  	snapA := snapasserts.NewInstalledSnap("snap-a", "mysnapaaaaaaaaaaaaaaaaaaaaaaaaaa", snap.R(1))
   614  	snapBlocal := snapasserts.NewInstalledSnap("snap-b", "", snap.R("x3"))
   615  
   616  	tests := []struct {
   617  		snaps    []*snapasserts.InstalledSnap
   618  		errorMsg string
   619  	}{
   620  		{
   621  			nil,
   622  			"validation sets assertions are not met:\n" +
   623  				"- missing required snaps:\n" +
   624  				"  - snap-b \\(required by sets acme/barname,acme/fooname\\)",
   625  		},
   626  		{
   627  			[]*snapasserts.InstalledSnap{snapA},
   628  			"validation sets assertions are not met:\n" +
   629  				"- missing required snaps:\n" +
   630  				"  - snap-b \\(required by sets acme/barname,acme/fooname\\)\n" +
   631  				"- invalid snaps:\n" +
   632  				"  - snap-a \\(invalid for sets acme/fooname\\)",
   633  		},
   634  		{
   635  			[]*snapasserts.InstalledSnap{snapBlocal},
   636  			"validation sets assertions are not met:\n" +
   637  				"- snaps at wrong revisions:\n" +
   638  				"  - snap-b \\(required at revision 3 by sets acme/fooname, at revision 5 by sets acme/barname\\)",
   639  		},
   640  	}
   641  
   642  	for i, tc := range tests {
   643  		err := valsets.CheckInstalledSnaps(tc.snaps)
   644  		c.Assert(err, NotNil, Commentf("#%d", i))
   645  		c.Assert(err, ErrorMatches, tc.errorMsg, Commentf("#%d: ", i))
   646  	}
   647  }
   648  
   649  func (s *validationSetsSuite) TestSortByRevision(c *C) {
   650  	revs := []snap.Revision{snap.R(10), snap.R(4), snap.R(5), snap.R(-1)}
   651  
   652  	sort.Sort(snapasserts.ByRevision(revs))
   653  	c.Assert(revs, DeepEquals, []snap.Revision{snap.R(-1), snap.R(4), snap.R(5), snap.R(10)})
   654  }