github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/daemon/api_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2020 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 daemon
    21  
    22  import (
    23  	"bytes"
    24  	"context"
    25  	"crypto"
    26  	"encoding/json"
    27  	"errors"
    28  	"fmt"
    29  	"io"
    30  	"io/ioutil"
    31  	"math"
    32  	"mime/multipart"
    33  	"net/http"
    34  	"net/http/httptest"
    35  	"net/url"
    36  	"os"
    37  	"path/filepath"
    38  	"regexp"
    39  	"sort"
    40  	"strconv"
    41  	"strings"
    42  	"time"
    43  
    44  	"golang.org/x/crypto/sha3"
    45  	"gopkg.in/check.v1"
    46  	"gopkg.in/tomb.v2"
    47  
    48  	"github.com/snapcore/snapd/arch"
    49  	"github.com/snapcore/snapd/asserts"
    50  	"github.com/snapcore/snapd/asserts/assertstest"
    51  	"github.com/snapcore/snapd/asserts/sysdb"
    52  	"github.com/snapcore/snapd/boot"
    53  	"github.com/snapcore/snapd/client"
    54  	"github.com/snapcore/snapd/dirs"
    55  	"github.com/snapcore/snapd/interfaces"
    56  	"github.com/snapcore/snapd/interfaces/builtin"
    57  	"github.com/snapcore/snapd/interfaces/ifacetest"
    58  	"github.com/snapcore/snapd/osutil"
    59  	"github.com/snapcore/snapd/overlord"
    60  	"github.com/snapcore/snapd/overlord/assertstate"
    61  	"github.com/snapcore/snapd/overlord/assertstate/assertstatetest"
    62  	"github.com/snapcore/snapd/overlord/auth"
    63  	"github.com/snapcore/snapd/overlord/configstate/config"
    64  	"github.com/snapcore/snapd/overlord/devicestate"
    65  	"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
    66  	"github.com/snapcore/snapd/overlord/healthstate"
    67  	"github.com/snapcore/snapd/overlord/hookstate"
    68  	"github.com/snapcore/snapd/overlord/hookstate/ctlcmd"
    69  	"github.com/snapcore/snapd/overlord/ifacestate"
    70  	"github.com/snapcore/snapd/overlord/servicestate"
    71  	"github.com/snapcore/snapd/overlord/snapstate"
    72  	"github.com/snapcore/snapd/overlord/state"
    73  	"github.com/snapcore/snapd/release"
    74  	"github.com/snapcore/snapd/sandbox"
    75  	"github.com/snapcore/snapd/snap"
    76  	"github.com/snapcore/snapd/snap/channel"
    77  	"github.com/snapcore/snapd/snap/snaptest"
    78  	"github.com/snapcore/snapd/store"
    79  	"github.com/snapcore/snapd/store/storetest"
    80  	"github.com/snapcore/snapd/systemd"
    81  	"github.com/snapcore/snapd/testutil"
    82  )
    83  
    84  type apiBaseSuite struct {
    85  	storetest.Store
    86  
    87  	rsnaps            []*snap.Info
    88  	err               error
    89  	vars              map[string]string
    90  	storeSearch       store.Search
    91  	suggestedCurrency string
    92  	d                 *Daemon
    93  	user              *auth.UserState
    94  	ctx               context.Context
    95  	restoreBackends   func()
    96  	currentSnaps      []*store.CurrentSnap
    97  	actions           []*store.SnapAction
    98  	buyOptions        *client.BuyOptions
    99  	buyResult         *client.BuyResult
   100  	storeSigning      *assertstest.StoreStack
   101  	restoreRelease    func()
   102  	trustedRestorer   func()
   103  	brands            *assertstest.SigningAccounts
   104  
   105  	systemctlRestorer func()
   106  	sysctlBufs        [][]byte
   107  
   108  	journalctlRestorer func()
   109  	jctlSvcses         [][]string
   110  	jctlNs             []int
   111  	jctlFollows        []bool
   112  	jctlRCs            []io.ReadCloser
   113  	jctlErrs           []error
   114  
   115  	serviceControlError error
   116  	serviceControlCalls []serviceControlArgs
   117  
   118  	connectivityResult     map[string]bool
   119  	loginUserStoreMacaroon string
   120  	loginUserDischarge     string
   121  	userInfoResult         *store.User
   122  	userInfoExpectedEmail  string
   123  
   124  	restoreSanitize func()
   125  
   126  	testutil.BaseTest
   127  }
   128  
   129  type serviceControlArgs struct {
   130  	action  string
   131  	options string
   132  	names   []string
   133  }
   134  
   135  func (s *apiBaseSuite) pokeStateLock() {
   136  	// the store should be called without the state lock held. Try
   137  	// to acquire it.
   138  	st := s.d.overlord.State()
   139  	st.Lock()
   140  	st.Unlock()
   141  }
   142  
   143  func (s *apiBaseSuite) SnapInfo(ctx context.Context, spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) {
   144  	s.pokeStateLock()
   145  	s.user = user
   146  	s.ctx = ctx
   147  	if len(s.rsnaps) > 0 {
   148  		return s.rsnaps[0], s.err
   149  	}
   150  	return nil, s.err
   151  }
   152  
   153  func (s *apiBaseSuite) Find(ctx context.Context, search *store.Search, user *auth.UserState) ([]*snap.Info, error) {
   154  	s.pokeStateLock()
   155  
   156  	s.storeSearch = *search
   157  	s.user = user
   158  	s.ctx = ctx
   159  
   160  	return s.rsnaps, s.err
   161  }
   162  
   163  func (s *apiBaseSuite) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, assertQuery store.AssertionQuery, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, []store.AssertionResult, error) {
   164  	s.pokeStateLock()
   165  	if assertQuery != nil {
   166  		panic("no assertion query support")
   167  	}
   168  
   169  	if ctx == nil {
   170  		panic("context required")
   171  	}
   172  	s.currentSnaps = currentSnaps
   173  	s.actions = actions
   174  	s.user = user
   175  
   176  	sars := make([]store.SnapActionResult, len(s.rsnaps))
   177  	for i, rsnap := range s.rsnaps {
   178  		sars[i] = store.SnapActionResult{Info: rsnap}
   179  	}
   180  	return sars, nil, s.err
   181  }
   182  
   183  func (s *apiBaseSuite) SuggestedCurrency() string {
   184  	s.pokeStateLock()
   185  
   186  	return s.suggestedCurrency
   187  }
   188  
   189  func (s *apiBaseSuite) Buy(options *client.BuyOptions, user *auth.UserState) (*client.BuyResult, error) {
   190  	s.pokeStateLock()
   191  
   192  	s.buyOptions = options
   193  	s.user = user
   194  	return s.buyResult, s.err
   195  }
   196  
   197  func (s *apiBaseSuite) ReadyToBuy(user *auth.UserState) error {
   198  	s.pokeStateLock()
   199  
   200  	s.user = user
   201  	return s.err
   202  }
   203  
   204  func (s *apiBaseSuite) ConnectivityCheck() (map[string]bool, error) {
   205  	s.pokeStateLock()
   206  
   207  	return s.connectivityResult, s.err
   208  }
   209  
   210  func (s *apiBaseSuite) LoginUser(username, password, otp string) (string, string, error) {
   211  	s.pokeStateLock()
   212  
   213  	return s.loginUserStoreMacaroon, s.loginUserDischarge, s.err
   214  }
   215  
   216  func (s *apiBaseSuite) UserInfo(email string) (userinfo *store.User, err error) {
   217  	s.pokeStateLock()
   218  
   219  	if s.userInfoExpectedEmail != email {
   220  		panic(fmt.Sprintf("%q != %q", s.userInfoExpectedEmail, email))
   221  	}
   222  	return s.userInfoResult, s.err
   223  }
   224  
   225  func (s *apiBaseSuite) muxVars(*http.Request) map[string]string {
   226  	return s.vars
   227  }
   228  
   229  func (s *apiBaseSuite) SetUpSuite(c *check.C) {
   230  	muxVars = s.muxVars
   231  	s.restoreRelease = sandbox.MockForceDevMode(false)
   232  	s.systemctlRestorer = systemd.MockSystemctl(s.systemctl)
   233  	s.journalctlRestorer = systemd.MockJournalctl(s.journalctl)
   234  	s.restoreSanitize = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
   235  }
   236  
   237  func (s *apiBaseSuite) TearDownSuite(c *check.C) {
   238  	muxVars = nil
   239  	s.restoreRelease()
   240  	s.systemctlRestorer()
   241  	s.journalctlRestorer()
   242  	s.restoreSanitize()
   243  }
   244  
   245  func (s *apiBaseSuite) systemctl(args ...string) (buf []byte, err error) {
   246  	if len(s.sysctlBufs) > 0 {
   247  		buf, s.sysctlBufs = s.sysctlBufs[0], s.sysctlBufs[1:]
   248  	}
   249  
   250  	return buf, err
   251  }
   252  
   253  func (s *apiBaseSuite) journalctl(svcs []string, n int, follow bool) (rc io.ReadCloser, err error) {
   254  	s.jctlSvcses = append(s.jctlSvcses, svcs)
   255  	s.jctlNs = append(s.jctlNs, n)
   256  	s.jctlFollows = append(s.jctlFollows, follow)
   257  
   258  	if len(s.jctlErrs) > 0 {
   259  		err, s.jctlErrs = s.jctlErrs[0], s.jctlErrs[1:]
   260  	}
   261  	if len(s.jctlRCs) > 0 {
   262  		rc, s.jctlRCs = s.jctlRCs[0], s.jctlRCs[1:]
   263  	}
   264  
   265  	return rc, err
   266  }
   267  
   268  func (s *apiBaseSuite) SetUpTest(c *check.C) {
   269  	s.sysctlBufs = nil
   270  	s.jctlSvcses = nil
   271  	s.jctlNs = nil
   272  	s.jctlFollows = nil
   273  	s.jctlRCs = nil
   274  	s.jctlErrs = nil
   275  
   276  	dirs.SetRootDir(c.MkDir())
   277  	err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755)
   278  	restore := osutil.MockMountInfo("")
   279  	s.AddCleanup(restore)
   280  
   281  	c.Assert(err, check.IsNil)
   282  	c.Assert(os.MkdirAll(dirs.SnapMountDir, 0755), check.IsNil)
   283  	c.Assert(os.MkdirAll(dirs.SnapBlobDir, 0755), check.IsNil)
   284  
   285  	s.rsnaps = nil
   286  	s.suggestedCurrency = ""
   287  	s.storeSearch = store.Search{}
   288  	s.err = nil
   289  	s.vars = nil
   290  	s.user = nil
   291  	s.d = nil
   292  	s.currentSnaps = nil
   293  	s.actions = nil
   294  	// Disable real security backends for all API tests
   295  	s.restoreBackends = ifacestate.MockSecurityBackends(nil)
   296  
   297  	s.buyOptions = nil
   298  	s.buyResult = nil
   299  
   300  	s.storeSigning = assertstest.NewStoreStack("can0nical", nil)
   301  	s.trustedRestorer = sysdb.InjectTrusted(s.storeSigning.Trusted)
   302  
   303  	s.brands = assertstest.NewSigningAccounts(s.storeSigning)
   304  	s.brands.Register("my-brand", brandPrivKey, nil)
   305  
   306  	assertstateRefreshSnapDeclarations = nil
   307  	snapstateInstall = nil
   308  	snapstateInstallMany = nil
   309  	snapstateInstallPath = nil
   310  	snapstateRefreshCandidates = nil
   311  	snapstateRemoveMany = nil
   312  	snapstateRevert = nil
   313  	snapstateRevertToRevision = nil
   314  	snapstateTryPath = nil
   315  	snapstateUpdate = nil
   316  	snapstateUpdateMany = nil
   317  	snapstateSwitch = nil
   318  
   319  	devicestateRemodel = nil
   320  
   321  	s.serviceControlCalls = nil
   322  	s.serviceControlError = nil
   323  	restoreServicestateCtrl := MockServicestateControl(s.fakeServiceControl)
   324  	s.AddCleanup(restoreServicestateCtrl)
   325  }
   326  
   327  func (s *apiBaseSuite) TearDownTest(c *check.C) {
   328  	s.trustedRestorer()
   329  	s.d = nil
   330  	s.ctx = nil
   331  	s.restoreBackends()
   332  	unsafeReadSnapInfo = unsafeReadSnapInfoImpl
   333  	ensureStateSoon = ensureStateSoonImpl
   334  	dirs.SetRootDir("")
   335  
   336  	assertstateRefreshSnapDeclarations = assertstate.RefreshSnapDeclarations
   337  	snapstateInstall = snapstate.Install
   338  	snapstateInstallMany = snapstate.InstallMany
   339  	snapstateInstallPath = snapstate.InstallPath
   340  	snapstateRefreshCandidates = snapstate.RefreshCandidates
   341  	snapstateRemoveMany = snapstate.RemoveMany
   342  	snapstateRevert = snapstate.Revert
   343  	snapstateRevertToRevision = snapstate.RevertToRevision
   344  	snapstateTryPath = snapstate.TryPath
   345  	snapstateUpdate = snapstate.Update
   346  	snapstateUpdateMany = snapstate.UpdateMany
   347  	snapstateSwitch = snapstate.Switch
   348  }
   349  
   350  var modelDefaults = map[string]interface{}{
   351  	"architecture": "amd64",
   352  	"gadget":       "gadget",
   353  	"kernel":       "kernel",
   354  }
   355  
   356  func (s *apiBaseSuite) fakeServiceControl(st *state.State, appInfos []*snap.AppInfo, inst *servicestate.Instruction, context *hookstate.Context) ([]*state.TaskSet, error) {
   357  	st.Lock()
   358  	defer st.Unlock()
   359  
   360  	if s.serviceControlError != nil {
   361  		return nil, s.serviceControlError
   362  	}
   363  
   364  	serviceCommand := serviceControlArgs{action: inst.Action}
   365  	if inst.RestartOptions.Reload {
   366  		serviceCommand.options = "reload"
   367  	}
   368  	// only one flag should ever be set (depending on Action), but appending
   369  	// them below acts as an extra sanity check.
   370  	if inst.StartOptions.Enable {
   371  		serviceCommand.options += "enable"
   372  	}
   373  	if inst.StopOptions.Disable {
   374  		serviceCommand.options += "disable"
   375  	}
   376  	for _, app := range appInfos {
   377  		serviceCommand.names = append(serviceCommand.names, fmt.Sprintf("%s.%s", app.Snap.InstanceName(), app.Name))
   378  	}
   379  	s.serviceControlCalls = append(s.serviceControlCalls, serviceCommand)
   380  
   381  	t := st.NewTask("dummy", "")
   382  	ts := state.NewTaskSet(t)
   383  	return []*state.TaskSet{ts}, nil
   384  }
   385  
   386  func (s *apiBaseSuite) mockModel(c *check.C, st *state.State, model *asserts.Model) {
   387  	// realistic model setup
   388  	if model == nil {
   389  		model = s.brands.Model("can0nical", "pc", modelDefaults)
   390  	}
   391  
   392  	snapstate.DeviceCtx = devicestate.DeviceCtx
   393  
   394  	assertstatetest.AddMany(st, model)
   395  
   396  	devicestatetest.SetDevice(st, &auth.DeviceState{
   397  		Brand:  model.BrandID(),
   398  		Model:  model.Model(),
   399  		Serial: "serialserial",
   400  	})
   401  }
   402  
   403  func (s *apiBaseSuite) daemon(c *check.C) *Daemon {
   404  	if s.d != nil {
   405  		panic("called daemon() twice")
   406  	}
   407  	d, err := New()
   408  	c.Assert(err, check.IsNil)
   409  	d.addRoutes()
   410  
   411  	c.Assert(d.overlord.StartUp(), check.IsNil)
   412  
   413  	st := d.overlord.State()
   414  	st.Lock()
   415  	defer st.Unlock()
   416  	snapstate.ReplaceStore(st, s)
   417  	// mark as already seeded
   418  	st.Set("seeded", true)
   419  	// registered
   420  	s.mockModel(c, st, nil)
   421  
   422  	// don't actually try to talk to the store on snapstate.Ensure
   423  	// needs doing after the call to devicestate.Manager (which
   424  	// happens in daemon.New via overlord.New)
   425  	snapstate.CanAutoRefresh = nil
   426  
   427  	s.d = d
   428  	return d
   429  }
   430  
   431  func (s *apiBaseSuite) daemonWithOverlordMock(c *check.C) *Daemon {
   432  	if s.d != nil {
   433  		panic("called daemon() twice")
   434  	}
   435  	d, err := New()
   436  	c.Assert(err, check.IsNil)
   437  	d.addRoutes()
   438  
   439  	o := overlord.Mock()
   440  	d.overlord = o
   441  
   442  	st := d.overlord.State()
   443  	// adds an assertion db
   444  	assertstate.Manager(st, o.TaskRunner())
   445  	st.Lock()
   446  	defer st.Unlock()
   447  	snapstate.ReplaceStore(st, s)
   448  
   449  	s.d = d
   450  	return d
   451  }
   452  
   453  type fakeSnapManager struct{}
   454  
   455  func newFakeSnapManager(st *state.State, runner *state.TaskRunner) *fakeSnapManager {
   456  	runner.AddHandler("fake-install-snap", func(t *state.Task, _ *tomb.Tomb) error {
   457  		return nil
   458  	}, nil)
   459  	runner.AddHandler("fake-install-snap-error", func(t *state.Task, _ *tomb.Tomb) error {
   460  		return fmt.Errorf("fake-install-snap-error errored")
   461  	}, nil)
   462  
   463  	return &fakeSnapManager{}
   464  }
   465  
   466  func (m *fakeSnapManager) Ensure() error {
   467  	return nil
   468  }
   469  
   470  // sanity
   471  var _ overlord.StateManager = (*fakeSnapManager)(nil)
   472  
   473  func (s *apiBaseSuite) daemonWithFakeSnapManager(c *check.C) *Daemon {
   474  	d := s.daemonWithOverlordMock(c)
   475  	st := d.overlord.State()
   476  	runner := d.overlord.TaskRunner()
   477  	d.overlord.AddManager(newFakeSnapManager(st, runner))
   478  	d.overlord.AddManager(runner)
   479  	c.Assert(d.overlord.StartUp(), check.IsNil)
   480  	return d
   481  }
   482  
   483  func (s *apiBaseSuite) waitTrivialChange(c *check.C, chg *state.Change) {
   484  	err := s.d.overlord.Settle(5 * time.Second)
   485  	c.Assert(err, check.IsNil)
   486  	c.Assert(chg.IsReady(), check.Equals, true)
   487  }
   488  
   489  func (s *apiBaseSuite) mkInstalledDesktopFile(c *check.C, name, content string) string {
   490  	df := filepath.Join(dirs.SnapDesktopFilesDir, name)
   491  	err := os.MkdirAll(filepath.Dir(df), 0755)
   492  	c.Assert(err, check.IsNil)
   493  	err = ioutil.WriteFile(df, []byte(content), 0644)
   494  	c.Assert(err, check.IsNil)
   495  	return df
   496  }
   497  
   498  func (s *apiBaseSuite) mkInstalledInState(c *check.C, daemon *Daemon, instanceName, developer, version string, revision snap.Revision, active bool, extraYaml string) *snap.Info {
   499  	snapName, instanceKey := snap.SplitInstanceName(instanceName)
   500  
   501  	if revision.Local() && developer != "" {
   502  		panic("not supported")
   503  	}
   504  
   505  	var snapID string
   506  	if revision.Store() {
   507  		snapID = snapName + "-id"
   508  	}
   509  	// Collect arguments into a snap.SideInfo structure
   510  	sideInfo := &snap.SideInfo{
   511  		SnapID:   snapID,
   512  		RealName: snapName,
   513  		Revision: revision,
   514  		Channel:  "stable",
   515  	}
   516  
   517  	// Collect other arguments into a yaml string
   518  	yamlText := fmt.Sprintf(`
   519  name: %s
   520  version: %s
   521  %s`, snapName, version, extraYaml)
   522  
   523  	// Mock the snap on disk
   524  	snapInfo := snaptest.MockSnapInstance(c, instanceName, yamlText, sideInfo)
   525  	if active {
   526  		dir, rev := filepath.Split(snapInfo.MountDir())
   527  		c.Assert(os.Symlink(rev, dir+"current"), check.IsNil)
   528  	}
   529  	c.Assert(snapInfo.InstanceName(), check.Equals, instanceName)
   530  
   531  	c.Assert(os.MkdirAll(snapInfo.DataDir(), 0755), check.IsNil)
   532  	metadir := filepath.Join(snapInfo.MountDir(), "meta")
   533  	guidir := filepath.Join(metadir, "gui")
   534  	c.Assert(os.MkdirAll(guidir, 0755), check.IsNil)
   535  	c.Check(ioutil.WriteFile(filepath.Join(guidir, "icon.svg"), []byte("yadda icon"), 0644), check.IsNil)
   536  
   537  	if daemon == nil {
   538  		return snapInfo
   539  	}
   540  	st := daemon.overlord.State()
   541  	st.Lock()
   542  	defer st.Unlock()
   543  
   544  	var snapst snapstate.SnapState
   545  	snapstate.Get(st, instanceName, &snapst)
   546  	snapst.Active = active
   547  	snapst.Sequence = append(snapst.Sequence, &snapInfo.SideInfo)
   548  	snapst.Current = snapInfo.SideInfo.Revision
   549  	snapst.TrackingChannel = "stable"
   550  	snapst.InstanceKey = instanceKey
   551  
   552  	snapstate.Set(st, instanceName, &snapst)
   553  
   554  	if developer == "" {
   555  		return snapInfo
   556  	}
   557  
   558  	devAcct := assertstest.NewAccount(s.storeSigning, developer, map[string]interface{}{
   559  		"account-id": developer + "-id",
   560  	}, "")
   561  
   562  	snapInfo.Publisher = snap.StoreAccount{
   563  		ID:          devAcct.AccountID(),
   564  		Username:    devAcct.Username(),
   565  		DisplayName: devAcct.DisplayName(),
   566  		Validation:  devAcct.Validation(),
   567  	}
   568  
   569  	snapDecl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{
   570  		"series":       "16",
   571  		"snap-id":      snapID,
   572  		"snap-name":    snapName,
   573  		"publisher-id": devAcct.AccountID(),
   574  		"timestamp":    time.Now().Format(time.RFC3339),
   575  	}, nil, "")
   576  	c.Assert(err, check.IsNil)
   577  
   578  	content, err := ioutil.ReadFile(snapInfo.MountFile())
   579  	c.Assert(err, check.IsNil)
   580  	h := sha3.Sum384(content)
   581  	dgst, err := asserts.EncodeDigest(crypto.SHA3_384, h[:])
   582  	c.Assert(err, check.IsNil)
   583  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{
   584  		"snap-sha3-384": string(dgst),
   585  		"snap-size":     "999",
   586  		"snap-id":       snapID,
   587  		"snap-revision": fmt.Sprintf("%s", revision),
   588  		"developer-id":  devAcct.AccountID(),
   589  		"timestamp":     time.Now().Format(time.RFC3339),
   590  	}, nil, "")
   591  	c.Assert(err, check.IsNil)
   592  
   593  	assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""), devAcct, snapDecl, snapRev)
   594  
   595  	return snapInfo
   596  }
   597  
   598  type apiSuite struct {
   599  	apiBaseSuite
   600  }
   601  
   602  var _ = check.Suite(&apiSuite{})
   603  
   604  func (s *apiSuite) TestUsersOnlyRoot(c *check.C) {
   605  	for _, cmd := range api {
   606  		if strings.Contains(cmd.Path, "user") {
   607  			c.Check(cmd.RootOnly, check.Equals, true, check.Commentf(cmd.Path))
   608  		}
   609  	}
   610  }
   611  
   612  func (s *apiSuite) TestSnapInfoOneIntegration(c *check.C) {
   613  	d := s.daemon(c)
   614  	s.vars = map[string]string{"name": "foo"}
   615  
   616  	// we have v0 [r5] installed
   617  	s.mkInstalledInState(c, d, "foo", "bar", "v0", snap.R(5), false, "")
   618  	// and v1 [r10] is current
   619  	s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), true, `title: title
   620  description: description
   621  summary: summary
   622  license: GPL-3.0
   623  base: base18
   624  apps:
   625    cmd:
   626      command: some.cmd
   627    cmd2:
   628      command: other.cmd
   629    cmd3:
   630      command: other.cmd
   631      common-id: org.foo.cmd
   632    svc1:
   633      command: somed1
   634      daemon: simple
   635    svc2:
   636      command: somed2
   637      daemon: forking
   638    svc3:
   639      command: somed3
   640      daemon: oneshot
   641    svc4:
   642      command: somed4
   643      daemon: notify
   644    svc5:
   645      command: some5
   646      timer: mon1,12:15
   647      daemon: simple
   648    svc6:
   649      command: some6
   650      daemon: simple
   651      sockets:
   652         sock:
   653           listen-stream: $SNAP_COMMON/run.sock
   654    svc7:
   655      command: some7
   656      daemon: simple
   657      sockets:
   658         other-sock:
   659           listen-stream: $SNAP_COMMON/other-run.sock
   660  `)
   661  	df := s.mkInstalledDesktopFile(c, "foo_cmd.desktop", "[Desktop]\nExec=foo.cmd %U")
   662  	s.sysctlBufs = [][]byte{
   663  		[]byte(`Type=simple
   664  Id=snap.foo.svc1.service
   665  ActiveState=fumbling
   666  UnitFileState=enabled
   667  `),
   668  		[]byte(`Type=forking
   669  Id=snap.foo.svc2.service
   670  ActiveState=active
   671  UnitFileState=disabled
   672  `),
   673  		[]byte(`Type=oneshot
   674  Id=snap.foo.svc3.service
   675  ActiveState=reloading
   676  UnitFileState=static
   677  `),
   678  		[]byte(`Type=notify
   679  Id=snap.foo.svc4.service
   680  ActiveState=inactive
   681  UnitFileState=potatoes
   682  `),
   683  		[]byte(`Type=simple
   684  Id=snap.foo.svc5.service
   685  ActiveState=inactive
   686  UnitFileState=static
   687  `),
   688  		[]byte(`Id=snap.foo.svc5.timer
   689  ActiveState=active
   690  UnitFileState=enabled
   691  `),
   692  		[]byte(`Type=simple
   693  Id=snap.foo.svc6.service
   694  ActiveState=inactive
   695  UnitFileState=static
   696  `),
   697  		[]byte(`Id=snap.foo.svc6.sock.socket
   698  ActiveState=active
   699  UnitFileState=enabled
   700  `),
   701  		[]byte(`Type=simple
   702  Id=snap.foo.svc7.service
   703  ActiveState=inactive
   704  UnitFileState=static
   705  `),
   706  		[]byte(`Id=snap.foo.svc7.other-sock.socket
   707  ActiveState=inactive
   708  UnitFileState=enabled
   709  `),
   710  	}
   711  
   712  	var snapst snapstate.SnapState
   713  	st := s.d.overlord.State()
   714  	st.Lock()
   715  	st.Set("health", map[string]healthstate.HealthState{
   716  		"foo": {Status: healthstate.OkayStatus},
   717  	})
   718  	err := snapstate.Get(st, "foo", &snapst)
   719  	st.Unlock()
   720  	c.Assert(err, check.IsNil)
   721  
   722  	// modify state
   723  	snapst.TrackingChannel = "beta"
   724  	snapst.IgnoreValidation = true
   725  	snapst.CohortKey = "some-long-cohort-key"
   726  	st.Lock()
   727  	snapstate.Set(st, "foo", &snapst)
   728  	st.Unlock()
   729  
   730  	req, err := http.NewRequest("GET", "/v2/snaps/foo", nil)
   731  	c.Assert(err, check.IsNil)
   732  	rsp, ok := getSnapInfo(snapCmd, req, nil).(*resp)
   733  	c.Assert(ok, check.Equals, true)
   734  
   735  	c.Assert(rsp, check.NotNil)
   736  	c.Assert(rsp.Result, check.FitsTypeOf, &client.Snap{})
   737  	m := rsp.Result.(*client.Snap)
   738  
   739  	// installed-size depends on vagaries of the filesystem, just check type
   740  	c.Check(m.InstalledSize, check.FitsTypeOf, int64(0))
   741  	m.InstalledSize = 0
   742  	// ditto install-date
   743  	c.Check(m.InstallDate, check.FitsTypeOf, time.Time{})
   744  	m.InstallDate = time.Time{}
   745  
   746  	meta := &Meta{}
   747  	expected := &resp{
   748  		Type:   ResponseTypeSync,
   749  		Status: 200,
   750  		Result: &client.Snap{
   751  			ID:               "foo-id",
   752  			Name:             "foo",
   753  			Revision:         snap.R(10),
   754  			Version:          "v1",
   755  			Channel:          "stable",
   756  			TrackingChannel:  "beta",
   757  			IgnoreValidation: true,
   758  			Title:            "title",
   759  			Summary:          "summary",
   760  			Description:      "description",
   761  			Developer:        "bar",
   762  			Publisher: &snap.StoreAccount{
   763  				ID:          "bar-id",
   764  				Username:    "bar",
   765  				DisplayName: "Bar",
   766  				Validation:  "unproven",
   767  			},
   768  			Status:      "active",
   769  			Health:      &client.SnapHealth{Status: "okay"},
   770  			Icon:        "/v2/icons/foo/icon",
   771  			Type:        string(snap.TypeApp),
   772  			Base:        "base18",
   773  			Private:     false,
   774  			DevMode:     false,
   775  			JailMode:    false,
   776  			Confinement: string(snap.StrictConfinement),
   777  			TryMode:     false,
   778  			MountedFrom: filepath.Join(dirs.SnapBlobDir, "foo_10.snap"),
   779  			Apps: []client.AppInfo{
   780  				{
   781  					Snap: "foo", Name: "cmd",
   782  					DesktopFile: df,
   783  				}, {
   784  					// no desktop file
   785  					Snap: "foo", Name: "cmd2",
   786  				}, {
   787  					// has AppStream ID
   788  					Snap: "foo", Name: "cmd3",
   789  					CommonID: "org.foo.cmd",
   790  				}, {
   791  					// services
   792  					Snap: "foo", Name: "svc1",
   793  					Daemon:  "simple",
   794  					Enabled: true,
   795  					Active:  false,
   796  				}, {
   797  					Snap: "foo", Name: "svc2",
   798  					Daemon:  "forking",
   799  					Enabled: false,
   800  					Active:  true,
   801  				}, {
   802  					Snap: "foo", Name: "svc3",
   803  					Daemon:  "oneshot",
   804  					Enabled: true,
   805  					Active:  true,
   806  				}, {
   807  					Snap: "foo", Name: "svc4",
   808  					Daemon:  "notify",
   809  					Enabled: false,
   810  					Active:  false,
   811  				}, {
   812  					Snap: "foo", Name: "svc5",
   813  					Daemon:  "simple",
   814  					Enabled: true,
   815  					Active:  false,
   816  					Activators: []client.AppActivator{
   817  						{Name: "svc5", Type: "timer", Active: true, Enabled: true},
   818  					},
   819  				}, {
   820  					Snap: "foo", Name: "svc6",
   821  					Daemon:  "simple",
   822  					Enabled: true,
   823  					Active:  false,
   824  					Activators: []client.AppActivator{
   825  						{Name: "sock", Type: "socket", Active: true, Enabled: true},
   826  					},
   827  				}, {
   828  					Snap: "foo", Name: "svc7",
   829  					Daemon:  "simple",
   830  					Enabled: true,
   831  					Active:  false,
   832  					Activators: []client.AppActivator{
   833  						{Name: "other-sock", Type: "socket", Active: false, Enabled: true},
   834  					},
   835  				},
   836  			},
   837  			Broken:    "",
   838  			Contact:   "",
   839  			License:   "GPL-3.0",
   840  			CommonIDs: []string{"org.foo.cmd"},
   841  			CohortKey: "some-long-cohort-key",
   842  		},
   843  		Meta: meta,
   844  	}
   845  
   846  	c.Check(rsp.Result, check.DeepEquals, expected.Result)
   847  }
   848  
   849  func (s *apiSuite) TestSnapInfoWithAuth(c *check.C) {
   850  	s.daemon(c)
   851  
   852  	state := snapCmd.d.overlord.State()
   853  	state.Lock()
   854  	user, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"})
   855  	state.Unlock()
   856  	c.Check(err, check.IsNil)
   857  
   858  	req, err := http.NewRequest("GET", "/v2/find/?q=name:gfoo", nil)
   859  	c.Assert(err, check.IsNil)
   860  
   861  	c.Assert(s.user, check.IsNil)
   862  
   863  	_, ok := searchStore(findCmd, req, user).(*resp)
   864  	c.Assert(ok, check.Equals, true)
   865  	// ensure user was set
   866  	c.Assert(s.user, check.DeepEquals, user)
   867  }
   868  
   869  func (s *apiSuite) TestSnapInfoNotFound(c *check.C) {
   870  	req, err := http.NewRequest("GET", "/v2/snaps/gfoo", nil)
   871  	c.Assert(err, check.IsNil)
   872  	c.Check(getSnapInfo(snapCmd, req, nil).(*resp).Status, check.Equals, 404)
   873  }
   874  
   875  func (s *apiSuite) TestSnapInfoNoneFound(c *check.C) {
   876  	s.vars = map[string]string{"name": "foo"}
   877  
   878  	req, err := http.NewRequest("GET", "/v2/snaps/gfoo", nil)
   879  	c.Assert(err, check.IsNil)
   880  	c.Check(getSnapInfo(snapCmd, req, nil).(*resp).Status, check.Equals, 404)
   881  }
   882  
   883  func (s *apiSuite) TestSnapInfoIgnoresRemoteErrors(c *check.C) {
   884  	s.vars = map[string]string{"name": "foo"}
   885  	s.err = errors.New("weird")
   886  
   887  	req, err := http.NewRequest("GET", "/v2/snaps/gfoo", nil)
   888  	c.Assert(err, check.IsNil)
   889  	rsp := getSnapInfo(snapCmd, req, nil).(*resp)
   890  
   891  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
   892  	c.Check(rsp.Status, check.Equals, 404)
   893  	c.Check(rsp.Result, check.NotNil)
   894  }
   895  
   896  func (s *apiSuite) TestMapLocalFields(c *check.C) {
   897  	media := snap.MediaInfos{
   898  		{
   899  			Type: "screenshot",
   900  			URL:  "https://example.com/shot1.svg",
   901  		}, {
   902  			Type: "icon",
   903  			URL:  "https://example.com/icon.png",
   904  		}, {
   905  			Type: "screenshot",
   906  			URL:  "https://example.com/shot2.svg",
   907  		},
   908  	}
   909  
   910  	publisher := snap.StoreAccount{
   911  		ID:          "some-dev-id",
   912  		Username:    "some-dev",
   913  		DisplayName: "Some Developer",
   914  		Validation:  "poor",
   915  	}
   916  	info := &snap.Info{
   917  		SideInfo: snap.SideInfo{
   918  			SnapID:            "some-snap-id",
   919  			RealName:          "some-snap",
   920  			EditedTitle:       "A Title",
   921  			EditedSummary:     "a summary",
   922  			EditedDescription: "the\nlong\ndescription",
   923  			Channel:           "bleeding/edge",
   924  			Contact:           "alice@example.com",
   925  			Revision:          snap.R(7),
   926  			Private:           true,
   927  		},
   928  		InstanceKey: "instance",
   929  		SnapType:    "app",
   930  		Base:        "the-base",
   931  		Version:     "v1.0",
   932  		License:     "MIT",
   933  		Broken:      "very",
   934  		Confinement: "very strict",
   935  		CommonIDs:   []string{"foo", "bar"},
   936  		Media:       media,
   937  		DownloadInfo: snap.DownloadInfo{
   938  			Size:     42,
   939  			Sha3_384: "some-sum",
   940  		},
   941  		Publisher: publisher,
   942  	}
   943  
   944  	// make InstallDate work
   945  	c.Assert(os.MkdirAll(info.MountDir(), 0755), check.IsNil)
   946  	c.Assert(os.Symlink("7", filepath.Join(info.MountDir(), "..", "current")), check.IsNil)
   947  
   948  	info.Apps = map[string]*snap.AppInfo{
   949  		"foo": {Snap: info, Name: "foo", Command: "foo"},
   950  		"bar": {Snap: info, Name: "bar", Command: "bar"},
   951  	}
   952  	about := aboutSnap{
   953  		info: info,
   954  		snapst: &snapstate.SnapState{
   955  			Active:          true,
   956  			TrackingChannel: "flaky/beta",
   957  			Current:         snap.R(7),
   958  			Flags: snapstate.Flags{
   959  				IgnoreValidation: true,
   960  				DevMode:          true,
   961  				JailMode:         true,
   962  			},
   963  		},
   964  	}
   965  
   966  	expected := &client.Snap{
   967  		ID:               "some-snap-id",
   968  		Name:             "some-snap_instance",
   969  		Summary:          "a summary",
   970  		Description:      "the\nlong\ndescription",
   971  		Developer:        "some-dev",
   972  		Publisher:        &publisher,
   973  		Icon:             "https://example.com/icon.png",
   974  		Type:             "app",
   975  		Base:             "the-base",
   976  		Version:          "v1.0",
   977  		Revision:         snap.R(7),
   978  		Channel:          "bleeding/edge",
   979  		TrackingChannel:  "flaky/beta",
   980  		InstallDate:      info.InstallDate(),
   981  		InstalledSize:    42,
   982  		Status:           "active",
   983  		Confinement:      "very strict",
   984  		IgnoreValidation: true,
   985  		DevMode:          true,
   986  		JailMode:         true,
   987  		Private:          true,
   988  		Broken:           "very",
   989  		Contact:          "alice@example.com",
   990  		Title:            "A Title",
   991  		License:          "MIT",
   992  		CommonIDs:        []string{"foo", "bar"},
   993  		MountedFrom:      filepath.Join(dirs.SnapBlobDir, "some-snap_instance_7.snap"),
   994  		Media:            media,
   995  		Apps: []client.AppInfo{
   996  			{Snap: "some-snap_instance", Name: "bar"},
   997  			{Snap: "some-snap_instance", Name: "foo"},
   998  		},
   999  	}
  1000  	c.Check(mapLocal(about, nil), check.DeepEquals, expected)
  1001  }
  1002  
  1003  func (s *apiSuite) TestMapLocalOfTryResolvesSymlink(c *check.C) {
  1004  	c.Assert(os.MkdirAll(dirs.SnapBlobDir, 0755), check.IsNil)
  1005  
  1006  	info := snap.Info{SideInfo: snap.SideInfo{RealName: "hello", Revision: snap.R(1)}}
  1007  	snapst := snapstate.SnapState{}
  1008  	mountFile := info.MountFile()
  1009  	about := aboutSnap{info: &info, snapst: &snapst}
  1010  
  1011  	// if not a 'try', then MountedFrom is just MountFile()
  1012  	c.Check(mapLocal(about, nil).MountedFrom, check.Equals, mountFile)
  1013  
  1014  	// if it's a try, then MountedFrom resolves the symlink
  1015  	// (note it doesn't matter, here, whether the target of the link exists)
  1016  	snapst.TryMode = true
  1017  	c.Assert(os.Symlink("/xyzzy", mountFile), check.IsNil)
  1018  	c.Check(mapLocal(about, nil).MountedFrom, check.Equals, "/xyzzy")
  1019  
  1020  	// if the readlink fails, it's unset
  1021  	c.Assert(os.Remove(mountFile), check.IsNil)
  1022  	c.Check(mapLocal(about, nil).MountedFrom, check.Equals, "")
  1023  }
  1024  
  1025  func (s *apiSuite) TestListIncludesAll(c *check.C) {
  1026  	// Very basic check to help stop us from not adding all the
  1027  	// commands to the command list.
  1028  	found := countCommandDecls(c, check.Commentf("TestListIncludesAll"))
  1029  
  1030  	c.Check(found, check.Equals, len(api),
  1031  		check.Commentf(`At a glance it looks like you've not added all the Commands defined in api to the api list.`))
  1032  }
  1033  
  1034  func (s *apiSuite) TestRootCmd(c *check.C) {
  1035  	// check it only does GET
  1036  	c.Check(rootCmd.PUT, check.IsNil)
  1037  	c.Check(rootCmd.POST, check.IsNil)
  1038  	c.Assert(rootCmd.GET, check.NotNil)
  1039  
  1040  	rec := httptest.NewRecorder()
  1041  	c.Check(rootCmd.Path, check.Equals, "/")
  1042  
  1043  	rootCmd.GET(rootCmd, nil, nil).ServeHTTP(rec, nil)
  1044  	c.Check(rec.Code, check.Equals, 200)
  1045  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json")
  1046  
  1047  	expected := []interface{}{"TBD"}
  1048  	var rsp resp
  1049  	c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
  1050  	c.Check(rsp.Status, check.Equals, 200)
  1051  	c.Check(rsp.Result, check.DeepEquals, expected)
  1052  }
  1053  
  1054  func mockSystemdVirt(newVirt string) (restore func()) {
  1055  	oldVirt := systemdVirt
  1056  	systemdVirt = newVirt
  1057  	return func() { systemdVirt = oldVirt }
  1058  }
  1059  
  1060  func (s *apiSuite) TestSysInfo(c *check.C) {
  1061  	// check it only does GET
  1062  	c.Check(sysInfoCmd.PUT, check.IsNil)
  1063  	c.Check(sysInfoCmd.POST, check.IsNil)
  1064  	c.Assert(sysInfoCmd.GET, check.NotNil)
  1065  
  1066  	rec := httptest.NewRecorder()
  1067  	c.Check(sysInfoCmd.Path, check.Equals, "/v2/system-info")
  1068  
  1069  	d := s.daemon(c)
  1070  	d.Version = "42b1"
  1071  
  1072  	// set both legacy and new refresh schedules. new one takes priority
  1073  	st := d.overlord.State()
  1074  	st.Lock()
  1075  	tr := config.NewTransaction(st)
  1076  	tr.Set("core", "refresh.schedule", "00:00-9:00/12:00-13:00")
  1077  	tr.Set("core", "refresh.timer", "8:00~9:00/2")
  1078  	tr.Commit()
  1079  	st.Unlock()
  1080  
  1081  	restore := release.MockReleaseInfo(&release.OS{ID: "distro-id", VersionID: "1.2"})
  1082  	defer restore()
  1083  	restore = release.MockOnClassic(true)
  1084  	defer restore()
  1085  	restore = sandbox.MockForceDevMode(true)
  1086  	defer restore()
  1087  	// reload dirs for release info to have effect
  1088  	dirs.SetRootDir(dirs.GlobalRootDir)
  1089  	restore = mockSystemdVirt("magic")
  1090  	defer restore()
  1091  
  1092  	buildID := "this-is-my-build-id"
  1093  	restore = MockBuildID(buildID)
  1094  	defer restore()
  1095  
  1096  	sysInfoCmd.GET(sysInfoCmd, nil, nil).ServeHTTP(rec, nil)
  1097  	c.Check(rec.Code, check.Equals, 200)
  1098  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json")
  1099  
  1100  	expected := map[string]interface{}{
  1101  		"series":  "16",
  1102  		"version": "42b1",
  1103  		"os-release": map[string]interface{}{
  1104  			"id":         "distro-id",
  1105  			"version-id": "1.2",
  1106  		},
  1107  		"build-id":   buildID,
  1108  		"on-classic": true,
  1109  		"managed":    false,
  1110  		"locations": map[string]interface{}{
  1111  			"snap-mount-dir": dirs.SnapMountDir,
  1112  			"snap-bin-dir":   dirs.SnapBinariesDir,
  1113  		},
  1114  		"refresh": map[string]interface{}{
  1115  			// only the "timer" field
  1116  			"timer": "8:00~9:00/2",
  1117  		},
  1118  		"confinement":      "partial",
  1119  		"sandbox-features": map[string]interface{}{"confinement-options": []interface{}{"classic", "devmode"}},
  1120  		"architecture":     arch.DpkgArchitecture(),
  1121  		"virtualization":   "magic",
  1122  		"system-mode":      "run",
  1123  	}
  1124  	var rsp resp
  1125  	c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
  1126  	c.Check(rsp.Status, check.Equals, 200)
  1127  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  1128  	// Ensure that we had a kernel-verrsion but don't check the actual value.
  1129  	const kernelVersionKey = "kernel-version"
  1130  	c.Check(rsp.Result.(map[string]interface{})[kernelVersionKey], check.Not(check.Equals), "")
  1131  	delete(rsp.Result.(map[string]interface{}), kernelVersionKey)
  1132  	c.Check(rsp.Result, check.DeepEquals, expected)
  1133  }
  1134  
  1135  func (s *apiSuite) TestSysInfoLegacyRefresh(c *check.C) {
  1136  	rec := httptest.NewRecorder()
  1137  
  1138  	d := s.daemon(c)
  1139  	d.Version = "42b1"
  1140  
  1141  	restore := release.MockReleaseInfo(&release.OS{ID: "distro-id", VersionID: "1.2"})
  1142  	defer restore()
  1143  	restore = release.MockOnClassic(true)
  1144  	defer restore()
  1145  	restore = sandbox.MockForceDevMode(true)
  1146  	defer restore()
  1147  	restore = mockSystemdVirt("kvm")
  1148  	defer restore()
  1149  	// reload dirs for release info to have effect
  1150  	dirs.SetRootDir(dirs.GlobalRootDir)
  1151  
  1152  	// set the legacy refresh schedule
  1153  	st := d.overlord.State()
  1154  	st.Lock()
  1155  	tr := config.NewTransaction(st)
  1156  	tr.Set("core", "refresh.schedule", "00:00-9:00/12:00-13:00")
  1157  	tr.Set("core", "refresh.timer", "")
  1158  	tr.Commit()
  1159  	st.Unlock()
  1160  
  1161  	// add a test security backend
  1162  	err := d.overlord.InterfaceManager().Repository().AddBackend(&ifacetest.TestSecurityBackend{
  1163  		BackendName:             "apparmor",
  1164  		SandboxFeaturesCallback: func() []string { return []string{"feature-1", "feature-2"} },
  1165  	})
  1166  	c.Assert(err, check.IsNil)
  1167  
  1168  	buildID := "this-is-my-build-id"
  1169  	restore = MockBuildID(buildID)
  1170  	defer restore()
  1171  
  1172  	sysInfoCmd.GET(sysInfoCmd, nil, nil).ServeHTTP(rec, nil)
  1173  	c.Check(rec.Code, check.Equals, 200)
  1174  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json")
  1175  
  1176  	expected := map[string]interface{}{
  1177  		"series":  "16",
  1178  		"version": "42b1",
  1179  		"os-release": map[string]interface{}{
  1180  			"id":         "distro-id",
  1181  			"version-id": "1.2",
  1182  		},
  1183  		"build-id":   buildID,
  1184  		"on-classic": true,
  1185  		"managed":    false,
  1186  		"locations": map[string]interface{}{
  1187  			"snap-mount-dir": dirs.SnapMountDir,
  1188  			"snap-bin-dir":   dirs.SnapBinariesDir,
  1189  		},
  1190  		"refresh": map[string]interface{}{
  1191  			// only the "schedule" field
  1192  			"schedule": "00:00-9:00/12:00-13:00",
  1193  		},
  1194  		"confinement": "partial",
  1195  		"sandbox-features": map[string]interface{}{
  1196  			"apparmor":            []interface{}{"feature-1", "feature-2"},
  1197  			"confinement-options": []interface{}{"classic", "devmode"}, // we know it's this because of the release.Mock... calls above
  1198  		},
  1199  		"architecture":   arch.DpkgArchitecture(),
  1200  		"virtualization": "kvm",
  1201  		"system-mode":    "run",
  1202  	}
  1203  	var rsp resp
  1204  	c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
  1205  	c.Check(rsp.Status, check.Equals, 200)
  1206  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  1207  	const kernelVersionKey = "kernel-version"
  1208  	delete(rsp.Result.(map[string]interface{}), kernelVersionKey)
  1209  	c.Check(rsp.Result, check.DeepEquals, expected)
  1210  }
  1211  
  1212  func (s *apiSuite) testSysInfoSystemMode(c *check.C, mode string) {
  1213  	c.Assert(mode != "", check.Equals, true, check.Commentf("mode is unset for the test"))
  1214  	rec := httptest.NewRecorder()
  1215  
  1216  	restore := release.MockReleaseInfo(&release.OS{ID: "distro-id", VersionID: "1.2"})
  1217  	defer restore()
  1218  	restore = release.MockOnClassic(false)
  1219  	defer restore()
  1220  	restore = sandbox.MockForceDevMode(false)
  1221  	defer restore()
  1222  	restore = mockSystemdVirt("")
  1223  	defer restore()
  1224  
  1225  	// reload dirs for release info to have effect on paths
  1226  	dirs.SetRootDir(dirs.GlobalRootDir)
  1227  
  1228  	// mock the modeenv file
  1229  	m := boot.Modeenv{
  1230  		Mode:           mode,
  1231  		RecoverySystem: "20191127",
  1232  	}
  1233  	err := m.WriteTo("")
  1234  	c.Assert(err, check.IsNil)
  1235  
  1236  	d := s.daemon(c)
  1237  	d.Version = "42b1"
  1238  
  1239  	// add a test security backend
  1240  	err = d.overlord.InterfaceManager().Repository().AddBackend(&ifacetest.TestSecurityBackend{
  1241  		BackendName:             "apparmor",
  1242  		SandboxFeaturesCallback: func() []string { return []string{"feature-1", "feature-2"} },
  1243  	})
  1244  	c.Assert(err, check.IsNil)
  1245  
  1246  	buildID := "this-is-my-build-id"
  1247  	restore = MockBuildID(buildID)
  1248  	defer restore()
  1249  
  1250  	sysInfoCmd.GET(sysInfoCmd, nil, nil).ServeHTTP(rec, nil)
  1251  	c.Check(rec.Code, check.Equals, 200)
  1252  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json")
  1253  
  1254  	expected := map[string]interface{}{
  1255  		"series":  "16",
  1256  		"version": "42b1",
  1257  		"os-release": map[string]interface{}{
  1258  			"id":         "distro-id",
  1259  			"version-id": "1.2",
  1260  		},
  1261  		"build-id":   buildID,
  1262  		"on-classic": false,
  1263  		"managed":    false,
  1264  		"locations": map[string]interface{}{
  1265  			"snap-mount-dir": dirs.SnapMountDir,
  1266  			"snap-bin-dir":   dirs.SnapBinariesDir,
  1267  		},
  1268  		"refresh": map[string]interface{}{
  1269  			"timer": "00:00~24:00/4",
  1270  		},
  1271  		"confinement": "strict",
  1272  		"sandbox-features": map[string]interface{}{
  1273  			"apparmor":            []interface{}{"feature-1", "feature-2"},
  1274  			"confinement-options": []interface{}{"devmode", "strict"}, // we know it's this because of the release.Mock... calls above
  1275  		},
  1276  		"architecture": arch.DpkgArchitecture(),
  1277  		"system-mode":  mode,
  1278  	}
  1279  	var rsp resp
  1280  	c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
  1281  	c.Check(rsp.Status, check.Equals, 200)
  1282  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  1283  	const kernelVersionKey = "kernel-version"
  1284  	delete(rsp.Result.(map[string]interface{}), kernelVersionKey)
  1285  	c.Check(rsp.Result, check.DeepEquals, expected)
  1286  }
  1287  
  1288  func (s *apiSuite) TestSysInfoSystemModeRun(c *check.C) {
  1289  	s.testSysInfoSystemMode(c, "run")
  1290  }
  1291  
  1292  func (s *apiSuite) TestSysInfoSystemModeRecover(c *check.C) {
  1293  	s.testSysInfoSystemMode(c, "recover")
  1294  }
  1295  
  1296  func (s *apiSuite) TestSysInfoSystemModeInstall(c *check.C) {
  1297  	s.testSysInfoSystemMode(c, "install")
  1298  }
  1299  
  1300  func (s *apiSuite) TestLoginUser(c *check.C) {
  1301  	d := s.daemon(c)
  1302  	state := d.overlord.State()
  1303  
  1304  	s.loginUserStoreMacaroon = "user-macaroon"
  1305  	s.loginUserDischarge = "the-discharge-macaroon-serialized-data"
  1306  	buf := bytes.NewBufferString(`{"username": "email@.com", "password": "password"}`)
  1307  	req, err := http.NewRequest("POST", "/v2/login", buf)
  1308  	c.Assert(err, check.IsNil)
  1309  
  1310  	rsp := loginUser(loginCmd, req, nil).(*resp)
  1311  
  1312  	state.Lock()
  1313  	user, err := auth.User(state, 1)
  1314  	state.Unlock()
  1315  	c.Check(err, check.IsNil)
  1316  
  1317  	expected := userResponseData{
  1318  		ID:    1,
  1319  		Email: "email@.com",
  1320  
  1321  		Macaroon:   user.Macaroon,
  1322  		Discharges: user.Discharges,
  1323  	}
  1324  
  1325  	c.Check(rsp.Status, check.Equals, 200)
  1326  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  1327  	c.Assert(rsp.Result, check.FitsTypeOf, expected)
  1328  	c.Check(rsp.Result, check.DeepEquals, expected)
  1329  
  1330  	c.Check(user.ID, check.Equals, 1)
  1331  	c.Check(user.Username, check.Equals, "")
  1332  	c.Check(user.Email, check.Equals, "email@.com")
  1333  	c.Check(user.Discharges, check.IsNil)
  1334  	c.Check(user.StoreMacaroon, check.Equals, s.loginUserStoreMacaroon)
  1335  	c.Check(user.StoreDischarges, check.DeepEquals, []string{"the-discharge-macaroon-serialized-data"})
  1336  	// snapd macaroon was setup too
  1337  	snapdMacaroon, err := auth.MacaroonDeserialize(user.Macaroon)
  1338  	c.Check(err, check.IsNil)
  1339  	c.Check(snapdMacaroon.Id(), check.Equals, "1")
  1340  	c.Check(snapdMacaroon.Location(), check.Equals, "snapd")
  1341  }
  1342  
  1343  func (s *apiSuite) TestLoginUserWithUsername(c *check.C) {
  1344  	d := s.daemon(c)
  1345  	state := d.overlord.State()
  1346  
  1347  	s.loginUserStoreMacaroon = "user-macaroon"
  1348  	s.loginUserDischarge = "the-discharge-macaroon-serialized-data"
  1349  	buf := bytes.NewBufferString(`{"username": "username", "email": "email@.com", "password": "password"}`)
  1350  	req, err := http.NewRequest("POST", "/v2/login", buf)
  1351  	c.Assert(err, check.IsNil)
  1352  
  1353  	rsp := loginUser(loginCmd, req, nil).(*resp)
  1354  
  1355  	state.Lock()
  1356  	user, err := auth.User(state, 1)
  1357  	state.Unlock()
  1358  	c.Check(err, check.IsNil)
  1359  
  1360  	expected := userResponseData{
  1361  		ID:         1,
  1362  		Username:   "username",
  1363  		Email:      "email@.com",
  1364  		Macaroon:   user.Macaroon,
  1365  		Discharges: user.Discharges,
  1366  	}
  1367  	c.Check(rsp.Status, check.Equals, 200)
  1368  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  1369  	c.Assert(rsp.Result, check.FitsTypeOf, expected)
  1370  	c.Check(rsp.Result, check.DeepEquals, expected)
  1371  
  1372  	c.Check(user.ID, check.Equals, 1)
  1373  	c.Check(user.Username, check.Equals, "username")
  1374  	c.Check(user.Email, check.Equals, "email@.com")
  1375  	c.Check(user.Discharges, check.IsNil)
  1376  	c.Check(user.StoreMacaroon, check.Equals, s.loginUserStoreMacaroon)
  1377  	c.Check(user.StoreDischarges, check.DeepEquals, []string{"the-discharge-macaroon-serialized-data"})
  1378  	// snapd macaroon was setup too
  1379  	snapdMacaroon, err := auth.MacaroonDeserialize(user.Macaroon)
  1380  	c.Check(err, check.IsNil)
  1381  	c.Check(snapdMacaroon.Id(), check.Equals, "1")
  1382  	c.Check(snapdMacaroon.Location(), check.Equals, "snapd")
  1383  }
  1384  
  1385  func (s *apiSuite) TestLoginUserNoEmailWithExistentLocalUser(c *check.C) {
  1386  	d := s.daemon(c)
  1387  	state := d.overlord.State()
  1388  
  1389  	// setup local-only user
  1390  	state.Lock()
  1391  	localUser, err := auth.NewUser(state, "username", "email@test.com", "", nil)
  1392  	state.Unlock()
  1393  	c.Assert(err, check.IsNil)
  1394  
  1395  	s.loginUserStoreMacaroon = "user-macaroon"
  1396  	s.loginUserDischarge = "the-discharge-macaroon-serialized-data"
  1397  	buf := bytes.NewBufferString(`{"username": "username", "email": "", "password": "password"}`)
  1398  	req, err := http.NewRequest("POST", "/v2/login", buf)
  1399  	c.Assert(err, check.IsNil)
  1400  	req.Header.Set("Authorization", fmt.Sprintf(`Macaroon root="%s"`, localUser.Macaroon))
  1401  
  1402  	rsp := loginUser(loginCmd, req, localUser).(*resp)
  1403  
  1404  	expected := userResponseData{
  1405  		ID:       1,
  1406  		Username: "username",
  1407  		Email:    "email@test.com",
  1408  
  1409  		Macaroon:   localUser.Macaroon,
  1410  		Discharges: localUser.Discharges,
  1411  	}
  1412  	c.Check(rsp.Status, check.Equals, 200)
  1413  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  1414  	c.Assert(rsp.Result, check.FitsTypeOf, expected)
  1415  	c.Check(rsp.Result, check.DeepEquals, expected)
  1416  
  1417  	state.Lock()
  1418  	user, err := auth.User(state, localUser.ID)
  1419  	state.Unlock()
  1420  	c.Check(err, check.IsNil)
  1421  	c.Check(user.Username, check.Equals, "username")
  1422  	c.Check(user.Email, check.Equals, localUser.Email)
  1423  	c.Check(user.Macaroon, check.Equals, localUser.Macaroon)
  1424  	c.Check(user.Discharges, check.IsNil)
  1425  	c.Check(user.StoreMacaroon, check.Equals, s.loginUserStoreMacaroon)
  1426  	c.Check(user.StoreDischarges, check.DeepEquals, []string{"the-discharge-macaroon-serialized-data"})
  1427  }
  1428  
  1429  func (s *apiSuite) TestLoginUserWithExistentLocalUser(c *check.C) {
  1430  	d := s.daemon(c)
  1431  	state := d.overlord.State()
  1432  
  1433  	// setup local-only user
  1434  	state.Lock()
  1435  	localUser, err := auth.NewUser(state, "username", "email@test.com", "", nil)
  1436  	state.Unlock()
  1437  	c.Assert(err, check.IsNil)
  1438  
  1439  	s.loginUserStoreMacaroon = "user-macaroon"
  1440  	s.loginUserDischarge = "the-discharge-macaroon-serialized-data"
  1441  	buf := bytes.NewBufferString(`{"username": "username", "email": "email@test.com", "password": "password"}`)
  1442  	req, err := http.NewRequest("POST", "/v2/login", buf)
  1443  	c.Assert(err, check.IsNil)
  1444  	req.Header.Set("Authorization", fmt.Sprintf(`Macaroon root="%s"`, localUser.Macaroon))
  1445  
  1446  	rsp := loginUser(loginCmd, req, localUser).(*resp)
  1447  
  1448  	expected := userResponseData{
  1449  		ID:       1,
  1450  		Username: "username",
  1451  		Email:    "email@test.com",
  1452  
  1453  		Macaroon:   localUser.Macaroon,
  1454  		Discharges: localUser.Discharges,
  1455  	}
  1456  	c.Check(rsp.Status, check.Equals, 200)
  1457  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  1458  	c.Assert(rsp.Result, check.FitsTypeOf, expected)
  1459  	c.Check(rsp.Result, check.DeepEquals, expected)
  1460  
  1461  	state.Lock()
  1462  	user, err := auth.User(state, localUser.ID)
  1463  	state.Unlock()
  1464  	c.Check(err, check.IsNil)
  1465  	c.Check(user.Username, check.Equals, "username")
  1466  	c.Check(user.Email, check.Equals, localUser.Email)
  1467  	c.Check(user.Macaroon, check.Equals, localUser.Macaroon)
  1468  	c.Check(user.Discharges, check.IsNil)
  1469  	c.Check(user.StoreMacaroon, check.Equals, s.loginUserStoreMacaroon)
  1470  	c.Check(user.StoreDischarges, check.DeepEquals, []string{"the-discharge-macaroon-serialized-data"})
  1471  }
  1472  
  1473  func (s *apiSuite) TestLoginUserNewEmailWithExistentLocalUser(c *check.C) {
  1474  	d := s.daemon(c)
  1475  	state := d.overlord.State()
  1476  
  1477  	// setup local-only user
  1478  	state.Lock()
  1479  	localUser, err := auth.NewUser(state, "username", "email@test.com", "", nil)
  1480  	state.Unlock()
  1481  	c.Assert(err, check.IsNil)
  1482  
  1483  	s.loginUserStoreMacaroon = "user-macaroon"
  1484  	s.loginUserDischarge = "the-discharge-macaroon-serialized-data"
  1485  	// same local user, but using a new SSO account
  1486  	buf := bytes.NewBufferString(`{"username": "username", "email": "new.email@test.com", "password": "password"}`)
  1487  	req, err := http.NewRequest("POST", "/v2/login", buf)
  1488  	c.Assert(err, check.IsNil)
  1489  	req.Header.Set("Authorization", fmt.Sprintf(`Macaroon root="%s"`, localUser.Macaroon))
  1490  
  1491  	rsp := loginUser(loginCmd, req, localUser).(*resp)
  1492  
  1493  	expected := userResponseData{
  1494  		ID:       1,
  1495  		Username: "username",
  1496  		Email:    "new.email@test.com",
  1497  
  1498  		Macaroon:   localUser.Macaroon,
  1499  		Discharges: localUser.Discharges,
  1500  	}
  1501  	c.Check(rsp.Status, check.Equals, 200)
  1502  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  1503  	c.Assert(rsp.Result, check.FitsTypeOf, expected)
  1504  	c.Check(rsp.Result, check.DeepEquals, expected)
  1505  
  1506  	state.Lock()
  1507  	user, err := auth.User(state, localUser.ID)
  1508  	state.Unlock()
  1509  	c.Check(err, check.IsNil)
  1510  	c.Check(user.Username, check.Equals, "username")
  1511  	c.Check(user.Email, check.Equals, expected.Email)
  1512  	c.Check(user.Macaroon, check.Equals, localUser.Macaroon)
  1513  	c.Check(user.Discharges, check.IsNil)
  1514  	c.Check(user.StoreMacaroon, check.Equals, s.loginUserStoreMacaroon)
  1515  	c.Check(user.StoreDischarges, check.DeepEquals, []string{"the-discharge-macaroon-serialized-data"})
  1516  }
  1517  
  1518  func (s *apiSuite) TestLogoutUser(c *check.C) {
  1519  	d := s.daemon(c)
  1520  	state := d.overlord.State()
  1521  	state.Lock()
  1522  	user, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"})
  1523  	state.Unlock()
  1524  	c.Assert(err, check.IsNil)
  1525  
  1526  	req, err := http.NewRequest("POST", "/v2/logout", nil)
  1527  	c.Assert(err, check.IsNil)
  1528  	req.Header.Set("Authorization", `Macaroon root="macaroon", discharge="discharge"`)
  1529  
  1530  	rsp := logoutUser(logoutCmd, req, user).(*resp)
  1531  	c.Check(rsp.Status, check.Equals, 200)
  1532  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  1533  
  1534  	state.Lock()
  1535  	_, err = auth.User(state, user.ID)
  1536  	state.Unlock()
  1537  	c.Check(err, check.Equals, auth.ErrInvalidUser)
  1538  }
  1539  
  1540  func (s *apiSuite) TestLoginUserBadRequest(c *check.C) {
  1541  	buf := bytes.NewBufferString(`hello`)
  1542  	req, err := http.NewRequest("POST", "/v2/login", buf)
  1543  	c.Assert(err, check.IsNil)
  1544  
  1545  	rsp := loginUser(snapCmd, req, nil).(*resp)
  1546  
  1547  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  1548  	c.Check(rsp.Status, check.Equals, 400)
  1549  	c.Check(rsp.Result, check.NotNil)
  1550  }
  1551  
  1552  func (s *apiSuite) TestLoginUserDeveloperAPIError(c *check.C) {
  1553  	s.daemon(c)
  1554  
  1555  	s.err = fmt.Errorf("error-from-login-user")
  1556  	buf := bytes.NewBufferString(`{"username": "email@.com", "password": "password"}`)
  1557  	req, err := http.NewRequest("POST", "/v2/login", buf)
  1558  	c.Assert(err, check.IsNil)
  1559  
  1560  	rsp := loginUser(snapCmd, req, nil).(*resp)
  1561  
  1562  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  1563  	c.Check(rsp.Status, check.Equals, 401)
  1564  	c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, "error-from-login-user")
  1565  }
  1566  
  1567  func (s *apiSuite) TestLoginUserTwoFactorRequiredError(c *check.C) {
  1568  	s.daemon(c)
  1569  
  1570  	s.err = store.ErrAuthenticationNeeds2fa
  1571  	buf := bytes.NewBufferString(`{"username": "email@.com", "password": "password"}`)
  1572  	req, err := http.NewRequest("POST", "/v2/login", buf)
  1573  	c.Assert(err, check.IsNil)
  1574  
  1575  	rsp := loginUser(snapCmd, req, nil).(*resp)
  1576  
  1577  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  1578  	c.Check(rsp.Status, check.Equals, 401)
  1579  	c.Check(rsp.Result.(*errorResult).Kind, check.Equals, client.ErrorKindTwoFactorRequired)
  1580  }
  1581  
  1582  func (s *apiSuite) TestLoginUserTwoFactorFailedError(c *check.C) {
  1583  	s.daemon(c)
  1584  
  1585  	s.err = store.Err2faFailed
  1586  	buf := bytes.NewBufferString(`{"username": "email@.com", "password": "password"}`)
  1587  	req, err := http.NewRequest("POST", "/v2/login", buf)
  1588  	c.Assert(err, check.IsNil)
  1589  
  1590  	rsp := loginUser(snapCmd, req, nil).(*resp)
  1591  
  1592  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  1593  	c.Check(rsp.Status, check.Equals, 401)
  1594  	c.Check(rsp.Result.(*errorResult).Kind, check.Equals, client.ErrorKindTwoFactorFailed)
  1595  }
  1596  
  1597  func (s *apiSuite) TestLoginUserInvalidCredentialsError(c *check.C) {
  1598  	s.daemon(c)
  1599  
  1600  	s.err = store.ErrInvalidCredentials
  1601  	buf := bytes.NewBufferString(`{"username": "email@.com", "password": "password"}`)
  1602  	req, err := http.NewRequest("POST", "/v2/login", buf)
  1603  	c.Assert(err, check.IsNil)
  1604  
  1605  	rsp := loginUser(snapCmd, req, nil).(*resp)
  1606  
  1607  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  1608  	c.Check(rsp.Status, check.Equals, 401)
  1609  	c.Check(rsp.Result.(*errorResult).Message, check.Equals, "invalid credentials")
  1610  }
  1611  
  1612  func (s *apiSuite) TestUserFromRequestNoHeader(c *check.C) {
  1613  	req, _ := http.NewRequest("GET", "http://example.com", nil)
  1614  
  1615  	state := snapCmd.d.overlord.State()
  1616  	state.Lock()
  1617  	user, err := UserFromRequest(state, req)
  1618  	state.Unlock()
  1619  
  1620  	c.Check(err, check.Equals, auth.ErrInvalidAuth)
  1621  	c.Check(user, check.IsNil)
  1622  }
  1623  
  1624  func (s *apiSuite) TestUserFromRequestHeaderNoMacaroons(c *check.C) {
  1625  	req, _ := http.NewRequest("GET", "http://example.com", nil)
  1626  	req.Header.Set("Authorization", "Invalid")
  1627  
  1628  	state := snapCmd.d.overlord.State()
  1629  	state.Lock()
  1630  	user, err := UserFromRequest(state, req)
  1631  	state.Unlock()
  1632  
  1633  	c.Check(err, check.ErrorMatches, "authorization header misses Macaroon prefix")
  1634  	c.Check(user, check.IsNil)
  1635  }
  1636  
  1637  func (s *apiSuite) TestUserFromRequestHeaderIncomplete(c *check.C) {
  1638  	req, _ := http.NewRequest("GET", "http://example.com", nil)
  1639  	req.Header.Set("Authorization", `Macaroon root=""`)
  1640  
  1641  	state := snapCmd.d.overlord.State()
  1642  	state.Lock()
  1643  	user, err := UserFromRequest(state, req)
  1644  	state.Unlock()
  1645  
  1646  	c.Check(err, check.ErrorMatches, "invalid authorization header")
  1647  	c.Check(user, check.IsNil)
  1648  }
  1649  
  1650  func (s *apiSuite) TestUserFromRequestHeaderCorrectMissingUser(c *check.C) {
  1651  	req, _ := http.NewRequest("GET", "http://example.com", nil)
  1652  	req.Header.Set("Authorization", `Macaroon root="macaroon", discharge="discharge"`)
  1653  
  1654  	state := snapCmd.d.overlord.State()
  1655  	state.Lock()
  1656  	user, err := UserFromRequest(state, req)
  1657  	state.Unlock()
  1658  
  1659  	c.Check(err, check.Equals, auth.ErrInvalidAuth)
  1660  	c.Check(user, check.IsNil)
  1661  }
  1662  
  1663  func (s *apiSuite) TestUserFromRequestHeaderValidUser(c *check.C) {
  1664  	state := snapCmd.d.overlord.State()
  1665  	state.Lock()
  1666  	expectedUser, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"})
  1667  	state.Unlock()
  1668  	c.Check(err, check.IsNil)
  1669  
  1670  	req, _ := http.NewRequest("GET", "http://example.com", nil)
  1671  	req.Header.Set("Authorization", fmt.Sprintf(`Macaroon root="%s"`, expectedUser.Macaroon))
  1672  
  1673  	state.Lock()
  1674  	user, err := UserFromRequest(state, req)
  1675  	state.Unlock()
  1676  
  1677  	c.Check(err, check.IsNil)
  1678  	c.Check(user, check.DeepEquals, expectedUser)
  1679  }
  1680  
  1681  func (s *apiSuite) TestSnapsInfoOnePerIntegration(c *check.C) {
  1682  	s.checkSnapInfoOnePerIntegration(c, false, nil)
  1683  }
  1684  
  1685  func (s *apiSuite) TestSnapsInfoOnePerIntegrationSome(c *check.C) {
  1686  	s.checkSnapInfoOnePerIntegration(c, false, []string{"foo", "baz"})
  1687  }
  1688  
  1689  func (s *apiSuite) TestSnapsInfoOnePerIntegrationAll(c *check.C) {
  1690  	s.checkSnapInfoOnePerIntegration(c, true, nil)
  1691  }
  1692  
  1693  func (s *apiSuite) TestSnapsInfoOnePerIntegrationAllSome(c *check.C) {
  1694  	s.checkSnapInfoOnePerIntegration(c, true, []string{"foo", "baz"})
  1695  }
  1696  
  1697  func (s *apiSuite) checkSnapInfoOnePerIntegration(c *check.C, all bool, names []string) {
  1698  	d := s.daemon(c)
  1699  
  1700  	type tsnap struct {
  1701  		name   string
  1702  		dev    string
  1703  		ver    string
  1704  		rev    int
  1705  		active bool
  1706  
  1707  		wanted bool
  1708  	}
  1709  
  1710  	tsnaps := []tsnap{
  1711  		{name: "foo", dev: "bar", ver: "v0.9", rev: 1},
  1712  		{name: "foo", dev: "bar", ver: "v1", rev: 5, active: true},
  1713  		{name: "bar", dev: "baz", ver: "v2", rev: 10, active: true},
  1714  		{name: "baz", dev: "qux", ver: "v3", rev: 15, active: true},
  1715  		{name: "qux", dev: "mip", ver: "v4", rev: 20, active: true},
  1716  	}
  1717  	numExpected := 0
  1718  
  1719  	for _, snp := range tsnaps {
  1720  		if all || snp.active {
  1721  			if len(names) == 0 {
  1722  				numExpected++
  1723  				snp.wanted = true
  1724  			}
  1725  			for _, n := range names {
  1726  				if snp.name == n {
  1727  					numExpected++
  1728  					snp.wanted = true
  1729  					break
  1730  				}
  1731  			}
  1732  		}
  1733  		s.mkInstalledInState(c, d, snp.name, snp.dev, snp.ver, snap.R(snp.rev), snp.active, "")
  1734  	}
  1735  
  1736  	q := url.Values{}
  1737  	if all {
  1738  		q.Set("select", "all")
  1739  	}
  1740  	if len(names) > 0 {
  1741  		q.Set("snaps", strings.Join(names, ","))
  1742  	}
  1743  	req, err := http.NewRequest("GET", "/v2/snaps?"+q.Encode(), nil)
  1744  	c.Assert(err, check.IsNil)
  1745  
  1746  	rsp, ok := getSnapsInfo(snapsCmd, req, nil).(*resp)
  1747  	c.Assert(ok, check.Equals, true)
  1748  
  1749  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  1750  	c.Check(rsp.Status, check.Equals, 200)
  1751  	c.Check(rsp.Result, check.NotNil)
  1752  
  1753  	snaps := snapList(rsp.Result)
  1754  	c.Check(snaps, check.HasLen, numExpected)
  1755  
  1756  	for _, s := range tsnaps {
  1757  		if !((all || s.active) && s.wanted) {
  1758  			continue
  1759  		}
  1760  		var got map[string]interface{}
  1761  		for _, got = range snaps {
  1762  			if got["name"].(string) == s.name && got["revision"].(string) == snap.R(s.rev).String() {
  1763  				break
  1764  			}
  1765  		}
  1766  		c.Check(got["name"], check.Equals, s.name)
  1767  		c.Check(got["version"], check.Equals, s.ver)
  1768  		c.Check(got["revision"], check.Equals, snap.R(s.rev).String())
  1769  		c.Check(got["developer"], check.Equals, s.dev)
  1770  		c.Check(got["confinement"], check.Equals, "strict")
  1771  	}
  1772  }
  1773  
  1774  func (s *apiSuite) TestSnapsInfoOnlyLocal(c *check.C) {
  1775  	d := s.daemon(c)
  1776  
  1777  	s.rsnaps = []*snap.Info{{
  1778  		SideInfo: snap.SideInfo{
  1779  			RealName: "store",
  1780  		},
  1781  		Publisher: snap.StoreAccount{
  1782  			ID:          "foo-id",
  1783  			Username:    "foo",
  1784  			DisplayName: "Foo",
  1785  			Validation:  "unproven",
  1786  		},
  1787  	}}
  1788  	s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "")
  1789  	st := s.d.overlord.State()
  1790  	st.Lock()
  1791  	st.Set("health", map[string]healthstate.HealthState{
  1792  		"local": {Status: healthstate.OkayStatus},
  1793  	})
  1794  	st.Unlock()
  1795  
  1796  	req, err := http.NewRequest("GET", "/v2/snaps?sources=local", nil)
  1797  	c.Assert(err, check.IsNil)
  1798  
  1799  	rsp := getSnapsInfo(snapsCmd, req, nil).(*resp)
  1800  
  1801  	c.Assert(rsp.Sources, check.DeepEquals, []string{"local"})
  1802  
  1803  	snaps := snapList(rsp.Result)
  1804  	c.Assert(snaps, check.HasLen, 1)
  1805  	c.Assert(snaps[0]["name"], check.Equals, "local")
  1806  	c.Check(snaps[0]["health"], check.DeepEquals, map[string]interface{}{
  1807  		"status":    "okay",
  1808  		"revision":  "unset",
  1809  		"timestamp": "0001-01-01T00:00:00Z",
  1810  	})
  1811  }
  1812  
  1813  func (s *apiSuite) TestSnapsInfoAllMixedPublishers(c *check.C) {
  1814  	d := s.daemon(c)
  1815  
  1816  	// the first 'local' is from a 'local' snap
  1817  	s.mkInstalledInState(c, d, "local", "", "v1", snap.R(-1), false, "")
  1818  	s.mkInstalledInState(c, d, "local", "foo", "v2", snap.R(1), false, "")
  1819  	s.mkInstalledInState(c, d, "local", "foo", "v3", snap.R(2), true, "")
  1820  
  1821  	req, err := http.NewRequest("GET", "/v2/snaps?select=all", nil)
  1822  	c.Assert(err, check.IsNil)
  1823  	rsp := getSnapsInfo(snapsCmd, req, nil).(*resp)
  1824  	c.Assert(rsp.Type, check.Equals, ResponseTypeSync)
  1825  
  1826  	snaps := snapList(rsp.Result)
  1827  	c.Assert(snaps, check.HasLen, 3)
  1828  
  1829  	publisher := map[string]interface{}{
  1830  		"id":           "foo-id",
  1831  		"username":     "foo",
  1832  		"display-name": "Foo",
  1833  		"validation":   "unproven",
  1834  	}
  1835  
  1836  	c.Check(snaps[0]["publisher"], check.IsNil)
  1837  	c.Check(snaps[1]["publisher"], check.DeepEquals, publisher)
  1838  	c.Check(snaps[2]["publisher"], check.DeepEquals, publisher)
  1839  }
  1840  
  1841  func (s *apiSuite) TestSnapsInfoAll(c *check.C) {
  1842  	d := s.daemon(c)
  1843  
  1844  	s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(1), false, "")
  1845  	s.mkInstalledInState(c, d, "local", "foo", "v2", snap.R(2), false, "")
  1846  	s.mkInstalledInState(c, d, "local", "foo", "v3", snap.R(3), true, "")
  1847  	s.mkInstalledInState(c, d, "local_foo", "foo", "v4", snap.R(4), true, "")
  1848  	brokenInfo := s.mkInstalledInState(c, d, "local_bar", "foo", "v5", snap.R(5), true, "")
  1849  	// make sure local_bar is 'broken'
  1850  	err := os.Remove(filepath.Join(brokenInfo.MountDir(), "meta", "snap.yaml"))
  1851  	c.Assert(err, check.IsNil)
  1852  
  1853  	expectedHappy := map[string]bool{
  1854  		"local":     true,
  1855  		"local_foo": true,
  1856  		"local_bar": true,
  1857  	}
  1858  	for _, t := range []struct {
  1859  		q        string
  1860  		numSnaps int
  1861  		typ      ResponseType
  1862  	}{
  1863  		{"?select=enabled", 3, "sync"},
  1864  		{`?select=`, 3, "sync"},
  1865  		{"", 3, "sync"},
  1866  		{"?select=all", 5, "sync"},
  1867  		{"?select=invalid-field", 0, "error"},
  1868  	} {
  1869  		c.Logf("trying: %v", t)
  1870  		req, err := http.NewRequest("GET", fmt.Sprintf("/v2/snaps%s", t.q), nil)
  1871  		c.Assert(err, check.IsNil)
  1872  		rsp := getSnapsInfo(snapsCmd, req, nil).(*resp)
  1873  		c.Assert(rsp.Type, check.Equals, t.typ)
  1874  
  1875  		if rsp.Type != "error" {
  1876  			snaps := snapList(rsp.Result)
  1877  			c.Assert(snaps, check.HasLen, t.numSnaps)
  1878  			seen := map[string]bool{}
  1879  			for _, s := range snaps {
  1880  				seen[s["name"].(string)] = true
  1881  			}
  1882  			c.Assert(seen, check.DeepEquals, expectedHappy)
  1883  		}
  1884  	}
  1885  }
  1886  
  1887  func (s *apiSuite) TestFind(c *check.C) {
  1888  	s.daemon(c)
  1889  
  1890  	s.suggestedCurrency = "EUR"
  1891  
  1892  	s.rsnaps = []*snap.Info{{
  1893  		SideInfo: snap.SideInfo{
  1894  			RealName: "store",
  1895  		},
  1896  		Publisher: snap.StoreAccount{
  1897  			ID:          "foo-id",
  1898  			Username:    "foo",
  1899  			DisplayName: "Foo",
  1900  			Validation:  "unproven",
  1901  		},
  1902  	}}
  1903  
  1904  	req, err := http.NewRequest("GET", "/v2/find?q=hi", nil)
  1905  	c.Assert(err, check.IsNil)
  1906  
  1907  	rsp := searchStore(findCmd, req, nil).(*resp)
  1908  
  1909  	snaps := snapList(rsp.Result)
  1910  	c.Assert(snaps, check.HasLen, 1)
  1911  	c.Assert(snaps[0]["name"], check.Equals, "store")
  1912  	c.Check(snaps[0]["prices"], check.IsNil)
  1913  	c.Check(snaps[0]["channels"], check.IsNil)
  1914  
  1915  	c.Check(rsp.SuggestedCurrency, check.Equals, "EUR")
  1916  
  1917  	c.Check(s.storeSearch, check.DeepEquals, store.Search{Query: "hi"})
  1918  	c.Check(s.currentSnaps, check.HasLen, 0)
  1919  	c.Check(s.actions, check.HasLen, 0)
  1920  }
  1921  
  1922  func (s *apiSuite) TestFindRefreshes(c *check.C) {
  1923  	snapstateRefreshCandidates = snapstate.RefreshCandidates
  1924  	s.daemon(c)
  1925  
  1926  	s.rsnaps = []*snap.Info{{
  1927  		SideInfo: snap.SideInfo{
  1928  			RealName: "store",
  1929  		},
  1930  		Publisher: snap.StoreAccount{
  1931  			ID:          "foo-id",
  1932  			Username:    "foo",
  1933  			DisplayName: "Foo",
  1934  			Validation:  "unproven",
  1935  		},
  1936  	}}
  1937  	s.mockSnap(c, "name: store\nversion: 1.0")
  1938  
  1939  	req, err := http.NewRequest("GET", "/v2/find?select=refresh", nil)
  1940  	c.Assert(err, check.IsNil)
  1941  
  1942  	rsp := searchStore(findCmd, req, nil).(*resp)
  1943  
  1944  	snaps := snapList(rsp.Result)
  1945  	c.Assert(snaps, check.HasLen, 1)
  1946  	c.Assert(snaps[0]["name"], check.Equals, "store")
  1947  	c.Check(s.currentSnaps, check.HasLen, 1)
  1948  	c.Check(s.actions, check.HasLen, 1)
  1949  }
  1950  
  1951  func (s *apiSuite) TestFindRefreshSideloaded(c *check.C) {
  1952  	snapstateRefreshCandidates = snapstate.RefreshCandidates
  1953  	s.daemon(c)
  1954  
  1955  	s.rsnaps = []*snap.Info{{
  1956  		SideInfo: snap.SideInfo{
  1957  			RealName: "store",
  1958  		},
  1959  		Publisher: snap.StoreAccount{
  1960  			ID:          "foo-id",
  1961  			Username:    "foo",
  1962  			DisplayName: "Foo",
  1963  			Validation:  "unproven",
  1964  		},
  1965  	}}
  1966  
  1967  	s.mockSnap(c, "name: store\nversion: 1.0")
  1968  
  1969  	var snapst snapstate.SnapState
  1970  	st := s.d.overlord.State()
  1971  	st.Lock()
  1972  	err := snapstate.Get(st, "store", &snapst)
  1973  	st.Unlock()
  1974  	c.Assert(err, check.IsNil)
  1975  	c.Assert(snapst.Sequence, check.HasLen, 1)
  1976  
  1977  	// clear the snapid
  1978  	snapst.Sequence[0].SnapID = ""
  1979  	st.Lock()
  1980  	snapstate.Set(st, "store", &snapst)
  1981  	st.Unlock()
  1982  
  1983  	req, err := http.NewRequest("GET", "/v2/find?select=refresh", nil)
  1984  	c.Assert(err, check.IsNil)
  1985  
  1986  	rsp := searchStore(findCmd, req, nil).(*resp)
  1987  
  1988  	snaps := snapList(rsp.Result)
  1989  	c.Assert(snaps, check.HasLen, 0)
  1990  	c.Check(s.currentSnaps, check.HasLen, 0)
  1991  	c.Check(s.actions, check.HasLen, 0)
  1992  }
  1993  
  1994  func (s *apiSuite) TestFindPrivate(c *check.C) {
  1995  	s.daemon(c)
  1996  
  1997  	s.rsnaps = []*snap.Info{}
  1998  
  1999  	req, err := http.NewRequest("GET", "/v2/find?q=foo&select=private", nil)
  2000  	c.Assert(err, check.IsNil)
  2001  
  2002  	_ = searchStore(findCmd, req, nil).(*resp)
  2003  
  2004  	c.Check(s.storeSearch, check.DeepEquals, store.Search{
  2005  		Query:   "foo",
  2006  		Private: true,
  2007  	})
  2008  }
  2009  
  2010  func (s *apiSuite) TestFindUserAgentContextCreated(c *check.C) {
  2011  	s.daemon(c)
  2012  
  2013  	req, err := http.NewRequest("GET", "/v2/find", nil)
  2014  	c.Assert(err, check.IsNil)
  2015  	req.Header.Add("User-Agent", "some-agent/1.0")
  2016  
  2017  	_ = searchStore(findCmd, req, nil).(*resp)
  2018  
  2019  	c.Check(store.ClientUserAgent(s.ctx), check.Equals, "some-agent/1.0")
  2020  }
  2021  
  2022  func (s *apiSuite) TestFindOneUserAgentContextCreated(c *check.C) {
  2023  	s.daemon(c)
  2024  
  2025  	s.rsnaps = []*snap.Info{{
  2026  		SnapType: snap.TypeApp,
  2027  		Version:  "v2",
  2028  		SideInfo: snap.SideInfo{
  2029  			RealName: "banana",
  2030  		},
  2031  		Publisher: snap.StoreAccount{
  2032  			ID:          "foo-id",
  2033  			Username:    "foo",
  2034  			DisplayName: "Foo",
  2035  			Validation:  "unproven",
  2036  		},
  2037  	}}
  2038  	req, err := http.NewRequest("GET", "/v2/find?name=foo", nil)
  2039  	c.Assert(err, check.IsNil)
  2040  	req.Header.Add("User-Agent", "some-agent/1.0")
  2041  
  2042  	_ = searchStore(findCmd, req, nil).(*resp)
  2043  
  2044  	c.Check(store.ClientUserAgent(s.ctx), check.Equals, "some-agent/1.0")
  2045  }
  2046  
  2047  func (s *apiSuite) TestFindPrefix(c *check.C) {
  2048  	s.daemon(c)
  2049  
  2050  	s.rsnaps = []*snap.Info{}
  2051  
  2052  	req, err := http.NewRequest("GET", "/v2/find?name=foo*", nil)
  2053  	c.Assert(err, check.IsNil)
  2054  
  2055  	_ = searchStore(findCmd, req, nil).(*resp)
  2056  
  2057  	c.Check(s.storeSearch, check.DeepEquals, store.Search{Query: "foo", Prefix: true})
  2058  }
  2059  
  2060  func (s *apiSuite) TestFindSection(c *check.C) {
  2061  	s.daemon(c)
  2062  
  2063  	s.rsnaps = []*snap.Info{}
  2064  
  2065  	req, err := http.NewRequest("GET", "/v2/find?q=foo&section=bar", nil)
  2066  	c.Assert(err, check.IsNil)
  2067  
  2068  	_ = searchStore(findCmd, req, nil).(*resp)
  2069  
  2070  	c.Check(s.storeSearch, check.DeepEquals, store.Search{
  2071  		Query:    "foo",
  2072  		Category: "bar",
  2073  	})
  2074  }
  2075  
  2076  func (s *apiSuite) TestFindScope(c *check.C) {
  2077  	s.daemon(c)
  2078  
  2079  	s.rsnaps = []*snap.Info{}
  2080  
  2081  	req, err := http.NewRequest("GET", "/v2/find?q=foo&scope=creep", nil)
  2082  	c.Assert(err, check.IsNil)
  2083  
  2084  	_ = searchStore(findCmd, req, nil).(*resp)
  2085  
  2086  	c.Check(s.storeSearch, check.DeepEquals, store.Search{
  2087  		Query: "foo",
  2088  		Scope: "creep",
  2089  	})
  2090  }
  2091  
  2092  func (s *apiSuite) TestFindCommonID(c *check.C) {
  2093  	s.daemon(c)
  2094  
  2095  	s.rsnaps = []*snap.Info{{
  2096  		SideInfo: snap.SideInfo{
  2097  			RealName: "store",
  2098  		},
  2099  		Publisher: snap.StoreAccount{
  2100  			ID:          "foo-id",
  2101  			Username:    "foo",
  2102  			DisplayName: "Foo",
  2103  			Validation:  "unproven",
  2104  		},
  2105  		CommonIDs: []string{"org.foo"},
  2106  	}}
  2107  	s.mockSnap(c, "name: store\nversion: 1.0")
  2108  
  2109  	req, err := http.NewRequest("GET", "/v2/find?name=foo", nil)
  2110  	c.Assert(err, check.IsNil)
  2111  
  2112  	rsp := searchStore(findCmd, req, nil).(*resp)
  2113  
  2114  	snaps := snapList(rsp.Result)
  2115  	c.Assert(snaps, check.HasLen, 1)
  2116  	c.Check(snaps[0]["common-ids"], check.DeepEquals, []interface{}{"org.foo"})
  2117  }
  2118  
  2119  func (s *apiSuite) TestFindByCommonID(c *check.C) {
  2120  	s.daemon(c)
  2121  
  2122  	s.rsnaps = []*snap.Info{{
  2123  		SideInfo: snap.SideInfo{
  2124  			RealName: "store",
  2125  		},
  2126  		Publisher: snap.StoreAccount{
  2127  			ID:          "foo-id",
  2128  			Username:    "foo",
  2129  			DisplayName: "Foo",
  2130  			Validation:  "unproven",
  2131  		},
  2132  		CommonIDs: []string{"org.foo"},
  2133  	}}
  2134  	s.mockSnap(c, "name: store\nversion: 1.0")
  2135  
  2136  	req, err := http.NewRequest("GET", "/v2/find?common-id=org.foo", nil)
  2137  	c.Assert(err, check.IsNil)
  2138  
  2139  	rsp := searchStore(findCmd, req, nil).(*resp)
  2140  
  2141  	snaps := snapList(rsp.Result)
  2142  	c.Assert(snaps, check.HasLen, 1)
  2143  	c.Check(s.storeSearch, check.DeepEquals, store.Search{CommonID: "org.foo"})
  2144  }
  2145  
  2146  func (s *apiSuite) TestFindOne(c *check.C) {
  2147  	s.daemon(c)
  2148  
  2149  	s.rsnaps = []*snap.Info{{
  2150  		SideInfo: snap.SideInfo{
  2151  			RealName: "store",
  2152  		},
  2153  		Base: "base0",
  2154  		Publisher: snap.StoreAccount{
  2155  			ID:          "foo-id",
  2156  			Username:    "foo",
  2157  			DisplayName: "Foo",
  2158  			Validation:  "verified",
  2159  		},
  2160  		Channels: map[string]*snap.ChannelSnapInfo{
  2161  			"stable": {
  2162  				Revision: snap.R(42),
  2163  			},
  2164  		},
  2165  	}}
  2166  	s.mockSnap(c, "name: store\nversion: 1.0")
  2167  
  2168  	req, err := http.NewRequest("GET", "/v2/find?name=foo", nil)
  2169  	c.Assert(err, check.IsNil)
  2170  
  2171  	rsp := searchStore(findCmd, req, nil).(*resp)
  2172  
  2173  	c.Check(s.storeSearch, check.DeepEquals, store.Search{})
  2174  
  2175  	snaps := snapList(rsp.Result)
  2176  	c.Assert(snaps, check.HasLen, 1)
  2177  	c.Check(snaps[0]["name"], check.Equals, "store")
  2178  	c.Check(snaps[0]["base"], check.Equals, "base0")
  2179  	c.Check(snaps[0]["publisher"], check.DeepEquals, map[string]interface{}{
  2180  		"id":           "foo-id",
  2181  		"username":     "foo",
  2182  		"display-name": "Foo",
  2183  		"validation":   "verified",
  2184  	})
  2185  	m := snaps[0]["channels"].(map[string]interface{})["stable"].(map[string]interface{})
  2186  
  2187  	c.Check(m["revision"], check.Equals, "42")
  2188  }
  2189  
  2190  func (s *apiSuite) TestFindOneNotFound(c *check.C) {
  2191  	s.daemon(c)
  2192  
  2193  	s.err = store.ErrSnapNotFound
  2194  	s.mockSnap(c, "name: store\nversion: 1.0")
  2195  
  2196  	req, err := http.NewRequest("GET", "/v2/find?name=foo", nil)
  2197  	c.Assert(err, check.IsNil)
  2198  
  2199  	rsp := searchStore(findCmd, req, nil).(*resp)
  2200  
  2201  	c.Check(s.storeSearch, check.DeepEquals, store.Search{})
  2202  	c.Check(rsp.Status, check.Equals, 404)
  2203  }
  2204  
  2205  func (s *apiSuite) TestFindRefreshNotOther(c *check.C) {
  2206  	for _, other := range []string{"name", "q", "common-id"} {
  2207  		req, err := http.NewRequest("GET", "/v2/find?select=refresh&"+other+"=foo*", nil)
  2208  		c.Assert(err, check.IsNil)
  2209  
  2210  		rsp := searchStore(findCmd, req, nil).(*resp)
  2211  		c.Check(rsp.Type, check.Equals, ResponseTypeError)
  2212  		c.Check(rsp.Status, check.Equals, 400)
  2213  		c.Check(rsp.Result.(*errorResult).Message, check.Equals, "cannot use '"+other+"' with 'select=refresh'")
  2214  	}
  2215  }
  2216  
  2217  func (s *apiSuite) TestFindNotTogether(c *check.C) {
  2218  	queries := map[string]string{"q": "foo", "name": "foo*", "common-id": "foo"}
  2219  	for ki, vi := range queries {
  2220  		for kj, vj := range queries {
  2221  			if ki == kj {
  2222  				continue
  2223  			}
  2224  
  2225  			req, err := http.NewRequest("GET", fmt.Sprintf("/v2/find?%s=%s&%s=%s", ki, vi, kj, vj), nil)
  2226  			c.Assert(err, check.IsNil)
  2227  
  2228  			rsp := searchStore(findCmd, req, nil).(*resp)
  2229  			c.Check(rsp.Type, check.Equals, ResponseTypeError)
  2230  			c.Check(rsp.Status, check.Equals, 400)
  2231  			exp1 := "cannot use '" + ki + "' and '" + kj + "' together"
  2232  			exp2 := "cannot use '" + kj + "' and '" + ki + "' together"
  2233  			c.Check(rsp.Result.(*errorResult).Message, check.Matches, exp1+"|"+exp2)
  2234  		}
  2235  	}
  2236  }
  2237  
  2238  func (s *apiSuite) TestFindBadQueryReturnsCorrectErrorKind(c *check.C) {
  2239  	s.daemon(c)
  2240  
  2241  	s.err = store.ErrBadQuery
  2242  	req, err := http.NewRequest("GET", "/v2/find?q=return-bad-query-please", nil)
  2243  	c.Assert(err, check.IsNil)
  2244  
  2245  	rsp := searchStore(findCmd, req, nil).(*resp)
  2246  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  2247  	c.Check(rsp.Status, check.Equals, 400)
  2248  	c.Check(rsp.Result.(*errorResult).Message, check.Matches, "bad query")
  2249  	c.Check(rsp.Result.(*errorResult).Kind, check.Equals, client.ErrorKindBadQuery)
  2250  }
  2251  
  2252  func (s *apiSuite) TestFindPriced(c *check.C) {
  2253  	s.daemon(c)
  2254  
  2255  	s.suggestedCurrency = "GBP"
  2256  
  2257  	s.rsnaps = []*snap.Info{{
  2258  		SnapType: snap.TypeApp,
  2259  		Version:  "v2",
  2260  		Prices: map[string]float64{
  2261  			"GBP": 1.23,
  2262  			"EUR": 2.34,
  2263  		},
  2264  		MustBuy: true,
  2265  		SideInfo: snap.SideInfo{
  2266  			RealName: "banana",
  2267  		},
  2268  		Publisher: snap.StoreAccount{
  2269  			ID:          "foo-id",
  2270  			Username:    "foo",
  2271  			DisplayName: "Foo",
  2272  			Validation:  "unproven",
  2273  		},
  2274  	}}
  2275  
  2276  	req, err := http.NewRequest("GET", "/v2/find?q=banana&channel=stable", nil)
  2277  	c.Assert(err, check.IsNil)
  2278  	rsp, ok := searchStore(findCmd, req, nil).(*resp)
  2279  	c.Assert(ok, check.Equals, true)
  2280  
  2281  	snaps := snapList(rsp.Result)
  2282  	c.Assert(snaps, check.HasLen, 1)
  2283  
  2284  	snap := snaps[0]
  2285  	c.Check(snap["name"], check.Equals, "banana")
  2286  	c.Check(snap["prices"], check.DeepEquals, map[string]interface{}{
  2287  		"EUR": 2.34,
  2288  		"GBP": 1.23,
  2289  	})
  2290  	c.Check(snap["status"], check.Equals, "priced")
  2291  
  2292  	c.Check(rsp.SuggestedCurrency, check.Equals, "GBP")
  2293  }
  2294  
  2295  func (s *apiSuite) TestFindScreenshotted(c *check.C) {
  2296  	s.daemon(c)
  2297  
  2298  	s.rsnaps = []*snap.Info{{
  2299  		SnapType: snap.TypeApp,
  2300  		Version:  "v2",
  2301  		Media: []snap.MediaInfo{
  2302  			{
  2303  				Type:   "screenshot",
  2304  				URL:    "http://example.com/screenshot.png",
  2305  				Width:  800,
  2306  				Height: 1280,
  2307  			},
  2308  			{
  2309  				Type: "screenshot",
  2310  				URL:  "http://example.com/screenshot2.png",
  2311  			},
  2312  		},
  2313  		MustBuy: true,
  2314  		SideInfo: snap.SideInfo{
  2315  			RealName: "test-screenshot",
  2316  		},
  2317  		Publisher: snap.StoreAccount{
  2318  			ID:          "foo-id",
  2319  			Username:    "foo",
  2320  			DisplayName: "Foo",
  2321  			Validation:  "unproven",
  2322  		},
  2323  	}}
  2324  
  2325  	req, err := http.NewRequest("GET", "/v2/find?q=test-screenshot", nil)
  2326  	c.Assert(err, check.IsNil)
  2327  	rsp, ok := searchStore(findCmd, req, nil).(*resp)
  2328  	c.Assert(ok, check.Equals, true)
  2329  
  2330  	snaps := snapList(rsp.Result)
  2331  	c.Assert(snaps, check.HasLen, 1)
  2332  
  2333  	c.Check(snaps[0]["name"], check.Equals, "test-screenshot")
  2334  	c.Check(snaps[0]["media"], check.DeepEquals, []interface{}{
  2335  		map[string]interface{}{
  2336  			"type":   "screenshot",
  2337  			"url":    "http://example.com/screenshot.png",
  2338  			"width":  float64(800),
  2339  			"height": float64(1280),
  2340  		},
  2341  		map[string]interface{}{
  2342  			"type": "screenshot",
  2343  			"url":  "http://example.com/screenshot2.png",
  2344  		},
  2345  	})
  2346  }
  2347  
  2348  func (s *apiSuite) TestSnapsInfoOnlyStore(c *check.C) {
  2349  	d := s.daemon(c)
  2350  
  2351  	s.suggestedCurrency = "EUR"
  2352  
  2353  	s.rsnaps = []*snap.Info{{
  2354  		SideInfo: snap.SideInfo{
  2355  			RealName: "store",
  2356  		},
  2357  		Publisher: snap.StoreAccount{
  2358  			ID:          "foo-id",
  2359  			Username:    "foo",
  2360  			DisplayName: "Foo",
  2361  			Validation:  "unproven",
  2362  		},
  2363  	}}
  2364  	s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "")
  2365  
  2366  	req, err := http.NewRequest("GET", "/v2/snaps?sources=store", nil)
  2367  	c.Assert(err, check.IsNil)
  2368  
  2369  	rsp := getSnapsInfo(snapsCmd, req, nil).(*resp)
  2370  
  2371  	c.Assert(rsp.Sources, check.DeepEquals, []string{"store"})
  2372  
  2373  	snaps := snapList(rsp.Result)
  2374  	c.Assert(snaps, check.HasLen, 1)
  2375  	c.Assert(snaps[0]["name"], check.Equals, "store")
  2376  	c.Check(snaps[0]["prices"], check.IsNil)
  2377  
  2378  	c.Check(rsp.SuggestedCurrency, check.Equals, "EUR")
  2379  }
  2380  
  2381  func (s *apiSuite) TestSnapsStoreConfinement(c *check.C) {
  2382  	s.daemon(c)
  2383  
  2384  	s.rsnaps = []*snap.Info{
  2385  		{
  2386  			// no explicit confinement in this one
  2387  			SideInfo: snap.SideInfo{
  2388  				RealName: "foo",
  2389  			},
  2390  		},
  2391  		{
  2392  			Confinement: snap.StrictConfinement,
  2393  			SideInfo: snap.SideInfo{
  2394  				RealName: "bar",
  2395  			},
  2396  		},
  2397  		{
  2398  			Confinement: snap.DevModeConfinement,
  2399  			SideInfo: snap.SideInfo{
  2400  				RealName: "baz",
  2401  			},
  2402  		},
  2403  	}
  2404  
  2405  	req, err := http.NewRequest("GET", "/v2/find", nil)
  2406  	c.Assert(err, check.IsNil)
  2407  
  2408  	rsp := searchStore(findCmd, req, nil).(*resp)
  2409  
  2410  	snaps := snapList(rsp.Result)
  2411  	c.Assert(snaps, check.HasLen, 3)
  2412  
  2413  	for i, ss := range [][2]string{
  2414  		{"foo", string(snap.StrictConfinement)},
  2415  		{"bar", string(snap.StrictConfinement)},
  2416  		{"baz", string(snap.DevModeConfinement)},
  2417  	} {
  2418  		name, mode := ss[0], ss[1]
  2419  		c.Check(snaps[i]["name"], check.Equals, name, check.Commentf(name))
  2420  		c.Check(snaps[i]["confinement"], check.Equals, mode, check.Commentf(name))
  2421  	}
  2422  }
  2423  
  2424  func (s *apiSuite) TestSnapsInfoStoreWithAuth(c *check.C) {
  2425  	s.daemon(c)
  2426  
  2427  	state := snapCmd.d.overlord.State()
  2428  	state.Lock()
  2429  	user, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"})
  2430  	state.Unlock()
  2431  	c.Check(err, check.IsNil)
  2432  
  2433  	req, err := http.NewRequest("GET", "/v2/snaps?sources=store", nil)
  2434  	c.Assert(err, check.IsNil)
  2435  
  2436  	c.Assert(s.user, check.IsNil)
  2437  
  2438  	_ = getSnapsInfo(snapsCmd, req, user).(*resp)
  2439  
  2440  	// ensure user was set
  2441  	c.Assert(s.user, check.DeepEquals, user)
  2442  }
  2443  
  2444  func (s *apiSuite) TestSnapsInfoLocalAndStore(c *check.C) {
  2445  	d := s.daemon(c)
  2446  
  2447  	s.rsnaps = []*snap.Info{{
  2448  		Version: "v42",
  2449  		SideInfo: snap.SideInfo{
  2450  			RealName: "remote",
  2451  		},
  2452  		Publisher: snap.StoreAccount{
  2453  			ID:          "foo-id",
  2454  			Username:    "foo",
  2455  			DisplayName: "Foo",
  2456  			Validation:  "unproven",
  2457  		},
  2458  	}}
  2459  	s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "")
  2460  
  2461  	req, err := http.NewRequest("GET", "/v2/snaps?sources=local,store", nil)
  2462  	c.Assert(err, check.IsNil)
  2463  
  2464  	rsp := getSnapsInfo(snapsCmd, req, nil).(*resp)
  2465  
  2466  	// presence of 'store' in sources bounces request over to /find
  2467  	c.Assert(rsp.Sources, check.DeepEquals, []string{"store"})
  2468  
  2469  	snaps := snapList(rsp.Result)
  2470  	c.Assert(snaps, check.HasLen, 1)
  2471  	c.Check(snaps[0]["version"], check.Equals, "v42")
  2472  
  2473  	// as does a 'q'
  2474  	req, err = http.NewRequest("GET", "/v2/snaps?q=what", nil)
  2475  	c.Assert(err, check.IsNil)
  2476  	rsp = getSnapsInfo(snapsCmd, req, nil).(*resp)
  2477  	snaps = snapList(rsp.Result)
  2478  	c.Assert(snaps, check.HasLen, 1)
  2479  	c.Check(snaps[0]["version"], check.Equals, "v42")
  2480  
  2481  	// otherwise, local only
  2482  	req, err = http.NewRequest("GET", "/v2/snaps", nil)
  2483  	c.Assert(err, check.IsNil)
  2484  	rsp = getSnapsInfo(snapsCmd, req, nil).(*resp)
  2485  	snaps = snapList(rsp.Result)
  2486  	c.Assert(snaps, check.HasLen, 1)
  2487  	c.Check(snaps[0]["version"], check.Equals, "v1")
  2488  }
  2489  
  2490  func (s *apiSuite) TestSnapsInfoDefaultSources(c *check.C) {
  2491  	d := s.daemon(c)
  2492  
  2493  	s.rsnaps = []*snap.Info{{
  2494  		SideInfo: snap.SideInfo{
  2495  			RealName: "remote",
  2496  		},
  2497  		Publisher: snap.StoreAccount{
  2498  			ID:          "foo-id",
  2499  			Username:    "foo",
  2500  			DisplayName: "Foo",
  2501  			Validation:  "unproven",
  2502  		},
  2503  	}}
  2504  	s.mkInstalledInState(c, d, "local", "foo", "v1", snap.R(10), true, "")
  2505  
  2506  	req, err := http.NewRequest("GET", "/v2/snaps", nil)
  2507  	c.Assert(err, check.IsNil)
  2508  
  2509  	rsp := getSnapsInfo(snapsCmd, req, nil).(*resp)
  2510  
  2511  	c.Assert(rsp.Sources, check.DeepEquals, []string{"local"})
  2512  	snaps := snapList(rsp.Result)
  2513  	c.Assert(snaps, check.HasLen, 1)
  2514  }
  2515  
  2516  func (s *apiSuite) TestSnapsInfoFilterRemote(c *check.C) {
  2517  	s.daemon(c)
  2518  
  2519  	s.rsnaps = nil
  2520  
  2521  	req, err := http.NewRequest("GET", "/v2/snaps?q=foo&sources=store", nil)
  2522  	c.Assert(err, check.IsNil)
  2523  
  2524  	rsp := getSnapsInfo(snapsCmd, req, nil).(*resp)
  2525  
  2526  	c.Check(s.storeSearch, check.DeepEquals, store.Search{Query: "foo"})
  2527  
  2528  	c.Assert(rsp.Result, check.NotNil)
  2529  }
  2530  
  2531  func (s *apiSuite) TestPostSnapBadRequest(c *check.C) {
  2532  	buf := bytes.NewBufferString(`hello`)
  2533  	req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  2534  	c.Assert(err, check.IsNil)
  2535  
  2536  	rsp := postSnap(snapCmd, req, nil).(*resp)
  2537  
  2538  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  2539  	c.Check(rsp.Status, check.Equals, 400)
  2540  	c.Check(rsp.Result, check.NotNil)
  2541  }
  2542  
  2543  func (s *apiSuite) TestPostSnapBadAction(c *check.C) {
  2544  	buf := bytes.NewBufferString(`{"action": "potato"}`)
  2545  	req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  2546  	c.Assert(err, check.IsNil)
  2547  
  2548  	rsp := postSnap(snapCmd, req, nil).(*resp)
  2549  
  2550  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  2551  	c.Check(rsp.Status, check.Equals, 400)
  2552  	c.Check(rsp.Result, check.NotNil)
  2553  }
  2554  
  2555  func (s *apiSuite) TestPostSnapBadChannel(c *check.C) {
  2556  	buf := bytes.NewBufferString(`{"channel": "1/2/3/4"}`)
  2557  	req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  2558  	c.Assert(err, check.IsNil)
  2559  
  2560  	rsp := postSnap(snapCmd, req, nil).(*resp)
  2561  
  2562  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  2563  	c.Check(rsp.Status, check.Equals, 400)
  2564  	c.Check(rsp.Result, check.NotNil)
  2565  }
  2566  
  2567  func (s *apiSuite) TestPostSnap(c *check.C) {
  2568  	s.testPostSnap(c, false)
  2569  }
  2570  
  2571  func (s *apiSuite) TestPostSnapWithChannel(c *check.C) {
  2572  	s.testPostSnap(c, true)
  2573  }
  2574  
  2575  func (s *apiSuite) testPostSnap(c *check.C, withChannel bool) {
  2576  	d := s.daemonWithOverlordMock(c)
  2577  
  2578  	soon := 0
  2579  	ensureStateSoon = func(st *state.State) {
  2580  		soon++
  2581  		ensureStateSoonImpl(st)
  2582  	}
  2583  
  2584  	s.vars = map[string]string{"name": "foo"}
  2585  
  2586  	snapInstructionDispTable["install"] = func(inst *snapInstruction, _ *state.State) (string, []*state.TaskSet, error) {
  2587  		if withChannel {
  2588  			// channel in -> channel out
  2589  			c.Check(inst.Channel, check.Equals, "xyzzy")
  2590  		} else {
  2591  			// no channel in -> no channel out
  2592  			c.Check(inst.Channel, check.Equals, "")
  2593  		}
  2594  		return "foooo", nil, nil
  2595  	}
  2596  	defer func() {
  2597  		snapInstructionDispTable["install"] = snapInstall
  2598  	}()
  2599  
  2600  	var buf *bytes.Buffer
  2601  	if withChannel {
  2602  		buf = bytes.NewBufferString(`{"action": "install", "channel": "xyzzy"}`)
  2603  	} else {
  2604  		buf = bytes.NewBufferString(`{"action": "install"}`)
  2605  	}
  2606  	req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  2607  	c.Assert(err, check.IsNil)
  2608  
  2609  	rsp := postSnap(snapCmd, req, nil).(*resp)
  2610  
  2611  	c.Check(rsp.Type, check.Equals, ResponseTypeAsync)
  2612  
  2613  	st := d.overlord.State()
  2614  	st.Lock()
  2615  	defer st.Unlock()
  2616  	chg := st.Change(rsp.Change)
  2617  	c.Assert(chg, check.NotNil)
  2618  	c.Check(chg.Summary(), check.Equals, "foooo")
  2619  	var names []string
  2620  	err = chg.Get("snap-names", &names)
  2621  	c.Assert(err, check.IsNil)
  2622  	c.Check(names, check.DeepEquals, []string{"foo"})
  2623  
  2624  	c.Check(soon, check.Equals, 1)
  2625  }
  2626  
  2627  func (s *apiSuite) TestPostSnapChannel(c *check.C) {
  2628  	d := s.daemonWithOverlordMock(c)
  2629  
  2630  	soon := 0
  2631  	ensureStateSoon = func(st *state.State) {
  2632  		soon++
  2633  		ensureStateSoonImpl(st)
  2634  	}
  2635  
  2636  	s.vars = map[string]string{"name": "foo"}
  2637  
  2638  	snapInstructionDispTable["install"] = func(*snapInstruction, *state.State) (string, []*state.TaskSet, error) {
  2639  		return "foooo", nil, nil
  2640  	}
  2641  	defer func() {
  2642  		snapInstructionDispTable["install"] = snapInstall
  2643  	}()
  2644  
  2645  	buf := bytes.NewBufferString(`{"action": "install"}`)
  2646  	req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  2647  	c.Assert(err, check.IsNil)
  2648  
  2649  	rsp := postSnap(snapCmd, req, nil).(*resp)
  2650  
  2651  	c.Check(rsp.Type, check.Equals, ResponseTypeAsync)
  2652  
  2653  	st := d.overlord.State()
  2654  	st.Lock()
  2655  	defer st.Unlock()
  2656  	chg := st.Change(rsp.Change)
  2657  	c.Assert(chg, check.NotNil)
  2658  	c.Check(chg.Summary(), check.Equals, "foooo")
  2659  	var names []string
  2660  	err = chg.Get("snap-names", &names)
  2661  	c.Assert(err, check.IsNil)
  2662  	c.Check(names, check.DeepEquals, []string{"foo"})
  2663  
  2664  	c.Check(soon, check.Equals, 1)
  2665  }
  2666  
  2667  func (s *apiSuite) TestPostSnapVerifySnapInstruction(c *check.C) {
  2668  	s.daemonWithOverlordMock(c)
  2669  
  2670  	buf := bytes.NewBufferString(`{"action": "install"}`)
  2671  	req, err := http.NewRequest("POST", "/v2/snaps/ubuntu-core", buf)
  2672  	c.Assert(err, check.IsNil)
  2673  	s.vars = map[string]string{"name": "ubuntu-core"}
  2674  
  2675  	rsp := postSnap(snapCmd, req, nil).(*resp)
  2676  
  2677  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  2678  	c.Check(rsp.Status, check.Equals, 400)
  2679  	c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, `cannot install "ubuntu-core", please use "core" instead`)
  2680  }
  2681  
  2682  func (s *apiSuite) TestPostSnapCohortRandoAction(c *check.C) {
  2683  	s.daemonWithOverlordMock(c)
  2684  	s.vars = map[string]string{"name": "some-snap"}
  2685  	const expectedErr = "cohort-key can only be specified for install, refresh, or switch"
  2686  
  2687  	for _, action := range []string{"remove", "revert", "enable", "disable", "xyzzy"} {
  2688  		buf := strings.NewReader(fmt.Sprintf(`{"action": "%s", "cohort-key": "32"}`, action))
  2689  		req, err := http.NewRequest("POST", "/v2/snaps/some-snap", buf)
  2690  		c.Assert(err, check.IsNil)
  2691  
  2692  		rsp := postSnap(snapCmd, req, nil).(*resp)
  2693  
  2694  		c.Check(rsp.Type, check.Equals, ResponseTypeError)
  2695  		c.Check(rsp.Status, check.Equals, 400, check.Commentf("%q", action))
  2696  		c.Check(rsp.Result.(*errorResult).Message, check.Equals, expectedErr, check.Commentf("%q", action))
  2697  	}
  2698  }
  2699  
  2700  func (s *apiSuite) TestPostSnapLeaveCohortRandoAction(c *check.C) {
  2701  	s.daemonWithOverlordMock(c)
  2702  	s.vars = map[string]string{"name": "some-snap"}
  2703  	const expectedErr = "leave-cohort can only be specified for refresh or switch"
  2704  
  2705  	for _, action := range []string{"install", "remove", "revert", "enable", "disable", "xyzzy"} {
  2706  		buf := strings.NewReader(fmt.Sprintf(`{"action": "%s", "leave-cohort": true}`, action))
  2707  		req, err := http.NewRequest("POST", "/v2/snaps/some-snap", buf)
  2708  		c.Assert(err, check.IsNil)
  2709  
  2710  		rsp := postSnap(snapCmd, req, nil).(*resp)
  2711  
  2712  		c.Check(rsp.Type, check.Equals, ResponseTypeError)
  2713  		c.Check(rsp.Status, check.Equals, 400, check.Commentf("%q", action))
  2714  		c.Check(rsp.Result.(*errorResult).Message, check.Equals, expectedErr, check.Commentf("%q", action))
  2715  	}
  2716  }
  2717  
  2718  func (s *apiSuite) TestPostSnapCohortIncompat(c *check.C) {
  2719  	s.daemonWithOverlordMock(c)
  2720  	s.vars = map[string]string{"name": "some-snap"}
  2721  
  2722  	type T struct {
  2723  		opts   string
  2724  		errmsg string
  2725  	}
  2726  
  2727  	for i, t := range []T{
  2728  		// TODO: more?
  2729  		{`"cohort-key": "what", "revision": "42"`, `cannot specify both cohort-key and revision`},
  2730  		{`"cohort-key": "what", "leave-cohort": true`, `cannot specify both cohort-key and leave-cohort`},
  2731  	} {
  2732  		buf := strings.NewReader(fmt.Sprintf(`{"action": "refresh", %s}`, t.opts))
  2733  		req, err := http.NewRequest("POST", "/v2/snaps/some-snap", buf)
  2734  		c.Assert(err, check.IsNil, check.Commentf("%d (%s)", i, t.opts))
  2735  
  2736  		rsp := postSnap(snapCmd, req, nil).(*resp)
  2737  
  2738  		c.Check(rsp.Type, check.Equals, ResponseTypeError, check.Commentf("%d (%s)", i, t.opts))
  2739  		c.Check(rsp.Status, check.Equals, 400, check.Commentf("%d (%s)", i, t.opts))
  2740  		c.Check(rsp.Result.(*errorResult).Message, check.Equals, t.errmsg, check.Commentf("%d (%s)", i, t.opts))
  2741  	}
  2742  }
  2743  
  2744  func (s *apiSuite) TestPostSnapVerifyMultiSnapInstruction(c *check.C) {
  2745  	s.daemonWithOverlordMock(c)
  2746  
  2747  	buf := strings.NewReader(`{"action": "install","snaps":["ubuntu-core"]}`)
  2748  	req, err := http.NewRequest("POST", "/v2/snaps", buf)
  2749  	c.Assert(err, check.IsNil)
  2750  	req.Header.Set("Content-Type", "application/json")
  2751  
  2752  	rsp := postSnaps(snapsCmd, req, nil).(*resp)
  2753  
  2754  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  2755  	c.Check(rsp.Status, check.Equals, 400)
  2756  	c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, `cannot install "ubuntu-core", please use "core" instead`)
  2757  }
  2758  
  2759  func (s *apiSuite) TestPostSnapsNoWeirdses(c *check.C) {
  2760  	s.daemonWithOverlordMock(c)
  2761  
  2762  	// one could add more actions here ... 🤷
  2763  	for _, action := range []string{"install", "refresh", "remove"} {
  2764  		for weird, v := range map[string]string{
  2765  			"channel":      `"beta"`,
  2766  			"revision":     `"1"`,
  2767  			"devmode":      "true",
  2768  			"jailmode":     "true",
  2769  			"cohort-key":   `"what"`,
  2770  			"leave-cohort": "true",
  2771  			"purge":        "true",
  2772  		} {
  2773  			buf := strings.NewReader(fmt.Sprintf(`{"action": "%s","snaps":["foo","bar"], "%s": %s}`, action, weird, v))
  2774  			req, err := http.NewRequest("POST", "/v2/snaps", buf)
  2775  			c.Assert(err, check.IsNil)
  2776  			req.Header.Set("Content-Type", "application/json")
  2777  
  2778  			rsp := postSnaps(snapsCmd, req, nil).(*resp)
  2779  
  2780  			c.Check(rsp.Type, check.Equals, ResponseTypeError)
  2781  			c.Check(rsp.Status, check.Equals, 400)
  2782  			c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, `unsupported option provided for multi-snap operation`)
  2783  		}
  2784  	}
  2785  }
  2786  
  2787  func (s *apiSuite) TestPostSnapSetsUser(c *check.C) {
  2788  	d := s.daemon(c)
  2789  	ensureStateSoon = func(st *state.State) {}
  2790  
  2791  	snapInstructionDispTable["install"] = func(inst *snapInstruction, st *state.State) (string, []*state.TaskSet, error) {
  2792  		return fmt.Sprintf("<install by user %d>", inst.userID), nil, nil
  2793  	}
  2794  	defer func() {
  2795  		snapInstructionDispTable["install"] = snapInstall
  2796  	}()
  2797  
  2798  	state := snapCmd.d.overlord.State()
  2799  	state.Lock()
  2800  	user, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"})
  2801  	state.Unlock()
  2802  	c.Check(err, check.IsNil)
  2803  
  2804  	buf := bytes.NewBufferString(`{"action": "install"}`)
  2805  	req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  2806  	c.Assert(err, check.IsNil)
  2807  	req.Header.Set("Authorization", `Macaroon root="macaroon", discharge="discharge"`)
  2808  
  2809  	rsp := postSnap(snapCmd, req, user).(*resp)
  2810  
  2811  	c.Check(rsp.Type, check.Equals, ResponseTypeAsync)
  2812  
  2813  	st := d.overlord.State()
  2814  	st.Lock()
  2815  	defer st.Unlock()
  2816  	chg := st.Change(rsp.Change)
  2817  	c.Assert(chg, check.NotNil)
  2818  	c.Check(chg.Summary(), check.Equals, "<install by user 1>")
  2819  }
  2820  
  2821  func (s *apiSuite) TestPostSnapDispatch(c *check.C) {
  2822  	inst := &snapInstruction{Snaps: []string{"foo"}}
  2823  
  2824  	type T struct {
  2825  		s    string
  2826  		impl snapActionFunc
  2827  	}
  2828  
  2829  	actions := []T{
  2830  		{"install", snapInstall},
  2831  		{"refresh", snapUpdate},
  2832  		{"remove", snapRemove},
  2833  		{"revert", snapRevert},
  2834  		{"enable", snapEnable},
  2835  		{"disable", snapDisable},
  2836  		{"switch", snapSwitch},
  2837  		{"xyzzy", nil},
  2838  	}
  2839  
  2840  	for _, action := range actions {
  2841  		inst.Action = action.s
  2842  		// do you feel dirty yet?
  2843  		c.Check(fmt.Sprintf("%p", action.impl), check.Equals, fmt.Sprintf("%p", inst.dispatch()))
  2844  	}
  2845  }
  2846  
  2847  func (s *apiSuite) TestPostSnapEnableDisableSwitchRevision(c *check.C) {
  2848  	for _, action := range []string{"enable", "disable", "switch"} {
  2849  		buf := bytes.NewBufferString(`{"action": "` + action + `", "revision": "42"}`)
  2850  		req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  2851  		c.Assert(err, check.IsNil)
  2852  
  2853  		rsp := postSnap(snapCmd, req, nil).(*resp)
  2854  
  2855  		c.Check(rsp.Type, check.Equals, ResponseTypeError)
  2856  		c.Check(rsp.Status, check.Equals, 400)
  2857  		c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, "takes no revision")
  2858  	}
  2859  }
  2860  
  2861  var sideLoadBodyWithoutDevMode = "" +
  2862  	"----hello--\r\n" +
  2863  	"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
  2864  	"\r\n" +
  2865  	"xyzzy\r\n" +
  2866  	"----hello--\r\n" +
  2867  	"Content-Disposition: form-data; name=\"dangerous\"\r\n" +
  2868  	"\r\n" +
  2869  	"true\r\n" +
  2870  	"----hello--\r\n" +
  2871  	"Content-Disposition: form-data; name=\"snap-path\"\r\n" +
  2872  	"\r\n" +
  2873  	"a/b/local.snap\r\n" +
  2874  	"----hello--\r\n"
  2875  
  2876  func (s *apiSuite) TestSideloadSnapOnNonDevModeDistro(c *check.C) {
  2877  	// try a multipart/form-data upload
  2878  	body := sideLoadBodyWithoutDevMode
  2879  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
  2880  	chgSummary := s.sideloadCheck(c, body, head, "local", snapstate.Flags{RemoveSnapPath: true})
  2881  	c.Check(chgSummary, check.Equals, `Install "local" snap from file "a/b/local.snap"`)
  2882  }
  2883  
  2884  func (s *apiSuite) TestSideloadSnapOnDevModeDistro(c *check.C) {
  2885  	// try a multipart/form-data upload
  2886  	body := sideLoadBodyWithoutDevMode
  2887  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
  2888  	restore := sandbox.MockForceDevMode(true)
  2889  	defer restore()
  2890  	flags := snapstate.Flags{RemoveSnapPath: true}
  2891  	chgSummary := s.sideloadCheck(c, body, head, "local", flags)
  2892  	c.Check(chgSummary, check.Equals, `Install "local" snap from file "a/b/local.snap"`)
  2893  }
  2894  
  2895  func (s *apiSuite) TestSideloadSnapDevMode(c *check.C) {
  2896  	body := "" +
  2897  		"----hello--\r\n" +
  2898  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
  2899  		"\r\n" +
  2900  		"xyzzy\r\n" +
  2901  		"----hello--\r\n" +
  2902  		"Content-Disposition: form-data; name=\"devmode\"\r\n" +
  2903  		"\r\n" +
  2904  		"true\r\n" +
  2905  		"----hello--\r\n"
  2906  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
  2907  	// try a multipart/form-data upload
  2908  	flags := snapstate.Flags{RemoveSnapPath: true}
  2909  	flags.DevMode = true
  2910  	chgSummary := s.sideloadCheck(c, body, head, "local", flags)
  2911  	c.Check(chgSummary, check.Equals, `Install "local" snap from file "x"`)
  2912  }
  2913  
  2914  func (s *apiSuite) TestSideloadSnapJailMode(c *check.C) {
  2915  	body := "" +
  2916  		"----hello--\r\n" +
  2917  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
  2918  		"\r\n" +
  2919  		"xyzzy\r\n" +
  2920  		"----hello--\r\n" +
  2921  		"Content-Disposition: form-data; name=\"jailmode\"\r\n" +
  2922  		"\r\n" +
  2923  		"true\r\n" +
  2924  		"----hello--\r\n" +
  2925  		"Content-Disposition: form-data; name=\"dangerous\"\r\n" +
  2926  		"\r\n" +
  2927  		"true\r\n" +
  2928  		"----hello--\r\n"
  2929  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
  2930  	// try a multipart/form-data upload
  2931  	flags := snapstate.Flags{JailMode: true, RemoveSnapPath: true}
  2932  	chgSummary := s.sideloadCheck(c, body, head, "local", flags)
  2933  	c.Check(chgSummary, check.Equals, `Install "local" snap from file "x"`)
  2934  }
  2935  
  2936  func (s *apiSuite) sideloadCheck(c *check.C, content string, head map[string]string, expectedInstanceName string, expectedFlags snapstate.Flags) string {
  2937  	d := s.daemonWithFakeSnapManager(c)
  2938  
  2939  	soon := 0
  2940  	ensureStateSoon = func(st *state.State) {
  2941  		soon++
  2942  		ensureStateSoonImpl(st)
  2943  	}
  2944  
  2945  	c.Assert(expectedInstanceName != "", check.Equals, true, check.Commentf("expected instance name must be set"))
  2946  	mockedName, _ := snap.SplitInstanceName(expectedInstanceName)
  2947  
  2948  	// setup done
  2949  	installQueue := []string{}
  2950  	unsafeReadSnapInfo = func(path string) (*snap.Info, error) {
  2951  		return &snap.Info{SuggestedName: mockedName}, nil
  2952  	}
  2953  
  2954  	snapstateInstall = func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  2955  		// NOTE: ubuntu-core is not installed in developer mode
  2956  		c.Check(flags, check.Equals, snapstate.Flags{})
  2957  		installQueue = append(installQueue, name)
  2958  
  2959  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  2960  		return state.NewTaskSet(t), nil
  2961  	}
  2962  
  2963  	snapstateInstallPath = func(s *state.State, si *snap.SideInfo, path, name, channel string, flags snapstate.Flags) (*state.TaskSet, *snap.Info, error) {
  2964  		c.Check(flags, check.DeepEquals, expectedFlags)
  2965  
  2966  		c.Check(path, testutil.FileEquals, "xyzzy")
  2967  
  2968  		c.Check(name, check.Equals, expectedInstanceName)
  2969  
  2970  		installQueue = append(installQueue, si.RealName+"::"+path)
  2971  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  2972  		return state.NewTaskSet(t), &snap.Info{SuggestedName: name}, nil
  2973  	}
  2974  
  2975  	buf := bytes.NewBufferString(content)
  2976  	req, err := http.NewRequest("POST", "/v2/snaps", buf)
  2977  	c.Assert(err, check.IsNil)
  2978  	for k, v := range head {
  2979  		req.Header.Set(k, v)
  2980  	}
  2981  
  2982  	rsp := postSnaps(snapsCmd, req, nil).(*resp)
  2983  	c.Assert(rsp.Type, check.Equals, ResponseTypeAsync)
  2984  	n := 1
  2985  	c.Assert(installQueue, check.HasLen, n)
  2986  	c.Check(installQueue[n-1], check.Matches, "local::.*/"+regexp.QuoteMeta(dirs.LocalInstallBlobTempPrefix)+".*")
  2987  
  2988  	st := d.overlord.State()
  2989  	st.Lock()
  2990  	defer st.Unlock()
  2991  	chg := st.Change(rsp.Change)
  2992  	c.Assert(chg, check.NotNil)
  2993  
  2994  	c.Check(soon, check.Equals, 1)
  2995  
  2996  	c.Assert(chg.Tasks(), check.HasLen, n)
  2997  
  2998  	st.Unlock()
  2999  	s.waitTrivialChange(c, chg)
  3000  	st.Lock()
  3001  
  3002  	c.Check(chg.Kind(), check.Equals, "install-snap")
  3003  	var names []string
  3004  	err = chg.Get("snap-names", &names)
  3005  	c.Assert(err, check.IsNil)
  3006  	c.Check(names, check.DeepEquals, []string{expectedInstanceName})
  3007  	var apiData map[string]interface{}
  3008  	err = chg.Get("api-data", &apiData)
  3009  	c.Assert(err, check.IsNil)
  3010  	c.Check(apiData, check.DeepEquals, map[string]interface{}{
  3011  		"snap-name": expectedInstanceName,
  3012  	})
  3013  
  3014  	return chg.Summary()
  3015  }
  3016  
  3017  func (s *apiSuite) TestSideloadSnapJailModeAndDevmode(c *check.C) {
  3018  	body := "" +
  3019  		"----hello--\r\n" +
  3020  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
  3021  		"\r\n" +
  3022  		"xyzzy\r\n" +
  3023  		"----hello--\r\n" +
  3024  		"Content-Disposition: form-data; name=\"jailmode\"\r\n" +
  3025  		"\r\n" +
  3026  		"true\r\n" +
  3027  		"----hello--\r\n" +
  3028  		"Content-Disposition: form-data; name=\"devmode\"\r\n" +
  3029  		"\r\n" +
  3030  		"true\r\n" +
  3031  		"----hello--\r\n"
  3032  	s.daemonWithOverlordMock(c)
  3033  
  3034  	req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
  3035  	c.Assert(err, check.IsNil)
  3036  	req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
  3037  
  3038  	rsp := postSnaps(snapsCmd, req, nil).(*resp)
  3039  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  3040  	c.Check(rsp.Result.(*errorResult).Message, check.Equals, "cannot use devmode and jailmode flags together")
  3041  }
  3042  
  3043  func (s *apiSuite) TestSideloadSnapJailModeInDevModeOS(c *check.C) {
  3044  	body := "" +
  3045  		"----hello--\r\n" +
  3046  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
  3047  		"\r\n" +
  3048  		"xyzzy\r\n" +
  3049  		"----hello--\r\n" +
  3050  		"Content-Disposition: form-data; name=\"jailmode\"\r\n" +
  3051  		"\r\n" +
  3052  		"true\r\n" +
  3053  		"----hello--\r\n"
  3054  	s.daemonWithOverlordMock(c)
  3055  
  3056  	req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
  3057  	c.Assert(err, check.IsNil)
  3058  	req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
  3059  
  3060  	restore := sandbox.MockForceDevMode(true)
  3061  	defer restore()
  3062  
  3063  	rsp := postSnaps(snapsCmd, req, nil).(*resp)
  3064  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  3065  	c.Check(rsp.Result.(*errorResult).Message, check.Equals, "this system cannot honour the jailmode flag")
  3066  }
  3067  
  3068  func (s *apiSuite) TestLocalInstallSnapDeriveSideInfo(c *check.C) {
  3069  	d := s.daemonWithOverlordMock(c)
  3070  	// add the assertions first
  3071  	st := d.overlord.State()
  3072  
  3073  	dev1Acct := assertstest.NewAccount(s.storeSigning, "devel1", nil, "")
  3074  
  3075  	snapDecl, err := s.storeSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{
  3076  		"series":       "16",
  3077  		"snap-id":      "x-id",
  3078  		"snap-name":    "x",
  3079  		"publisher-id": dev1Acct.AccountID(),
  3080  		"timestamp":    time.Now().Format(time.RFC3339),
  3081  	}, nil, "")
  3082  	c.Assert(err, check.IsNil)
  3083  
  3084  	snapRev, err := s.storeSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{
  3085  		"snap-sha3-384": "YK0GWATaZf09g_fvspYPqm_qtaiqf-KjaNj5uMEQCjQpuXWPjqQbeBINL5H_A0Lo",
  3086  		"snap-size":     "5",
  3087  		"snap-id":       "x-id",
  3088  		"snap-revision": "41",
  3089  		"developer-id":  dev1Acct.AccountID(),
  3090  		"timestamp":     time.Now().Format(time.RFC3339),
  3091  	}, nil, "")
  3092  	c.Assert(err, check.IsNil)
  3093  
  3094  	func() {
  3095  		st.Lock()
  3096  		defer st.Unlock()
  3097  		assertstatetest.AddMany(st, s.storeSigning.StoreAccountKey(""), dev1Acct, snapDecl, snapRev)
  3098  	}()
  3099  
  3100  	body := "" +
  3101  		"----hello--\r\n" +
  3102  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x.snap\"\r\n" +
  3103  		"\r\n" +
  3104  		"xyzzy\r\n" +
  3105  		"----hello--\r\n"
  3106  	req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
  3107  	c.Assert(err, check.IsNil)
  3108  	req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
  3109  
  3110  	snapstateInstallPath = func(s *state.State, si *snap.SideInfo, path, name, channel string, flags snapstate.Flags) (*state.TaskSet, *snap.Info, error) {
  3111  		c.Check(flags, check.Equals, snapstate.Flags{RemoveSnapPath: true})
  3112  		c.Check(si, check.DeepEquals, &snap.SideInfo{
  3113  			RealName: "x",
  3114  			SnapID:   "x-id",
  3115  			Revision: snap.R(41),
  3116  		})
  3117  
  3118  		return state.NewTaskSet(), &snap.Info{SuggestedName: "x"}, nil
  3119  	}
  3120  
  3121  	rsp := postSnaps(snapsCmd, req, nil).(*resp)
  3122  	c.Assert(rsp.Type, check.Equals, ResponseTypeAsync)
  3123  
  3124  	st.Lock()
  3125  	defer st.Unlock()
  3126  	chg := st.Change(rsp.Change)
  3127  	c.Assert(chg, check.NotNil)
  3128  	c.Check(chg.Summary(), check.Equals, `Install "x" snap from file "x.snap"`)
  3129  	var names []string
  3130  	err = chg.Get("snap-names", &names)
  3131  	c.Assert(err, check.IsNil)
  3132  	c.Check(names, check.DeepEquals, []string{"x"})
  3133  	var apiData map[string]interface{}
  3134  	err = chg.Get("api-data", &apiData)
  3135  	c.Assert(err, check.IsNil)
  3136  	c.Check(apiData, check.DeepEquals, map[string]interface{}{
  3137  		"snap-name": "x",
  3138  	})
  3139  }
  3140  
  3141  func (s *apiSuite) TestSideloadSnapNoSignaturesDangerOff(c *check.C) {
  3142  	body := "" +
  3143  		"----hello--\r\n" +
  3144  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
  3145  		"\r\n" +
  3146  		"xyzzy\r\n" +
  3147  		"----hello--\r\n"
  3148  	s.daemonWithOverlordMock(c)
  3149  
  3150  	req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
  3151  	c.Assert(err, check.IsNil)
  3152  	req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
  3153  
  3154  	// this is the prefix used for tempfiles for sideloading
  3155  	glob := filepath.Join(os.TempDir(), "snapd-sideload-pkg-*")
  3156  	glbBefore, _ := filepath.Glob(glob)
  3157  	rsp := postSnaps(snapsCmd, req, nil).(*resp)
  3158  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  3159  	c.Check(rsp.Result.(*errorResult).Message, check.Equals, `cannot find signatures with metadata for snap "x"`)
  3160  	glbAfter, _ := filepath.Glob(glob)
  3161  	c.Check(len(glbBefore), check.Equals, len(glbAfter))
  3162  }
  3163  
  3164  func (s *apiSuite) TestSideloadSnapNotValidFormFile(c *check.C) {
  3165  	newTestDaemon(c)
  3166  
  3167  	// try a multipart/form-data upload with missing "name"
  3168  	content := "" +
  3169  		"----hello--\r\n" +
  3170  		"Content-Disposition: form-data; filename=\"x\"\r\n" +
  3171  		"\r\n" +
  3172  		"xyzzy\r\n" +
  3173  		"----hello--\r\n"
  3174  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
  3175  
  3176  	buf := bytes.NewBufferString(content)
  3177  	req, err := http.NewRequest("POST", "/v2/snaps", buf)
  3178  	c.Assert(err, check.IsNil)
  3179  	for k, v := range head {
  3180  		req.Header.Set(k, v)
  3181  	}
  3182  
  3183  	rsp := postSnaps(snapsCmd, req, nil).(*resp)
  3184  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  3185  	c.Assert(rsp.Result.(*errorResult).Message, check.Matches, `cannot find "snap" file field in provided multipart/form-data payload`)
  3186  }
  3187  
  3188  func (s *apiSuite) TestSideloadSnapChangeConflict(c *check.C) {
  3189  	body := "" +
  3190  		"----hello--\r\n" +
  3191  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
  3192  		"\r\n" +
  3193  		"xyzzy\r\n" +
  3194  		"----hello--\r\n" +
  3195  		"Content-Disposition: form-data; name=\"dangerous\"\r\n" +
  3196  		"\r\n" +
  3197  		"true\r\n" +
  3198  		"----hello--\r\n"
  3199  	s.daemonWithOverlordMock(c)
  3200  
  3201  	unsafeReadSnapInfo = func(path string) (*snap.Info, error) {
  3202  		return &snap.Info{SuggestedName: "foo"}, nil
  3203  	}
  3204  
  3205  	snapstateInstallPath = func(s *state.State, si *snap.SideInfo, path, name, channel string, flags snapstate.Flags) (*state.TaskSet, *snap.Info, error) {
  3206  		return nil, nil, &snapstate.ChangeConflictError{Snap: "foo"}
  3207  	}
  3208  
  3209  	req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
  3210  	c.Assert(err, check.IsNil)
  3211  	req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
  3212  
  3213  	rsp := postSnaps(snapsCmd, req, nil).(*resp)
  3214  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  3215  	c.Check(rsp.Result.(*errorResult).Kind, check.Equals, client.ErrorKindSnapChangeConflict)
  3216  }
  3217  
  3218  func (s *apiSuite) TestSideloadSnapInstanceName(c *check.C) {
  3219  	// try a multipart/form-data upload
  3220  	body := sideLoadBodyWithoutDevMode +
  3221  		"Content-Disposition: form-data; name=\"name\"\r\n" +
  3222  		"\r\n" +
  3223  		"local_instance\r\n" +
  3224  		"----hello--\r\n"
  3225  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
  3226  	chgSummary := s.sideloadCheck(c, body, head, "local_instance", snapstate.Flags{RemoveSnapPath: true})
  3227  	c.Check(chgSummary, check.Equals, `Install "local_instance" snap from file "a/b/local.snap"`)
  3228  }
  3229  
  3230  func (s *apiSuite) TestSideloadSnapInstanceNameNoKey(c *check.C) {
  3231  	// try a multipart/form-data upload
  3232  	body := sideLoadBodyWithoutDevMode +
  3233  		"Content-Disposition: form-data; name=\"name\"\r\n" +
  3234  		"\r\n" +
  3235  		"local\r\n" +
  3236  		"----hello--\r\n"
  3237  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
  3238  	chgSummary := s.sideloadCheck(c, body, head, "local", snapstate.Flags{RemoveSnapPath: true})
  3239  	c.Check(chgSummary, check.Equals, `Install "local" snap from file "a/b/local.snap"`)
  3240  }
  3241  
  3242  func (s *apiSuite) TestSideloadSnapInstanceNameMismatch(c *check.C) {
  3243  	s.daemonWithFakeSnapManager(c)
  3244  
  3245  	unsafeReadSnapInfo = func(path string) (*snap.Info, error) {
  3246  		return &snap.Info{SuggestedName: "bar"}, nil
  3247  	}
  3248  
  3249  	body := sideLoadBodyWithoutDevMode +
  3250  		"Content-Disposition: form-data; name=\"name\"\r\n" +
  3251  		"\r\n" +
  3252  		"foo_instance\r\n" +
  3253  		"----hello--\r\n"
  3254  
  3255  	req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(body))
  3256  	c.Assert(err, check.IsNil)
  3257  	req.Header.Set("Content-Type", "multipart/thing; boundary=--hello--")
  3258  
  3259  	rsp := postSnaps(snapsCmd, req, nil).(*resp)
  3260  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  3261  	c.Check(rsp.Result.(*errorResult).Message, check.Equals, `instance name "foo_instance" does not match snap name "bar"`)
  3262  }
  3263  
  3264  func (s *apiSuite) TestTrySnap(c *check.C) {
  3265  	d := s.daemonWithFakeSnapManager(c)
  3266  
  3267  	var err error
  3268  
  3269  	// mock a try dir
  3270  	tryDir := c.MkDir()
  3271  	snapYaml := filepath.Join(tryDir, "meta", "snap.yaml")
  3272  	err = os.MkdirAll(filepath.Dir(snapYaml), 0755)
  3273  	c.Assert(err, check.IsNil)
  3274  	err = ioutil.WriteFile(snapYaml, []byte("name: foo\nversion: 1.0\n"), 0644)
  3275  	c.Assert(err, check.IsNil)
  3276  
  3277  	reqForFlags := func(f snapstate.Flags) *http.Request {
  3278  		b := "" +
  3279  			"--hello\r\n" +
  3280  			"Content-Disposition: form-data; name=\"action\"\r\n" +
  3281  			"\r\n" +
  3282  			"try\r\n" +
  3283  			"--hello\r\n" +
  3284  			"Content-Disposition: form-data; name=\"snap-path\"\r\n" +
  3285  			"\r\n" +
  3286  			tryDir + "\r\n" +
  3287  			"--hello"
  3288  
  3289  		snip := "\r\n" +
  3290  			"Content-Disposition: form-data; name=%q\r\n" +
  3291  			"\r\n" +
  3292  			"true\r\n" +
  3293  			"--hello"
  3294  
  3295  		if f.DevMode {
  3296  			b += fmt.Sprintf(snip, "devmode")
  3297  		}
  3298  		if f.JailMode {
  3299  			b += fmt.Sprintf(snip, "jailmode")
  3300  		}
  3301  		if f.Classic {
  3302  			b += fmt.Sprintf(snip, "classic")
  3303  		}
  3304  		b += "--\r\n"
  3305  
  3306  		req, err := http.NewRequest("POST", "/v2/snaps", bytes.NewBufferString(b))
  3307  		c.Assert(err, check.IsNil)
  3308  		req.Header.Set("Content-Type", "multipart/thing; boundary=hello")
  3309  
  3310  		return req
  3311  	}
  3312  
  3313  	st := d.overlord.State()
  3314  	st.Lock()
  3315  	defer st.Unlock()
  3316  
  3317  	for _, t := range []struct {
  3318  		flags snapstate.Flags
  3319  		desc  string
  3320  	}{
  3321  		{snapstate.Flags{}, "core; -"},
  3322  		{snapstate.Flags{DevMode: true}, "core; devmode"},
  3323  		{snapstate.Flags{JailMode: true}, "core; jailmode"},
  3324  		{snapstate.Flags{Classic: true}, "core; classic"},
  3325  	} {
  3326  		soon := 0
  3327  		ensureStateSoon = func(st *state.State) {
  3328  			soon++
  3329  			ensureStateSoonImpl(st)
  3330  		}
  3331  
  3332  		tryWasCalled := true
  3333  		snapstateTryPath = func(s *state.State, name, path string, flags snapstate.Flags) (*state.TaskSet, error) {
  3334  			c.Check(flags, check.DeepEquals, t.flags, check.Commentf(t.desc))
  3335  			tryWasCalled = true
  3336  			t := s.NewTask("fake-install-snap", "Doing a fake try")
  3337  			return state.NewTaskSet(t), nil
  3338  		}
  3339  
  3340  		snapstateInstall = func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  3341  			if name != "core" {
  3342  				c.Check(flags, check.DeepEquals, t.flags, check.Commentf(t.desc))
  3343  			}
  3344  			t := s.NewTask("fake-install-snap", "Doing a fake install")
  3345  			return state.NewTaskSet(t), nil
  3346  		}
  3347  
  3348  		// try the snap (without an installed core)
  3349  		st.Unlock()
  3350  		rsp := postSnaps(snapsCmd, reqForFlags(t.flags), nil).(*resp)
  3351  		st.Lock()
  3352  		c.Assert(rsp.Type, check.Equals, ResponseTypeAsync, check.Commentf(t.desc))
  3353  		c.Assert(tryWasCalled, check.Equals, true, check.Commentf(t.desc))
  3354  
  3355  		chg := st.Change(rsp.Change)
  3356  		c.Assert(chg, check.NotNil, check.Commentf(t.desc))
  3357  
  3358  		c.Assert(chg.Tasks(), check.HasLen, 1, check.Commentf(t.desc))
  3359  
  3360  		st.Unlock()
  3361  		s.waitTrivialChange(c, chg)
  3362  		st.Lock()
  3363  
  3364  		c.Check(chg.Kind(), check.Equals, "try-snap", check.Commentf(t.desc))
  3365  		c.Check(chg.Summary(), check.Equals, fmt.Sprintf(`Try "%s" snap from %s`, "foo", tryDir), check.Commentf(t.desc))
  3366  		var names []string
  3367  		err = chg.Get("snap-names", &names)
  3368  		c.Assert(err, check.IsNil, check.Commentf(t.desc))
  3369  		c.Check(names, check.DeepEquals, []string{"foo"}, check.Commentf(t.desc))
  3370  		var apiData map[string]interface{}
  3371  		err = chg.Get("api-data", &apiData)
  3372  		c.Assert(err, check.IsNil, check.Commentf(t.desc))
  3373  		c.Check(apiData, check.DeepEquals, map[string]interface{}{
  3374  			"snap-name": "foo",
  3375  		}, check.Commentf(t.desc))
  3376  
  3377  		c.Check(soon, check.Equals, 1, check.Commentf(t.desc))
  3378  	}
  3379  }
  3380  
  3381  func (s *apiSuite) TestTrySnapRelative(c *check.C) {
  3382  	req, err := http.NewRequest("POST", "/v2/snaps", nil)
  3383  	c.Assert(err, check.IsNil)
  3384  
  3385  	rsp := trySnap(snapsCmd, req, nil, "relative-path", snapstate.Flags{}).(*resp)
  3386  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  3387  	c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, "need an absolute path")
  3388  }
  3389  
  3390  func (s *apiSuite) TestTrySnapNotDir(c *check.C) {
  3391  	req, err := http.NewRequest("POST", "/v2/snaps", nil)
  3392  	c.Assert(err, check.IsNil)
  3393  
  3394  	rsp := trySnap(snapsCmd, req, nil, "/does/not/exist", snapstate.Flags{}).(*resp)
  3395  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  3396  	c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, "not a snap directory")
  3397  }
  3398  
  3399  func (s *apiSuite) TestTryChangeConflict(c *check.C) {
  3400  	s.daemonWithOverlordMock(c)
  3401  
  3402  	// mock a try dir
  3403  	tryDir := c.MkDir()
  3404  
  3405  	unsafeReadSnapInfo = func(path string) (*snap.Info, error) {
  3406  		return &snap.Info{SuggestedName: "foo"}, nil
  3407  	}
  3408  
  3409  	snapstateTryPath = func(s *state.State, name, path string, flags snapstate.Flags) (*state.TaskSet, error) {
  3410  		return nil, &snapstate.ChangeConflictError{Snap: "foo"}
  3411  	}
  3412  
  3413  	req, err := http.NewRequest("POST", "/v2/snaps", nil)
  3414  	c.Assert(err, check.IsNil)
  3415  
  3416  	rsp := trySnap(snapsCmd, req, nil, tryDir, snapstate.Flags{}).(*resp)
  3417  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  3418  	c.Check(rsp.Result.(*errorResult).Kind, check.Equals, client.ErrorKindSnapChangeConflict)
  3419  }
  3420  
  3421  func (s *apiSuite) runGetConf(c *check.C, snapName string, keys []string, statusCode int) map[string]interface{} {
  3422  	s.vars = map[string]string{"name": snapName}
  3423  	req, err := http.NewRequest("GET", "/v2/snaps/"+snapName+"/conf?keys="+strings.Join(keys, ","), nil)
  3424  	c.Check(err, check.IsNil)
  3425  	rec := httptest.NewRecorder()
  3426  	snapConfCmd.GET(snapConfCmd, req, nil).ServeHTTP(rec, req)
  3427  	c.Check(rec.Code, check.Equals, statusCode)
  3428  
  3429  	var body map[string]interface{}
  3430  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  3431  	c.Check(err, check.IsNil)
  3432  	return body["result"].(map[string]interface{})
  3433  }
  3434  
  3435  func (s *apiSuite) TestGetConfSingleKey(c *check.C) {
  3436  	d := s.daemon(c)
  3437  
  3438  	// Set a config that we'll get in a moment
  3439  	d.overlord.State().Lock()
  3440  	tr := config.NewTransaction(d.overlord.State())
  3441  	tr.Set("test-snap", "test-key1", "test-value1")
  3442  	tr.Set("test-snap", "test-key2", "test-value2")
  3443  	tr.Commit()
  3444  	d.overlord.State().Unlock()
  3445  
  3446  	result := s.runGetConf(c, "test-snap", []string{"test-key1"}, 200)
  3447  	c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1"})
  3448  
  3449  	result = s.runGetConf(c, "test-snap", []string{"test-key1", "test-key2"}, 200)
  3450  	c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1", "test-key2": "test-value2"})
  3451  }
  3452  
  3453  func (s *apiSuite) TestGetConfCoreSystemAlias(c *check.C) {
  3454  	d := s.daemon(c)
  3455  
  3456  	// Set a config that we'll get in a moment
  3457  	d.overlord.State().Lock()
  3458  	tr := config.NewTransaction(d.overlord.State())
  3459  	tr.Set("core", "test-key1", "test-value1")
  3460  	tr.Commit()
  3461  	d.overlord.State().Unlock()
  3462  
  3463  	result := s.runGetConf(c, "core", []string{"test-key1"}, 200)
  3464  	c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1"})
  3465  
  3466  	result = s.runGetConf(c, "system", []string{"test-key1"}, 200)
  3467  	c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1"})
  3468  }
  3469  
  3470  func (s *apiSuite) TestGetConfMissingKey(c *check.C) {
  3471  	result := s.runGetConf(c, "test-snap", []string{"test-key2"}, 400)
  3472  	c.Check(result, check.DeepEquals, map[string]interface{}{
  3473  		"value": map[string]interface{}{
  3474  			"SnapName": "test-snap",
  3475  			"Key":      "test-key2",
  3476  		},
  3477  		"message": `snap "test-snap" has no "test-key2" configuration option`,
  3478  		"kind":    "option-not-found",
  3479  	})
  3480  }
  3481  
  3482  func (s *apiSuite) TestGetRootDocument(c *check.C) {
  3483  	d := s.daemon(c)
  3484  	d.overlord.State().Lock()
  3485  	tr := config.NewTransaction(d.overlord.State())
  3486  	tr.Set("test-snap", "test-key1", "test-value1")
  3487  	tr.Set("test-snap", "test-key2", "test-value2")
  3488  	tr.Commit()
  3489  	d.overlord.State().Unlock()
  3490  
  3491  	result := s.runGetConf(c, "test-snap", nil, 200)
  3492  	c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1", "test-key2": "test-value2"})
  3493  }
  3494  
  3495  func (s *apiSuite) TestGetConfBadKey(c *check.C) {
  3496  	s.daemon(c)
  3497  	// TODO: this one in particular should really be a 400 also
  3498  	result := s.runGetConf(c, "test-snap", []string{"."}, 500)
  3499  	c.Check(result, check.DeepEquals, map[string]interface{}{"message": `invalid option name: ""`})
  3500  }
  3501  
  3502  func (s *apiSuite) TestSetConf(c *check.C) {
  3503  	d := s.daemon(c)
  3504  	s.mockSnap(c, configYaml)
  3505  
  3506  	// Mock the hook runner
  3507  	hookRunner := testutil.MockCommand(c, "snap", "")
  3508  	defer hookRunner.Restore()
  3509  
  3510  	d.overlord.Loop()
  3511  	defer d.overlord.Stop()
  3512  
  3513  	text, err := json.Marshal(map[string]interface{}{"key": "value"})
  3514  	c.Assert(err, check.IsNil)
  3515  
  3516  	buffer := bytes.NewBuffer(text)
  3517  	req, err := http.NewRequest("PUT", "/v2/snaps/config-snap/conf", buffer)
  3518  	c.Assert(err, check.IsNil)
  3519  
  3520  	s.vars = map[string]string{"name": "config-snap"}
  3521  
  3522  	rec := httptest.NewRecorder()
  3523  	snapConfCmd.PUT(snapConfCmd, req, nil).ServeHTTP(rec, req)
  3524  	c.Check(rec.Code, check.Equals, 202)
  3525  
  3526  	var body map[string]interface{}
  3527  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  3528  	c.Assert(err, check.IsNil)
  3529  	id := body["change"].(string)
  3530  
  3531  	st := d.overlord.State()
  3532  	st.Lock()
  3533  	chg := st.Change(id)
  3534  	st.Unlock()
  3535  	c.Assert(chg, check.NotNil)
  3536  
  3537  	<-chg.Ready()
  3538  
  3539  	st.Lock()
  3540  	err = chg.Err()
  3541  	st.Unlock()
  3542  	c.Assert(err, check.IsNil)
  3543  
  3544  	// Check that the configure hook was run correctly
  3545  	c.Check(hookRunner.Calls(), check.DeepEquals, [][]string{{
  3546  		"snap", "run", "--hook", "configure", "-r", "unset", "config-snap",
  3547  	}})
  3548  }
  3549  
  3550  func (s *apiSuite) TestSetConfCoreSystemAlias(c *check.C) {
  3551  	d := s.daemon(c)
  3552  	s.mockSnap(c, `
  3553  name: core
  3554  version: 1
  3555  `)
  3556  	// Mock the hook runner
  3557  	hookRunner := testutil.MockCommand(c, "snap", "")
  3558  	defer hookRunner.Restore()
  3559  
  3560  	d.overlord.Loop()
  3561  	defer d.overlord.Stop()
  3562  
  3563  	text, err := json.Marshal(map[string]interface{}{"proxy.ftp": "value"})
  3564  	c.Assert(err, check.IsNil)
  3565  
  3566  	buffer := bytes.NewBuffer(text)
  3567  	req, err := http.NewRequest("PUT", "/v2/snaps/system/conf", buffer)
  3568  	c.Assert(err, check.IsNil)
  3569  
  3570  	s.vars = map[string]string{"name": "system"}
  3571  
  3572  	rec := httptest.NewRecorder()
  3573  	snapConfCmd.PUT(snapConfCmd, req, nil).ServeHTTP(rec, req)
  3574  	c.Check(rec.Code, check.Equals, 202)
  3575  
  3576  	var body map[string]interface{}
  3577  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  3578  	c.Assert(err, check.IsNil)
  3579  	id := body["change"].(string)
  3580  
  3581  	st := d.overlord.State()
  3582  	st.Lock()
  3583  	chg := st.Change(id)
  3584  	st.Unlock()
  3585  	c.Assert(chg, check.NotNil)
  3586  
  3587  	<-chg.Ready()
  3588  
  3589  	st.Lock()
  3590  	err = chg.Err()
  3591  	c.Assert(err, check.IsNil)
  3592  
  3593  	tr := config.NewTransaction(st)
  3594  	st.Unlock()
  3595  	c.Assert(err, check.IsNil)
  3596  
  3597  	var value string
  3598  	tr.Get("core", "proxy.ftp", &value)
  3599  	c.Assert(value, check.Equals, "value")
  3600  
  3601  }
  3602  
  3603  func (s *apiSuite) TestSetConfNumber(c *check.C) {
  3604  	d := s.daemon(c)
  3605  	s.mockSnap(c, configYaml)
  3606  
  3607  	// Mock the hook runner
  3608  	hookRunner := testutil.MockCommand(c, "snap", "")
  3609  	defer hookRunner.Restore()
  3610  
  3611  	d.overlord.Loop()
  3612  	defer d.overlord.Stop()
  3613  
  3614  	text, err := json.Marshal(map[string]interface{}{"key": 1234567890})
  3615  	c.Assert(err, check.IsNil)
  3616  
  3617  	buffer := bytes.NewBuffer(text)
  3618  	req, err := http.NewRequest("PUT", "/v2/snaps/config-snap/conf", buffer)
  3619  	c.Assert(err, check.IsNil)
  3620  
  3621  	s.vars = map[string]string{"name": "config-snap"}
  3622  
  3623  	rec := httptest.NewRecorder()
  3624  	snapConfCmd.PUT(snapConfCmd, req, nil).ServeHTTP(rec, req)
  3625  	c.Check(rec.Code, check.Equals, 202)
  3626  
  3627  	var body map[string]interface{}
  3628  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  3629  	c.Assert(err, check.IsNil)
  3630  	id := body["change"].(string)
  3631  
  3632  	st := d.overlord.State()
  3633  	st.Lock()
  3634  	chg := st.Change(id)
  3635  	st.Unlock()
  3636  	c.Assert(chg, check.NotNil)
  3637  
  3638  	<-chg.Ready()
  3639  
  3640  	st.Lock()
  3641  	defer st.Unlock()
  3642  	tr := config.NewTransaction(d.overlord.State())
  3643  	var result interface{}
  3644  	c.Assert(tr.Get("config-snap", "key", &result), check.IsNil)
  3645  	c.Assert(result, check.DeepEquals, json.Number("1234567890"))
  3646  }
  3647  
  3648  func (s *apiSuite) TestSetConfBadSnap(c *check.C) {
  3649  	s.daemonWithOverlordMock(c)
  3650  
  3651  	text, err := json.Marshal(map[string]interface{}{"key": "value"})
  3652  	c.Assert(err, check.IsNil)
  3653  
  3654  	buffer := bytes.NewBuffer(text)
  3655  	req, err := http.NewRequest("PUT", "/v2/snaps/config-snap/conf", buffer)
  3656  	c.Assert(err, check.IsNil)
  3657  
  3658  	s.vars = map[string]string{"name": "config-snap"}
  3659  
  3660  	rec := httptest.NewRecorder()
  3661  	snapConfCmd.PUT(snapConfCmd, req, nil).ServeHTTP(rec, req)
  3662  	c.Check(rec.Code, check.Equals, 404)
  3663  
  3664  	var body map[string]interface{}
  3665  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  3666  	c.Assert(err, check.IsNil)
  3667  	c.Check(body, check.DeepEquals, map[string]interface{}{
  3668  		"status-code": 404.,
  3669  		"status":      "Not Found",
  3670  		"result": map[string]interface{}{
  3671  			"message": `snap "config-snap" is not installed`,
  3672  			"kind":    "snap-not-found",
  3673  			"value":   "config-snap",
  3674  		},
  3675  		"type": "error"})
  3676  }
  3677  
  3678  func simulateConflict(o *overlord.Overlord, name string) {
  3679  	st := o.State()
  3680  	st.Lock()
  3681  	defer st.Unlock()
  3682  	t := st.NewTask("link-snap", "...")
  3683  	snapsup := &snapstate.SnapSetup{SideInfo: &snap.SideInfo{
  3684  		RealName: name,
  3685  	}}
  3686  	t.Set("snap-setup", snapsup)
  3687  	chg := st.NewChange("manip", "...")
  3688  	chg.AddTask(t)
  3689  }
  3690  
  3691  func (s *apiSuite) TestSetConfChangeConflict(c *check.C) {
  3692  	d := s.daemon(c)
  3693  	s.mockSnap(c, configYaml)
  3694  
  3695  	simulateConflict(d.overlord, "config-snap")
  3696  
  3697  	text, err := json.Marshal(map[string]interface{}{"key": "value"})
  3698  	c.Assert(err, check.IsNil)
  3699  
  3700  	buffer := bytes.NewBuffer(text)
  3701  	req, err := http.NewRequest("PUT", "/v2/snaps/config-snap/conf", buffer)
  3702  	c.Assert(err, check.IsNil)
  3703  
  3704  	s.vars = map[string]string{"name": "config-snap"}
  3705  
  3706  	rec := httptest.NewRecorder()
  3707  	snapConfCmd.PUT(snapConfCmd, req, nil).ServeHTTP(rec, req)
  3708  	c.Check(rec.Code, check.Equals, 409)
  3709  
  3710  	var body map[string]interface{}
  3711  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  3712  	c.Assert(err, check.IsNil)
  3713  	c.Check(body, check.DeepEquals, map[string]interface{}{
  3714  		"status-code": 409.,
  3715  		"status":      "Conflict",
  3716  		"result": map[string]interface{}{
  3717  			"message": `snap "config-snap" has "manip" change in progress`,
  3718  			"kind":    "snap-change-conflict",
  3719  			"value": map[string]interface{}{
  3720  				"change-kind": "manip",
  3721  				"snap-name":   "config-snap",
  3722  			},
  3723  		},
  3724  		"type": "error"})
  3725  }
  3726  
  3727  func (s *apiSuite) TestAppIconGet(c *check.C) {
  3728  	d := s.daemon(c)
  3729  
  3730  	// have an active foo in the system
  3731  	info := s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), true, "")
  3732  
  3733  	// have an icon for it in the package itself
  3734  	iconfile := filepath.Join(info.MountDir(), "meta", "gui", "icon.ick")
  3735  	c.Assert(os.MkdirAll(filepath.Dir(iconfile), 0755), check.IsNil)
  3736  	c.Check(ioutil.WriteFile(iconfile, []byte("ick"), 0644), check.IsNil)
  3737  
  3738  	s.vars = map[string]string{"name": "foo"}
  3739  	req, err := http.NewRequest("GET", "/v2/icons/foo/icon", nil)
  3740  	c.Assert(err, check.IsNil)
  3741  
  3742  	rec := httptest.NewRecorder()
  3743  
  3744  	appIconCmd.GET(appIconCmd, req, nil).ServeHTTP(rec, req)
  3745  	c.Check(rec.Code, check.Equals, 200)
  3746  	c.Check(rec.Body.String(), check.Equals, "ick")
  3747  }
  3748  
  3749  func (s *apiSuite) TestAppIconGetInactive(c *check.C) {
  3750  	d := s.daemon(c)
  3751  
  3752  	// have an *in*active foo in the system
  3753  	info := s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), false, "")
  3754  
  3755  	// have an icon for it in the package itself
  3756  	iconfile := filepath.Join(info.MountDir(), "meta", "gui", "icon.ick")
  3757  	c.Assert(os.MkdirAll(filepath.Dir(iconfile), 0755), check.IsNil)
  3758  	c.Check(ioutil.WriteFile(iconfile, []byte("ick"), 0644), check.IsNil)
  3759  
  3760  	s.vars = map[string]string{"name": "foo"}
  3761  	req, err := http.NewRequest("GET", "/v2/icons/foo/icon", nil)
  3762  	c.Assert(err, check.IsNil)
  3763  
  3764  	rec := httptest.NewRecorder()
  3765  
  3766  	appIconCmd.GET(appIconCmd, req, nil).ServeHTTP(rec, req)
  3767  	c.Check(rec.Code, check.Equals, 200)
  3768  	c.Check(rec.Body.String(), check.Equals, "ick")
  3769  }
  3770  
  3771  func (s *apiSuite) TestAppIconGetNoIcon(c *check.C) {
  3772  	d := s.daemon(c)
  3773  
  3774  	// have an *in*active foo in the system
  3775  	info := s.mkInstalledInState(c, d, "foo", "bar", "v1", snap.R(10), true, "")
  3776  
  3777  	// NO ICON!
  3778  	err := os.RemoveAll(filepath.Join(info.MountDir(), "meta", "gui", "icon.svg"))
  3779  	c.Assert(err, check.IsNil)
  3780  
  3781  	s.vars = map[string]string{"name": "foo"}
  3782  	req, err := http.NewRequest("GET", "/v2/icons/foo/icon", nil)
  3783  	c.Assert(err, check.IsNil)
  3784  
  3785  	rec := httptest.NewRecorder()
  3786  
  3787  	appIconCmd.GET(appIconCmd, req, nil).ServeHTTP(rec, req)
  3788  	c.Check(rec.Code/100, check.Equals, 4)
  3789  }
  3790  
  3791  func (s *apiSuite) TestAppIconGetNoApp(c *check.C) {
  3792  	s.daemon(c)
  3793  
  3794  	s.vars = map[string]string{"name": "foo"}
  3795  	req, err := http.NewRequest("GET", "/v2/icons/foo/icon", nil)
  3796  	c.Assert(err, check.IsNil)
  3797  
  3798  	rec := httptest.NewRecorder()
  3799  
  3800  	appIconCmd.GET(appIconCmd, req, nil).ServeHTTP(rec, req)
  3801  	c.Check(rec.Code, check.Equals, 404)
  3802  }
  3803  
  3804  func (s *apiSuite) TestNotInstalledSnapIcon(c *check.C) {
  3805  	info := &snap.Info{SuggestedName: "notInstalledSnap", Media: []snap.MediaInfo{{Type: "icon", URL: "icon.svg"}}}
  3806  	iconfile := snapIcon(info)
  3807  	c.Check(iconfile, check.Equals, "")
  3808  }
  3809  
  3810  func (s *apiSuite) TestInstallOnNonDevModeDistro(c *check.C) {
  3811  	s.testInstall(c, false, snapstate.Flags{}, snap.R(0))
  3812  }
  3813  func (s *apiSuite) TestInstallOnDevModeDistro(c *check.C) {
  3814  	s.testInstall(c, true, snapstate.Flags{}, snap.R(0))
  3815  }
  3816  func (s *apiSuite) TestInstallRevision(c *check.C) {
  3817  	s.testInstall(c, false, snapstate.Flags{}, snap.R(42))
  3818  }
  3819  
  3820  func (s *apiSuite) testInstall(c *check.C, forcedDevmode bool, flags snapstate.Flags, revision snap.Revision) {
  3821  	calledFlags := snapstate.Flags{}
  3822  	installQueue := []string{}
  3823  	restore := sandbox.MockForceDevMode(forcedDevmode)
  3824  	defer restore()
  3825  
  3826  	snapstateInstall = func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  3827  		calledFlags = flags
  3828  		installQueue = append(installQueue, name)
  3829  		c.Check(revision, check.Equals, opts.Revision)
  3830  
  3831  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  3832  		return state.NewTaskSet(t), nil
  3833  	}
  3834  
  3835  	defer func() {
  3836  		snapstateInstall = nil
  3837  	}()
  3838  
  3839  	d := s.daemonWithFakeSnapManager(c)
  3840  
  3841  	var buf bytes.Buffer
  3842  	if revision.Unset() {
  3843  		buf.WriteString(`{"action": "install"}`)
  3844  	} else {
  3845  		fmt.Fprintf(&buf, `{"action": "install", "revision": %s}`, revision.String())
  3846  	}
  3847  	req, err := http.NewRequest("POST", "/v2/snaps/some-snap", &buf)
  3848  	c.Assert(err, check.IsNil)
  3849  
  3850  	s.vars = map[string]string{"name": "some-snap"}
  3851  	rsp := postSnap(snapCmd, req, nil).(*resp)
  3852  
  3853  	c.Assert(rsp.Type, check.Equals, ResponseTypeAsync)
  3854  
  3855  	st := d.overlord.State()
  3856  	st.Lock()
  3857  	defer st.Unlock()
  3858  	chg := st.Change(rsp.Change)
  3859  	c.Assert(chg, check.NotNil)
  3860  
  3861  	c.Check(chg.Tasks(), check.HasLen, 1)
  3862  
  3863  	st.Unlock()
  3864  	s.waitTrivialChange(c, chg)
  3865  	st.Lock()
  3866  
  3867  	c.Check(chg.Status(), check.Equals, state.DoneStatus)
  3868  	c.Check(calledFlags, check.Equals, flags)
  3869  	c.Check(err, check.IsNil)
  3870  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  3871  	c.Check(chg.Kind(), check.Equals, "install-snap")
  3872  	c.Check(chg.Summary(), check.Equals, `Install "some-snap" snap`)
  3873  }
  3874  
  3875  func (s *apiSuite) TestInstallUserAgentContextCreated(c *check.C) {
  3876  	snapstateInstall = func(ctx context.Context, st *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  3877  		s.ctx = ctx
  3878  		t := st.NewTask("fake-install-snap", "Doing a fake install")
  3879  		return state.NewTaskSet(t), nil
  3880  	}
  3881  	defer func() {
  3882  		snapstateInstall = nil
  3883  	}()
  3884  
  3885  	s.daemonWithFakeSnapManager(c)
  3886  
  3887  	var buf bytes.Buffer
  3888  	buf.WriteString(`{"action": "install"}`)
  3889  	req, err := http.NewRequest("POST", "/v2/snaps/some-snap", &buf)
  3890  	req.RemoteAddr = "pid=100;uid=0;socket=;"
  3891  	c.Assert(err, check.IsNil)
  3892  	req.Header.Add("User-Agent", "some-agent/1.0")
  3893  
  3894  	s.vars = map[string]string{"name": "some-snap"}
  3895  	rec := httptest.NewRecorder()
  3896  	snapCmd.ServeHTTP(rec, req)
  3897  	c.Assert(rec.Code, check.Equals, 202)
  3898  	c.Check(store.ClientUserAgent(s.ctx), check.Equals, "some-agent/1.0")
  3899  }
  3900  
  3901  func (s *apiSuite) TestRefresh(c *check.C) {
  3902  	var calledFlags snapstate.Flags
  3903  	calledUserID := 0
  3904  	installQueue := []string{}
  3905  	assertstateCalledUserID := 0
  3906  
  3907  	snapstateUpdate = func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  3908  		calledFlags = flags
  3909  		calledUserID = userID
  3910  		installQueue = append(installQueue, name)
  3911  
  3912  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  3913  		return state.NewTaskSet(t), nil
  3914  	}
  3915  	assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
  3916  		assertstateCalledUserID = userID
  3917  		return nil
  3918  	}
  3919  
  3920  	d := s.daemon(c)
  3921  	inst := &snapInstruction{
  3922  		Action: "refresh",
  3923  		Snaps:  []string{"some-snap"},
  3924  		userID: 17,
  3925  	}
  3926  
  3927  	st := d.overlord.State()
  3928  	st.Lock()
  3929  	defer st.Unlock()
  3930  	summary, _, err := inst.dispatch()(inst, st)
  3931  	c.Check(err, check.IsNil)
  3932  
  3933  	c.Check(assertstateCalledUserID, check.Equals, 17)
  3934  	c.Check(calledFlags, check.DeepEquals, snapstate.Flags{})
  3935  	c.Check(calledUserID, check.Equals, 17)
  3936  	c.Check(err, check.IsNil)
  3937  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  3938  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  3939  }
  3940  
  3941  func (s *apiSuite) TestRefreshDevMode(c *check.C) {
  3942  	var calledFlags snapstate.Flags
  3943  	calledUserID := 0
  3944  	installQueue := []string{}
  3945  
  3946  	snapstateUpdate = func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  3947  		calledFlags = flags
  3948  		calledUserID = userID
  3949  		installQueue = append(installQueue, name)
  3950  
  3951  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  3952  		return state.NewTaskSet(t), nil
  3953  	}
  3954  	assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
  3955  		return nil
  3956  	}
  3957  
  3958  	d := s.daemon(c)
  3959  	inst := &snapInstruction{
  3960  		Action:  "refresh",
  3961  		DevMode: true,
  3962  		Snaps:   []string{"some-snap"},
  3963  		userID:  17,
  3964  	}
  3965  
  3966  	st := d.overlord.State()
  3967  	st.Lock()
  3968  	defer st.Unlock()
  3969  	summary, _, err := inst.dispatch()(inst, st)
  3970  	c.Check(err, check.IsNil)
  3971  
  3972  	flags := snapstate.Flags{}
  3973  	flags.DevMode = true
  3974  	c.Check(calledFlags, check.DeepEquals, flags)
  3975  	c.Check(calledUserID, check.Equals, 17)
  3976  	c.Check(err, check.IsNil)
  3977  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  3978  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  3979  }
  3980  
  3981  func (s *apiSuite) TestRefreshClassic(c *check.C) {
  3982  	var calledFlags snapstate.Flags
  3983  
  3984  	snapstateUpdate = func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  3985  		calledFlags = flags
  3986  		return nil, nil
  3987  	}
  3988  	assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
  3989  		return nil
  3990  	}
  3991  
  3992  	d := s.daemon(c)
  3993  	inst := &snapInstruction{
  3994  		Action:  "refresh",
  3995  		Classic: true,
  3996  		Snaps:   []string{"some-snap"},
  3997  		userID:  17,
  3998  	}
  3999  
  4000  	st := d.overlord.State()
  4001  	st.Lock()
  4002  	defer st.Unlock()
  4003  	_, _, err := inst.dispatch()(inst, st)
  4004  	c.Check(err, check.IsNil)
  4005  
  4006  	c.Check(calledFlags, check.DeepEquals, snapstate.Flags{Classic: true})
  4007  }
  4008  
  4009  func (s *apiSuite) TestRefreshIgnoreValidation(c *check.C) {
  4010  	var calledFlags snapstate.Flags
  4011  	calledUserID := 0
  4012  	installQueue := []string{}
  4013  
  4014  	snapstateUpdate = func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  4015  		calledFlags = flags
  4016  		calledUserID = userID
  4017  		installQueue = append(installQueue, name)
  4018  
  4019  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  4020  		return state.NewTaskSet(t), nil
  4021  	}
  4022  	assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
  4023  		return nil
  4024  	}
  4025  
  4026  	d := s.daemon(c)
  4027  	inst := &snapInstruction{
  4028  		Action:           "refresh",
  4029  		IgnoreValidation: true,
  4030  		Snaps:            []string{"some-snap"},
  4031  		userID:           17,
  4032  	}
  4033  
  4034  	st := d.overlord.State()
  4035  	st.Lock()
  4036  	defer st.Unlock()
  4037  	summary, _, err := inst.dispatch()(inst, st)
  4038  	c.Check(err, check.IsNil)
  4039  
  4040  	flags := snapstate.Flags{}
  4041  	flags.IgnoreValidation = true
  4042  
  4043  	c.Check(calledFlags, check.DeepEquals, flags)
  4044  	c.Check(calledUserID, check.Equals, 17)
  4045  	c.Check(err, check.IsNil)
  4046  	c.Check(installQueue, check.DeepEquals, []string{"some-snap"})
  4047  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  4048  }
  4049  
  4050  func (s *apiSuite) TestRefreshCohort(c *check.C) {
  4051  	cohort := ""
  4052  
  4053  	snapstateUpdate = func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  4054  		cohort = opts.CohortKey
  4055  
  4056  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  4057  		return state.NewTaskSet(t), nil
  4058  	}
  4059  	assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
  4060  		return nil
  4061  	}
  4062  
  4063  	d := s.daemon(c)
  4064  	inst := &snapInstruction{
  4065  		Action: "refresh",
  4066  		Snaps:  []string{"some-snap"},
  4067  		snapRevisionOptions: snapRevisionOptions{
  4068  			CohortKey: "xyzzy",
  4069  		},
  4070  	}
  4071  
  4072  	st := d.overlord.State()
  4073  	st.Lock()
  4074  	defer st.Unlock()
  4075  	summary, _, err := inst.dispatch()(inst, st)
  4076  	c.Check(err, check.IsNil)
  4077  
  4078  	c.Check(cohort, check.Equals, "xyzzy")
  4079  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  4080  }
  4081  
  4082  func (s *apiSuite) TestRefreshLeaveCohort(c *check.C) {
  4083  	var leave *bool
  4084  
  4085  	snapstateUpdate = func(s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  4086  		leave = &opts.LeaveCohort
  4087  
  4088  		t := s.NewTask("fake-refresh-snap", "Doing a fake install")
  4089  		return state.NewTaskSet(t), nil
  4090  	}
  4091  	assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
  4092  		return nil
  4093  	}
  4094  
  4095  	d := s.daemon(c)
  4096  	inst := &snapInstruction{
  4097  		Action:              "refresh",
  4098  		snapRevisionOptions: snapRevisionOptions{LeaveCohort: true},
  4099  		Snaps:               []string{"some-snap"},
  4100  	}
  4101  
  4102  	st := d.overlord.State()
  4103  	st.Lock()
  4104  	defer st.Unlock()
  4105  	summary, _, err := inst.dispatch()(inst, st)
  4106  	c.Check(err, check.IsNil)
  4107  
  4108  	c.Check(*leave, check.Equals, true)
  4109  	c.Check(summary, check.Equals, `Refresh "some-snap" snap`)
  4110  }
  4111  
  4112  func (s *apiSuite) TestSwitchInstruction(c *check.C) {
  4113  	var cohort, channel string
  4114  	var leave *bool
  4115  	snapstateSwitch = func(s *state.State, name string, opts *snapstate.RevisionOptions) (*state.TaskSet, error) {
  4116  		cohort = opts.CohortKey
  4117  		leave = &opts.LeaveCohort
  4118  		channel = opts.Channel
  4119  
  4120  		t := s.NewTask("fake-switch", "Doing a fake switch")
  4121  		return state.NewTaskSet(t), nil
  4122  	}
  4123  
  4124  	d := s.daemon(c)
  4125  	st := d.overlord.State()
  4126  
  4127  	type T struct {
  4128  		channel string
  4129  		cohort  string
  4130  		leave   bool
  4131  		summary string
  4132  	}
  4133  	table := []T{
  4134  		{"", "some-cohort", false, `Switch "some-snap" snap to cohort "…me-cohort"`},
  4135  		{"some-channel", "", false, `Switch "some-snap" snap to channel "some-channel"`},
  4136  		{"some-channel", "some-cohort", false, `Switch "some-snap" snap to channel "some-channel" and cohort "…me-cohort"`},
  4137  		{"", "", true, `Switch "some-snap" snap away from cohort`},
  4138  		{"some-channel", "", true, `Switch "some-snap" snap to channel "some-channel" and away from cohort`},
  4139  	}
  4140  
  4141  	for _, t := range table {
  4142  		cohort, channel = "", ""
  4143  		leave = nil
  4144  		inst := &snapInstruction{
  4145  			Action: "switch",
  4146  			snapRevisionOptions: snapRevisionOptions{
  4147  				CohortKey:   t.cohort,
  4148  				LeaveCohort: t.leave,
  4149  				Channel:     t.channel,
  4150  			},
  4151  			Snaps: []string{"some-snap"},
  4152  		}
  4153  
  4154  		st.Lock()
  4155  		summary, _, err := inst.dispatch()(inst, st)
  4156  		st.Unlock()
  4157  		c.Check(err, check.IsNil)
  4158  
  4159  		c.Check(cohort, check.Equals, t.cohort)
  4160  		c.Check(channel, check.Equals, t.channel)
  4161  		c.Check(summary, check.Equals, t.summary)
  4162  		c.Check(*leave, check.Equals, t.leave)
  4163  	}
  4164  }
  4165  
  4166  func (s *apiSuite) TestPostSnapOp(c *check.C) {
  4167  	s.testPostSnapsOp(c, "application/json")
  4168  }
  4169  
  4170  func (s *apiSuite) TestPostSnapOpMoreComplexContentType(c *check.C) {
  4171  	s.testPostSnapsOp(c, "application/json; charset=utf-8")
  4172  }
  4173  
  4174  func (s *apiSuite) TestPostSnapOpInvalidCharset(c *check.C) {
  4175  	buf := bytes.NewBufferString(`{"action": "refresh"}`)
  4176  	req, err := http.NewRequest("POST", "/v2/snaps", buf)
  4177  	c.Assert(err, check.IsNil)
  4178  	req.Header.Set("Content-Type", "application/json; charset=iso-8859-1")
  4179  
  4180  	rsp := postSnaps(snapsCmd, req, nil).(*resp)
  4181  	c.Check(rsp.Status, check.Equals, 400)
  4182  	c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, "unknown charset in content type")
  4183  }
  4184  
  4185  func (s *apiSuite) testPostSnapsOp(c *check.C, contentType string) {
  4186  	assertstateRefreshSnapDeclarations = func(*state.State, int) error { return nil }
  4187  	snapstateUpdateMany = func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) {
  4188  		c.Check(names, check.HasLen, 0)
  4189  		t := s.NewTask("fake-refresh-all", "Refreshing everything")
  4190  		return []string{"fake1", "fake2"}, []*state.TaskSet{state.NewTaskSet(t)}, nil
  4191  	}
  4192  
  4193  	d := s.daemonWithOverlordMock(c)
  4194  
  4195  	buf := bytes.NewBufferString(`{"action": "refresh"}`)
  4196  	req, err := http.NewRequest("POST", "/v2/snaps", buf)
  4197  	c.Assert(err, check.IsNil)
  4198  	req.Header.Set("Content-Type", contentType)
  4199  
  4200  	rsp, ok := postSnaps(snapsCmd, req, nil).(*resp)
  4201  	c.Assert(ok, check.Equals, true)
  4202  	c.Check(rsp.Type, check.Equals, ResponseTypeAsync)
  4203  
  4204  	st := d.overlord.State()
  4205  	st.Lock()
  4206  	defer st.Unlock()
  4207  	chg := st.Change(rsp.Change)
  4208  	c.Check(chg.Summary(), check.Equals, `Refresh snaps "fake1", "fake2"`)
  4209  	var apiData map[string]interface{}
  4210  	c.Check(chg.Get("api-data", &apiData), check.IsNil)
  4211  	c.Check(apiData["snap-names"], check.DeepEquals, []interface{}{"fake1", "fake2"})
  4212  }
  4213  
  4214  func (s *apiSuite) TestRefreshAll(c *check.C) {
  4215  	refreshSnapDecls := false
  4216  	assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
  4217  		refreshSnapDecls = true
  4218  		return assertstate.RefreshSnapDeclarations(s, userID)
  4219  	}
  4220  	d := s.daemon(c)
  4221  
  4222  	for _, tst := range []struct {
  4223  		snaps []string
  4224  		msg   string
  4225  	}{
  4226  		{nil, "Refresh all snaps: no updates"},
  4227  		{[]string{"fake"}, `Refresh snap "fake"`},
  4228  		{[]string{"fake1", "fake2"}, `Refresh snaps "fake1", "fake2"`},
  4229  	} {
  4230  		refreshSnapDecls = false
  4231  
  4232  		snapstateUpdateMany = func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) {
  4233  			c.Check(names, check.HasLen, 0)
  4234  			t := s.NewTask("fake-refresh-all", "Refreshing everything")
  4235  			return tst.snaps, []*state.TaskSet{state.NewTaskSet(t)}, nil
  4236  		}
  4237  
  4238  		inst := &snapInstruction{Action: "refresh"}
  4239  		st := d.overlord.State()
  4240  		st.Lock()
  4241  		res, err := snapUpdateMany(inst, st)
  4242  		st.Unlock()
  4243  		c.Assert(err, check.IsNil)
  4244  		c.Check(res.Summary, check.Equals, tst.msg)
  4245  		c.Check(refreshSnapDecls, check.Equals, true)
  4246  	}
  4247  }
  4248  
  4249  func (s *apiSuite) TestRefreshAllNoChanges(c *check.C) {
  4250  	refreshSnapDecls := false
  4251  	assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
  4252  		refreshSnapDecls = true
  4253  		return assertstate.RefreshSnapDeclarations(s, userID)
  4254  	}
  4255  
  4256  	snapstateUpdateMany = func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) {
  4257  		c.Check(names, check.HasLen, 0)
  4258  		return nil, nil, nil
  4259  	}
  4260  
  4261  	d := s.daemon(c)
  4262  	inst := &snapInstruction{Action: "refresh"}
  4263  	st := d.overlord.State()
  4264  	st.Lock()
  4265  	res, err := snapUpdateMany(inst, st)
  4266  	st.Unlock()
  4267  	c.Assert(err, check.IsNil)
  4268  	c.Check(res.Summary, check.Equals, `Refresh all snaps: no updates`)
  4269  	c.Check(refreshSnapDecls, check.Equals, true)
  4270  }
  4271  
  4272  func (s *apiSuite) TestRefreshMany(c *check.C) {
  4273  	refreshSnapDecls := false
  4274  	assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
  4275  		refreshSnapDecls = true
  4276  		return nil
  4277  	}
  4278  
  4279  	snapstateUpdateMany = func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) {
  4280  		c.Check(names, check.HasLen, 2)
  4281  		t := s.NewTask("fake-refresh-2", "Refreshing two")
  4282  		return names, []*state.TaskSet{state.NewTaskSet(t)}, nil
  4283  	}
  4284  
  4285  	d := s.daemon(c)
  4286  	inst := &snapInstruction{Action: "refresh", Snaps: []string{"foo", "bar"}}
  4287  	st := d.overlord.State()
  4288  	st.Lock()
  4289  	res, err := snapUpdateMany(inst, st)
  4290  	st.Unlock()
  4291  	c.Assert(err, check.IsNil)
  4292  	c.Check(res.Summary, check.Equals, `Refresh snaps "foo", "bar"`)
  4293  	c.Check(res.Affected, check.DeepEquals, inst.Snaps)
  4294  	c.Check(refreshSnapDecls, check.Equals, true)
  4295  }
  4296  
  4297  func (s *apiSuite) TestRefreshMany1(c *check.C) {
  4298  	refreshSnapDecls := false
  4299  	assertstateRefreshSnapDeclarations = func(s *state.State, userID int) error {
  4300  		refreshSnapDecls = true
  4301  		return nil
  4302  	}
  4303  
  4304  	snapstateUpdateMany = func(_ context.Context, s *state.State, names []string, userID int, flags *snapstate.Flags) ([]string, []*state.TaskSet, error) {
  4305  		c.Check(names, check.HasLen, 1)
  4306  		t := s.NewTask("fake-refresh-1", "Refreshing one")
  4307  		return names, []*state.TaskSet{state.NewTaskSet(t)}, nil
  4308  	}
  4309  
  4310  	d := s.daemon(c)
  4311  	inst := &snapInstruction{Action: "refresh", Snaps: []string{"foo"}}
  4312  	st := d.overlord.State()
  4313  	st.Lock()
  4314  	res, err := snapUpdateMany(inst, st)
  4315  	st.Unlock()
  4316  	c.Assert(err, check.IsNil)
  4317  	c.Check(res.Summary, check.Equals, `Refresh snap "foo"`)
  4318  	c.Check(res.Affected, check.DeepEquals, inst.Snaps)
  4319  	c.Check(refreshSnapDecls, check.Equals, true)
  4320  }
  4321  
  4322  func (s *apiSuite) TestInstallMany(c *check.C) {
  4323  	snapstateInstallMany = func(s *state.State, names []string, userID int) ([]string, []*state.TaskSet, error) {
  4324  		c.Check(names, check.HasLen, 2)
  4325  		t := s.NewTask("fake-install-2", "Install two")
  4326  		return names, []*state.TaskSet{state.NewTaskSet(t)}, nil
  4327  	}
  4328  
  4329  	d := s.daemon(c)
  4330  	inst := &snapInstruction{Action: "install", Snaps: []string{"foo", "bar"}}
  4331  	st := d.overlord.State()
  4332  	st.Lock()
  4333  	res, err := snapInstallMany(inst, st)
  4334  	st.Unlock()
  4335  	c.Assert(err, check.IsNil)
  4336  	c.Check(res.Summary, check.Equals, `Install snaps "foo", "bar"`)
  4337  	c.Check(res.Affected, check.DeepEquals, inst.Snaps)
  4338  }
  4339  
  4340  func (s *apiSuite) TestInstallManyEmptyName(c *check.C) {
  4341  	snapstateInstallMany = func(_ *state.State, _ []string, _ int) ([]string, []*state.TaskSet, error) {
  4342  		return nil, nil, errors.New("should not be called")
  4343  	}
  4344  	d := s.daemon(c)
  4345  	inst := &snapInstruction{Action: "install", Snaps: []string{"", "bar"}}
  4346  	st := d.overlord.State()
  4347  	st.Lock()
  4348  	res, err := snapInstallMany(inst, st)
  4349  	st.Unlock()
  4350  	c.Assert(res, check.IsNil)
  4351  	c.Assert(err, check.ErrorMatches, "cannot install snap with empty name")
  4352  }
  4353  
  4354  func (s *apiSuite) TestRemoveMany(c *check.C) {
  4355  	snapstateRemoveMany = func(s *state.State, names []string) ([]string, []*state.TaskSet, error) {
  4356  		c.Check(names, check.HasLen, 2)
  4357  		t := s.NewTask("fake-remove-2", "Remove two")
  4358  		return names, []*state.TaskSet{state.NewTaskSet(t)}, nil
  4359  	}
  4360  
  4361  	d := s.daemon(c)
  4362  	inst := &snapInstruction{Action: "remove", Snaps: []string{"foo", "bar"}}
  4363  	st := d.overlord.State()
  4364  	st.Lock()
  4365  	res, err := snapRemoveMany(inst, st)
  4366  	st.Unlock()
  4367  	c.Assert(err, check.IsNil)
  4368  	c.Check(res.Summary, check.Equals, `Remove snaps "foo", "bar"`)
  4369  	c.Check(res.Affected, check.DeepEquals, inst.Snaps)
  4370  }
  4371  
  4372  func (s *apiSuite) TestInstallFails(c *check.C) {
  4373  	snapstateInstall = func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  4374  		t := s.NewTask("fake-install-snap-error", "Install task")
  4375  		return state.NewTaskSet(t), nil
  4376  	}
  4377  
  4378  	d := s.daemonWithFakeSnapManager(c)
  4379  	s.vars = map[string]string{"name": "hello-world"}
  4380  	buf := bytes.NewBufferString(`{"action": "install"}`)
  4381  	req, err := http.NewRequest("POST", "/v2/snaps/hello-world", buf)
  4382  	c.Assert(err, check.IsNil)
  4383  
  4384  	rsp := postSnap(snapCmd, req, nil).(*resp)
  4385  
  4386  	c.Assert(rsp.Type, check.Equals, ResponseTypeAsync)
  4387  
  4388  	st := d.overlord.State()
  4389  	st.Lock()
  4390  	defer st.Unlock()
  4391  	chg := st.Change(rsp.Change)
  4392  	c.Assert(chg, check.NotNil)
  4393  
  4394  	c.Check(chg.Tasks(), check.HasLen, 1)
  4395  
  4396  	st.Unlock()
  4397  	s.waitTrivialChange(c, chg)
  4398  	st.Lock()
  4399  
  4400  	c.Check(chg.Err(), check.ErrorMatches, `(?sm).*Install task \(fake-install-snap-error errored\)`)
  4401  }
  4402  
  4403  func (s *apiSuite) TestInstallLeaveOld(c *check.C) {
  4404  	c.Skip("temporarily dropped half-baked support while sorting out flag mess")
  4405  	var calledFlags snapstate.Flags
  4406  
  4407  	snapstateInstall = func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  4408  		calledFlags = flags
  4409  
  4410  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  4411  		return state.NewTaskSet(t), nil
  4412  	}
  4413  
  4414  	d := s.daemon(c)
  4415  	inst := &snapInstruction{
  4416  		Action:   "install",
  4417  		LeaveOld: true,
  4418  	}
  4419  
  4420  	st := d.overlord.State()
  4421  	st.Lock()
  4422  	defer st.Unlock()
  4423  	_, _, err := inst.dispatch()(inst, st)
  4424  	c.Assert(err, check.IsNil)
  4425  
  4426  	c.Check(calledFlags, check.DeepEquals, snapstate.Flags{})
  4427  	c.Check(err, check.IsNil)
  4428  }
  4429  
  4430  func (s *apiSuite) TestInstall(c *check.C) {
  4431  	var calledName string
  4432  
  4433  	snapstateInstall = func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  4434  		calledName = name
  4435  
  4436  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  4437  		return state.NewTaskSet(t), nil
  4438  	}
  4439  
  4440  	d := s.daemon(c)
  4441  	inst := &snapInstruction{
  4442  		Action: "install",
  4443  		// Install the snap in developer mode
  4444  		DevMode: true,
  4445  		Snaps:   []string{"fake"},
  4446  	}
  4447  
  4448  	st := d.overlord.State()
  4449  	st.Lock()
  4450  	defer st.Unlock()
  4451  	_, _, err := inst.dispatch()(inst, st)
  4452  	c.Check(err, check.IsNil)
  4453  	c.Check(calledName, check.Equals, "fake")
  4454  }
  4455  
  4456  func (s *apiSuite) TestInstallCohort(c *check.C) {
  4457  	var calledName string
  4458  	var calledCohort string
  4459  
  4460  	snapstateInstall = func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  4461  		calledName = name
  4462  		calledCohort = opts.CohortKey
  4463  
  4464  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  4465  		return state.NewTaskSet(t), nil
  4466  	}
  4467  
  4468  	d := s.daemon(c)
  4469  	inst := &snapInstruction{
  4470  		Action: "install",
  4471  		snapRevisionOptions: snapRevisionOptions{
  4472  			CohortKey: "To the legion of the lost ones, to the cohort of the damned.",
  4473  		},
  4474  		Snaps: []string{"fake"},
  4475  	}
  4476  
  4477  	st := d.overlord.State()
  4478  	st.Lock()
  4479  	defer st.Unlock()
  4480  	msg, _, err := inst.dispatch()(inst, st)
  4481  	c.Check(err, check.IsNil)
  4482  	c.Check(calledName, check.Equals, "fake")
  4483  	c.Check(calledCohort, check.Equals, "To the legion of the lost ones, to the cohort of the damned.")
  4484  	c.Check(msg, check.Equals, `Install "fake" snap from "…e damned." cohort`)
  4485  }
  4486  
  4487  func (s *apiSuite) TestInstallDevMode(c *check.C) {
  4488  	var calledFlags snapstate.Flags
  4489  
  4490  	snapstateInstall = func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  4491  		calledFlags = flags
  4492  
  4493  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  4494  		return state.NewTaskSet(t), nil
  4495  	}
  4496  
  4497  	d := s.daemon(c)
  4498  	inst := &snapInstruction{
  4499  		Action: "install",
  4500  		// Install the snap in developer mode
  4501  		DevMode: true,
  4502  		Snaps:   []string{"fake"},
  4503  	}
  4504  
  4505  	st := d.overlord.State()
  4506  	st.Lock()
  4507  	defer st.Unlock()
  4508  	_, _, err := inst.dispatch()(inst, st)
  4509  	c.Check(err, check.IsNil)
  4510  
  4511  	c.Check(calledFlags.DevMode, check.Equals, true)
  4512  }
  4513  
  4514  func (s *apiSuite) TestInstallJailMode(c *check.C) {
  4515  	var calledFlags snapstate.Flags
  4516  
  4517  	snapstateInstall = func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  4518  		calledFlags = flags
  4519  
  4520  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  4521  		return state.NewTaskSet(t), nil
  4522  	}
  4523  
  4524  	d := s.daemon(c)
  4525  	inst := &snapInstruction{
  4526  		Action:   "install",
  4527  		JailMode: true,
  4528  		Snaps:    []string{"fake"},
  4529  	}
  4530  
  4531  	st := d.overlord.State()
  4532  	st.Lock()
  4533  	defer st.Unlock()
  4534  	_, _, err := inst.dispatch()(inst, st)
  4535  	c.Check(err, check.IsNil)
  4536  
  4537  	c.Check(calledFlags.JailMode, check.Equals, true)
  4538  }
  4539  
  4540  func (s *apiSuite) TestInstallJailModeDevModeOS(c *check.C) {
  4541  	restore := sandbox.MockForceDevMode(true)
  4542  	defer restore()
  4543  
  4544  	d := s.daemon(c)
  4545  	inst := &snapInstruction{
  4546  		Action:   "install",
  4547  		JailMode: true,
  4548  		Snaps:    []string{"foo"},
  4549  	}
  4550  
  4551  	st := d.overlord.State()
  4552  	st.Lock()
  4553  	defer st.Unlock()
  4554  	_, _, err := inst.dispatch()(inst, st)
  4555  	c.Check(err, check.ErrorMatches, "this system cannot honour the jailmode flag")
  4556  }
  4557  
  4558  func (s *apiSuite) TestInstallEmptyName(c *check.C) {
  4559  	snapstateInstall = func(ctx context.Context, _ *state.State, _ string, _ *snapstate.RevisionOptions, _ int, _ snapstate.Flags) (*state.TaskSet, error) {
  4560  		return nil, errors.New("should not be called")
  4561  	}
  4562  	d := s.daemon(c)
  4563  	inst := &snapInstruction{
  4564  		Action: "install",
  4565  		Snaps:  []string{""},
  4566  	}
  4567  
  4568  	st := d.overlord.State()
  4569  	st.Lock()
  4570  	defer st.Unlock()
  4571  	_, _, err := inst.dispatch()(inst, st)
  4572  	c.Check(err, check.ErrorMatches, "cannot install snap with empty name")
  4573  }
  4574  
  4575  func (s *apiSuite) TestInstallJailModeDevMode(c *check.C) {
  4576  	d := s.daemon(c)
  4577  	inst := &snapInstruction{
  4578  		Action:   "install",
  4579  		DevMode:  true,
  4580  		JailMode: true,
  4581  		Snaps:    []string{"foo"},
  4582  	}
  4583  
  4584  	st := d.overlord.State()
  4585  	st.Lock()
  4586  	defer st.Unlock()
  4587  	_, _, err := inst.dispatch()(inst, st)
  4588  	c.Check(err, check.ErrorMatches, "cannot use devmode and jailmode flags together")
  4589  }
  4590  
  4591  func (s *apiSuite) testRevertSnap(inst *snapInstruction, c *check.C) {
  4592  	queue := []string{}
  4593  
  4594  	instFlags, err := inst.modeFlags()
  4595  	c.Assert(err, check.IsNil)
  4596  
  4597  	snapstateRevert = func(s *state.State, name string, flags snapstate.Flags) (*state.TaskSet, error) {
  4598  		c.Check(flags, check.Equals, instFlags)
  4599  		queue = append(queue, name)
  4600  		return nil, nil
  4601  	}
  4602  	snapstateRevertToRevision = func(s *state.State, name string, rev snap.Revision, flags snapstate.Flags) (*state.TaskSet, error) {
  4603  		c.Check(flags, check.Equals, instFlags)
  4604  		queue = append(queue, fmt.Sprintf("%s (%s)", name, rev))
  4605  		return nil, nil
  4606  	}
  4607  
  4608  	d := s.daemon(c)
  4609  	inst.Action = "revert"
  4610  	inst.Snaps = []string{"some-snap"}
  4611  
  4612  	st := d.overlord.State()
  4613  	st.Lock()
  4614  	defer st.Unlock()
  4615  	summary, _, err := inst.dispatch()(inst, st)
  4616  	c.Check(err, check.IsNil)
  4617  	if inst.Revision.Unset() {
  4618  		c.Check(queue, check.DeepEquals, []string{inst.Snaps[0]})
  4619  	} else {
  4620  		c.Check(queue, check.DeepEquals, []string{fmt.Sprintf("%s (%s)", inst.Snaps[0], inst.Revision)})
  4621  	}
  4622  	c.Check(summary, check.Equals, `Revert "some-snap" snap`)
  4623  }
  4624  
  4625  func (s *apiSuite) TestRevertSnap(c *check.C) {
  4626  	s.testRevertSnap(&snapInstruction{}, c)
  4627  }
  4628  
  4629  func (s *apiSuite) TestRevertSnapDevMode(c *check.C) {
  4630  	s.testRevertSnap(&snapInstruction{DevMode: true}, c)
  4631  }
  4632  
  4633  func (s *apiSuite) TestRevertSnapJailMode(c *check.C) {
  4634  	s.testRevertSnap(&snapInstruction{JailMode: true}, c)
  4635  }
  4636  
  4637  func (s *apiSuite) TestRevertSnapClassic(c *check.C) {
  4638  	s.testRevertSnap(&snapInstruction{Classic: true}, c)
  4639  }
  4640  
  4641  func (s *apiSuite) TestRevertSnapToRevision(c *check.C) {
  4642  	s.testRevertSnap(&snapInstruction{snapRevisionOptions: snapRevisionOptions{Revision: snap.R(1)}}, c)
  4643  }
  4644  
  4645  func (s *apiSuite) TestRevertSnapToRevisionDevMode(c *check.C) {
  4646  	s.testRevertSnap(&snapInstruction{snapRevisionOptions: snapRevisionOptions{Revision: snap.R(1)}, DevMode: true}, c)
  4647  }
  4648  
  4649  func (s *apiSuite) TestRevertSnapToRevisionJailMode(c *check.C) {
  4650  	s.testRevertSnap(&snapInstruction{snapRevisionOptions: snapRevisionOptions{Revision: snap.R(1)}, JailMode: true}, c)
  4651  }
  4652  
  4653  func (s *apiSuite) TestRevertSnapToRevisionClassic(c *check.C) {
  4654  	s.testRevertSnap(&snapInstruction{snapRevisionOptions: snapRevisionOptions{Revision: snap.R(1)}, Classic: true}, c)
  4655  }
  4656  
  4657  func snapList(rawSnaps interface{}) []map[string]interface{} {
  4658  	snaps := make([]map[string]interface{}, len(rawSnaps.([]*json.RawMessage)))
  4659  	for i, raw := range rawSnaps.([]*json.RawMessage) {
  4660  		err := json.Unmarshal([]byte(*raw), &snaps[i])
  4661  		if err != nil {
  4662  			panic(err)
  4663  		}
  4664  	}
  4665  	return snaps
  4666  }
  4667  
  4668  // inverseCaseMapper implements SnapMapper to use lower case internally and upper case externally.
  4669  type inverseCaseMapper struct {
  4670  	ifacestate.IdentityMapper // Embed the identity mapper to reuse empty state mapping functions.
  4671  }
  4672  
  4673  func (m *inverseCaseMapper) RemapSnapFromRequest(snapName string) string {
  4674  	return strings.ToLower(snapName)
  4675  }
  4676  
  4677  func (m *inverseCaseMapper) RemapSnapToResponse(snapName string) string {
  4678  	return strings.ToUpper(snapName)
  4679  }
  4680  
  4681  func (m *inverseCaseMapper) SystemSnapName() string {
  4682  	return "core"
  4683  }
  4684  
  4685  // Tests for GET /v2/interfaces
  4686  
  4687  func (s *apiSuite) TestInterfacesLegacy(c *check.C) {
  4688  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  4689  	defer restore()
  4690  	// Install an inverse case mapper to exercise the interface mapping at the same time.
  4691  	restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
  4692  	defer restore()
  4693  
  4694  	d := s.daemon(c)
  4695  
  4696  	var anotherConsumerYaml = `
  4697  name: another-consumer-%s
  4698  version: 1
  4699  apps:
  4700   app:
  4701  plugs:
  4702   plug:
  4703    interface: test
  4704    key: value
  4705    label: label
  4706  `
  4707  	s.mockSnap(c, consumerYaml)
  4708  	s.mockSnap(c, fmt.Sprintf(anotherConsumerYaml, "def"))
  4709  	s.mockSnap(c, fmt.Sprintf(anotherConsumerYaml, "abc"))
  4710  	s.mockSnap(c, producerYaml)
  4711  
  4712  	repo := d.overlord.InterfaceManager().Repository()
  4713  	connRef := &interfaces.ConnRef{
  4714  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  4715  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
  4716  	}
  4717  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
  4718  	c.Assert(err, check.IsNil)
  4719  
  4720  	st := s.d.overlord.State()
  4721  	st.Lock()
  4722  	st.Set("conns", map[string]interface{}{
  4723  		"consumer:plug producer:slot": map[string]interface{}{
  4724  			"interface": "test",
  4725  			"auto":      true,
  4726  		},
  4727  		"another-consumer-def:plug producer:slot": map[string]interface{}{
  4728  			"interface": "test",
  4729  			"by-gadget": true,
  4730  			"auto":      true,
  4731  		},
  4732  		"another-consumer-abc:plug producer:slot": map[string]interface{}{
  4733  			"interface": "test",
  4734  			"by-gadget": true,
  4735  			"auto":      true,
  4736  		},
  4737  	})
  4738  	st.Unlock()
  4739  
  4740  	req, err := http.NewRequest("GET", "/v2/interfaces", nil)
  4741  	c.Assert(err, check.IsNil)
  4742  	rec := httptest.NewRecorder()
  4743  	interfacesCmd.GET(interfacesCmd, req, nil).ServeHTTP(rec, req)
  4744  	c.Check(rec.Code, check.Equals, 200)
  4745  	var body map[string]interface{}
  4746  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  4747  	c.Check(err, check.IsNil)
  4748  	c.Check(body, check.DeepEquals, map[string]interface{}{
  4749  		"result": map[string]interface{}{
  4750  			"plugs": []interface{}{
  4751  				map[string]interface{}{
  4752  					"snap":      "another-consumer-abc",
  4753  					"plug":      "plug",
  4754  					"interface": "test",
  4755  					"attrs":     map[string]interface{}{"key": "value"},
  4756  					"apps":      []interface{}{"app"},
  4757  					"label":     "label",
  4758  					"connections": []interface{}{
  4759  						map[string]interface{}{"snap": "producer", "slot": "slot"},
  4760  					},
  4761  				},
  4762  				map[string]interface{}{
  4763  					"snap":      "another-consumer-def",
  4764  					"plug":      "plug",
  4765  					"interface": "test",
  4766  					"attrs":     map[string]interface{}{"key": "value"},
  4767  					"apps":      []interface{}{"app"},
  4768  					"label":     "label",
  4769  					"connections": []interface{}{
  4770  						map[string]interface{}{"snap": "producer", "slot": "slot"},
  4771  					},
  4772  				},
  4773  				map[string]interface{}{
  4774  					"snap":      "consumer",
  4775  					"plug":      "plug",
  4776  					"interface": "test",
  4777  					"attrs":     map[string]interface{}{"key": "value"},
  4778  					"apps":      []interface{}{"app"},
  4779  					"label":     "label",
  4780  					"connections": []interface{}{
  4781  						map[string]interface{}{"snap": "producer", "slot": "slot"},
  4782  					},
  4783  				},
  4784  			},
  4785  			"slots": []interface{}{
  4786  				map[string]interface{}{
  4787  					"snap":      "producer",
  4788  					"slot":      "slot",
  4789  					"interface": "test",
  4790  					"attrs":     map[string]interface{}{"key": "value"},
  4791  					"apps":      []interface{}{"app"},
  4792  					"label":     "label",
  4793  					"connections": []interface{}{
  4794  						map[string]interface{}{"snap": "another-consumer-abc", "plug": "plug"},
  4795  						map[string]interface{}{"snap": "another-consumer-def", "plug": "plug"},
  4796  						map[string]interface{}{"snap": "consumer", "plug": "plug"},
  4797  					},
  4798  				},
  4799  			},
  4800  		},
  4801  		"status":      "OK",
  4802  		"status-code": 200.0,
  4803  		"type":        "sync",
  4804  	})
  4805  }
  4806  
  4807  func (s *apiSuite) TestInterfacesModern(c *check.C) {
  4808  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  4809  	defer restore()
  4810  	// Install an inverse case mapper to exercise the interface mapping at the same time.
  4811  	restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
  4812  	defer restore()
  4813  
  4814  	d := s.daemon(c)
  4815  
  4816  	s.mockSnap(c, consumerYaml)
  4817  	s.mockSnap(c, producerYaml)
  4818  
  4819  	repo := d.overlord.InterfaceManager().Repository()
  4820  	connRef := &interfaces.ConnRef{
  4821  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  4822  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
  4823  	}
  4824  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
  4825  	c.Assert(err, check.IsNil)
  4826  
  4827  	req, err := http.NewRequest("GET", "/v2/interfaces?select=connected&doc=true&plugs=true&slots=true", nil)
  4828  	c.Assert(err, check.IsNil)
  4829  	rec := httptest.NewRecorder()
  4830  	interfacesCmd.GET(interfacesCmd, req, nil).ServeHTTP(rec, req)
  4831  	c.Check(rec.Code, check.Equals, 200)
  4832  	var body map[string]interface{}
  4833  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  4834  	c.Check(err, check.IsNil)
  4835  	c.Check(body, check.DeepEquals, map[string]interface{}{
  4836  		"result": []interface{}{
  4837  			map[string]interface{}{
  4838  				"name": "test",
  4839  				"plugs": []interface{}{
  4840  					map[string]interface{}{
  4841  						"snap":  "consumer",
  4842  						"plug":  "plug",
  4843  						"label": "label",
  4844  						"attrs": map[string]interface{}{
  4845  							"key": "value",
  4846  						},
  4847  					}},
  4848  				"slots": []interface{}{
  4849  					map[string]interface{}{
  4850  						"snap":  "producer",
  4851  						"slot":  "slot",
  4852  						"label": "label",
  4853  						"attrs": map[string]interface{}{
  4854  							"key": "value",
  4855  						},
  4856  					},
  4857  				},
  4858  			},
  4859  		},
  4860  		"status":      "OK",
  4861  		"status-code": 200.0,
  4862  		"type":        "sync",
  4863  	})
  4864  }
  4865  
  4866  // Test for POST /v2/interfaces
  4867  
  4868  func (s *apiSuite) TestConnectPlugSuccess(c *check.C) {
  4869  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  4870  	defer restore()
  4871  	// Install an inverse case mapper to exercise the interface mapping at the same time.
  4872  	restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
  4873  	defer restore()
  4874  
  4875  	d := s.daemon(c)
  4876  
  4877  	s.mockSnap(c, consumerYaml)
  4878  	s.mockSnap(c, producerYaml)
  4879  
  4880  	d.overlord.Loop()
  4881  	defer d.overlord.Stop()
  4882  
  4883  	action := &interfaceAction{
  4884  		Action: "connect",
  4885  		Plugs:  []plugJSON{{Snap: "CONSUMER", Name: "plug"}},
  4886  		Slots:  []slotJSON{{Snap: "PRODUCER", Name: "slot"}},
  4887  	}
  4888  	text, err := json.Marshal(action)
  4889  	c.Assert(err, check.IsNil)
  4890  	buf := bytes.NewBuffer(text)
  4891  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  4892  	c.Assert(err, check.IsNil)
  4893  	rec := httptest.NewRecorder()
  4894  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  4895  	c.Check(rec.Code, check.Equals, 202)
  4896  	var body map[string]interface{}
  4897  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  4898  	c.Check(err, check.IsNil)
  4899  	id := body["change"].(string)
  4900  
  4901  	st := d.overlord.State()
  4902  	st.Lock()
  4903  	chg := st.Change(id)
  4904  	st.Unlock()
  4905  	c.Assert(chg, check.NotNil)
  4906  
  4907  	<-chg.Ready()
  4908  
  4909  	st.Lock()
  4910  	err = chg.Err()
  4911  	st.Unlock()
  4912  	c.Assert(err, check.IsNil)
  4913  
  4914  	repo := d.overlord.InterfaceManager().Repository()
  4915  	ifaces := repo.Interfaces()
  4916  	c.Assert(ifaces.Connections, check.HasLen, 1)
  4917  	c.Check(ifaces.Connections, check.DeepEquals, []*interfaces.ConnRef{{
  4918  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  4919  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
  4920  	}})
  4921  }
  4922  
  4923  func (s *apiSuite) TestConnectPlugFailureInterfaceMismatch(c *check.C) {
  4924  	d := s.daemon(c)
  4925  
  4926  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4927  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "different"})
  4928  	s.mockSnap(c, consumerYaml)
  4929  	s.mockSnap(c, differentProducerYaml)
  4930  
  4931  	action := &interfaceAction{
  4932  		Action: "connect",
  4933  		Plugs:  []plugJSON{{Snap: "consumer", Name: "plug"}},
  4934  		Slots:  []slotJSON{{Snap: "producer", Name: "slot"}},
  4935  	}
  4936  	text, err := json.Marshal(action)
  4937  	c.Assert(err, check.IsNil)
  4938  	buf := bytes.NewBuffer(text)
  4939  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  4940  	c.Assert(err, check.IsNil)
  4941  	rec := httptest.NewRecorder()
  4942  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  4943  	c.Check(rec.Code, check.Equals, 400)
  4944  	var body map[string]interface{}
  4945  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  4946  	c.Check(err, check.IsNil)
  4947  	c.Check(body, check.DeepEquals, map[string]interface{}{
  4948  		"result": map[string]interface{}{
  4949  			"message": "cannot connect consumer:plug (\"test\" interface) to producer:slot (\"different\" interface)",
  4950  		},
  4951  		"status":      "Bad Request",
  4952  		"status-code": 400.0,
  4953  		"type":        "error",
  4954  	})
  4955  	repo := d.overlord.InterfaceManager().Repository()
  4956  	ifaces := repo.Interfaces()
  4957  	c.Assert(ifaces.Connections, check.HasLen, 0)
  4958  }
  4959  
  4960  func (s *apiSuite) TestConnectPlugFailureNoSuchPlug(c *check.C) {
  4961  	d := s.daemon(c)
  4962  
  4963  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  4964  	// there is no consumer, no plug defined
  4965  	s.mockSnap(c, producerYaml)
  4966  	s.mockSnap(c, consumerYaml)
  4967  
  4968  	action := &interfaceAction{
  4969  		Action: "connect",
  4970  		Plugs:  []plugJSON{{Snap: "consumer", Name: "missingplug"}},
  4971  		Slots:  []slotJSON{{Snap: "producer", Name: "slot"}},
  4972  	}
  4973  	text, err := json.Marshal(action)
  4974  	c.Assert(err, check.IsNil)
  4975  	buf := bytes.NewBuffer(text)
  4976  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  4977  	c.Assert(err, check.IsNil)
  4978  	rec := httptest.NewRecorder()
  4979  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  4980  	c.Check(rec.Code, check.Equals, 400)
  4981  
  4982  	var body map[string]interface{}
  4983  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  4984  	c.Check(err, check.IsNil)
  4985  	c.Check(body, check.DeepEquals, map[string]interface{}{
  4986  		"result": map[string]interface{}{
  4987  			"message": "snap \"consumer\" has no plug named \"missingplug\"",
  4988  		},
  4989  		"status":      "Bad Request",
  4990  		"status-code": 400.0,
  4991  		"type":        "error",
  4992  	})
  4993  
  4994  	repo := d.overlord.InterfaceManager().Repository()
  4995  	ifaces := repo.Interfaces()
  4996  	c.Assert(ifaces.Connections, check.HasLen, 0)
  4997  }
  4998  
  4999  func (s *apiSuite) TestConnectAlreadyConnected(c *check.C) {
  5000  	d := s.daemon(c)
  5001  
  5002  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  5003  	// there is no consumer, no plug defined
  5004  	s.mockSnap(c, producerYaml)
  5005  	s.mockSnap(c, consumerYaml)
  5006  
  5007  	repo := d.overlord.InterfaceManager().Repository()
  5008  	connRef := &interfaces.ConnRef{
  5009  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  5010  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
  5011  	}
  5012  
  5013  	d.overlord.Loop()
  5014  	defer d.overlord.Stop()
  5015  
  5016  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
  5017  	c.Assert(err, check.IsNil)
  5018  	conns := map[string]interface{}{
  5019  		"consumer:plug producer:slot": map[string]interface{}{
  5020  			"auto": false,
  5021  		},
  5022  	}
  5023  	st := d.overlord.State()
  5024  	st.Lock()
  5025  	st.Set("conns", conns)
  5026  	st.Unlock()
  5027  
  5028  	action := &interfaceAction{
  5029  		Action: "connect",
  5030  		Plugs:  []plugJSON{{Snap: "consumer", Name: "plug"}},
  5031  		Slots:  []slotJSON{{Snap: "producer", Name: "slot"}},
  5032  	}
  5033  	text, err := json.Marshal(action)
  5034  	c.Assert(err, check.IsNil)
  5035  	buf := bytes.NewBuffer(text)
  5036  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5037  	c.Assert(err, check.IsNil)
  5038  	rec := httptest.NewRecorder()
  5039  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5040  	c.Check(rec.Code, check.Equals, 202)
  5041  	var body map[string]interface{}
  5042  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5043  	c.Check(err, check.IsNil)
  5044  	id := body["change"].(string)
  5045  
  5046  	st.Lock()
  5047  	chg := st.Change(id)
  5048  	c.Assert(chg.Tasks(), check.HasLen, 0)
  5049  	c.Assert(chg.Status(), check.Equals, state.DoneStatus)
  5050  	st.Unlock()
  5051  }
  5052  
  5053  func (s *apiSuite) TestConnectPlugFailureNoSuchSlot(c *check.C) {
  5054  	d := s.daemon(c)
  5055  
  5056  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  5057  	s.mockSnap(c, consumerYaml)
  5058  	s.mockSnap(c, producerYaml)
  5059  	// there is no producer, no slot defined
  5060  
  5061  	action := &interfaceAction{
  5062  		Action: "connect",
  5063  		Plugs:  []plugJSON{{Snap: "consumer", Name: "plug"}},
  5064  		Slots:  []slotJSON{{Snap: "producer", Name: "missingslot"}},
  5065  	}
  5066  	text, err := json.Marshal(action)
  5067  	c.Assert(err, check.IsNil)
  5068  	buf := bytes.NewBuffer(text)
  5069  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5070  	c.Assert(err, check.IsNil)
  5071  	rec := httptest.NewRecorder()
  5072  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5073  	c.Check(rec.Code, check.Equals, 400)
  5074  
  5075  	var body map[string]interface{}
  5076  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5077  	c.Check(err, check.IsNil)
  5078  	c.Check(body, check.DeepEquals, map[string]interface{}{
  5079  		"result": map[string]interface{}{
  5080  			"message": "snap \"producer\" has no slot named \"missingslot\"",
  5081  		},
  5082  		"status":      "Bad Request",
  5083  		"status-code": 400.0,
  5084  		"type":        "error",
  5085  	})
  5086  
  5087  	repo := d.overlord.InterfaceManager().Repository()
  5088  	ifaces := repo.Interfaces()
  5089  	c.Assert(ifaces.Connections, check.HasLen, 0)
  5090  }
  5091  
  5092  func (s *apiSuite) TestConnectPlugChangeConflict(c *check.C) {
  5093  	d := s.daemon(c)
  5094  
  5095  	s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
  5096  	s.mockSnap(c, consumerYaml)
  5097  	s.mockSnap(c, producerYaml)
  5098  	// there is no producer, no slot defined
  5099  
  5100  	simulateConflict(d.overlord, "consumer")
  5101  
  5102  	action := &interfaceAction{
  5103  		Action: "connect",
  5104  		Plugs:  []plugJSON{{Snap: "consumer", Name: "plug"}},
  5105  		Slots:  []slotJSON{{Snap: "producer", Name: "slot"}},
  5106  	}
  5107  	text, err := json.Marshal(action)
  5108  	c.Assert(err, check.IsNil)
  5109  	buf := bytes.NewBuffer(text)
  5110  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5111  	c.Assert(err, check.IsNil)
  5112  	rec := httptest.NewRecorder()
  5113  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5114  	c.Check(rec.Code, check.Equals, 409)
  5115  
  5116  	var body map[string]interface{}
  5117  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5118  	c.Check(err, check.IsNil)
  5119  	c.Check(body, check.DeepEquals, map[string]interface{}{
  5120  		"status-code": 409.,
  5121  		"status":      "Conflict",
  5122  		"result": map[string]interface{}{
  5123  			"message": `snap "consumer" has "manip" change in progress`,
  5124  			"kind":    "snap-change-conflict",
  5125  			"value": map[string]interface{}{
  5126  				"change-kind": "manip",
  5127  				"snap-name":   "consumer",
  5128  			},
  5129  		},
  5130  		"type": "error"})
  5131  }
  5132  
  5133  func (s *apiSuite) TestConnectCoreSystemAlias(c *check.C) {
  5134  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  5135  	defer revert()
  5136  	d := s.daemon(c)
  5137  
  5138  	s.mockSnap(c, consumerYaml)
  5139  	s.mockSnap(c, coreProducerYaml)
  5140  
  5141  	d.overlord.Loop()
  5142  	defer d.overlord.Stop()
  5143  
  5144  	action := &interfaceAction{
  5145  		Action: "connect",
  5146  		Plugs:  []plugJSON{{Snap: "consumer", Name: "plug"}},
  5147  		Slots:  []slotJSON{{Snap: "system", Name: "slot"}},
  5148  	}
  5149  	text, err := json.Marshal(action)
  5150  	c.Assert(err, check.IsNil)
  5151  	buf := bytes.NewBuffer(text)
  5152  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5153  	c.Assert(err, check.IsNil)
  5154  	rec := httptest.NewRecorder()
  5155  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5156  	c.Check(rec.Code, check.Equals, 202)
  5157  	var body map[string]interface{}
  5158  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5159  	c.Check(err, check.IsNil)
  5160  	id := body["change"].(string)
  5161  
  5162  	st := d.overlord.State()
  5163  	st.Lock()
  5164  	chg := st.Change(id)
  5165  	st.Unlock()
  5166  	c.Assert(chg, check.NotNil)
  5167  
  5168  	<-chg.Ready()
  5169  
  5170  	st.Lock()
  5171  	err = chg.Err()
  5172  	st.Unlock()
  5173  	c.Assert(err, check.IsNil)
  5174  
  5175  	repo := d.overlord.InterfaceManager().Repository()
  5176  	ifaces := repo.Interfaces()
  5177  	c.Assert(ifaces.Connections, check.HasLen, 1)
  5178  	c.Check(ifaces.Connections, check.DeepEquals, []*interfaces.ConnRef{{
  5179  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  5180  		SlotRef: interfaces.SlotRef{Snap: "core", Name: "slot"}}})
  5181  }
  5182  
  5183  func (s *apiSuite) testDisconnect(c *check.C, plugSnap, plugName, slotSnap, slotName string) {
  5184  	restore := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  5185  	defer restore()
  5186  	// Install an inverse case mapper to exercise the interface mapping at the same time.
  5187  	restore = ifacestate.MockSnapMapper(&inverseCaseMapper{})
  5188  	defer restore()
  5189  	d := s.daemon(c)
  5190  
  5191  	s.mockSnap(c, consumerYaml)
  5192  	s.mockSnap(c, producerYaml)
  5193  
  5194  	repo := d.overlord.InterfaceManager().Repository()
  5195  	connRef := &interfaces.ConnRef{
  5196  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  5197  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
  5198  	}
  5199  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
  5200  	c.Assert(err, check.IsNil)
  5201  
  5202  	st := d.overlord.State()
  5203  	st.Lock()
  5204  	st.Set("conns", map[string]interface{}{
  5205  		"consumer:plug producer:slot": map[string]interface{}{
  5206  			"interface": "test",
  5207  		},
  5208  	})
  5209  	st.Unlock()
  5210  
  5211  	d.overlord.Loop()
  5212  	defer d.overlord.Stop()
  5213  
  5214  	action := &interfaceAction{
  5215  		Action: "disconnect",
  5216  		Plugs:  []plugJSON{{Snap: plugSnap, Name: plugName}},
  5217  		Slots:  []slotJSON{{Snap: slotSnap, Name: slotName}},
  5218  	}
  5219  	text, err := json.Marshal(action)
  5220  	c.Assert(err, check.IsNil)
  5221  	buf := bytes.NewBuffer(text)
  5222  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5223  	c.Assert(err, check.IsNil)
  5224  	rec := httptest.NewRecorder()
  5225  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5226  	c.Check(rec.Code, check.Equals, 202)
  5227  	var body map[string]interface{}
  5228  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5229  	c.Check(err, check.IsNil)
  5230  	id := body["change"].(string)
  5231  
  5232  	st.Lock()
  5233  	chg := st.Change(id)
  5234  	st.Unlock()
  5235  	c.Assert(chg, check.NotNil)
  5236  
  5237  	<-chg.Ready()
  5238  
  5239  	st.Lock()
  5240  	err = chg.Err()
  5241  	st.Unlock()
  5242  	c.Assert(err, check.IsNil)
  5243  
  5244  	ifaces := repo.Interfaces()
  5245  	c.Assert(ifaces.Connections, check.HasLen, 0)
  5246  }
  5247  
  5248  func (s *apiSuite) TestDisconnectPlugSuccess(c *check.C) {
  5249  	s.testDisconnect(c, "CONSUMER", "plug", "PRODUCER", "slot")
  5250  }
  5251  
  5252  func (s *apiSuite) TestDisconnectPlugSuccessWithEmptyPlug(c *check.C) {
  5253  	s.testDisconnect(c, "", "", "PRODUCER", "slot")
  5254  }
  5255  
  5256  func (s *apiSuite) TestDisconnectPlugSuccessWithEmptySlot(c *check.C) {
  5257  	s.testDisconnect(c, "CONSUMER", "plug", "", "")
  5258  }
  5259  
  5260  func (s *apiSuite) TestDisconnectPlugFailureNoSuchPlug(c *check.C) {
  5261  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  5262  	defer revert()
  5263  	s.daemon(c)
  5264  
  5265  	// there is no consumer, no plug defined
  5266  	s.mockSnap(c, producerYaml)
  5267  
  5268  	action := &interfaceAction{
  5269  		Action: "disconnect",
  5270  		Plugs:  []plugJSON{{Snap: "consumer", Name: "plug"}},
  5271  		Slots:  []slotJSON{{Snap: "producer", Name: "slot"}},
  5272  	}
  5273  	text, err := json.Marshal(action)
  5274  	c.Assert(err, check.IsNil)
  5275  	buf := bytes.NewBuffer(text)
  5276  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5277  	c.Assert(err, check.IsNil)
  5278  	rec := httptest.NewRecorder()
  5279  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5280  	c.Check(rec.Code, check.Equals, 400)
  5281  	var body map[string]interface{}
  5282  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5283  	c.Check(err, check.IsNil)
  5284  	c.Check(body, check.DeepEquals, map[string]interface{}{
  5285  		"result": map[string]interface{}{
  5286  			"message": "snap \"consumer\" has no plug named \"plug\"",
  5287  		},
  5288  		"status":      "Bad Request",
  5289  		"status-code": 400.0,
  5290  		"type":        "error",
  5291  	})
  5292  }
  5293  
  5294  func (s *apiSuite) TestDisconnectPlugNothingToDo(c *check.C) {
  5295  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  5296  	defer revert()
  5297  	s.daemon(c)
  5298  
  5299  	s.mockSnap(c, consumerYaml)
  5300  	s.mockSnap(c, producerYaml)
  5301  
  5302  	action := &interfaceAction{
  5303  		Action: "disconnect",
  5304  		Plugs:  []plugJSON{{Snap: "consumer", Name: "plug"}},
  5305  		Slots:  []slotJSON{{Snap: "", Name: ""}},
  5306  	}
  5307  	text, err := json.Marshal(action)
  5308  	c.Assert(err, check.IsNil)
  5309  	buf := bytes.NewBuffer(text)
  5310  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5311  	c.Assert(err, check.IsNil)
  5312  	rec := httptest.NewRecorder()
  5313  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5314  	c.Check(rec.Code, check.Equals, 400)
  5315  	var body map[string]interface{}
  5316  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5317  	c.Check(err, check.IsNil)
  5318  	c.Check(body, check.DeepEquals, map[string]interface{}{
  5319  		"result": map[string]interface{}{
  5320  			"message": "nothing to do",
  5321  			"kind":    "interfaces-unchanged",
  5322  		},
  5323  		"status":      "Bad Request",
  5324  		"status-code": 400.0,
  5325  		"type":        "error",
  5326  	})
  5327  }
  5328  
  5329  func (s *apiSuite) TestDisconnectPlugFailureNoSuchSlot(c *check.C) {
  5330  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  5331  	defer revert()
  5332  	s.daemon(c)
  5333  
  5334  	s.mockSnap(c, consumerYaml)
  5335  	// there is no producer, no slot defined
  5336  
  5337  	action := &interfaceAction{
  5338  		Action: "disconnect",
  5339  		Plugs:  []plugJSON{{Snap: "consumer", Name: "plug"}},
  5340  		Slots:  []slotJSON{{Snap: "producer", Name: "slot"}},
  5341  	}
  5342  	text, err := json.Marshal(action)
  5343  	c.Assert(err, check.IsNil)
  5344  	buf := bytes.NewBuffer(text)
  5345  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5346  	c.Assert(err, check.IsNil)
  5347  	rec := httptest.NewRecorder()
  5348  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5349  
  5350  	c.Check(rec.Code, check.Equals, 400)
  5351  	var body map[string]interface{}
  5352  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5353  	c.Check(err, check.IsNil)
  5354  	c.Check(body, check.DeepEquals, map[string]interface{}{
  5355  		"result": map[string]interface{}{
  5356  			"message": "snap \"producer\" has no slot named \"slot\"",
  5357  		},
  5358  		"status":      "Bad Request",
  5359  		"status-code": 400.0,
  5360  		"type":        "error",
  5361  	})
  5362  }
  5363  
  5364  func (s *apiSuite) TestDisconnectPlugFailureNotConnected(c *check.C) {
  5365  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  5366  	defer revert()
  5367  	s.daemon(c)
  5368  
  5369  	s.mockSnap(c, consumerYaml)
  5370  	s.mockSnap(c, producerYaml)
  5371  
  5372  	action := &interfaceAction{
  5373  		Action: "disconnect",
  5374  		Plugs:  []plugJSON{{Snap: "consumer", Name: "plug"}},
  5375  		Slots:  []slotJSON{{Snap: "producer", Name: "slot"}},
  5376  	}
  5377  	text, err := json.Marshal(action)
  5378  	c.Assert(err, check.IsNil)
  5379  	buf := bytes.NewBuffer(text)
  5380  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5381  	c.Assert(err, check.IsNil)
  5382  	rec := httptest.NewRecorder()
  5383  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5384  
  5385  	c.Check(rec.Code, check.Equals, 400)
  5386  	var body map[string]interface{}
  5387  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5388  	c.Check(err, check.IsNil)
  5389  	c.Check(body, check.DeepEquals, map[string]interface{}{
  5390  		"result": map[string]interface{}{
  5391  			"message": "cannot disconnect consumer:plug from producer:slot, it is not connected",
  5392  		},
  5393  		"status":      "Bad Request",
  5394  		"status-code": 400.0,
  5395  		"type":        "error",
  5396  	})
  5397  }
  5398  
  5399  func (s *apiSuite) TestDisconnectForgetPlugFailureNotConnected(c *check.C) {
  5400  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  5401  	defer revert()
  5402  	s.daemon(c)
  5403  
  5404  	s.mockSnap(c, consumerYaml)
  5405  	s.mockSnap(c, producerYaml)
  5406  
  5407  	action := &interfaceAction{
  5408  		Action: "disconnect",
  5409  		Forget: true,
  5410  		Plugs:  []plugJSON{{Snap: "consumer", Name: "plug"}},
  5411  		Slots:  []slotJSON{{Snap: "producer", Name: "slot"}},
  5412  	}
  5413  	text, err := json.Marshal(action)
  5414  	c.Assert(err, check.IsNil)
  5415  	buf := bytes.NewBuffer(text)
  5416  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5417  	c.Assert(err, check.IsNil)
  5418  	rec := httptest.NewRecorder()
  5419  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5420  
  5421  	c.Check(rec.Code, check.Equals, 400)
  5422  	var body map[string]interface{}
  5423  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5424  	c.Check(err, check.IsNil)
  5425  	c.Check(body, check.DeepEquals, map[string]interface{}{
  5426  		"result": map[string]interface{}{
  5427  			"message": "cannot forget connection consumer:plug from producer:slot, it was not connected",
  5428  		},
  5429  		"status":      "Bad Request",
  5430  		"status-code": 400.0,
  5431  		"type":        "error",
  5432  	})
  5433  }
  5434  
  5435  func (s *apiSuite) TestDisconnectConflict(c *check.C) {
  5436  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  5437  	defer revert()
  5438  	d := s.daemon(c)
  5439  
  5440  	s.mockSnap(c, consumerYaml)
  5441  	s.mockSnap(c, producerYaml)
  5442  
  5443  	repo := d.overlord.InterfaceManager().Repository()
  5444  	connRef := &interfaces.ConnRef{
  5445  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  5446  		SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
  5447  	}
  5448  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
  5449  	c.Assert(err, check.IsNil)
  5450  
  5451  	st := d.overlord.State()
  5452  	st.Lock()
  5453  	st.Set("conns", map[string]interface{}{
  5454  		"consumer:plug producer:slot": map[string]interface{}{
  5455  			"interface": "test",
  5456  		},
  5457  	})
  5458  	st.Unlock()
  5459  
  5460  	simulateConflict(d.overlord, "consumer")
  5461  
  5462  	action := &interfaceAction{
  5463  		Action: "disconnect",
  5464  		Plugs:  []plugJSON{{Snap: "consumer", Name: "plug"}},
  5465  		Slots:  []slotJSON{{Snap: "producer", Name: "slot"}},
  5466  	}
  5467  	text, err := json.Marshal(action)
  5468  	c.Assert(err, check.IsNil)
  5469  	buf := bytes.NewBuffer(text)
  5470  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5471  	c.Assert(err, check.IsNil)
  5472  	rec := httptest.NewRecorder()
  5473  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5474  
  5475  	c.Check(rec.Code, check.Equals, 409)
  5476  
  5477  	var body map[string]interface{}
  5478  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5479  	c.Check(err, check.IsNil)
  5480  	c.Check(body, check.DeepEquals, map[string]interface{}{
  5481  		"status-code": 409.,
  5482  		"status":      "Conflict",
  5483  		"result": map[string]interface{}{
  5484  			"message": `snap "consumer" has "manip" change in progress`,
  5485  			"kind":    "snap-change-conflict",
  5486  			"value": map[string]interface{}{
  5487  				"change-kind": "manip",
  5488  				"snap-name":   "consumer",
  5489  			},
  5490  		},
  5491  		"type": "error"})
  5492  }
  5493  
  5494  func (s *apiSuite) TestDisconnectCoreSystemAlias(c *check.C) {
  5495  	revert := builtin.MockInterface(&ifacetest.TestInterface{InterfaceName: "test"})
  5496  	defer revert()
  5497  	d := s.daemon(c)
  5498  
  5499  	s.mockSnap(c, consumerYaml)
  5500  	s.mockSnap(c, coreProducerYaml)
  5501  
  5502  	repo := d.overlord.InterfaceManager().Repository()
  5503  	connRef := &interfaces.ConnRef{
  5504  		PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
  5505  		SlotRef: interfaces.SlotRef{Snap: "core", Name: "slot"},
  5506  	}
  5507  	_, err := repo.Connect(connRef, nil, nil, nil, nil, nil)
  5508  	c.Assert(err, check.IsNil)
  5509  
  5510  	st := d.overlord.State()
  5511  	st.Lock()
  5512  	st.Set("conns", map[string]interface{}{
  5513  		"consumer:plug core:slot": map[string]interface{}{
  5514  			"interface": "test",
  5515  		},
  5516  	})
  5517  	st.Unlock()
  5518  
  5519  	d.overlord.Loop()
  5520  	defer d.overlord.Stop()
  5521  
  5522  	action := &interfaceAction{
  5523  		Action: "disconnect",
  5524  		Plugs:  []plugJSON{{Snap: "consumer", Name: "plug"}},
  5525  		Slots:  []slotJSON{{Snap: "system", Name: "slot"}},
  5526  	}
  5527  	text, err := json.Marshal(action)
  5528  	c.Assert(err, check.IsNil)
  5529  	buf := bytes.NewBuffer(text)
  5530  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5531  	c.Assert(err, check.IsNil)
  5532  	rec := httptest.NewRecorder()
  5533  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5534  	c.Check(rec.Code, check.Equals, 202)
  5535  	var body map[string]interface{}
  5536  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5537  	c.Check(err, check.IsNil)
  5538  	id := body["change"].(string)
  5539  
  5540  	st.Lock()
  5541  	chg := st.Change(id)
  5542  	st.Unlock()
  5543  	c.Assert(chg, check.NotNil)
  5544  
  5545  	<-chg.Ready()
  5546  
  5547  	st.Lock()
  5548  	err = chg.Err()
  5549  	st.Unlock()
  5550  	c.Assert(err, check.IsNil)
  5551  
  5552  	ifaces := repo.Interfaces()
  5553  	c.Assert(ifaces.Connections, check.HasLen, 0)
  5554  }
  5555  
  5556  func (s *apiSuite) TestUnsupportedInterfaceRequest(c *check.C) {
  5557  	buf := bytes.NewBuffer([]byte(`garbage`))
  5558  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5559  	c.Assert(err, check.IsNil)
  5560  	rec := httptest.NewRecorder()
  5561  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5562  	c.Check(rec.Code, check.Equals, 400)
  5563  	var body map[string]interface{}
  5564  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5565  	c.Check(err, check.IsNil)
  5566  	c.Check(body, check.DeepEquals, map[string]interface{}{
  5567  		"result": map[string]interface{}{
  5568  			"message": "cannot decode request body into an interface action: invalid character 'g' looking for beginning of value",
  5569  		},
  5570  		"status":      "Bad Request",
  5571  		"status-code": 400.0,
  5572  		"type":        "error",
  5573  	})
  5574  }
  5575  
  5576  func (s *apiSuite) TestMissingInterfaceAction(c *check.C) {
  5577  	action := &interfaceAction{}
  5578  	text, err := json.Marshal(action)
  5579  	c.Assert(err, check.IsNil)
  5580  	buf := bytes.NewBuffer(text)
  5581  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5582  	c.Assert(err, check.IsNil)
  5583  	rec := httptest.NewRecorder()
  5584  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5585  	c.Check(rec.Code, check.Equals, 400)
  5586  	var body map[string]interface{}
  5587  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5588  	c.Check(err, check.IsNil)
  5589  	c.Check(body, check.DeepEquals, map[string]interface{}{
  5590  		"result": map[string]interface{}{
  5591  			"message": "interface action not specified",
  5592  		},
  5593  		"status":      "Bad Request",
  5594  		"status-code": 400.0,
  5595  		"type":        "error",
  5596  	})
  5597  }
  5598  
  5599  func (s *apiSuite) TestUnsupportedInterfaceAction(c *check.C) {
  5600  	s.daemon(c)
  5601  	action := &interfaceAction{Action: "foo"}
  5602  	text, err := json.Marshal(action)
  5603  	c.Assert(err, check.IsNil)
  5604  	buf := bytes.NewBuffer(text)
  5605  	req, err := http.NewRequest("POST", "/v2/interfaces", buf)
  5606  	c.Assert(err, check.IsNil)
  5607  	rec := httptest.NewRecorder()
  5608  	interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
  5609  	c.Check(rec.Code, check.Equals, 400)
  5610  	var body map[string]interface{}
  5611  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5612  	c.Check(err, check.IsNil)
  5613  	c.Check(body, check.DeepEquals, map[string]interface{}{
  5614  		"result": map[string]interface{}{
  5615  			"message": "unsupported interface action: \"foo\"",
  5616  		},
  5617  		"status":      "Bad Request",
  5618  		"status-code": 400.0,
  5619  		"type":        "error",
  5620  	})
  5621  }
  5622  
  5623  func setupChanges(st *state.State) []string {
  5624  	chg1 := st.NewChange("install", "install...")
  5625  	chg1.Set("snap-names", []string{"funky-snap-name"})
  5626  	t1 := st.NewTask("download", "1...")
  5627  	t2 := st.NewTask("activate", "2...")
  5628  	chg1.AddAll(state.NewTaskSet(t1, t2))
  5629  	t1.Logf("l11")
  5630  	t1.Logf("l12")
  5631  	chg2 := st.NewChange("remove", "remove..")
  5632  	t3 := st.NewTask("unlink", "1...")
  5633  	chg2.AddTask(t3)
  5634  	t3.SetStatus(state.ErrorStatus)
  5635  	t3.Errorf("rm failed")
  5636  
  5637  	return []string{chg1.ID(), chg2.ID(), t1.ID(), t2.ID(), t3.ID()}
  5638  }
  5639  
  5640  func (s *apiSuite) TestStateChangesDefaultToInProgress(c *check.C) {
  5641  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
  5642  	defer restore()
  5643  
  5644  	// Setup
  5645  	d := newTestDaemon(c)
  5646  	st := d.overlord.State()
  5647  	st.Lock()
  5648  	setupChanges(st)
  5649  	st.Unlock()
  5650  
  5651  	// Execute
  5652  	req, err := http.NewRequest("GET", "/v2/changes", nil)
  5653  	c.Assert(err, check.IsNil)
  5654  	rsp := getChanges(stateChangesCmd, req, nil).(*resp)
  5655  
  5656  	// Verify
  5657  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  5658  	c.Check(rsp.Status, check.Equals, 200)
  5659  	c.Assert(rsp.Result, check.HasLen, 1)
  5660  
  5661  	res, err := rsp.MarshalJSON()
  5662  	c.Assert(err, check.IsNil)
  5663  
  5664  	c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"install","summary":"install...","status":"Do","tasks":\[{"id":"\w+","kind":"download","summary":"1...","status":"Do","log":\["2016-04-21T01:02:03Z INFO l11","2016-04-21T01:02:03Z INFO l12"],"progress":{"label":"","done":0,"total":1},"spawn-time":"2016-04-21T01:02:03Z"}.*`)
  5665  }
  5666  
  5667  func (s *apiSuite) TestStateChangesInProgress(c *check.C) {
  5668  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
  5669  	defer restore()
  5670  
  5671  	// Setup
  5672  	d := newTestDaemon(c)
  5673  	st := d.overlord.State()
  5674  	st.Lock()
  5675  	setupChanges(st)
  5676  	st.Unlock()
  5677  
  5678  	// Execute
  5679  	req, err := http.NewRequest("GET", "/v2/changes?select=in-progress", nil)
  5680  	c.Assert(err, check.IsNil)
  5681  	rsp := getChanges(stateChangesCmd, req, nil).(*resp)
  5682  
  5683  	// Verify
  5684  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  5685  	c.Check(rsp.Status, check.Equals, 200)
  5686  	c.Assert(rsp.Result, check.HasLen, 1)
  5687  
  5688  	res, err := rsp.MarshalJSON()
  5689  	c.Assert(err, check.IsNil)
  5690  
  5691  	c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"install","summary":"install...","status":"Do","tasks":\[{"id":"\w+","kind":"download","summary":"1...","status":"Do","log":\["2016-04-21T01:02:03Z INFO l11","2016-04-21T01:02:03Z INFO l12"],"progress":{"label":"","done":0,"total":1},"spawn-time":"2016-04-21T01:02:03Z"}.*],"ready":false,"spawn-time":"2016-04-21T01:02:03Z"}.*`)
  5692  }
  5693  
  5694  func (s *apiSuite) TestStateChangesAll(c *check.C) {
  5695  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
  5696  	defer restore()
  5697  
  5698  	// Setup
  5699  	d := newTestDaemon(c)
  5700  	st := d.overlord.State()
  5701  	st.Lock()
  5702  	setupChanges(st)
  5703  	st.Unlock()
  5704  
  5705  	// Execute
  5706  	req, err := http.NewRequest("GET", "/v2/changes?select=all", nil)
  5707  	c.Assert(err, check.IsNil)
  5708  	rsp := getChanges(stateChangesCmd, req, nil).(*resp)
  5709  
  5710  	// Verify
  5711  	c.Check(rsp.Status, check.Equals, 200)
  5712  	c.Assert(rsp.Result, check.HasLen, 2)
  5713  
  5714  	res, err := rsp.MarshalJSON()
  5715  	c.Assert(err, check.IsNil)
  5716  
  5717  	c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"install","summary":"install...","status":"Do","tasks":\[{"id":"\w+","kind":"download","summary":"1...","status":"Do","log":\["2016-04-21T01:02:03Z INFO l11","2016-04-21T01:02:03Z INFO l12"],"progress":{"label":"","done":0,"total":1},"spawn-time":"2016-04-21T01:02:03Z"}.*],"ready":false,"spawn-time":"2016-04-21T01:02:03Z"}.*`)
  5718  	c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"remove","summary":"remove..","status":"Error","tasks":\[{"id":"\w+","kind":"unlink","summary":"1...","status":"Error","log":\["2016-04-21T01:02:03Z ERROR rm failed"],"progress":{"label":"","done":1,"total":1},"spawn-time":"2016-04-21T01:02:03Z","ready-time":"2016-04-21T01:02:03Z"}.*],"ready":true,"err":"[^"]+".*`)
  5719  }
  5720  
  5721  func (s *apiSuite) TestStateChangesReady(c *check.C) {
  5722  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
  5723  	defer restore()
  5724  
  5725  	// Setup
  5726  	d := newTestDaemon(c)
  5727  	st := d.overlord.State()
  5728  	st.Lock()
  5729  	setupChanges(st)
  5730  	st.Unlock()
  5731  
  5732  	// Execute
  5733  	req, err := http.NewRequest("GET", "/v2/changes?select=ready", nil)
  5734  	c.Assert(err, check.IsNil)
  5735  	rsp := getChanges(stateChangesCmd, req, nil).(*resp)
  5736  
  5737  	// Verify
  5738  	c.Check(rsp.Status, check.Equals, 200)
  5739  	c.Assert(rsp.Result, check.HasLen, 1)
  5740  
  5741  	res, err := rsp.MarshalJSON()
  5742  	c.Assert(err, check.IsNil)
  5743  
  5744  	c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"remove","summary":"remove..","status":"Error","tasks":\[{"id":"\w+","kind":"unlink","summary":"1...","status":"Error","log":\["2016-04-21T01:02:03Z ERROR rm failed"],"progress":{"label":"","done":1,"total":1},"spawn-time":"2016-04-21T01:02:03Z","ready-time":"2016-04-21T01:02:03Z"}.*],"ready":true,"err":"[^"]+".*`)
  5745  }
  5746  
  5747  func (s *apiSuite) TestStateChangesForSnapName(c *check.C) {
  5748  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
  5749  	defer restore()
  5750  
  5751  	// Setup
  5752  	d := newTestDaemon(c)
  5753  	st := d.overlord.State()
  5754  	st.Lock()
  5755  	setupChanges(st)
  5756  	st.Unlock()
  5757  
  5758  	// Execute
  5759  	req, err := http.NewRequest("GET", "/v2/changes?for=funky-snap-name&select=all", nil)
  5760  	c.Assert(err, check.IsNil)
  5761  	rsp := getChanges(stateChangesCmd, req, nil).(*resp)
  5762  
  5763  	// Verify
  5764  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  5765  	c.Check(rsp.Status, check.Equals, 200)
  5766  	c.Assert(rsp.Result, check.FitsTypeOf, []*changeInfo(nil))
  5767  
  5768  	res := rsp.Result.([]*changeInfo)
  5769  	c.Assert(res, check.HasLen, 1)
  5770  	c.Check(res[0].Kind, check.Equals, `install`)
  5771  
  5772  	_, err = rsp.MarshalJSON()
  5773  	c.Assert(err, check.IsNil)
  5774  }
  5775  
  5776  func (s *apiSuite) TestStateChangesForSnapNameWithApp(c *check.C) {
  5777  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
  5778  	defer restore()
  5779  
  5780  	// Setup
  5781  	d := newTestDaemon(c)
  5782  	st := d.overlord.State()
  5783  	st.Lock()
  5784  	chg1 := st.NewChange("service-control", "install...")
  5785  	// as triggered by snap restart lxd.daemon
  5786  	chg1.Set("snap-names", []string{"lxd.daemon"})
  5787  	t1 := st.NewTask("exec-command", "1...")
  5788  	chg1.AddAll(state.NewTaskSet(t1))
  5789  	t1.Logf("foobar")
  5790  
  5791  	st.Unlock()
  5792  
  5793  	// Execute
  5794  	req, err := http.NewRequest("GET", "/v2/changes?for=lxd&select=all", nil)
  5795  	c.Assert(err, check.IsNil)
  5796  	rsp := getChanges(stateChangesCmd, req, nil).(*resp)
  5797  
  5798  	// Verify
  5799  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  5800  	c.Check(rsp.Status, check.Equals, 200)
  5801  	c.Assert(rsp.Result, check.FitsTypeOf, []*changeInfo(nil))
  5802  
  5803  	res := rsp.Result.([]*changeInfo)
  5804  	c.Assert(res, check.HasLen, 1)
  5805  	c.Check(res[0].Kind, check.Equals, `service-control`)
  5806  
  5807  	_, err = rsp.MarshalJSON()
  5808  	c.Assert(err, check.IsNil)
  5809  }
  5810  
  5811  func (s *apiSuite) TestStateChange(c *check.C) {
  5812  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
  5813  	defer restore()
  5814  
  5815  	// Setup
  5816  	d := newTestDaemon(c)
  5817  	st := d.overlord.State()
  5818  	st.Lock()
  5819  	ids := setupChanges(st)
  5820  	chg := st.Change(ids[0])
  5821  	chg.Set("api-data", map[string]int{"n": 42})
  5822  	st.Unlock()
  5823  	s.vars = map[string]string{"id": ids[0]}
  5824  
  5825  	// Execute
  5826  	req, err := http.NewRequest("POST", "/v2/change/"+ids[0], nil)
  5827  	c.Assert(err, check.IsNil)
  5828  	rsp := getChange(stateChangeCmd, req, nil).(*resp)
  5829  	rec := httptest.NewRecorder()
  5830  	rsp.ServeHTTP(rec, req)
  5831  
  5832  	// Verify
  5833  	c.Check(rec.Code, check.Equals, 200)
  5834  	c.Check(rsp.Status, check.Equals, 200)
  5835  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  5836  	c.Check(rsp.Result, check.NotNil)
  5837  
  5838  	var body map[string]interface{}
  5839  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5840  	c.Check(err, check.IsNil)
  5841  	c.Check(body["result"], check.DeepEquals, map[string]interface{}{
  5842  		"id":         ids[0],
  5843  		"kind":       "install",
  5844  		"summary":    "install...",
  5845  		"status":     "Do",
  5846  		"ready":      false,
  5847  		"spawn-time": "2016-04-21T01:02:03Z",
  5848  		"tasks": []interface{}{
  5849  			map[string]interface{}{
  5850  				"id":         ids[2],
  5851  				"kind":       "download",
  5852  				"summary":    "1...",
  5853  				"status":     "Do",
  5854  				"log":        []interface{}{"2016-04-21T01:02:03Z INFO l11", "2016-04-21T01:02:03Z INFO l12"},
  5855  				"progress":   map[string]interface{}{"label": "", "done": 0., "total": 1.},
  5856  				"spawn-time": "2016-04-21T01:02:03Z",
  5857  			},
  5858  			map[string]interface{}{
  5859  				"id":         ids[3],
  5860  				"kind":       "activate",
  5861  				"summary":    "2...",
  5862  				"status":     "Do",
  5863  				"progress":   map[string]interface{}{"label": "", "done": 0., "total": 1.},
  5864  				"spawn-time": "2016-04-21T01:02:03Z",
  5865  			},
  5866  		},
  5867  		"data": map[string]interface{}{
  5868  			"n": float64(42),
  5869  		},
  5870  	})
  5871  }
  5872  
  5873  func (s *apiSuite) TestStateChangeAbort(c *check.C) {
  5874  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
  5875  	defer restore()
  5876  
  5877  	soon := 0
  5878  	ensureStateSoon = func(st *state.State) {
  5879  		soon++
  5880  	}
  5881  
  5882  	// Setup
  5883  	d := newTestDaemon(c)
  5884  	st := d.overlord.State()
  5885  	st.Lock()
  5886  	ids := setupChanges(st)
  5887  	st.Unlock()
  5888  	s.vars = map[string]string{"id": ids[0]}
  5889  
  5890  	buf := bytes.NewBufferString(`{"action": "abort"}`)
  5891  
  5892  	// Execute
  5893  	req, err := http.NewRequest("POST", "/v2/changes/"+ids[0], buf)
  5894  	c.Assert(err, check.IsNil)
  5895  	rsp := abortChange(stateChangeCmd, req, nil).(*resp)
  5896  	rec := httptest.NewRecorder()
  5897  	rsp.ServeHTTP(rec, req)
  5898  
  5899  	// Ensure scheduled
  5900  	c.Check(soon, check.Equals, 1)
  5901  
  5902  	// Verify
  5903  	c.Check(rec.Code, check.Equals, 200)
  5904  	c.Check(rsp.Status, check.Equals, 200)
  5905  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  5906  	c.Check(rsp.Result, check.NotNil)
  5907  
  5908  	var body map[string]interface{}
  5909  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5910  	c.Check(err, check.IsNil)
  5911  	c.Check(body["result"], check.DeepEquals, map[string]interface{}{
  5912  		"id":         ids[0],
  5913  		"kind":       "install",
  5914  		"summary":    "install...",
  5915  		"status":     "Hold",
  5916  		"ready":      true,
  5917  		"spawn-time": "2016-04-21T01:02:03Z",
  5918  		"ready-time": "2016-04-21T01:02:03Z",
  5919  		"tasks": []interface{}{
  5920  			map[string]interface{}{
  5921  				"id":         ids[2],
  5922  				"kind":       "download",
  5923  				"summary":    "1...",
  5924  				"status":     "Hold",
  5925  				"log":        []interface{}{"2016-04-21T01:02:03Z INFO l11", "2016-04-21T01:02:03Z INFO l12"},
  5926  				"progress":   map[string]interface{}{"label": "", "done": 1., "total": 1.},
  5927  				"spawn-time": "2016-04-21T01:02:03Z",
  5928  				"ready-time": "2016-04-21T01:02:03Z",
  5929  			},
  5930  			map[string]interface{}{
  5931  				"id":         ids[3],
  5932  				"kind":       "activate",
  5933  				"summary":    "2...",
  5934  				"status":     "Hold",
  5935  				"progress":   map[string]interface{}{"label": "", "done": 1., "total": 1.},
  5936  				"spawn-time": "2016-04-21T01:02:03Z",
  5937  				"ready-time": "2016-04-21T01:02:03Z",
  5938  			},
  5939  		},
  5940  	})
  5941  }
  5942  
  5943  func (s *apiSuite) TestStateChangeAbortIsReady(c *check.C) {
  5944  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
  5945  	defer restore()
  5946  
  5947  	// Setup
  5948  	d := newTestDaemon(c)
  5949  	st := d.overlord.State()
  5950  	st.Lock()
  5951  	ids := setupChanges(st)
  5952  	st.Change(ids[0]).SetStatus(state.DoneStatus)
  5953  	st.Unlock()
  5954  	s.vars = map[string]string{"id": ids[0]}
  5955  
  5956  	buf := bytes.NewBufferString(`{"action": "abort"}`)
  5957  
  5958  	// Execute
  5959  	req, err := http.NewRequest("POST", "/v2/changes/"+ids[0], buf)
  5960  	c.Assert(err, check.IsNil)
  5961  	rsp := abortChange(stateChangeCmd, req, nil).(*resp)
  5962  	rec := httptest.NewRecorder()
  5963  	rsp.ServeHTTP(rec, req)
  5964  
  5965  	// Verify
  5966  	c.Check(rec.Code, check.Equals, 400)
  5967  	c.Check(rsp.Status, check.Equals, 400)
  5968  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  5969  	c.Check(rsp.Result, check.NotNil)
  5970  
  5971  	var body map[string]interface{}
  5972  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  5973  	c.Check(err, check.IsNil)
  5974  	c.Check(body["result"], check.DeepEquals, map[string]interface{}{
  5975  		"message": fmt.Sprintf("cannot abort change %s with nothing pending", ids[0]),
  5976  	})
  5977  }
  5978  
  5979  const validBuyInput = `{
  5980  		  "snap-id": "the-snap-id-1234abcd",
  5981  		  "snap-name": "the snap name",
  5982  		  "price": 1.23,
  5983  		  "currency": "EUR"
  5984  		}`
  5985  
  5986  var validBuyOptions = &client.BuyOptions{
  5987  	SnapID:   "the-snap-id-1234abcd",
  5988  	Price:    1.23,
  5989  	Currency: "EUR",
  5990  }
  5991  
  5992  var buyTests = []struct {
  5993  	input                string
  5994  	result               *client.BuyResult
  5995  	err                  error
  5996  	expectedStatus       int
  5997  	expectedResult       interface{}
  5998  	expectedResponseType ResponseType
  5999  	expectedBuyOptions   *client.BuyOptions
  6000  }{
  6001  	{
  6002  		// Success
  6003  		input: validBuyInput,
  6004  		result: &client.BuyResult{
  6005  			State: "Complete",
  6006  		},
  6007  		expectedStatus: 200,
  6008  		expectedResult: &client.BuyResult{
  6009  			State: "Complete",
  6010  		},
  6011  		expectedResponseType: ResponseTypeSync,
  6012  		expectedBuyOptions:   validBuyOptions,
  6013  	},
  6014  	{
  6015  		// Fail with internal error
  6016  		input: `{
  6017  		  "snap-id": "the-snap-id-1234abcd",
  6018  		  "price": 1.23,
  6019  		  "currency": "EUR"
  6020  		}`,
  6021  		err:                  fmt.Errorf("internal error banana"),
  6022  		expectedStatus:       500,
  6023  		expectedResponseType: ResponseTypeError,
  6024  		expectedResult: &errorResult{
  6025  			Message: "internal error banana",
  6026  		},
  6027  		expectedBuyOptions: &client.BuyOptions{
  6028  			SnapID:   "the-snap-id-1234abcd",
  6029  			Price:    1.23,
  6030  			Currency: "EUR",
  6031  		},
  6032  	},
  6033  	{
  6034  		// Fail with unauthenticated error
  6035  		input:                validBuyInput,
  6036  		err:                  store.ErrUnauthenticated,
  6037  		expectedStatus:       400,
  6038  		expectedResponseType: ResponseTypeError,
  6039  		expectedResult: &errorResult{
  6040  			Message: "you need to log in first",
  6041  			Kind:    "login-required",
  6042  		},
  6043  		expectedBuyOptions: validBuyOptions,
  6044  	},
  6045  	{
  6046  		// Fail with TOS not accepted
  6047  		input:                validBuyInput,
  6048  		err:                  store.ErrTOSNotAccepted,
  6049  		expectedStatus:       400,
  6050  		expectedResponseType: ResponseTypeError,
  6051  		expectedResult: &errorResult{
  6052  			Message: "terms of service not accepted",
  6053  			Kind:    "terms-not-accepted",
  6054  		},
  6055  		expectedBuyOptions: validBuyOptions,
  6056  	},
  6057  	{
  6058  		// Fail with no payment methods
  6059  		input:                validBuyInput,
  6060  		err:                  store.ErrNoPaymentMethods,
  6061  		expectedStatus:       400,
  6062  		expectedResponseType: ResponseTypeError,
  6063  		expectedResult: &errorResult{
  6064  			Message: "no payment methods",
  6065  			Kind:    "no-payment-methods",
  6066  		},
  6067  		expectedBuyOptions: validBuyOptions,
  6068  	},
  6069  	{
  6070  		// Fail with payment declined
  6071  		input:                validBuyInput,
  6072  		err:                  store.ErrPaymentDeclined,
  6073  		expectedStatus:       400,
  6074  		expectedResponseType: ResponseTypeError,
  6075  		expectedResult: &errorResult{
  6076  			Message: "payment declined",
  6077  			Kind:    "payment-declined",
  6078  		},
  6079  		expectedBuyOptions: validBuyOptions,
  6080  	},
  6081  }
  6082  
  6083  func (s *apiSuite) TestBuySnap(c *check.C) {
  6084  	s.daemon(c)
  6085  
  6086  	for _, test := range buyTests {
  6087  		s.buyResult = test.result
  6088  		s.err = test.err
  6089  
  6090  		buf := bytes.NewBufferString(test.input)
  6091  		req, err := http.NewRequest("POST", "/v2/buy", buf)
  6092  		c.Assert(err, check.IsNil)
  6093  
  6094  		state := snapCmd.d.overlord.State()
  6095  		state.Lock()
  6096  		user, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"})
  6097  		state.Unlock()
  6098  		c.Check(err, check.IsNil)
  6099  
  6100  		rsp := postBuy(buyCmd, req, user).(*resp)
  6101  
  6102  		c.Check(rsp.Status, check.Equals, test.expectedStatus)
  6103  		c.Check(rsp.Type, check.Equals, test.expectedResponseType)
  6104  		c.Assert(rsp.Result, check.FitsTypeOf, test.expectedResult)
  6105  		c.Check(rsp.Result, check.DeepEquals, test.expectedResult)
  6106  
  6107  		c.Check(s.buyOptions, check.DeepEquals, test.expectedBuyOptions)
  6108  		c.Check(s.user, check.Equals, user)
  6109  	}
  6110  }
  6111  
  6112  func (s *apiSuite) TestIsTrue(c *check.C) {
  6113  	form := &multipart.Form{}
  6114  	c.Check(isTrue(form, "foo"), check.Equals, false)
  6115  	for _, f := range []string{"", "false", "0", "False", "f", "try"} {
  6116  		form.Value = map[string][]string{"foo": {f}}
  6117  		c.Check(isTrue(form, "foo"), check.Equals, false, check.Commentf("expected %q to be false", f))
  6118  	}
  6119  	for _, t := range []string{"true", "1", "True", "t"} {
  6120  		form.Value = map[string][]string{"foo": {t}}
  6121  		c.Check(isTrue(form, "foo"), check.Equals, true, check.Commentf("expected %q to be true", t))
  6122  	}
  6123  }
  6124  
  6125  var readyToBuyTests = []struct {
  6126  	input    error
  6127  	status   int
  6128  	respType interface{}
  6129  	response interface{}
  6130  }{
  6131  	{
  6132  		// Success
  6133  		input:    nil,
  6134  		status:   200,
  6135  		respType: ResponseTypeSync,
  6136  		response: true,
  6137  	},
  6138  	{
  6139  		// Not accepted TOS
  6140  		input:    store.ErrTOSNotAccepted,
  6141  		status:   400,
  6142  		respType: ResponseTypeError,
  6143  		response: &errorResult{
  6144  			Message: "terms of service not accepted",
  6145  			Kind:    client.ErrorKindTermsNotAccepted,
  6146  		},
  6147  	},
  6148  	{
  6149  		// No payment methods
  6150  		input:    store.ErrNoPaymentMethods,
  6151  		status:   400,
  6152  		respType: ResponseTypeError,
  6153  		response: &errorResult{
  6154  			Message: "no payment methods",
  6155  			Kind:    client.ErrorKindNoPaymentMethods,
  6156  		},
  6157  	},
  6158  }
  6159  
  6160  func (s *apiSuite) TestReadyToBuy(c *check.C) {
  6161  	s.daemon(c)
  6162  
  6163  	for _, test := range readyToBuyTests {
  6164  		s.err = test.input
  6165  
  6166  		req, err := http.NewRequest("GET", "/v2/buy/ready", nil)
  6167  		c.Assert(err, check.IsNil)
  6168  
  6169  		state := snapCmd.d.overlord.State()
  6170  		state.Lock()
  6171  		user, err := auth.NewUser(state, "username", "email@test.com", "macaroon", []string{"discharge"})
  6172  		state.Unlock()
  6173  		c.Check(err, check.IsNil)
  6174  
  6175  		rsp := readyToBuy(readyToBuyCmd, req, user).(*resp)
  6176  		c.Check(rsp.Status, check.Equals, test.status)
  6177  		c.Check(rsp.Type, check.Equals, test.respType)
  6178  		c.Assert(rsp.Result, check.FitsTypeOf, test.response)
  6179  		c.Check(rsp.Result, check.DeepEquals, test.response)
  6180  	}
  6181  }
  6182  
  6183  // aliases
  6184  
  6185  func (s *apiSuite) TestAliasSuccess(c *check.C) {
  6186  	err := os.MkdirAll(dirs.SnapBinariesDir, 0755)
  6187  	c.Assert(err, check.IsNil)
  6188  	d := s.daemon(c)
  6189  
  6190  	s.mockSnap(c, aliasYaml)
  6191  
  6192  	oldAutoAliases := snapstate.AutoAliases
  6193  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
  6194  		return nil, nil
  6195  	}
  6196  	defer func() { snapstate.AutoAliases = oldAutoAliases }()
  6197  
  6198  	d.overlord.Loop()
  6199  	defer d.overlord.Stop()
  6200  
  6201  	action := &aliasAction{
  6202  		Action: "alias",
  6203  		Snap:   "alias-snap",
  6204  		App:    "app",
  6205  		Alias:  "alias1",
  6206  	}
  6207  	text, err := json.Marshal(action)
  6208  	c.Assert(err, check.IsNil)
  6209  	buf := bytes.NewBuffer(text)
  6210  	req, err := http.NewRequest("POST", "/v2/aliases", buf)
  6211  	c.Assert(err, check.IsNil)
  6212  	rec := httptest.NewRecorder()
  6213  	aliasesCmd.POST(aliasesCmd, req, nil).ServeHTTP(rec, req)
  6214  	c.Assert(rec.Code, check.Equals, 202)
  6215  	var body map[string]interface{}
  6216  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  6217  	c.Check(err, check.IsNil)
  6218  	id := body["change"].(string)
  6219  
  6220  	st := d.overlord.State()
  6221  	st.Lock()
  6222  	chg := st.Change(id)
  6223  	st.Unlock()
  6224  	c.Assert(chg, check.NotNil)
  6225  
  6226  	<-chg.Ready()
  6227  
  6228  	st.Lock()
  6229  	err = chg.Err()
  6230  	st.Unlock()
  6231  	c.Assert(err, check.IsNil)
  6232  
  6233  	// sanity check
  6234  	c.Check(osutil.IsSymlink(filepath.Join(dirs.SnapBinariesDir, "alias1")), check.Equals, true)
  6235  }
  6236  
  6237  func (s *apiSuite) TestAliasChangeConflict(c *check.C) {
  6238  	err := os.MkdirAll(dirs.SnapBinariesDir, 0755)
  6239  	c.Assert(err, check.IsNil)
  6240  	d := s.daemon(c)
  6241  
  6242  	s.mockSnap(c, aliasYaml)
  6243  
  6244  	simulateConflict(d.overlord, "alias-snap")
  6245  
  6246  	oldAutoAliases := snapstate.AutoAliases
  6247  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
  6248  		return nil, nil
  6249  	}
  6250  	defer func() { snapstate.AutoAliases = oldAutoAliases }()
  6251  
  6252  	action := &aliasAction{
  6253  		Action: "alias",
  6254  		Snap:   "alias-snap",
  6255  		App:    "app",
  6256  		Alias:  "alias1",
  6257  	}
  6258  	text, err := json.Marshal(action)
  6259  	c.Assert(err, check.IsNil)
  6260  	buf := bytes.NewBuffer(text)
  6261  	req, err := http.NewRequest("POST", "/v2/aliases", buf)
  6262  	c.Assert(err, check.IsNil)
  6263  	rec := httptest.NewRecorder()
  6264  	aliasesCmd.POST(aliasesCmd, req, nil).ServeHTTP(rec, req)
  6265  	c.Check(rec.Code, check.Equals, 409)
  6266  
  6267  	var body map[string]interface{}
  6268  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  6269  	c.Check(err, check.IsNil)
  6270  	c.Check(body, check.DeepEquals, map[string]interface{}{
  6271  		"status-code": 409.,
  6272  		"status":      "Conflict",
  6273  		"result": map[string]interface{}{
  6274  			"message": `snap "alias-snap" has "manip" change in progress`,
  6275  			"kind":    "snap-change-conflict",
  6276  			"value": map[string]interface{}{
  6277  				"change-kind": "manip",
  6278  				"snap-name":   "alias-snap",
  6279  			},
  6280  		},
  6281  		"type": "error"})
  6282  }
  6283  
  6284  func (s *apiSuite) TestAliasErrors(c *check.C) {
  6285  	s.daemon(c)
  6286  
  6287  	errScenarios := []struct {
  6288  		mangle func(*aliasAction)
  6289  		err    string
  6290  	}{
  6291  		{func(a *aliasAction) { a.Action = "" }, `unsupported alias action: ""`},
  6292  		{func(a *aliasAction) { a.Action = "what" }, `unsupported alias action: "what"`},
  6293  		{func(a *aliasAction) { a.Snap = "lalala" }, `snap "lalala" is not installed`},
  6294  		{func(a *aliasAction) { a.Alias = ".foo" }, `invalid alias name: ".foo"`},
  6295  		{func(a *aliasAction) { a.Aliases = []string{"baz"} }, `cannot interpret request, snaps can no longer be expected to declare their aliases`},
  6296  	}
  6297  
  6298  	for _, scen := range errScenarios {
  6299  		action := &aliasAction{
  6300  			Action: "alias",
  6301  			Snap:   "alias-snap",
  6302  			App:    "app",
  6303  			Alias:  "alias1",
  6304  		}
  6305  		scen.mangle(action)
  6306  
  6307  		text, err := json.Marshal(action)
  6308  		c.Assert(err, check.IsNil)
  6309  		buf := bytes.NewBuffer(text)
  6310  		req, err := http.NewRequest("POST", "/v2/aliases", buf)
  6311  		c.Assert(err, check.IsNil)
  6312  
  6313  		rsp := changeAliases(aliasesCmd, req, nil).(*resp)
  6314  		c.Check(rsp.Type, check.Equals, ResponseTypeError)
  6315  		c.Check(rsp.Status, check.Equals, 400)
  6316  		c.Check(rsp.Result.(*errorResult).Message, check.Matches, scen.err)
  6317  	}
  6318  }
  6319  
  6320  func (s *apiSuite) TestUnaliasSnapSuccess(c *check.C) {
  6321  	err := os.MkdirAll(dirs.SnapBinariesDir, 0755)
  6322  	c.Assert(err, check.IsNil)
  6323  	d := s.daemon(c)
  6324  
  6325  	s.mockSnap(c, aliasYaml)
  6326  
  6327  	oldAutoAliases := snapstate.AutoAliases
  6328  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
  6329  		return nil, nil
  6330  	}
  6331  	defer func() { snapstate.AutoAliases = oldAutoAliases }()
  6332  
  6333  	d.overlord.Loop()
  6334  	defer d.overlord.Stop()
  6335  
  6336  	action := &aliasAction{
  6337  		Action: "unalias",
  6338  		Snap:   "alias-snap",
  6339  	}
  6340  	text, err := json.Marshal(action)
  6341  	c.Assert(err, check.IsNil)
  6342  	buf := bytes.NewBuffer(text)
  6343  	req, err := http.NewRequest("POST", "/v2/aliases", buf)
  6344  	c.Assert(err, check.IsNil)
  6345  	rec := httptest.NewRecorder()
  6346  	aliasesCmd.POST(aliasesCmd, req, nil).ServeHTTP(rec, req)
  6347  	c.Assert(rec.Code, check.Equals, 202)
  6348  	var body map[string]interface{}
  6349  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  6350  	c.Check(err, check.IsNil)
  6351  	id := body["change"].(string)
  6352  
  6353  	st := d.overlord.State()
  6354  	st.Lock()
  6355  	chg := st.Change(id)
  6356  	c.Check(chg.Summary(), check.Equals, `Disable all aliases for snap "alias-snap"`)
  6357  	st.Unlock()
  6358  	c.Assert(chg, check.NotNil)
  6359  
  6360  	<-chg.Ready()
  6361  
  6362  	st.Lock()
  6363  	defer st.Unlock()
  6364  	err = chg.Err()
  6365  	c.Assert(err, check.IsNil)
  6366  
  6367  	// sanity check
  6368  	var snapst snapstate.SnapState
  6369  	err = snapstate.Get(st, "alias-snap", &snapst)
  6370  	c.Assert(err, check.IsNil)
  6371  	c.Check(snapst.AutoAliasesDisabled, check.Equals, true)
  6372  }
  6373  
  6374  func (s *apiSuite) TestUnaliasDWIMSnapSuccess(c *check.C) {
  6375  	err := os.MkdirAll(dirs.SnapBinariesDir, 0755)
  6376  	c.Assert(err, check.IsNil)
  6377  	d := s.daemon(c)
  6378  
  6379  	s.mockSnap(c, aliasYaml)
  6380  
  6381  	oldAutoAliases := snapstate.AutoAliases
  6382  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
  6383  		return nil, nil
  6384  	}
  6385  	defer func() { snapstate.AutoAliases = oldAutoAliases }()
  6386  
  6387  	d.overlord.Loop()
  6388  	defer d.overlord.Stop()
  6389  
  6390  	action := &aliasAction{
  6391  		Action: "unalias",
  6392  		Snap:   "alias-snap",
  6393  		Alias:  "alias-snap",
  6394  	}
  6395  	text, err := json.Marshal(action)
  6396  	c.Assert(err, check.IsNil)
  6397  	buf := bytes.NewBuffer(text)
  6398  	req, err := http.NewRequest("POST", "/v2/aliases", buf)
  6399  	c.Assert(err, check.IsNil)
  6400  	rec := httptest.NewRecorder()
  6401  	aliasesCmd.POST(aliasesCmd, req, nil).ServeHTTP(rec, req)
  6402  	c.Assert(rec.Code, check.Equals, 202)
  6403  	var body map[string]interface{}
  6404  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  6405  	c.Check(err, check.IsNil)
  6406  	id := body["change"].(string)
  6407  
  6408  	st := d.overlord.State()
  6409  	st.Lock()
  6410  	chg := st.Change(id)
  6411  	c.Check(chg.Summary(), check.Equals, `Disable all aliases for snap "alias-snap"`)
  6412  	st.Unlock()
  6413  	c.Assert(chg, check.NotNil)
  6414  
  6415  	<-chg.Ready()
  6416  
  6417  	st.Lock()
  6418  	defer st.Unlock()
  6419  	err = chg.Err()
  6420  	c.Assert(err, check.IsNil)
  6421  
  6422  	// sanity check
  6423  	var snapst snapstate.SnapState
  6424  	err = snapstate.Get(st, "alias-snap", &snapst)
  6425  	c.Assert(err, check.IsNil)
  6426  	c.Check(snapst.AutoAliasesDisabled, check.Equals, true)
  6427  }
  6428  
  6429  func (s *apiSuite) TestUnaliasAliasSuccess(c *check.C) {
  6430  	err := os.MkdirAll(dirs.SnapBinariesDir, 0755)
  6431  	c.Assert(err, check.IsNil)
  6432  	d := s.daemon(c)
  6433  
  6434  	s.mockSnap(c, aliasYaml)
  6435  
  6436  	oldAutoAliases := snapstate.AutoAliases
  6437  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
  6438  		return nil, nil
  6439  	}
  6440  	defer func() { snapstate.AutoAliases = oldAutoAliases }()
  6441  
  6442  	d.overlord.Loop()
  6443  	defer d.overlord.Stop()
  6444  
  6445  	action := &aliasAction{
  6446  		Action: "alias",
  6447  		Snap:   "alias-snap",
  6448  		App:    "app",
  6449  		Alias:  "alias1",
  6450  	}
  6451  	text, err := json.Marshal(action)
  6452  	c.Assert(err, check.IsNil)
  6453  	buf := bytes.NewBuffer(text)
  6454  	req, err := http.NewRequest("POST", "/v2/aliases", buf)
  6455  	c.Assert(err, check.IsNil)
  6456  	rec := httptest.NewRecorder()
  6457  	aliasesCmd.POST(aliasesCmd, req, nil).ServeHTTP(rec, req)
  6458  	c.Assert(rec.Code, check.Equals, 202)
  6459  	var body map[string]interface{}
  6460  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  6461  	c.Check(err, check.IsNil)
  6462  	id := body["change"].(string)
  6463  
  6464  	st := d.overlord.State()
  6465  	st.Lock()
  6466  	chg := st.Change(id)
  6467  	st.Unlock()
  6468  	c.Assert(chg, check.NotNil)
  6469  
  6470  	<-chg.Ready()
  6471  
  6472  	st.Lock()
  6473  	err = chg.Err()
  6474  	st.Unlock()
  6475  	c.Assert(err, check.IsNil)
  6476  
  6477  	// unalias
  6478  	action = &aliasAction{
  6479  		Action: "unalias",
  6480  		Alias:  "alias1",
  6481  	}
  6482  	text, err = json.Marshal(action)
  6483  	c.Assert(err, check.IsNil)
  6484  	buf = bytes.NewBuffer(text)
  6485  	req, err = http.NewRequest("POST", "/v2/aliases", buf)
  6486  	c.Assert(err, check.IsNil)
  6487  	rec = httptest.NewRecorder()
  6488  	aliasesCmd.POST(aliasesCmd, req, nil).ServeHTTP(rec, req)
  6489  	c.Assert(rec.Code, check.Equals, 202)
  6490  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  6491  	c.Check(err, check.IsNil)
  6492  	id = body["change"].(string)
  6493  
  6494  	st.Lock()
  6495  	chg = st.Change(id)
  6496  	c.Check(chg.Summary(), check.Equals, `Remove manual alias "alias1" for snap "alias-snap"`)
  6497  	st.Unlock()
  6498  	c.Assert(chg, check.NotNil)
  6499  
  6500  	<-chg.Ready()
  6501  
  6502  	st.Lock()
  6503  	defer st.Unlock()
  6504  	err = chg.Err()
  6505  	c.Assert(err, check.IsNil)
  6506  
  6507  	// sanity check
  6508  	c.Check(osutil.FileExists(filepath.Join(dirs.SnapBinariesDir, "alias1")), check.Equals, false)
  6509  }
  6510  
  6511  func (s *apiSuite) TestUnaliasDWIMAliasSuccess(c *check.C) {
  6512  	err := os.MkdirAll(dirs.SnapBinariesDir, 0755)
  6513  	c.Assert(err, check.IsNil)
  6514  	d := s.daemon(c)
  6515  
  6516  	s.mockSnap(c, aliasYaml)
  6517  
  6518  	oldAutoAliases := snapstate.AutoAliases
  6519  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
  6520  		return nil, nil
  6521  	}
  6522  	defer func() { snapstate.AutoAliases = oldAutoAliases }()
  6523  
  6524  	d.overlord.Loop()
  6525  	defer d.overlord.Stop()
  6526  
  6527  	action := &aliasAction{
  6528  		Action: "alias",
  6529  		Snap:   "alias-snap",
  6530  		App:    "app",
  6531  		Alias:  "alias1",
  6532  	}
  6533  	text, err := json.Marshal(action)
  6534  	c.Assert(err, check.IsNil)
  6535  	buf := bytes.NewBuffer(text)
  6536  	req, err := http.NewRequest("POST", "/v2/aliases", buf)
  6537  	c.Assert(err, check.IsNil)
  6538  	rec := httptest.NewRecorder()
  6539  	aliasesCmd.POST(aliasesCmd, req, nil).ServeHTTP(rec, req)
  6540  	c.Assert(rec.Code, check.Equals, 202)
  6541  	var body map[string]interface{}
  6542  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  6543  	c.Check(err, check.IsNil)
  6544  	id := body["change"].(string)
  6545  
  6546  	st := d.overlord.State()
  6547  	st.Lock()
  6548  	chg := st.Change(id)
  6549  	st.Unlock()
  6550  	c.Assert(chg, check.NotNil)
  6551  
  6552  	<-chg.Ready()
  6553  
  6554  	st.Lock()
  6555  	err = chg.Err()
  6556  	st.Unlock()
  6557  	c.Assert(err, check.IsNil)
  6558  
  6559  	// DWIM unalias an alias
  6560  	action = &aliasAction{
  6561  		Action: "unalias",
  6562  		Snap:   "alias1",
  6563  		Alias:  "alias1",
  6564  	}
  6565  	text, err = json.Marshal(action)
  6566  	c.Assert(err, check.IsNil)
  6567  	buf = bytes.NewBuffer(text)
  6568  	req, err = http.NewRequest("POST", "/v2/aliases", buf)
  6569  	c.Assert(err, check.IsNil)
  6570  	rec = httptest.NewRecorder()
  6571  	aliasesCmd.POST(aliasesCmd, req, nil).ServeHTTP(rec, req)
  6572  	c.Assert(rec.Code, check.Equals, 202)
  6573  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  6574  	c.Check(err, check.IsNil)
  6575  	id = body["change"].(string)
  6576  
  6577  	st.Lock()
  6578  	chg = st.Change(id)
  6579  	c.Check(chg.Summary(), check.Equals, `Remove manual alias "alias1" for snap "alias-snap"`)
  6580  	st.Unlock()
  6581  	c.Assert(chg, check.NotNil)
  6582  
  6583  	<-chg.Ready()
  6584  
  6585  	st.Lock()
  6586  	defer st.Unlock()
  6587  	err = chg.Err()
  6588  	c.Assert(err, check.IsNil)
  6589  
  6590  	// sanity check
  6591  	c.Check(osutil.FileExists(filepath.Join(dirs.SnapBinariesDir, "alias1")), check.Equals, false)
  6592  }
  6593  
  6594  func (s *apiSuite) TestPreferSuccess(c *check.C) {
  6595  	err := os.MkdirAll(dirs.SnapBinariesDir, 0755)
  6596  	c.Assert(err, check.IsNil)
  6597  	d := s.daemon(c)
  6598  
  6599  	s.mockSnap(c, aliasYaml)
  6600  
  6601  	oldAutoAliases := snapstate.AutoAliases
  6602  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
  6603  		return nil, nil
  6604  	}
  6605  	defer func() { snapstate.AutoAliases = oldAutoAliases }()
  6606  
  6607  	d.overlord.Loop()
  6608  	defer d.overlord.Stop()
  6609  
  6610  	action := &aliasAction{
  6611  		Action: "prefer",
  6612  		Snap:   "alias-snap",
  6613  	}
  6614  	text, err := json.Marshal(action)
  6615  	c.Assert(err, check.IsNil)
  6616  	buf := bytes.NewBuffer(text)
  6617  	req, err := http.NewRequest("POST", "/v2/aliases", buf)
  6618  	c.Assert(err, check.IsNil)
  6619  	rec := httptest.NewRecorder()
  6620  	aliasesCmd.POST(aliasesCmd, req, nil).ServeHTTP(rec, req)
  6621  	c.Assert(rec.Code, check.Equals, 202)
  6622  	var body map[string]interface{}
  6623  	err = json.Unmarshal(rec.Body.Bytes(), &body)
  6624  	c.Check(err, check.IsNil)
  6625  	id := body["change"].(string)
  6626  
  6627  	st := d.overlord.State()
  6628  	st.Lock()
  6629  	chg := st.Change(id)
  6630  	c.Check(chg.Summary(), check.Equals, `Prefer aliases of snap "alias-snap"`)
  6631  	st.Unlock()
  6632  	c.Assert(chg, check.NotNil)
  6633  
  6634  	<-chg.Ready()
  6635  
  6636  	st.Lock()
  6637  	defer st.Unlock()
  6638  	err = chg.Err()
  6639  	c.Assert(err, check.IsNil)
  6640  
  6641  	// sanity check
  6642  	var snapst snapstate.SnapState
  6643  	err = snapstate.Get(st, "alias-snap", &snapst)
  6644  	c.Assert(err, check.IsNil)
  6645  	c.Check(snapst.AutoAliasesDisabled, check.Equals, false)
  6646  }
  6647  
  6648  func (s *apiSuite) TestAliases(c *check.C) {
  6649  	d := s.daemon(c)
  6650  
  6651  	st := d.overlord.State()
  6652  	st.Lock()
  6653  	snapstate.Set(st, "alias-snap1", &snapstate.SnapState{
  6654  		Sequence: []*snap.SideInfo{
  6655  			{RealName: "alias-snap1", Revision: snap.R(11)},
  6656  		},
  6657  		Current: snap.R(11),
  6658  		Active:  true,
  6659  		Aliases: map[string]*snapstate.AliasTarget{
  6660  			"alias1": {Manual: "cmd1x", Auto: "cmd1"},
  6661  			"alias2": {Auto: "cmd2"},
  6662  		},
  6663  	})
  6664  	snapstate.Set(st, "alias-snap2", &snapstate.SnapState{
  6665  		Sequence: []*snap.SideInfo{
  6666  			{RealName: "alias-snap2", Revision: snap.R(12)},
  6667  		},
  6668  		Current:             snap.R(12),
  6669  		Active:              true,
  6670  		AutoAliasesDisabled: true,
  6671  		Aliases: map[string]*snapstate.AliasTarget{
  6672  			"alias2": {Auto: "cmd2"},
  6673  			"alias3": {Manual: "cmd3"},
  6674  			"alias4": {Manual: "cmd4x", Auto: "cmd4"},
  6675  		},
  6676  	})
  6677  	st.Unlock()
  6678  
  6679  	req, err := http.NewRequest("GET", "/v2/aliases", nil)
  6680  	c.Assert(err, check.IsNil)
  6681  
  6682  	rsp := getAliases(aliasesCmd, req, nil).(*resp)
  6683  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  6684  	c.Check(rsp.Status, check.Equals, 200)
  6685  	c.Check(rsp.Result, check.DeepEquals, map[string]map[string]aliasStatus{
  6686  		"alias-snap1": {
  6687  			"alias1": {
  6688  				Command: "alias-snap1.cmd1x",
  6689  				Status:  "manual",
  6690  				Manual:  "cmd1x",
  6691  				Auto:    "cmd1",
  6692  			},
  6693  			"alias2": {
  6694  				Command: "alias-snap1.cmd2",
  6695  				Status:  "auto",
  6696  				Auto:    "cmd2",
  6697  			},
  6698  		},
  6699  		"alias-snap2": {
  6700  			"alias2": {
  6701  				Command: "alias-snap2.cmd2",
  6702  				Status:  "disabled",
  6703  				Auto:    "cmd2",
  6704  			},
  6705  			"alias3": {
  6706  				Command: "alias-snap2.cmd3",
  6707  				Status:  "manual",
  6708  				Manual:  "cmd3",
  6709  			},
  6710  			"alias4": {
  6711  				Command: "alias-snap2.cmd4x",
  6712  				Status:  "manual",
  6713  				Manual:  "cmd4x",
  6714  				Auto:    "cmd4",
  6715  			},
  6716  		},
  6717  	})
  6718  
  6719  }
  6720  
  6721  func (s *apiSuite) TestInstallUnaliased(c *check.C) {
  6722  	var calledFlags snapstate.Flags
  6723  
  6724  	snapstateInstall = func(ctx context.Context, s *state.State, name string, opts *snapstate.RevisionOptions, userID int, flags snapstate.Flags) (*state.TaskSet, error) {
  6725  		calledFlags = flags
  6726  
  6727  		t := s.NewTask("fake-install-snap", "Doing a fake install")
  6728  		return state.NewTaskSet(t), nil
  6729  	}
  6730  
  6731  	d := s.daemon(c)
  6732  	inst := &snapInstruction{
  6733  		Action: "install",
  6734  		// Install the snap without enabled automatic aliases
  6735  		Unaliased: true,
  6736  		Snaps:     []string{"fake"},
  6737  	}
  6738  
  6739  	st := d.overlord.State()
  6740  	st.Lock()
  6741  	defer st.Unlock()
  6742  	_, _, err := inst.dispatch()(inst, st)
  6743  	c.Check(err, check.IsNil)
  6744  
  6745  	c.Check(calledFlags.Unaliased, check.Equals, true)
  6746  }
  6747  
  6748  func (s *apiSuite) TestInstallPathUnaliased(c *check.C) {
  6749  	body := "" +
  6750  		"----hello--\r\n" +
  6751  		"Content-Disposition: form-data; name=\"snap\"; filename=\"x\"\r\n" +
  6752  		"\r\n" +
  6753  		"xyzzy\r\n" +
  6754  		"----hello--\r\n" +
  6755  		"Content-Disposition: form-data; name=\"devmode\"\r\n" +
  6756  		"\r\n" +
  6757  		"true\r\n" +
  6758  		"----hello--\r\n" +
  6759  		"Content-Disposition: form-data; name=\"unaliased\"\r\n" +
  6760  		"\r\n" +
  6761  		"true\r\n" +
  6762  		"----hello--\r\n"
  6763  	head := map[string]string{"Content-Type": "multipart/thing; boundary=--hello--"}
  6764  	// try a multipart/form-data upload
  6765  	flags := snapstate.Flags{Unaliased: true, RemoveSnapPath: true, DevMode: true}
  6766  	chgSummary := s.sideloadCheck(c, body, head, "local", flags)
  6767  	c.Check(chgSummary, check.Equals, `Install "local" snap from file "x"`)
  6768  }
  6769  
  6770  func (s *apiSuite) TestSnapctlGetNoUID(c *check.C) {
  6771  	buf := bytes.NewBufferString(`{"context-id": "some-context", "args": ["get", "something"]}`)
  6772  	req, err := http.NewRequest("POST", "/v2/snapctl", buf)
  6773  	c.Assert(err, check.IsNil)
  6774  	rsp := runSnapctl(snapctlCmd, req, nil).(*resp)
  6775  	c.Assert(rsp.Status, check.Equals, 403)
  6776  }
  6777  
  6778  func (s *apiSuite) TestSnapctlForbiddenError(c *check.C) {
  6779  	_ = s.daemon(c)
  6780  
  6781  	runSnapctlUcrednetGet = func(string) (int32, uint32, string, error) {
  6782  		return 100, 9999, dirs.SnapSocket, nil
  6783  	}
  6784  	defer func() { runSnapctlUcrednetGet = ucrednetGet }()
  6785  	ctlcmdRun = func(ctx *hookstate.Context, arg []string, uid uint32) ([]byte, []byte, error) {
  6786  		return nil, nil, &ctlcmd.ForbiddenCommandError{}
  6787  	}
  6788  	defer func() { ctlcmdRun = ctlcmd.Run }()
  6789  
  6790  	buf := bytes.NewBufferString(fmt.Sprintf(`{"context-id": "some-context", "args": [%q, %q]}`, "set", "foo=bar"))
  6791  	req, err := http.NewRequest("POST", "/v2/snapctl", buf)
  6792  	c.Assert(err, check.IsNil)
  6793  	rsp := runSnapctl(snapctlCmd, req, nil).(*resp)
  6794  	c.Assert(rsp.Status, check.Equals, 403)
  6795  }
  6796  
  6797  func (s *apiSuite) TestSnapctlUnsuccesfulError(c *check.C) {
  6798  	_ = s.daemon(c)
  6799  
  6800  	runSnapctlUcrednetGet = func(string) (int32, uint32, string, error) {
  6801  		return 100, 9999, dirs.SnapSocket, nil
  6802  	}
  6803  	defer func() { runSnapctlUcrednetGet = ucrednetGet }()
  6804  
  6805  	ctlcmdRun = func(ctx *hookstate.Context, arg []string, uid uint32) ([]byte, []byte, error) {
  6806  		return nil, nil, &ctlcmd.UnsuccessfulError{ExitCode: 123}
  6807  	}
  6808  	defer func() { ctlcmdRun = ctlcmd.Run }()
  6809  
  6810  	buf := bytes.NewBufferString(fmt.Sprintf(`{"context-id": "some-context", "args": [%q, %q]}`, "is-connected", "plug"))
  6811  	req, err := http.NewRequest("POST", "/v2/snapctl", buf)
  6812  	c.Assert(err, check.IsNil)
  6813  	rsp := runSnapctl(snapctlCmd, req, nil).(*resp)
  6814  	c.Check(rsp.Status, check.Equals, 200)
  6815  	c.Check(rsp.Result.(*errorResult).Kind, check.Equals, client.ErrorKindUnsuccessful)
  6816  	c.Check(rsp.Result.(*errorResult).Value, check.DeepEquals, map[string]interface{}{
  6817  		"stdout":    "",
  6818  		"stderr":    "",
  6819  		"exit-code": 123,
  6820  	})
  6821  }
  6822  
  6823  type appSuite struct {
  6824  	apiBaseSuite
  6825  	cmd *testutil.MockCmd
  6826  
  6827  	infoA, infoB, infoC, infoD *snap.Info
  6828  }
  6829  
  6830  var _ = check.Suite(&appSuite{})
  6831  
  6832  func (s *appSuite) SetUpTest(c *check.C) {
  6833  	s.apiBaseSuite.SetUpTest(c)
  6834  	s.cmd = testutil.MockCommand(c, "systemctl", "").Also("journalctl", "")
  6835  	s.daemon(c)
  6836  	s.infoA = s.mkInstalledInState(c, s.d, "snap-a", "dev", "v1", snap.R(1), true, "apps: {svc1: {daemon: simple}, svc2: {daemon: simple, reload-command: x}}")
  6837  	s.infoB = s.mkInstalledInState(c, s.d, "snap-b", "dev", "v1", snap.R(1), false, "apps: {svc3: {daemon: simple}, cmd1: {}}")
  6838  	s.infoC = s.mkInstalledInState(c, s.d, "snap-c", "dev", "v1", snap.R(1), true, "")
  6839  	s.infoD = s.mkInstalledInState(c, s.d, "snap-d", "dev", "v1", snap.R(1), true, "apps: {cmd2: {}, cmd3: {}}")
  6840  	s.d.overlord.Loop()
  6841  }
  6842  
  6843  func (s *appSuite) TearDownTest(c *check.C) {
  6844  	s.d.overlord.Stop()
  6845  	s.cmd.Restore()
  6846  	s.apiBaseSuite.TearDownTest(c)
  6847  }
  6848  
  6849  func (s *appSuite) TestSplitAppName(c *check.C) {
  6850  	type T struct {
  6851  		name string
  6852  		snap string
  6853  		app  string
  6854  	}
  6855  
  6856  	for _, x := range []T{
  6857  		{name: "foo.bar", snap: "foo", app: "bar"},
  6858  		{name: "foo", snap: "foo", app: ""},
  6859  		{name: "foo.bar.baz", snap: "foo", app: "bar.baz"},
  6860  		{name: ".", snap: "", app: ""}, // SISO
  6861  	} {
  6862  		snap, app := splitAppName(x.name)
  6863  		c.Check(x.snap, check.Equals, snap, check.Commentf(x.name))
  6864  		c.Check(x.app, check.Equals, app, check.Commentf(x.name))
  6865  	}
  6866  }
  6867  
  6868  func (s *appSuite) TestGetAppsInfo(c *check.C) {
  6869  	svcNames := []string{"snap-a.svc1", "snap-a.svc2", "snap-b.svc3"}
  6870  	for _, name := range svcNames {
  6871  		s.sysctlBufs = append(s.sysctlBufs, []byte(fmt.Sprintf(`
  6872  Id=snap.%s.service
  6873  Type=simple
  6874  ActiveState=active
  6875  UnitFileState=enabled
  6876  `[1:], name)))
  6877  	}
  6878  
  6879  	req, err := http.NewRequest("GET", "/v2/apps", nil)
  6880  	c.Assert(err, check.IsNil)
  6881  
  6882  	rsp := getAppsInfo(appsCmd, req, nil).(*resp)
  6883  	c.Assert(rsp.Status, check.Equals, 200)
  6884  	c.Assert(rsp.Type, check.Equals, ResponseTypeSync)
  6885  	c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{})
  6886  	apps := rsp.Result.([]client.AppInfo)
  6887  	c.Assert(apps, check.HasLen, 6)
  6888  
  6889  	for _, name := range svcNames {
  6890  		snap, app := splitAppName(name)
  6891  		needle := client.AppInfo{
  6892  			Snap:   snap,
  6893  			Name:   app,
  6894  			Daemon: "simple",
  6895  		}
  6896  		if snap != "snap-b" {
  6897  			// snap-b is not active (all the others are)
  6898  			needle.Active = true
  6899  			needle.Enabled = true
  6900  		}
  6901  		c.Check(apps, testutil.DeepContains, needle)
  6902  	}
  6903  
  6904  	for _, name := range []string{"snap-b.cmd1", "snap-d.cmd2", "snap-d.cmd3"} {
  6905  		snap, app := splitAppName(name)
  6906  		c.Check(apps, testutil.DeepContains, client.AppInfo{
  6907  			Snap: snap,
  6908  			Name: app,
  6909  		})
  6910  	}
  6911  
  6912  	appNames := make([]string, len(apps))
  6913  	for i, app := range apps {
  6914  		appNames[i] = app.Snap + "." + app.Name
  6915  	}
  6916  	c.Check(sort.StringsAreSorted(appNames), check.Equals, true)
  6917  }
  6918  
  6919  func (s *appSuite) TestGetAppsInfoNames(c *check.C) {
  6920  
  6921  	req, err := http.NewRequest("GET", "/v2/apps?names=snap-d", nil)
  6922  	c.Assert(err, check.IsNil)
  6923  
  6924  	rsp := getAppsInfo(appsCmd, req, nil).(*resp)
  6925  	c.Assert(rsp.Status, check.Equals, 200)
  6926  	c.Assert(rsp.Type, check.Equals, ResponseTypeSync)
  6927  	c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{})
  6928  	apps := rsp.Result.([]client.AppInfo)
  6929  	c.Assert(apps, check.HasLen, 2)
  6930  
  6931  	for _, name := range []string{"snap-d.cmd2", "snap-d.cmd3"} {
  6932  		snap, app := splitAppName(name)
  6933  		c.Check(apps, testutil.DeepContains, client.AppInfo{
  6934  			Snap: snap,
  6935  			Name: app,
  6936  		})
  6937  	}
  6938  
  6939  	appNames := make([]string, len(apps))
  6940  	for i, app := range apps {
  6941  		appNames[i] = app.Snap + "." + app.Name
  6942  	}
  6943  	c.Check(sort.StringsAreSorted(appNames), check.Equals, true)
  6944  }
  6945  
  6946  func (s *appSuite) TestGetAppsInfoServices(c *check.C) {
  6947  	svcNames := []string{"snap-a.svc1", "snap-a.svc2", "snap-b.svc3"}
  6948  	for _, name := range svcNames {
  6949  		s.sysctlBufs = append(s.sysctlBufs, []byte(fmt.Sprintf(`
  6950  Id=snap.%s.service
  6951  Type=simple
  6952  ActiveState=active
  6953  UnitFileState=enabled
  6954  `[1:], name)))
  6955  	}
  6956  
  6957  	req, err := http.NewRequest("GET", "/v2/apps?select=service", nil)
  6958  	c.Assert(err, check.IsNil)
  6959  
  6960  	rsp := getAppsInfo(appsCmd, req, nil).(*resp)
  6961  	c.Assert(rsp.Status, check.Equals, 200)
  6962  	c.Assert(rsp.Type, check.Equals, ResponseTypeSync)
  6963  	c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{})
  6964  	svcs := rsp.Result.([]client.AppInfo)
  6965  	c.Assert(svcs, check.HasLen, 3)
  6966  
  6967  	for _, name := range svcNames {
  6968  		snap, app := splitAppName(name)
  6969  		needle := client.AppInfo{
  6970  			Snap:   snap,
  6971  			Name:   app,
  6972  			Daemon: "simple",
  6973  		}
  6974  		if snap != "snap-b" {
  6975  			// snap-b is not active (all the others are)
  6976  			needle.Active = true
  6977  			needle.Enabled = true
  6978  		}
  6979  		c.Check(svcs, testutil.DeepContains, needle)
  6980  	}
  6981  
  6982  	appNames := make([]string, len(svcs))
  6983  	for i, svc := range svcs {
  6984  		appNames[i] = svc.Snap + "." + svc.Name
  6985  	}
  6986  	c.Check(sort.StringsAreSorted(appNames), check.Equals, true)
  6987  }
  6988  
  6989  func (s *appSuite) TestGetAppsInfoBadSelect(c *check.C) {
  6990  	req, err := http.NewRequest("GET", "/v2/apps?select=potato", nil)
  6991  	c.Assert(err, check.IsNil)
  6992  
  6993  	rsp := getAppsInfo(appsCmd, req, nil).(*resp)
  6994  	c.Assert(rsp.Status, check.Equals, 400)
  6995  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  6996  }
  6997  
  6998  func (s *appSuite) TestGetAppsInfoBadName(c *check.C) {
  6999  	req, err := http.NewRequest("GET", "/v2/apps?names=potato", nil)
  7000  	c.Assert(err, check.IsNil)
  7001  
  7002  	rsp := getAppsInfo(appsCmd, req, nil).(*resp)
  7003  	c.Assert(rsp.Status, check.Equals, 404)
  7004  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  7005  }
  7006  
  7007  func (s *appSuite) TestAppInfosForOne(c *check.C) {
  7008  	st := s.d.overlord.State()
  7009  	appInfos, rsp := appInfosFor(st, []string{"snap-a.svc1"}, appInfoOptions{service: true})
  7010  	c.Assert(rsp, check.IsNil)
  7011  	c.Assert(appInfos, check.HasLen, 1)
  7012  	c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA)
  7013  	c.Check(appInfos[0].Name, check.Equals, "svc1")
  7014  }
  7015  
  7016  func (s *appSuite) TestAppInfosForAll(c *check.C) {
  7017  	type T struct {
  7018  		opts  appInfoOptions
  7019  		snaps []*snap.Info
  7020  		names []string
  7021  	}
  7022  
  7023  	for _, t := range []T{
  7024  		{
  7025  			opts:  appInfoOptions{service: true},
  7026  			names: []string{"svc1", "svc2", "svc3"},
  7027  			snaps: []*snap.Info{s.infoA, s.infoA, s.infoB},
  7028  		},
  7029  		{
  7030  			opts:  appInfoOptions{},
  7031  			names: []string{"svc1", "svc2", "cmd1", "svc3", "cmd2", "cmd3"},
  7032  			snaps: []*snap.Info{s.infoA, s.infoA, s.infoB, s.infoB, s.infoD, s.infoD},
  7033  		},
  7034  	} {
  7035  		c.Assert(len(t.names), check.Equals, len(t.snaps), check.Commentf("%s", t.opts))
  7036  
  7037  		st := s.d.overlord.State()
  7038  		appInfos, rsp := appInfosFor(st, nil, t.opts)
  7039  		c.Assert(rsp, check.IsNil, check.Commentf("%s", t.opts))
  7040  		names := make([]string, len(appInfos))
  7041  		for i, appInfo := range appInfos {
  7042  			names[i] = appInfo.Name
  7043  		}
  7044  		c.Assert(names, check.DeepEquals, t.names, check.Commentf("%s", t.opts))
  7045  
  7046  		for i := range appInfos {
  7047  			c.Check(appInfos[i].Snap, check.DeepEquals, t.snaps[i], check.Commentf("%s: %s", t.opts, t.names[i]))
  7048  		}
  7049  	}
  7050  }
  7051  
  7052  func (s *appSuite) TestAppInfosForOneSnap(c *check.C) {
  7053  	st := s.d.overlord.State()
  7054  	appInfos, rsp := appInfosFor(st, []string{"snap-a"}, appInfoOptions{service: true})
  7055  	c.Assert(rsp, check.IsNil)
  7056  	c.Assert(appInfos, check.HasLen, 2)
  7057  	sort.Sort(snap.AppInfoBySnapApp(appInfos))
  7058  
  7059  	c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA)
  7060  	c.Check(appInfos[0].Name, check.Equals, "svc1")
  7061  	c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA)
  7062  	c.Check(appInfos[1].Name, check.Equals, "svc2")
  7063  }
  7064  
  7065  func (s *appSuite) TestAppInfosForMixedArgs(c *check.C) {
  7066  	st := s.d.overlord.State()
  7067  	appInfos, rsp := appInfosFor(st, []string{"snap-a", "snap-a.svc1"}, appInfoOptions{service: true})
  7068  	c.Assert(rsp, check.IsNil)
  7069  	c.Assert(appInfos, check.HasLen, 2)
  7070  	sort.Sort(snap.AppInfoBySnapApp(appInfos))
  7071  
  7072  	c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA)
  7073  	c.Check(appInfos[0].Name, check.Equals, "svc1")
  7074  	c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA)
  7075  	c.Check(appInfos[1].Name, check.Equals, "svc2")
  7076  }
  7077  
  7078  func (s *appSuite) TestAppInfosCleanupAndSorted(c *check.C) {
  7079  	st := s.d.overlord.State()
  7080  	appInfos, rsp := appInfosFor(st, []string{
  7081  		"snap-b.svc3",
  7082  		"snap-a.svc2",
  7083  		"snap-a.svc1",
  7084  		"snap-a.svc2",
  7085  		"snap-b.svc3",
  7086  		"snap-a.svc1",
  7087  		"snap-b",
  7088  		"snap-a",
  7089  	}, appInfoOptions{service: true})
  7090  	c.Assert(rsp, check.IsNil)
  7091  	c.Assert(appInfos, check.HasLen, 3)
  7092  	sort.Sort(snap.AppInfoBySnapApp(appInfos))
  7093  
  7094  	c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA)
  7095  	c.Check(appInfos[0].Name, check.Equals, "svc1")
  7096  	c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA)
  7097  	c.Check(appInfos[1].Name, check.Equals, "svc2")
  7098  	c.Check(appInfos[2].Snap, check.DeepEquals, s.infoB)
  7099  	c.Check(appInfos[2].Name, check.Equals, "svc3")
  7100  }
  7101  
  7102  func (s *appSuite) TestAppInfosForAppless(c *check.C) {
  7103  	st := s.d.overlord.State()
  7104  	appInfos, rsp := appInfosFor(st, []string{"snap-c"}, appInfoOptions{service: true})
  7105  	c.Assert(rsp, check.FitsTypeOf, &resp{})
  7106  	c.Check(rsp.(*resp).Status, check.Equals, 404)
  7107  	c.Check(rsp.(*resp).Result.(*errorResult).Kind, check.Equals, client.ErrorKindAppNotFound)
  7108  	c.Assert(appInfos, check.IsNil)
  7109  }
  7110  
  7111  func (s *appSuite) TestAppInfosForMissingApp(c *check.C) {
  7112  	st := s.d.overlord.State()
  7113  	appInfos, rsp := appInfosFor(st, []string{"snap-c.whatever"}, appInfoOptions{service: true})
  7114  	c.Assert(rsp, check.FitsTypeOf, &resp{})
  7115  	c.Check(rsp.(*resp).Status, check.Equals, 404)
  7116  	c.Check(rsp.(*resp).Result.(*errorResult).Kind, check.Equals, client.ErrorKindAppNotFound)
  7117  	c.Assert(appInfos, check.IsNil)
  7118  }
  7119  
  7120  func (s *appSuite) TestAppInfosForMissingSnap(c *check.C) {
  7121  	st := s.d.overlord.State()
  7122  	appInfos, rsp := appInfosFor(st, []string{"snap-x"}, appInfoOptions{service: true})
  7123  	c.Assert(rsp, check.FitsTypeOf, &resp{})
  7124  	c.Check(rsp.(*resp).Status, check.Equals, 404)
  7125  	c.Check(rsp.(*resp).Result.(*errorResult).Kind, check.Equals, client.ErrorKindSnapNotFound)
  7126  	c.Assert(appInfos, check.IsNil)
  7127  }
  7128  
  7129  func (s *apiSuite) TestLogsNoServices(c *check.C) {
  7130  	// NOTE this is *apiSuite, not *appSuite, so there are no
  7131  	// installed snaps with services
  7132  
  7133  	cmd := testutil.MockCommand(c, "systemctl", "").Also("journalctl", "")
  7134  	defer cmd.Restore()
  7135  	s.daemon(c)
  7136  	s.d.overlord.Loop()
  7137  	defer s.d.overlord.Stop()
  7138  
  7139  	req, err := http.NewRequest("GET", "/v2/logs", nil)
  7140  	c.Assert(err, check.IsNil)
  7141  
  7142  	rsp := getLogs(logsCmd, req, nil).(*resp)
  7143  	c.Assert(rsp.Status, check.Equals, 404)
  7144  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  7145  }
  7146  
  7147  func (s *appSuite) TestLogs(c *check.C) {
  7148  	s.jctlRCs = []io.ReadCloser{ioutil.NopCloser(strings.NewReader(`
  7149  {"MESSAGE": "hello1", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "42"}
  7150  {"MESSAGE": "hello2", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "44"}
  7151  {"MESSAGE": "hello3", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "46"}
  7152  {"MESSAGE": "hello4", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "48"}
  7153  {"MESSAGE": "hello5", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "50"}
  7154  	`))}
  7155  
  7156  	req, err := http.NewRequest("GET", "/v2/logs?names=snap-a.svc2&n=42&follow=false", nil)
  7157  	c.Assert(err, check.IsNil)
  7158  
  7159  	rec := httptest.NewRecorder()
  7160  	getLogs(logsCmd, req, nil).ServeHTTP(rec, req)
  7161  
  7162  	c.Check(s.jctlSvcses, check.DeepEquals, [][]string{{"snap.snap-a.svc2.service"}})
  7163  	c.Check(s.jctlNs, check.DeepEquals, []int{42})
  7164  	c.Check(s.jctlFollows, check.DeepEquals, []bool{false})
  7165  
  7166  	c.Check(rec.Code, check.Equals, 200)
  7167  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json-seq")
  7168  	c.Check(rec.Body.String(), check.Equals, `
  7169  {"timestamp":"1970-01-01T00:00:00.000042Z","message":"hello1","sid":"xyzzy","pid":"42"}
  7170  {"timestamp":"1970-01-01T00:00:00.000044Z","message":"hello2","sid":"xyzzy","pid":"42"}
  7171  {"timestamp":"1970-01-01T00:00:00.000046Z","message":"hello3","sid":"xyzzy","pid":"42"}
  7172  {"timestamp":"1970-01-01T00:00:00.000048Z","message":"hello4","sid":"xyzzy","pid":"42"}
  7173  {"timestamp":"1970-01-01T00:00:00.00005Z","message":"hello5","sid":"xyzzy","pid":"42"}
  7174  `[1:])
  7175  }
  7176  
  7177  func (s *appSuite) TestLogsN(c *check.C) {
  7178  	type T struct {
  7179  		in  string
  7180  		out int
  7181  	}
  7182  
  7183  	for _, t := range []T{
  7184  		{in: "", out: 10},
  7185  		{in: "0", out: 0},
  7186  		{in: "-1", out: -1},
  7187  		{in: strconv.Itoa(math.MinInt32), out: math.MinInt32},
  7188  		{in: strconv.Itoa(math.MaxInt32), out: math.MaxInt32},
  7189  	} {
  7190  
  7191  		s.jctlRCs = []io.ReadCloser{ioutil.NopCloser(strings.NewReader(""))}
  7192  		s.jctlNs = nil
  7193  
  7194  		req, err := http.NewRequest("GET", "/v2/logs?n="+t.in, nil)
  7195  		c.Assert(err, check.IsNil)
  7196  
  7197  		rec := httptest.NewRecorder()
  7198  		getLogs(logsCmd, req, nil).ServeHTTP(rec, req)
  7199  
  7200  		c.Check(s.jctlNs, check.DeepEquals, []int{t.out})
  7201  	}
  7202  }
  7203  
  7204  func (s *appSuite) TestLogsBadN(c *check.C) {
  7205  	req, err := http.NewRequest("GET", "/v2/logs?n=hello", nil)
  7206  	c.Assert(err, check.IsNil)
  7207  
  7208  	rsp := getLogs(logsCmd, req, nil).(*resp)
  7209  	c.Assert(rsp.Status, check.Equals, 400)
  7210  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  7211  }
  7212  
  7213  func (s *appSuite) TestLogsFollow(c *check.C) {
  7214  	s.jctlRCs = []io.ReadCloser{
  7215  		ioutil.NopCloser(strings.NewReader("")),
  7216  		ioutil.NopCloser(strings.NewReader("")),
  7217  		ioutil.NopCloser(strings.NewReader("")),
  7218  	}
  7219  
  7220  	reqT, err := http.NewRequest("GET", "/v2/logs?follow=true", nil)
  7221  	c.Assert(err, check.IsNil)
  7222  	reqF, err := http.NewRequest("GET", "/v2/logs?follow=false", nil)
  7223  	c.Assert(err, check.IsNil)
  7224  	reqN, err := http.NewRequest("GET", "/v2/logs", nil)
  7225  	c.Assert(err, check.IsNil)
  7226  
  7227  	rec := httptest.NewRecorder()
  7228  	getLogs(logsCmd, reqT, nil).ServeHTTP(rec, reqT)
  7229  	getLogs(logsCmd, reqF, nil).ServeHTTP(rec, reqF)
  7230  	getLogs(logsCmd, reqN, nil).ServeHTTP(rec, reqN)
  7231  
  7232  	c.Check(s.jctlFollows, check.DeepEquals, []bool{true, false, false})
  7233  }
  7234  
  7235  func (s *appSuite) TestLogsBadFollow(c *check.C) {
  7236  	req, err := http.NewRequest("GET", "/v2/logs?follow=hello", nil)
  7237  	c.Assert(err, check.IsNil)
  7238  
  7239  	rsp := getLogs(logsCmd, req, nil).(*resp)
  7240  	c.Assert(rsp.Status, check.Equals, 400)
  7241  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  7242  }
  7243  
  7244  func (s *appSuite) TestLogsBadName(c *check.C) {
  7245  	req, err := http.NewRequest("GET", "/v2/logs?names=hello", nil)
  7246  	c.Assert(err, check.IsNil)
  7247  
  7248  	rsp := getLogs(logsCmd, req, nil).(*resp)
  7249  	c.Assert(rsp.Status, check.Equals, 404)
  7250  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  7251  }
  7252  
  7253  func (s *appSuite) TestLogsSad(c *check.C) {
  7254  	s.jctlErrs = []error{errors.New("potato")}
  7255  	req, err := http.NewRequest("GET", "/v2/logs", nil)
  7256  	c.Assert(err, check.IsNil)
  7257  
  7258  	rsp := getLogs(logsCmd, req, nil).(*resp)
  7259  	c.Assert(rsp.Status, check.Equals, 500)
  7260  	c.Assert(rsp.Type, check.Equals, ResponseTypeError)
  7261  }
  7262  
  7263  func (s *appSuite) testPostApps(c *check.C, inst servicestate.Instruction, servicecmds []serviceControlArgs) *state.Change {
  7264  	postBody, err := json.Marshal(inst)
  7265  	c.Assert(err, check.IsNil)
  7266  
  7267  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBuffer(postBody))
  7268  	c.Assert(err, check.IsNil)
  7269  
  7270  	rsp := postApps(appsCmd, req, nil).(*resp)
  7271  	c.Assert(rsp.Status, check.Equals, 202)
  7272  	c.Assert(rsp.Type, check.Equals, ResponseTypeAsync)
  7273  	c.Check(rsp.Change, check.Matches, `[0-9]+`)
  7274  
  7275  	st := s.d.overlord.State()
  7276  	st.Lock()
  7277  	defer st.Unlock()
  7278  	chg := st.Change(rsp.Change)
  7279  	c.Assert(chg, check.NotNil)
  7280  	c.Check(chg.Tasks(), check.HasLen, len(servicecmds))
  7281  
  7282  	st.Unlock()
  7283  	<-chg.Ready()
  7284  	st.Lock()
  7285  
  7286  	c.Check(s.serviceControlCalls, check.DeepEquals, servicecmds)
  7287  	return chg
  7288  }
  7289  
  7290  func (s *appSuite) TestPostAppsStartOne(c *check.C) {
  7291  	inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a.svc2"}}
  7292  	expected := []serviceControlArgs{
  7293  		{action: "start", names: []string{"snap-a.svc2"}},
  7294  	}
  7295  	chg := s.testPostApps(c, inst, expected)
  7296  	chg.State().Lock()
  7297  	defer chg.State().Unlock()
  7298  
  7299  	var names []string
  7300  	err := chg.Get("snap-names", &names)
  7301  	c.Assert(err, check.IsNil)
  7302  	c.Assert(names, check.DeepEquals, []string{"snap-a"})
  7303  }
  7304  
  7305  func (s *appSuite) TestPostAppsStartTwo(c *check.C) {
  7306  	inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a"}}
  7307  	expected := []serviceControlArgs{
  7308  		{action: "start", names: []string{"snap-a.svc1", "snap-a.svc2"}},
  7309  	}
  7310  	chg := s.testPostApps(c, inst, expected)
  7311  
  7312  	chg.State().Lock()
  7313  	defer chg.State().Unlock()
  7314  
  7315  	var names []string
  7316  	err := chg.Get("snap-names", &names)
  7317  	c.Assert(err, check.IsNil)
  7318  	c.Assert(names, check.DeepEquals, []string{"snap-a"})
  7319  }
  7320  
  7321  func (s *appSuite) TestPostAppsStartThree(c *check.C) {
  7322  	inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a", "snap-b"}}
  7323  	expected := []serviceControlArgs{
  7324  		{action: "start", names: []string{"snap-a.svc1", "snap-a.svc2", "snap-b.svc3"}},
  7325  	}
  7326  	chg := s.testPostApps(c, inst, expected)
  7327  	// check the summary expands the snap into actual apps
  7328  	c.Check(chg.Summary(), check.Equals, "Running service command")
  7329  	chg.State().Lock()
  7330  	defer chg.State().Unlock()
  7331  
  7332  	var names []string
  7333  	err := chg.Get("snap-names", &names)
  7334  	c.Assert(err, check.IsNil)
  7335  	c.Assert(names, check.DeepEquals, []string{"snap-a", "snap-b"})
  7336  }
  7337  
  7338  func (s *appSuite) TestPostAppsStop(c *check.C) {
  7339  	inst := servicestate.Instruction{Action: "stop", Names: []string{"snap-a.svc2"}}
  7340  	expected := []serviceControlArgs{
  7341  		{action: "stop", names: []string{"snap-a.svc2"}},
  7342  	}
  7343  	s.testPostApps(c, inst, expected)
  7344  }
  7345  
  7346  func (s *appSuite) TestPostAppsRestart(c *check.C) {
  7347  	inst := servicestate.Instruction{Action: "restart", Names: []string{"snap-a.svc2"}}
  7348  	expected := []serviceControlArgs{
  7349  		{action: "restart", names: []string{"snap-a.svc2"}},
  7350  	}
  7351  	s.testPostApps(c, inst, expected)
  7352  }
  7353  
  7354  func (s *appSuite) TestPostAppsReload(c *check.C) {
  7355  	inst := servicestate.Instruction{Action: "restart", Names: []string{"snap-a.svc2"}}
  7356  	inst.Reload = true
  7357  	expected := []serviceControlArgs{
  7358  		{action: "restart", options: "reload", names: []string{"snap-a.svc2"}},
  7359  	}
  7360  	s.testPostApps(c, inst, expected)
  7361  }
  7362  
  7363  func (s *appSuite) TestPostAppsEnableNow(c *check.C) {
  7364  	inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a.svc2"}}
  7365  	inst.Enable = true
  7366  	expected := []serviceControlArgs{
  7367  		{action: "start", options: "enable", names: []string{"snap-a.svc2"}},
  7368  	}
  7369  	s.testPostApps(c, inst, expected)
  7370  }
  7371  
  7372  func (s *appSuite) TestPostAppsDisableNow(c *check.C) {
  7373  	inst := servicestate.Instruction{Action: "stop", Names: []string{"snap-a.svc2"}}
  7374  	inst.Disable = true
  7375  	expected := []serviceControlArgs{
  7376  		{action: "stop", options: "disable", names: []string{"snap-a.svc2"}},
  7377  	}
  7378  	s.testPostApps(c, inst, expected)
  7379  }
  7380  
  7381  func (s *appSuite) TestPostAppsBadJSON(c *check.C) {
  7382  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`'junk`))
  7383  	c.Assert(err, check.IsNil)
  7384  	rsp := postApps(appsCmd, req, nil).(*resp)
  7385  	c.Check(rsp.Status, check.Equals, 400)
  7386  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  7387  	c.Check(rsp.Result.(*errorResult).Message, check.Matches, ".*cannot decode request body.*")
  7388  }
  7389  
  7390  func (s *appSuite) TestPostAppsBadOp(c *check.C) {
  7391  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"random": "json"}`))
  7392  	c.Assert(err, check.IsNil)
  7393  	rsp := postApps(appsCmd, req, nil).(*resp)
  7394  	c.Check(rsp.Status, check.Equals, 400)
  7395  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  7396  	c.Check(rsp.Result.(*errorResult).Message, check.Matches, ".*cannot perform operation on services without a list of services.*")
  7397  }
  7398  
  7399  func (s *appSuite) TestPostAppsBadSnap(c *check.C) {
  7400  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-c"]}`))
  7401  	c.Assert(err, check.IsNil)
  7402  	rsp := postApps(appsCmd, req, nil).(*resp)
  7403  	c.Check(rsp.Status, check.Equals, 404)
  7404  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  7405  	c.Check(rsp.Result.(*errorResult).Message, check.Equals, `snap "snap-c" has no services`)
  7406  }
  7407  
  7408  func (s *appSuite) TestPostAppsBadApp(c *check.C) {
  7409  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-a.what"]}`))
  7410  	c.Assert(err, check.IsNil)
  7411  	rsp := postApps(appsCmd, req, nil).(*resp)
  7412  	c.Check(rsp.Status, check.Equals, 404)
  7413  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  7414  	c.Check(rsp.Result.(*errorResult).Message, check.Equals, `snap "snap-a" has no service "what"`)
  7415  }
  7416  
  7417  func (s *appSuite) TestPostAppsServiceControlError(c *check.C) {
  7418  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "start", "names": ["snap-a.svc1"]}`))
  7419  	c.Assert(err, check.IsNil)
  7420  	s.serviceControlError = fmt.Errorf("total failure")
  7421  	rsp := postApps(appsCmd, req, nil).(*resp)
  7422  	c.Check(rsp.Status, check.Equals, 400)
  7423  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  7424  	c.Check(rsp.Result.(*errorResult).Message, check.Equals, `total failure`)
  7425  }
  7426  
  7427  func (s *appSuite) TestPostAppsConflict(c *check.C) {
  7428  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "start", "names": ["snap-a.svc1"]}`))
  7429  	c.Assert(err, check.IsNil)
  7430  	s.serviceControlError = &snapstate.ChangeConflictError{Snap: "snap-a", ChangeKind: "enable"}
  7431  	rsp := postApps(appsCmd, req, nil).(*resp)
  7432  	c.Check(rsp.Status, check.Equals, 400)
  7433  	c.Check(rsp.Type, check.Equals, ResponseTypeError)
  7434  	c.Check(rsp.Result.(*errorResult).Message, check.Equals, `snap "snap-a" has "enable" change in progress`)
  7435  }
  7436  
  7437  type fakeNetError struct {
  7438  	message   string
  7439  	timeout   bool
  7440  	temporary bool
  7441  }
  7442  
  7443  func (e fakeNetError) Error() string   { return e.message }
  7444  func (e fakeNetError) Timeout() bool   { return e.timeout }
  7445  func (e fakeNetError) Temporary() bool { return e.temporary }
  7446  
  7447  func (s *apiSuite) TestErrToResponseNoSnapsDoesNotPanic(c *check.C) {
  7448  	si := &snapInstruction{Action: "frobble"}
  7449  	errors := []error{
  7450  		store.ErrSnapNotFound,
  7451  		&store.RevisionNotAvailableError{},
  7452  		store.ErrNoUpdateAvailable,
  7453  		store.ErrLocalSnap,
  7454  		&snap.AlreadyInstalledError{Snap: "foo"},
  7455  		&snap.NotInstalledError{Snap: "foo"},
  7456  		&snapstate.SnapNeedsDevModeError{Snap: "foo"},
  7457  		&snapstate.SnapNeedsClassicError{Snap: "foo"},
  7458  		&snapstate.SnapNeedsClassicSystemError{Snap: "foo"},
  7459  		fakeNetError{message: "other"},
  7460  		fakeNetError{message: "timeout", timeout: true},
  7461  		fakeNetError{message: "temp", temporary: true},
  7462  		errors.New("some other error"),
  7463  	}
  7464  
  7465  	for _, err := range errors {
  7466  		rsp := si.errToResponse(err)
  7467  		com := check.Commentf("%v", err)
  7468  		c.Check(rsp, check.NotNil, com)
  7469  		status := rsp.(*resp).Status
  7470  		c.Check(status/100 == 4 || status/100 == 5, check.Equals, true, com)
  7471  	}
  7472  }
  7473  
  7474  func (s *apiSuite) TestErrToResponseForRevisionNotAvailable(c *check.C) {
  7475  	si := &snapInstruction{Action: "frobble", Snaps: []string{"foo"}}
  7476  
  7477  	thisArch := arch.DpkgArchitecture()
  7478  
  7479  	err := &store.RevisionNotAvailableError{
  7480  		Action:  "install",
  7481  		Channel: "stable",
  7482  		Releases: []channel.Channel{
  7483  			snaptest.MustParseChannel("beta", thisArch),
  7484  		},
  7485  	}
  7486  	rsp := si.errToResponse(err).(*resp)
  7487  	c.Check(rsp, check.DeepEquals, &resp{
  7488  		Status: 404,
  7489  		Type:   ResponseTypeError,
  7490  		Result: &errorResult{
  7491  			Message: "no snap revision on specified channel",
  7492  			Kind:    client.ErrorKindSnapChannelNotAvailable,
  7493  			Value: map[string]interface{}{
  7494  				"snap-name":    "foo",
  7495  				"action":       "install",
  7496  				"channel":      "stable",
  7497  				"architecture": thisArch,
  7498  				"releases": []map[string]interface{}{
  7499  					{"architecture": thisArch, "channel": "beta"},
  7500  				},
  7501  			},
  7502  		},
  7503  	})
  7504  
  7505  	err = &store.RevisionNotAvailableError{
  7506  		Action:  "install",
  7507  		Channel: "stable",
  7508  		Releases: []channel.Channel{
  7509  			snaptest.MustParseChannel("beta", "other-arch"),
  7510  		},
  7511  	}
  7512  	rsp = si.errToResponse(err).(*resp)
  7513  	c.Check(rsp, check.DeepEquals, &resp{
  7514  		Status: 404,
  7515  		Type:   ResponseTypeError,
  7516  		Result: &errorResult{
  7517  			Message: "no snap revision on specified architecture",
  7518  			Kind:    client.ErrorKindSnapArchitectureNotAvailable,
  7519  			Value: map[string]interface{}{
  7520  				"snap-name":    "foo",
  7521  				"action":       "install",
  7522  				"channel":      "stable",
  7523  				"architecture": thisArch,
  7524  				"releases": []map[string]interface{}{
  7525  					{"architecture": "other-arch", "channel": "beta"},
  7526  				},
  7527  			},
  7528  		},
  7529  	})
  7530  
  7531  	err = &store.RevisionNotAvailableError{}
  7532  	rsp = si.errToResponse(err).(*resp)
  7533  	c.Check(rsp, check.DeepEquals, &resp{
  7534  		Status: 404,
  7535  		Type:   ResponseTypeError,
  7536  		Result: &errorResult{
  7537  			Message: "no snap revision available as specified",
  7538  			Kind:    client.ErrorKindSnapRevisionNotAvailable,
  7539  			Value:   "foo",
  7540  		},
  7541  	})
  7542  }
  7543  
  7544  func (s *apiSuite) testWarnings(c *check.C, all bool, body io.Reader) (calls string, result interface{}) {
  7545  	s.daemon(c)
  7546  
  7547  	oldOK := stateOkayWarnings
  7548  	oldAll := stateAllWarnings
  7549  	oldPending := statePendingWarnings
  7550  	stateOkayWarnings = func(*state.State, time.Time) int { calls += "ok"; return 0 }
  7551  	stateAllWarnings = func(*state.State) []*state.Warning { calls += "all"; return nil }
  7552  	statePendingWarnings = func(*state.State) ([]*state.Warning, time.Time) { calls += "show"; return nil, time.Time{} }
  7553  	defer func() {
  7554  		stateOkayWarnings = oldOK
  7555  		stateAllWarnings = oldAll
  7556  		statePendingWarnings = oldPending
  7557  	}()
  7558  
  7559  	method := "GET"
  7560  	f := warningsCmd.GET
  7561  	if body != nil {
  7562  		method = "POST"
  7563  		f = warningsCmd.POST
  7564  	}
  7565  	q := url.Values{}
  7566  	if all {
  7567  		q.Set("select", "all")
  7568  	}
  7569  	req, err := http.NewRequest(method, "/v2/warnings?"+q.Encode(), body)
  7570  	c.Assert(err, check.IsNil)
  7571  
  7572  	rsp, ok := f(warningsCmd, req, nil).(*resp)
  7573  	c.Assert(ok, check.Equals, true)
  7574  
  7575  	c.Check(rsp.Type, check.Equals, ResponseTypeSync)
  7576  	c.Check(rsp.Status, check.Equals, 200)
  7577  	c.Assert(rsp.Result, check.NotNil)
  7578  	return calls, rsp.Result
  7579  }
  7580  
  7581  func (s *apiSuite) TestAllWarnings(c *check.C) {
  7582  	calls, result := s.testWarnings(c, true, nil)
  7583  	c.Check(calls, check.Equals, "all")
  7584  	c.Check(result, check.DeepEquals, []state.Warning{})
  7585  }
  7586  
  7587  func (s *apiSuite) TestSomeWarnings(c *check.C) {
  7588  	calls, result := s.testWarnings(c, false, nil)
  7589  	c.Check(calls, check.Equals, "show")
  7590  	c.Check(result, check.DeepEquals, []state.Warning{})
  7591  }
  7592  
  7593  func (s *apiSuite) TestAckWarnings(c *check.C) {
  7594  	calls, result := s.testWarnings(c, false, bytes.NewReader([]byte(`{"action": "okay", "timestamp": "2006-01-02T15:04:05Z"}`)))
  7595  	c.Check(calls, check.Equals, "ok")
  7596  	c.Check(result, check.DeepEquals, 0)
  7597  }
  7598  
  7599  func (s *apiSuite) TestErrToResponseForChangeConflict(c *check.C) {
  7600  	si := &snapInstruction{Action: "frobble", Snaps: []string{"foo"}}
  7601  
  7602  	err := &snapstate.ChangeConflictError{Snap: "foo", ChangeKind: "install"}
  7603  	rsp := si.errToResponse(err).(*resp)
  7604  	c.Check(rsp, check.DeepEquals, &resp{
  7605  		Status: 409,
  7606  		Type:   ResponseTypeError,
  7607  		Result: &errorResult{
  7608  			Message: `snap "foo" has "install" change in progress`,
  7609  			Kind:    client.ErrorKindSnapChangeConflict,
  7610  			Value: map[string]interface{}{
  7611  				"snap-name":   "foo",
  7612  				"change-kind": "install",
  7613  			},
  7614  		},
  7615  	})
  7616  
  7617  	// only snap
  7618  	err = &snapstate.ChangeConflictError{Snap: "foo"}
  7619  	rsp = si.errToResponse(err).(*resp)
  7620  	c.Check(rsp, check.DeepEquals, &resp{
  7621  		Status: 409,
  7622  		Type:   ResponseTypeError,
  7623  		Result: &errorResult{
  7624  			Message: `snap "foo" has changes in progress`,
  7625  			Kind:    client.ErrorKindSnapChangeConflict,
  7626  			Value: map[string]interface{}{
  7627  				"snap-name": "foo",
  7628  			},
  7629  		},
  7630  	})
  7631  
  7632  	// only kind
  7633  	err = &snapstate.ChangeConflictError{Message: "specific error msg", ChangeKind: "some-global-op"}
  7634  	rsp = si.errToResponse(err).(*resp)
  7635  	c.Check(rsp, check.DeepEquals, &resp{
  7636  		Status: 409,
  7637  		Type:   ResponseTypeError,
  7638  		Result: &errorResult{
  7639  			Message: "specific error msg",
  7640  			Kind:    client.ErrorKindSnapChangeConflict,
  7641  			Value: map[string]interface{}{
  7642  				"change-kind": "some-global-op",
  7643  			},
  7644  		},
  7645  	})
  7646  }
  7647  
  7648  func (s *apiSuite) TestErrToResponseInsufficentSpace(c *check.C) {
  7649  	err := &snapstate.InsufficientSpaceError{
  7650  		Snaps:      []string{"foo", "bar"},
  7651  		ChangeKind: "some-change",
  7652  		Path:       "/path",
  7653  		Message:    "specific error msg",
  7654  	}
  7655  	rsp := errToResponse(err, nil, BadRequest, "%s: %v", "ERR").(*resp)
  7656  	c.Check(rsp, check.DeepEquals, &resp{
  7657  		Status: 507,
  7658  		Type:   ResponseTypeError,
  7659  		Result: &errorResult{
  7660  			Message: "specific error msg",
  7661  			Kind:    client.ErrorKindInsufficientDiskSpace,
  7662  			Value: map[string]interface{}{
  7663  				"snap-names":  []string{"foo", "bar"},
  7664  				"change-kind": "some-change",
  7665  			},
  7666  		},
  7667  	})
  7668  }
  7669  
  7670  func (s *apiSuite) TestErrToResponse(c *check.C) {
  7671  	aie := &snap.AlreadyInstalledError{Snap: "foo"}
  7672  	nie := &snap.NotInstalledError{Snap: "foo"}
  7673  	cce := &snapstate.ChangeConflictError{Snap: "foo"}
  7674  	ndme := &snapstate.SnapNeedsDevModeError{Snap: "foo"}
  7675  	nc := &snapstate.SnapNotClassicError{Snap: "foo"}
  7676  	nce := &snapstate.SnapNeedsClassicError{Snap: "foo"}
  7677  	ncse := &snapstate.SnapNeedsClassicSystemError{Snap: "foo"}
  7678  	netoe := fakeNetError{message: "other"}
  7679  	nettoute := fakeNetError{message: "timeout", timeout: true}
  7680  	nettmpe := fakeNetError{message: "temp", temporary: true}
  7681  
  7682  	e := errors.New("other error")
  7683  
  7684  	sa1e := &store.SnapActionError{Refresh: map[string]error{"foo": store.ErrSnapNotFound}}
  7685  	sa2e := &store.SnapActionError{Refresh: map[string]error{
  7686  		"foo": store.ErrSnapNotFound,
  7687  		"bar": store.ErrSnapNotFound,
  7688  	}}
  7689  	saOe := &store.SnapActionError{Other: []error{e}}
  7690  	// this one can't happen (but fun to test):
  7691  	saXe := &store.SnapActionError{Refresh: map[string]error{"foo": sa1e}}
  7692  
  7693  	makeErrorRsp := func(kind client.ErrorKind, err error, value interface{}) Response {
  7694  		return SyncResponse(&resp{
  7695  			Type:   ResponseTypeError,
  7696  			Result: &errorResult{Message: err.Error(), Kind: kind, Value: value},
  7697  			Status: 400,
  7698  		}, nil)
  7699  	}
  7700  
  7701  	tests := []struct {
  7702  		err         error
  7703  		expectedRsp Response
  7704  	}{
  7705  		{store.ErrSnapNotFound, SnapNotFound("foo", store.ErrSnapNotFound)},
  7706  		{store.ErrNoUpdateAvailable, makeErrorRsp(client.ErrorKindSnapNoUpdateAvailable, store.ErrNoUpdateAvailable, "")},
  7707  		{store.ErrLocalSnap, makeErrorRsp(client.ErrorKindSnapLocal, store.ErrLocalSnap, "")},
  7708  		{aie, makeErrorRsp(client.ErrorKindSnapAlreadyInstalled, aie, "foo")},
  7709  		{nie, makeErrorRsp(client.ErrorKindSnapNotInstalled, nie, "foo")},
  7710  		{ndme, makeErrorRsp(client.ErrorKindSnapNeedsDevMode, ndme, "foo")},
  7711  		{nc, makeErrorRsp(client.ErrorKindSnapNotClassic, nc, "foo")},
  7712  		{nce, makeErrorRsp(client.ErrorKindSnapNeedsClassic, nce, "foo")},
  7713  		{ncse, makeErrorRsp(client.ErrorKindSnapNeedsClassicSystem, ncse, "foo")},
  7714  		{cce, SnapChangeConflict(cce)},
  7715  		{nettoute, makeErrorRsp(client.ErrorKindNetworkTimeout, nettoute, "")},
  7716  		{netoe, BadRequest("ERR: %v", netoe)},
  7717  		{nettmpe, BadRequest("ERR: %v", nettmpe)},
  7718  		{e, BadRequest("ERR: %v", e)},
  7719  
  7720  		// action error unwrapping:
  7721  		{sa1e, SnapNotFound("foo", store.ErrSnapNotFound)},
  7722  		{saXe, SnapNotFound("foo", store.ErrSnapNotFound)},
  7723  		// action errors, unwrapped:
  7724  		{sa2e, BadRequest(`ERR: cannot refresh: snap not found: "bar", "foo"`)},
  7725  		{saOe, BadRequest("ERR: cannot refresh, install, or download: other error")},
  7726  	}
  7727  
  7728  	for _, t := range tests {
  7729  		com := check.Commentf("%v", t.err)
  7730  		rsp := errToResponse(t.err, []string{"foo"}, BadRequest, "%s: %v", "ERR")
  7731  		c.Check(rsp, check.DeepEquals, t.expectedRsp, com)
  7732  	}
  7733  }