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