github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/store/store_action_fetch_assertions_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-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 store_test
    21  
    22  import (
    23  	"encoding/json"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"net/http"
    27  	"net/http/httptest"
    28  	"net/url"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/asserts"
    33  	"github.com/snapcore/snapd/release"
    34  	"github.com/snapcore/snapd/store"
    35  )
    36  
    37  type storeActionFetchAssertionsSuite struct {
    38  	baseStoreSuite
    39  }
    40  
    41  var _ = Suite(&storeActionFetchAssertionsSuite{})
    42  
    43  type testAssertQuery struct {
    44  	toResolve    map[asserts.Grouping][]*asserts.AtRevision
    45  	toResolveSeq map[asserts.Grouping][]*asserts.AtSequence
    46  	errors       map[string]error
    47  }
    48  
    49  func (q *testAssertQuery) ToResolve() (map[asserts.Grouping][]*asserts.AtRevision, map[asserts.Grouping][]*asserts.AtSequence, error) {
    50  	return q.toResolve, q.toResolveSeq, nil
    51  }
    52  
    53  func (q *testAssertQuery) addError(e error, u string) {
    54  	if q.errors == nil {
    55  		q.errors = make(map[string]error)
    56  	}
    57  	q.errors[u] = e
    58  }
    59  
    60  func (q *testAssertQuery) AddError(e error, ref *asserts.Ref) error {
    61  	q.addError(e, ref.Unique())
    62  	return nil
    63  }
    64  
    65  func (q *testAssertQuery) AddSequenceError(e error, atSeq *asserts.AtSequence) error {
    66  	q.addError(e, atSeq.Unique())
    67  	return nil
    68  }
    69  
    70  func (q *testAssertQuery) AddGroupingError(e error, grouping asserts.Grouping) error {
    71  	q.addError(e, fmt.Sprintf("{%s}", grouping))
    72  	return nil
    73  }
    74  
    75  func (s *storeActionFetchAssertionsSuite) TestFetch(c *C) {
    76  	restore := release.MockOnClassic(false)
    77  	defer restore()
    78  
    79  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    80  		assertRequest(c, r, "POST", snapActionPath)
    81  		// check device authorization is set, implicitly checking doRequest was used
    82  		c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
    83  
    84  		jsonReq, err := ioutil.ReadAll(r.Body)
    85  		c.Assert(err, IsNil)
    86  		var req struct {
    87  			Context []map[string]interface{} `json:"context"`
    88  			Actions []map[string]interface{} `json:"actions"`
    89  
    90  			AssertionMaxFormats map[string]int `json:"assertion-max-formats"`
    91  		}
    92  
    93  		err = json.Unmarshal(jsonReq, &req)
    94  		c.Assert(err, IsNil)
    95  
    96  		c.Assert(req.Context, HasLen, 0)
    97  		c.Assert(req.Actions, HasLen, 1)
    98  		expectedAction := map[string]interface{}{
    99  			"action": "fetch-assertions",
   100  			"key":    "g1",
   101  			"assertions": []interface{}{
   102  				map[string]interface{}{
   103  					"type": "snap-declaration",
   104  					"primary-key": []interface{}{
   105  						"16",
   106  						"iEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5",
   107  					},
   108  				},
   109  			},
   110  		}
   111  		c.Assert(req.Actions[0], DeepEquals, expectedAction)
   112  
   113  		c.Assert(req.AssertionMaxFormats, DeepEquals, asserts.MaxSupportedFormats(1))
   114  
   115  		fmt.Fprintf(w, `{
   116    "results": [{
   117       "result": "fetch-assertions",
   118       "key": "g1",
   119       "assertion-stream-urls": [
   120          "https://api.snapcraft.io/v2/assertions/snap-declaration/16/iEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5"
   121        ]
   122       }
   123     ]
   124  }`)
   125  	}))
   126  
   127  	c.Assert(mockServer, NotNil)
   128  	defer mockServer.Close()
   129  
   130  	mockServerURL, _ := url.Parse(mockServer.URL)
   131  	cfg := store.Config{
   132  		StoreBaseURL: mockServerURL,
   133  	}
   134  	dauthCtx := &testDauthContext{c: c, device: s.device}
   135  	sto := store.New(&cfg, dauthCtx)
   136  
   137  	assertq := &testAssertQuery{
   138  		toResolve: map[asserts.Grouping][]*asserts.AtRevision{
   139  			asserts.Grouping("g1"): {{
   140  				Ref: asserts.Ref{
   141  					Type: asserts.SnapDeclarationType,
   142  					PrimaryKey: []string{
   143  						"16",
   144  						"iEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5",
   145  					},
   146  				},
   147  				Revision: asserts.RevisionNotKnown,
   148  			}},
   149  		},
   150  	}
   151  
   152  	results, aresults, err := sto.SnapAction(s.ctx, nil,
   153  		nil, assertq, nil, nil)
   154  	c.Assert(err, IsNil)
   155  	c.Check(results, HasLen, 0)
   156  	c.Check(aresults, HasLen, 1)
   157  	c.Check(aresults[0].Grouping, Equals, asserts.Grouping("g1"))
   158  	c.Check(aresults[0].StreamURLs, DeepEquals, []string{
   159  		"https://api.snapcraft.io/v2/assertions/snap-declaration/16/iEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5",
   160  	})
   161  }
   162  
   163  func (s *storeActionFetchAssertionsSuite) TestUpdateIfNewerThan(c *C) {
   164  	restore := release.MockOnClassic(false)
   165  	defer restore()
   166  
   167  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   168  		assertRequest(c, r, "POST", snapActionPath)
   169  		// check device authorization is set, implicitly checking doRequest was used
   170  		c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
   171  
   172  		jsonReq, err := ioutil.ReadAll(r.Body)
   173  		c.Assert(err, IsNil)
   174  		var req struct {
   175  			Context []map[string]interface{} `json:"context"`
   176  			Actions []map[string]interface{} `json:"actions"`
   177  
   178  			AssertionMaxFormats map[string]int `json:"assertion-max-formats"`
   179  		}
   180  
   181  		err = json.Unmarshal(jsonReq, &req)
   182  		c.Assert(err, IsNil)
   183  
   184  		c.Assert(req.Context, HasLen, 0)
   185  		c.Assert(req.Actions, HasLen, 2)
   186  		expectedAction1 := map[string]interface{}{
   187  			"action": "fetch-assertions",
   188  			"key":    "g1",
   189  			"assertions": []interface{}{
   190  				map[string]interface{}{
   191  					"type": "snap-declaration",
   192  					"primary-key": []interface{}{
   193  						"16",
   194  						"iEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5",
   195  					},
   196  					"if-newer-than": float64(0),
   197  				},
   198  			},
   199  		}
   200  		expectedAction2 := map[string]interface{}{
   201  			"action": "fetch-assertions",
   202  			"key":    "g2",
   203  			"assertions": []interface{}{
   204  				map[string]interface{}{
   205  					"type": "snap-declaration",
   206  					"primary-key": []interface{}{
   207  						"16",
   208  						"CSO04Jhav2yK0uz97cr0ipQRyqg0qQL6",
   209  					},
   210  					"if-newer-than": float64(1),
   211  				},
   212  			},
   213  		}
   214  		expectedActions := []map[string]interface{}{expectedAction1, expectedAction2}
   215  		if req.Actions[0]["key"] != "g1" {
   216  			expectedActions = []map[string]interface{}{expectedAction2, expectedAction1}
   217  		}
   218  		c.Assert(req.Actions, DeepEquals, expectedActions)
   219  
   220  		c.Assert(req.AssertionMaxFormats, DeepEquals, asserts.MaxSupportedFormats(1))
   221  
   222  		fmt.Fprintf(w, `{
   223    "results": [{
   224       "result": "fetch-assertions",
   225       "key": "g1",
   226       "assertion-stream-urls": [
   227          "https://api.snapcraft.io/v2/assertions/snap-declaration/16/iEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5"
   228        ]
   229       }, {
   230       "result": "fetch-assertions",
   231       "key": "g2",
   232       "assertion-stream-urls": []
   233       }
   234     ]
   235  }`)
   236  	}))
   237  
   238  	c.Assert(mockServer, NotNil)
   239  	defer mockServer.Close()
   240  
   241  	mockServerURL, _ := url.Parse(mockServer.URL)
   242  	cfg := store.Config{
   243  		StoreBaseURL: mockServerURL,
   244  	}
   245  	dauthCtx := &testDauthContext{c: c, device: s.device}
   246  	sto := store.New(&cfg, dauthCtx)
   247  
   248  	assertq := &testAssertQuery{
   249  		toResolve: map[asserts.Grouping][]*asserts.AtRevision{
   250  			asserts.Grouping("g1"): {{
   251  				Ref: asserts.Ref{
   252  					Type: asserts.SnapDeclarationType,
   253  					PrimaryKey: []string{
   254  						"16",
   255  						"iEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5",
   256  					},
   257  				},
   258  				Revision: 0,
   259  			}},
   260  			asserts.Grouping("g2"): {{
   261  				Ref: asserts.Ref{
   262  					Type: asserts.SnapDeclarationType,
   263  					PrimaryKey: []string{
   264  						"16",
   265  						"CSO04Jhav2yK0uz97cr0ipQRyqg0qQL6",
   266  					},
   267  				},
   268  				Revision: 1,
   269  			}},
   270  		},
   271  	}
   272  
   273  	results, aresults, err := sto.SnapAction(s.ctx, nil,
   274  		nil, assertq, nil, nil)
   275  	c.Assert(err, IsNil)
   276  	c.Check(results, HasLen, 0)
   277  	c.Check(aresults, HasLen, 2)
   278  	seen := 0
   279  	for _, aresult := range aresults {
   280  		if aresult.Grouping == asserts.Grouping("g1") {
   281  			seen++
   282  			c.Check(aresult.StreamURLs, DeepEquals, []string{
   283  				"https://api.snapcraft.io/v2/assertions/snap-declaration/16/iEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5",
   284  			})
   285  		} else {
   286  			seen++
   287  			c.Check(aresult.Grouping, Equals, asserts.Grouping("g2"))
   288  			c.Check(aresult.StreamURLs, HasLen, 0)
   289  		}
   290  	}
   291  	c.Check(seen, Equals, 2)
   292  }
   293  
   294  func (s *storeActionFetchAssertionsSuite) TestFetchNotFound(c *C) {
   295  	restore := release.MockOnClassic(false)
   296  	defer restore()
   297  
   298  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   299  		assertRequest(c, r, "POST", snapActionPath)
   300  		// check device authorization is set, implicitly checking doRequest was used
   301  		c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
   302  
   303  		jsonReq, err := ioutil.ReadAll(r.Body)
   304  		c.Assert(err, IsNil)
   305  		var req struct {
   306  			Context []map[string]interface{} `json:"context"`
   307  			Actions []map[string]interface{} `json:"actions"`
   308  		}
   309  
   310  		err = json.Unmarshal(jsonReq, &req)
   311  		c.Assert(err, IsNil)
   312  
   313  		c.Assert(req.Context, HasLen, 0)
   314  		c.Assert(req.Actions, HasLen, 1)
   315  		expectedAction := map[string]interface{}{
   316  			"action": "fetch-assertions",
   317  			"key":    "g1",
   318  			"assertions": []interface{}{
   319  				map[string]interface{}{
   320  					"type": "snap-declaration",
   321  					"primary-key": []interface{}{
   322  						"16",
   323  						"xEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5",
   324  					},
   325  				},
   326  			},
   327  		}
   328  		c.Assert(req.Actions[0], DeepEquals, expectedAction)
   329  
   330  		fmt.Fprintf(w, `{
   331    "results": [{
   332       "result": "fetch-assertions",
   333       "key": "g1",
   334       "assertion-stream-urls": [],
   335       "error-list": [
   336          {
   337            "code": "not-found",
   338            "message": "not found: no assertion with type \"snap-declaration\" and key {\"series\":\"16\",\"snap-id\":\"xEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5\"}",
   339            "primary-key": [
   340              "16",
   341              "xEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5"
   342            ],
   343            "type": "snap-declaration"
   344          }
   345       ]
   346       }
   347     ]
   348  }`)
   349  	}))
   350  
   351  	c.Assert(mockServer, NotNil)
   352  	defer mockServer.Close()
   353  
   354  	mockServerURL, _ := url.Parse(mockServer.URL)
   355  	cfg := store.Config{
   356  		StoreBaseURL: mockServerURL,
   357  	}
   358  	dauthCtx := &testDauthContext{c: c, device: s.device}
   359  	sto := store.New(&cfg, dauthCtx)
   360  
   361  	assertq := &testAssertQuery{
   362  		toResolve: map[asserts.Grouping][]*asserts.AtRevision{
   363  			asserts.Grouping("g1"): {{
   364  				Ref: asserts.Ref{
   365  					Type: asserts.SnapDeclarationType,
   366  					PrimaryKey: []string{
   367  						"16",
   368  						"xEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5",
   369  					},
   370  				},
   371  				Revision: asserts.RevisionNotKnown,
   372  			}},
   373  		},
   374  	}
   375  
   376  	results, aresults, err := sto.SnapAction(s.ctx, nil,
   377  		nil, assertq, nil, nil)
   378  	c.Assert(err, IsNil)
   379  	c.Check(results, HasLen, 0)
   380  	c.Check(aresults, HasLen, 0)
   381  
   382  	c.Check(assertq.errors, DeepEquals, map[string]error{
   383  		"snap-declaration/16/xEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5": &asserts.NotFoundError{
   384  			Type:    asserts.SnapDeclarationType,
   385  			Headers: map[string]string{"series": "16", "snap-id": "xEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5"}},
   386  	})
   387  }
   388  
   389  func (s *storeActionFetchAssertionsSuite) TestFetchValidationSetNotFound(c *C) {
   390  	restore := release.MockOnClassic(false)
   391  	defer restore()
   392  
   393  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   394  		assertRequest(c, r, "POST", snapActionPath)
   395  		// check device authorization is set, implicitly checking doRequest was used
   396  		c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
   397  
   398  		jsonReq, err := ioutil.ReadAll(r.Body)
   399  		c.Assert(err, IsNil)
   400  		var req struct {
   401  			Context []map[string]interface{} `json:"context"`
   402  			Actions []map[string]interface{} `json:"actions"`
   403  		}
   404  
   405  		err = json.Unmarshal(jsonReq, &req)
   406  		c.Assert(err, IsNil)
   407  
   408  		c.Assert(req.Context, HasLen, 0)
   409  		c.Assert(req.Actions, HasLen, 1)
   410  		expectedAction := map[string]interface{}{
   411  			"action": "fetch-assertions",
   412  			"key":    "g1",
   413  			"assertions": []interface{}{
   414  				map[string]interface{}{
   415  					"type": "validation-set",
   416  					"sequence-key": []interface{}{
   417  						"16",
   418  						"foo",
   419  						"bar",
   420  					},
   421  				},
   422  			},
   423  		}
   424  		c.Assert(req.Actions[0], DeepEquals, expectedAction)
   425  
   426  		fmt.Fprintf(w, `{
   427    "results": [{
   428       "result": "fetch-assertions",
   429       "key": "g1",
   430       "assertion-stream-urls": [],
   431       "error-list": [
   432  		{
   433  			"code": "not-found",
   434  			"message": "not found: no assertion with type \"validation-set\" and sequence key {\"account-id\":\"foo\",\"name\":\"bar\",\"series\":\"16\"}",
   435  			"sequence-key": [
   436  			  "16",
   437  			  "foo",
   438  			  "bar"
   439  			],
   440  			"type": "validation-set"
   441  		}
   442       ]
   443       }
   444     ]
   445  }`)
   446  	}))
   447  
   448  	c.Assert(mockServer, NotNil)
   449  	defer mockServer.Close()
   450  
   451  	mockServerURL, _ := url.Parse(mockServer.URL)
   452  	cfg := store.Config{
   453  		StoreBaseURL: mockServerURL,
   454  	}
   455  	dauthCtx := &testDauthContext{c: c, device: s.device}
   456  	sto := store.New(&cfg, dauthCtx)
   457  
   458  	assertq := &testAssertQuery{
   459  		toResolveSeq: map[asserts.Grouping][]*asserts.AtSequence{
   460  			asserts.Grouping("g1"): {
   461  				&asserts.AtSequence{
   462  					Type: asserts.ValidationSetType,
   463  					SequenceKey: []string{
   464  						"16",
   465  						"foo",
   466  						"bar",
   467  					},
   468  					Revision: asserts.RevisionNotKnown,
   469  				},
   470  			},
   471  		},
   472  	}
   473  
   474  	results, aresults, err := sto.SnapAction(s.ctx, nil,
   475  		nil, assertq, nil, nil)
   476  	c.Assert(err, IsNil)
   477  	c.Check(results, HasLen, 0)
   478  	c.Check(aresults, HasLen, 0)
   479  
   480  	c.Check(assertq.errors, DeepEquals, map[string]error{
   481  		"validation-set/16/foo/bar": &asserts.NotFoundError{
   482  			Type:    asserts.ValidationSetType,
   483  			Headers: map[string]string{"series": "16", "account-id": "foo", "name": "bar"}},
   484  	})
   485  }
   486  
   487  func (s *storeActionFetchAssertionsSuite) TestReportFetchAssertionsError(c *C) {
   488  	notFound := store.ErrorListEntryJSON{
   489  		Code: "not-found",
   490  		Type: "snap-declaration",
   491  		PrimaryKey: []string{
   492  			"16",
   493  			"xEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5",
   494  		},
   495  		Message: `not found: no assertion with type "snap-declaration" and key {"series":"16","snap-id":"xEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5"}`,
   496  	}
   497  	invalidRequest := store.ErrorListEntryJSON{
   498  		Code:       "invalid-request",
   499  		Type:       "snap-declaration",
   500  		PrimaryKey: []string{},
   501  		Message:    `invalid request: invalid key, should be "{series}/{snap-id}"`,
   502  	}
   503  	// not a realistic error, but for completeness
   504  	otherRefError := store.ErrorListEntryJSON{
   505  		Code: "other-ref-error",
   506  		Type: "snap-declaration",
   507  		PrimaryKey: []string{
   508  			"16",
   509  			"xEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5",
   510  		},
   511  		Message: "other ref error",
   512  	}
   513  	otherSeqKeyError := store.ErrorListEntryJSON{
   514  		Code: "other-seq-error",
   515  		Type: "validation-set",
   516  		SequenceKey: []string{
   517  			"16",
   518  			"foo",
   519  			"bar",
   520  		},
   521  		Message: "other sequence key error",
   522  	}
   523  
   524  	tests := []struct {
   525  		errorList []store.ErrorListEntryJSON
   526  		errkey    string
   527  		err       string
   528  	}{
   529  		{[]store.ErrorListEntryJSON{notFound}, "snap-declaration/16/xEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5", "snap-declaration.*not found"},
   530  		{[]store.ErrorListEntryJSON{otherRefError}, "snap-declaration/16/xEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5", "other ref error"},
   531  		{[]store.ErrorListEntryJSON{otherRefError, notFound}, "snap-declaration/16/xEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5", "other ref error"},
   532  		{[]store.ErrorListEntryJSON{notFound, otherRefError}, "snap-declaration/16/xEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5", "other ref error"},
   533  		{[]store.ErrorListEntryJSON{otherSeqKeyError}, "validation-set/16/foo/bar", "other sequence key error"},
   534  		{[]store.ErrorListEntryJSON{notFound, otherSeqKeyError}, "validation-set/16/foo/bar", "other sequence key error"},
   535  		{[]store.ErrorListEntryJSON{invalidRequest}, "{g1}", "invalid request: invalid key.*"},
   536  		{[]store.ErrorListEntryJSON{invalidRequest, otherRefError}, "{g1}", "invalid request: invalid key.*"},
   537  		{[]store.ErrorListEntryJSON{invalidRequest, notFound}, "{g1}", "invalid request: invalid key.*"},
   538  		{[]store.ErrorListEntryJSON{notFound, invalidRequest, otherRefError}, "{g1}", "invalid request: invalid key.*"},
   539  	}
   540  
   541  	for _, t := range tests {
   542  		assertq := &testAssertQuery{}
   543  
   544  		res := &store.SnapActionResultJSON{
   545  			Key:       "g1",
   546  			ErrorList: t.errorList,
   547  		}
   548  
   549  		err := store.ReportFetchAssertionsError(res, assertq)
   550  		c.Assert(err, IsNil)
   551  
   552  		c.Check(assertq.errors, HasLen, 1)
   553  		for k, e := range assertq.errors {
   554  			c.Check(k, Equals, t.errkey)
   555  			c.Check(e, ErrorMatches, t.err)
   556  		}
   557  	}
   558  }
   559  
   560  func (s *storeActionFetchAssertionsSuite) TestUpdateSequenceForming(c *C) {
   561  	restore := release.MockOnClassic(false)
   562  	defer restore()
   563  
   564  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   565  		assertRequest(c, r, "POST", snapActionPath)
   566  		// check device authorization is set, implicitly checking doRequest was used
   567  		c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
   568  
   569  		jsonReq, err := ioutil.ReadAll(r.Body)
   570  		c.Assert(err, IsNil)
   571  		var req struct {
   572  			Context []map[string]interface{} `json:"context"`
   573  			Actions []map[string]interface{} `json:"actions"`
   574  
   575  			AssertionMaxFormats map[string]int `json:"assertion-max-formats"`
   576  		}
   577  
   578  		err = json.Unmarshal(jsonReq, &req)
   579  		c.Assert(err, IsNil)
   580  
   581  		c.Assert(req.Context, HasLen, 0)
   582  		c.Assert(req.Actions, HasLen, 2)
   583  		expectedAction1 := map[string]interface{}{
   584  			"action": "fetch-assertions",
   585  			"key":    "g1",
   586  			"assertions": []interface{}{
   587  				map[string]interface{}{
   588  					"type": "validation-set",
   589  					"sequence-key": []interface{}{
   590  						"16",
   591  						"account-1/name-1",
   592  					},
   593  					"sequence": float64(3),
   594  				},
   595  				map[string]interface{}{
   596  					"type": "validation-set",
   597  					"sequence-key": []interface{}{
   598  						"16",
   599  						"account-1/name-2",
   600  					},
   601  					"if-sequence-equal-or-newer-than": float64(5),
   602  					"if-newer-than":                   float64(10),
   603  				},
   604  			},
   605  		}
   606  		expectedAction2 := map[string]interface{}{
   607  			"action": "fetch-assertions",
   608  			"key":    "g2",
   609  			"assertions": []interface{}{
   610  				map[string]interface{}{
   611  					"type": "validation-set",
   612  					"sequence-key": []interface{}{
   613  						"16",
   614  						"account-2/name",
   615  					},
   616  				},
   617  			},
   618  		}
   619  		expectedActions := []map[string]interface{}{expectedAction1, expectedAction2}
   620  		if req.Actions[0]["key"] != "g1" {
   621  			expectedActions = []map[string]interface{}{expectedAction2, expectedAction1}
   622  		}
   623  		c.Assert(req.Actions, DeepEquals, expectedActions)
   624  
   625  		c.Assert(req.AssertionMaxFormats, DeepEquals, asserts.MaxSupportedFormats(1))
   626  
   627  		fmt.Fprintf(w, `{
   628    "results": [{
   629       "result": "fetch-assertions",
   630       "key": "g1",
   631       "assertion-stream-urls": [
   632          "https://api.snapcraft.io/v2/assertions/snap-declaration/16/iEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5"
   633        ]
   634       }, {
   635       "result": "fetch-assertions",
   636       "key": "g2",
   637       "assertion-stream-urls": []
   638       }
   639     ]
   640  }`)
   641  	}))
   642  
   643  	c.Assert(mockServer, NotNil)
   644  	defer mockServer.Close()
   645  
   646  	mockServerURL, _ := url.Parse(mockServer.URL)
   647  	cfg := store.Config{
   648  		StoreBaseURL: mockServerURL,
   649  	}
   650  	dauthCtx := &testDauthContext{c: c, device: s.device}
   651  	sto := store.New(&cfg, dauthCtx)
   652  
   653  	assertq := &testAssertQuery{
   654  		toResolveSeq: map[asserts.Grouping][]*asserts.AtSequence{
   655  			asserts.Grouping("g1"): {
   656  				&asserts.AtSequence{
   657  					Type: asserts.ValidationSetType,
   658  					SequenceKey: []string{
   659  						"16",
   660  						"account-1/name-1",
   661  					},
   662  					Sequence: 3,
   663  					Pinned:   true,
   664  					Revision: asserts.RevisionNotKnown,
   665  				},
   666  				&asserts.AtSequence{
   667  					Type: asserts.ValidationSetType,
   668  					SequenceKey: []string{
   669  						"16",
   670  						"account-1/name-2",
   671  					},
   672  					Sequence: 5,
   673  					Revision: 10,
   674  				},
   675  			},
   676  			asserts.Grouping("g2"): {
   677  				&asserts.AtSequence{
   678  					Type: asserts.ValidationSetType,
   679  					SequenceKey: []string{
   680  						"16",
   681  						"account-2/name",
   682  					},
   683  					Revision: asserts.RevisionNotKnown,
   684  				},
   685  			},
   686  		},
   687  	}
   688  
   689  	results, aresults, err := sto.SnapAction(s.ctx, nil,
   690  		nil, assertq, nil, nil)
   691  	c.Assert(err, IsNil)
   692  	c.Check(results, HasLen, 0)
   693  	c.Check(aresults, HasLen, 2)
   694  	seen := 0
   695  	for _, aresult := range aresults {
   696  		if aresult.Grouping == asserts.Grouping("g1") {
   697  			seen++
   698  			c.Check(aresult.StreamURLs, DeepEquals, []string{
   699  				"https://api.snapcraft.io/v2/assertions/snap-declaration/16/iEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5",
   700  			})
   701  		} else {
   702  			seen++
   703  			c.Check(aresult.Grouping, Equals, asserts.Grouping("g2"))
   704  			c.Check(aresult.StreamURLs, HasLen, 0)
   705  		}
   706  	}
   707  	c.Check(seen, Equals, 2)
   708  }
   709  
   710  func (s *storeActionFetchAssertionsSuite) TestUpdateSequenceFormingCommonGroupings(c *C) {
   711  	restore := release.MockOnClassic(false)
   712  	defer restore()
   713  
   714  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   715  		assertRequest(c, r, "POST", snapActionPath)
   716  		// check device authorization is set, implicitly checking doRequest was used
   717  		c.Check(r.Header.Get("Snap-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
   718  
   719  		jsonReq, err := ioutil.ReadAll(r.Body)
   720  		c.Assert(err, IsNil)
   721  		var req struct {
   722  			Context []map[string]interface{} `json:"context"`
   723  			Actions []map[string]interface{} `json:"actions"`
   724  
   725  			AssertionMaxFormats map[string]int `json:"assertion-max-formats"`
   726  		}
   727  
   728  		err = json.Unmarshal(jsonReq, &req)
   729  		c.Assert(err, IsNil)
   730  
   731  		c.Assert(req.Context, HasLen, 0)
   732  		c.Assert(req.Actions, HasLen, 2)
   733  
   734  		expectedAction1 := map[string]interface{}{
   735  			"action": "fetch-assertions",
   736  			"key":    "g1",
   737  			"assertions": []interface{}{
   738  				map[string]interface{}{
   739  					"type": "snap-declaration",
   740  					"primary-key": []interface{}{
   741  						"16",
   742  						"iEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5",
   743  					},
   744  					"if-newer-than": float64(0),
   745  				},
   746  				map[string]interface{}{
   747  					"type": "validation-set",
   748  					"sequence-key": []interface{}{
   749  						"16",
   750  						"account-1/name-1",
   751  					},
   752  					"sequence": float64(3),
   753  				},
   754  				map[string]interface{}{
   755  					"type": "validation-set",
   756  					"sequence-key": []interface{}{
   757  						"16",
   758  						"account-1/name-2",
   759  					},
   760  					"if-sequence-equal-or-newer-than": float64(5),
   761  					"if-newer-than":                   float64(10),
   762  				},
   763  			},
   764  		}
   765  		expectedAction2 := map[string]interface{}{
   766  			"action": "fetch-assertions",
   767  			"key":    "g2",
   768  			"assertions": []interface{}{
   769  				map[string]interface{}{
   770  					"type": "snap-declaration",
   771  					"primary-key": []interface{}{
   772  						"16",
   773  						"CSO04Jhav2yK0uz97cr0ipQRyqg0qQL6",
   774  					},
   775  					"if-newer-than": float64(1),
   776  				},
   777  				map[string]interface{}{
   778  					"type": "validation-set",
   779  					"sequence-key": []interface{}{
   780  						"16",
   781  						"account-2/name",
   782  					},
   783  				},
   784  			},
   785  		}
   786  
   787  		expectedActions := []map[string]interface{}{expectedAction1, expectedAction2}
   788  		if req.Actions[0]["key"] != "g1" {
   789  			expectedActions = []map[string]interface{}{expectedAction2, expectedAction1}
   790  		}
   791  		c.Assert(req.Actions, DeepEquals, expectedActions)
   792  		c.Assert(req.AssertionMaxFormats, DeepEquals, asserts.MaxSupportedFormats(1))
   793  
   794  		fmt.Fprintf(w, `{
   795    "results": [{
   796       "result": "fetch-assertions", "key": "g1", "assertion-stream-urls": []
   797       }, {
   798       "result": "fetch-assertions", "key": "g2", "assertion-stream-urls": []
   799       }]
   800  }`)
   801  	}))
   802  
   803  	c.Assert(mockServer, NotNil)
   804  	defer mockServer.Close()
   805  
   806  	mockServerURL, _ := url.Parse(mockServer.URL)
   807  	cfg := store.Config{
   808  		StoreBaseURL: mockServerURL,
   809  	}
   810  	dauthCtx := &testDauthContext{c: c, device: s.device}
   811  	sto := store.New(&cfg, dauthCtx)
   812  
   813  	assertq := &testAssertQuery{
   814  		toResolve: map[asserts.Grouping][]*asserts.AtRevision{
   815  			asserts.Grouping("g1"): {{
   816  				Ref: asserts.Ref{
   817  					Type: asserts.SnapDeclarationType,
   818  					PrimaryKey: []string{
   819  						"16",
   820  						"iEr2EpvaIaqrXxoM2JyHOmuXQYvSzUt5",
   821  					},
   822  				},
   823  				Revision: 0,
   824  			}},
   825  			asserts.Grouping("g2"): {{
   826  				Ref: asserts.Ref{
   827  					Type: asserts.SnapDeclarationType,
   828  					PrimaryKey: []string{
   829  						"16",
   830  						"CSO04Jhav2yK0uz97cr0ipQRyqg0qQL6",
   831  					},
   832  				},
   833  				Revision: 1,
   834  			}},
   835  		},
   836  		toResolveSeq: map[asserts.Grouping][]*asserts.AtSequence{
   837  			asserts.Grouping("g1"): {
   838  				&asserts.AtSequence{
   839  					Type: asserts.ValidationSetType,
   840  					SequenceKey: []string{
   841  						"16",
   842  						"account-1/name-1",
   843  					},
   844  					Sequence: 3,
   845  					Pinned:   true,
   846  					Revision: asserts.RevisionNotKnown,
   847  				},
   848  				&asserts.AtSequence{
   849  					Type: asserts.ValidationSetType,
   850  					SequenceKey: []string{
   851  						"16",
   852  						"account-1/name-2",
   853  					},
   854  					Sequence: 5,
   855  					Revision: 10,
   856  				},
   857  			},
   858  			asserts.Grouping("g2"): {
   859  				&asserts.AtSequence{
   860  					Type: asserts.ValidationSetType,
   861  					SequenceKey: []string{
   862  						"16",
   863  						"account-2/name",
   864  					},
   865  					Revision: asserts.RevisionNotKnown,
   866  				},
   867  			},
   868  		},
   869  	}
   870  
   871  	_, _, err := sto.SnapAction(s.ctx, nil, nil, assertq, nil, nil)
   872  	c.Assert(err, IsNil)
   873  }