github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/store/store_asserts_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  	"bytes"
    24  	"io"
    25  	"net/http"
    26  	"net/http/httptest"
    27  	"net/url"
    28  	"time"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/asserts"
    33  	"github.com/snapcore/snapd/asserts/assertstest"
    34  	"github.com/snapcore/snapd/store"
    35  )
    36  
    37  type storeAssertsSuite struct {
    38  	baseStoreSuite
    39  
    40  	storeSigning *assertstest.StoreStack
    41  	dev1Acct     *asserts.Account
    42  	decl1        *asserts.SnapDeclaration
    43  
    44  	db *asserts.Database
    45  }
    46  
    47  var _ = Suite(&storeAssertsSuite{})
    48  
    49  func (s *storeAssertsSuite) SetUpTest(c *C) {
    50  	s.baseStoreSuite.SetUpTest(c)
    51  
    52  	s.storeSigning = assertstest.NewStoreStack("can0nical", nil)
    53  	s.dev1Acct = assertstest.NewAccount(s.storeSigning, "developer1", map[string]interface{}{
    54  		"account-id": "developer1",
    55  	}, "")
    56  
    57  	a, err := s.storeSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{
    58  		"series":       "16",
    59  		"snap-id":      "asnapid",
    60  		"snap-name":    "asnap",
    61  		"publisher-id": "developer1",
    62  		"timestamp":    time.Now().UTC().Format(time.RFC3339),
    63  	}, nil, "")
    64  	c.Assert(err, IsNil)
    65  	s.decl1 = a.(*asserts.SnapDeclaration)
    66  
    67  	db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
    68  		Backstore: asserts.NewMemoryBackstore(),
    69  		Trusted:   s.storeSigning.Trusted,
    70  	})
    71  	c.Assert(err, IsNil)
    72  	s.db = db
    73  }
    74  
    75  var testAssertion = `type: snap-declaration
    76  authority-id: super
    77  series: 16
    78  snap-id: snapidfoo
    79  publisher-id: devidbaz
    80  snap-name: mysnap
    81  timestamp: 2016-03-30T12:22:16Z
    82  sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij
    83  
    84  openpgp wsBcBAABCAAQBQJW+8VBCRDWhXkqAWcrfgAAQ9gIABZFgMPByJZeUE835FkX3/y2hORn
    85  AzE3R1ktDkQEVe/nfVDMACAuaw1fKmUS4zQ7LIrx/AZYw5i0vKVmJszL42LBWVsqR0+p9Cxebzv9
    86  U2VUSIajEsUUKkBwzD8wxFzagepFlScif1NvCGZx0vcGUOu0Ent0v+gqgAv21of4efKqEW7crlI1
    87  T/A8LqZYmIzKRHGwCVucCyAUD8xnwt9nyWLgLB+LLPOVFNK8SR6YyNsX05Yz1BUSndBfaTN8j/k8
    88  8isKGZE6P0O9ozBbNIAE8v8NMWQegJ4uWuil7D3psLkzQIrxSypk9TrQ2GlIG2hJdUovc5zBuroe
    89  xS4u9rVT6UY=`
    90  
    91  func (s *storeAssertsSuite) TestAssertion(c *C) {
    92  	restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 88)
    93  	defer restore()
    94  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    95  		assertRequest(c, r, "GET", "/v2/assertions/.*")
    96  		// check device authorization is set, implicitly checking doRequest was used
    97  		c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
    98  
    99  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   100  		c.Check(r.URL.Path, Matches, ".*/snap-declaration/16/snapidfoo")
   101  		c.Check(r.URL.Query().Get("max-format"), Equals, "88")
   102  		io.WriteString(w, testAssertion)
   103  	}))
   104  
   105  	c.Assert(mockServer, NotNil)
   106  	defer mockServer.Close()
   107  
   108  	mockServerURL, _ := url.Parse(mockServer.URL)
   109  	cfg := store.Config{
   110  		StoreBaseURL: mockServerURL,
   111  	}
   112  	dauthCtx := &testDauthContext{c: c, device: s.device}
   113  	sto := store.New(&cfg, dauthCtx)
   114  
   115  	a, err := sto.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, nil)
   116  	c.Assert(err, IsNil)
   117  	c.Check(a, NotNil)
   118  	c.Check(a.Type(), Equals, asserts.SnapDeclarationType)
   119  }
   120  
   121  func (s *storeAssertsSuite) TestAssertionProxyStoreFromAuthContext(c *C) {
   122  	restore := asserts.MockMaxSupportedFormat(asserts.SnapDeclarationType, 88)
   123  	defer restore()
   124  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   125  		assertRequest(c, r, "GET", "/v2/assertions/.*")
   126  		// check device authorization is set, implicitly checking doRequest was used
   127  		c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
   128  
   129  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   130  		c.Check(r.URL.Path, Matches, ".*/snap-declaration/16/snapidfoo")
   131  		c.Check(r.URL.Query().Get("max-format"), Equals, "88")
   132  		io.WriteString(w, testAssertion)
   133  	}))
   134  
   135  	c.Assert(mockServer, NotNil)
   136  	defer mockServer.Close()
   137  
   138  	mockServerURL, _ := url.Parse(mockServer.URL)
   139  	nowhereURL, err := url.Parse("http://nowhere.invalid")
   140  	c.Assert(err, IsNil)
   141  	cfg := store.Config{
   142  		AssertionsBaseURL: nowhereURL,
   143  	}
   144  	dauthCtx := &testDauthContext{
   145  		c:             c,
   146  		device:        s.device,
   147  		proxyStoreID:  "foo",
   148  		proxyStoreURL: mockServerURL,
   149  	}
   150  	sto := store.New(&cfg, dauthCtx)
   151  
   152  	a, err := sto.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, nil)
   153  	c.Assert(err, IsNil)
   154  	c.Check(a, NotNil)
   155  	c.Check(a.Type(), Equals, asserts.SnapDeclarationType)
   156  }
   157  
   158  func (s *storeAssertsSuite) TestAssertionNotFoundV1(c *C) {
   159  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   160  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   161  		c.Check(r.URL.Path, Matches, ".*/snap-declaration/16/snapidfoo")
   162  		w.Header().Set("Content-Type", "application/problem+json")
   163  		w.WriteHeader(404)
   164  		io.WriteString(w, `{"status": 404,"title": "not found"}`)
   165  	}))
   166  
   167  	c.Assert(mockServer, NotNil)
   168  	defer mockServer.Close()
   169  
   170  	mockServerURL, _ := url.Parse(mockServer.URL)
   171  	cfg := store.Config{
   172  		AssertionsBaseURL: mockServerURL,
   173  	}
   174  	sto := store.New(&cfg, nil)
   175  
   176  	_, err := sto.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, nil)
   177  	c.Check(asserts.IsNotFound(err), Equals, true)
   178  	c.Check(err, DeepEquals, &asserts.NotFoundError{
   179  		Type: asserts.SnapDeclarationType,
   180  		Headers: map[string]string{
   181  			"series":  "16",
   182  			"snap-id": "snapidfoo",
   183  		},
   184  	})
   185  }
   186  
   187  func (s *storeAssertsSuite) TestAssertionNotFoundV2(c *C) {
   188  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   189  		assertRequest(c, r, "GET", "/v2/assertions/.*")
   190  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   191  		c.Check(r.URL.Path, Matches, ".*/snap-declaration/16/snapidfoo")
   192  		w.Header().Set("Content-Type", "application/json")
   193  		w.WriteHeader(404)
   194  		io.WriteString(w, `{"error-list":[{"code":"not-found","message":"not found: no ..."}]}`)
   195  	}))
   196  
   197  	c.Assert(mockServer, NotNil)
   198  	defer mockServer.Close()
   199  
   200  	mockServerURL, _ := url.Parse(mockServer.URL)
   201  	cfg := store.Config{
   202  		AssertionsBaseURL: mockServerURL,
   203  	}
   204  	sto := store.New(&cfg, nil)
   205  
   206  	_, err := sto.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, nil)
   207  	c.Check(asserts.IsNotFound(err), Equals, true)
   208  	c.Check(err, DeepEquals, &asserts.NotFoundError{
   209  		Type: asserts.SnapDeclarationType,
   210  		Headers: map[string]string{
   211  			"series":  "16",
   212  			"snap-id": "snapidfoo",
   213  		},
   214  	})
   215  }
   216  
   217  func (s *storeAssertsSuite) TestAssertion500(c *C) {
   218  	var n = 0
   219  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   220  		assertRequest(c, r, "GET", "/v2/assertions/.*")
   221  		n++
   222  		w.WriteHeader(500)
   223  	}))
   224  
   225  	c.Assert(mockServer, NotNil)
   226  	defer mockServer.Close()
   227  
   228  	mockServerURL, _ := url.Parse(mockServer.URL)
   229  	cfg := store.Config{
   230  		AssertionsBaseURL: mockServerURL,
   231  	}
   232  	sto := store.New(&cfg, nil)
   233  
   234  	_, err := sto.Assertion(asserts.SnapDeclarationType, []string{"16", "snapidfoo"}, nil)
   235  	c.Assert(err, ErrorMatches, `cannot fetch assertion: got unexpected HTTP status code 500 via .+`)
   236  	c.Assert(n, Equals, 5)
   237  }
   238  
   239  func (s *storeAssertsSuite) TestDownloadAssertionsSimple(c *C) {
   240  	assertstest.AddMany(s.db, s.storeSigning.StoreAccountKey(""), s.dev1Acct)
   241  
   242  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   243  		assertRequest(c, r, "GET", "/assertions/.*")
   244  		// check device authorization is set, implicitly checking doRequest was used
   245  		c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
   246  
   247  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   248  		c.Check(r.URL.Path, Matches, ".*/snap-declaration/16/asnapid")
   249  		c.Check(r.URL.Query().Get("max-format"), Equals, "88")
   250  		w.Write(asserts.Encode(s.decl1))
   251  	}))
   252  
   253  	c.Assert(mockServer, NotNil)
   254  	defer mockServer.Close()
   255  
   256  	mockServerURL, _ := url.Parse(mockServer.URL)
   257  	cfg := store.Config{
   258  		StoreBaseURL: mockServerURL,
   259  	}
   260  
   261  	dauthCtx := &testDauthContext{c: c, device: s.device}
   262  	sto := store.New(&cfg, dauthCtx)
   263  
   264  	streamURL, err := mockServerURL.Parse("/assertions/snap-declaration/16/asnapid")
   265  	c.Assert(err, IsNil)
   266  	urls := []string{streamURL.String() + "?max-format=88"}
   267  
   268  	b := asserts.NewBatch(nil)
   269  	err = sto.DownloadAssertions(urls, b, nil)
   270  	c.Assert(err, IsNil)
   271  
   272  	c.Assert(b.CommitTo(s.db, nil), IsNil)
   273  
   274  	// added
   275  	_, err = s.decl1.Ref().Resolve(s.db.Find)
   276  	c.Check(err, IsNil)
   277  }
   278  
   279  func (s *storeAssertsSuite) TestDownloadAssertionsWithStreams(c *C) {
   280  	stream1 := append(asserts.Encode(s.decl1), "\n"...)
   281  	stream1 = append(stream1, asserts.Encode(s.dev1Acct)...)
   282  	stream2 := asserts.Encode(s.storeSigning.StoreAccountKey(""))
   283  
   284  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   285  		assertRequest(c, r, "GET", "/assertions/.*")
   286  
   287  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   288  		var stream []byte
   289  		switch r.URL.Path {
   290  		case "/assertions/stream1":
   291  			stream = stream1
   292  		case "/assertions/stream2":
   293  			stream = stream2
   294  		default:
   295  			c.Fatal("unexpected stream url")
   296  		}
   297  
   298  		w.Write(stream)
   299  	}))
   300  
   301  	c.Assert(mockServer, NotNil)
   302  	defer mockServer.Close()
   303  
   304  	mockServerURL, _ := url.Parse(mockServer.URL)
   305  	cfg := store.Config{
   306  		StoreBaseURL: mockServerURL,
   307  	}
   308  
   309  	dauthCtx := &testDauthContext{c: c, device: s.device}
   310  	sto := store.New(&cfg, dauthCtx)
   311  
   312  	stream1URL, err := mockServerURL.Parse("/assertions/stream1")
   313  	c.Assert(err, IsNil)
   314  	stream2URL, err := mockServerURL.Parse("/assertions/stream2")
   315  	c.Assert(err, IsNil)
   316  
   317  	urls := []string{stream1URL.String(), stream2URL.String()}
   318  
   319  	b := asserts.NewBatch(nil)
   320  	err = sto.DownloadAssertions(urls, b, nil)
   321  	c.Assert(err, IsNil)
   322  
   323  	c.Assert(b.CommitTo(s.db, nil), IsNil)
   324  
   325  	// added
   326  	_, err = s.decl1.Ref().Resolve(s.db.Find)
   327  	c.Check(err, IsNil)
   328  }
   329  
   330  func (s *storeAssertsSuite) TestDownloadAssertionsBrokenStream(c *C) {
   331  	stream1 := append(asserts.Encode(s.decl1), "\n"...)
   332  	stream1 = append(stream1, asserts.Encode(s.dev1Acct)...)
   333  
   334  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   335  		assertRequest(c, r, "GET", "/assertions/stream1")
   336  
   337  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   338  
   339  		breakAt := bytes.Index(stream1, []byte("account-id"))
   340  		w.Write(stream1[:breakAt])
   341  	}))
   342  
   343  	c.Assert(mockServer, NotNil)
   344  	defer mockServer.Close()
   345  
   346  	mockServerURL, _ := url.Parse(mockServer.URL)
   347  	cfg := store.Config{
   348  		StoreBaseURL: mockServerURL,
   349  	}
   350  
   351  	dauthCtx := &testDauthContext{c: c, device: s.device}
   352  	sto := store.New(&cfg, dauthCtx)
   353  
   354  	stream1URL, err := mockServerURL.Parse("/assertions/stream1")
   355  	c.Assert(err, IsNil)
   356  
   357  	urls := []string{stream1URL.String()}
   358  
   359  	b := asserts.NewBatch(nil)
   360  	err = sto.DownloadAssertions(urls, b, nil)
   361  	c.Assert(err, Equals, io.ErrUnexpectedEOF)
   362  }
   363  
   364  func (s *storeAssertsSuite) TestDownloadAssertions500(c *C) {
   365  	n := 0
   366  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   367  		assertRequest(c, r, "GET", "/assertions/stream1")
   368  
   369  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   370  
   371  		n++
   372  		w.WriteHeader(500)
   373  	}))
   374  
   375  	c.Assert(mockServer, NotNil)
   376  	defer mockServer.Close()
   377  
   378  	mockServerURL, _ := url.Parse(mockServer.URL)
   379  	cfg := store.Config{
   380  		StoreBaseURL: mockServerURL,
   381  	}
   382  
   383  	dauthCtx := &testDauthContext{c: c, device: s.device}
   384  	sto := store.New(&cfg, dauthCtx)
   385  
   386  	stream1URL, err := mockServerURL.Parse("/assertions/stream1")
   387  	c.Assert(err, IsNil)
   388  
   389  	urls := []string{stream1URL.String()}
   390  
   391  	b := asserts.NewBatch(nil)
   392  	err = sto.DownloadAssertions(urls, b, nil)
   393  	c.Assert(err, ErrorMatches, `cannot download assertion stream: got unexpected HTTP status code 500 via .+`)
   394  	c.Check(n, Equals, 5)
   395  }
   396  
   397  func (s *storeAssertsSuite) TestDownloadAssertionsStreamNotFound(c *C) {
   398  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   399  		assertRequest(c, r, "GET", "/assertions/stream1")
   400  
   401  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   402  		w.Header().Set("Content-Type", "application/problem+json")
   403  		w.WriteHeader(404)
   404  		io.WriteString(w, `{"error-list":[{"code":"not-found","message":"not found: no ..."}]}`)
   405  	}))
   406  
   407  	c.Assert(mockServer, NotNil)
   408  	defer mockServer.Close()
   409  
   410  	mockServerURL, _ := url.Parse(mockServer.URL)
   411  	cfg := store.Config{
   412  		StoreBaseURL: mockServerURL,
   413  	}
   414  
   415  	dauthCtx := &testDauthContext{c: c, device: s.device}
   416  	sto := store.New(&cfg, dauthCtx)
   417  
   418  	stream1URL, err := mockServerURL.Parse("/assertions/stream1")
   419  	c.Assert(err, IsNil)
   420  
   421  	urls := []string{stream1URL.String()}
   422  
   423  	b := asserts.NewBatch(nil)
   424  	err = sto.DownloadAssertions(urls, b, nil)
   425  	c.Assert(err, ErrorMatches, `assertion service error: \"not found.*`)
   426  }
   427  
   428  var testValidationSetAssertion = `type: validation-set
   429  authority-id: K9scFPuK62alndiUfSxJte9WSeZihEcD
   430  series: 16
   431  account-id: K9scFPuK62alndiUfSxJte9WSeZihEcD
   432  name: set-1
   433  sequence: 2
   434  snaps:
   435    -
   436      id: yOqKhntON3vR7kwEbVPsILm7bUViPDzz
   437      name: lxd
   438      presence: optional
   439      revision: 1
   440    -
   441      id: andRelFqGSFNzJRfWD6SEL34YzEfEEiR
   442      name: jq
   443      presence: optional
   444    -
   445      id: UP3QB9yet9QvNhXcCZVdgL1VleVaqz8V
   446      name: suligap-python3-qrcode
   447      revision: 4
   448  timestamp: 2020-11-06T09:16:26Z
   449  sign-key-sha3-384: 4sq0NF2nUf53bg-G3AXBs0Paj73IYg4g1kWpBEVaAnzh1eNQEI2-UVeFz4e1MEUW
   450  
   451  AcLBcwQAAQoAHRYhBA519PIIp64v4+mi5pz1bjeveYQwBQJfpRRqAAoJEJz1bjeveYQwIHUP/A6z
   452  51knc4y/hYF/aAbrea1VFBxddu7BW18w4J97QDWJOah+TT7HMbvduEneeTEPNl9fO8CUtqUSV5JH
   453  GO5WmcS8gHMELMRz7deMKkwzHU1tL7G3xAqIP5ctkNDhobJyCQmU8yyJdp2e6dw5RVFBE9WcAlpO
   454  bRhYIFIUUO0Fn6XvKZuDvCFC3rzRmQV/taAR0jYTbHgeOirr8loEfTKKQZQOaE2GyA5cl0vzx3UT
   455  5uct/giBHDNXFocHEpw/1wwUkqZgOGkT3/tuyiYd0HQ5jdTDldHs9EPRIcwTEjjFtseBUr9W5m/a
   456  kFkWBWPe5FkLvC74H8WXUQbQHgii6RxDnJ1bBVzCOH65pgtRWNCTcoYr5sEB2tPEFEh50bha+37Q
   457  1c3lvGGQWyQRz5uxE5aZNiTaLdnQxPEF+nFd1yTwh7yR8Gqv/SuQMxS/AMQz/3sltfssOjayOtV1
   458  N2R8HGUVKutoRGWMp+YmGO68wHjk5Ff9cIQvXfDviSl4KezrDIIFRqx0ZJaYh1FDmOTfAK68yEFu
   459  P8aWCC2W3HIrdx2mnikT3oVf6yN1KSY5qCE2xdhyyKtt+4y5ZJdQK6JxzTanzh4PZVdiPIUhDv4r
   460  AeDBddPc+mqQtb8bpZ7hMD+dA/B4dA3cRl44Nb/5KcfKjdvl7qpmJQl88OA3DOMpXuxmrrVA
   461  `
   462  
   463  func (s *storeAssertsSuite) TestSeqFormingAssertion(c *C) {
   464  	restore := asserts.MockMaxSupportedFormat(asserts.ValidationSetType, 88)
   465  	defer restore()
   466  
   467  	// overwritten by test loop for each test case
   468  	expectedSeqArg := "dummy"
   469  
   470  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   471  		assertRequest(c, r, "GET", "/v2/assertions/.*")
   472  		// check device authorization is set, implicitly checking doRequest was used
   473  		c.Check(r.Header.Get("X-Device-Authorization"), Equals, `Macaroon root="device-macaroon"`)
   474  
   475  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   476  		c.Check(r.URL.Path, Matches, ".*/validation-set/16/account-foo/set-bar")
   477  		q := r.URL.Query()
   478  		c.Check(q.Get("sequence"), Equals, expectedSeqArg)
   479  		c.Check(q.Get("max-format"), Equals, "88")
   480  		io.WriteString(w, testValidationSetAssertion)
   481  	}))
   482  
   483  	c.Assert(mockServer, NotNil)
   484  	defer mockServer.Close()
   485  
   486  	for _, tc := range []struct {
   487  		sequenceKey    []string
   488  		sequence       int
   489  		expectedSeqArg string
   490  	}{
   491  		{[]string{"16", "account-foo", "set-bar"}, 2, "2"},
   492  		{[]string{"16", "account-foo", "set-bar"}, 0, "latest"},
   493  	} {
   494  		expectedSeqArg = tc.expectedSeqArg
   495  
   496  		mockServerURL, _ := url.Parse(mockServer.URL)
   497  		cfg := store.Config{
   498  			StoreBaseURL: mockServerURL,
   499  		}
   500  		dauthCtx := &testDauthContext{c: c, device: s.device}
   501  		sto := store.New(&cfg, dauthCtx)
   502  
   503  		a, err := sto.SeqFormingAssertion(asserts.ValidationSetType, tc.sequenceKey, tc.sequence, nil)
   504  		c.Assert(err, IsNil)
   505  		c.Check(a, NotNil)
   506  		c.Check(a.Type(), Equals, asserts.ValidationSetType)
   507  	}
   508  }
   509  
   510  func (s *storeAssertsSuite) TestSeqFormingAssertionNotFound(c *C) {
   511  	mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   512  		assertRequest(c, r, "GET", "/v2/assertions/.*")
   513  		c.Check(r.Header.Get("Accept"), Equals, "application/x.ubuntu.assertion")
   514  		c.Check(r.URL.Path, Matches, ".*/validation-set/16/account-foo/set-bar")
   515  		w.Header().Set("Content-Type", "application/problem+json")
   516  		w.WriteHeader(404)
   517  		io.WriteString(w, `{"error-list":[{"code":"not-found","message":"not found: no ..."}]}`)
   518  	}))
   519  
   520  	c.Assert(mockServer, NotNil)
   521  	defer mockServer.Close()
   522  
   523  	mockServerURL, _ := url.Parse(mockServer.URL)
   524  	cfg := store.Config{
   525  		AssertionsBaseURL: mockServerURL,
   526  	}
   527  	sto := store.New(&cfg, nil)
   528  
   529  	_, err := sto.SeqFormingAssertion(asserts.ValidationSetType, []string{"16", "account-foo", "set-bar"}, 1, nil)
   530  	c.Check(asserts.IsNotFound(err), Equals, true)
   531  	c.Check(err, DeepEquals, &asserts.NotFoundError{
   532  		Type: asserts.ValidationSetType,
   533  		Headers: map[string]string{
   534  			"series":     "16",
   535  			"account-id": "account-foo",
   536  			"name":       "set-bar",
   537  			"sequence":   "1",
   538  		},
   539  	})
   540  
   541  	// latest requested
   542  	_, err = sto.SeqFormingAssertion(asserts.ValidationSetType, []string{"16", "account-foo", "set-bar"}, 0, nil)
   543  	c.Check(asserts.IsNotFound(err), Equals, true)
   544  	c.Check(err, DeepEquals, &asserts.NotFoundError{
   545  		Type: asserts.ValidationSetType,
   546  		Headers: map[string]string{
   547  			"series":     "16",
   548  			"account-id": "account-foo",
   549  			"name":       "set-bar",
   550  		},
   551  	})
   552  }