github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/daemon/api_find_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 daemon_test
    21  
    22  import (
    23  	"fmt"
    24  	"net/http"
    25  
    26  	"gopkg.in/check.v1"
    27  
    28  	"github.com/snapcore/snapd/client"
    29  	"github.com/snapcore/snapd/daemon"
    30  	"github.com/snapcore/snapd/overlord/auth"
    31  	"github.com/snapcore/snapd/overlord/snapstate"
    32  	"github.com/snapcore/snapd/snap"
    33  	"github.com/snapcore/snapd/store"
    34  )
    35  
    36  var _ = check.Suite(&findSuite{})
    37  
    38  type findSuite struct {
    39  	apiBaseSuite
    40  }
    41  
    42  func (s *findSuite) TestFind(c *check.C) {
    43  	s.daemon(c)
    44  
    45  	s.suggestedCurrency = "EUR"
    46  
    47  	s.rsnaps = []*snap.Info{{
    48  		SideInfo: snap.SideInfo{
    49  			RealName: "store",
    50  		},
    51  		Publisher: snap.StoreAccount{
    52  			ID:          "foo-id",
    53  			Username:    "foo",
    54  			DisplayName: "Foo",
    55  			Validation:  "unproven",
    56  		},
    57  	}}
    58  
    59  	req, err := http.NewRequest("GET", "/v2/find?q=hi", nil)
    60  	c.Assert(err, check.IsNil)
    61  
    62  	rsp := s.syncReq(c, req, nil)
    63  
    64  	snaps := snapList(rsp.Result)
    65  	c.Assert(snaps, check.HasLen, 1)
    66  	c.Assert(snaps[0]["name"], check.Equals, "store")
    67  	c.Check(snaps[0]["prices"], check.IsNil)
    68  	c.Check(snaps[0]["channels"], check.IsNil)
    69  
    70  	c.Check(rsp.SuggestedCurrency, check.Equals, "EUR")
    71  
    72  	c.Check(s.storeSearch, check.DeepEquals, store.Search{Query: "hi"})
    73  	c.Check(s.currentSnaps, check.HasLen, 0)
    74  	c.Check(s.actions, check.HasLen, 0)
    75  }
    76  
    77  func (s *findSuite) TestFindRefreshes(c *check.C) {
    78  	s.daemon(c)
    79  
    80  	s.rsnaps = []*snap.Info{{
    81  		SideInfo: snap.SideInfo{
    82  			RealName: "store",
    83  		},
    84  		Publisher: snap.StoreAccount{
    85  			ID:          "foo-id",
    86  			Username:    "foo",
    87  			DisplayName: "Foo",
    88  			Validation:  "unproven",
    89  		},
    90  	}}
    91  	s.mockSnap(c, "name: store\nversion: 1.0")
    92  
    93  	req, err := http.NewRequest("GET", "/v2/find?select=refresh", nil)
    94  	c.Assert(err, check.IsNil)
    95  
    96  	rsp := s.syncReq(c, req, nil)
    97  
    98  	snaps := snapList(rsp.Result)
    99  	c.Assert(snaps, check.HasLen, 1)
   100  	c.Assert(snaps[0]["name"], check.Equals, "store")
   101  	c.Check(s.currentSnaps, check.HasLen, 1)
   102  	c.Check(s.actions, check.HasLen, 1)
   103  }
   104  
   105  func (s *findSuite) TestFindRefreshSideloaded(c *check.C) {
   106  	d := s.daemon(c)
   107  
   108  	s.rsnaps = []*snap.Info{{
   109  		SideInfo: snap.SideInfo{
   110  			RealName: "store",
   111  		},
   112  		Publisher: snap.StoreAccount{
   113  			ID:          "foo-id",
   114  			Username:    "foo",
   115  			DisplayName: "Foo",
   116  			Validation:  "unproven",
   117  		},
   118  	}}
   119  
   120  	s.mockSnap(c, "name: store\nversion: 1.0")
   121  
   122  	var snapst snapstate.SnapState
   123  	st := d.Overlord().State()
   124  	st.Lock()
   125  	err := snapstate.Get(st, "store", &snapst)
   126  	st.Unlock()
   127  	c.Assert(err, check.IsNil)
   128  	c.Assert(snapst.Sequence, check.HasLen, 1)
   129  
   130  	// clear the snapid
   131  	snapst.Sequence[0].SnapID = ""
   132  	st.Lock()
   133  	snapstate.Set(st, "store", &snapst)
   134  	st.Unlock()
   135  
   136  	req, err := http.NewRequest("GET", "/v2/find?select=refresh", nil)
   137  	c.Assert(err, check.IsNil)
   138  
   139  	rsp := s.syncReq(c, req, nil)
   140  
   141  	snaps := snapList(rsp.Result)
   142  	c.Assert(snaps, check.HasLen, 0)
   143  	c.Check(s.currentSnaps, check.HasLen, 0)
   144  	c.Check(s.actions, check.HasLen, 0)
   145  }
   146  
   147  func (s *findSuite) TestFindPrivate(c *check.C) {
   148  	s.daemon(c)
   149  
   150  	s.rsnaps = []*snap.Info{}
   151  
   152  	req, err := http.NewRequest("GET", "/v2/find?q=foo&select=private", nil)
   153  	c.Assert(err, check.IsNil)
   154  
   155  	_ = s.syncReq(c, req, nil)
   156  
   157  	c.Check(s.storeSearch, check.DeepEquals, store.Search{
   158  		Query:   "foo",
   159  		Private: true,
   160  	})
   161  }
   162  
   163  func (s *findSuite) TestFindUserAgentContextCreated(c *check.C) {
   164  	s.daemon(c)
   165  
   166  	req, err := http.NewRequest("GET", "/v2/find", nil)
   167  	c.Assert(err, check.IsNil)
   168  	req.Header.Add("User-Agent", "some-agent/1.0")
   169  
   170  	_ = s.syncReq(c, req, nil)
   171  
   172  	c.Check(store.ClientUserAgent(s.ctx), check.Equals, "some-agent/1.0")
   173  }
   174  
   175  func (s *findSuite) TestFindOneUserAgentContextCreated(c *check.C) {
   176  	s.daemon(c)
   177  
   178  	s.rsnaps = []*snap.Info{{
   179  		SnapType: snap.TypeApp,
   180  		Version:  "v2",
   181  		SideInfo: snap.SideInfo{
   182  			RealName: "banana",
   183  		},
   184  		Publisher: snap.StoreAccount{
   185  			ID:          "foo-id",
   186  			Username:    "foo",
   187  			DisplayName: "Foo",
   188  			Validation:  "unproven",
   189  		},
   190  	}}
   191  	req, err := http.NewRequest("GET", "/v2/find?name=foo", nil)
   192  	c.Assert(err, check.IsNil)
   193  	req.Header.Add("User-Agent", "some-agent/1.0")
   194  
   195  	_ = s.syncReq(c, req, nil)
   196  
   197  	c.Check(store.ClientUserAgent(s.ctx), check.Equals, "some-agent/1.0")
   198  }
   199  
   200  func (s *findSuite) TestFindPrefix(c *check.C) {
   201  	s.daemon(c)
   202  
   203  	s.rsnaps = []*snap.Info{}
   204  
   205  	req, err := http.NewRequest("GET", "/v2/find?name=foo*", nil)
   206  	c.Assert(err, check.IsNil)
   207  
   208  	_ = s.syncReq(c, req, nil)
   209  
   210  	c.Check(s.storeSearch, check.DeepEquals, store.Search{Query: "foo", Prefix: true})
   211  }
   212  
   213  func (s *findSuite) TestFindSection(c *check.C) {
   214  	s.daemon(c)
   215  
   216  	s.rsnaps = []*snap.Info{}
   217  
   218  	req, err := http.NewRequest("GET", "/v2/find?q=foo&section=bar", nil)
   219  	c.Assert(err, check.IsNil)
   220  
   221  	_ = s.syncReq(c, req, nil)
   222  
   223  	c.Check(s.storeSearch, check.DeepEquals, store.Search{
   224  		Query:    "foo",
   225  		Category: "bar",
   226  	})
   227  }
   228  
   229  func (s *findSuite) TestFindScope(c *check.C) {
   230  	s.daemon(c)
   231  
   232  	s.rsnaps = []*snap.Info{}
   233  
   234  	req, err := http.NewRequest("GET", "/v2/find?q=foo&scope=creep", nil)
   235  	c.Assert(err, check.IsNil)
   236  
   237  	_ = s.syncReq(c, req, nil)
   238  
   239  	c.Check(s.storeSearch, check.DeepEquals, store.Search{
   240  		Query: "foo",
   241  		Scope: "creep",
   242  	})
   243  }
   244  
   245  func (s *findSuite) TestFindCommonID(c *check.C) {
   246  	s.daemon(c)
   247  
   248  	s.rsnaps = []*snap.Info{{
   249  		SideInfo: snap.SideInfo{
   250  			RealName: "store",
   251  		},
   252  		Publisher: snap.StoreAccount{
   253  			ID:          "foo-id",
   254  			Username:    "foo",
   255  			DisplayName: "Foo",
   256  			Validation:  "unproven",
   257  		},
   258  		CommonIDs: []string{"org.foo"},
   259  	}}
   260  	s.mockSnap(c, "name: store\nversion: 1.0")
   261  
   262  	req, err := http.NewRequest("GET", "/v2/find?name=foo", nil)
   263  	c.Assert(err, check.IsNil)
   264  
   265  	rsp := s.syncReq(c, req, nil)
   266  
   267  	snaps := snapList(rsp.Result)
   268  	c.Assert(snaps, check.HasLen, 1)
   269  	c.Check(snaps[0]["common-ids"], check.DeepEquals, []interface{}{"org.foo"})
   270  }
   271  
   272  func (s *findSuite) TestFindByCommonID(c *check.C) {
   273  	s.daemon(c)
   274  
   275  	s.rsnaps = []*snap.Info{{
   276  		SideInfo: snap.SideInfo{
   277  			RealName: "store",
   278  		},
   279  		Publisher: snap.StoreAccount{
   280  			ID:          "foo-id",
   281  			Username:    "foo",
   282  			DisplayName: "Foo",
   283  			Validation:  "unproven",
   284  		},
   285  		CommonIDs: []string{"org.foo"},
   286  	}}
   287  	s.mockSnap(c, "name: store\nversion: 1.0")
   288  
   289  	req, err := http.NewRequest("GET", "/v2/find?common-id=org.foo", nil)
   290  	c.Assert(err, check.IsNil)
   291  
   292  	rsp := s.syncReq(c, req, nil)
   293  
   294  	snaps := snapList(rsp.Result)
   295  	c.Assert(snaps, check.HasLen, 1)
   296  	c.Check(s.storeSearch, check.DeepEquals, store.Search{CommonID: "org.foo"})
   297  }
   298  
   299  func (s *findSuite) TestFindOne(c *check.C) {
   300  	s.daemon(c)
   301  
   302  	s.rsnaps = []*snap.Info{{
   303  		SideInfo: snap.SideInfo{
   304  			RealName: "store",
   305  		},
   306  		Base: "base0",
   307  		Publisher: snap.StoreAccount{
   308  			ID:          "foo-id",
   309  			Username:    "foo",
   310  			DisplayName: "Foo",
   311  			Validation:  "verified",
   312  		},
   313  		Channels: map[string]*snap.ChannelSnapInfo{
   314  			"stable": {
   315  				Revision: snap.R(42),
   316  			},
   317  		},
   318  	}}
   319  	s.mockSnap(c, "name: store\nversion: 1.0")
   320  
   321  	req, err := http.NewRequest("GET", "/v2/find?name=foo", nil)
   322  	c.Assert(err, check.IsNil)
   323  
   324  	rsp := s.syncReq(c, req, nil)
   325  
   326  	c.Check(s.storeSearch, check.DeepEquals, store.Search{})
   327  
   328  	snaps := snapList(rsp.Result)
   329  	c.Assert(snaps, check.HasLen, 1)
   330  	c.Check(snaps[0]["name"], check.Equals, "store")
   331  	c.Check(snaps[0]["base"], check.Equals, "base0")
   332  	c.Check(snaps[0]["publisher"], check.DeepEquals, map[string]interface{}{
   333  		"id":           "foo-id",
   334  		"username":     "foo",
   335  		"display-name": "Foo",
   336  		"validation":   "verified",
   337  	})
   338  	m := snaps[0]["channels"].(map[string]interface{})["stable"].(map[string]interface{})
   339  
   340  	c.Check(m["revision"], check.Equals, "42")
   341  }
   342  
   343  func (s *findSuite) TestFindOneNotFound(c *check.C) {
   344  	s.daemon(c)
   345  
   346  	s.err = store.ErrSnapNotFound
   347  	s.mockSnap(c, "name: store\nversion: 1.0")
   348  
   349  	req, err := http.NewRequest("GET", "/v2/find?name=foo", nil)
   350  	c.Assert(err, check.IsNil)
   351  
   352  	rsp := s.errorReq(c, req, nil)
   353  
   354  	c.Check(s.storeSearch, check.DeepEquals, store.Search{})
   355  	c.Check(rsp.Status, check.Equals, 404)
   356  }
   357  
   358  func (s *findSuite) TestFindOneWithAuth(c *check.C) {
   359  	d := s.daemon(c)
   360  
   361  	state := d.Overlord().State()
   362  	state.Lock()
   363  	user, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"})
   364  	state.Unlock()
   365  	c.Check(err, check.IsNil)
   366  
   367  	req, err := http.NewRequest("GET", "/v2/find?q=name:gfoo", nil)
   368  	c.Assert(err, check.IsNil)
   369  
   370  	c.Assert(s.user, check.IsNil)
   371  
   372  	_ = s.syncReq(c, req, user)
   373  
   374  	// ensure user was set
   375  	c.Assert(s.user, check.DeepEquals, user)
   376  }
   377  
   378  func (s *findSuite) TestFindRefreshNotOther(c *check.C) {
   379  	s.daemon(c)
   380  
   381  	for _, other := range []string{"name", "q", "common-id"} {
   382  		req, err := http.NewRequest("GET", "/v2/find?select=refresh&"+other+"=foo*", nil)
   383  		c.Assert(err, check.IsNil)
   384  
   385  		rsp := s.errorReq(c, req, nil)
   386  		c.Check(rsp.Status, check.Equals, 400)
   387  		c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, "cannot use '"+other+"' with 'select=refresh'")
   388  	}
   389  }
   390  
   391  func (s *findSuite) TestFindNotTogether(c *check.C) {
   392  	s.daemon(c)
   393  
   394  	queries := map[string]string{"q": "foo", "name": "foo*", "common-id": "foo"}
   395  	for ki, vi := range queries {
   396  		for kj, vj := range queries {
   397  			if ki == kj {
   398  				continue
   399  			}
   400  
   401  			req, err := http.NewRequest("GET", fmt.Sprintf("/v2/find?%s=%s&%s=%s", ki, vi, kj, vj), nil)
   402  			c.Assert(err, check.IsNil)
   403  
   404  			rsp := s.errorReq(c, req, nil)
   405  			c.Check(rsp.Status, check.Equals, 400)
   406  			exp1 := "cannot use '" + ki + "' and '" + kj + "' together"
   407  			exp2 := "cannot use '" + kj + "' and '" + ki + "' together"
   408  			c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, exp1+"|"+exp2)
   409  		}
   410  	}
   411  }
   412  
   413  func (s *findSuite) TestFindBadQueryReturnsCorrectErrorKind(c *check.C) {
   414  	s.daemon(c)
   415  
   416  	s.err = store.ErrBadQuery
   417  	req, err := http.NewRequest("GET", "/v2/find?q=return-bad-query-please", nil)
   418  	c.Assert(err, check.IsNil)
   419  
   420  	rsp := s.errorReq(c, req, nil)
   421  	c.Check(rsp.Status, check.Equals, 400)
   422  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, "bad query")
   423  	c.Check(rsp.Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindBadQuery)
   424  }
   425  
   426  func (s *findSuite) TestFindPriced(c *check.C) {
   427  	s.daemon(c)
   428  
   429  	s.suggestedCurrency = "GBP"
   430  
   431  	s.rsnaps = []*snap.Info{{
   432  		SnapType: snap.TypeApp,
   433  		Version:  "v2",
   434  		Prices: map[string]float64{
   435  			"GBP": 1.23,
   436  			"EUR": 2.34,
   437  		},
   438  		MustBuy: true,
   439  		SideInfo: snap.SideInfo{
   440  			RealName: "banana",
   441  		},
   442  		Publisher: snap.StoreAccount{
   443  			ID:          "foo-id",
   444  			Username:    "foo",
   445  			DisplayName: "Foo",
   446  			Validation:  "unproven",
   447  		},
   448  	}}
   449  
   450  	req, err := http.NewRequest("GET", "/v2/find?q=banana&channel=stable", nil)
   451  	c.Assert(err, check.IsNil)
   452  	rsp := s.syncReq(c, req, nil)
   453  
   454  	snaps := snapList(rsp.Result)
   455  	c.Assert(snaps, check.HasLen, 1)
   456  
   457  	snap := snaps[0]
   458  	c.Check(snap["name"], check.Equals, "banana")
   459  	c.Check(snap["prices"], check.DeepEquals, map[string]interface{}{
   460  		"EUR": 2.34,
   461  		"GBP": 1.23,
   462  	})
   463  	c.Check(snap["status"], check.Equals, "priced")
   464  
   465  	c.Check(rsp.SuggestedCurrency, check.Equals, "GBP")
   466  }
   467  
   468  func (s *findSuite) TestFindScreenshotted(c *check.C) {
   469  	s.daemon(c)
   470  
   471  	s.rsnaps = []*snap.Info{{
   472  		SnapType: snap.TypeApp,
   473  		Version:  "v2",
   474  		Media: []snap.MediaInfo{
   475  			{
   476  				Type:   "screenshot",
   477  				URL:    "http://example.com/screenshot.png",
   478  				Width:  800,
   479  				Height: 1280,
   480  			},
   481  			{
   482  				Type: "screenshot",
   483  				URL:  "http://example.com/screenshot2.png",
   484  			},
   485  		},
   486  		MustBuy: true,
   487  		SideInfo: snap.SideInfo{
   488  			RealName: "test-screenshot",
   489  		},
   490  		Publisher: snap.StoreAccount{
   491  			ID:          "foo-id",
   492  			Username:    "foo",
   493  			DisplayName: "Foo",
   494  			Validation:  "unproven",
   495  		},
   496  	}}
   497  
   498  	req, err := http.NewRequest("GET", "/v2/find?q=test-screenshot", nil)
   499  	c.Assert(err, check.IsNil)
   500  	rsp := s.syncReq(c, req, nil)
   501  
   502  	snaps := snapList(rsp.Result)
   503  	c.Assert(snaps, check.HasLen, 1)
   504  
   505  	c.Check(snaps[0]["name"], check.Equals, "test-screenshot")
   506  	c.Check(snaps[0]["media"], check.DeepEquals, []interface{}{
   507  		map[string]interface{}{
   508  			"type":   "screenshot",
   509  			"url":    "http://example.com/screenshot.png",
   510  			"width":  float64(800),
   511  			"height": float64(1280),
   512  		},
   513  		map[string]interface{}{
   514  			"type": "screenshot",
   515  			"url":  "http://example.com/screenshot2.png",
   516  		},
   517  	})
   518  }
   519  
   520  func (s *findSuite) TestSnapsStoreConfinement(c *check.C) {
   521  	s.daemon(c)
   522  
   523  	s.rsnaps = []*snap.Info{
   524  		{
   525  			// no explicit confinement in this one
   526  			SideInfo: snap.SideInfo{
   527  				RealName: "foo",
   528  			},
   529  		},
   530  		{
   531  			Confinement: snap.StrictConfinement,
   532  			SideInfo: snap.SideInfo{
   533  				RealName: "bar",
   534  			},
   535  		},
   536  		{
   537  			Confinement: snap.DevModeConfinement,
   538  			SideInfo: snap.SideInfo{
   539  				RealName: "baz",
   540  			},
   541  		},
   542  	}
   543  
   544  	req, err := http.NewRequest("GET", "/v2/find", nil)
   545  	c.Assert(err, check.IsNil)
   546  
   547  	rsp := s.syncReq(c, req, nil)
   548  
   549  	snaps := snapList(rsp.Result)
   550  	c.Assert(snaps, check.HasLen, 3)
   551  
   552  	for i, ss := range [][2]string{
   553  		{"foo", string(snap.StrictConfinement)},
   554  		{"bar", string(snap.StrictConfinement)},
   555  		{"baz", string(snap.DevModeConfinement)},
   556  	} {
   557  		name, mode := ss[0], ss[1]
   558  		c.Check(snaps[i]["name"], check.Equals, name, check.Commentf(name))
   559  		c.Check(snaps[i]["confinement"], check.Equals, mode, check.Commentf(name))
   560  	}
   561  }