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