github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/snapstate/catalogrefresh_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  	"io"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"time"
    29  
    30  	. "gopkg.in/check.v1"
    31  
    32  	"github.com/snapcore/snapd/advisor"
    33  	"github.com/snapcore/snapd/dirs"
    34  	"github.com/snapcore/snapd/osutil"
    35  	"github.com/snapcore/snapd/overlord/auth"
    36  	"github.com/snapcore/snapd/overlord/snapstate"
    37  	"github.com/snapcore/snapd/overlord/state"
    38  	"github.com/snapcore/snapd/store"
    39  	"github.com/snapcore/snapd/store/storetest"
    40  	"github.com/snapcore/snapd/testutil"
    41  )
    42  
    43  type catalogStore struct {
    44  	storetest.Store
    45  
    46  	ops     []string
    47  	tooMany bool
    48  }
    49  
    50  func (r *catalogStore) WriteCatalogs(ctx context.Context, w io.Writer, a store.SnapAdder) error {
    51  	if ctx == nil || !auth.IsEnsureContext(ctx) {
    52  		panic("Ensure marked context required")
    53  	}
    54  	r.ops = append(r.ops, "write-catalog")
    55  	if r.tooMany {
    56  		return store.ErrTooManyRequests
    57  	}
    58  	w.Write([]byte("pkg1\npkg2"))
    59  	a.AddSnap("foo", "1.0", "foo summary", []string{"foo", "meh"})
    60  	a.AddSnap("bar", "2.0", "bar summray", []string{"bar", "meh"})
    61  	return nil
    62  }
    63  
    64  func (r *catalogStore) Sections(ctx context.Context, _ *auth.UserState) ([]string, error) {
    65  	if ctx == nil || !auth.IsEnsureContext(ctx) {
    66  		panic("Ensure marked context required")
    67  	}
    68  	r.ops = append(r.ops, "sections")
    69  	if r.tooMany {
    70  		return nil, store.ErrTooManyRequests
    71  	}
    72  	return []string{"section1", "section2"}, nil
    73  }
    74  
    75  type catalogRefreshTestSuite struct {
    76  	state *state.State
    77  
    78  	store  *catalogStore
    79  	tmpdir string
    80  }
    81  
    82  var _ = Suite(&catalogRefreshTestSuite{})
    83  
    84  func (s *catalogRefreshTestSuite) SetUpTest(c *C) {
    85  	s.tmpdir = c.MkDir()
    86  	dirs.SetRootDir(s.tmpdir)
    87  	s.state = state.New(nil)
    88  
    89  	s.store = &catalogStore{}
    90  	s.state.Lock()
    91  	snapstate.ReplaceStore(s.state, s.store)
    92  	s.state.Unlock()
    93  
    94  	snapstate.CanAutoRefresh = func(*state.State) (bool, error) { return true, nil }
    95  }
    96  
    97  func (s *catalogRefreshTestSuite) TearDownTest(c *C) {
    98  	snapstate.CanAutoRefresh = nil
    99  }
   100  
   101  func (s *catalogRefreshTestSuite) TestCatalogRefresh(c *C) {
   102  	// start with no catalog
   103  	c.Check(dirs.SnapSectionsFile, testutil.FileAbsent)
   104  	c.Check(dirs.SnapNamesFile, testutil.FileAbsent)
   105  	c.Check(dirs.SnapCommandsDB, testutil.FileAbsent)
   106  
   107  	cr7 := snapstate.NewCatalogRefresh(s.state)
   108  	// next is initially zero
   109  	c.Check(snapstate.NextCatalogRefresh(cr7).IsZero(), Equals, true)
   110  	t0 := time.Now()
   111  
   112  	err := cr7.Ensure()
   113  	c.Check(err, IsNil)
   114  
   115  	// next now has a delta (next refresh is not before t0 + delta)
   116  	c.Check(snapstate.NextCatalogRefresh(cr7).Before(t0.Add(snapstate.CatalogRefreshDelayWithDelta)), Equals, false)
   117  
   118  	c.Check(s.store.ops, DeepEquals, []string{"sections", "write-catalog"})
   119  
   120  	c.Check(osutil.FileExists(dirs.SnapSectionsFile), Equals, true)
   121  	c.Check(dirs.SnapSectionsFile, testutil.FileEquals, "section1\nsection2")
   122  
   123  	c.Check(osutil.FileExists(dirs.SnapNamesFile), Equals, true)
   124  	c.Check(dirs.SnapNamesFile, testutil.FileEquals, "pkg1\npkg2")
   125  
   126  	c.Check(osutil.FileExists(dirs.SnapCommandsDB), Equals, true)
   127  	dump, err := advisor.DumpCommands()
   128  	c.Assert(err, IsNil)
   129  	c.Check(dump, DeepEquals, map[string]string{
   130  		"foo": `[{"snap":"foo","version":"1.0"}]`,
   131  		"bar": `[{"snap":"bar","version":"2.0"}]`,
   132  		"meh": `[{"snap":"foo","version":"1.0"},{"snap":"bar","version":"2.0"}]`,
   133  	})
   134  }
   135  
   136  func (s *catalogRefreshTestSuite) TestCatalogRefreshTooMany(c *C) {
   137  	s.store.tooMany = true
   138  
   139  	cr7 := snapstate.NewCatalogRefresh(s.state)
   140  	// next is initially zero
   141  	c.Check(snapstate.NextCatalogRefresh(cr7).IsZero(), Equals, true)
   142  	t0 := time.Now()
   143  
   144  	err := cr7.Ensure()
   145  	c.Check(err, IsNil) // !!
   146  
   147  	// next now has a delta (next refresh is not before t0 + delta)
   148  	c.Check(snapstate.NextCatalogRefresh(cr7).Before(t0.Add(snapstate.CatalogRefreshDelayWithDelta)), Equals, false)
   149  
   150  	// it tried one endpoint and bailed at the first 429
   151  	c.Check(s.store.ops, HasLen, 1)
   152  
   153  	// nothing got created
   154  	c.Check(osutil.FileExists(dirs.SnapSectionsFile), Equals, false)
   155  	c.Check(osutil.FileExists(dirs.SnapNamesFile), Equals, false)
   156  	c.Check(osutil.FileExists(dirs.SnapCommandsDB), Equals, false)
   157  }
   158  
   159  func (s *catalogRefreshTestSuite) TestCatalogRefreshNotNeeded(c *C) {
   160  	cr7 := snapstate.NewCatalogRefresh(s.state)
   161  	snapstate.MockCatalogRefreshNextRefresh(cr7, time.Now().Add(1*time.Hour))
   162  	err := cr7.Ensure()
   163  	c.Check(err, IsNil)
   164  	c.Check(s.store.ops, HasLen, 0)
   165  	c.Check(osutil.FileExists(dirs.SnapSectionsFile), Equals, false)
   166  	c.Check(osutil.FileExists(dirs.SnapNamesFile), Equals, false)
   167  }
   168  
   169  func (s *catalogRefreshTestSuite) TestCatalogRefreshNewEnough(c *C) {
   170  	// write a fake sections file just to have it
   171  	c.Assert(os.MkdirAll(filepath.Dir(dirs.SnapNamesFile), 0755), IsNil)
   172  	c.Assert(ioutil.WriteFile(dirs.SnapNamesFile, nil, 0644), IsNil)
   173  	// set the timestamp to something known
   174  	t0 := time.Now().Truncate(time.Hour)
   175  	c.Assert(os.Chtimes(dirs.SnapNamesFile, t0, t0), IsNil)
   176  
   177  	cr7 := snapstate.NewCatalogRefresh(s.state)
   178  	// next is initially zero
   179  	c.Check(snapstate.NextCatalogRefresh(cr7).IsZero(), Equals, true)
   180  	err := cr7.Ensure()
   181  	c.Assert(err, IsNil)
   182  	c.Check(s.store.ops, HasLen, 0)
   183  	next := snapstate.NextCatalogRefresh(cr7)
   184  	// next is no longer zero,
   185  	c.Check(next.IsZero(), Equals, false)
   186  	// but has a delta WRT the timestamp
   187  	c.Check(next.Equal(t0.Add(snapstate.CatalogRefreshDelayWithDelta)), Equals, true)
   188  }
   189  
   190  func (s *catalogRefreshTestSuite) TestCatalogRefreshTooNew(c *C) {
   191  	// write a fake sections file just to have it
   192  	c.Assert(os.MkdirAll(filepath.Dir(dirs.SnapNamesFile), 0755), IsNil)
   193  	c.Assert(ioutil.WriteFile(dirs.SnapNamesFile, nil, 0644), IsNil)
   194  	// but set the timestamp in the future
   195  	t := time.Now().Add(time.Hour)
   196  	c.Assert(os.Chtimes(dirs.SnapNamesFile, t, t), IsNil)
   197  
   198  	cr7 := snapstate.NewCatalogRefresh(s.state)
   199  	err := cr7.Ensure()
   200  	c.Check(err, IsNil)
   201  	c.Check(s.store.ops, DeepEquals, []string{"sections", "write-catalog"})
   202  }