github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap/cmd_snap_op_test.go (about)

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