github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/snapstate/autorefresh_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017-2018 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package snapstate_test
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"os"
    26  	"path/filepath"
    27  	"time"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/dirs"
    32  	"github.com/snapcore/snapd/httputil"
    33  	"github.com/snapcore/snapd/overlord/auth"
    34  	"github.com/snapcore/snapd/overlord/configstate/config"
    35  	"github.com/snapcore/snapd/overlord/snapstate"
    36  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    37  	"github.com/snapcore/snapd/overlord/state"
    38  	"github.com/snapcore/snapd/release"
    39  	"github.com/snapcore/snapd/snap"
    40  	"github.com/snapcore/snapd/store"
    41  	"github.com/snapcore/snapd/store/storetest"
    42  	"github.com/snapcore/snapd/timeutil"
    43  )
    44  
    45  type autoRefreshStore struct {
    46  	storetest.Store
    47  
    48  	ops []string
    49  
    50  	err error
    51  }
    52  
    53  func (r *autoRefreshStore) SnapAction(ctx context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, user *auth.UserState, opts *store.RefreshOptions) ([]*snap.Info, error) {
    54  	if !opts.IsAutoRefresh {
    55  		panic("AutoRefresh snap action did not set IsAutoRefresh flag")
    56  	}
    57  
    58  	if ctx == nil || !auth.IsEnsureContext(ctx) {
    59  		panic("Ensure marked context required")
    60  	}
    61  	if len(currentSnaps) != len(actions) || len(currentSnaps) == 0 {
    62  		panic("expected in test one action for each current snaps, and at least one snap")
    63  	}
    64  	for _, a := range actions {
    65  		if a.Action != "refresh" {
    66  			panic("expected refresh actions")
    67  		}
    68  	}
    69  	r.ops = append(r.ops, "list-refresh")
    70  	return nil, r.err
    71  }
    72  
    73  type autoRefreshTestSuite struct {
    74  	state *state.State
    75  
    76  	store *autoRefreshStore
    77  
    78  	restore func()
    79  }
    80  
    81  var _ = Suite(&autoRefreshTestSuite{})
    82  
    83  func (s *autoRefreshTestSuite) SetUpTest(c *C) {
    84  	dirs.SetRootDir(c.MkDir())
    85  
    86  	s.state = state.New(nil)
    87  
    88  	s.store = &autoRefreshStore{}
    89  
    90  	s.state.Lock()
    91  	defer s.state.Unlock()
    92  	snapstate.ReplaceStore(s.state, s.store)
    93  
    94  	snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
    95  		Active: true,
    96  		Sequence: []*snap.SideInfo{
    97  			{RealName: "some-snap", Revision: snap.R(5), SnapID: "some-snap-id"},
    98  		},
    99  		Current:  snap.R(5),
   100  		SnapType: "app",
   101  		UserID:   1,
   102  	})
   103  
   104  	snapstate.CanAutoRefresh = func(*state.State) (bool, error) { return true, nil }
   105  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
   106  		return nil, nil
   107  	}
   108  	snapstate.IsOnMeteredConnection = func() (bool, error) { return false, nil }
   109  
   110  	s.state.Set("seeded", true)
   111  	s.state.Set("seed-time", time.Now())
   112  	s.state.Set("refresh-privacy-key", "privacy-key")
   113  	s.restore = snapstatetest.MockDeviceModel(DefaultModel())
   114  }
   115  
   116  func (s *autoRefreshTestSuite) TearDownTest(c *C) {
   117  	snapstate.CanAutoRefresh = nil
   118  	snapstate.AutoAliases = nil
   119  	s.restore()
   120  	dirs.SetRootDir("")
   121  }
   122  
   123  func (s *autoRefreshTestSuite) TestLastRefresh(c *C) {
   124  	// this does an immediate refresh
   125  
   126  	af := snapstate.NewAutoRefresh(s.state)
   127  	err := af.Ensure()
   128  	c.Check(err, IsNil)
   129  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh"})
   130  
   131  	var lastRefresh time.Time
   132  	s.state.Lock()
   133  	s.state.Get("last-refresh", &lastRefresh)
   134  	s.state.Unlock()
   135  	c.Check(lastRefresh.Year(), Equals, time.Now().Year())
   136  }
   137  
   138  func (s *autoRefreshTestSuite) TestLastRefreshRefreshManaged(c *C) {
   139  	snapstate.CanManageRefreshes = func(st *state.State) bool {
   140  		return true
   141  	}
   142  	defer func() { snapstate.CanManageRefreshes = nil }()
   143  
   144  	s.state.Lock()
   145  	defer s.state.Unlock()
   146  
   147  	for _, t := range []struct {
   148  		conf   string
   149  		legacy bool
   150  	}{
   151  		{"refresh.timer", false},
   152  		{"refresh.schedule", true},
   153  	} {
   154  		tr := config.NewTransaction(s.state)
   155  		tr.Set("core", t.conf, "managed")
   156  		tr.Commit()
   157  
   158  		af := snapstate.NewAutoRefresh(s.state)
   159  		s.state.Unlock()
   160  		err := af.Ensure()
   161  		s.state.Lock()
   162  		c.Check(err, IsNil)
   163  		c.Check(s.store.ops, HasLen, 0)
   164  
   165  		refreshScheduleStr, legacy, err := af.RefreshSchedule()
   166  		c.Check(refreshScheduleStr, Equals, "managed")
   167  		c.Check(legacy, Equals, t.legacy)
   168  		c.Check(err, IsNil)
   169  
   170  		c.Check(af.NextRefresh(), DeepEquals, time.Time{})
   171  
   172  		// ensure clean config for the next run
   173  		s.state.Set("config", nil)
   174  	}
   175  }
   176  
   177  func (s *autoRefreshTestSuite) TestRefreshManagedTimerWins(c *C) {
   178  	snapstate.CanManageRefreshes = func(st *state.State) bool {
   179  		return true
   180  	}
   181  	defer func() { snapstate.CanManageRefreshes = nil }()
   182  
   183  	s.state.Lock()
   184  	defer s.state.Unlock()
   185  
   186  	tr := config.NewTransaction(s.state)
   187  	// the "refresh.timer" setting always takes precedence over
   188  	// refresh.schedule
   189  	tr.Set("core", "refresh.timer", "00:00-12:00")
   190  	tr.Set("core", "refresh.schedule", "managed")
   191  	tr.Commit()
   192  
   193  	af := snapstate.NewAutoRefresh(s.state)
   194  	s.state.Unlock()
   195  	err := af.Ensure()
   196  	s.state.Lock()
   197  	c.Check(err, IsNil)
   198  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh"})
   199  
   200  	refreshScheduleStr, legacy, err := af.RefreshSchedule()
   201  	c.Check(refreshScheduleStr, Equals, "00:00-12:00")
   202  	c.Check(legacy, Equals, false)
   203  	c.Check(err, IsNil)
   204  }
   205  
   206  func (s *autoRefreshTestSuite) TestLastRefreshNoRefreshNeeded(c *C) {
   207  	s.state.Lock()
   208  	s.state.Set("last-refresh", time.Now())
   209  	s.state.Unlock()
   210  
   211  	af := snapstate.NewAutoRefresh(s.state)
   212  	err := af.Ensure()
   213  	c.Check(err, IsNil)
   214  	c.Check(s.store.ops, HasLen, 0)
   215  }
   216  
   217  func (s *autoRefreshTestSuite) TestRefreshBackoff(c *C) {
   218  	s.store.err = fmt.Errorf("random store error")
   219  	af := snapstate.NewAutoRefresh(s.state)
   220  	err := af.Ensure()
   221  	c.Check(err, ErrorMatches, "random store error")
   222  	c.Check(s.store.ops, HasLen, 1)
   223  
   224  	// override next refresh to be here already
   225  	now := time.Now()
   226  	snapstate.MockNextRefresh(af, now)
   227  
   228  	// call ensure again, our back-off will prevent the store from
   229  	// being hit again
   230  	err = af.Ensure()
   231  	c.Check(err, IsNil)
   232  	c.Check(s.store.ops, HasLen, 1)
   233  
   234  	// nextRefresh unchanged
   235  	c.Check(af.NextRefresh().Equal(now), Equals, true)
   236  
   237  	// fake that the retryRefreshDelay is over
   238  	restore := snapstate.MockRefreshRetryDelay(1 * time.Millisecond)
   239  	defer restore()
   240  	time.Sleep(10 * time.Millisecond)
   241  
   242  	// ensure hits the store again
   243  	err = af.Ensure()
   244  	c.Check(err, ErrorMatches, "random store error")
   245  	c.Check(s.store.ops, HasLen, 2)
   246  
   247  	// nextRefresh now zero
   248  	c.Check(af.NextRefresh().IsZero(), Equals, true)
   249  	// set it to something in the future
   250  	snapstate.MockNextRefresh(af, time.Now().Add(time.Minute))
   251  
   252  	// nothing really happens yet: the previous autorefresh failed
   253  	// but it still counts as having tried to autorefresh
   254  	err = af.Ensure()
   255  	c.Check(err, IsNil)
   256  	c.Check(s.store.ops, HasLen, 2)
   257  
   258  	// pretend the time for next refresh is here
   259  	snapstate.MockNextRefresh(af, time.Now())
   260  	// including the wait for the retryRefreshDelay backoff
   261  	time.Sleep(10 * time.Millisecond)
   262  
   263  	// now yes it happens again
   264  	err = af.Ensure()
   265  	c.Check(err, ErrorMatches, "random store error")
   266  	c.Check(s.store.ops, HasLen, 3)
   267  	// and not *again* again
   268  	err = af.Ensure()
   269  	c.Check(err, IsNil)
   270  	c.Check(s.store.ops, HasLen, 3)
   271  
   272  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh", "list-refresh", "list-refresh"})
   273  }
   274  
   275  func (s *autoRefreshTestSuite) TestRefreshPersistentError(c *C) {
   276  	// fake that the retryRefreshDelay is over
   277  	restore := snapstate.MockRefreshRetryDelay(1 * time.Millisecond)
   278  	defer restore()
   279  
   280  	initialLastRefresh := time.Now().Add(-12 * time.Hour)
   281  	s.state.Lock()
   282  	s.state.Set("last-refresh", initialLastRefresh)
   283  	s.state.Unlock()
   284  
   285  	s.store.err = &httputil.PerstistentNetworkError{Err: fmt.Errorf("error")}
   286  	af := snapstate.NewAutoRefresh(s.state)
   287  	err := af.Ensure()
   288  	c.Check(err, ErrorMatches, "persistent network error: error")
   289  	c.Check(s.store.ops, HasLen, 1)
   290  
   291  	// last-refresh time remains untouched
   292  	var lastRefresh time.Time
   293  	s.state.Lock()
   294  	s.state.Get("last-refresh", &lastRefresh)
   295  	s.state.Unlock()
   296  	c.Check(lastRefresh.Format(time.RFC3339), Equals, initialLastRefresh.Format(time.RFC3339))
   297  
   298  	s.store.err = nil
   299  	time.Sleep(10 * time.Millisecond)
   300  
   301  	// call ensure again, refresh should be attempted again
   302  	err = af.Ensure()
   303  	c.Check(err, IsNil)
   304  	c.Check(s.store.ops, HasLen, 2)
   305  }
   306  
   307  func (s *autoRefreshTestSuite) TestDefaultScheduleIsRandomized(c *C) {
   308  	schedule, err := timeutil.ParseSchedule(snapstate.DefaultRefreshSchedule)
   309  	c.Assert(err, IsNil)
   310  
   311  	for _, sched := range schedule {
   312  		for _, span := range sched.ClockSpans {
   313  			c.Check(span.Start == span.End, Equals, false,
   314  				Commentf("clock span %v is a single time, expected an actual span", span))
   315  			c.Check(span.Spread, Equals, true,
   316  				Commentf("clock span %v is not randomized", span))
   317  		}
   318  	}
   319  }
   320  
   321  func (s *autoRefreshTestSuite) TestLastRefreshRefreshHold(c *C) {
   322  	s.state.Lock()
   323  	defer s.state.Unlock()
   324  
   325  	t0 := time.Now()
   326  	s.state.Set("last-refresh", t0.Add(-12*time.Hour))
   327  
   328  	holdTime := t0.Add(5 * time.Minute)
   329  	tr := config.NewTransaction(s.state)
   330  	tr.Set("core", "refresh.hold", holdTime)
   331  	tr.Commit()
   332  
   333  	af := snapstate.NewAutoRefresh(s.state)
   334  	s.state.Unlock()
   335  	err := af.Ensure()
   336  	s.state.Lock()
   337  	c.Check(err, IsNil)
   338  
   339  	// no refresh
   340  	c.Check(s.store.ops, HasLen, 0)
   341  
   342  	// hold still kept
   343  	tr = config.NewTransaction(s.state)
   344  	var t1 time.Time
   345  	err = tr.Get("core", "refresh.hold", &t1)
   346  	c.Assert(err, IsNil)
   347  	c.Check(t1.Equal(holdTime), Equals, true)
   348  }
   349  
   350  func (s *autoRefreshTestSuite) TestLastRefreshRefreshHoldExpired(c *C) {
   351  	s.state.Lock()
   352  	defer s.state.Unlock()
   353  
   354  	t0 := time.Now()
   355  	s.state.Set("last-refresh", t0.Add(-12*time.Hour))
   356  
   357  	holdTime := t0.Add(-5 * time.Minute)
   358  	tr := config.NewTransaction(s.state)
   359  	tr.Set("core", "refresh.hold", holdTime)
   360  	tr.Commit()
   361  
   362  	af := snapstate.NewAutoRefresh(s.state)
   363  	s.state.Unlock()
   364  	err := af.Ensure()
   365  	s.state.Lock()
   366  	c.Check(err, IsNil)
   367  
   368  	// refresh happened
   369  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh"})
   370  
   371  	var lastRefresh time.Time
   372  	s.state.Get("last-refresh", &lastRefresh)
   373  	c.Check(lastRefresh.Year(), Equals, time.Now().Year())
   374  
   375  	// hold was reset
   376  	tr = config.NewTransaction(s.state)
   377  	var t1 time.Time
   378  	err = tr.Get("core", "refresh.hold", &t1)
   379  	c.Assert(config.IsNoOption(err), Equals, true)
   380  }
   381  
   382  func (s *autoRefreshTestSuite) TestLastRefreshRefreshHoldExpiredReschedule(c *C) {
   383  	s.state.Lock()
   384  	defer s.state.Unlock()
   385  
   386  	t0 := time.Now()
   387  	s.state.Set("last-refresh", t0.Add(-12*time.Hour))
   388  
   389  	holdTime := t0.Add(-1 * time.Minute)
   390  	tr := config.NewTransaction(s.state)
   391  	tr.Set("core", "refresh.hold", holdTime)
   392  
   393  	nextRefresh := t0.Add(5 * time.Minute).Truncate(time.Minute)
   394  	schedule := fmt.Sprintf("%02d:%02d-%02d:59", nextRefresh.Hour(), nextRefresh.Minute(), nextRefresh.Hour())
   395  	tr.Set("core", "refresh.timer", schedule)
   396  	tr.Commit()
   397  
   398  	af := snapstate.NewAutoRefresh(s.state)
   399  	snapstate.MockLastRefreshSchedule(af, schedule)
   400  	snapstate.MockNextRefresh(af, holdTime.Add(-2*time.Minute))
   401  
   402  	s.state.Unlock()
   403  	err := af.Ensure()
   404  	s.state.Lock()
   405  	c.Check(err, IsNil)
   406  
   407  	// refresh did not happen yet
   408  	c.Check(s.store.ops, HasLen, 0)
   409  
   410  	// hold was reset
   411  	tr = config.NewTransaction(s.state)
   412  	var t1 time.Time
   413  	err = tr.Get("core", "refresh.hold", &t1)
   414  	c.Assert(config.IsNoOption(err), Equals, true)
   415  
   416  	// check next refresh
   417  	nextRefresh1 := af.NextRefresh()
   418  	c.Check(nextRefresh1.Before(nextRefresh), Equals, false)
   419  }
   420  
   421  func (s *autoRefreshTestSuite) TestEffectiveRefreshHold(c *C) {
   422  	s.state.Lock()
   423  	defer s.state.Unlock()
   424  
   425  	// assume no seed-time
   426  	s.state.Set("seed-time", nil)
   427  
   428  	af := snapstate.NewAutoRefresh(s.state)
   429  
   430  	t0, err := af.EffectiveRefreshHold()
   431  	c.Assert(err, IsNil)
   432  	c.Check(t0.IsZero(), Equals, true)
   433  
   434  	holdTime := time.Now()
   435  	tr := config.NewTransaction(s.state)
   436  	tr.Set("core", "refresh.hold", holdTime)
   437  	tr.Commit()
   438  
   439  	seedTime := holdTime.Add(-70 * 24 * time.Hour)
   440  	s.state.Set("seed-time", seedTime)
   441  
   442  	t1, err := af.EffectiveRefreshHold()
   443  	c.Assert(err, IsNil)
   444  	c.Check(t1.Equal(seedTime.Add(60*24*time.Hour)), Equals, true)
   445  
   446  	lastRefresh := holdTime.Add(-65 * 24 * time.Hour)
   447  	s.state.Set("last-refresh", lastRefresh)
   448  
   449  	t1, err = af.EffectiveRefreshHold()
   450  	c.Assert(err, IsNil)
   451  	c.Check(t1.Equal(lastRefresh.Add(60*24*time.Hour)), Equals, true)
   452  
   453  	s.state.Set("last-refresh", holdTime.Add(-6*time.Hour))
   454  	t1, err = af.EffectiveRefreshHold()
   455  	c.Assert(err, IsNil)
   456  	c.Check(t1.Equal(holdTime), Equals, true)
   457  }
   458  
   459  func (s *autoRefreshTestSuite) TestEnsureLastRefreshAnchor(c *C) {
   460  	s.state.Lock()
   461  	defer s.state.Unlock()
   462  	// set hold => no refreshes
   463  	t0 := time.Now()
   464  	holdTime := t0.Add(1 * time.Hour)
   465  	tr := config.NewTransaction(s.state)
   466  	tr.Set("core", "refresh.hold", holdTime)
   467  	tr.Commit()
   468  
   469  	// with seed-time
   470  	s.state.Set("seed-time", t0.Add(-1*time.Hour))
   471  
   472  	af := snapstate.NewAutoRefresh(s.state)
   473  	s.state.Unlock()
   474  	err := af.Ensure()
   475  	s.state.Lock()
   476  	c.Check(err, IsNil)
   477  	// no refresh
   478  	c.Check(s.store.ops, HasLen, 0)
   479  	lastRefresh, err := af.LastRefresh()
   480  	c.Assert(err, IsNil)
   481  	c.Check(lastRefresh.IsZero(), Equals, true)
   482  
   483  	// no seed-time
   484  	s.state.Set("seed-time", nil)
   485  
   486  	// fallback to time of executable
   487  	st, err := os.Stat("/proc/self/exe")
   488  	c.Assert(err, IsNil)
   489  	exeTime := st.ModTime()
   490  
   491  	af = snapstate.NewAutoRefresh(s.state)
   492  	s.state.Unlock()
   493  	err = af.Ensure()
   494  	s.state.Lock()
   495  	c.Check(err, IsNil)
   496  	// no refresh
   497  	c.Check(s.store.ops, HasLen, 0)
   498  	lastRefresh, err = af.LastRefresh()
   499  	c.Assert(err, IsNil)
   500  	c.Check(lastRefresh.Equal(exeTime), Equals, true)
   501  
   502  	// clear
   503  	s.state.Set("last-refresh", nil)
   504  	// use core last refresh time
   505  	coreCurrent := filepath.Join(dirs.SnapMountDir, "core", "current")
   506  	err = os.MkdirAll(coreCurrent, 0755)
   507  	c.Assert(err, IsNil)
   508  	st, err = os.Stat(coreCurrent)
   509  	c.Assert(err, IsNil)
   510  	coreRefreshed := st.ModTime()
   511  
   512  	af = snapstate.NewAutoRefresh(s.state)
   513  	s.state.Unlock()
   514  	err = af.Ensure()
   515  	s.state.Lock()
   516  	c.Check(err, IsNil)
   517  	// no refresh
   518  	c.Check(s.store.ops, HasLen, 0)
   519  	lastRefresh, err = af.LastRefresh()
   520  	c.Assert(err, IsNil)
   521  	c.Check(lastRefresh.Equal(coreRefreshed), Equals, true)
   522  }
   523  
   524  func (s *autoRefreshTestSuite) TestAtSeedPolicy(c *C) {
   525  	r := release.MockOnClassic(false)
   526  	defer r()
   527  
   528  	s.state.Lock()
   529  	defer s.state.Unlock()
   530  
   531  	af := snapstate.NewAutoRefresh(s.state)
   532  
   533  	// on core, does nothing
   534  	err := af.AtSeed()
   535  	c.Assert(err, IsNil)
   536  	c.Check(af.NextRefresh().IsZero(), Equals, true)
   537  	tr := config.NewTransaction(s.state)
   538  	var t1 time.Time
   539  	err = tr.Get("core", "refresh.hold", &t1)
   540  	c.Check(config.IsNoOption(err), Equals, true)
   541  
   542  	release.MockOnClassic(true)
   543  	now := time.Now()
   544  	// on classic it sets a refresh hold of 2h
   545  	err = af.AtSeed()
   546  	c.Assert(err, IsNil)
   547  	c.Check(af.NextRefresh().IsZero(), Equals, false)
   548  	tr = config.NewTransaction(s.state)
   549  	err = tr.Get("core", "refresh.hold", &t1)
   550  	c.Check(err, IsNil)
   551  	c.Check(t1.Before(now.Add(2*time.Hour)), Equals, false)
   552  	c.Check(t1.After(now.Add(2*time.Hour+5*time.Minute)), Equals, false)
   553  
   554  	// nop
   555  	err = af.AtSeed()
   556  	c.Assert(err, IsNil)
   557  	var t2 time.Time
   558  	tr = config.NewTransaction(s.state)
   559  	err = tr.Get("core", "refresh.hold", &t2)
   560  	c.Check(err, IsNil)
   561  	c.Check(t1.Equal(t2), Equals, true)
   562  }
   563  
   564  func (s *autoRefreshTestSuite) TestCanRefreshOnMetered(c *C) {
   565  	s.state.Lock()
   566  	defer s.state.Unlock()
   567  
   568  	can, err := snapstate.CanRefreshOnMeteredConnection(s.state)
   569  	c.Assert(can, Equals, true)
   570  	c.Assert(err, Equals, nil)
   571  
   572  	// enable holding refreshes when on metered connection
   573  	tr := config.NewTransaction(s.state)
   574  	err = tr.Set("core", "refresh.metered", "hold")
   575  	c.Assert(err, IsNil)
   576  	tr.Commit()
   577  
   578  	can, err = snapstate.CanRefreshOnMeteredConnection(s.state)
   579  	c.Assert(can, Equals, false)
   580  	c.Assert(err, Equals, nil)
   581  
   582  	// explicitly disable holding refreshes when on metered connection
   583  	tr = config.NewTransaction(s.state)
   584  	err = tr.Set("core", "refresh.metered", "")
   585  	c.Assert(err, IsNil)
   586  	tr.Commit()
   587  
   588  	can, err = snapstate.CanRefreshOnMeteredConnection(s.state)
   589  	c.Assert(can, Equals, true)
   590  	c.Assert(err, Equals, nil)
   591  }
   592  
   593  func (s *autoRefreshTestSuite) TestRefreshOnMeteredConnIsMetered(c *C) {
   594  	// pretend we're on metered connection
   595  	revert := snapstate.MockIsOnMeteredConnection(func() (bool, error) {
   596  		return true, nil
   597  	})
   598  	defer revert()
   599  
   600  	s.state.Lock()
   601  	defer s.state.Unlock()
   602  
   603  	tr := config.NewTransaction(s.state)
   604  	tr.Set("core", "refresh.metered", "hold")
   605  	tr.Commit()
   606  
   607  	af := snapstate.NewAutoRefresh(s.state)
   608  
   609  	s.state.Set("last-refresh", time.Now().Add(-5*24*time.Hour))
   610  	s.state.Unlock()
   611  	err := af.Ensure()
   612  	s.state.Lock()
   613  	c.Check(err, IsNil)
   614  	// no refresh
   615  	c.Check(s.store.ops, HasLen, 0)
   616  
   617  	c.Check(af.NextRefresh(), DeepEquals, time.Time{})
   618  
   619  	// last refresh over 60 days ago, new one is launched regardless of
   620  	// connection being metered
   621  	s.state.Set("last-refresh", time.Now().Add(-61*24*time.Hour))
   622  	s.state.Unlock()
   623  	err = af.Ensure()
   624  	s.state.Lock()
   625  	c.Check(err, IsNil)
   626  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh"})
   627  }
   628  
   629  func (s *autoRefreshTestSuite) TestRefreshOnMeteredConnNotMetered(c *C) {
   630  	// pretend we're on non-metered connection
   631  	revert := snapstate.MockIsOnMeteredConnection(func() (bool, error) {
   632  		return false, nil
   633  	})
   634  	defer revert()
   635  
   636  	s.state.Lock()
   637  	defer s.state.Unlock()
   638  
   639  	tr := config.NewTransaction(s.state)
   640  	tr.Set("core", "refresh.metered", "hold")
   641  	tr.Commit()
   642  
   643  	af := snapstate.NewAutoRefresh(s.state)
   644  
   645  	s.state.Set("last-refresh", time.Now().Add(-5*24*time.Hour))
   646  	s.state.Unlock()
   647  	err := af.Ensure()
   648  	s.state.Lock()
   649  	c.Check(err, IsNil)
   650  	c.Check(s.store.ops, DeepEquals, []string{"list-refresh"})
   651  }