github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap/cmd_find_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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 main_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"net/http"
    26  	"os"
    27  	"path"
    28  
    29  	"github.com/jessevdk/go-flags"
    30  	"gopkg.in/check.v1"
    31  
    32  	snap "github.com/snapcore/snapd/cmd/snap"
    33  	"github.com/snapcore/snapd/dirs"
    34  )
    35  
    36  const findJSON = `
    37  {
    38    "type": "sync",
    39    "status-code": 200,
    40    "status": "OK",
    41    "result": [
    42      {
    43        "channel": "stable",
    44        "confinement": "strict",
    45        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
    46        "developer": "canonical",
    47        "publisher": {
    48           "id": "canonical",
    49           "username": "canonical",
    50           "display-name": "Canonical",
    51           "validation": "verified"
    52        },
    53        "download-size": 65536,
    54        "icon": "",
    55        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
    56        "name": "hello",
    57        "private": false,
    58        "resource": "/v2/snaps/hello",
    59        "revision": "1",
    60        "status": "available",
    61        "summary": "GNU Hello, the \"hello world\" snap",
    62        "type": "app",
    63        "version": "2.10"
    64      },
    65      {
    66        "channel": "stable",
    67        "confinement": "strict",
    68        "description": "This is a simple hello world example.",
    69        "developer": "canonical",
    70        "publisher": {
    71           "id": "canonical",
    72           "username": "canonical",
    73           "display-name": "Canonical",
    74           "validation": "verified"
    75        },
    76        "download-size": 20480,
    77        "icon": "",
    78        "id": "buPKUD3TKqCOgLEjjHx5kSiCpIs5cMuQ",
    79        "name": "hello-world",
    80        "private": false,
    81        "resource": "/v2/snaps/hello-world",
    82        "revision": "26",
    83        "status": "available",
    84        "summary": "Hello world example",
    85        "type": "app",
    86        "version": "6.1"
    87      },
    88      {
    89        "channel": "stable",
    90        "confinement": "strict",
    91        "description": "1.0GB",
    92        "developer": "noise",
    93        "publisher": {
    94           "id": "noise-id",
    95           "username": "noise",
    96           "display-name": "Bret",
    97           "validation": "unproven"
    98        },
    99        "download-size": 512004096,
   100        "icon": "",
   101        "id": "asXOGCreK66DIAdyXmucwspTMgqA4rne",
   102        "name": "hello-huge",
   103        "private": false,
   104        "resource": "/v2/snaps/hello-huge",
   105        "revision": "1",
   106        "status": "available",
   107        "summary": "a really big snap",
   108        "type": "app",
   109        "version": "1.0"
   110      }
   111    ],
   112    "sources": [
   113      "store"
   114    ],
   115    "suggested-currency": "GBP"
   116  }
   117  `
   118  
   119  func (s *SnapSuite) TestFindSnapName(c *check.C) {
   120  	n := 0
   121  
   122  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   123  		switch n {
   124  		case 0:
   125  			c.Check(r.Method, check.Equals, "GET")
   126  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   127  			q := r.URL.Query()
   128  			if q.Get("q") == "" {
   129  				v, ok := q["section"]
   130  				c.Check(ok, check.Equals, true)
   131  				c.Check(v, check.DeepEquals, []string{""})
   132  			}
   133  			fmt.Fprintln(w, findJSON)
   134  		default:
   135  			c.Fatalf("expected to get 2 requests, now on %d", n+1)
   136  		}
   137  		n++
   138  	})
   139  
   140  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"})
   141  
   142  	c.Assert(err, check.IsNil)
   143  	c.Assert(rest, check.DeepEquals, []string{})
   144  
   145  	c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary
   146  hello +2.10 +canonical\* +- +GNU Hello, the "hello world" snap
   147  hello-world +6.1 +canonical\* +- +Hello world example
   148  hello-huge +1.0 +noise +- +a really big snap
   149  `)
   150  	c.Check(s.Stderr(), check.Equals, "")
   151  
   152  	s.ResetStdStreams()
   153  }
   154  
   155  const findHelloJSON = `
   156  {
   157    "type": "sync",
   158    "status-code": 200,
   159    "status": "OK",
   160    "result": [
   161      {
   162        "channel": "stable",
   163        "confinement": "strict",
   164        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
   165        "developer": "canonical",
   166        "publisher": {
   167           "id": "canonical",
   168           "username": "canonical",
   169           "display-name": "Canonical",
   170           "validation": "verified"
   171        },
   172        "download-size": 65536,
   173        "icon": "",
   174        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
   175        "name": "hello",
   176        "private": false,
   177        "resource": "/v2/snaps/hello",
   178        "revision": "1",
   179        "status": "available",
   180        "summary": "GNU Hello, the \"hello world\" snap",
   181        "type": "app",
   182        "version": "2.10"
   183      },
   184      {
   185        "channel": "stable",
   186        "confinement": "strict",
   187        "description": "1.0GB",
   188        "developer": "noise",
   189        "publisher": {
   190           "id": "noise-id",
   191           "username": "noise",
   192           "display-name": "Bret",
   193           "validation": "unproven"
   194        },
   195        "download-size": 512004096,
   196        "icon": "",
   197        "id": "asXOGCreK66DIAdyXmucwspTMgqA4rne",
   198        "name": "hello-huge",
   199        "private": false,
   200        "resource": "/v2/snaps/hello-huge",
   201        "revision": "1",
   202        "status": "available",
   203        "summary": "a really big snap",
   204        "type": "app",
   205        "version": "1.0"
   206      }
   207    ],
   208    "sources": [
   209      "store"
   210    ],
   211    "suggested-currency": "GBP"
   212  }
   213  `
   214  
   215  func (s *SnapSuite) TestFindHello(c *check.C) {
   216  	n := 0
   217  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   218  		switch n {
   219  		case 0:
   220  			c.Check(r.Method, check.Equals, "GET")
   221  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   222  			q := r.URL.Query()
   223  			c.Check(q, check.HasLen, 2)
   224  			c.Check(q.Get("q"), check.Equals, "hello")
   225  			c.Check(q.Get("scope"), check.Equals, "wide")
   226  			fmt.Fprintln(w, findHelloJSON)
   227  		default:
   228  			c.Fatalf("expected to get 1 requests, now on %d", n+1)
   229  		}
   230  
   231  		n++
   232  	})
   233  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"})
   234  	c.Assert(err, check.IsNil)
   235  	c.Assert(rest, check.DeepEquals, []string{})
   236  	c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary
   237  hello +2.10 +canonical\* +- +GNU Hello, the "hello world" snap
   238  hello-huge +1.0 +noise +- +a really big snap
   239  `)
   240  	c.Check(s.Stderr(), check.Equals, "")
   241  }
   242  
   243  func (s *SnapSuite) TestFindHelloNarrow(c *check.C) {
   244  	n := 0
   245  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   246  		switch n {
   247  		case 0:
   248  			c.Check(r.Method, check.Equals, "GET")
   249  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   250  			q := r.URL.Query()
   251  			c.Check(q, check.HasLen, 1)
   252  			c.Check(q.Get("q"), check.Equals, "hello")
   253  			fmt.Fprintln(w, findHelloJSON)
   254  		default:
   255  			c.Fatalf("expected to get 1 requests, now on %d", n+1)
   256  		}
   257  
   258  		n++
   259  	})
   260  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--narrow", "hello"})
   261  	c.Assert(err, check.IsNil)
   262  	c.Assert(rest, check.DeepEquals, []string{})
   263  	c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary
   264  hello +2.10 +canonical\* +- +GNU Hello, the "hello world" snap
   265  hello-huge +1.0 +noise +- +a really big snap
   266  `)
   267  	c.Check(s.Stderr(), check.Equals, "")
   268  }
   269  
   270  const findPricedJSON = `
   271  {
   272    "type": "sync",
   273    "status-code": 200,
   274    "status": "OK",
   275    "result": [
   276      {
   277        "channel": "stable",
   278        "confinement": "strict",
   279        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
   280        "developer": "canonical",
   281        "publisher": {
   282           "id": "canonical",
   283           "username": "canonical",
   284           "display-name": "Canonical",
   285           "validation": "verified"
   286        },
   287        "download-size": 65536,
   288        "icon": "",
   289        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
   290        "name": "hello",
   291        "prices": {"GBP": 1.99, "USD": 2.99},
   292        "private": false,
   293        "resource": "/v2/snaps/hello",
   294        "revision": "1",
   295        "status": "priced",
   296        "summary": "GNU Hello, the \"hello world\" snap",
   297        "type": "app",
   298        "version": "2.10",
   299        "license": "Proprietary"
   300      }
   301    ],
   302    "sources": [
   303      "store"
   304    ],
   305    "suggested-currency": "GBP"
   306  }
   307  `
   308  
   309  func (s *SnapSuite) TestFindPriced(c *check.C) {
   310  	n := 0
   311  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   312  		switch n {
   313  		case 0:
   314  			c.Check(r.Method, check.Equals, "GET")
   315  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   316  			fmt.Fprintln(w, findPricedJSON)
   317  		default:
   318  			c.Fatalf("expected to get 1 requests, now on %d", n+1)
   319  		}
   320  
   321  		n++
   322  	})
   323  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"})
   324  	c.Assert(err, check.IsNil)
   325  	c.Assert(rest, check.DeepEquals, []string{})
   326  	c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary
   327  hello +2.10 +canonical\* +1.99GBP +GNU Hello, the "hello world" snap
   328  `)
   329  	c.Check(s.Stderr(), check.Equals, "")
   330  }
   331  
   332  const findPricedAndBoughtJSON = `
   333  {
   334    "type": "sync",
   335    "status-code": 200,
   336    "status": "OK",
   337    "result": [
   338      {
   339        "channel": "stable",
   340        "confinement": "strict",
   341        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
   342        "developer": "canonical",
   343        "publisher": {
   344           "id": "canonical",
   345           "username": "canonical",
   346           "display-name": "Canonical",
   347           "validation": "verified"
   348        },
   349        "download-size": 65536,
   350        "icon": "",
   351        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
   352        "name": "hello",
   353        "prices": {"GBP": 1.99, "USD": 2.99},
   354        "private": false,
   355        "resource": "/v2/snaps/hello",
   356        "revision": "1",
   357        "status": "available",
   358        "summary": "GNU Hello, the \"hello world\" snap",
   359        "type": "app",
   360        "version": "2.10"
   361      }
   362    ],
   363    "sources": [
   364      "store"
   365    ],
   366    "suggested-currency": "GBP"
   367  }
   368  `
   369  
   370  func (s *SnapSuite) TestFindPricedAndBought(c *check.C) {
   371  	n := 0
   372  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   373  		switch n {
   374  		case 0:
   375  			c.Check(r.Method, check.Equals, "GET")
   376  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   377  			fmt.Fprintln(w, findPricedAndBoughtJSON)
   378  		default:
   379  			c.Fatalf("expected to get 1 requests, now on %d", n+1)
   380  		}
   381  
   382  		n++
   383  	})
   384  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"})
   385  	c.Assert(err, check.IsNil)
   386  	c.Assert(rest, check.DeepEquals, []string{})
   387  	c.Check(s.Stdout(), check.Matches, `Name +Version +Publisher +Notes +Summary
   388  hello +2.10 +canonical\* +bought +GNU Hello, the "hello world" snap
   389  `)
   390  	c.Check(s.Stderr(), check.Equals, "")
   391  }
   392  
   393  func (s *SnapSuite) TestFindNothingMeansFeaturedSection(c *check.C) {
   394  	n := 0
   395  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   396  		switch n {
   397  		case 0:
   398  			c.Check(r.Method, check.Equals, "GET")
   399  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   400  			c.Check(r.URL.Query().Get("section"), check.Equals, "featured")
   401  			fmt.Fprintln(w, findJSON)
   402  		default:
   403  			c.Fatalf("expected to get 1 request, now on %d", n+1)
   404  		}
   405  		n++
   406  	})
   407  
   408  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"find"})
   409  	c.Assert(err, check.IsNil)
   410  	c.Check(s.Stderr(), check.Equals, "")
   411  	c.Check(n, check.Equals, 1)
   412  }
   413  
   414  func (s *SnapSuite) TestSectionCompletion(c *check.C) {
   415  	n := 0
   416  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   417  		switch n {
   418  		case 0, 1:
   419  			c.Check(r.Method, check.Equals, "GET")
   420  			c.Check(r.URL.Path, check.Equals, "/v2/sections")
   421  			EncodeResponseBody(c, w, map[string]interface{}{
   422  				"type":   "sync",
   423  				"result": []string{"foo", "bar", "baz"},
   424  			})
   425  		default:
   426  			c.Fatalf("expected to get 2 requests, now on #%d", n+1)
   427  		}
   428  		n++
   429  	})
   430  
   431  	c.Check(snap.SectionName("").Complete(""), check.DeepEquals, []flags.Completion{
   432  		{Item: "foo"},
   433  		{Item: "bar"},
   434  		{Item: "baz"},
   435  	})
   436  
   437  	c.Check(snap.SectionName("").Complete("f"), check.DeepEquals, []flags.Completion{
   438  		{Item: "foo"},
   439  	})
   440  }
   441  
   442  const findNetworkTimeoutErrorJSON = `
   443  {
   444    "type": "error",
   445    "result": {
   446      "message": "Get https://search.apps.ubuntu.com/api/v1/snaps/search?confinement=strict%2Cclassic&fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha3_384%2Csummary%2Cdescription%2Cdeltas%2Cbinary_filesize%2Cdownload_url%2Cepoch%2Cicon_url%2Clast_updated%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Csupport_url%2Ccontact%2Ctitle%2Ccontent%2Cversion%2Corigin%2Cdeveloper_id%2Cprivate%2Cconfinement%2Cchannel_maps_list&q=test: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)",
   447      "kind": "network-timeout"
   448    },
   449    "status-code": 400
   450  }`
   451  
   452  func (s *SnapSuite) TestFindNetworkTimeoutError(c *check.C) {
   453  	n := 0
   454  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   455  		switch n {
   456  		case 0:
   457  			c.Check(r.Method, check.Equals, "GET")
   458  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   459  			fmt.Fprintln(w, findNetworkTimeoutErrorJSON)
   460  		default:
   461  			c.Fatalf("expected to get 1 requests, now on %d", n+1)
   462  		}
   463  
   464  		n++
   465  	})
   466  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "hello"})
   467  	c.Assert(err, check.ErrorMatches, `unable to contact snap store`)
   468  	c.Check(s.Stdout(), check.Equals, "")
   469  }
   470  
   471  func (s *SnapSuite) TestFindSnapSectionOverview(c *check.C) {
   472  	n := 0
   473  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   474  		switch n {
   475  		case 0, 1:
   476  			c.Check(r.Method, check.Equals, "GET")
   477  			c.Check(r.URL.Path, check.Equals, "/v2/sections")
   478  			EncodeResponseBody(c, w, map[string]interface{}{
   479  				"type":   "sync",
   480  				"result": []string{"sec2", "sec1"},
   481  			})
   482  		default:
   483  			c.Fatalf("expected to get 2 requests, now on #%d", n+1)
   484  		}
   485  		n++
   486  	})
   487  
   488  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section"})
   489  
   490  	c.Assert(err, check.IsNil)
   491  	c.Assert(rest, check.DeepEquals, []string{})
   492  
   493  	c.Check(s.Stdout(), check.Equals, `No section specified. Available sections:
   494   * sec1
   495   * sec2
   496  Please try 'snap find --section=<selected section>'
   497  `)
   498  	c.Check(s.Stderr(), check.Equals, "")
   499  
   500  	s.ResetStdStreams()
   501  }
   502  
   503  func (s *SnapSuite) TestFindSnapInvalidSection(c *check.C) {
   504  	n := 0
   505  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   506  		switch n {
   507  		case 0:
   508  			c.Check(r.Method, check.Equals, "GET")
   509  			c.Check(r.URL.Path, check.Equals, "/v2/sections")
   510  			EncodeResponseBody(c, w, map[string]interface{}{
   511  				"type":   "sync",
   512  				"result": []string{"sec1"},
   513  			})
   514  		default:
   515  			c.Fatalf("expected to get 1 request, now on %d", n+1)
   516  		}
   517  
   518  		n++
   519  	})
   520  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section=foobar", "hello"})
   521  	c.Assert(err, check.ErrorMatches, `No matching section "foobar", use --section to list existing sections`)
   522  }
   523  
   524  func (s *SnapSuite) TestFindSnapNotFoundInSection(c *check.C) {
   525  	n := 0
   526  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   527  		switch n {
   528  		case 0:
   529  			c.Check(r.Method, check.Equals, "GET")
   530  			c.Check(r.URL.Path, check.Equals, "/v2/sections")
   531  			EncodeResponseBody(c, w, map[string]interface{}{
   532  				"type":   "sync",
   533  				"result": []string{"foobar"},
   534  			})
   535  		case 1:
   536  			c.Check(r.Method, check.Equals, "GET")
   537  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   538  			v, ok := r.URL.Query()["section"]
   539  			c.Check(ok, check.Equals, true)
   540  			c.Check(v, check.DeepEquals, []string{"foobar"})
   541  			EncodeResponseBody(c, w, map[string]interface{}{
   542  				"type":   "sync",
   543  				"result": []string{},
   544  			})
   545  		default:
   546  			c.Fatalf("expected to get 2 requests, now on #%d", n+1)
   547  		}
   548  		n++
   549  	})
   550  
   551  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section=foobar", "hello"})
   552  	c.Assert(err, check.IsNil)
   553  	c.Check(s.Stderr(), check.Equals, "No matching snaps for \"hello\" in section \"foobar\"\n")
   554  	c.Check(s.Stdout(), check.Equals, "")
   555  
   556  	s.ResetStdStreams()
   557  }
   558  
   559  func (s *SnapSuite) TestFindSnapCachedSection(c *check.C) {
   560  	numHits := 0
   561  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   562  		numHits++
   563  		c.Check(numHits, check.Equals, 1)
   564  		c.Check(r.URL.Path, check.Equals, "/v2/sections")
   565  		EncodeResponseBody(c, w, map[string]interface{}{
   566  			"type":   "sync",
   567  			"result": []string{"sec1", "sec2", "sec3"},
   568  		})
   569  	})
   570  
   571  	os.MkdirAll(path.Dir(dirs.SnapSectionsFile), 0755)
   572  	ioutil.WriteFile(dirs.SnapSectionsFile, []byte("sec1\nsec2\nsec3"), 0644)
   573  
   574  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section=foobar", "hello"})
   575  	c.Logf("stdout: %s", s.Stdout())
   576  	c.Assert(err, check.ErrorMatches, `No matching section "foobar", use --section to list existing sections`)
   577  
   578  	s.ResetStdStreams()
   579  
   580  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"find", "--section"})
   581  
   582  	c.Assert(err, check.IsNil)
   583  	c.Assert(rest, check.DeepEquals, []string{})
   584  
   585  	c.Check(s.Stdout(), check.Equals, `No section specified. Available sections:
   586   * sec1
   587   * sec2
   588   * sec3
   589  Please try 'snap find --section=<selected section>'
   590  `)
   591  
   592  	s.ResetStdStreams()
   593  	c.Check(numHits, check.Equals, 1)
   594  }