github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/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  	rebooting       bool
    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.rebooting {
    75  			fmt.Fprintln(w, `{"type": "sync", "result": {"status": "Doing"}}`)
    76  		} else {
    77  			fmt.Fprintln(w, `{"type": "sync", "result": {"status": "Doing"}, "maintenance": {"kind": "system-restart", "message": "system is restarting"}}}`)
    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, 100*time.Millisecond)
   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.rebooting = true
  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  
  1336  func (s *SnapOpSuite) TestRefreshOneChanDeprecated(c *check.C) {
  1337  	var in, out string
  1338  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1339  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{"action": "refresh", "channel": out})
  1340  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "snap not found", "value": "foo", "kind": "snap-not-found"}, "status-code": 404}`)
  1341  	})
  1342  
  1343  	for in, out = range map[string]string{
  1344  		"/foo":            "foo/stable",
  1345  		"/stable":         "latest/stable",
  1346  		"///foo/stable//": "foo/stable",
  1347  	} {
  1348  		s.stderr.Reset()
  1349  		_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--channel=" + in, "one"})
  1350  		c.Assert(err, check.ErrorMatches, "snap \"one\" not found")
  1351  		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.`)
  1352  	}
  1353  }
  1354  
  1355  func (s *SnapOpSuite) TestRefreshOneModeErr(c *check.C) {
  1356  	s.RedirectClientToTestServer(nil)
  1357  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--jailmode", "--devmode", "one"})
  1358  	c.Assert(err, check.ErrorMatches, `cannot use devmode and jailmode flags together`)
  1359  }
  1360  
  1361  func (s *SnapOpSuite) TestRefreshOneChanErr(c *check.C) {
  1362  	s.RedirectClientToTestServer(nil)
  1363  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta", "--channel=foo", "one"})
  1364  	c.Assert(err, check.ErrorMatches, `Please specify a single channel`)
  1365  }
  1366  
  1367  func (s *SnapOpSuite) TestRefreshAllChannel(c *check.C) {
  1368  	s.RedirectClientToTestServer(nil)
  1369  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta"})
  1370  	c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`)
  1371  }
  1372  
  1373  func (s *SnapOpSuite) TestRefreshManyChannel(c *check.C) {
  1374  	s.RedirectClientToTestServer(nil)
  1375  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--beta", "one", "two"})
  1376  	c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`)
  1377  }
  1378  
  1379  func (s *SnapOpSuite) TestRefreshManyIgnoreValidation(c *check.C) {
  1380  	s.RedirectClientToTestServer(nil)
  1381  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--ignore-validation", "one", "two"})
  1382  	c.Assert(err, check.ErrorMatches, `a single snap name must be specified when ignoring validation`)
  1383  }
  1384  
  1385  func (s *SnapOpSuite) TestRefreshAllModeFlags(c *check.C) {
  1386  	s.RedirectClientToTestServer(nil)
  1387  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--devmode"})
  1388  	c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`)
  1389  }
  1390  
  1391  func (s *SnapOpSuite) TestRefreshOneAmend(c *check.C) {
  1392  	s.RedirectClientToTestServer(s.srv.handle)
  1393  	s.srv.checker = func(r *http.Request) {
  1394  		c.Check(r.Method, check.Equals, "POST")
  1395  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/one")
  1396  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1397  			"action": "refresh",
  1398  			"amend":  true,
  1399  		})
  1400  	}
  1401  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--amend", "one"})
  1402  	c.Assert(err, check.IsNil)
  1403  }
  1404  
  1405  func (s *SnapOpSuite) runTryTest(c *check.C, opts *client.SnapOptions) {
  1406  	// pass relative path to cmd
  1407  	tryDir := "some-dir"
  1408  
  1409  	modes := []struct {
  1410  		enabled bool
  1411  		name    string
  1412  	}{
  1413  		{opts.DevMode, "devmode"},
  1414  		{opts.JailMode, "jailmode"},
  1415  		{opts.Classic, "classic"},
  1416  	}
  1417  
  1418  	s.srv.checker = func(r *http.Request) {
  1419  		// ensure the client always sends the absolute path
  1420  		fullTryDir, err := filepath.Abs(tryDir)
  1421  		c.Assert(err, check.IsNil)
  1422  
  1423  		c.Check(r.URL.Path, check.Equals, "/v2/snaps")
  1424  		form := testForm(r, c)
  1425  		defer form.RemoveAll()
  1426  
  1427  		c.Assert(form.Value["action"], check.HasLen, 1)
  1428  		c.Assert(form.Value["snap-path"], check.HasLen, 1)
  1429  		c.Check(form.File, check.HasLen, 0)
  1430  		c.Check(form.Value["action"][0], check.Equals, "try")
  1431  		c.Check(form.Value["snap-path"][0], check.Matches, regexp.QuoteMeta(fullTryDir))
  1432  
  1433  		for _, mode := range modes {
  1434  			if mode.enabled {
  1435  				c.Assert(form.Value[mode.name], check.HasLen, 1)
  1436  				c.Check(form.Value[mode.name][0], check.Equals, "true")
  1437  			} else {
  1438  				c.Check(form.Value[mode.name], check.IsNil)
  1439  			}
  1440  		}
  1441  	}
  1442  
  1443  	s.RedirectClientToTestServer(s.srv.handle)
  1444  
  1445  	cmd := []string{"try", tryDir}
  1446  	for _, mode := range modes {
  1447  		if mode.enabled {
  1448  			cmd = append(cmd, "--"+mode.name)
  1449  		}
  1450  	}
  1451  
  1452  	rest, err := snap.Parser(snap.Client()).ParseArgs(cmd)
  1453  	c.Assert(err, check.IsNil)
  1454  	c.Assert(rest, check.DeepEquals, []string{})
  1455  	c.Check(s.Stdout(), check.Matches, fmt.Sprintf(`(?sm).*foo 1.0 mounted from .*%s`, tryDir))
  1456  	c.Check(s.Stderr(), check.Equals, "")
  1457  	// ensure that the fake server api was actually hit
  1458  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1459  }
  1460  
  1461  func (s *SnapOpSuite) TestTryNoMode(c *check.C) {
  1462  	s.runTryTest(c, &client.SnapOptions{})
  1463  }
  1464  
  1465  func (s *SnapOpSuite) TestTryDevMode(c *check.C) {
  1466  	s.runTryTest(c, &client.SnapOptions{DevMode: true})
  1467  }
  1468  
  1469  func (s *SnapOpSuite) TestTryJailMode(c *check.C) {
  1470  	s.runTryTest(c, &client.SnapOptions{JailMode: true})
  1471  }
  1472  
  1473  func (s *SnapOpSuite) TestTryClassic(c *check.C) {
  1474  	s.runTryTest(c, &client.SnapOptions{Classic: true})
  1475  }
  1476  
  1477  func (s *SnapOpSuite) TestTryNoSnapDirErrors(c *check.C) {
  1478  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1479  		c.Check(r.Method, check.Equals, "POST")
  1480  		w.WriteHeader(202)
  1481  		fmt.Fprintln(w, `
  1482  {
  1483    "type": "error",
  1484    "result": {
  1485      "message":"error from server",
  1486      "kind":"snap-not-a-snap"
  1487    },
  1488    "status-code": 400
  1489  }`)
  1490  	})
  1491  
  1492  	cmd := []string{"try", "/"}
  1493  	_, err := snap.Parser(snap.Client()).ParseArgs(cmd)
  1494  	c.Assert(err, testutil.EqualsWrapped, `
  1495  "/" does not contain an unpacked snap.
  1496  
  1497  Try 'snapcraft prime' in your project directory, then 'snap try' again.`)
  1498  }
  1499  
  1500  func (s *SnapOpSuite) TestTryMissingOpt(c *check.C) {
  1501  	oldArgs := os.Args
  1502  	defer func() {
  1503  		os.Args = oldArgs
  1504  	}()
  1505  	os.Args = []string{"snap", "try", "./"}
  1506  	var kind string
  1507  
  1508  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1509  		c.Check(r.Method, check.Equals, "POST", check.Commentf("%q", kind))
  1510  		w.WriteHeader(400)
  1511  		fmt.Fprintf(w, `
  1512  {
  1513    "type": "error",
  1514    "result": {
  1515      "message":"error from server",
  1516      "value": "some-snap",
  1517      "kind": %q
  1518    },
  1519    "status-code": 400
  1520  }`, kind)
  1521  	})
  1522  
  1523  	type table struct {
  1524  		kind, expected string
  1525  	}
  1526  
  1527  	tests := []table{
  1528  		{"snap-needs-classic", "published using classic confinement"},
  1529  		{"snap-needs-devmode", "only meant for development"},
  1530  	}
  1531  
  1532  	for _, test := range tests {
  1533  		kind = test.kind
  1534  		c.Check(snap.RunMain(), testutil.ContainsWrapped, test.expected, check.Commentf("%q", kind))
  1535  	}
  1536  }
  1537  
  1538  func (s *SnapOpSuite) TestInstallConfinedAsClassic(c *check.C) {
  1539  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1540  		c.Check(r.Method, check.Equals, "POST")
  1541  		w.WriteHeader(400)
  1542  		fmt.Fprintf(w, `{
  1543    "type": "error",
  1544    "result": {
  1545      "message":"error from server",
  1546      "value": "some-snap",
  1547      "kind": "snap-not-classic"
  1548    },
  1549    "status-code": 400
  1550  }`)
  1551  	})
  1552  
  1553  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--classic", "some-snap"})
  1554  	c.Assert(err, check.ErrorMatches, `snap "some-snap" is not compatible with --classic`)
  1555  }
  1556  
  1557  func (s *SnapSuite) TestInstallChannelDuplicationError(c *check.C) {
  1558  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--edge", "--beta", "some-snap"})
  1559  	c.Assert(err, check.ErrorMatches, "Please specify a single channel")
  1560  }
  1561  
  1562  func (s *SnapSuite) TestRefreshChannelDuplicationError(c *check.C) {
  1563  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "--edge", "--beta", "some-snap"})
  1564  	c.Assert(err, check.ErrorMatches, "Please specify a single channel")
  1565  }
  1566  
  1567  func (s *SnapOpSuite) TestInstallFromChannel(c *check.C) {
  1568  	s.srv.checker = func(r *http.Request) {
  1569  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1570  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1571  			"action":  "install",
  1572  			"channel": "edge",
  1573  		})
  1574  		s.srv.channel = "edge"
  1575  	}
  1576  
  1577  	s.RedirectClientToTestServer(s.srv.handle)
  1578  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--edge", "foo"})
  1579  	c.Assert(err, check.IsNil)
  1580  	c.Assert(rest, check.DeepEquals, []string{})
  1581  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(edge\) 1.0 from Bar installed`)
  1582  	c.Check(s.Stderr(), check.Equals, "")
  1583  	// ensure that the fake server api was actually hit
  1584  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1585  }
  1586  
  1587  func (s *SnapOpSuite) TestEnable(c *check.C) {
  1588  	s.srv.total = 3
  1589  	s.srv.checker = func(r *http.Request) {
  1590  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1591  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1592  			"action": "enable",
  1593  		})
  1594  	}
  1595  
  1596  	s.RedirectClientToTestServer(s.srv.handle)
  1597  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"enable", "foo"})
  1598  	c.Assert(err, check.IsNil)
  1599  	c.Assert(rest, check.DeepEquals, []string{})
  1600  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo enabled`)
  1601  	c.Check(s.Stderr(), check.Equals, "")
  1602  	// ensure that the fake server api was actually hit
  1603  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1604  }
  1605  
  1606  func (s *SnapOpSuite) TestDisable(c *check.C) {
  1607  	s.srv.total = 3
  1608  	s.srv.checker = func(r *http.Request) {
  1609  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1610  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1611  			"action": "disable",
  1612  		})
  1613  	}
  1614  
  1615  	s.RedirectClientToTestServer(s.srv.handle)
  1616  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"disable", "foo"})
  1617  	c.Assert(err, check.IsNil)
  1618  	c.Assert(rest, check.DeepEquals, []string{})
  1619  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo disabled`)
  1620  	c.Check(s.Stderr(), check.Equals, "")
  1621  	// ensure that the fake server api was actually hit
  1622  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1623  }
  1624  
  1625  func (s *SnapOpSuite) TestRemove(c *check.C) {
  1626  	s.srv.total = 3
  1627  	s.srv.checker = func(r *http.Request) {
  1628  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1629  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1630  			"action": "remove",
  1631  		})
  1632  	}
  1633  
  1634  	s.RedirectClientToTestServer(s.srv.handle)
  1635  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "foo"})
  1636  	c.Assert(err, check.IsNil)
  1637  	c.Assert(rest, check.DeepEquals, []string{})
  1638  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo removed`)
  1639  	c.Check(s.Stderr(), check.Equals, "")
  1640  	// ensure that the fake server api was actually hit
  1641  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1642  }
  1643  
  1644  func (s *SnapOpSuite) TestRemoveWithPurge(c *check.C) {
  1645  	s.srv.total = 3
  1646  	s.srv.checker = func(r *http.Request) {
  1647  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1648  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1649  			"action": "remove",
  1650  			"purge":  true,
  1651  		})
  1652  	}
  1653  
  1654  	s.RedirectClientToTestServer(s.srv.handle)
  1655  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--purge", "foo"})
  1656  	c.Assert(err, check.IsNil)
  1657  	c.Assert(rest, check.DeepEquals, []string{})
  1658  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo removed`)
  1659  	c.Check(s.Stderr(), check.Equals, "")
  1660  	// ensure that the fake server api was actually hit
  1661  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1662  }
  1663  
  1664  func (s *SnapOpSuite) TestRemoveInsufficientDiskSpace(c *check.C) {
  1665  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1666  		fmt.Fprintln(w, `{
  1667  			"type": "error",
  1668  			"result": {
  1669  				"message": "disk space error",
  1670  				"kind": "insufficient-disk-space",
  1671  				"value": {
  1672  					"snap-names": ["foo", "bar"],
  1673  					"change-kind": "remove"
  1674  				},
  1675  				"status-code": 507
  1676  				}}`)
  1677  	})
  1678  
  1679  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "foo"})
  1680  	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`)
  1681  	c.Check(s.Stdout(), check.Equals, "")
  1682  	c.Check(s.Stderr(), check.Equals, "")
  1683  }
  1684  
  1685  func (s *SnapOpSuite) TestInstallInsufficientDiskSpace(c *check.C) {
  1686  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1687  		fmt.Fprintln(w, `{
  1688  			"type": "error",
  1689  			"result": {
  1690  				"message": "disk space error",
  1691  				"kind": "insufficient-disk-space",
  1692  				"value": {
  1693  					"snap-names": ["foo"],
  1694  					"change-kind": "install"
  1695  				},
  1696  				"status-code": 507
  1697  				}}`)
  1698  	})
  1699  
  1700  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "foo"})
  1701  	c.Check(err, check.ErrorMatches, `cannot install "foo" due to low disk space`)
  1702  	c.Check(s.Stdout(), check.Equals, "")
  1703  	c.Check(s.Stderr(), check.Equals, "")
  1704  }
  1705  
  1706  func (s *SnapOpSuite) TestRefreshInsufficientDiskSpace(c *check.C) {
  1707  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1708  		fmt.Fprintln(w, `{
  1709  			"type": "error",
  1710  			"result": {
  1711  				"message": "disk space error",
  1712  				"kind": "insufficient-disk-space",
  1713  				"value": {
  1714  					"snap-names": ["foo"],
  1715  					"change-kind": "refresh"
  1716  				},
  1717  				"status-code": 507
  1718  				}}`)
  1719  	})
  1720  
  1721  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"refresh", "foo"})
  1722  	c.Check(err, check.ErrorMatches, `cannot refresh "foo" due to low disk space`)
  1723  	c.Check(s.Stdout(), check.Equals, "")
  1724  	c.Check(s.Stderr(), check.Equals, "")
  1725  }
  1726  
  1727  func (s *SnapOpSuite) TestRemoveRevision(c *check.C) {
  1728  	s.srv.total = 3
  1729  	s.srv.checker = func(r *http.Request) {
  1730  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1731  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1732  			"action":   "remove",
  1733  			"revision": "17",
  1734  		})
  1735  	}
  1736  
  1737  	s.RedirectClientToTestServer(s.srv.handle)
  1738  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--revision=17", "foo"})
  1739  	c.Assert(err, check.IsNil)
  1740  	c.Assert(rest, check.DeepEquals, []string{})
  1741  	c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(revision 17\) removed`)
  1742  	c.Check(s.Stderr(), check.Equals, "")
  1743  	// ensure that the fake server api was actually hit
  1744  	c.Check(s.srv.n, check.Equals, s.srv.total)
  1745  }
  1746  
  1747  func (s *SnapOpSuite) TestRemoveManyOptions(c *check.C) {
  1748  	s.RedirectClientToTestServer(nil)
  1749  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--revision=17", "one", "two"})
  1750  	c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify options`)
  1751  	_, err = snap.Parser(snap.Client()).ParseArgs([]string{"remove", "--purge", "one", "two"})
  1752  	c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify options`)
  1753  }
  1754  
  1755  func (s *SnapOpSuite) TestRemoveMany(c *check.C) {
  1756  	total := 3
  1757  	n := 0
  1758  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1759  		switch n {
  1760  		case 0:
  1761  			c.Check(r.URL.Path, check.Equals, "/v2/snaps")
  1762  			c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1763  				"action": "remove",
  1764  				"snaps":  []interface{}{"one", "two"},
  1765  			})
  1766  
  1767  			c.Check(r.Method, check.Equals, "POST")
  1768  			w.WriteHeader(202)
  1769  			fmt.Fprintln(w, `{"type":"async", "change": "42", "status-code": 202}`)
  1770  		case 1:
  1771  			c.Check(r.Method, check.Equals, "GET")
  1772  			c.Check(r.URL.Path, check.Equals, "/v2/changes/42")
  1773  			fmt.Fprintln(w, `{"type": "sync", "result": {"status": "Doing"}}`)
  1774  		case 2:
  1775  			c.Check(r.Method, check.Equals, "GET")
  1776  			c.Check(r.URL.Path, check.Equals, "/v2/changes/42")
  1777  			fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done", "data": {"snap-names": ["one","two"]}}}`)
  1778  		default:
  1779  			c.Fatalf("expected to get %d requests, now on %d", total, n+1)
  1780  		}
  1781  
  1782  		n++
  1783  	})
  1784  
  1785  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"remove", "one", "two"})
  1786  	c.Assert(err, check.IsNil)
  1787  	c.Assert(rest, check.DeepEquals, []string{})
  1788  	c.Check(s.Stdout(), check.Matches, `(?sm).*one removed`)
  1789  	c.Check(s.Stdout(), check.Matches, `(?sm).*two removed`)
  1790  	c.Check(s.Stderr(), check.Equals, "")
  1791  	// ensure that the fake server api was actually hit
  1792  	c.Check(n, check.Equals, total)
  1793  }
  1794  
  1795  func (s *SnapOpSuite) TestInstallManyChannel(c *check.C) {
  1796  	s.RedirectClientToTestServer(nil)
  1797  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "--beta", "one", "two"})
  1798  	c.Assert(err, check.ErrorMatches, `a single snap name is needed to specify mode or channel flags`)
  1799  }
  1800  
  1801  func (s *SnapOpSuite) TestInstallManyMixFileAndStore(c *check.C) {
  1802  	s.RedirectClientToTestServer(nil)
  1803  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "store-snap", "./local.snap"})
  1804  	c.Assert(err, check.ErrorMatches, `only one snap file can be installed at a time`)
  1805  }
  1806  
  1807  func (s *SnapOpSuite) TestInstallMany(c *check.C) {
  1808  	total := 4
  1809  	n := 0
  1810  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1811  		switch n {
  1812  		case 0:
  1813  			c.Check(r.URL.Path, check.Equals, "/v2/snaps")
  1814  			c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1815  				"action": "install",
  1816  				"snaps":  []interface{}{"one", "two"},
  1817  			})
  1818  
  1819  			c.Check(r.Method, check.Equals, "POST")
  1820  			w.WriteHeader(202)
  1821  			fmt.Fprintln(w, `{"type":"async", "change": "42", "status-code": 202}`)
  1822  		case 1:
  1823  			c.Check(r.Method, check.Equals, "GET")
  1824  			c.Check(r.URL.Path, check.Equals, "/v2/changes/42")
  1825  			fmt.Fprintln(w, `{"type": "sync", "result": {"status": "Doing"}}`)
  1826  		case 2:
  1827  			c.Check(r.Method, check.Equals, "GET")
  1828  			c.Check(r.URL.Path, check.Equals, "/v2/changes/42")
  1829  			fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done", "data": {"snap-names": ["one","two"]}}}`)
  1830  		case 3:
  1831  			c.Check(r.Method, check.Equals, "GET")
  1832  			c.Check(r.URL.Path, check.Equals, "/v2/snaps")
  1833  			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`)
  1834  
  1835  		default:
  1836  			c.Fatalf("expected to get %d requests, now on %d", total, n+1)
  1837  		}
  1838  
  1839  		n++
  1840  	})
  1841  
  1842  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"install", "one", "two"})
  1843  	c.Assert(err, check.IsNil)
  1844  	c.Assert(rest, check.DeepEquals, []string{})
  1845  	// note that (stable) is omitted
  1846  	c.Check(s.Stdout(), check.Matches, `(?sm).*one 1.0 from Bar installed`)
  1847  	c.Check(s.Stdout(), check.Matches, `(?sm).*two \(edge\) 2.0 from Baz installed`)
  1848  	c.Check(s.Stderr(), check.Equals, "")
  1849  	// ensure that the fake server api was actually hit
  1850  	c.Check(n, check.Equals, total)
  1851  }
  1852  
  1853  func (s *SnapOpSuite) TestInstallZeroEmpty(c *check.C) {
  1854  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"install"})
  1855  	c.Assert(err, check.ErrorMatches, "cannot install zero snaps")
  1856  	_, err = snap.Parser(snap.Client()).ParseArgs([]string{"install", ""})
  1857  	c.Assert(err, check.ErrorMatches, "cannot install snap with empty name")
  1858  	_, err = snap.Parser(snap.Client()).ParseArgs([]string{"install", "", "bar"})
  1859  	c.Assert(err, check.ErrorMatches, "cannot install snap with empty name")
  1860  }
  1861  
  1862  func (s *SnapOpSuite) TestNoWait(c *check.C) {
  1863  	s.srv.checker = func(r *http.Request) {}
  1864  
  1865  	cmds := [][]string{
  1866  		{"remove", "--no-wait", "foo"},
  1867  		{"remove", "--no-wait", "foo", "bar"},
  1868  		{"install", "--no-wait", "foo"},
  1869  		{"install", "--no-wait", "foo", "bar"},
  1870  		{"revert", "--no-wait", "foo"},
  1871  		{"refresh", "--no-wait", "foo"},
  1872  		{"refresh", "--no-wait", "foo", "bar"},
  1873  		{"refresh", "--no-wait"},
  1874  		{"enable", "--no-wait", "foo"},
  1875  		{"disable", "--no-wait", "foo"},
  1876  		{"try", "--no-wait", "."},
  1877  		{"switch", "--no-wait", "--channel=foo", "bar"},
  1878  		// commands that use waitMixin from elsewhere
  1879  		{"start", "--no-wait", "foo"},
  1880  		{"stop", "--no-wait", "foo"},
  1881  		{"restart", "--no-wait", "foo"},
  1882  		{"alias", "--no-wait", "foo", "bar"},
  1883  		{"unalias", "--no-wait", "foo"},
  1884  		{"prefer", "--no-wait", "foo"},
  1885  		{"set", "--no-wait", "foo", "bar=baz"},
  1886  		{"disconnect", "--no-wait", "foo:bar"},
  1887  		{"connect", "--no-wait", "foo:bar"},
  1888  	}
  1889  
  1890  	s.RedirectClientToTestServer(s.srv.handle)
  1891  	for _, cmd := range cmds {
  1892  		rest, err := snap.Parser(snap.Client()).ParseArgs(cmd)
  1893  		c.Assert(err, check.IsNil, check.Commentf("%v", cmd))
  1894  		c.Assert(rest, check.DeepEquals, []string{})
  1895  		c.Check(s.Stdout(), check.Matches, "(?sm)42\n")
  1896  		c.Check(s.Stderr(), check.Equals, "")
  1897  		c.Check(s.srv.n, check.Equals, 1)
  1898  		// reset
  1899  		s.srv.n = 0
  1900  		s.stdout.Reset()
  1901  	}
  1902  }
  1903  
  1904  func (s *SnapOpSuite) TestNoWaitImmediateError(c *check.C) {
  1905  
  1906  	cmds := [][]string{
  1907  		{"remove", "--no-wait", "foo"},
  1908  		{"remove", "--no-wait", "foo", "bar"},
  1909  		{"install", "--no-wait", "foo"},
  1910  		{"install", "--no-wait", "foo", "bar"},
  1911  		{"revert", "--no-wait", "foo"},
  1912  		{"refresh", "--no-wait", "foo"},
  1913  		{"refresh", "--no-wait", "foo", "bar"},
  1914  		{"refresh", "--no-wait"},
  1915  		{"enable", "--no-wait", "foo"},
  1916  		{"disable", "--no-wait", "foo"},
  1917  		{"try", "--no-wait", "."},
  1918  		{"switch", "--no-wait", "--channel=foo", "bar"},
  1919  		// commands that use waitMixin from elsewhere
  1920  		{"start", "--no-wait", "foo"},
  1921  		{"stop", "--no-wait", "foo"},
  1922  		{"restart", "--no-wait", "foo"},
  1923  		{"alias", "--no-wait", "foo", "bar"},
  1924  		{"unalias", "--no-wait", "foo"},
  1925  		{"prefer", "--no-wait", "foo"},
  1926  		{"set", "--no-wait", "foo", "bar=baz"},
  1927  		{"disconnect", "--no-wait", "foo:bar"},
  1928  		{"connect", "--no-wait", "foo:bar"},
  1929  	}
  1930  
  1931  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1932  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "failure"}}`)
  1933  	})
  1934  
  1935  	for _, cmd := range cmds {
  1936  		_, err := snap.Parser(snap.Client()).ParseArgs(cmd)
  1937  		c.Assert(err, check.ErrorMatches, "failure", check.Commentf("%v", cmd))
  1938  	}
  1939  }
  1940  
  1941  func (s *SnapOpSuite) TestWaitServerError(c *check.C) {
  1942  	r := snap.MockMaxGoneTime(0)
  1943  	defer r()
  1944  
  1945  	cmds := [][]string{
  1946  		{"remove", "foo"},
  1947  		{"remove", "foo", "bar"},
  1948  		{"install", "foo"},
  1949  		{"install", "foo", "bar"},
  1950  		{"revert", "foo"},
  1951  		{"refresh", "foo"},
  1952  		{"refresh", "foo", "bar"},
  1953  		{"refresh"},
  1954  		{"enable", "foo"},
  1955  		{"disable", "foo"},
  1956  		{"try", "."},
  1957  		{"switch", "--channel=foo", "bar"},
  1958  		// commands that use waitMixin from elsewhere
  1959  		{"start", "foo"},
  1960  		{"stop", "foo"},
  1961  		{"restart", "foo"},
  1962  		{"alias", "foo", "bar"},
  1963  		{"unalias", "foo"},
  1964  		{"prefer", "foo"},
  1965  		{"set", "foo", "bar=baz"},
  1966  		{"disconnect", "foo:bar"},
  1967  		{"connect", "foo:bar"},
  1968  	}
  1969  
  1970  	n := 0
  1971  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  1972  		n++
  1973  		if n == 1 {
  1974  			w.WriteHeader(202)
  1975  			fmt.Fprintln(w, `{"type":"async", "change": "42", "status-code": 202}`)
  1976  			return
  1977  		}
  1978  		if n == 3 {
  1979  			fmt.Fprintln(w, `{"type": "error", "result": {"message": "unexpected request"}}`)
  1980  			return
  1981  		}
  1982  		fmt.Fprintln(w, `{"type": "error", "result": {"message": "server error"}}`)
  1983  	})
  1984  
  1985  	for _, cmd := range cmds {
  1986  		_, err := snap.Parser(snap.Client()).ParseArgs(cmd)
  1987  		c.Assert(err, check.ErrorMatches, "server error", check.Commentf("%v", cmd))
  1988  		// reset
  1989  		n = 0
  1990  	}
  1991  }
  1992  
  1993  func (s *SnapOpSuite) TestSwitchHappy(c *check.C) {
  1994  	s.srv.total = 4
  1995  	s.srv.checker = func(r *http.Request) {
  1996  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  1997  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  1998  			"action":  "switch",
  1999  			"channel": "beta",
  2000  		})
  2001  		s.srv.trackingChannel = "beta"
  2002  	}
  2003  
  2004  	s.RedirectClientToTestServer(s.srv.handle)
  2005  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--beta", "foo"})
  2006  	c.Assert(err, check.IsNil)
  2007  	c.Assert(rest, check.DeepEquals, []string{})
  2008  	c.Check(s.Stdout(), check.Equals, `"foo" switched to the "beta" channel
  2009  
  2010  `)
  2011  	c.Check(s.Stderr(), check.Equals, "")
  2012  	// ensure that the fake server api was actually hit
  2013  	c.Check(s.srv.n, check.Equals, s.srv.total)
  2014  }
  2015  
  2016  func (s *SnapOpSuite) TestSwitchHappyCohort(c *check.C) {
  2017  	s.srv.total = 4
  2018  	s.srv.checker = func(r *http.Request) {
  2019  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  2020  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  2021  			"action":     "switch",
  2022  			"cohort-key": "what",
  2023  		})
  2024  	}
  2025  
  2026  	s.RedirectClientToTestServer(s.srv.handle)
  2027  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--cohort=what", "foo"})
  2028  	c.Assert(err, check.IsNil)
  2029  	c.Assert(rest, check.DeepEquals, []string{})
  2030  	c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" switched to the "what" cohort`)
  2031  	c.Check(s.Stderr(), check.Equals, "")
  2032  	// ensure that the fake server api was actually hit
  2033  	c.Check(s.srv.n, check.Equals, s.srv.total)
  2034  }
  2035  
  2036  func (s *SnapOpSuite) TestSwitchHappyLeaveCohort(c *check.C) {
  2037  	s.srv.total = 4
  2038  	s.srv.checker = func(r *http.Request) {
  2039  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  2040  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  2041  			"action":       "switch",
  2042  			"leave-cohort": true,
  2043  		})
  2044  	}
  2045  
  2046  	s.RedirectClientToTestServer(s.srv.handle)
  2047  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--leave-cohort", "foo"})
  2048  	c.Assert(err, check.IsNil)
  2049  	c.Assert(rest, check.DeepEquals, []string{})
  2050  	c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" left the cohort`)
  2051  	c.Check(s.Stderr(), check.Equals, "")
  2052  	// ensure that the fake server api was actually hit
  2053  	c.Check(s.srv.n, check.Equals, s.srv.total)
  2054  }
  2055  
  2056  func (s *SnapOpSuite) TestSwitchHappyChannelAndCohort(c *check.C) {
  2057  	s.srv.total = 4
  2058  	s.srv.checker = func(r *http.Request) {
  2059  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  2060  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  2061  			"action":     "switch",
  2062  			"cohort-key": "what",
  2063  			"channel":    "edge",
  2064  		})
  2065  		s.srv.trackingChannel = "edge"
  2066  	}
  2067  
  2068  	s.RedirectClientToTestServer(s.srv.handle)
  2069  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--cohort=what", "--edge", "foo"})
  2070  	c.Assert(err, check.IsNil)
  2071  	c.Assert(rest, check.DeepEquals, []string{})
  2072  	c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" switched to the "edge" channel and the "what" cohort`)
  2073  	c.Check(s.Stderr(), check.Equals, "")
  2074  	// ensure that the fake server api was actually hit
  2075  	c.Check(s.srv.n, check.Equals, s.srv.total)
  2076  }
  2077  
  2078  func (s *SnapOpSuite) TestSwitchHappyChannelAndLeaveCohort(c *check.C) {
  2079  	s.srv.total = 4
  2080  	s.srv.checker = func(r *http.Request) {
  2081  		c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
  2082  		c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
  2083  			"action":       "switch",
  2084  			"leave-cohort": true,
  2085  			"channel":      "edge",
  2086  		})
  2087  		s.srv.trackingChannel = "edge"
  2088  	}
  2089  
  2090  	s.RedirectClientToTestServer(s.srv.handle)
  2091  	rest, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "--leave-cohort", "--edge", "foo"})
  2092  	c.Assert(err, check.IsNil)
  2093  	c.Assert(rest, check.DeepEquals, []string{})
  2094  	c.Check(s.Stdout(), check.Matches, `(?sm).*"foo" left the cohort, and switched to the "edge" channel`)
  2095  	c.Check(s.Stderr(), check.Equals, "")
  2096  	// ensure that the fake server api was actually hit
  2097  	c.Check(s.srv.n, check.Equals, s.srv.total)
  2098  }
  2099  
  2100  func (s *SnapOpSuite) TestSwitchUnhappy(c *check.C) {
  2101  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch"})
  2102  	c.Assert(err, check.ErrorMatches, "the required argument `<snap>` was not provided")
  2103  }
  2104  
  2105  func (s *SnapOpSuite) TestSwitchAlsoUnhappy(c *check.C) {
  2106  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "foo"})
  2107  	c.Assert(err, check.ErrorMatches, `nothing to switch.*`)
  2108  }
  2109  
  2110  func (s *SnapOpSuite) TestSwitchMoreUnhappy(c *check.C) {
  2111  	_, err := snap.Parser(snap.Client()).ParseArgs([]string{"switch", "foo", "--cohort=what", "--leave-cohort"})
  2112  	c.Assert(err, check.ErrorMatches, `cannot specify both --cohort and --leave-cohort`)
  2113  }
  2114  
  2115  func (s *SnapOpSuite) TestSnapOpNetworkTimeoutError(c *check.C) {
  2116  	s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
  2117  		c.Check(r.Method, check.Equals, "POST")
  2118  		w.WriteHeader(202)
  2119  		w.Write([]byte(`
  2120  {
  2121    "type": "error",
  2122    "result": {
  2123      "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)",
  2124      "kind":"network-timeout"
  2125    },
  2126    "status-code": 400
  2127  }
  2128  `))
  2129  
  2130  	})
  2131  
  2132  	cmd := []string{"install", "hello"}
  2133  	_, err := snap.Parser(snap.Client()).ParseArgs(cmd)
  2134  	c.Assert(err, check.ErrorMatches, `unable to contact snap store`)
  2135  }