github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/daemon/api_base_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2021 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_test
    21  
    22  import (
    23  	"context"
    24  	"crypto"
    25  	"fmt"
    26  	"io/ioutil"
    27  	"net/http"
    28  	"os"
    29  	"path/filepath"
    30  	"time"
    31  
    32  	"github.com/gorilla/mux"
    33  	"golang.org/x/crypto/sha3"
    34  	"gopkg.in/check.v1"
    35  	"gopkg.in/tomb.v2"
    36  
    37  	"github.com/snapcore/snapd/asserts"
    38  	"github.com/snapcore/snapd/asserts/assertstest"
    39  	"github.com/snapcore/snapd/asserts/sysdb"
    40  	"github.com/snapcore/snapd/daemon"
    41  	"github.com/snapcore/snapd/dirs"
    42  	"github.com/snapcore/snapd/osutil"
    43  	"github.com/snapcore/snapd/overlord"
    44  	"github.com/snapcore/snapd/overlord/assertstate"
    45  	"github.com/snapcore/snapd/overlord/assertstate/assertstatetest"
    46  	"github.com/snapcore/snapd/overlord/auth"
    47  	"github.com/snapcore/snapd/overlord/devicestate"
    48  	"github.com/snapcore/snapd/overlord/devicestate/devicestatetest"
    49  	"github.com/snapcore/snapd/overlord/ifacestate"
    50  	"github.com/snapcore/snapd/overlord/snapstate"
    51  	"github.com/snapcore/snapd/overlord/state"
    52  	"github.com/snapcore/snapd/sandbox"
    53  	"github.com/snapcore/snapd/snap"
    54  	"github.com/snapcore/snapd/snap/snaptest"
    55  	"github.com/snapcore/snapd/store"
    56  	"github.com/snapcore/snapd/store/storetest"
    57  	"github.com/snapcore/snapd/systemd"
    58  	"github.com/snapcore/snapd/testutil"
    59  )
    60  
    61  // TODO: as we split api_test.go and move more tests to live in daemon_test
    62  // instead of daemon, split out functionality from APIBaseSuite
    63  // to only the relevant suite when possible
    64  
    65  type apiBaseSuite struct {
    66  	testutil.BaseTest
    67  
    68  	storetest.Store
    69  
    70  	rsnaps            []*snap.Info
    71  	err               error
    72  	vars              map[string]string
    73  	storeSearch       store.Search
    74  	suggestedCurrency string
    75  	d                 *daemon.Daemon
    76  	user              *auth.UserState
    77  	ctx               context.Context
    78  	currentSnaps      []*store.CurrentSnap
    79  	actions           []*store.SnapAction
    80  
    81  	restoreRelease func()
    82  
    83  	StoreSigning *assertstest.StoreStack
    84  	Brands       *assertstest.SigningAccounts
    85  
    86  	systemctlRestorer func()
    87  	SysctlBufs        [][]byte
    88  
    89  	connectivityResult map[string]bool
    90  
    91  	restoreSanitize func()
    92  	restoreMuxVars  func()
    93  
    94  	authUser *auth.UserState
    95  
    96  	expectedReadAccess  daemon.AccessChecker
    97  	expectedWriteAccess daemon.AccessChecker
    98  }
    99  
   100  func (s *apiBaseSuite) pokeStateLock() {
   101  	// the store should be called without the state lock held. Try
   102  	// to acquire it.
   103  	st := s.d.Overlord().State()
   104  	st.Lock()
   105  	st.Unlock()
   106  }
   107  
   108  func (s *apiBaseSuite) SnapInfo(ctx context.Context, spec store.SnapSpec, user *auth.UserState) (*snap.Info, error) {
   109  	s.pokeStateLock()
   110  	s.user = user
   111  	s.ctx = ctx
   112  	if len(s.rsnaps) > 0 {
   113  		return s.rsnaps[0], s.err
   114  	}
   115  	return nil, s.err
   116  }
   117  
   118  func (s *apiBaseSuite) Find(ctx context.Context, search *store.Search, user *auth.UserState) ([]*snap.Info, error) {
   119  	s.pokeStateLock()
   120  
   121  	s.storeSearch = *search
   122  	s.user = user
   123  	s.ctx = ctx
   124  
   125  	return s.rsnaps, s.err
   126  }
   127  
   128  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) {
   129  	s.pokeStateLock()
   130  	if assertQuery != nil {
   131  		toResolve, toResolveSeq, err := assertQuery.ToResolve()
   132  		if err != nil {
   133  			return nil, nil, err
   134  		}
   135  		if len(toResolve) != 0 || len(toResolveSeq) != 0 {
   136  			panic("no assertion query support")
   137  		}
   138  	}
   139  
   140  	if ctx == nil {
   141  		panic("context required")
   142  	}
   143  	s.currentSnaps = currentSnaps
   144  	s.actions = actions
   145  	s.user = user
   146  
   147  	sars := make([]store.SnapActionResult, len(s.rsnaps))
   148  	for i, rsnap := range s.rsnaps {
   149  		sars[i] = store.SnapActionResult{Info: rsnap}
   150  	}
   151  	return sars, nil, s.err
   152  }
   153  
   154  func (s *apiBaseSuite) SuggestedCurrency() string {
   155  	s.pokeStateLock()
   156  
   157  	return s.suggestedCurrency
   158  }
   159  
   160  func (s *apiBaseSuite) ConnectivityCheck() (map[string]bool, error) {
   161  	s.pokeStateLock()
   162  
   163  	return s.connectivityResult, s.err
   164  }
   165  
   166  func (s *apiBaseSuite) muxVars(*http.Request) map[string]string {
   167  	return s.vars
   168  }
   169  
   170  func (s *apiBaseSuite) SetUpSuite(c *check.C) {
   171  	s.restoreMuxVars = daemon.MockMuxVars(s.muxVars)
   172  	s.restoreRelease = sandbox.MockForceDevMode(false)
   173  	s.systemctlRestorer = systemd.MockSystemctl(s.systemctl)
   174  	s.restoreSanitize = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
   175  }
   176  
   177  func (s *apiBaseSuite) TearDownSuite(c *check.C) {
   178  	s.restoreMuxVars()
   179  	s.restoreRelease()
   180  	s.systemctlRestorer()
   181  	s.restoreSanitize()
   182  }
   183  
   184  func (s *apiBaseSuite) systemctl(args ...string) (buf []byte, err error) {
   185  	if len(s.SysctlBufs) > 0 {
   186  		buf, s.SysctlBufs = s.SysctlBufs[0], s.SysctlBufs[1:]
   187  	}
   188  	return buf, err
   189  }
   190  
   191  var (
   192  	brandPrivKey, _ = assertstest.GenerateKey(752)
   193  )
   194  
   195  func (s *apiBaseSuite) SetUpTest(c *check.C) {
   196  	s.BaseTest.SetUpTest(c)
   197  
   198  	ctlcmds := testutil.MockCommand(c, "systemctl", "").Also("journalctl", "")
   199  	s.AddCleanup(ctlcmds.Restore)
   200  
   201  	s.SysctlBufs = nil
   202  
   203  	dirs.SetRootDir(c.MkDir())
   204  	err := os.MkdirAll(filepath.Dir(dirs.SnapStateFile), 0755)
   205  	restore := osutil.MockMountInfo("")
   206  	s.AddCleanup(restore)
   207  
   208  	c.Assert(err, check.IsNil)
   209  	c.Assert(os.MkdirAll(dirs.SnapMountDir, 0755), check.IsNil)
   210  	c.Assert(os.MkdirAll(dirs.SnapBlobDir, 0755), check.IsNil)
   211  
   212  	s.rsnaps = nil
   213  	s.suggestedCurrency = ""
   214  	s.storeSearch = store.Search{}
   215  	s.err = nil
   216  	s.vars = nil
   217  	s.user = nil
   218  	s.d = nil
   219  	s.currentSnaps = nil
   220  	s.actions = nil
   221  	s.authUser = nil
   222  
   223  	// TODO: consider making the default ReadAccess expectation
   224  	// authenticatedAccess, but that would need even more test changes
   225  	s.expectedReadAccess = daemon.OpenAccess{}
   226  	s.expectedWriteAccess = daemon.AuthenticatedAccess{}
   227  
   228  	// Disable real security backends for all API tests
   229  	s.AddCleanup(ifacestate.MockSecurityBackends(nil))
   230  
   231  	s.StoreSigning = assertstest.NewStoreStack("can0nical", nil)
   232  	s.AddCleanup(sysdb.InjectTrusted(s.StoreSigning.Trusted))
   233  
   234  	s.Brands = assertstest.NewSigningAccounts(s.StoreSigning)
   235  	s.Brands.Register("my-brand", brandPrivKey, nil)
   236  }
   237  
   238  func (s *apiBaseSuite) TearDownTest(c *check.C) {
   239  	s.d = nil
   240  	s.ctx = nil
   241  
   242  	dirs.SetRootDir("")
   243  	s.BaseTest.TearDownTest(c)
   244  }
   245  
   246  func (s *apiBaseSuite) mockModel(c *check.C, st *state.State, model *asserts.Model) {
   247  	// realistic model setup
   248  	if model == nil {
   249  		model = s.Brands.Model("can0nical", "pc", map[string]interface{}{
   250  			"architecture": "amd64",
   251  			"gadget":       "gadget",
   252  			"kernel":       "kernel",
   253  		})
   254  	}
   255  
   256  	snapstate.DeviceCtx = devicestate.DeviceCtx
   257  
   258  	assertstatetest.AddMany(st, model)
   259  
   260  	devicestatetest.SetDevice(st, &auth.DeviceState{
   261  		Brand:  model.BrandID(),
   262  		Model:  model.Model(),
   263  		Serial: "serialserial",
   264  	})
   265  }
   266  
   267  func (s *apiBaseSuite) daemonWithStore(c *check.C, sto snapstate.StoreService) *daemon.Daemon {
   268  	if s.d != nil {
   269  		panic("called daemon*() twice")
   270  	}
   271  	d, err := daemon.NewAndAddRoutes()
   272  	c.Assert(err, check.IsNil)
   273  
   274  	st := d.Overlord().State()
   275  	// mark as already seeded
   276  	st.Lock()
   277  	st.Set("seeded", true)
   278  	st.Unlock()
   279  	c.Assert(d.Overlord().StartUp(), check.IsNil)
   280  
   281  	st.Lock()
   282  	defer st.Unlock()
   283  	snapstate.ReplaceStore(st, sto)
   284  	// registered
   285  	s.mockModel(c, st, nil)
   286  
   287  	// don't actually try to talk to the store on snapstate.Ensure
   288  	// needs doing after the call to devicestate.Manager (which
   289  	// happens in daemon.New via overlord.New)
   290  	snapstate.CanAutoRefresh = nil
   291  
   292  	s.d = d
   293  	return d
   294  }
   295  
   296  func (s *apiBaseSuite) resetDaemon() {
   297  	s.d = nil
   298  }
   299  
   300  func (s *apiBaseSuite) daemon(c *check.C) *daemon.Daemon {
   301  	return s.daemonWithStore(c, s)
   302  }
   303  
   304  func (s *apiBaseSuite) daemonWithOverlordMock(c *check.C) *daemon.Daemon {
   305  	if s.d != nil {
   306  		panic("called daemon*() twice")
   307  	}
   308  
   309  	o := overlord.Mock()
   310  	s.d = daemon.NewWithOverlord(o)
   311  	return s.d
   312  }
   313  
   314  func (s *apiBaseSuite) daemonWithOverlordMockAndStore(c *check.C) *daemon.Daemon {
   315  	if s.d != nil {
   316  		panic("called daemon*() twice")
   317  	}
   318  
   319  	o := overlord.Mock()
   320  	d := daemon.NewWithOverlord(o)
   321  
   322  	st := d.Overlord().State()
   323  	// adds an assertion db
   324  	assertstate.Manager(st, o.TaskRunner())
   325  	st.Lock()
   326  	defer st.Unlock()
   327  	snapstate.ReplaceStore(st, s)
   328  
   329  	s.d = d
   330  	return d
   331  }
   332  
   333  // asUserAuth fakes authorization into the request as for root
   334  func (s *apiBaseSuite) asRootAuth(req *http.Request) {
   335  	req.RemoteAddr = fmt.Sprintf("pid=100;uid=0;socket=%s;", dirs.SnapdSocket)
   336  }
   337  
   338  // asUserAuth adds authorization to the request as for a logged in user
   339  func (s *apiBaseSuite) asUserAuth(c *check.C, req *http.Request) {
   340  	if s.d == nil {
   341  		panic("call s.daemon(c) etc in your test first")
   342  	}
   343  	if s.authUser == nil {
   344  		st := s.d.Overlord().State()
   345  		st.Lock()
   346  		u, err := auth.NewUser(st, "username", "email@test.com", "macaroon", []string{"discharge"})
   347  		st.Unlock()
   348  		c.Assert(err, check.IsNil)
   349  		s.authUser = u
   350  	}
   351  	req.Header.Set("Authorization", fmt.Sprintf(`Macaroon root="%s"`, s.authUser.Macaroon))
   352  	req.RemoteAddr = fmt.Sprintf("pid=100;uid=1000;socket=%s;", dirs.SnapdSocket)
   353  }
   354  
   355  type fakeSnapManager struct{}
   356  
   357  func newFakeSnapManager(st *state.State, runner *state.TaskRunner) *fakeSnapManager {
   358  	runner.AddHandler("fake-install-snap", func(t *state.Task, _ *tomb.Tomb) error {
   359  		return nil
   360  	}, nil)
   361  	runner.AddHandler("fake-install-snap-error", func(t *state.Task, _ *tomb.Tomb) error {
   362  		return fmt.Errorf("fake-install-snap-error errored")
   363  	}, nil)
   364  
   365  	return &fakeSnapManager{}
   366  }
   367  
   368  func (m *fakeSnapManager) Ensure() error {
   369  	return nil
   370  }
   371  
   372  // sanity
   373  var _ overlord.StateManager = (*fakeSnapManager)(nil)
   374  
   375  func (s *apiBaseSuite) daemonWithFakeSnapManager(c *check.C) *daemon.Daemon {
   376  	d := s.daemonWithOverlordMockAndStore(c)
   377  	st := d.Overlord().State()
   378  	runner := d.Overlord().TaskRunner()
   379  	d.Overlord().AddManager(newFakeSnapManager(st, runner))
   380  	d.Overlord().AddManager(runner)
   381  	c.Assert(d.Overlord().StartUp(), check.IsNil)
   382  	return d
   383  }
   384  
   385  func (s *apiBaseSuite) waitTrivialChange(c *check.C, chg *state.Change) {
   386  	err := s.d.Overlord().Settle(5 * time.Second)
   387  	c.Assert(err, check.IsNil)
   388  	c.Assert(chg.IsReady(), check.Equals, true)
   389  }
   390  
   391  func (s *apiBaseSuite) mkInstalledDesktopFile(c *check.C, name, content string) string {
   392  	df := filepath.Join(dirs.SnapDesktopFilesDir, name)
   393  	err := os.MkdirAll(filepath.Dir(df), 0755)
   394  	c.Assert(err, check.IsNil)
   395  	err = ioutil.WriteFile(df, []byte(content), 0644)
   396  	c.Assert(err, check.IsNil)
   397  	return df
   398  }
   399  
   400  func (s *apiBaseSuite) mockSnap(c *check.C, yamlText string) *snap.Info {
   401  	if s.d == nil {
   402  		panic("call s.daemon(c) etc in your test first")
   403  	}
   404  
   405  	snapInfo := snaptest.MockSnap(c, yamlText, &snap.SideInfo{Revision: snap.R(1)})
   406  
   407  	st := s.d.Overlord().State()
   408  
   409  	st.Lock()
   410  	defer st.Unlock()
   411  
   412  	// Put a side info into the state
   413  	snapstate.Set(st, snapInfo.InstanceName(), &snapstate.SnapState{
   414  		Active: true,
   415  		Sequence: []*snap.SideInfo{
   416  			{
   417  				RealName: snapInfo.SnapName(),
   418  				Revision: snapInfo.Revision,
   419  				SnapID:   "ididid",
   420  			},
   421  		},
   422  		Current:  snapInfo.Revision,
   423  		SnapType: string(snapInfo.Type()),
   424  	})
   425  
   426  	// Put the snap into the interface repository
   427  	repo := s.d.Overlord().InterfaceManager().Repository()
   428  	err := repo.AddSnap(snapInfo)
   429  	c.Assert(err, check.IsNil)
   430  	return snapInfo
   431  }
   432  
   433  func (s *apiBaseSuite) mkInstalledInState(c *check.C, d *daemon.Daemon, instanceName, developer, version string, revision snap.Revision, active bool, extraYaml string) *snap.Info {
   434  	snapName, instanceKey := snap.SplitInstanceName(instanceName)
   435  
   436  	if revision.Local() && developer != "" {
   437  		panic("not supported")
   438  	}
   439  
   440  	var snapID string
   441  	if revision.Store() {
   442  		snapID = snapName + "-id"
   443  	}
   444  	// Collect arguments into a snap.SideInfo structure
   445  	sideInfo := &snap.SideInfo{
   446  		SnapID:   snapID,
   447  		RealName: snapName,
   448  		Revision: revision,
   449  		Channel:  "stable",
   450  	}
   451  
   452  	// Collect other arguments into a yaml string
   453  	yamlText := fmt.Sprintf(`
   454  name: %s
   455  version: %s
   456  %s`, snapName, version, extraYaml)
   457  
   458  	// Mock the snap on disk
   459  	snapInfo := snaptest.MockSnapInstance(c, instanceName, yamlText, sideInfo)
   460  	if active {
   461  		dir, rev := filepath.Split(snapInfo.MountDir())
   462  		c.Assert(os.Symlink(rev, dir+"current"), check.IsNil)
   463  	}
   464  	c.Assert(snapInfo.InstanceName(), check.Equals, instanceName)
   465  
   466  	c.Assert(os.MkdirAll(snapInfo.DataDir(), 0755), check.IsNil)
   467  	metadir := filepath.Join(snapInfo.MountDir(), "meta")
   468  	guidir := filepath.Join(metadir, "gui")
   469  	c.Assert(os.MkdirAll(guidir, 0755), check.IsNil)
   470  	c.Check(ioutil.WriteFile(filepath.Join(guidir, "icon.svg"), []byte("yadda icon"), 0644), check.IsNil)
   471  
   472  	if d == nil {
   473  		return snapInfo
   474  	}
   475  	st := d.Overlord().State()
   476  	st.Lock()
   477  	defer st.Unlock()
   478  
   479  	var snapst snapstate.SnapState
   480  	snapstate.Get(st, instanceName, &snapst)
   481  	snapst.Active = active
   482  	snapst.Sequence = append(snapst.Sequence, &snapInfo.SideInfo)
   483  	snapst.Current = snapInfo.SideInfo.Revision
   484  	snapst.TrackingChannel = "stable"
   485  	snapst.InstanceKey = instanceKey
   486  
   487  	snapstate.Set(st, instanceName, &snapst)
   488  
   489  	if developer == "" {
   490  		return snapInfo
   491  	}
   492  
   493  	devAcct := assertstest.NewAccount(s.StoreSigning, developer, map[string]interface{}{
   494  		"account-id": developer + "-id",
   495  	}, "")
   496  
   497  	snapInfo.Publisher = snap.StoreAccount{
   498  		ID:          devAcct.AccountID(),
   499  		Username:    devAcct.Username(),
   500  		DisplayName: devAcct.DisplayName(),
   501  		Validation:  devAcct.Validation(),
   502  	}
   503  
   504  	snapDecl, err := s.StoreSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{
   505  		"series":       "16",
   506  		"snap-id":      snapID,
   507  		"snap-name":    snapName,
   508  		"publisher-id": devAcct.AccountID(),
   509  		"timestamp":    time.Now().Format(time.RFC3339),
   510  	}, nil, "")
   511  	c.Assert(err, check.IsNil)
   512  
   513  	content, err := ioutil.ReadFile(snapInfo.MountFile())
   514  	c.Assert(err, check.IsNil)
   515  	h := sha3.Sum384(content)
   516  	dgst, err := asserts.EncodeDigest(crypto.SHA3_384, h[:])
   517  	c.Assert(err, check.IsNil)
   518  	snapRev, err := s.StoreSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{
   519  		"snap-sha3-384": string(dgst),
   520  		"snap-size":     "999",
   521  		"snap-id":       snapID,
   522  		"snap-revision": revision.String(), // this must be a string
   523  		"developer-id":  devAcct.AccountID(),
   524  		"timestamp":     time.Now().Format(time.RFC3339),
   525  	}, nil, "")
   526  	c.Assert(err, check.IsNil)
   527  
   528  	assertstatetest.AddMany(st, s.StoreSigning.StoreAccountKey(""), devAcct, snapDecl, snapRev)
   529  
   530  	return snapInfo
   531  }
   532  
   533  func handlerCommand(c *check.C, d *daemon.Daemon, req *http.Request) (cmd *daemon.Command, vars map[string]string) {
   534  	m := &mux.RouteMatch{}
   535  	if !d.RouterMatch(req, m) {
   536  		c.Fatalf("no command for URL %q", req.URL)
   537  	}
   538  	cmd, ok := m.Handler.(*daemon.Command)
   539  	if !ok {
   540  		c.Fatalf("no command for URL %q", req.URL)
   541  	}
   542  	return cmd, m.Vars
   543  }
   544  
   545  func (s *apiBaseSuite) checkGetOnly(c *check.C, req *http.Request) {
   546  	if s.d == nil {
   547  		panic("call s.daemon(c) etc in your test first")
   548  	}
   549  
   550  	cmd, _ := handlerCommand(c, s.d, req)
   551  	c.Check(cmd.POST, check.IsNil)
   552  	c.Check(cmd.PUT, check.IsNil)
   553  	c.Check(cmd.GET, check.NotNil)
   554  }
   555  
   556  func (s *apiBaseSuite) expectOpenAccess() {
   557  	s.expectedReadAccess = daemon.OpenAccess{}
   558  }
   559  
   560  func (s *apiBaseSuite) expectRootAccess() {
   561  	s.expectedReadAccess = daemon.RootAccess{}
   562  	s.expectedWriteAccess = daemon.RootAccess{}
   563  }
   564  
   565  func (s *apiBaseSuite) expectAuthenticatedAccess() {
   566  	s.expectedReadAccess = daemon.AuthenticatedAccess{}
   567  	s.expectedWriteAccess = daemon.AuthenticatedAccess{}
   568  }
   569  
   570  func (s *apiBaseSuite) expectReadAccess(a daemon.AccessChecker) {
   571  	s.expectedReadAccess = a
   572  }
   573  
   574  func (s *apiBaseSuite) expectWriteAccess(a daemon.AccessChecker) {
   575  	s.expectedWriteAccess = a
   576  }
   577  
   578  func (s *apiBaseSuite) req(c *check.C, req *http.Request, u *auth.UserState) daemon.Response {
   579  	if s.d == nil {
   580  		panic("call s.daemon(c) etc in your test first")
   581  	}
   582  
   583  	cmd, vars := handlerCommand(c, s.d, req)
   584  	s.vars = vars
   585  	var f daemon.ResponseFunc
   586  	var acc, expAcc daemon.AccessChecker
   587  	var whichAcc string
   588  	switch req.Method {
   589  	case "GET":
   590  		f = cmd.GET
   591  		acc = cmd.ReadAccess
   592  		expAcc = s.expectedReadAccess
   593  		whichAcc = "ReadAccess"
   594  	case "POST":
   595  		f = cmd.POST
   596  		acc = cmd.WriteAccess
   597  		expAcc = s.expectedWriteAccess
   598  		whichAcc = "WriteAccess"
   599  	case "PUT":
   600  		f = cmd.PUT
   601  		acc = cmd.WriteAccess
   602  		expAcc = s.expectedWriteAccess
   603  		whichAcc = "WriteAccess"
   604  	default:
   605  		c.Fatalf("unsupported HTTP method %q", req.Method)
   606  	}
   607  	if f == nil {
   608  		c.Fatalf("no support for %q for %q", req.Method, req.URL)
   609  	}
   610  	c.Check(acc, check.DeepEquals, expAcc, check.Commentf("expected %s check mismatch, use the apiBaseSuite.expect*Access methods to match the appropriate access check for the API under test", whichAcc))
   611  	return f(cmd, req, u)
   612  }
   613  
   614  func (s *apiBaseSuite) jsonReq(c *check.C, req *http.Request, u *auth.UserState) *daemon.RespJSON {
   615  	rsp, ok := s.req(c, req, u).(daemon.StructuredResponse)
   616  	c.Assert(ok, check.Equals, true, check.Commentf("expected structured response"))
   617  	return rsp.JSON()
   618  }
   619  
   620  func (s *apiBaseSuite) syncReq(c *check.C, req *http.Request, u *auth.UserState) *daemon.RespJSON {
   621  	rsp := s.jsonReq(c, req, u)
   622  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeSync, check.Commentf("expected sync resp: %#v, result: %+v", rsp, rsp.Result))
   623  	return rsp
   624  }
   625  
   626  func (s *apiBaseSuite) asyncReq(c *check.C, req *http.Request, u *auth.UserState) *daemon.RespJSON {
   627  	rsp := s.jsonReq(c, req, u)
   628  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeAsync, check.Commentf("expected async resp: %#v", rsp))
   629  	return rsp
   630  }
   631  
   632  func (s *apiBaseSuite) errorReq(c *check.C, req *http.Request, u *auth.UserState) *daemon.APIError {
   633  	rsp := s.req(c, req, u)
   634  	rspe, ok := rsp.(*daemon.APIError)
   635  	c.Assert(ok, check.Equals, true, check.Commentf("expected apiError resp: %#v", rsp))
   636  	return rspe
   637  }
   638  
   639  func (s *apiBaseSuite) serveHTTP(c *check.C, w http.ResponseWriter, req *http.Request) {
   640  	if s.d == nil {
   641  		panic("call s.daemon(c) etc in your test first")
   642  	}
   643  
   644  	cmd, vars := handlerCommand(c, s.d, req)
   645  	s.vars = vars
   646  
   647  	cmd.ServeHTTP(w, req)
   648  }
   649  
   650  func (s *apiBaseSuite) simulateConflict(name string) {
   651  	if s.d == nil {
   652  		panic("call s.daemon(c) etc in your test first")
   653  	}
   654  
   655  	o := s.d.Overlord()
   656  	st := o.State()
   657  	st.Lock()
   658  	defer st.Unlock()
   659  	t := st.NewTask("link-snap", "...")
   660  	snapsup := &snapstate.SnapSetup{SideInfo: &snap.SideInfo{
   661  		RealName: name,
   662  	}}
   663  	t.Set("snap-setup", snapsup)
   664  	chg := st.NewChange("manip", "...")
   665  	chg.AddTask(t)
   666  }