github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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, "app"), 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, "app"), 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  		"": "",
   341  		"mailto:joe@example.com": "contact:\tjoe@example.com\n",
   342  		"foo": "contact:\tfoo\n",
   343  	} {
   344  		buf.Reset()
   345  		snap.SetupDiskSnap(iw, "", &client.Snap{Contact: contact})
   346  		snap.MaybePrintContact(iw)
   347  		c.Check(buf.String(), check.Equals, expected, check.Commentf("%q", contact))
   348  	}
   349  }
   350  
   351  func (s *infoSuite) TestMaybePrintBase(c *check.C) {
   352  	var buf flushBuffer
   353  	iw := snap.NewInfoWriter(&buf)
   354  	dSnap := &client.Snap{}
   355  	snap.SetupDiskSnap(iw, "", dSnap)
   356  
   357  	// no verbose -> no base
   358  	snap.SetVerbose(iw, false)
   359  	snap.MaybePrintBase(iw)
   360  	c.Check(buf.String(), check.Equals, "")
   361  	buf.Reset()
   362  
   363  	// no base -> no base :)
   364  	snap.SetVerbose(iw, true)
   365  	snap.MaybePrintBase(iw)
   366  	c.Check(buf.String(), check.Equals, "")
   367  	buf.Reset()
   368  
   369  	// base + verbose -> base
   370  	dSnap.Base = "xyzzy"
   371  	snap.MaybePrintBase(iw)
   372  	c.Check(buf.String(), check.Equals, "base:\txyzzy\n")
   373  	buf.Reset()
   374  }
   375  
   376  func (s *infoSuite) TestMaybePrintPath(c *check.C) {
   377  	var buf flushBuffer
   378  	iw := snap.NewInfoWriter(&buf)
   379  	dSnap := &client.Snap{}
   380  
   381  	// no path -> no path
   382  	snap.SetupDiskSnap(iw, "", dSnap)
   383  	snap.MaybePrintPath(iw)
   384  	c.Check(buf.String(), check.Equals, "")
   385  	buf.Reset()
   386  
   387  	// path -> path (quoted!)
   388  	snap.SetupDiskSnap(iw, "xyzzy", dSnap)
   389  	snap.MaybePrintPath(iw)
   390  	c.Check(buf.String(), check.Equals, "path:\t\"xyzzy\"\n")
   391  	buf.Reset()
   392  }
   393  
   394  func (s *infoSuite) TestClientSnapFromPath(c *check.C) {
   395  	// minimal sanity check
   396  	fn := snaptest.MakeTestSnapWithFiles(c, `
   397  name: some-snap
   398  version: 9
   399  `, nil)
   400  	dSnap, err := snap.ClientSnapFromPath(fn)
   401  	c.Assert(err, check.IsNil)
   402  	c.Check(dSnap.Version, check.Equals, "9")
   403  }
   404  
   405  func (s *infoSuite) TestInfoPricedNarrowTerminal(c *check.C) {
   406  	defer snap.MockTermSize(func() (int, int) { return 44, 25 })()
   407  
   408  	n := 0
   409  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   410  		switch n {
   411  		case 0:
   412  			c.Check(r.Method, check.Equals, "GET")
   413  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   414  			fmt.Fprintln(w, findPricedJSON)
   415  		case 1:
   416  			c.Check(r.Method, check.Equals, "GET")
   417  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   418  			fmt.Fprintln(w, "{}")
   419  		default:
   420  			c.Fatalf("expected to get 1 requests, now on %d (%v)", n+1, r)
   421  		}
   422  
   423  		n++
   424  	})
   425  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"})
   426  	c.Assert(err, check.IsNil)
   427  	c.Assert(rest, check.DeepEquals, []string{})
   428  	c.Check(s.Stdout(), check.Equals, `
   429  name:    hello
   430  summary: GNU Hello, the "hello world"
   431    snap
   432  publisher: Canonical*
   433  license:   Proprietary
   434  price:     1.99GBP
   435  description: |
   436    GNU hello prints a friendly greeting.
   437    This is part of the snapcraft tour at
   438    https://snapcraft.io/
   439  snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   440  `[1:])
   441  	c.Check(s.Stderr(), check.Equals, "")
   442  }
   443  
   444  func (s *infoSuite) TestInfoPriced(c *check.C) {
   445  	n := 0
   446  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   447  		switch n {
   448  		case 0:
   449  			c.Check(r.Method, check.Equals, "GET")
   450  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   451  			fmt.Fprintln(w, findPricedJSON)
   452  		case 1:
   453  			c.Check(r.Method, check.Equals, "GET")
   454  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   455  			fmt.Fprintln(w, "{}")
   456  		default:
   457  			c.Fatalf("expected to get 1 requests, now on %d (%v)", n+1, r)
   458  		}
   459  
   460  		n++
   461  	})
   462  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"})
   463  	c.Assert(err, check.IsNil)
   464  	c.Assert(rest, check.DeepEquals, []string{})
   465  	c.Check(s.Stdout(), check.Equals, `name:      hello
   466  summary:   GNU Hello, the "hello world" snap
   467  publisher: Canonical*
   468  license:   Proprietary
   469  price:     1.99GBP
   470  description: |
   471    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   472    https://snapcraft.io/
   473  snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   474  `)
   475  	c.Check(s.Stderr(), check.Equals, "")
   476  }
   477  
   478  // only used for results on /v2/find
   479  const mockInfoJSON = `
   480  {
   481    "type": "sync",
   482    "status-code": 200,
   483    "status": "OK",
   484    "result": [
   485      {
   486        "channel": "stable",
   487        "confinement": "strict",
   488        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
   489        "developer": "canonical",
   490        "publisher": {
   491           "id": "canonical",
   492           "username": "canonical",
   493           "display-name": "Canonical",
   494           "validation": "verified"
   495        },
   496        "download-size": 65536,
   497        "icon": "",
   498        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
   499        "name": "hello",
   500        "private": false,
   501        "resource": "/v2/snaps/hello",
   502        "revision": "1",
   503        "status": "available",
   504        "summary": "The GNU Hello snap",
   505        "type": "app",
   506        "version": "2.10",
   507        "license": "MIT"
   508      }
   509    ],
   510    "sources": [
   511      "store"
   512    ],
   513    "suggested-currency": "GBP"
   514  }
   515  `
   516  
   517  const mockInfoJSONWithChannels = `
   518  {
   519    "type": "sync",
   520    "status-code": 200,
   521    "status": "OK",
   522    "result": [
   523      {
   524        "channel": "stable",
   525        "confinement": "strict",
   526        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
   527        "developer": "canonical",
   528        "publisher": {
   529           "id": "canonical",
   530           "username": "canonical",
   531           "display-name": "Canonical",
   532           "validation": "verified"
   533        },
   534        "download-size": 65536,
   535        "icon": "",
   536        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
   537        "name": "hello",
   538        "private": false,
   539        "resource": "/v2/snaps/hello",
   540        "revision": "1",
   541        "status": "available",
   542        "summary": "The GNU Hello snap",
   543        "type": "app",
   544        "version": "2.10",
   545        "license": "MIT",
   546        "channels": {
   547          "1/stable": {
   548            "revision": "1",
   549            "version": "2.10",
   550            "channel": "1/stable",
   551            "size": 65536,
   552            "released-at": "2018-12-18T15:16:56.723501Z"
   553          }
   554        },
   555        "tracks": ["1"]
   556      }
   557    ],
   558    "sources": [
   559      "store"
   560    ],
   561    "suggested-currency": "GBP"
   562  }
   563  `
   564  
   565  func (s *infoSuite) TestInfoUnquoted(c *check.C) {
   566  	n := 0
   567  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   568  		switch n {
   569  		case 0:
   570  			c.Check(r.Method, check.Equals, "GET")
   571  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   572  			fmt.Fprintln(w, mockInfoJSON)
   573  		case 1:
   574  			c.Check(r.Method, check.Equals, "GET")
   575  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   576  			fmt.Fprintln(w, "{}")
   577  		default:
   578  			c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
   579  		}
   580  
   581  		n++
   582  	})
   583  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"})
   584  	c.Assert(err, check.IsNil)
   585  	c.Assert(rest, check.DeepEquals, []string{})
   586  	c.Check(s.Stdout(), check.Equals, `name:      hello
   587  summary:   The GNU Hello snap
   588  publisher: Canonical*
   589  license:   MIT
   590  description: |
   591    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   592    https://snapcraft.io/
   593  snap-id: mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   594  `)
   595  	c.Check(s.Stderr(), check.Equals, "")
   596  }
   597  
   598  // only used for /v2/snaps/hello
   599  const mockInfoJSONOtherLicense = `
   600  {
   601    "type": "sync",
   602    "status-code": 200,
   603    "status": "OK",
   604    "result": {
   605        "channel": "stable",
   606        "confinement": "strict",
   607        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
   608        "developer": "canonical",
   609        "publisher": {
   610           "id": "canonical",
   611           "username": "canonical",
   612           "display-name": "Canonical",
   613           "validation": "verified"
   614        },
   615        "health": {"revision": "1", "status": "blocked", "message": "please configure the grawflit", "timestamp": "2019-05-13T16:27:01.475851677+01:00"},
   616        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
   617        "install-date": "2006-01-02T22:04:07.123456789Z",
   618        "installed-size": 1024,
   619        "name": "hello",
   620        "private": false,
   621        "resource": "/v2/snaps/hello",
   622        "revision": "1",
   623        "status": "available",
   624        "summary": "The GNU Hello snap",
   625        "type": "app",
   626        "version": "2.10",
   627        "license": "BSD-3",
   628        "tracking-channel": "beta"
   629      }
   630  }
   631  `
   632  const mockInfoJSONNoLicense = `
   633  {
   634    "type": "sync",
   635    "status-code": 200,
   636    "status": "OK",
   637    "result": {
   638        "channel": "stable",
   639        "confinement": "strict",
   640        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
   641        "developer": "canonical",
   642        "publisher": {
   643           "id": "canonical",
   644           "username": "canonical",
   645           "display-name": "Canonical",
   646           "validation": "verified"
   647        },
   648        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
   649        "install-date": "2006-01-02T22:04:07.123456789Z",
   650        "installed-size": 1024,
   651        "name": "hello",
   652        "private": false,
   653        "resource": "/v2/snaps/hello",
   654        "revision": "100",
   655        "status": "available",
   656        "summary": "The GNU Hello snap",
   657        "type": "app",
   658        "version": "2.10",
   659        "license": "",
   660        "tracking-channel": "beta"
   661      }
   662  }
   663  `
   664  
   665  func (s *infoSuite) TestInfoWithLocalDifferentLicense(c *check.C) {
   666  	n := 0
   667  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   668  		switch n {
   669  		case 0:
   670  			c.Check(r.Method, check.Equals, "GET")
   671  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   672  			fmt.Fprintln(w, mockInfoJSON)
   673  		case 1:
   674  			c.Check(r.Method, check.Equals, "GET")
   675  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   676  			fmt.Fprintln(w, mockInfoJSONOtherLicense)
   677  		default:
   678  			c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
   679  		}
   680  
   681  		n++
   682  	})
   683  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"})
   684  	c.Assert(err, check.IsNil)
   685  	c.Assert(rest, check.DeepEquals, []string{})
   686  	c.Check(s.Stdout(), check.Equals, `
   687  name:    hello
   688  summary: The GNU Hello snap
   689  health:
   690    status:   blocked
   691    message:  please configure the grawflit
   692    checked:  2019-05-13T16:27:01+01:00
   693    revision: 1
   694  publisher: Canonical*
   695  license:   BSD-3
   696  description: |
   697    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   698    https://snapcraft.io/
   699  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   700  tracking:     beta
   701  refresh-date: 2006-01-02T22:04:07Z
   702  installed:    2.10 (1) 1kB disabled,blocked
   703  `[1:])
   704  	c.Check(s.Stderr(), check.Equals, "")
   705  }
   706  
   707  func (s *infoSuite) TestInfoNotFound(c *check.C) {
   708  	n := 0
   709  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   710  		switch n % 2 {
   711  		case 0:
   712  			c.Check(r.Method, check.Equals, "GET")
   713  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   714  		case 1:
   715  			c.Check(r.Method, check.Equals, "GET")
   716  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/x")
   717  		}
   718  		w.WriteHeader(404)
   719  		fmt.Fprintln(w, `{"type":"error","status-code":404,"status":"Not Found","result":{"message":"No.","kind":"snap-not-found","value":"x"}}`)
   720  
   721  		n++
   722  	})
   723  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--verbose", "/x"})
   724  	c.Check(err, check.ErrorMatches, `no snap found for "/x"`)
   725  	c.Check(s.Stdout(), check.Equals, "")
   726  	c.Check(s.Stderr(), check.Equals, "")
   727  }
   728  
   729  func (s *infoSuite) TestInfoWithLocalNoLicense(c *check.C) {
   730  	n := 0
   731  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   732  		switch n {
   733  		case 0:
   734  			c.Check(r.Method, check.Equals, "GET")
   735  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   736  			fmt.Fprintln(w, mockInfoJSON)
   737  		case 1:
   738  			c.Check(r.Method, check.Equals, "GET")
   739  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   740  			fmt.Fprintln(w, mockInfoJSONNoLicense)
   741  		default:
   742  			c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
   743  		}
   744  
   745  		n++
   746  	})
   747  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"})
   748  	c.Assert(err, check.IsNil)
   749  	c.Assert(rest, check.DeepEquals, []string{})
   750  	c.Check(s.Stdout(), check.Equals, `name:      hello
   751  summary:   The GNU Hello snap
   752  publisher: Canonical*
   753  license:   unset
   754  description: |
   755    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   756    https://snapcraft.io/
   757  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   758  tracking:     beta
   759  refresh-date: 2006-01-02T22:04:07Z
   760  installed:    2.10 (100) 1kB disabled
   761  `)
   762  	c.Check(s.Stderr(), check.Equals, "")
   763  }
   764  
   765  func (s *infoSuite) TestInfoWithChannelsAndLocal(c *check.C) {
   766  	n := 0
   767  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   768  		switch n {
   769  		case 0, 2, 4:
   770  			c.Check(r.Method, check.Equals, "GET")
   771  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   772  			fmt.Fprintln(w, mockInfoJSONWithChannels)
   773  		case 1, 3, 5:
   774  			c.Check(r.Method, check.Equals, "GET")
   775  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   776  			fmt.Fprintln(w, mockInfoJSONNoLicense)
   777  		default:
   778  			c.Fatalf("expected to get 6 requests, now on %d (%v)", n+1, r)
   779  		}
   780  
   781  		n++
   782  	})
   783  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "--abs-time", "hello"})
   784  	c.Assert(err, check.IsNil)
   785  	c.Assert(rest, check.DeepEquals, []string{})
   786  	c.Check(s.Stdout(), check.Equals, `name:      hello
   787  summary:   The GNU Hello snap
   788  publisher: Canonical*
   789  license:   unset
   790  description: |
   791    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   792    https://snapcraft.io/
   793  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   794  tracking:     beta
   795  refresh-date: 2006-01-02T22:04:07Z
   796  channels:
   797    1/stable:    2.10 2018-12-18T15:16:56Z   (1) 65kB -
   798    1/candidate: ^                                    
   799    1/beta:      ^                                    
   800    1/edge:      ^                                    
   801  installed:     2.10                      (100)  1kB disabled
   802  `)
   803  	c.Check(s.Stderr(), check.Equals, "")
   804  	c.Check(n, check.Equals, 2)
   805  
   806  	// now the same but without abs-time
   807  	s.ResetStdStreams()
   808  	rest, err = snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"})
   809  	c.Assert(err, check.IsNil)
   810  	c.Assert(rest, check.DeepEquals, []string{})
   811  	c.Check(s.Stdout(), check.Equals, `name:      hello
   812  summary:   The GNU Hello snap
   813  publisher: Canonical*
   814  license:   unset
   815  description: |
   816    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   817    https://snapcraft.io/
   818  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   819  tracking:     beta
   820  refresh-date: 2006-01-02
   821  channels:
   822    1/stable:    2.10 2018-12-18   (1) 65kB -
   823    1/candidate: ^                          
   824    1/beta:      ^                          
   825    1/edge:      ^                          
   826  installed:     2.10            (100)  1kB disabled
   827  `)
   828  	c.Check(s.Stderr(), check.Equals, "")
   829  	c.Check(n, check.Equals, 4)
   830  
   831  	// now the same but with unicode on
   832  	s.ResetStdStreams()
   833  	rest, err = snap.Parser(snap.Client()).ParseArgs([]string{"info", "--unicode=always", "hello"})
   834  	c.Assert(err, check.IsNil)
   835  	c.Assert(rest, check.DeepEquals, []string{})
   836  	c.Check(s.Stdout(), check.Equals, `name:      hello
   837  summary:   The GNU Hello snap
   838  publisher: Canonical✓
   839  license:   unset
   840  description: |
   841    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   842    https://snapcraft.io/
   843  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   844  tracking:     beta
   845  refresh-date: 2006-01-02
   846  channels:
   847    1/stable:    2.10 2018-12-18   (1) 65kB -
   848    1/candidate: ↑                          
   849    1/beta:      ↑                          
   850    1/edge:      ↑                          
   851  installed:     2.10            (100)  1kB disabled
   852  `)
   853  	c.Check(s.Stderr(), check.Equals, "")
   854  	c.Check(n, check.Equals, 6)
   855  }
   856  
   857  func (s *infoSuite) TestInfoHumanTimes(c *check.C) {
   858  	// checks that tiemutil.Human is called when no --abs-time is given
   859  	restore := snap.MockTimeutilHuman(func(time.Time) string { return "TOTALLY NOT A ROBOT" })
   860  	defer restore()
   861  
   862  	n := 0
   863  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   864  		switch n {
   865  		case 0:
   866  			c.Check(r.Method, check.Equals, "GET")
   867  			c.Check(r.URL.Path, check.Equals, "/v2/find")
   868  			fmt.Fprintln(w, "{}")
   869  		case 1:
   870  			c.Check(r.Method, check.Equals, "GET")
   871  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello")
   872  			fmt.Fprintln(w, mockInfoJSONNoLicense)
   873  		default:
   874  			c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
   875  		}
   876  
   877  		n++
   878  	})
   879  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello"})
   880  	c.Assert(err, check.IsNil)
   881  	c.Assert(rest, check.DeepEquals, []string{})
   882  	c.Check(s.Stdout(), check.Equals, `name:      hello
   883  summary:   The GNU Hello snap
   884  publisher: Canonical*
   885  license:   unset
   886  description: |
   887    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
   888    https://snapcraft.io/
   889  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
   890  tracking:     beta
   891  refresh-date: TOTALLY NOT A ROBOT
   892  installed:    2.10 (100) 1kB disabled
   893  `)
   894  	c.Check(s.Stderr(), check.Equals, "")
   895  }
   896  
   897  func (infoSuite) TestDescr(c *check.C) {
   898  	for k, v := range map[string]string{
   899  		"": "  \n",
   900  		`one:
   901   * two three four five six  
   902     * seven height nine ten
   903  `: `  one:
   904     * two three four
   905     five six
   906       * seven height
   907       nine ten
   908  `,
   909  		"abcdefghijklm nopqrstuvwxyz ABCDEFGHIJKLMNOPQR STUVWXYZ": `
   910    abcdefghijklm
   911    nopqrstuvwxyz
   912    ABCDEFGHIJKLMNOPQR
   913    STUVWXYZ
   914  `[1:],
   915  		// not much we can do when it won't fit
   916  		"abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ": `
   917    abcdefghijklmnopqr
   918    stuvwxyz
   919    ABCDEFGHIJKLMNOPQR
   920    STUVWXYZ
   921  `[1:],
   922  	} {
   923  		var buf bytes.Buffer
   924  		snap.PrintDescr(&buf, k, 20)
   925  		c.Check(buf.String(), check.Equals, v, check.Commentf("%q", k))
   926  	}
   927  }
   928  
   929  func (infoSuite) TestMaybePrintCohortKey(c *check.C) {
   930  	type T struct {
   931  		snap     *client.Snap
   932  		verbose  bool
   933  		expected string
   934  	}
   935  
   936  	tests := []T{
   937  		{snap: nil, verbose: false, expected: ""},
   938  		{snap: nil, verbose: true, expected: ""},
   939  		{snap: &client.Snap{}, verbose: false, expected: ""},
   940  		{snap: &client.Snap{}, verbose: true, expected: ""},
   941  		{snap: &client.Snap{CohortKey: "some-cohort-key"}, verbose: false, expected: ""},
   942  		{snap: &client.Snap{CohortKey: "some-cohort-key"}, verbose: true, expected: "cohort:\t…-key\n"},
   943  	}
   944  
   945  	var buf flushBuffer
   946  	iw := snap.NewInfoWriter(&buf)
   947  	defer snap.MockIsStdoutTTY(true)()
   948  
   949  	for i, t := range tests {
   950  		buf.Reset()
   951  		snap.SetupSnap(iw, t.snap, nil, nil)
   952  		snap.SetVerbose(iw, t.verbose)
   953  		snap.MaybePrintCohortKey(iw)
   954  		c.Check(buf.String(), check.Equals, t.expected, check.Commentf("tty:true/%d", i))
   955  	}
   956  	// now the same but without a tty -> the last test should no longer ellipt
   957  	tests[len(tests)-1].expected = "cohort:\tsome-cohort-key\n"
   958  	snap.MockIsStdoutTTY(false)
   959  	for i, t := range tests {
   960  		buf.Reset()
   961  		snap.SetupSnap(iw, t.snap, nil, nil)
   962  		snap.SetVerbose(iw, t.verbose)
   963  		snap.MaybePrintCohortKey(iw)
   964  		c.Check(buf.String(), check.Equals, t.expected, check.Commentf("tty:false/%d", i))
   965  	}
   966  }
   967  
   968  func (infoSuite) TestMaybePrintHealth(c *check.C) {
   969  	type T struct {
   970  		snap     *client.Snap
   971  		verbose  bool
   972  		expected string
   973  	}
   974  
   975  	goodHealth := &client.SnapHealth{Status: "okay"}
   976  	t0 := time.Date(1970, 1, 1, 10, 24, 0, 0, time.UTC)
   977  	badHealth := &client.SnapHealth{
   978  		Status:    "waiting",
   979  		Message:   "godot should be here any moment now",
   980  		Code:      "godot-is-a-lie",
   981  		Revision:  snaplib.R("42"),
   982  		Timestamp: t0,
   983  	}
   984  
   985  	tests := []T{
   986  		{snap: nil, verbose: false, expected: ""},
   987  		{snap: nil, verbose: true, expected: ""},
   988  		{snap: &client.Snap{}, verbose: false, expected: ""},
   989  		{snap: &client.Snap{}, verbose: true, expected: `health:
   990    status:	unknown
   991    message:	health
   992      has not been set
   993  `},
   994  		{snap: &client.Snap{Health: goodHealth}, verbose: false, expected: ``},
   995  		{snap: &client.Snap{Health: goodHealth}, verbose: true, expected: `health:
   996    status:	okay
   997  `},
   998  		{snap: &client.Snap{Health: badHealth}, verbose: false, expected: `health:
   999    status:	waiting
  1000    message:	godot
  1001      should be here
  1002      any moment now
  1003    code:	godot-is-a-lie
  1004    checked:	10:24AM
  1005    revision:	42
  1006  `},
  1007  		{snap: &client.Snap{Health: badHealth}, verbose: true, expected: `health:
  1008    status:	waiting
  1009    message:	godot
  1010      should be here
  1011      any moment now
  1012    code:	godot-is-a-lie
  1013    checked:	10:24AM
  1014    revision:	42
  1015  `},
  1016  	}
  1017  
  1018  	var buf flushBuffer
  1019  	iw := snap.NewInfoWriter(&buf)
  1020  	defer snap.MockIsStdoutTTY(false)()
  1021  
  1022  	for i, t := range tests {
  1023  		buf.Reset()
  1024  		snap.SetupSnap(iw, t.snap, nil, nil)
  1025  		snap.SetVerbose(iw, t.verbose)
  1026  		snap.MaybePrintHealth(iw)
  1027  		c.Check(buf.String(), check.Equals, t.expected, check.Commentf("%d", i))
  1028  	}
  1029  }
  1030  
  1031  func (infoSuite) TestWrapCornerCase(c *check.C) {
  1032  	// this particular corner case isn't currently reachable from
  1033  	// printDescr nor printSummary, but best to have it covered
  1034  	var buf bytes.Buffer
  1035  	const s = "This is a paragraph indented with leading spaces that are encoded as multiple bytes. All hail EN SPACE."
  1036  	snap.WrapFlow(&buf, []rune(s), "\u2002\u2002", 30)
  1037  	c.Check(buf.String(), check.Equals, `
  1038    This is a paragraph indented
  1039    with leading spaces that are
  1040    encoded as multiple bytes.
  1041    All hail EN SPACE.
  1042  `[1:])
  1043  }
  1044  
  1045  func (infoSuite) TestBug1828425(c *check.C) {
  1046  	const s = `This is a description
  1047                                    that has
  1048                                    lines
  1049                                    too deeply
  1050                                    indented.
  1051  `
  1052  	var buf bytes.Buffer
  1053  	err := snap.PrintDescr(&buf, s, 30)
  1054  	c.Assert(err, check.IsNil)
  1055  	c.Check(buf.String(), check.Equals, `  This is a description
  1056      that has
  1057      lines
  1058      too deeply
  1059      indented.
  1060  `)
  1061  }
  1062  
  1063  const mockInfoJSONParallelInstance = `
  1064  {
  1065    "type": "sync",
  1066    "status-code": 200,
  1067    "status": "OK",
  1068    "result": {
  1069        "channel": "stable",
  1070        "confinement": "strict",
  1071        "description": "GNU hello prints a friendly greeting. This is part of the snapcraft tour at https://snapcraft.io/",
  1072        "developer": "canonical",
  1073        "publisher": {
  1074           "id": "canonical",
  1075           "username": "canonical",
  1076           "display-name": "Canonical",
  1077           "validation": "verified"
  1078        },
  1079        "id": "mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6",
  1080        "install-date": "2006-01-02T22:04:07.123456789Z",
  1081        "installed-size": 1024,
  1082        "name": "hello_foo",
  1083        "private": false,
  1084        "revision": "100",
  1085        "status": "available",
  1086        "summary": "The GNU Hello snap",
  1087        "type": "app",
  1088        "version": "2.10",
  1089        "license": "",
  1090        "tracking-channel": "beta"
  1091      }
  1092  }
  1093  `
  1094  
  1095  func (s *infoSuite) TestInfoParllelInstance(c *check.C) {
  1096  	n := 0
  1097  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1098  		switch n {
  1099  		case 0:
  1100  			c.Check(r.Method, check.Equals, "GET")
  1101  			c.Check(r.URL.Path, check.Equals, "/v2/find")
  1102  			q := r.URL.Query()
  1103  			// asks for the instance snap
  1104  			c.Check(q.Get("name"), check.Equals, "hello")
  1105  			fmt.Fprintln(w, mockInfoJSONWithChannels)
  1106  		case 1:
  1107  			c.Check(r.Method, check.Equals, "GET")
  1108  			c.Check(r.URL.Path, check.Equals, "/v2/snaps/hello_foo")
  1109  			fmt.Fprintln(w, mockInfoJSONParallelInstance)
  1110  		default:
  1111  			c.Fatalf("expected to get 2 requests, now on %d (%v)", n+1, r)
  1112  		}
  1113  
  1114  		n++
  1115  	})
  1116  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"info", "hello_foo"})
  1117  	c.Assert(err, check.IsNil)
  1118  	c.Assert(rest, check.DeepEquals, []string{})
  1119  	// make sure local and remote info is combined in the output
  1120  	c.Check(s.Stdout(), check.Equals, `name:      hello_foo
  1121  summary:   The GNU Hello snap
  1122  publisher: Canonical*
  1123  license:   unset
  1124  description: |
  1125    GNU hello prints a friendly greeting. This is part of the snapcraft tour at
  1126    https://snapcraft.io/
  1127  snap-id:      mVyGrEwiqSi5PugCwyH7WgpoQLemtTd6
  1128  tracking:     beta
  1129  refresh-date: 2006-01-02
  1130  channels:
  1131    1/stable:    2.10 2018-12-18   (1) 65kB -
  1132    1/candidate: ^                          
  1133    1/beta:      ^                          
  1134    1/edge:      ^                          
  1135  installed:     2.10            (100)  1kB disabled
  1136  `)
  1137  	c.Check(s.Stderr(), check.Equals, "")
  1138  }