github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/cmd/snap/cmd_info_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  	"bytes"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"net/http"
    27  	"path/filepath"
    28  	"time"
    29  
    30  	"gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/client"
    33  	snap "github.com/snapcore/snapd/cmd/snap"
    34  	snaplib "github.com/snapcore/snapd/snap"
    35  	"github.com/snapcore/snapd/snap/snaptest"
    36  	"github.com/snapcore/snapd/snap/squashfs"
    37  )
    38  
    39  var cmdAppInfos = []client.AppInfo{{Name: "app1"}, {Name: "app2"}}
    40  var svcAppInfos = []client.AppInfo{
    41  	{
    42  		Name:    "svc1",
    43  		Daemon:  "simple",
    44  		Enabled: false,
    45  		Active:  true,
    46  	},
    47  	{
    48  		Name:    "svc2",
    49  		Daemon:  "simple",
    50  		Enabled: true,
    51  		Active:  false,
    52  	},
    53  }
    54  
    55  var mixedAppInfos = append(append([]client.AppInfo(nil), cmdAppInfos...), svcAppInfos...)
    56  
    57  type infoSuite struct {
    58  	BaseSnapSuite
    59  }
    60  
    61  var _ = check.Suite(&infoSuite{})
    62  
    63  type flushBuffer struct{ bytes.Buffer }
    64  
    65  func (*flushBuffer) Flush() error { return nil }
    66  
    67  func (s *infoSuite) TestMaybePrintServices(c *check.C) {
    68  	var buf flushBuffer
    69  	iw := snap.NewInfoWriter(&buf)
    70  	for _, infos := range [][]client.AppInfo{svcAppInfos, mixedAppInfos} {
    71  		buf.Reset()
    72  		snap.SetupDiskSnap(iw, "", &client.Snap{Name: "foo", Apps: infos})
    73  		snap.MaybePrintServices(iw)
    74  
    75  		c.Check(buf.String(), check.Equals, `services:
    76    foo.svc1:	simple, disabled, active
    77    foo.svc2:	simple, enabled, inactive
    78  `)
    79  	}
    80  }
    81  
    82  func (s *infoSuite) TestMaybePrintServicesNoServices(c *check.C) {
    83  	var buf flushBuffer
    84  	iw := snap.NewInfoWriter(&buf)
    85  	for _, infos := range [][]client.AppInfo{cmdAppInfos, nil} {
    86  		buf.Reset()
    87  		snap.SetupDiskSnap(iw, "", &client.Snap{Name: "foo", Apps: infos})
    88  		snap.MaybePrintServices(iw)
    89  		c.Check(buf.String(), check.Equals, "")
    90  	}
    91  }
    92  
    93  func (s *infoSuite) TestMaybePrintCommands(c *check.C) {
    94  	var buf flushBuffer
    95  	iw := snap.NewInfoWriter(&buf)
    96  	for _, infos := range [][]client.AppInfo{cmdAppInfos, mixedAppInfos} {
    97  		buf.Reset()
    98  		snap.SetupDiskSnap(iw, "", &client.Snap{Name: "foo", Apps: infos})
    99  		snap.MaybePrintCommands(iw)
   100  
   101  		c.Check(buf.String(), check.Equals, `commands:
   102    - foo.app1
   103    - foo.app2
   104  `)
   105  	}
   106  }
   107  
   108  func (s *infoSuite) TestMaybePrintCommandsNoCommands(c *check.C) {
   109  	var buf flushBuffer
   110  	iw := snap.NewInfoWriter(&buf)
   111  	for _, infos := range [][]client.AppInfo{svcAppInfos, nil} {
   112  		buf.Reset()
   113  		snap.SetupDiskSnap(iw, "", &client.Snap{Name: "foo", Apps: infos})
   114  		snap.MaybePrintCommands(iw)
   115  
   116  		c.Check(buf.String(), check.Equals, "")
   117  	}
   118  }
   119  
   120  func (infoSuite) TestPrintType(c *check.C) {
   121  	var buf flushBuffer
   122  	iw := snap.NewInfoWriter(&buf)
   123  	for from, to := range map[string]string{
   124  		"":            "",
   125  		"app":         "",
   126  		"application": "",
   127  		"gadget":      "type:\tgadget\n",
   128  		"core":        "type:\tcore\n",
   129  		"os":          "type:\tcore\n",
   130  	} {
   131  		buf.Reset()
   132  		snap.SetupDiskSnap(iw, "", &client.Snap{Type: from})
   133  		snap.MaybePrintType(iw)
   134  		c.Check(buf.String(), check.Equals, to, check.Commentf("%q", from))
   135  	}
   136  }
   137  
   138  func (infoSuite) TestPrintSummary(c *check.C) {
   139  	var buf flushBuffer
   140  	iw := snap.NewInfoWriter(&buf)
   141  	for from, to := range map[string]string{
   142  		"":               `""`,                // empty results in quoted empty
   143  		"foo":            "foo",               // plain text results in unquoted
   144  		"two words":      "two words",         // ...even when multi-word
   145  		"{":              `"{"`,               // but yaml-breaking is quoted
   146  		"very long text": "very long\n  text", // too-long text gets split (TODO: split with tabbed indent to preserve alignment)
   147  	} {
   148  		buf.Reset()
   149  		snap.SetupDiskSnap(iw, "", &client.Snap{Summary: from})
   150  		snap.PrintSummary(iw)
   151  		c.Check(buf.String(), check.Equals, "summary:\t"+to+"\n", check.Commentf("%q", from))
   152  	}
   153  }
   154  
   155  func (s *infoSuite) TestMaybePrintPublisher(c *check.C) {
   156  	acct := &snaplib.StoreAccount{
   157  		Validation:  "verified",
   158  		Username:    "team-potato",
   159  		DisplayName: "Team Potato",
   160  	}
   161  
   162  	type T struct {
   163  		diskSnap, localSnap *client.Snap
   164  		expected            string
   165  	}
   166  
   167  	var buf flushBuffer
   168  	iw := snap.NewInfoWriter(&buf)
   169  	for i, t := range []T{
   170  		{&client.Snap{}, nil, ""},                 // nothing output for on-disk snap
   171  		{nil, &client.Snap{}, "publisher:\t--\n"}, // from-snapd snap with no publisher is explicit
   172  		{nil, &client.Snap{Publisher: acct}, "publisher:\tTeam Potato*\n"},
   173  	} {
   174  		buf.Reset()
   175  		if t.diskSnap == nil {
   176  			snap.SetupSnap(iw, t.localSnap, nil, nil)
   177  		} else {
   178  			snap.SetupDiskSnap(iw, "", t.diskSnap)
   179  		}
   180  		snap.MaybePrintPublisher(iw)
   181  		c.Check(buf.String(), check.Equals, t.expected, check.Commentf("%d", i))
   182  	}
   183  }
   184  
   185  func (s *infoSuite) TestMaybePrintNotes(c *check.C) {
   186  	type T struct {
   187  		localSnap, diskSnap *client.Snap
   188  		expected            string
   189  	}
   190  
   191  	var buf flushBuffer
   192  	iw := snap.NewInfoWriter(&buf)
   193  	for i, t := range []T{
   194  		{
   195  			nil,
   196  			&client.Snap{Private: true, Confinement: "devmode"},
   197  			"notes:\t\n" +
   198  				"  private:\ttrue\n" +
   199  				"  confinement:\tdevmode\n",
   200  		}, {
   201  			&client.Snap{Private: true, Confinement: "devmode"},
   202  			nil,
   203  			"notes:\t\n" +
   204  				"  private:\ttrue\n" +
   205  				"  confinement:\tdevmode\n" +
   206  				"  devmode:\tfalse\n" +
   207  				"  jailmode:\ttrue\n" +
   208  				"  trymode:\tfalse\n" +
   209  				"  enabled:\tfalse\n" +
   210  				"  broken:\tfalse\n" +
   211  				"  ignore-validation:\tfalse\n",
   212  		}, {
   213  			&client.Snap{Private: true, Confinement: "devmode", Broken: "ouch"},
   214  			nil,
   215  			"notes:\t\n" +
   216  				"  private:\ttrue\n" +
   217  				"  confinement:\tdevmode\n" +
   218  				"  devmode:\tfalse\n" +
   219  				"  jailmode:\ttrue\n" +
   220  				"  trymode:\tfalse\n" +
   221  				"  enabled:\tfalse\n" +
   222  				"  broken:\ttrue (ouch)\n" +
   223  				"  ignore-validation:\tfalse\n",
   224  		},
   225  	} {
   226  		buf.Reset()
   227  		snap.SetVerbose(iw, false)
   228  		if t.diskSnap == nil {
   229  			snap.SetupSnap(iw, t.localSnap, nil, nil)
   230  		} else {
   231  			snap.SetupDiskSnap(iw, "", t.diskSnap)
   232  		}
   233  		snap.MaybePrintNotes(iw)
   234  		c.Check(buf.String(), check.Equals, "", check.Commentf("%d/false", i))
   235  
   236  		buf.Reset()
   237  		snap.SetVerbose(iw, true)
   238  		snap.MaybePrintNotes(iw)
   239  		c.Check(buf.String(), check.Equals, t.expected, check.Commentf("%d/true", i))
   240  	}
   241  }
   242  
   243  func (s *infoSuite) TestMaybePrintStandaloneVersion(c *check.C) {
   244  	var buf flushBuffer
   245  	iw := snap.NewInfoWriter(&buf)
   246  
   247  	// no disk snap -> no version
   248  	snap.MaybePrintStandaloneVersion(iw)
   249  	c.Check(buf.String(), check.Equals, "")
   250  
   251  	for version, expected := range map[string]string{
   252  		"":    "--",
   253  		"4.2": "4.2",
   254  	} {
   255  		buf.Reset()
   256  		snap.SetupDiskSnap(iw, "", &client.Snap{Version: version})
   257  		snap.MaybePrintStandaloneVersion(iw)
   258  		c.Check(buf.String(), check.Equals, "version:\t"+expected+" -\n", check.Commentf("%q", version))
   259  
   260  		buf.Reset()
   261  		snap.SetupDiskSnap(iw, "", &client.Snap{Version: version, Confinement: "devmode"})
   262  		snap.MaybePrintStandaloneVersion(iw)
   263  		c.Check(buf.String(), check.Equals, "version:\t"+expected+" devmode\n", check.Commentf("%q", version))
   264  	}
   265  }
   266  
   267  func (s *infoSuite) TestMaybePrintBuildDate(c *check.C) {
   268  	var buf flushBuffer
   269  	iw := snap.NewInfoWriter(&buf)
   270  	// some prep
   271  	dir := c.MkDir()
   272  	arbfile := filepath.Join(dir, "arb")
   273  	c.Assert(ioutil.WriteFile(arbfile, nil, 0600), check.IsNil)
   274  	filename := filepath.Join(c.MkDir(), "foo.snap")
   275  	diskSnap := squashfs.New(filename)
   276  	c.Assert(diskSnap.Build(dir, nil), check.IsNil)
   277  	buildDate := diskSnap.BuildDate().Format(time.Kitchen)
   278  
   279  	// no disk snap -> no build date
   280  	snap.MaybePrintBuildDate(iw)
   281  	c.Check(buf.String(), check.Equals, "")
   282  
   283  	// path is directory -> no build date
   284  	buf.Reset()
   285  	snap.SetupDiskSnap(iw, dir, &client.Snap{})
   286  	snap.MaybePrintBuildDate(iw)
   287  	c.Check(buf.String(), check.Equals, "")
   288  
   289  	// not actually a snap -> no build date
   290  	buf.Reset()
   291  	snap.SetupDiskSnap(iw, arbfile, &client.Snap{})
   292  	snap.MaybePrintBuildDate(iw)
   293  	c.Check(buf.String(), check.Equals, "")
   294  
   295  	// disk snap -> get build date
   296  	buf.Reset()
   297  	snap.SetupDiskSnap(iw, filename, &client.Snap{})
   298  	snap.MaybePrintBuildDate(iw)
   299  	c.Check(buf.String(), check.Equals, "build-date:\t"+buildDate+"\n")
   300  }
   301  
   302  func (s *infoSuite) TestMaybePrintSum(c *check.C) {
   303  	var buf flushBuffer
   304  	// some prep
   305  	dir := c.MkDir()
   306  	filename := filepath.Join(c.MkDir(), "foo.snap")
   307  	diskSnap := squashfs.New(filename)
   308  	c.Assert(diskSnap.Build(dir, nil), check.IsNil)
   309  	iw := snap.NewInfoWriter(&buf)
   310  	snap.SetVerbose(iw, true)
   311  
   312  	// no disk snap -> no checksum
   313  	snap.MaybePrintSum(iw)
   314  	c.Check(buf.String(), check.Equals, "")
   315  
   316  	// path is directory -> no checksum
   317  	buf.Reset()
   318  	snap.SetupDiskSnap(iw, dir, &client.Snap{})
   319  	snap.MaybePrintSum(iw)
   320  	c.Check(buf.String(), check.Equals, "")
   321  
   322  	// disk snap and verbose -> get checksum
   323  	buf.Reset()
   324  	snap.SetupDiskSnap(iw, filename, &client.Snap{})
   325  	snap.MaybePrintSum(iw)
   326  	c.Check(buf.String(), check.Matches, "sha3-384:\t\\S+\n")
   327  
   328  	// disk snap but not verbose -> no checksum
   329  	buf.Reset()
   330  	snap.SetVerbose(iw, false)
   331  	snap.MaybePrintSum(iw)
   332  	c.Check(buf.String(), check.Equals, "")
   333  }
   334  
   335  func (s *infoSuite) TestMaybePrintContact(c *check.C) {
   336  	var buf flushBuffer
   337  	iw := snap.NewInfoWriter(&buf)
   338  
   339  	for contact, expected := range map[string]string{
   340  		"mailto:joe@example.com": "contact:\tjoe@example.com\n",
   341  		// gofmt 1.9 being silly
   342  		"foo": "contact:\tfoo\n",
   343  		"":    "",
   344  	} {
   345  		buf.Reset()
   346  		snap.SetupDiskSnap(iw, "", &client.Snap{Contact: contact})
   347  		snap.MaybePrintContact(iw)
   348  		c.Check(buf.String(), check.Equals, expected, check.Commentf("%q", contact))
   349  	}
   350  }
   351  
   352  func (s *infoSuite) TestMaybePrintBase(c *check.C) {
   353  	var buf flushBuffer
   354  	iw := snap.NewInfoWriter(&buf)
   355  	dSnap := &client.Snap{}
   356  	snap.SetupDiskSnap(iw, "", dSnap)
   357  
   358  	// no verbose -> no base
   359  	snap.SetVerbose(iw, false)
   360  	snap.MaybePrintBase(iw)
   361  	c.Check(buf.String(), check.Equals, "")
   362  	buf.Reset()
   363  
   364  	// no base -> no base :)
   365  	snap.SetVerbose(iw, true)
   366  	snap.MaybePrintBase(iw)
   367  	c.Check(buf.String(), check.Equals, "")
   368  	buf.Reset()
   369  
   370  	// base + verbose -> base
   371  	dSnap.Base = "xyzzy"
   372  	snap.MaybePrintBase(iw)
   373  	c.Check(buf.String(), check.Equals, "base:\txyzzy\n")
   374  	buf.Reset()
   375  }
   376  
   377  func (s *infoSuite) TestMaybePrintPath(c *check.C) {
   378  	var buf flushBuffer
   379  	iw := snap.NewInfoWriter(&buf)
   380  	dSnap := &client.Snap{}
   381  
   382  	// no path -> no path
   383  	snap.SetupDiskSnap(iw, "", dSnap)
   384  	snap.MaybePrintPath(iw)
   385  	c.Check(buf.String(), check.Equals, "")
   386  	buf.Reset()
   387  
   388  	// path -> path (quoted!)
   389  	snap.SetupDiskSnap(iw, "xyzzy", dSnap)
   390  	snap.MaybePrintPath(iw)
   391  	c.Check(buf.String(), check.Equals, "path:\t\"xyzzy\"\n")
   392  	buf.Reset()
   393  }
   394  
   395  func (s *infoSuite) TestClientSnapFromPath(c *check.C) {
   396  	// minimal sanity check
   397  	fn := snaptest.MakeTestSnapWithFiles(c, `
   398  name: some-snap
   399  version: 9
   400  `, nil)
   401  	dSnap, err := snap.ClientSnapFromPath(fn)
   402  	c.Assert(err, check.IsNil)
   403  	c.Check(dSnap.Version, check.Equals, "9")
   404  }
   405  
   406  func (s *infoSuite) TestInfoPricedNarrowTerminal(c *check.C) {
   407  	defer snap.MockTermSize(func() (int, int) { return 44, 25 })()
   408  
   409  	n := 0
   410  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   411  		switch n {
   412  		case 0:
   413  			c.Check(r.Method, check.Equals, "GET")
   414  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   415  			fmt.Fprintln(w, findPricedJSON)
   416  		case 1:
   417  			c.Check(r.Method, check.Equals, "GET")
   418  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   419  			fmt.Fprintln(w, "{}")
   420  		default:
   421  			c.Fatalf("expected to get 1 requests, now on %d (%v)", n+1, r)
   422  		}
   423  
   424  		n++
   425  	})
   426  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"})
   427  	c.Assert(err, check.IsNil)
   428  	c.Assert(rest, check.DeepEquals, []string{})
   429  	c.Check(s.Stdout(), check.Equals, `
   430  name:    hello
   431  summary: GNU Hello, the "hello world"
   432    snap
   433  publisher: Canonical*
   434  license:   Proprietary
   435  price:     1.99GBP
   436  description: |
   437    GNU hello prints a friendly greeting.
   438    This is part of the snapcraft tour at
   439    https://snapcraft.io/
   440  snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   441  `[1:])
   442  	c.Check(s.Stderr(), check.Equals, "")
   443  }
   444  
   445  func (s *infoSuite) TestInfoPriced(c *check.C) {
   446  	n := 0
   447  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   448  		switch n {
   449  		case 0:
   450  			c.Check(r.Method, check.Equals, "GET")
   451  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   452  			fmt.Fprintln(w, findPricedJSON)
   453  		case 1:
   454  			c.Check(r.Method, check.Equals, "GET")
   455  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   456  			fmt.Fprintln(w, "{}")
   457  		default:
   458  			c.Fatalf("expected to get 1 requests, now on %d (%v)", n+1, r)
   459  		}
   460  
   461  		n++
   462  	})
   463  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"})
   464  	c.Assert(err, check.IsNil)
   465  	c.Assert(rest, check.DeepEquals, []string{})
   466  	c.Check(s.Stdout(), check.Equals, `name:      hello
   467  summary:   GNU Hello, the "hello world" snap
   468  publisher: Canonical*
   469  license:   Proprietary
   470  price:     1.99GBP
   471  description: |
   472    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   473    https://snapcraft.io/
   474  snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   475  `)
   476  	c.Check(s.Stderr(), check.Equals, "")
   477  }
   478  
   479  // only used for results on /v2/find
   480  const mockInfoJSON = `
   481  {
   482    "type": "sync",
   483    "status-code": 200,
   484    "status": "OK",
   485    "result": [
   486      {
   487        "channel": "stable",
   488        "confinement": "strict",
   489        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
   490        "developer": "canonical",
   491        "publisher": {
   492           "id": "canonical",
   493           "username": "canonical",
   494           "display-name": "Canonical",
   495           "validation": "verified"
   496        },
   497        "download-size": 65536,
   498        "icon": "",
   499        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
   500        "name": "hello",
   501        "private": false,
   502        "resource": "/v2/snaps/hello",
   503        "revision": "1",
   504        "status": "available",
   505        "summary": "The GNU Hello snap",
   506        "type": "app",
   507        "version": "2.10",
   508        "license": "MIT"
   509      }
   510    ],
   511    "sources": [
   512      "store"
   513    ],
   514    "suggested-currency": "GBP"
   515  }
   516  `
   517  
   518  const mockInfoJSONWithChannels = `
   519  {
   520    "type": "sync",
   521    "status-code": 200,
   522    "status": "OK",
   523    "result": [
   524      {
   525        "channel": "stable",
   526        "confinement": "strict",
   527        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
   528        "developer": "canonical",
   529        "publisher": {
   530           "id": "canonical",
   531           "username": "canonical",
   532           "display-name": "Canonical",
   533           "validation": "verified"
   534        },
   535        "download-size": 65536,
   536        "icon": "",
   537        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
   538        "name": "hello",
   539        "private": false,
   540        "resource": "/v2/snaps/hello",
   541        "revision": "1",
   542        "status": "available",
   543        "summary": "The GNU Hello snap",
   544        "store-url": "https://snapcraft.io/hello",
   545        "type": "app",
   546        "version": "2.10",
   547        "license": "MIT",
   548        "channels": {
   549          "1/stable": {
   550            "revision": "1",
   551            "version": "2.10",
   552            "channel": "1/stable",
   553            "size": 65536,
   554            "released-at": "2018-12-18T15:16:56.723501Z"
   555          }
   556        },
   557        "tracks": ["1"]
   558      }
   559    ],
   560    "sources": [
   561      "store"
   562    ],
   563    "suggested-currency": "GBP"
   564  }
   565  `
   566  
   567  func (s *infoSuite) TestInfoUnquoted(c *check.C) {
   568  	n := 0
   569  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   570  		switch n {
   571  		case 0:
   572  			c.Check(r.Method, check.Equals, "GET")
   573  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   574  			fmt.Fprintln(w, mockInfoJSON)
   575  		case 1:
   576  			c.Check(r.Method, check.Equals, "GET")
   577  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   578  			fmt.Fprintln(w, "{}")
   579  		default:
   580  			c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
   581  		}
   582  
   583  		n++
   584  	})
   585  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"})
   586  	c.Assert(err, check.IsNil)
   587  	c.Assert(rest, check.DeepEquals, []string{})
   588  	c.Check(s.Stdout(), check.Equals, `name:      hello
   589  summary:   The GNU Hello snap
   590  publisher: Canonical*
   591  license:   MIT
   592  description: |
   593    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   594    https://snapcraft.io/
   595  snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   596  `)
   597  	c.Check(s.Stderr(), check.Equals, "")
   598  }
   599  
   600  // only used for /v2/snaps/hello
   601  const mockInfoJSONOtherLicense = `
   602  {
   603    "type": "sync",
   604    "status-code": 200,
   605    "status": "OK",
   606    "result": {
   607        "channel": "stable",
   608        "confinement": "strict",
   609        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
   610        "developer": "canonical",
   611        "publisher": {
   612           "id": "canonical",
   613           "username": "canonical",
   614           "display-name": "Canonical",
   615           "validation": "verified"
   616        },
   617        "health": {"revision": "1", "status": "blocked", "message": "please configure the grawflit", "timestamp": "2019-05-13T16:27:01.475851677+01:00"},
   618        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
   619        "install-date": "2006-01-02T22:04:07.123456789Z",
   620        "installed-size": 1024,
   621        "name": "hello",
   622        "private": false,
   623        "resource": "/v2/snaps/hello",
   624        "revision": "1",
   625        "status": "available",
   626        "summary": "The GNU Hello snap",
   627        "type": "app",
   628        "version": "2.10",
   629        "license": "BSD-3",
   630        "tracking-channel": "beta"
   631      }
   632  }
   633  `
   634  const mockInfoJSONNoLicense = `
   635  {
   636    "type": "sync",
   637    "status-code": 200,
   638    "status": "OK",
   639    "result": {
   640        "channel": "stable",
   641        "confinement": "strict",
   642        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
   643        "developer": "canonical",
   644        "publisher": {
   645           "id": "canonical",
   646           "username": "canonical",
   647           "display-name": "Canonical",
   648           "validation": "verified"
   649        },
   650        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
   651        "install-date": "2006-01-02T22:04:07.123456789Z",
   652        "installed-size": 1024,
   653        "name": "hello",
   654        "private": false,
   655        "resource": "/v2/snaps/hello",
   656        "revision": "100",
   657        "status": "available",
   658        "summary": "The GNU Hello snap",
   659        "type": "app",
   660        "version": "2.10",
   661        "license": "",
   662        "tracking-channel": "beta"
   663      }
   664  }
   665  `
   666  
   667  func (s *infoSuite) TestInfoWithLocalDifferentLicense(c *check.C) {
   668  	n := 0
   669  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   670  		switch n {
   671  		case 0:
   672  			c.Check(r.Method, check.Equals, "GET")
   673  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   674  			fmt.Fprintln(w, mockInfoJSON)
   675  		case 1:
   676  			c.Check(r.Method, check.Equals, "GET")
   677  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   678  			fmt.Fprintln(w, mockInfoJSONOtherLicense)
   679  		default:
   680  			c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
   681  		}
   682  
   683  		n++
   684  	})
   685  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"})
   686  	c.Assert(err, check.IsNil)
   687  	c.Assert(rest, check.DeepEquals, []string{})
   688  	c.Check(s.Stdout(), check.Equals, `
   689  name:    hello
   690  summary: The GNU Hello snap
   691  health:
   692    status:   blocked
   693    message:  please configure the grawflit
   694    checked:  2019-05-13T16:27:01+01:00
   695    revision: 1
   696  publisher: Canonical*
   697  license:   BSD-3
   698  description: |
   699    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   700    https://snapcraft.io/
   701  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   702  tracking:     beta
   703  refresh-date: 2006-01-02T22:04:07Z
   704  installed:    2.10 (1) 1kB disabled,blocked
   705  `[1:])
   706  	c.Check(s.Stderr(), check.Equals, "")
   707  }
   708  
   709  func (s *infoSuite) TestInfoNotFound(c *check.C) {
   710  	n := 0
   711  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   712  		switch n % 2 {
   713  		case 0:
   714  			c.Check(r.Method, check.Equals, "GET")
   715  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   716  		case 1:
   717  			c.Check(r.Method, check.Equals, "GET")
   718  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/x")
   719  		}
   720  		w.WriteHeader(404)
   721  		fmt.Fprintln(w, `{"type":"error","status-code":404,"status":"Not Found","result":{"message":"No.","kind":"snap-not-found","value":"x"}}`)
   722  
   723  		n++
   724  	})
   725  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--verbose", "/x"})
   726  	c.Check(err, check.ErrorMatches, `no snap found for "/x"`)
   727  	c.Check(s.Stdout(), check.Equals, "")
   728  	c.Check(s.Stderr(), check.Equals, "")
   729  }
   730  
   731  func (s *infoSuite) TestInfoWithLocalNoLicense(c *check.C) {
   732  	n := 0
   733  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   734  		switch n {
   735  		case 0:
   736  			c.Check(r.Method, check.Equals, "GET")
   737  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   738  			fmt.Fprintln(w, mockInfoJSON)
   739  		case 1:
   740  			c.Check(r.Method, check.Equals, "GET")
   741  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   742  			fmt.Fprintln(w, mockInfoJSONNoLicense)
   743  		default:
   744  			c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
   745  		}
   746  
   747  		n++
   748  	})
   749  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"})
   750  	c.Assert(err, check.IsNil)
   751  	c.Assert(rest, check.DeepEquals, []string{})
   752  	c.Check(s.Stdout(), check.Equals, `name:      hello
   753  summary:   The GNU Hello snap
   754  publisher: Canonical*
   755  license:   unset
   756  description: |
   757    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   758    https://snapcraft.io/
   759  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   760  tracking:     beta
   761  refresh-date: 2006-01-02T22:04:07Z
   762  installed:    2.10 (100) 1kB disabled
   763  `)
   764  	c.Check(s.Stderr(), check.Equals, "")
   765  }
   766  
   767  func (s *infoSuite) TestInfoWithChannelsAndLocal(c *check.C) {
   768  	n := 0
   769  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   770  		switch n {
   771  		case 0, 2, 4:
   772  			c.Check(r.Method, check.Equals, "GET")
   773  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   774  			fmt.Fprintln(w, mockInfoJSONWithChannels)
   775  		case 1, 3, 5:
   776  			c.Check(r.Method, check.Equals, "GET")
   777  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   778  			fmt.Fprintln(w, mockInfoJSONNoLicense)
   779  		default:
   780  			c.Fatalf("expected to get 6 requests, now on %d (%v)", n+1, r)
   781  		}
   782  
   783  		n++
   784  	})
   785  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"})
   786  	c.Assert(err, check.IsNil)
   787  	c.Assert(rest, check.DeepEquals, []string{})
   788  	c.Check(s.Stdout(), check.Equals, `name:      hello
   789  summary:   The GNU Hello snap
   790  publisher: Canonical*
   791  store-url: https://snapcraft.io/hello
   792  license:   unset
   793  description: |
   794    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   795    https://snapcraft.io/
   796  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   797  tracking:     beta
   798  refresh-date: 2006-01-02T22:04:07Z
   799  channels:
   800    1/stable:    2.10 2018-12-18T15:16:56Z   (1) 65kB -
   801    1/candidate: ^                                    
   802    1/beta:      ^                                    
   803    1/edge:      ^                                    
   804  installed:     2.10                      (100)  1kB disabled
   805  `)
   806  	c.Check(s.Stderr(), check.Equals, "")
   807  	c.Check(n, check.Equals, 2)
   808  
   809  	// now the same but without abs-time
   810  	s.ResetStdStreams()
   811  	rest, err = snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"})
   812  	c.Assert(err, check.IsNil)
   813  	c.Assert(rest, check.DeepEquals, []string{})
   814  	c.Check(s.Stdout(), check.Equals, `name:      hello
   815  summary:   The GNU Hello snap
   816  publisher: Canonical*
   817  store-url: https://snapcraft.io/hello
   818  license:   unset
   819  description: |
   820    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   821    https://snapcraft.io/
   822  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   823  tracking:     beta
   824  refresh-date: 2006-01-02
   825  channels:
   826    1/stable:    2.10 2018-12-18   (1) 65kB -
   827    1/candidate: ^                          
   828    1/beta:      ^                          
   829    1/edge:      ^                          
   830  installed:     2.10            (100)  1kB disabled
   831  `)
   832  	c.Check(s.Stderr(), check.Equals, "")
   833  	c.Check(n, check.Equals, 4)
   834  
   835  	// now the same but with unicode on
   836  	s.ResetStdStreams()
   837  	rest, err = snap.Parser(snap.Client()).ParseArgs([]string{"info", "--unicode=always", "hello"})
   838  	c.Assert(err, check.IsNil)
   839  	c.Assert(rest, check.DeepEquals, []string{})
   840  	c.Check(s.Stdout(), check.Equals, `name:      hello
   841  summary:   The GNU Hello snap
   842  publisher: Canonical✓
   843  store-url: https://snapcraft.io/hello
   844  license:   unset
   845  description: |
   846    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   847    https://snapcraft.io/
   848  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   849  tracking:     beta
   850  refresh-date: 2006-01-02
   851  channels:
   852    1/stable:    2.10 2018-12-18   (1) 65kB -
   853    1/candidate: ↑                          
   854    1/beta:      ↑                          
   855    1/edge:      ↑                          
   856  installed:     2.10            (100)  1kB disabled
   857  `)
   858  	c.Check(s.Stderr(), check.Equals, "")
   859  	c.Check(n, check.Equals, 6)
   860  }
   861  
   862  func (s *infoSuite) TestInfoHumanTimes(c *check.C) {
   863  	// checks that tiemutil.Human is called when no --abs-time is given
   864  	restore := snap.MockTimeutilHuman(func(time.Time) string { return "TOTALLY NOT A ROBOT" })
   865  	defer restore()
   866  
   867  	n := 0
   868  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   869  		switch n {
   870  		case 0:
   871  			c.Check(r.Method, check.Equals, "GET")
   872  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   873  			fmt.Fprintln(w, "{}")
   874  		case 1:
   875  			c.Check(r.Method, check.Equals, "GET")
   876  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   877  			fmt.Fprintln(w, mockInfoJSONNoLicense)
   878  		default:
   879  			c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
   880  		}
   881  
   882  		n++
   883  	})
   884  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"})
   885  	c.Assert(err, check.IsNil)
   886  	c.Assert(rest, check.DeepEquals, []string{})
   887  	c.Check(s.Stdout(), check.Equals, `name:      hello
   888  summary:   The GNU Hello snap
   889  publisher: Canonical*
   890  license:   unset
   891  description: |
   892    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   893    https://snapcraft.io/
   894  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   895  tracking:     beta
   896  refresh-date: TOTALLY NOT A ROBOT
   897  installed:    2.10 (100) 1kB disabled
   898  `)
   899  	c.Check(s.Stderr(), check.Equals, "")
   900  }
   901  
   902  func (infoSuite) TestDescr(c *check.C) {
   903  	for k, v := range map[string]string{
   904  		"": "  \n",
   905  		`one:
   906   * two three four five six  
   907     * seven height nine ten
   908  `: `  one:
   909     * two three four
   910     five six
   911       * seven height
   912       nine ten
   913  `,
   914  		"abcdefghijklm nopqrstuvwxyz ABCDEFGHIJKLMNOPQR STUVWXYZ": `
   915    abcdefghijklm
   916    nopqrstuvwxyz
   917    ABCDEFGHIJKLMNOPQR
   918    STUVWXYZ
   919  `[1:],
   920  		// not much we can do when it won't fit
   921  		"abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ": `
   922    abcdefghijklmnopqr
   923    stuvwxyz
   924    ABCDEFGHIJKLMNOPQR
   925    STUVWXYZ
   926  `[1:],
   927  	} {
   928  		var buf bytes.Buffer
   929  		snap.PrintDescr(&buf, k, 20)
   930  		c.Check(buf.String(), check.Equals, v, check.Commentf("%q", k))
   931  	}
   932  }
   933  
   934  func (infoSuite) TestMaybePrintCohortKey(c *check.C) {
   935  	type T struct {
   936  		snap     *client.Snap
   937  		verbose  bool
   938  		expected string
   939  	}
   940  
   941  	tests := []T{
   942  		{snap: nil, verbose: false, expected: ""},
   943  		{snap: nil, verbose: true, expected: ""},
   944  		{snap: &client.Snap{}, verbose: false, expected: ""},
   945  		{snap: &client.Snap{}, verbose: true, expected: ""},
   946  		{snap: &client.Snap{CohortKey: "some-cohort-key"}, verbose: false, expected: ""},
   947  		{snap: &client.Snap{CohortKey: "some-cohort-key"}, verbose: true, expected: "cohort:\t…-key\n"},
   948  	}
   949  
   950  	var buf flushBuffer
   951  	iw := snap.NewInfoWriter(&buf)
   952  	defer snap.MockIsStdoutTTY(true)()
   953  
   954  	for i, t := range tests {
   955  		buf.Reset()
   956  		snap.SetupSnap(iw, t.snap, nil, nil)
   957  		snap.SetVerbose(iw, t.verbose)
   958  		snap.MaybePrintCohortKey(iw)
   959  		c.Check(buf.String(), check.Equals, t.expected, check.Commentf("tty:true/%d", i))
   960  	}
   961  	// now the same but without a tty -> the last test should no longer ellipt
   962  	tests[len(tests)-1].expected = "cohort:\tsome-cohort-key\n"
   963  	snap.MockIsStdoutTTY(false)
   964  	for i, t := range tests {
   965  		buf.Reset()
   966  		snap.SetupSnap(iw, t.snap, nil, nil)
   967  		snap.SetVerbose(iw, t.verbose)
   968  		snap.MaybePrintCohortKey(iw)
   969  		c.Check(buf.String(), check.Equals, t.expected, check.Commentf("tty:false/%d", i))
   970  	}
   971  }
   972  
   973  func (infoSuite) TestMaybePrintHealth(c *check.C) {
   974  	type T struct {
   975  		snap     *client.Snap
   976  		verbose  bool
   977  		expected string
   978  	}
   979  
   980  	goodHealth := &client.SnapHealth{Status: "okay"}
   981  	t0 := time.Date(1970, 1, 1, 10, 24, 0, 0, time.UTC)
   982  	badHealth := &client.SnapHealth{
   983  		Status:    "waiting",
   984  		Message:   "godot should be here any moment now",
   985  		Code:      "godot-is-a-lie",
   986  		Revision:  snaplib.R("42"),
   987  		Timestamp: t0,
   988  	}
   989  
   990  	tests := []T{
   991  		{snap: nil, verbose: false, expected: ""},
   992  		{snap: nil, verbose: true, expected: ""},
   993  		{snap: &client.Snap{}, verbose: false, expected: ""},
   994  		{snap: &client.Snap{}, verbose: true, expected: `health:
   995    status:	unknown
   996    message:	health
   997      has not been set
   998  `},
   999  		{snap: &client.Snap{Health: goodHealth}, verbose: false, expected: ``},
  1000  		{snap: &client.Snap{Health: goodHealth}, verbose: true, expected: `health:
  1001    status:	okay
  1002  `},
  1003  		{snap: &client.Snap{Health: badHealth}, verbose: false, expected: `health:
  1004    status:	waiting
  1005    message:	godot
  1006      should be here
  1007      any moment now
  1008    code:	godot-is-a-lie
  1009    checked:	10:24AM
  1010    revision:	42
  1011  `},
  1012  		{snap: &client.Snap{Health: badHealth}, verbose: true, expected: `health:
  1013    status:	waiting
  1014    message:	godot
  1015      should be here
  1016      any moment now
  1017    code:	godot-is-a-lie
  1018    checked:	10:24AM
  1019    revision:	42
  1020  `},
  1021  	}
  1022  
  1023  	var buf flushBuffer
  1024  	iw := snap.NewInfoWriter(&buf)
  1025  	defer snap.MockIsStdoutTTY(false)()
  1026  
  1027  	for i, t := range tests {
  1028  		buf.Reset()
  1029  		snap.SetupSnap(iw, t.snap, nil, nil)
  1030  		snap.SetVerbose(iw, t.verbose)
  1031  		snap.MaybePrintHealth(iw)
  1032  		c.Check(buf.String(), check.Equals, t.expected, check.Commentf("%d", i))
  1033  	}
  1034  }
  1035  
  1036  func (infoSuite) TestWrapCornerCase(c *check.C) {
  1037  	// this particular corner case isn't currently reachable from
  1038  	// printDescr nor printSummary, but best to have it covered
  1039  	var buf bytes.Buffer
  1040  	const s = "This is a paragraph indented with leading spaces that are encoded as multiple bytes. All hail EN SPACE."
  1041  	snap.WrapFlow(&buf, []rune(s), "\u2002\u2002", 30)
  1042  	c.Check(buf.String(), check.Equals, `
  1043    This is a paragraph indented
  1044    with leading spaces that are
  1045    encoded as multiple bytes.
  1046    All hail EN SPACE.
  1047  `[1:])
  1048  }
  1049  
  1050  func (infoSuite) TestBug1828425(c *check.C) {
  1051  	const s = `This is a description
  1052                                    that has
  1053                                    lines
  1054                                    too deeply
  1055                                    indented.
  1056  `
  1057  	var buf bytes.Buffer
  1058  	err := snap.PrintDescr(&buf, s, 30)
  1059  	c.Assert(err, check.IsNil)
  1060  	c.Check(buf.String(), check.Equals, `  This is a description
  1061      that has
  1062      lines
  1063      too deeply
  1064      indented.
  1065  `)
  1066  }
  1067  
  1068  const mockInfoJSONParallelInstance = `
  1069  {
  1070    "type": "sync",
  1071    "status-code": 200,
  1072    "status": "OK",
  1073    "result": {
  1074        "channel": "stable",
  1075        "confinement": "strict",
  1076        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
  1077        "developer": "canonical",
  1078        "publisher": {
  1079           "id": "canonical",
  1080           "username": "canonical",
  1081           "display-name": "Canonical",
  1082           "validation": "verified"
  1083        },
  1084        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
  1085        "install-date": "2006-01-02T22:04:07.123456789Z",
  1086        "installed-size": 1024,
  1087        "name": "hello_foo",
  1088        "private": false,
  1089        "revision": "100",
  1090        "status": "available",
  1091        "summary": "The GNU Hello snap",
  1092        "type": "app",
  1093        "version": "2.10",
  1094        "license": "",
  1095        "tracking-channel": "beta"
  1096      }
  1097  }
  1098  `
  1099  
  1100  func (s *infoSuite) TestInfoParllelInstance(c *check.C) {
  1101  	n := 0
  1102  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1103  		switch n {
  1104  		case 0:
  1105  			c.Check(r.Method, check.Equals, "GET")
  1106  			c.Check(r.URL.Path, check.Equals, "/v2/find")
  1107  			q := r.URL.Query()
  1108  			// asks for the instance snap
  1109  			c.Check(q.Get("name"), check.Equals, "hello")
  1110  			fmt.Fprintln(w, mockInfoJSONWithChannels)
  1111  		case 1:
  1112  			c.Check(r.Method, check.Equals, "GET")
  1113  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello_foo")
  1114  			fmt.Fprintln(w, mockInfoJSONParallelInstance)
  1115  		default:
  1116  			c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
  1117  		}
  1118  
  1119  		n++
  1120  	})
  1121  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello_foo"})
  1122  	c.Assert(err, check.IsNil)
  1123  	c.Assert(rest, check.DeepEquals, []string{})
  1124  	// make sure local and remote info is combined in the output
  1125  	c.Check(s.Stdout(), check.Equals, `name:      hello_foo
  1126  summary:   The GNU Hello snap
  1127  publisher: Canonical*
  1128  store-url: https://snapcraft.io/hello
  1129  license:   unset
  1130  description: |
  1131    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
  1132    https://snapcraft.io/
  1133  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
  1134  tracking:     beta
  1135  refresh-date: 2006-01-02
  1136  channels:
  1137    1/stable:    2.10 2018-12-18   (1) 65kB -
  1138    1/candidate: ^                          
  1139    1/beta:      ^                          
  1140    1/edge:      ^                          
  1141  installed:     2.10            (100)  1kB disabled
  1142  `)
  1143  	c.Check(s.Stderr(), check.Equals, "")
  1144  }
  1145  
  1146  const mockInfoJSONWithStoreURL = `
  1147  {
  1148    "type": "sync",
  1149    "status-code": 200,
  1150    "status": "OK",
  1151    "result": {
  1152        "channel": "stable",
  1153        "confinement": "strict",
  1154        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
  1155        "developer": "canonical",
  1156        "publisher": {
  1157           "id": "canonical",
  1158           "username": "canonical",
  1159           "display-name": "Canonical",
  1160           "validation": "verified"
  1161        },
  1162        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
  1163        "install-date": "2006-01-02T22:04:07.123456789Z",
  1164        "installed-size": 1024,
  1165        "name": "hello",
  1166        "private": false,
  1167        "revision": "100",
  1168        "status": "available",
  1169        "store-url": "https://snapcraft.io/hello",
  1170        "summary": "The GNU Hello snap",
  1171        "type": "app",
  1172        "version": "2.10",
  1173        "license": "",
  1174        "tracking-channel": "beta"
  1175      }
  1176  }
  1177  `
  1178  
  1179  func (s *infoSuite) TestInfoStoreURL(c *check.C) {
  1180  	n := 0
  1181  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1182  		switch n {
  1183  		case 0:
  1184  			c.Check(r.Method, check.Equals, "GET")
  1185  			c.Check(r.URL.Path, check.Equals, "/v2/find")
  1186  			q := r.URL.Query()
  1187  			// asks for the instance snap
  1188  			c.Check(q.Get("name"), check.Equals, "hello")
  1189  			fmt.Fprintln(w, mockInfoJSONWithChannels)
  1190  		case 1:
  1191  			c.Check(r.Method, check.Equals, "GET")
  1192  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
  1193  			fmt.Fprintln(w, mockInfoJSONWithStoreURL)
  1194  		default:
  1195  			c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
  1196  		}
  1197  
  1198  		n++
  1199  	})
  1200  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"})
  1201  	c.Assert(err, check.IsNil)
  1202  	c.Assert(rest, check.DeepEquals, []string{})
  1203  	// make sure local and remote info is combined in the output
  1204  	c.Check(s.Stdout(), check.Equals, `name:      hello
  1205  summary:   The GNU Hello snap
  1206  publisher: Canonical*
  1207  store-url: https://snapcraft.io/hello
  1208  license:   unset
  1209  description: |
  1210    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
  1211    https://snapcraft.io/
  1212  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
  1213  tracking:     beta
  1214  refresh-date: 2006-01-02
  1215  channels:
  1216    1/stable:    2.10 2018-12-18   (1) 65kB -
  1217    1/candidate: ^                          
  1218    1/beta:      ^                          
  1219    1/edge:      ^                          
  1220  installed:     2.10            (100)  1kB disabled
  1221  `)
  1222  	c.Check(s.Stderr(), check.Equals, "")
  1223  }