github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/cmd/snap/cmd_snap_op_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2018 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package main_test
    21  
    22  import (
    23  	"fmt"
    24  	"io/ioutil"
    25  	"mime"
    26  	"mime/multipart"
    27  	"net/http"
    28  	"net/http/httptest"
    29  	"os"
    30  	"path/filepath"
    31  	"regexp"
    32  	"strings"
    33  	"time"
    34  
    35  	"gopkg.in/check.v1"
    36  
    37  	"github.com/snapcore/snapd/client"
    38  	snap "github.com/snapcore/snapd/cmd/snap"
    39  	"github.com/snapcore/snapd/progress"
    40  	"github.com/snapcore/snapd/progress/progresstest"
    41  	"github.com/snapcore/snapd/release"
    42  	"github.com/snapcore/snapd/testutil"
    43  )
    44  
    45  type snapOpTestServer struct {
    46  	c *check.C
    47  
    48  	checker         func(r *http.Request)
    49  	n               int
    50  	total           int
    51  	channel         string
    52  	trackingChannel string
    53  	confinement     string
    54  	restart         string
    55  	snap            string
    56  }
    57  
    58  var _ = check.Suite(&SnapOpSuite{})
    59  
    60  func (t *snapOpTestServer) handle(w http.ResponseWriter, r *http.Request) {
    61  	switch t.n {
    62  	case 0:
    63  		t.checker(r)
    64  		method := "POST"
    65  		if strings.HasSuffix(r.URL.Path, "/conf") {
    66  			method = "PUT"
    67  		}
    68  		t.c.Check(r.Method, check.Equals, method)
    69  		w.WriteHeader(202)
    70  		fmt.Fprintln(w, `{"type":"async", "change": "42", "status-code": 202}`)
    71  	case 1:
    72  		t.c.Check(r.Method, check.Equals, "GET")
    73  		t.c.Check(r.URL.Path, check.Equals, "/v2/changes/42")
    74  		if t.restart == "" {
    75  			fmt.Fprintln(w, `{"type": "sync", "result": {"status": "Doing"}}`)
    76  		} else {
    77  			fmt.Fprintln(w, fmt.Sprintf(`{"type": "sync", "result": {"status": "Doing"}, "maintenance": {"kind": "system-restart", "message": "system is %sing", "value": {"op": %q}}}}`, t.restart, t.restart))
    78  		}
    79  	case 2:
    80  		t.c.Check(r.Method, check.Equals, "GET")
    81  		t.c.Check(r.URL.Path, check.Equals, "/v2/changes/42")
    82  		fmt.Fprintf(w, `{"type": "sync", "result": {"ready": true, "status": "Done", "data": {"snap-name": "%s"}}}\n`, t.snap)
    83  	case 3:
    84  		t.c.Check(r.Method, check.Equals, "GET")
    85  		t.c.Check(r.URL.Path, check.Equals, "/v2/snaps")
    86  		fmt.Fprintf(w, `{"type": "sync", "result": [{"name": "%s", "status": "active", "version": "1.0", "developer": "bar", "publisher": {"id": "bar-id", "username": "bar", "display-name": "Bar", "validation": "unproven"}, "revision":42, "channel":"%s", "tracking-channel": "%s", "confinement": "%s"}]}\n`, t.snap, t.channel, t.trackingChannel, t.confinement)
    87  	default:
    88  		t.c.Fatalf("expected to get %d requests, now on %d", t.total, t.n+1)
    89  	}
    90  
    91  	t.n++
    92  }
    93  
    94  type SnapOpSuite struct {
    95  	BaseSnapSuite
    96  
    97  	restoreAll func()
    98  	srv        snapOpTestServer
    99  }
   100  
   101  func (s *SnapOpSuite) SetUpTest(c *check.C) {
   102  	s.BaseSnapSuite.SetUpTest(c)
   103  
   104  	restoreClientRetry := client.MockDoTimings(time.Millisecond, time.Second)
   105  	restorePollTime := snap.MockPollTime(time.Millisecond)
   106  	s.restoreAll = func() {
   107  		restoreClientRetry()
   108  		restorePollTime()
   109  	}
   110  
   111  	s.srv = snapOpTestServer{
   112  		c:     c,
   113  		total: 4,
   114  		snap:  "foo",
   115  	}
   116  }
   117  
   118  func (s *SnapOpSuite) TearDownTest(c *check.C) {
   119  	s.restoreAll()
   120  	s.BaseSnapSuite.TearDownTest(c)
   121  }
   122  
   123  func (s *SnapOpSuite) TestWait(c *check.C) {
   124  	meter := &progresstest.Meter{}
   125  	defer progress.MockMeter(meter)()
   126  	restore := snap.MockMaxGoneTime(time.Millisecond)
   127  	defer restore()
   128  
   129  	// lazy way of getting a URL that won't work nor break stuff
   130  	server := httptest.NewServer(nil)
   131  	snap.ClientConfig.BaseURL = server.URL
   132  	server.Close()
   133  
   134  	cli := snap.Client()
   135  	chg, err := snap.Wait(cli, "x")
   136  	c.Assert(chg, check.IsNil)
   137  	c.Assert(err, check.NotNil)
   138  	c.Check(meter.Labels, testutil.Contains, "Waiting for server to restart")
   139  }
   140  
   141  func (s *SnapOpSuite) TestWaitRecovers(c *check.C) {
   142  	meter := &progresstest.Meter{}
   143  	defer progress.MockMeter(meter)()
   144  
   145  	nah := true
   146  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   147  		if nah {
   148  			nah = false
   149  			return
   150  		}
   151  		fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done"}}`)
   152  	})
   153  
   154  	cli := snap.Client()
   155  	chg, err := snap.Wait(cli, "x")
   156  	// we got the change
   157  	c.Check(chg, check.NotNil)
   158  	c.Assert(err, check.IsNil)
   159  
   160  	// but only after recovering
   161  	c.Check(meter.Labels, testutil.Contains, "Waiting for server to restart")
   162  }
   163  
   164  func (s *SnapOpSuite) TestWaitRebooting(c *check.C) {
   165  	meter := &progresstest.Meter{}
   166  	defer progress.MockMeter(meter)()
   167  	restore := snap.MockMaxGoneTime(time.Millisecond)
   168  	defer restore()
   169  
   170  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   171  		fmt.Fprintln(w, `{"type": "sync",
   172  "result": {
   173  "ready": false,
   174  "status": "Doing",
   175  "tasks": [{"kind": "bar", "summary": "...", "status": "Doing", "progress": {"done": 1, "total": 1}, "log": ["INFO: info"]}]
   176  },
   177  "maintenance": {"kind": "system-restart", "message": "system is restarting"}}`)
   178  	})
   179  
   180  	cli := snap.Client()
   181  	chg, err := snap.Wait(cli, "x")
   182  	c.Assert(chg, check.IsNil)
   183  	c.Assert(err, check.DeepEquals, &client.Error{Kind: client.ErrorKindSystemRestart, Message: "system is restarting"})
   184  
   185  	// last available info is still displayed
   186  	c.Check(meter.Notices, testutil.Contains, "INFO: info")
   187  }
   188  
   189  func (s *SnapOpSuite) TestInstall(c *check.C) {
   190  	s.srv.checker = func(r *http.Request) {
   191  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   192  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   193  			"action":     "install",
   194  			"channel":    "candidate",
   195  			"cohort-key": "what",
   196  		})
   197  		s.srv.channel = "candidate"
   198  	}
   199  
   200  	s.RedirectClientToTestServer(s.srv.handle)
   201  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "candidate", "--cohort", "what", "foo"})
   202  	c.Assert(err, check.IsNil)
   203  	c.Assert(rest, check.DeepEquals, []string{})
   204  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(candidate\) 1.0 from Bar installed`)
   205  	c.Check(s.Stderr(), check.Equals, "")
   206  	// ensure that the fake server api was actually hit
   207  	c.Check(s.srv.n, check.Equals, s.srv.total)
   208  }
   209  
   210  func (s *SnapOpSuite) TestInstallIgnoreRunning(c *check.C) {
   211  	s.srv.checker = func(r *http.Request) {
   212  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   213  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   214  			"action":         "install",
   215  			"ignore-running": true,
   216  		})
   217  	}
   218  
   219  	s.RedirectClientToTestServer(s.srv.handle)
   220  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--ignore-running", "foo"})
   221  	c.Assert(err, check.IsNil)
   222  	c.Assert(rest, check.DeepEquals, []string{})
   223  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`)
   224  	c.Check(s.Stderr(), check.Equals, "")
   225  	// ensure that the fake server api was actually hit
   226  	c.Check(s.srv.n, check.Equals, s.srv.total)
   227  }
   228  
   229  func (s *SnapOpSuite) TestInstallNoPATH(c *check.C) {
   230  	// PATH restored by test tear down
   231  	os.Setenv("PATH", "/bin:/usr/bin:/sbin:/usr/sbin")
   232  	s.srv.checker = func(r *http.Request) {
   233  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   234  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   235  			"action":  "install",
   236  			"channel": "candidate",
   237  		})
   238  		s.srv.channel = "candidate"
   239  	}
   240  
   241  	s.RedirectClientToTestServer(s.srv.handle)
   242  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "candidate", "foo"})
   243  	c.Assert(err, check.IsNil)
   244  	c.Assert(rest, check.DeepEquals, []string{})
   245  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(candidate\) 1.0 from Bar installed`)
   246  	c.Check(s.Stderr(), testutil.MatchesWrapped, `Warning: \S+/bin was not found in your \$PATH.*`)
   247  	// ensure that the fake server api was actually hit
   248  	c.Check(s.srv.n, check.Equals, s.srv.total)
   249  }
   250  
   251  func (s *SnapOpSuite) TestInstallNoPATHMaybeResetBySudo(c *check.C) {
   252  	// PATH restored by test tear down
   253  	os.Setenv("PATH", "/bin:/usr/bin:/sbin:/usr/sbin")
   254  	if old, isset := os.LookupEnv("SUDO_UID"); isset {
   255  		defer os.Setenv("SUDO_UID", old)
   256  	}
   257  	os.Setenv("SUDO_UID", "1234")
   258  	restore := release.MockReleaseInfo(&release.OS{ID: "fedora"})
   259  	defer restore()
   260  	s.srv.checker = func(r *http.Request) {
   261  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   262  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   263  			"action":  "install",
   264  			"channel": "candidate",
   265  		})
   266  		s.srv.channel = "candidate"
   267  	}
   268  
   269  	s.RedirectClientToTestServer(s.srv.handle)
   270  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "candidate", "foo"})
   271  	c.Assert(err, check.IsNil)
   272  	c.Assert(rest, check.DeepEquals, []string{})
   273  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(candidate\) 1.0 from Bar installed`)
   274  	c.Check(s.Stderr(), check.HasLen, 0)
   275  	// ensure that the fake server api was actually hit
   276  	c.Check(s.srv.n, check.Equals, s.srv.total)
   277  }
   278  
   279  func (s *SnapOpSuite) TestInstallFromTrack(c *check.C) {
   280  	s.srv.checker = func(r *http.Request) {
   281  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   282  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   283  			"action":  "install",
   284  			"channel": "3.4",
   285  		})
   286  		s.srv.channel = "3.4/stable"
   287  	}
   288  
   289  	s.RedirectClientToTestServer(s.srv.handle)
   290  	// snap install --channel=3.4 means 3.4/stable, this is what we test here
   291  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "3.4", "foo"})
   292  	c.Assert(err, check.IsNil)
   293  	c.Assert(rest, check.DeepEquals, []string{})
   294  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(3.4/stable\) 1.0 from Bar installed`)
   295  	c.Check(s.Stderr(), check.Equals, "")
   296  	// ensure that the fake server api was actually hit
   297  	c.Check(s.srv.n, check.Equals, s.srv.total)
   298  }
   299  
   300  func (s *SnapOpSuite) TestInstallFromBranch(c *check.C) {
   301  	s.srv.checker = func(r *http.Request) {
   302  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   303  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   304  			"action":  "install",
   305  			"channel": "3.4/stable/hotfix-1",
   306  		})
   307  		s.srv.channel = "3.4/stable/hotfix-1"
   308  	}
   309  
   310  	s.RedirectClientToTestServer(s.srv.handle)
   311  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "3.4/stable/hotfix-1", "foo"})
   312  	c.Assert(err, check.IsNil)
   313  	c.Assert(rest, check.DeepEquals, []string{})
   314  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(3.4/stable/hotfix-1\) 1.0 from Bar installed`)
   315  	c.Check(s.Stderr(), check.Equals, "")
   316  	// ensure that the fake server api was actually hit
   317  	c.Check(s.srv.n, check.Equals, s.srv.total)
   318  }
   319  
   320  func (s *SnapOpSuite) TestInstallSameRiskInTrack(c *check.C) {
   321  	s.srv.checker = func(r *http.Request) {
   322  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   323  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   324  			"action":  "install",
   325  			"channel": "latest/stable",
   326  		})
   327  		s.srv.channel = "stable"
   328  		s.srv.trackingChannel = "latest/stable"
   329  	}
   330  
   331  	s.RedirectClientToTestServer(s.srv.handle)
   332  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "latest/stable", "foo"})
   333  	c.Assert(err, check.IsNil)
   334  	c.Assert(rest, check.DeepEquals, []string{})
   335  	c.Check(s.Stdout(), check.Equals, "foo 1.0 from Bar installed\n")
   336  	c.Check(s.Stderr(), check.Equals, "")
   337  	// ensure that the fake server api was actually hit
   338  	c.Check(s.srv.n, check.Equals, s.srv.total)
   339  }
   340  
   341  func (s *SnapOpSuite) TestInstallSameRiskInDefaultTrack(c *check.C) {
   342  	s.srv.checker = func(r *http.Request) {
   343  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   344  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   345  			"action":  "install",
   346  			"channel": "stable",
   347  		})
   348  		s.srv.channel = "18/stable"
   349  		s.srv.trackingChannel = "18/stable"
   350  	}
   351  
   352  	s.RedirectClientToTestServer(s.srv.handle)
   353  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--stable", "foo"})
   354  	c.Assert(err, check.IsNil)
   355  	c.Assert(rest, check.DeepEquals, []string{})
   356  	c.Check(s.Stdout(), check.Equals, "foo (18/stable) 1.0 from Bar installed\n")
   357  	c.Check(s.Stderr(), check.Equals, "")
   358  	// ensure that the fake server api was actually hit
   359  	c.Check(s.srv.n, check.Equals, s.srv.total)
   360  }
   361  
   362  func (s *SnapOpSuite) TestInstallRiskChannelClosed(c *check.C) {
   363  	s.srv.checker = func(r *http.Request) {
   364  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   365  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   366  			"action":  "install",
   367  			"channel": "edge",
   368  		})
   369  		s.srv.channel = "stable"
   370  		s.srv.trackingChannel = "edge"
   371  	}
   372  
   373  	s.RedirectClientToTestServer(s.srv.handle)
   374  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "edge", "foo"})
   375  	c.Assert(err, check.IsNil)
   376  	c.Assert(rest, check.DeepEquals, []string{})
   377  	c.Check(s.Stdout(), check.Equals, `foo 1.0 from Bar installed
   378  Channel edge for foo is closed; temporarily forwarding to stable.
   379  `)
   380  	c.Check(s.Stderr(), check.Equals, "")
   381  	// ensure that the fake server api was actually hit
   382  	c.Check(s.srv.n, check.Equals, s.srv.total)
   383  }
   384  
   385  func (s *SnapOpSuite) TestInstallDevMode(c *check.C) {
   386  	s.srv.checker = func(r *http.Request) {
   387  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   388  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   389  			"action":  "install",
   390  			"devmode": true,
   391  			"channel": "beta",
   392  		})
   393  		s.srv.channel = "beta"
   394  	}
   395  
   396  	s.RedirectClientToTestServer(s.srv.handle)
   397  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel", "beta", "--devmode", "foo"})
   398  	c.Assert(err, check.IsNil)
   399  	c.Assert(rest, check.DeepEquals, []string{})
   400  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(beta\) 1.0 from Bar installed`)
   401  	c.Check(s.Stderr(), check.Equals, "")
   402  	// ensure that the fake server api was actually hit
   403  	c.Check(s.srv.n, check.Equals, s.srv.total)
   404  }
   405  
   406  func (s *SnapOpSuite) TestInstallClassic(c *check.C) {
   407  	s.srv.checker = func(r *http.Request) {
   408  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   409  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   410  			"action":  "install",
   411  			"classic": true,
   412  		})
   413  		s.srv.confinement = "classic"
   414  	}
   415  
   416  	s.RedirectClientToTestServer(s.srv.handle)
   417  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--classic", "foo"})
   418  	c.Assert(err, check.IsNil)
   419  	c.Assert(rest, check.DeepEquals, []string{})
   420  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`)
   421  	c.Check(s.Stderr(), check.Equals, "")
   422  	// ensure that the fake server api was actually hit
   423  	c.Check(s.srv.n, check.Equals, s.srv.total)
   424  }
   425  
   426  func (s *SnapOpSuite) TestInstallStrictWithClassicFlag(c *check.C) {
   427  	s.srv.checker = func(r *http.Request) {
   428  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   429  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   430  			"action":  "install",
   431  			"classic": true,
   432  		})
   433  		s.srv.confinement = "strict"
   434  	}
   435  
   436  	s.RedirectClientToTestServer(s.srv.handle)
   437  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--classic", "foo"})
   438  	c.Assert(err, check.IsNil)
   439  	c.Assert(rest, check.DeepEquals, []string{})
   440  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`)
   441  	c.Check(s.Stderr(), testutil.MatchesWrapped, `Warning:\s+flag --classic ignored for strictly confined snap foo.*`)
   442  	// ensure that the fake server api was actually hit
   443  	c.Check(s.srv.n, check.Equals, s.srv.total)
   444  }
   445  
   446  func (s *SnapOpSuite) TestInstallUnaliased(c *check.C) {
   447  	s.srv.checker = func(r *http.Request) {
   448  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   449  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   450  			"action":    "install",
   451  			"unaliased": true,
   452  		})
   453  	}
   454  
   455  	s.RedirectClientToTestServer(s.srv.handle)
   456  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--unaliased", "foo"})
   457  	c.Assert(err, check.IsNil)
   458  	c.Assert(rest, check.DeepEquals, []string{})
   459  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`)
   460  	c.Check(s.Stderr(), check.Equals, "")
   461  	// ensure that the fake server api was actually hit
   462  	c.Check(s.srv.n, check.Equals, s.srv.total)
   463  }
   464  
   465  func (s *SnapOpSuite) TestInstallSnapNotFound(c *check.C) {
   466  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   467  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "snap not found", "value": "foo", "kind": "snap-not-found"}, "status-code": 404}`)
   468  	})
   469  
   470  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"})
   471  	c.Assert(err, check.NotNil)
   472  	c.Check(fmt.Sprintf("error: %v\n", err), check.Equals, `error: snap "foo" not found
   473  `)
   474  
   475  	c.Check(s.Stdout(), check.Equals, "")
   476  	c.Check(s.Stderr(), check.Equals, "")
   477  }
   478  
   479  func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailable(c *check.C) {
   480  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   481  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision available as specified", "value": "foo", "kind": "snap-revision-not-available"}, "status-code": 404}`)
   482  	})
   483  
   484  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"})
   485  	c.Assert(err, check.NotNil)
   486  	c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, `
   487  error: snap "foo" not available as specified (see 'snap info foo')
   488  `)
   489  
   490  	c.Check(s.Stdout(), check.Equals, "")
   491  	c.Check(s.Stderr(), check.Equals, "")
   492  }
   493  
   494  func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableOnChannel(c *check.C) {
   495  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   496  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision available as specified", "value": "foo", "kind": "snap-revision-not-available"}, "status-code": 404}`)
   497  	})
   498  
   499  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=mytrack", "foo"})
   500  	c.Check(err, check.ErrorMatches, `snap "foo" not available on channel "mytrack" \(see 'snap info foo'\)`)
   501  
   502  	c.Check(s.Stdout(), check.Equals, "")
   503  	c.Check(s.Stderr(), check.Equals, "")
   504  }
   505  
   506  func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableAtRevision(c *check.C) {
   507  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   508  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision available as specified", "value": "foo", "kind": "snap-revision-not-available"}, "status-code": 404}`)
   509  	})
   510  
   511  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--revision=2", "foo"})
   512  	c.Assert(err, check.NotNil)
   513  	c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, `
   514  error: snap "foo" revision 2 not available (see 'snap info foo')
   515  `)
   516  
   517  	c.Check(s.Stdout(), check.Equals, "")
   518  	c.Check(s.Stderr(), check.Equals, "")
   519  }
   520  
   521  func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelTrackOK(c *check.C) {
   522  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   523  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": {
   524    "snap-name": "foo",
   525    "action": "install",
   526    "architecture": "amd64",
   527    "channel": "stable",
   528    "releases": [{"architecture": "amd64", "channel": "beta"},
   529                 {"architecture": "amd64", "channel": "edge"}]
   530  }, "kind": "snap-channel-not-available"}, "status-code": 404}`)
   531  	})
   532  
   533  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"})
   534  	c.Assert(err, check.NotNil)
   535  	c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, `
   536  error: snap "foo" is not available on stable but is available to install on the
   537         following channels:
   538  
   539         beta       snap install --beta foo
   540         edge       snap install --edge foo
   541  
   542         Please be mindful pre-release channels may include features not
   543         completely tested or implemented. Get more information with 'snap info
   544         foo'.
   545  `)
   546  
   547  	c.Check(s.Stdout(), check.Equals, "")
   548  	c.Check(s.Stderr(), check.Equals, "")
   549  }
   550  
   551  func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelTrackOKPrerelOK(c *check.C) {
   552  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   553  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": {
   554    "snap-name": "foo",
   555    "action": "install",
   556    "architecture": "amd64",
   557    "channel": "candidate",
   558    "releases": [{"architecture": "amd64", "channel": "beta"},
   559                 {"architecture": "amd64", "channel": "edge"}]
   560  }, "kind": "snap-channel-not-available"}, "status-code": 404}`)
   561  	})
   562  
   563  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--candidate", "foo"})
   564  	c.Assert(err, check.NotNil)
   565  	c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, `
   566  error: snap "foo" is not available on candidate but is available to install on
   567         the following channels:
   568  
   569         beta       snap install --beta foo
   570         edge       snap install --edge foo
   571  
   572         Get more information with 'snap info foo'.
   573  `)
   574  
   575  	c.Check(s.Stdout(), check.Equals, "")
   576  	c.Check(s.Stderr(), check.Equals, "")
   577  }
   578  
   579  func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelTrackOther(c *check.C) {
   580  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   581  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": {
   582    "snap-name": "foo",
   583    "action": "install",
   584    "architecture": "amd64",
   585    "channel": "stable",
   586    "releases": [{"architecture": "amd64", "channel": "1.0/stable"},
   587                 {"architecture": "amd64", "channel": "2.0/stable"}]
   588  }, "kind": "snap-channel-not-available"}, "status-code": 404}`)
   589  	})
   590  
   591  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"})
   592  	c.Assert(err, check.NotNil)
   593  	c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, `
   594  error: snap "foo" is not available on latest/stable but is available to install
   595         on the following tracks:
   596  
   597         1.0/stable  snap install --channel=1.0 foo
   598         2.0/stable  snap install --channel=2.0 foo
   599  
   600         Please be mindful that different tracks may include different features.
   601         Get more information with 'snap info foo'.
   602  `)
   603  
   604  	c.Check(s.Stdout(), check.Equals, "")
   605  	c.Check(s.Stderr(), check.Equals, "")
   606  }
   607  
   608  func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelTrackLatestStable(c *check.C) {
   609  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   610  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": {
   611    "snap-name": "foo",
   612    "action": "install",
   613    "architecture": "amd64",
   614    "channel": "2.0/stable",
   615    "releases": [{"architecture": "amd64", "channel": "stable"}]
   616  }, "kind": "snap-channel-not-available"}, "status-code": 404}`)
   617  	})
   618  
   619  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=2.0/stable", "foo"})
   620  	c.Assert(err, check.NotNil)
   621  	c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, `
   622  error: snap "foo" is not available on 2.0/stable but is available to install on
   623         the following tracks:
   624  
   625         latest/stable  snap install --stable foo
   626  
   627         Please be mindful that different tracks may include different features.
   628         Get more information with 'snap info foo'.
   629  `)
   630  
   631  	c.Check(s.Stdout(), check.Equals, "")
   632  	c.Check(s.Stderr(), check.Equals, "")
   633  }
   634  
   635  func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelTrackAndRiskOther(c *check.C) {
   636  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   637  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": {
   638    "snap-name": "foo",
   639    "action": "install",
   640    "architecture": "amd64",
   641    "channel": "2.0/stable",
   642    "releases": [{"architecture": "amd64", "channel": "1.0/edge"}]
   643  }, "kind": "snap-channel-not-available"}, "status-code": 404}`)
   644  	})
   645  
   646  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=2.0/stable", "foo"})
   647  	c.Assert(err, check.NotNil)
   648  	c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, `
   649  error: snap "foo" is not available on 2.0/stable but other tracks exist.
   650  
   651         Please be mindful that different tracks may include different features.
   652         Get more information with 'snap info foo'.
   653  `)
   654  
   655  	c.Check(s.Stdout(), check.Equals, "")
   656  	c.Check(s.Stderr(), check.Equals, "")
   657  }
   658  
   659  func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForArchitectureTrackAndRiskOK(c *check.C) {
   660  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   661  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified architecture", "value": {
   662    "snap-name": "foo",
   663    "action": "install",
   664    "architecture": "arm64",
   665    "channel": "stable",
   666    "releases": [{"architecture": "amd64", "channel": "stable"},
   667                 {"architecture": "s390x", "channel": "stable"}]
   668  }, "kind": "snap-architecture-not-available"}, "status-code": 404}`)
   669  	})
   670  
   671  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"})
   672  	c.Assert(err, check.NotNil)
   673  	c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, `
   674  error: snap "foo" is not available on stable for this architecture (arm64) but
   675         exists on other architectures (amd64, s390x).
   676  `)
   677  
   678  	c.Check(s.Stdout(), check.Equals, "")
   679  	c.Check(s.Stderr(), check.Equals, "")
   680  }
   681  
   682  func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForArchitectureTrackAndRiskOther(c *check.C) {
   683  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   684  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified architecture", "value": {
   685    "snap-name": "foo",
   686    "action": "install",
   687    "architecture": "arm64",
   688    "channel": "1.0/stable",
   689    "releases": [{"architecture": "amd64", "channel": "stable"},
   690                 {"architecture": "s390x", "channel": "stable"}]
   691  }, "kind": "snap-architecture-not-available"}, "status-code": 404}`)
   692  	})
   693  
   694  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=1.0/stable", "foo"})
   695  	c.Assert(err, check.NotNil)
   696  	c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, `
   697  error: snap "foo" is not available on this architecture (arm64) but exists on
   698         other architectures (amd64, s390x).
   699  `)
   700  
   701  	c.Check(s.Stdout(), check.Equals, "")
   702  	c.Check(s.Stderr(), check.Equals, "")
   703  }
   704  
   705  func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableInvalidChannel(c *check.C) {
   706  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   707  		c.Fatal("unexpected call to server")
   708  	})
   709  
   710  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=a/b/c/d", "foo"})
   711  	c.Assert(err, check.ErrorMatches, "channel name has too many components: a/b/c/d")
   712  
   713  	c.Check(s.Stdout(), check.Equals, "")
   714  	c.Check(s.Stderr(), check.Equals, "")
   715  }
   716  
   717  func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelNonExistingBranchOnMainChannel(c *check.C) {
   718  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   719  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": {
   720    "snap-name": "foo",
   721    "action": "install",
   722    "architecture": "amd64",
   723    "channel": "stable/baz",
   724    "releases": [{"architecture": "amd64", "channel": "stable"}]
   725  }, "kind": "snap-channel-not-available"}, "status-code": 404}`)
   726  	})
   727  
   728  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=stable/baz", "foo"})
   729  	c.Assert(err, check.NotNil)
   730  	c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, `
   731  error: requested a non-existing branch on latest/stable for snap "foo": baz
   732  `)
   733  
   734  	c.Check(s.Stdout(), check.Equals, "")
   735  	c.Check(s.Stderr(), check.Equals, "")
   736  }
   737  
   738  func (s *SnapOpSuite) TestInstallSnapRevisionNotAvailableForChannelNonExistingBranch(c *check.C) {
   739  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
   740  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "no snap revision on specified channel", "value": {
   741    "snap-name": "foo",
   742    "action": "install",
   743    "architecture": "amd64",
   744    "channel": "stable/baz",
   745    "releases": [{"architecture": "amd64", "channel": "edge"}]
   746  }, "kind": "snap-channel-not-available"}, "status-code": 404}`)
   747  	})
   748  
   749  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--channel=stable/baz", "foo"})
   750  	c.Assert(err, check.NotNil)
   751  	c.Check(fmt.Sprintf("\nerror: %v\n", err), check.Equals, `
   752  error: requested a non-existing branch for snap "foo": latest/stable/baz
   753  `)
   754  
   755  	c.Check(s.Stdout(), check.Equals, "")
   756  	c.Check(s.Stderr(), check.Equals, "")
   757  }
   758  
   759  func testForm(r *http.Request, c *check.C) *multipart.Form {
   760  	contentType := r.Header.Get("Content-Type")
   761  	mediaType, params, err := mime.ParseMediaType(contentType)
   762  	c.Assert(err, check.IsNil)
   763  	c.Assert(params["boundary"], check.Matches, ".{10,}")
   764  	c.Check(mediaType, check.Equals, "multipart/form-data")
   765  
   766  	form, err := multipart.NewReader(r.Body, params["boundary"]).ReadForm(1 << 20)
   767  	c.Assert(err, check.IsNil)
   768  
   769  	return form
   770  }
   771  
   772  func formFile(form *multipart.Form, c *check.C) (name, filename string, content []byte) {
   773  	c.Assert(form.File, check.HasLen, 1)
   774  
   775  	for name, fheaders := range form.File {
   776  		c.Assert(fheaders, check.HasLen, 1)
   777  		body, err := fheaders[0].Open()
   778  		c.Assert(err, check.IsNil)
   779  		defer body.Close()
   780  		filename = fheaders[0].Filename
   781  		content, err = ioutil.ReadAll(body)
   782  		c.Assert(err, check.IsNil)
   783  
   784  		return name, filename, content
   785  	}
   786  
   787  	return "", "", nil
   788  }
   789  
   790  func (s *SnapOpSuite) TestInstallPath(c *check.C) {
   791  	s.srv.checker = func(r *http.Request) {
   792  		c.Check(r.URL.Path, check.Equals, "/v2/snaps")
   793  
   794  		form := testForm(r, c)
   795  		defer form.RemoveAll()
   796  
   797  		c.Check(form.Value["action"], check.DeepEquals, []string{"install"})
   798  		c.Check(form.Value["devmode"], check.IsNil)
   799  		c.Check(form.Value["snap-path"], check.NotNil)
   800  		c.Check(form.Value, check.HasLen, 2)
   801  
   802  		name, _, body := formFile(form, c)
   803  		c.Check(name, check.Equals, "snap")
   804  		c.Check(string(body), check.Equals, "snap-data")
   805  	}
   806  
   807  	snapBody := []byte("snap-data")
   808  	s.RedirectClientToTestServer(s.srv.handle)
   809  	snapPath := filepath.Join(c.MkDir(), "foo.snap")
   810  	err := ioutil.WriteFile(snapPath, snapBody, 0644)
   811  	c.Assert(err, check.IsNil)
   812  
   813  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", snapPath})
   814  	c.Assert(err, check.IsNil)
   815  	c.Assert(rest, check.DeepEquals, []string{})
   816  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`)
   817  	c.Check(s.Stderr(), check.Equals, "")
   818  	// ensure that the fake server api was actually hit
   819  	c.Check(s.srv.n, check.Equals, s.srv.total)
   820  }
   821  
   822  func (s *SnapOpSuite) TestInstallPathDevMode(c *check.C) {
   823  	s.srv.checker = func(r *http.Request) {
   824  		c.Check(r.URL.Path, check.Equals, "/v2/snaps")
   825  		form := testForm(r, c)
   826  		defer form.RemoveAll()
   827  
   828  		c.Check(form.Value["action"], check.DeepEquals, []string{"install"})
   829  		c.Check(form.Value["devmode"], check.DeepEquals, []string{"true"})
   830  		c.Check(form.Value["snap-path"], check.NotNil)
   831  		c.Check(form.Value, check.HasLen, 3)
   832  
   833  		name, _, body := formFile(form, c)
   834  		c.Check(name, check.Equals, "snap")
   835  		c.Check(string(body), check.Equals, "snap-data")
   836  	}
   837  
   838  	snapBody := []byte("snap-data")
   839  	s.RedirectClientToTestServer(s.srv.handle)
   840  	snapPath := filepath.Join(c.MkDir(), "foo.snap")
   841  	err := ioutil.WriteFile(snapPath, snapBody, 0644)
   842  	c.Assert(err, check.IsNil)
   843  
   844  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--devmode", snapPath})
   845  	c.Assert(err, check.IsNil)
   846  	c.Assert(rest, check.DeepEquals, []string{})
   847  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`)
   848  	c.Check(s.Stderr(), check.Equals, "")
   849  	// ensure that the fake server api was actually hit
   850  	c.Check(s.srv.n, check.Equals, s.srv.total)
   851  }
   852  
   853  func (s *SnapOpSuite) TestInstallPathClassic(c *check.C) {
   854  	s.srv.checker = func(r *http.Request) {
   855  		c.Check(r.URL.Path, check.Equals, "/v2/snaps")
   856  		form := testForm(r, c)
   857  		defer form.RemoveAll()
   858  
   859  		c.Check(form.Value["action"], check.DeepEquals, []string{"install"})
   860  		c.Check(form.Value["classic"], check.DeepEquals, []string{"true"})
   861  		c.Check(form.Value["snap-path"], check.NotNil)
   862  		c.Check(form.Value, check.HasLen, 3)
   863  
   864  		name, _, body := formFile(form, c)
   865  		c.Check(name, check.Equals, "snap")
   866  		c.Check(string(body), check.Equals, "snap-data")
   867  
   868  		s.srv.confinement = "classic"
   869  	}
   870  
   871  	snapBody := []byte("snap-data")
   872  	s.RedirectClientToTestServer(s.srv.handle)
   873  	snapPath := filepath.Join(c.MkDir(), "foo.snap")
   874  	err := ioutil.WriteFile(snapPath, snapBody, 0644)
   875  	c.Assert(err, check.IsNil)
   876  
   877  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--classic", snapPath})
   878  	c.Assert(err, check.IsNil)
   879  	c.Assert(rest, check.DeepEquals, []string{})
   880  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`)
   881  	c.Check(s.Stderr(), check.Equals, "")
   882  	// ensure that the fake server api was actually hit
   883  	c.Check(s.srv.n, check.Equals, s.srv.total)
   884  }
   885  
   886  func (s *SnapOpSuite) TestInstallPathDangerous(c *check.C) {
   887  	s.srv.checker = func(r *http.Request) {
   888  		c.Check(r.URL.Path, check.Equals, "/v2/snaps")
   889  		form := testForm(r, c)
   890  		defer form.RemoveAll()
   891  
   892  		c.Check(form.Value["action"], check.DeepEquals, []string{"install"})
   893  		c.Check(form.Value["dangerous"], check.DeepEquals, []string{"true"})
   894  		c.Check(form.Value["snap-path"], check.NotNil)
   895  		c.Check(form.Value, check.HasLen, 3)
   896  
   897  		name, _, body := formFile(form, c)
   898  		c.Check(name, check.Equals, "snap")
   899  		c.Check(string(body), check.Equals, "snap-data")
   900  	}
   901  
   902  	snapBody := []byte("snap-data")
   903  	s.RedirectClientToTestServer(s.srv.handle)
   904  	snapPath := filepath.Join(c.MkDir(), "foo.snap")
   905  	err := ioutil.WriteFile(snapPath, snapBody, 0644)
   906  	c.Assert(err, check.IsNil)
   907  
   908  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--dangerous", snapPath})
   909  	c.Assert(err, check.IsNil)
   910  	c.Assert(rest, check.DeepEquals, []string{})
   911  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar installed`)
   912  	c.Check(s.Stderr(), check.Equals, "")
   913  	// ensure that the fake server api was actually hit
   914  	c.Check(s.srv.n, check.Equals, s.srv.total)
   915  }
   916  
   917  func (s *SnapOpSuite) TestInstallPathInstance(c *check.C) {
   918  	s.srv.checker = func(r *http.Request) {
   919  		c.Check(r.URL.Path, check.Equals, "/v2/snaps")
   920  
   921  		form := testForm(r, c)
   922  		defer form.RemoveAll()
   923  
   924  		c.Check(form.Value["action"], check.DeepEquals, []string{"install"})
   925  		c.Check(form.Value["name"], check.DeepEquals, []string{"foo_bar"})
   926  		c.Check(form.Value["devmode"], check.IsNil)
   927  		c.Check(form.Value["snap-path"], check.NotNil)
   928  		c.Check(form.Value, check.HasLen, 3)
   929  
   930  		name, _, body := formFile(form, c)
   931  		c.Check(name, check.Equals, "snap")
   932  		c.Check(string(body), check.Equals, "snap-data")
   933  	}
   934  
   935  	snapBody := []byte("snap-data")
   936  	s.RedirectClientToTestServer(s.srv.handle)
   937  	// instance is named foo_bar
   938  	s.srv.snap = "foo_bar"
   939  	snapPath := filepath.Join(c.MkDir(), "foo.snap")
   940  	err := ioutil.WriteFile(snapPath, snapBody, 0644)
   941  	c.Assert(err, check.IsNil)
   942  
   943  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", snapPath, "--name", "foo_bar"})
   944  	c.Assert(rest, check.DeepEquals, []string{})
   945  	c.Assert(err, check.IsNil)
   946  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo_bar 1.0 from Bar installed`)
   947  	c.Check(s.Stderr(), check.Equals, "")
   948  	// ensure that the fake server api was actually hit
   949  	c.Check(s.srv.n, check.Equals, s.srv.total)
   950  }
   951  
   952  func (s *SnapSuite) TestInstallWithInstanceNoPath(c *check.C) {
   953  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--name", "foo_bar", "some-snap"})
   954  	c.Assert(err, check.ErrorMatches, "cannot use explicit name when installing from store")
   955  }
   956  
   957  func (s *SnapSuite) TestInstallManyWithInstance(c *check.C) {
   958  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--name", "foo_bar", "some-snap-1", "some-snap-2"})
   959  	c.Assert(err, check.ErrorMatches, "cannot use instance name when installing multiple snaps")
   960  }
   961  
   962  func (s *SnapOpSuite) TestRevertRunthrough(c *check.C) {
   963  	s.srv.total = 4
   964  	s.srv.channel = "potato"
   965  	s.srv.checker = func(r *http.Request) {
   966  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   967  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
   968  			"action": "revert",
   969  		})
   970  	}
   971  
   972  	s.RedirectClientToTestServer(s.srv.handle)
   973  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"revert", "foo"})
   974  	c.Assert(err, check.IsNil)
   975  	c.Assert(rest, check.DeepEquals, []string{})
   976  	// tracking channel is "" in the test server
   977  	c.Check(s.Stdout(), check.Equals, `foo reverted to 1.0
   978  `)
   979  	c.Check(s.Stderr(), check.Equals, "")
   980  	// ensure that the fake server api was actually hit
   981  	c.Check(s.srv.n, check.Equals, s.srv.total)
   982  }
   983  
   984  func (s *SnapOpSuite) runRevertTest(c *check.C, opts *client.SnapOptions) {
   985  	modes := []struct {
   986  		enabled bool
   987  		name    string
   988  	}{
   989  		{opts.DevMode, "devmode"},
   990  		{opts.JailMode, "jailmode"},
   991  		{opts.Classic, "classic"},
   992  	}
   993  
   994  	s.srv.checker = func(r *http.Request) {
   995  
   996  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
   997  		d := DecodedRequestBody(c, r)
   998  
   999  		n := 1
  1000  		c.Check(d["action"], check.Equals, "revert")
  1001  
  1002  		for _, mode := range modes {
  1003  			if mode.enabled {
  1004  				n++
  1005  				c.Check(d[mode.name], check.Equals, true)
  1006  			} else {
  1007  				c.Check(d[mode.name], check.IsNil)
  1008  			}
  1009  		}
  1010  		c.Check(d, check.HasLen, n)
  1011  	}
  1012  
  1013  	s.RedirectClientToTestServer(s.srv.handle)
  1014  
  1015  	cmd := []string{"revert", "foo"}
  1016  	for _, mode := range modes {
  1017  		if mode.enabled {
  1018  			cmd = append(cmd, "--"+mode.name)
  1019  		}
  1020  	}
  1021  
  1022  	rest, err := snap.Parser(snap.Client()).ParseArgs(cmd)
  1023  	c.Assert(err, check.IsNil)
  1024  	c.Assert(rest, check.DeepEquals, []string{})
  1025  	c.Check(s.Stdout(), check.Equals, "foo reverted to 1.0\n")
  1026  	c.Check(s.Stderr(), check.Equals, "")
  1027  	// ensure that the fake server api was actually hit
  1028  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1029  }
  1030  
  1031  func (s *SnapOpSuite) TestRevertNoMode(c *check.C) {
  1032  	s.runRevertTest(c, &client.SnapOptions{})
  1033  }
  1034  
  1035  func (s *SnapOpSuite) TestRevertDevMode(c *check.C) {
  1036  	s.runRevertTest(c, &client.SnapOptions{DevMode: true})
  1037  }
  1038  
  1039  func (s *SnapOpSuite) TestRevertJailMode(c *check.C) {
  1040  	s.runRevertTest(c, &client.SnapOptions{JailMode: true})
  1041  }
  1042  
  1043  func (s *SnapOpSuite) TestRevertClassic(c *check.C) {
  1044  	s.runRevertTest(c, &client.SnapOptions{Classic: true})
  1045  }
  1046  
  1047  func (s *SnapOpSuite) TestRevertMissingName(c *check.C) {
  1048  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"revert"})
  1049  	c.Assert(err, check.NotNil)
  1050  	c.Assert(err, check.ErrorMatches, "the required argument `<snap>` was not provided")
  1051  }
  1052  
  1053  func (s *SnapSuite) TestRefreshListLessOptions(c *check.C) {
  1054  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1055  		c.Fatal("expected to get 0 requests")
  1056  	})
  1057  
  1058  	for _, flag := range []string{"--beta", "--channel=potato", "--classic"} {
  1059  		_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--list", flag})
  1060  		c.Assert(err, check.ErrorMatches, "--list does not accept additional arguments")
  1061  
  1062  		_, err = snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--list", flag, "some-snap"})
  1063  		c.Assert(err, check.ErrorMatches, "--list does not accept additional arguments")
  1064  	}
  1065  }
  1066  
  1067  func (s *SnapSuite) TestRefreshList(c *check.C) {
  1068  	n := 0
  1069  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1070  		switch n {
  1071  		case 0:
  1072  			c.Check(r.Method, check.Equals, "GET")
  1073  			c.Check(r.URL.Path, check.Equals, "/v2/find")
  1074  			c.Check(r.URL.Query().Get("select"), check.Equals, "refresh")
  1075  			fmt.Fprintln(w, `{"type": "sync", "result": [{"name": "foo", "status": "active", "version": "4.2update1", "developer": "bar", "publisher": {"id": "bar-id", "username": "bar", "display-name": "Bar", "validation": "unproven"}, "revision":17,"summary":"some summary"}]}`)
  1076  		default:
  1077  			c.Fatalf("expected to get 1 requests, now on %d", n+1)
  1078  		}
  1079  
  1080  		n++
  1081  	})
  1082  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--list"})
  1083  	c.Assert(err, check.IsNil)
  1084  	c.Assert(rest, check.DeepEquals, []string{})
  1085  	c.Check(s.Stdout(), check.Matches, `Name +Version +Rev +Publisher +Notes
  1086  foo +4.2update1 +17 +bar +-.*
  1087  `)
  1088  	c.Check(s.Stderr(), check.Equals, "")
  1089  	// ensure that the fake server api was actually hit
  1090  	c.Check(n, check.Equals, 1)
  1091  }
  1092  
  1093  func (s *SnapSuite) TestRefreshLegacyTime(c *check.C) {
  1094  	n := 0
  1095  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1096  		switch n {
  1097  		case 0:
  1098  			c.Check(r.Method, check.Equals, "GET")
  1099  			c.Check(r.URL.Path, check.Equals, "/v2/system-info")
  1100  			fmt.Fprintln(w, `{"type": "sync", "status-code": 200, "result": {"refresh": {"schedule": "00:00-04:59/5:00-10:59/11:00-16:59/17:00-23:59", "last": "2017-04-25T17:35:00+02:00", "next": "2017-04-26T00:58:00+02:00"}}}`)
  1101  		default:
  1102  			c.Fatalf("expected to get 1 requests, now on %d", n+1)
  1103  		}
  1104  
  1105  		n++
  1106  	})
  1107  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--time", "--abs-time"})
  1108  	c.Assert(err, check.IsNil)
  1109  	c.Assert(rest, check.DeepEquals, []string{})
  1110  	c.Check(s.Stdout(), check.Equals, `schedule: 00:00-04:59/5:00-10:59/11:00-16:59/17:00-23:59
  1111  last: 2017-04-25T17:35:00+02:00
  1112  next: 2017-04-26T00:58:00+02:00
  1113  `)
  1114  	c.Check(s.Stderr(), check.Equals, "")
  1115  	// ensure that the fake server api was actually hit
  1116  	c.Check(n, check.Equals, 1)
  1117  }
  1118  
  1119  func (s *SnapSuite) TestRefreshTimer(c *check.C) {
  1120  	n := 0
  1121  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1122  		switch n {
  1123  		case 0:
  1124  			c.Check(r.Method, check.Equals, "GET")
  1125  			c.Check(r.URL.Path, check.Equals, "/v2/system-info")
  1126  			fmt.Fprintln(w, `{"type": "sync", "status-code": 200, "result": {"refresh": {"timer": "0:00-24:00/4", "last": "2017-04-25T17:35:00+02:00", "next": "2017-04-26T00:58:00+02:00"}}}`)
  1127  		default:
  1128  			c.Fatalf("expected to get 1 requests, now on %d", n+1)
  1129  		}
  1130  
  1131  		n++
  1132  	})
  1133  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--time", "--abs-time"})
  1134  	c.Assert(err, check.IsNil)
  1135  	c.Assert(rest, check.DeepEquals, []string{})
  1136  	c.Check(s.Stdout(), check.Equals, `timer: 0:00-24:00/4
  1137  last: 2017-04-25T17:35:00+02:00
  1138  next: 2017-04-26T00:58:00+02:00
  1139  `)
  1140  	c.Check(s.Stderr(), check.Equals, "")
  1141  	// ensure that the fake server api was actually hit
  1142  	c.Check(n, check.Equals, 1)
  1143  }
  1144  
  1145  func (s *SnapSuite) TestRefreshHold(c *check.C) {
  1146  	n := 0
  1147  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1148  		switch n {
  1149  		case 0:
  1150  			c.Check(r.Method, check.Equals, "GET")
  1151  			c.Check(r.URL.Path, check.Equals, "/v2/system-info")
  1152  			fmt.Fprintln(w, `{"type": "sync", "status-code": 200, "result": {"refresh": {"timer": "0:00-24:00/4", "last": "2017-04-25T17:35:00+02:00", "next": "2017-04-26T00:58:00+02:00", "hold": "2017-04-28T00:00:00+02:00"}}}`)
  1153  		default:
  1154  			c.Fatalf("expected to get 1 requests, now on %d", n+1)
  1155  		}
  1156  
  1157  		n++
  1158  	})
  1159  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--time", "--abs-time"})
  1160  	c.Assert(err, check.IsNil)
  1161  	c.Assert(rest, check.DeepEquals, []string{})
  1162  	c.Check(s.Stdout(), check.Equals, `timer: 0:00-24:00/4
  1163  last: 2017-04-25T17:35:00+02:00
  1164  hold: 2017-04-28T00:00:00+02:00
  1165  next: 2017-04-26T00:58:00+02:00 (but held)
  1166  `)
  1167  	c.Check(s.Stderr(), check.Equals, "")
  1168  	// ensure that the fake server api was actually hit
  1169  	c.Check(n, check.Equals, 1)
  1170  }
  1171  
  1172  func (s *SnapSuite) TestRefreshNoTimerNoSchedule(c *check.C) {
  1173  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1174  		c.Check(r.Method, check.Equals, "GET")
  1175  		c.Check(r.URL.Path, check.Equals, "/v2/system-info")
  1176  		fmt.Fprintln(w, `{"type": "sync", "status-code": 200, "result": {"refresh": {"last": "2017-04-25T17:35:00+0200", "next": "2017-04-26T00:58:00+0200"}}}`)
  1177  	})
  1178  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--time"})
  1179  	c.Assert(err, check.ErrorMatches, `internal error: both refresh.timer and refresh.schedule are empty`)
  1180  }
  1181  
  1182  func (s *SnapOpSuite) TestRefreshOne(c *check.C) {
  1183  	s.RedirectClientToTestServer(s.srv.handle)
  1184  	s.srv.checker = func(r *http.Request) {
  1185  		c.Check(r.Method, check.Equals, "POST")
  1186  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1187  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1188  			"action": "refresh",
  1189  		})
  1190  	}
  1191  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "foo"})
  1192  	c.Assert(err, check.IsNil)
  1193  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar refreshed`)
  1194  
  1195  }
  1196  
  1197  func (s *SnapOpSuite) TestRefreshOneSwitchChannel(c *check.C) {
  1198  	s.RedirectClientToTestServer(s.srv.handle)
  1199  	s.srv.checker = func(r *http.Request) {
  1200  		c.Check(r.Method, check.Equals, "POST")
  1201  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1202  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1203  			"action":  "refresh",
  1204  			"channel": "beta",
  1205  		})
  1206  		s.srv.channel = "beta"
  1207  	}
  1208  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta", "foo"})
  1209  	c.Assert(err, check.IsNil)
  1210  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(beta\) 1.0 from Bar refreshed`)
  1211  }
  1212  
  1213  func (s *SnapOpSuite) TestRefreshOneSwitchCohort(c *check.C) {
  1214  	s.RedirectClientToTestServer(s.srv.handle)
  1215  	s.srv.checker = func(r *http.Request) {
  1216  		c.Check(r.Method, check.Equals, "POST")
  1217  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1218  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1219  			"action":     "refresh",
  1220  			"cohort-key": "what",
  1221  		})
  1222  	}
  1223  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--cohort=what", "foo"})
  1224  	c.Assert(err, check.IsNil)
  1225  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar refreshed`)
  1226  }
  1227  
  1228  func (s *SnapOpSuite) TestRefreshOneLeaveCohort(c *check.C) {
  1229  	s.RedirectClientToTestServer(s.srv.handle)
  1230  	s.srv.checker = func(r *http.Request) {
  1231  		c.Check(r.Method, check.Equals, "POST")
  1232  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1233  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1234  			"action":       "refresh",
  1235  			"leave-cohort": true,
  1236  		})
  1237  	}
  1238  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--leave-cohort", "foo"})
  1239  	c.Assert(err, check.IsNil)
  1240  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo 1.0 from Bar refreshed`)
  1241  }
  1242  
  1243  func (s *SnapOpSuite) TestRefreshOneWithPinnedTrack(c *check.C) {
  1244  	s.RedirectClientToTestServer(s.srv.handle)
  1245  	s.srv.checker = func(r *http.Request) {
  1246  		c.Check(r.Method, check.Equals, "POST")
  1247  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1248  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1249  			"action":  "refresh",
  1250  			"channel": "stable",
  1251  		})
  1252  		s.srv.channel = "18/stable"
  1253  		s.srv.trackingChannel = "18/stable"
  1254  	}
  1255  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--stable", "foo"})
  1256  	c.Assert(err, check.IsNil)
  1257  	c.Check(s.Stdout(), check.Equals, "foo (18/stable) 1.0 from Bar refreshed\n")
  1258  }
  1259  
  1260  func (s *SnapOpSuite) TestRefreshOneClassic(c *check.C) {
  1261  	s.RedirectClientToTestServer(s.srv.handle)
  1262  	s.srv.checker = func(r *http.Request) {
  1263  		c.Check(r.Method, check.Equals, "POST")
  1264  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/one")
  1265  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1266  			"action":  "refresh",
  1267  			"classic": true,
  1268  		})
  1269  	}
  1270  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--classic", "one"})
  1271  	c.Assert(err, check.IsNil)
  1272  }
  1273  
  1274  func (s *SnapOpSuite) TestRefreshOneDevmode(c *check.C) {
  1275  	s.RedirectClientToTestServer(s.srv.handle)
  1276  	s.srv.checker = func(r *http.Request) {
  1277  		c.Check(r.Method, check.Equals, "POST")
  1278  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/one")
  1279  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1280  			"action":  "refresh",
  1281  			"devmode": true,
  1282  		})
  1283  	}
  1284  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--devmode", "one"})
  1285  	c.Assert(err, check.IsNil)
  1286  }
  1287  
  1288  func (s *SnapOpSuite) TestRefreshOneJailmode(c *check.C) {
  1289  	s.RedirectClientToTestServer(s.srv.handle)
  1290  	s.srv.checker = func(r *http.Request) {
  1291  		c.Check(r.Method, check.Equals, "POST")
  1292  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/one")
  1293  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1294  			"action":   "refresh",
  1295  			"jailmode": true,
  1296  		})
  1297  	}
  1298  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--jailmode", "one"})
  1299  	c.Assert(err, check.IsNil)
  1300  }
  1301  
  1302  func (s *SnapOpSuite) TestRefreshOneIgnoreValidation(c *check.C) {
  1303  	s.RedirectClientToTestServer(s.srv.handle)
  1304  	s.srv.checker = func(r *http.Request) {
  1305  		c.Check(r.Method, check.Equals, "POST")
  1306  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/one")
  1307  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1308  			"action":            "refresh",
  1309  			"ignore-validation": true,
  1310  		})
  1311  	}
  1312  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--ignore-validation", "one"})
  1313  	c.Assert(err, check.IsNil)
  1314  }
  1315  
  1316  func (s *SnapOpSuite) TestRefreshOneRebooting(c *check.C) {
  1317  	s.RedirectClientToTestServer(s.srv.handle)
  1318  	s.srv.checker = func(r *http.Request) {
  1319  		c.Check(r.Method, check.Equals, "POST")
  1320  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/core")
  1321  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1322  			"action": "refresh",
  1323  		})
  1324  	}
  1325  	s.srv.restart = "reboot"
  1326  
  1327  	restore := mockArgs("snap", "refresh", "core")
  1328  	defer restore()
  1329  
  1330  	err := snap.RunMain()
  1331  	c.Check(err, check.IsNil)
  1332  	c.Check(s.Stderr(), check.Equals, "snapd is about to reboot the system\n")
  1333  }
  1334  
  1335  func (s *SnapOpSuite) TestRefreshOneHalting(c *check.C) {
  1336  	s.RedirectClientToTestServer(s.srv.handle)
  1337  	s.srv.checker = func(r *http.Request) {
  1338  		c.Check(r.Method, check.Equals, "POST")
  1339  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/core")
  1340  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1341  			"action": "refresh",
  1342  		})
  1343  	}
  1344  	s.srv.restart = "halt"
  1345  
  1346  	restore := mockArgs("snap", "refresh", "core")
  1347  	defer restore()
  1348  
  1349  	err := snap.RunMain()
  1350  	c.Check(err, check.IsNil)
  1351  	c.Check(s.Stderr(), check.Equals, "snapd is about to halt the system\n")
  1352  }
  1353  
  1354  func (s *SnapOpSuite) TestRefreshOnePoweringOff(c *check.C) {
  1355  	s.RedirectClientToTestServer(s.srv.handle)
  1356  	s.srv.checker = func(r *http.Request) {
  1357  		c.Check(r.Method, check.Equals, "POST")
  1358  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/core")
  1359  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1360  			"action": "refresh",
  1361  		})
  1362  	}
  1363  	s.srv.restart = "poweroff"
  1364  
  1365  	restore := mockArgs("snap", "refresh", "core")
  1366  	defer restore()
  1367  
  1368  	err := snap.RunMain()
  1369  	c.Check(err, check.IsNil)
  1370  	c.Check(s.Stderr(), check.Equals, "snapd is about to power off the system\n")
  1371  }
  1372  
  1373  func (s *SnapOpSuite) TestRefreshOneChanDeprecated(c *check.C) {
  1374  	var in, out string
  1375  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1376  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{"action": "refresh", "channel": out})
  1377  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "snap not found", "value": "foo", "kind": "snap-not-found"}, "status-code": 404}`)
  1378  	})
  1379  
  1380  	for in, out = range map[string]string{
  1381  		"/foo":            "foo/stable",
  1382  		"/stable":         "latest/stable",
  1383  		"///foo/stable//": "foo/stable",
  1384  	} {
  1385  		s.stderr.Reset()
  1386  		_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--channel=" + in, "one"})
  1387  		c.Assert(err, check.ErrorMatches, "snap \"one\" not found")
  1388  		c.Check(s.Stderr(), testutil.EqualsWrapped, `Warning: Specifying a channel "`+in+`" is relying on undefined behaviour. Interpreting it as "`+out+`" for now, but this will be an error later.`)
  1389  	}
  1390  }
  1391  
  1392  func (s *SnapOpSuite) TestRefreshOneModeErr(c *check.C) {
  1393  	s.RedirectClientToTestServer(nil)
  1394  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--jailmode", "--devmode", "one"})
  1395  	c.Assert(err, check.ErrorMatches, `cannot use devmode and jailmode flags together`)
  1396  }
  1397  
  1398  func (s *SnapOpSuite) TestRefreshOneChanErr(c *check.C) {
  1399  	s.RedirectClientToTestServer(nil)
  1400  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta", "--channel=foo", "one"})
  1401  	c.Assert(err, check.ErrorMatches, `Please specify a single channel`)
  1402  }
  1403  
  1404  func (s *SnapOpSuite) TestRefreshAllChannel(c *check.C) {
  1405  	s.RedirectClientToTestServer(nil)
  1406  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta"})
  1407  	c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`)
  1408  }
  1409  
  1410  func (s *SnapOpSuite) TestRefreshManyChannel(c *check.C) {
  1411  	s.RedirectClientToTestServer(nil)
  1412  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta", "one", "two"})
  1413  	c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`)
  1414  }
  1415  
  1416  func (s *SnapOpSuite) TestRefreshManyIgnoreValidation(c *check.C) {
  1417  	s.RedirectClientToTestServer(nil)
  1418  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--ignore-validation", "one", "two"})
  1419  	c.Assert(err, check.ErrorMatches, `a single snap name must be specified when ignoring validation`)
  1420  }
  1421  
  1422  func (s *SnapOpSuite) TestRefreshAllModeFlags(c *check.C) {
  1423  	s.RedirectClientToTestServer(nil)
  1424  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--devmode"})
  1425  	c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`)
  1426  }
  1427  
  1428  func (s *SnapOpSuite) TestRefreshOneAmend(c *check.C) {
  1429  	s.RedirectClientToTestServer(s.srv.handle)
  1430  	s.srv.checker = func(r *http.Request) {
  1431  		c.Check(r.Method, check.Equals, "POST")
  1432  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/one")
  1433  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1434  			"action": "refresh",
  1435  			"amend":  true,
  1436  		})
  1437  	}
  1438  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--amend", "one"})
  1439  	c.Assert(err, check.IsNil)
  1440  }
  1441  
  1442  func (s *SnapOpSuite) runTryTest(c *check.C, opts *client.SnapOptions) {
  1443  	// pass relative path to cmd
  1444  	tryDir := "some-dir"
  1445  
  1446  	modes := []struct {
  1447  		enabled bool
  1448  		name    string
  1449  	}{
  1450  		{opts.DevMode, "devmode"},
  1451  		{opts.JailMode, "jailmode"},
  1452  		{opts.Classic, "classic"},
  1453  	}
  1454  
  1455  	s.srv.checker = func(r *http.Request) {
  1456  		// ensure the client always sends the absolute path
  1457  		fullTryDir, err := filepath.Abs(tryDir)
  1458  		c.Assert(err, check.IsNil)
  1459  
  1460  		c.Check(r.URL.Path, check.Equals, "/v2/snaps")
  1461  		form := testForm(r, c)
  1462  		defer form.RemoveAll()
  1463  
  1464  		c.Assert(form.Value["action"], check.HasLen, 1)
  1465  		c.Assert(form.Value["snap-path"], check.HasLen, 1)
  1466  		c.Check(form.File, check.HasLen, 0)
  1467  		c.Check(form.Value["action"][0], check.Equals, "try")
  1468  		c.Check(form.Value["snap-path"][0], check.Matches, regexp.QuoteMeta(fullTryDir))
  1469  
  1470  		for _, mode := range modes {
  1471  			if mode.enabled {
  1472  				c.Assert(form.Value[mode.name], check.HasLen, 1)
  1473  				c.Check(form.Value[mode.name][0], check.Equals, "true")
  1474  			} else {
  1475  				c.Check(form.Value[mode.name], check.IsNil)
  1476  			}
  1477  		}
  1478  	}
  1479  
  1480  	s.RedirectClientToTestServer(s.srv.handle)
  1481  
  1482  	cmd := []string{"try", tryDir}
  1483  	for _, mode := range modes {
  1484  		if mode.enabled {
  1485  			cmd = append(cmd, "--"+mode.name)
  1486  		}
  1487  	}
  1488  
  1489  	rest, err := snap.Parser(snap.Client()).ParseArgs(cmd)
  1490  	c.Assert(err, check.IsNil)
  1491  	c.Assert(rest, check.DeepEquals, []string{})
  1492  	c.Check(s.Stdout(), check.Matches, fmt.Sprintf(`(?sm).*foo 1.0 mounted from .*%s`, tryDir))
  1493  	c.Check(s.Stderr(), check.Equals, "")
  1494  	// ensure that the fake server api was actually hit
  1495  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1496  }
  1497  
  1498  func (s *SnapOpSuite) TestTryNoMode(c *check.C) {
  1499  	s.runTryTest(c, &client.SnapOptions{})
  1500  }
  1501  
  1502  func (s *SnapOpSuite) TestTryDevMode(c *check.C) {
  1503  	s.runTryTest(c, &client.SnapOptions{DevMode: true})
  1504  }
  1505  
  1506  func (s *SnapOpSuite) TestTryJailMode(c *check.C) {
  1507  	s.runTryTest(c, &client.SnapOptions{JailMode: true})
  1508  }
  1509  
  1510  func (s *SnapOpSuite) TestTryClassic(c *check.C) {
  1511  	s.runTryTest(c, &client.SnapOptions{Classic: true})
  1512  }
  1513  
  1514  func (s *SnapOpSuite) TestTryNoSnapDirErrors(c *check.C) {
  1515  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1516  		c.Check(r.Method, check.Equals, "POST")
  1517  		w.WriteHeader(202)
  1518  		fmt.Fprintln(w, `
  1519  {
  1520    "type": "error",
  1521    "result": {
  1522      "message":"error from server",
  1523      "kind":"snap-not-a-snap"
  1524    },
  1525    "status-code": 400
  1526  }`)
  1527  	})
  1528  
  1529  	cmd := []string{"try", "/"}
  1530  	_, err := snap.Parser(snap.Client()).ParseArgs(cmd)
  1531  	c.Assert(err, testutil.EqualsWrapped, `
  1532  "/" does not contain an unpacked snap.
  1533  
  1534  Try 'snapcraft prime' in your project directory, then 'snap try' again.`)
  1535  }
  1536  
  1537  func (s *SnapOpSuite) TestTryMissingOpt(c *check.C) {
  1538  	oldArgs := os.Args
  1539  	defer func() {
  1540  		os.Args = oldArgs
  1541  	}()
  1542  	os.Args = []string{"snap", "try", "./"}
  1543  	var kind string
  1544  
  1545  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1546  		c.Check(r.Method, check.Equals, "POST", check.Commentf("%q", kind))
  1547  		w.WriteHeader(400)
  1548  		fmt.Fprintf(w, `
  1549  {
  1550    "type": "error",
  1551    "result": {
  1552      "message":"error from server",
  1553      "value": "some-snap",
  1554      "kind": %q
  1555    },
  1556    "status-code": 400
  1557  }`, kind)
  1558  	})
  1559  
  1560  	type table struct {
  1561  		kind, expected string
  1562  	}
  1563  
  1564  	tests := []table{
  1565  		{"snap-needs-classic", "published using classic confinement"},
  1566  		{"snap-needs-devmode", "only meant for development"},
  1567  	}
  1568  
  1569  	for _, test := range tests {
  1570  		kind = test.kind
  1571  		c.Check(snap.RunMain(), testutil.ContainsWrapped, test.expected, check.Commentf("%q", kind))
  1572  	}
  1573  }
  1574  
  1575  func (s *SnapOpSuite) TestInstallConfinedAsClassic(c *check.C) {
  1576  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1577  		c.Check(r.Method, check.Equals, "POST")
  1578  		w.WriteHeader(400)
  1579  		fmt.Fprintf(w, `{
  1580    "type": "error",
  1581    "result": {
  1582      "message":"error from server",
  1583      "value": "some-snap",
  1584      "kind": "snap-not-classic"
  1585    },
  1586    "status-code": 400
  1587  }`)
  1588  	})
  1589  
  1590  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--classic", "some-snap"})
  1591  	c.Assert(err, check.ErrorMatches, `snap "some-snap" is not compatible with --classic`)
  1592  }
  1593  
  1594  func (s *SnapSuite) TestInstallChannelDuplicationError(c *check.C) {
  1595  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--edge", "--beta", "some-snap"})
  1596  	c.Assert(err, check.ErrorMatches, "Please specify a single channel")
  1597  }
  1598  
  1599  func (s *SnapSuite) TestRefreshChannelDuplicationError(c *check.C) {
  1600  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--edge", "--beta", "some-snap"})
  1601  	c.Assert(err, check.ErrorMatches, "Please specify a single channel")
  1602  }
  1603  
  1604  func (s *SnapOpSuite) TestInstallFromChannel(c *check.C) {
  1605  	s.srv.checker = func(r *http.Request) {
  1606  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1607  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1608  			"action":  "install",
  1609  			"channel": "edge",
  1610  		})
  1611  		s.srv.channel = "edge"
  1612  	}
  1613  
  1614  	s.RedirectClientToTestServer(s.srv.handle)
  1615  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--edge", "foo"})
  1616  	c.Assert(err, check.IsNil)
  1617  	c.Assert(rest, check.DeepEquals, []string{})
  1618  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(edge\) 1.0 from Bar installed`)
  1619  	c.Check(s.Stderr(), check.Equals, "")
  1620  	// ensure that the fake server api was actually hit
  1621  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1622  }
  1623  
  1624  func (s *SnapOpSuite) TestEnable(c *check.C) {
  1625  	s.srv.total = 3
  1626  	s.srv.checker = func(r *http.Request) {
  1627  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1628  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1629  			"action": "enable",
  1630  		})
  1631  	}
  1632  
  1633  	s.RedirectClientToTestServer(s.srv.handle)
  1634  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"enable", "foo"})
  1635  	c.Assert(err, check.IsNil)
  1636  	c.Assert(rest, check.DeepEquals, []string{})
  1637  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo enabled`)
  1638  	c.Check(s.Stderr(), check.Equals, "")
  1639  	// ensure that the fake server api was actually hit
  1640  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1641  }
  1642  
  1643  func (s *SnapOpSuite) TestDisable(c *check.C) {
  1644  	s.srv.total = 3
  1645  	s.srv.checker = func(r *http.Request) {
  1646  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1647  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1648  			"action": "disable",
  1649  		})
  1650  	}
  1651  
  1652  	s.RedirectClientToTestServer(s.srv.handle)
  1653  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"disable", "foo"})
  1654  	c.Assert(err, check.IsNil)
  1655  	c.Assert(rest, check.DeepEquals, []string{})
  1656  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo disabled`)
  1657  	c.Check(s.Stderr(), check.Equals, "")
  1658  	// ensure that the fake server api was actually hit
  1659  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1660  }
  1661  
  1662  func (s *SnapOpSuite) TestRemove(c *check.C) {
  1663  	s.srv.total = 3
  1664  	s.srv.checker = func(r *http.Request) {
  1665  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1666  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1667  			"action": "remove",
  1668  		})
  1669  	}
  1670  
  1671  	s.RedirectClientToTestServer(s.srv.handle)
  1672  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "foo"})
  1673  	c.Assert(err, check.IsNil)
  1674  	c.Assert(rest, check.DeepEquals, []string{})
  1675  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo removed`)
  1676  	c.Check(s.Stderr(), check.Equals, "")
  1677  	// ensure that the fake server api was actually hit
  1678  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1679  }
  1680  
  1681  func (s *SnapOpSuite) TestRemoveWithPurge(c *check.C) {
  1682  	s.srv.total = 3
  1683  	s.srv.checker = func(r *http.Request) {
  1684  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1685  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1686  			"action": "remove",
  1687  			"purge":  true,
  1688  		})
  1689  	}
  1690  
  1691  	s.RedirectClientToTestServer(s.srv.handle)
  1692  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--purge", "foo"})
  1693  	c.Assert(err, check.IsNil)
  1694  	c.Assert(rest, check.DeepEquals, []string{})
  1695  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo removed`)
  1696  	c.Check(s.Stderr(), check.Equals, "")
  1697  	// ensure that the fake server api was actually hit
  1698  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1699  }
  1700  
  1701  func (s *SnapOpSuite) TestRemoveInsufficientDiskSpace(c *check.C) {
  1702  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1703  		fmt.Fprintln(w, `{
  1704  			"type": "error",
  1705  			"result": {
  1706  				"message": "disk space error",
  1707  				"kind": "insufficient-disk-space",
  1708  				"value": {
  1709  					"snap-names": ["foo", "bar"],
  1710  					"change-kind": "remove"
  1711  				},
  1712  				"status-code": 507
  1713  				}}`)
  1714  	})
  1715  
  1716  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "foo"})
  1717  	c.Check(err, check.ErrorMatches, `(?sm)cannot remove "foo", "bar" due to low disk space for automatic snapshot,.*use --purge to avoid creating a snapshot`)
  1718  	c.Check(s.Stdout(), check.Equals, "")
  1719  	c.Check(s.Stderr(), check.Equals, "")
  1720  }
  1721  
  1722  func (s *SnapOpSuite) TestInstallInsufficientDiskSpace(c *check.C) {
  1723  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1724  		fmt.Fprintln(w, `{
  1725  			"type": "error",
  1726  			"result": {
  1727  				"message": "disk space error",
  1728  				"kind": "insufficient-disk-space",
  1729  				"value": {
  1730  					"snap-names": ["foo"],
  1731  					"change-kind": "install"
  1732  				},
  1733  				"status-code": 507
  1734  				}}`)
  1735  	})
  1736  
  1737  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"})
  1738  	c.Check(err, check.ErrorMatches, `cannot install "foo" due to low disk space`)
  1739  	c.Check(s.Stdout(), check.Equals, "")
  1740  	c.Check(s.Stderr(), check.Equals, "")
  1741  }
  1742  
  1743  func (s *SnapOpSuite) TestRefreshInsufficientDiskSpace(c *check.C) {
  1744  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1745  		fmt.Fprintln(w, `{
  1746  			"type": "error",
  1747  			"result": {
  1748  				"message": "disk space error",
  1749  				"kind": "insufficient-disk-space",
  1750  				"value": {
  1751  					"snap-names": ["foo"],
  1752  					"change-kind": "refresh"
  1753  				},
  1754  				"status-code": 507
  1755  				}}`)
  1756  	})
  1757  
  1758  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "foo"})
  1759  	c.Check(err, check.ErrorMatches, `cannot refresh "foo" due to low disk space`)
  1760  	c.Check(s.Stdout(), check.Equals, "")
  1761  	c.Check(s.Stderr(), check.Equals, "")
  1762  }
  1763  
  1764  func (s *SnapOpSuite) TestRemoveRevision(c *check.C) {
  1765  	s.srv.total = 3
  1766  	s.srv.checker = func(r *http.Request) {
  1767  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1768  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1769  			"action":   "remove",
  1770  			"revision": "17",
  1771  		})
  1772  	}
  1773  
  1774  	s.RedirectClientToTestServer(s.srv.handle)
  1775  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--revision=17", "foo"})
  1776  	c.Assert(err, check.IsNil)
  1777  	c.Assert(rest, check.DeepEquals, []string{})
  1778  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(revision 17\) removed`)
  1779  	c.Check(s.Stderr(), check.Equals, "")
  1780  	// ensure that the fake server api was actually hit
  1781  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1782  }
  1783  
  1784  func (s *SnapOpSuite) TestRemoveManyOptions(c *check.C) {
  1785  	s.RedirectClientToTestServer(nil)
  1786  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--revision=17", "one", "two"})
  1787  	c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify options`)
  1788  	_, err = snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--purge", "one", "two"})
  1789  	c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify options`)
  1790  }
  1791  
  1792  func (s *SnapOpSuite) TestRemoveMany(c *check.C) {
  1793  	total := 3
  1794  	n := 0
  1795  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1796  		switch n {
  1797  		case 0:
  1798  			c.Check(r.URL.Path, check.Equals, "/v2/snaps")
  1799  			c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1800  				"action": "remove",
  1801  				"snaps":  []interface{}{"one", "two"},
  1802  			})
  1803  
  1804  			c.Check(r.Method, check.Equals, "POST")
  1805  			w.WriteHeader(202)
  1806  			fmt.Fprintln(w, `{"type":"async", "change": "42", "status-code": 202}`)
  1807  		case 1:
  1808  			c.Check(r.Method, check.Equals, "GET")
  1809  			c.Check(r.URL.Path, check.Equals, "/v2/changes/42")
  1810  			fmt.Fprintln(w, `{"type": "sync", "result": {"status": "Doing"}}`)
  1811  		case 2:
  1812  			c.Check(r.Method, check.Equals, "GET")
  1813  			c.Check(r.URL.Path, check.Equals, "/v2/changes/42")
  1814  			fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done", "data": {"snap-names": ["one","two"]}}}`)
  1815  		default:
  1816  			c.Fatalf("expected to get %d requests, now on %d", total, n+1)
  1817  		}
  1818  
  1819  		n++
  1820  	})
  1821  
  1822  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "one", "two"})
  1823  	c.Assert(err, check.IsNil)
  1824  	c.Assert(rest, check.DeepEquals, []string{})
  1825  	c.Check(s.Stdout(), check.Matches, `(?sm).*one removed`)
  1826  	c.Check(s.Stdout(), check.Matches, `(?sm).*two removed`)
  1827  	c.Check(s.Stderr(), check.Equals, "")
  1828  	// ensure that the fake server api was actually hit
  1829  	c.Check(n, check.Equals, total)
  1830  }
  1831  
  1832  func (s *SnapOpSuite) TestInstallManyChannel(c *check.C) {
  1833  	s.RedirectClientToTestServer(nil)
  1834  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--beta", "one", "two"})
  1835  	c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`)
  1836  }
  1837  
  1838  func (s *SnapOpSuite) TestInstallManyMixFileAndStore(c *check.C) {
  1839  	s.RedirectClientToTestServer(nil)
  1840  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "store-snap", "./local.snap"})
  1841  	c.Assert(err, check.ErrorMatches, `only one snap file can be installed at a time`)
  1842  }
  1843  
  1844  func (s *SnapOpSuite) TestInstallMany(c *check.C) {
  1845  	total := 4
  1846  	n := 0
  1847  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1848  		switch n {
  1849  		case 0:
  1850  			c.Check(r.URL.Path, check.Equals, "/v2/snaps")
  1851  			c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1852  				"action": "install",
  1853  				"snaps":  []interface{}{"one", "two"},
  1854  			})
  1855  
  1856  			c.Check(r.Method, check.Equals, "POST")
  1857  			w.WriteHeader(202)
  1858  			fmt.Fprintln(w, `{"type":"async", "change": "42", "status-code": 202}`)
  1859  		case 1:
  1860  			c.Check(r.Method, check.Equals, "GET")
  1861  			c.Check(r.URL.Path, check.Equals, "/v2/changes/42")
  1862  			fmt.Fprintln(w, `{"type": "sync", "result": {"status": "Doing"}}`)
  1863  		case 2:
  1864  			c.Check(r.Method, check.Equals, "GET")
  1865  			c.Check(r.URL.Path, check.Equals, "/v2/changes/42")
  1866  			fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done", "data": {"snap-names": ["one","two"]}}}`)
  1867  		case 3:
  1868  			c.Check(r.Method, check.Equals, "GET")
  1869  			c.Check(r.URL.Path, check.Equals, "/v2/snaps")
  1870  			fmt.Fprintf(w, `{"type": "sync", "result": [{"name": "one", "status": "active", "version": "1.0", "developer": "bar", "publisher": {"id": "bar-id", "username": "bar", "display-name": "Bar", "validation": "unproven"}, "revision":42, "channel":"stable"},{"name": "two", "status": "active", "version": "2.0", "developer": "baz", "publisher": {"id": "baz-id", "username": "baz", "display-name": "Baz", "validation": "unproven"}, "revision":42, "channel":"edge"}]}\n`)
  1871  
  1872  		default:
  1873  			c.Fatalf("expected to get %d requests, now on %d", total, n+1)
  1874  		}
  1875  
  1876  		n++
  1877  	})
  1878  
  1879  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "one", "two"})
  1880  	c.Assert(err, check.IsNil)
  1881  	c.Assert(rest, check.DeepEquals, []string{})
  1882  	// note that (stable) is omitted
  1883  	c.Check(s.Stdout(), check.Matches, `(?sm).*one 1.0 from Bar installed`)
  1884  	c.Check(s.Stdout(), check.Matches, `(?sm).*two \(edge\) 2.0 from Baz installed`)
  1885  	c.Check(s.Stderr(), check.Equals, "")
  1886  	// ensure that the fake server api was actually hit
  1887  	c.Check(n, check.Equals, total)
  1888  }
  1889  
  1890  func (s *SnapOpSuite) TestInstallZeroEmpty(c *check.C) {
  1891  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install"})
  1892  	c.Assert(err, check.ErrorMatches, "cannot install zero snaps")
  1893  	_, err = snap.Parser(snap.Client()).ParseArgs([]string{"install", ""})
  1894  	c.Assert(err, check.ErrorMatches, "cannot install snap with empty name")
  1895  	_, err = snap.Parser(snap.Client()).ParseArgs([]string{"install", "", "bar"})
  1896  	c.Assert(err, check.ErrorMatches, "cannot install snap with empty name")
  1897  }
  1898  
  1899  func (s *SnapOpSuite) TestNoWait(c *check.C) {
  1900  	s.srv.checker = func(r *http.Request) {}
  1901  
  1902  	cmds := [][]string{
  1903  		{"remove", "--no-wait", "foo"},
  1904  		{"remove", "--no-wait", "foo", "bar"},
  1905  		{"install", "--no-wait", "foo"},
  1906  		{"install", "--no-wait", "foo", "bar"},
  1907  		{"revert", "--no-wait", "foo"},
  1908  		{"refresh", "--no-wait", "foo"},
  1909  		{"refresh", "--no-wait", "foo", "bar"},
  1910  		{"refresh", "--no-wait"},
  1911  		{"enable", "--no-wait", "foo"},
  1912  		{"disable", "--no-wait", "foo"},
  1913  		{"try", "--no-wait", "."},
  1914  		{"switch", "--no-wait", "--channel=foo", "bar"},
  1915  		// commands that use waitMixin from elsewhere
  1916  		{"start", "--no-wait", "foo"},
  1917  		{"stop", "--no-wait", "foo"},
  1918  		{"restart", "--no-wait", "foo"},
  1919  		{"alias", "--no-wait", "foo", "bar"},
  1920  		{"unalias", "--no-wait", "foo"},
  1921  		{"prefer", "--no-wait", "foo"},
  1922  		{"set", "--no-wait", "foo", "bar=baz"},
  1923  		{"disconnect", "--no-wait", "foo:bar"},
  1924  		{"connect", "--no-wait", "foo:bar"},
  1925  	}
  1926  
  1927  	s.RedirectClientToTestServer(s.srv.handle)
  1928  	for _, cmd := range cmds {
  1929  		rest, err := snap.Parser(snap.Client()).ParseArgs(cmd)
  1930  		c.Assert(err, check.IsNil, check.Commentf("%v", cmd))
  1931  		c.Assert(rest, check.DeepEquals, []string{})
  1932  		c.Check(s.Stdout(), check.Matches, "(?sm)42\n")
  1933  		c.Check(s.Stderr(), check.Equals, "")
  1934  		c.Check(s.srv.n, check.Equals, 1)
  1935  		// reset
  1936  		s.srv.n = 0
  1937  		s.stdout.Reset()
  1938  	}
  1939  }
  1940  
  1941  func (s *SnapOpSuite) TestNoWaitImmediateError(c *check.C) {
  1942  
  1943  	cmds := [][]string{
  1944  		{"remove", "--no-wait", "foo"},
  1945  		{"remove", "--no-wait", "foo", "bar"},
  1946  		{"install", "--no-wait", "foo"},
  1947  		{"install", "--no-wait", "foo", "bar"},
  1948  		{"revert", "--no-wait", "foo"},
  1949  		{"refresh", "--no-wait", "foo"},
  1950  		{"refresh", "--no-wait", "foo", "bar"},
  1951  		{"refresh", "--no-wait"},
  1952  		{"enable", "--no-wait", "foo"},
  1953  		{"disable", "--no-wait", "foo"},
  1954  		{"try", "--no-wait", "."},
  1955  		{"switch", "--no-wait", "--channel=foo", "bar"},
  1956  		// commands that use waitMixin from elsewhere
  1957  		{"start", "--no-wait", "foo"},
  1958  		{"stop", "--no-wait", "foo"},
  1959  		{"restart", "--no-wait", "foo"},
  1960  		{"alias", "--no-wait", "foo", "bar"},
  1961  		{"unalias", "--no-wait", "foo"},
  1962  		{"prefer", "--no-wait", "foo"},
  1963  		{"set", "--no-wait", "foo", "bar=baz"},
  1964  		{"disconnect", "--no-wait", "foo:bar"},
  1965  		{"connect", "--no-wait", "foo:bar"},
  1966  	}
  1967  
  1968  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1969  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "failure"}}`)
  1970  	})
  1971  
  1972  	for _, cmd := range cmds {
  1973  		_, err := snap.Parser(snap.Client()).ParseArgs(cmd)
  1974  		c.Assert(err, check.ErrorMatches, "failure", check.Commentf("%v", cmd))
  1975  	}
  1976  }
  1977  
  1978  func (s *SnapOpSuite) TestWaitServerError(c *check.C) {
  1979  	r := snap.MockMaxGoneTime(0)
  1980  	defer r()
  1981  
  1982  	cmds := [][]string{
  1983  		{"remove", "foo"},
  1984  		{"remove", "foo", "bar"},
  1985  		{"install", "foo"},
  1986  		{"install", "foo", "bar"},
  1987  		{"revert", "foo"},
  1988  		{"refresh", "foo"},
  1989  		{"refresh", "foo", "bar"},
  1990  		{"refresh"},
  1991  		{"enable", "foo"},
  1992  		{"disable", "foo"},
  1993  		{"try", "."},
  1994  		{"switch", "--channel=foo", "bar"},
  1995  		// commands that use waitMixin from elsewhere
  1996  		{"start", "foo"},
  1997  		{"stop", "foo"},
  1998  		{"restart", "foo"},
  1999  		{"alias", "foo", "bar"},
  2000  		{"unalias", "foo"},
  2001  		{"prefer", "foo"},
  2002  		{"set", "foo", "bar=baz"},
  2003  		{"disconnect", "foo:bar"},
  2004  		{"connect", "foo:bar"},
  2005  	}
  2006  
  2007  	n := 0
  2008  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  2009  		n++
  2010  		if n == 1 {
  2011  			w.WriteHeader(202)
  2012  			fmt.Fprintln(w, `{"type":"async", "change": "42", "status-code": 202}`)
  2013  			return
  2014  		}
  2015  		if n == 3 {
  2016  			fmt.Fprintln(w, `{"type": "error", "result": {"message": "unexpected request"}}`)
  2017  			return
  2018  		}
  2019  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "server error"}}`)
  2020  	})
  2021  
  2022  	for _, cmd := range cmds {
  2023  		_, err := snap.Parser(snap.Client()).ParseArgs(cmd)
  2024  		c.Assert(err, check.ErrorMatches, "server error", check.Commentf("%v", cmd))
  2025  		// reset
  2026  		n = 0
  2027  	}
  2028  }
  2029  
  2030  func (s *SnapOpSuite) TestSwitchHappy(c *check.C) {
  2031  	s.srv.total = 4
  2032  	s.srv.checker = func(r *http.Request) {
  2033  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  2034  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  2035  			"action":  "switch",
  2036  			"channel": "beta",
  2037  		})
  2038  		s.srv.trackingChannel = "beta"
  2039  	}
  2040  
  2041  	s.RedirectClientToTestServer(s.srv.handle)
  2042  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--beta", "foo"})
  2043  	c.Assert(err, check.IsNil)
  2044  	c.Assert(rest, check.DeepEquals, []string{})
  2045  	c.Check(s.Stdout(), check.Equals, `"foo" switched to the "beta" channel
  2046  
  2047  `)
  2048  	c.Check(s.Stderr(), check.Equals, "")
  2049  	// ensure that the fake server api was actually hit
  2050  	c.Check(s.srv.n, check.Equals, s.srv.total)
  2051  }
  2052  
  2053  func (s *SnapOpSuite) TestSwitchHappyCohort(c *check.C) {
  2054  	s.srv.total = 4
  2055  	s.srv.checker = func(r *http.Request) {
  2056  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  2057  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  2058  			"action":     "switch",
  2059  			"cohort-key": "what",
  2060  		})
  2061  	}
  2062  
  2063  	s.RedirectClientToTestServer(s.srv.handle)
  2064  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--cohort=what", "foo"})
  2065  	c.Assert(err, check.IsNil)
  2066  	c.Assert(rest, check.DeepEquals, []string{})
  2067  	c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" switched to the "what" cohort`)
  2068  	c.Check(s.Stderr(), check.Equals, "")
  2069  	// ensure that the fake server api was actually hit
  2070  	c.Check(s.srv.n, check.Equals, s.srv.total)
  2071  }
  2072  
  2073  func (s *SnapOpSuite) TestSwitchHappyLeaveCohort(c *check.C) {
  2074  	s.srv.total = 4
  2075  	s.srv.checker = func(r *http.Request) {
  2076  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  2077  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  2078  			"action":       "switch",
  2079  			"leave-cohort": true,
  2080  		})
  2081  	}
  2082  
  2083  	s.RedirectClientToTestServer(s.srv.handle)
  2084  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--leave-cohort", "foo"})
  2085  	c.Assert(err, check.IsNil)
  2086  	c.Assert(rest, check.DeepEquals, []string{})
  2087  	c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" left the cohort`)
  2088  	c.Check(s.Stderr(), check.Equals, "")
  2089  	// ensure that the fake server api was actually hit
  2090  	c.Check(s.srv.n, check.Equals, s.srv.total)
  2091  }
  2092  
  2093  func (s *SnapOpSuite) TestSwitchHappyChannelAndCohort(c *check.C) {
  2094  	s.srv.total = 4
  2095  	s.srv.checker = func(r *http.Request) {
  2096  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  2097  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  2098  			"action":     "switch",
  2099  			"cohort-key": "what",
  2100  			"channel":    "edge",
  2101  		})
  2102  		s.srv.trackingChannel = "edge"
  2103  	}
  2104  
  2105  	s.RedirectClientToTestServer(s.srv.handle)
  2106  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--cohort=what", "--edge", "foo"})
  2107  	c.Assert(err, check.IsNil)
  2108  	c.Assert(rest, check.DeepEquals, []string{})
  2109  	c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" switched to the "edge" channel and the "what" cohort`)
  2110  	c.Check(s.Stderr(), check.Equals, "")
  2111  	// ensure that the fake server api was actually hit
  2112  	c.Check(s.srv.n, check.Equals, s.srv.total)
  2113  }
  2114  
  2115  func (s *SnapOpSuite) TestSwitchHappyChannelAndLeaveCohort(c *check.C) {
  2116  	s.srv.total = 4
  2117  	s.srv.checker = func(r *http.Request) {
  2118  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  2119  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  2120  			"action":       "switch",
  2121  			"leave-cohort": true,
  2122  			"channel":      "edge",
  2123  		})
  2124  		s.srv.trackingChannel = "edge"
  2125  	}
  2126  
  2127  	s.RedirectClientToTestServer(s.srv.handle)
  2128  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--leave-cohort", "--edge", "foo"})
  2129  	c.Assert(err, check.IsNil)
  2130  	c.Assert(rest, check.DeepEquals, []string{})
  2131  	c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" left the cohort, and switched to the "edge" channel`)
  2132  	c.Check(s.Stderr(), check.Equals, "")
  2133  	// ensure that the fake server api was actually hit
  2134  	c.Check(s.srv.n, check.Equals, s.srv.total)
  2135  }
  2136  
  2137  func (s *SnapOpSuite) TestSwitchUnhappy(c *check.C) {
  2138  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch"})
  2139  	c.Assert(err, check.ErrorMatches, "the required argument `<snap>` was not provided")
  2140  }
  2141  
  2142  func (s *SnapOpSuite) TestSwitchAlsoUnhappy(c *check.C) {
  2143  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "foo"})
  2144  	c.Assert(err, check.ErrorMatches, `nothing to switch.*`)
  2145  }
  2146  
  2147  func (s *SnapOpSuite) TestSwitchMoreUnhappy(c *check.C) {
  2148  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "foo", "--cohort=what", "--leave-cohort"})
  2149  	c.Assert(err, check.ErrorMatches, `cannot specify both --cohort and --leave-cohort`)
  2150  }
  2151  
  2152  func (s *SnapOpSuite) TestSnapOpNetworkTimeoutError(c *check.C) {
  2153  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  2154  		c.Check(r.Method, check.Equals, "POST")
  2155  		w.WriteHeader(202)
  2156  		w.Write([]byte(`
  2157  {
  2158    "type": "error",
  2159    "result": {
  2160      "message":"Get https://api.snapcraft.io/api/v1/snaps/details/hello?channel=stable&fields=anon_download_url%2Carchitecture%2Cchannel%2Cdownload_sha3_384%2Csummary%2Cdescription%2Cdeltas%2Cbinary_filesize%2Cdownload_url%2Cepoch%2Cicon_url%2Clast_updated%2Cpackage_name%2Cprices%2Cpublisher%2Cratings_average%2Crevision%2Cscreenshot_urls%2Csnap_id%2Clicense%2Cbase%2Csupport_url%2Ccontact%2Ctitle%2Ccontent%2Cversion%2Corigin%2Cdeveloper_id%2Cprivate%2Cconfinement%2Cchannel_maps_list: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)",
  2161      "kind":"network-timeout"
  2162    },
  2163    "status-code": 400
  2164  }
  2165  `))
  2166  
  2167  	})
  2168  
  2169  	cmd := []string{"install", "hello"}
  2170  	_, err := snap.Parser(snap.Client()).ParseArgs(cmd)
  2171  	c.Assert(err, check.ErrorMatches, `unable to contact snap store`)
  2172  }