go.temporal.io/server@v1.23.0/common/dynamicconfig/file_based_client_test.go (about)

     1  // The MIT License
     2  //
     3  // Copyright (c) 2020 Temporal Technologies Inc.  All rights reserved.
     4  //
     5  // Copyright (c) 2020 Uber Technologies, Inc.
     6  //
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  //
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  //
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  package dynamicconfig
    26  
    27  import (
    28  	"os"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/golang/mock/gomock"
    33  	"github.com/stretchr/testify/require"
    34  	"github.com/stretchr/testify/suite"
    35  
    36  	enumspb "go.temporal.io/api/enums/v1"
    37  
    38  	enumsspb "go.temporal.io/server/api/enums/v1"
    39  	"go.temporal.io/server/common/log"
    40  )
    41  
    42  // Note: fileBasedClientSuite also heavily tests Collection, since some tests are easier with data
    43  // provided from a file.
    44  type fileBasedClientSuite struct {
    45  	suite.Suite
    46  	*require.Assertions
    47  	client     Client
    48  	collection *Collection
    49  	doneCh     chan interface{}
    50  }
    51  
    52  func TestFileBasedClientSuite(t *testing.T) {
    53  	s := new(fileBasedClientSuite)
    54  	suite.Run(t, s)
    55  }
    56  
    57  func (s *fileBasedClientSuite) SetupSuite() {
    58  	var err error
    59  	s.doneCh = make(chan interface{})
    60  	logger := log.NewNoopLogger()
    61  	s.client, err = NewFileBasedClient(&FileBasedClientConfig{
    62  		Filepath:     "config/testConfig.yaml",
    63  		PollInterval: time.Second * 5,
    64  	}, logger, s.doneCh)
    65  	s.collection = NewCollection(s.client, logger)
    66  	s.Require().NoError(err)
    67  }
    68  
    69  func (s *fileBasedClientSuite) TearDownSuite() {
    70  	close(s.doneCh)
    71  }
    72  
    73  func (s *fileBasedClientSuite) SetupTest() {
    74  	s.Assertions = require.New(s.T())
    75  }
    76  
    77  func (s *fileBasedClientSuite) TestGetValue() {
    78  	cvs := s.client.GetValue(testGetBoolPropertyKey)
    79  	s.Equal(3, len(cvs))
    80  	s.ElementsMatch([]ConstrainedValue{
    81  		{Constraints: Constraints{}, Value: false},
    82  		{Constraints: Constraints{Namespace: "global-samples-namespace"}, Value: true},
    83  		{Constraints: Constraints{Namespace: "samples-namespace"}, Value: true},
    84  	}, cvs)
    85  }
    86  
    87  func (s *fileBasedClientSuite) TestGetValue_NonExistKey() {
    88  	cvs := s.client.GetValue(unknownKey)
    89  	s.Nil(cvs)
    90  
    91  	defaultValue := true
    92  	v := s.collection.GetBoolProperty(unknownKey, defaultValue)()
    93  	s.Equal(defaultValue, v)
    94  }
    95  
    96  func (s *fileBasedClientSuite) TestGetValue_CaseInsensitie() {
    97  	cvs := s.client.GetValue(testCaseInsensitivePropertyKey)
    98  	s.Equal(1, len(cvs))
    99  
   100  	v := s.collection.GetBoolProperty(testCaseInsensitivePropertyKey, false)()
   101  	s.Equal(true, v)
   102  }
   103  
   104  func (s *fileBasedClientSuite) TestGetIntValue() {
   105  	v := s.collection.GetIntProperty(testGetIntPropertyKey, 1)()
   106  	s.Equal(1000, v)
   107  }
   108  
   109  func (s *fileBasedClientSuite) TestGetIntValue_FilterNotMatch() {
   110  	v := s.collection.GetIntPropertyFilteredByNamespace(testGetIntPropertyKey, 500)("samples-namespace")
   111  	s.Equal(1000, v)
   112  }
   113  
   114  func (s *fileBasedClientSuite) TestGetIntValue_WrongType() {
   115  	defaultValue := 2000
   116  	v := s.collection.GetIntPropertyFilteredByNamespace(testGetIntPropertyKey, defaultValue)("global-samples-namespace")
   117  	s.Equal(defaultValue, v)
   118  }
   119  
   120  func (s *fileBasedClientSuite) TestGetIntValue_FilteredByWorkflowTaskQueueInfo() {
   121  	expectedValue := 1001
   122  	v := s.collection.GetIntPropertyFilteredByTaskQueueInfo(testGetIntPropertyKey, 0)(
   123  		"global-samples-namespace", "test-tq", enumspb.TASK_QUEUE_TYPE_WORKFLOW)
   124  	s.Equal(expectedValue, v)
   125  }
   126  
   127  func (s *fileBasedClientSuite) TestGetIntValue_FilteredByNoTaskTypeQueueInfo() {
   128  	expectedValue := 1003
   129  	v := s.collection.GetIntPropertyFilteredByTaskQueueInfo(testGetIntPropertyKey, 0)(
   130  		// this is contrived, but simulates something that doesn't match workflow or activity
   131  		"global-samples-namespace", "test-tq", enumspb.TaskQueueType(3),
   132  	)
   133  	s.Equal(expectedValue, v)
   134  }
   135  
   136  func (s *fileBasedClientSuite) TestGetIntValue_FilteredByActivityTaskQueueInfo() {
   137  	expectedValue := 1002
   138  	v := s.collection.GetIntPropertyFilteredByTaskQueueInfo(testGetIntPropertyKey, 0)(
   139  		"global-samples-namespace", "test-tq", enumspb.TASK_QUEUE_TYPE_ACTIVITY)
   140  	s.Equal(expectedValue, v)
   141  }
   142  
   143  func (s *fileBasedClientSuite) TestGetIntValue_FilteredByTaskQueueNameOnly() {
   144  	expectedValue := 1005
   145  	v := s.collection.GetIntPropertyFilteredByTaskQueueInfo(testGetIntPropertyKey, 0)(
   146  		"some-other-namespace", "other-test-tq", enumspb.TASK_QUEUE_TYPE_WORKFLOW)
   147  	s.Equal(expectedValue, v)
   148  }
   149  
   150  func (s *fileBasedClientSuite) TestGetIntValue_FilterByTQ_NamespaceOnly() {
   151  	expectedValue := 1004
   152  	v := s.collection.GetIntPropertyFilteredByTaskQueueInfo(testGetIntPropertyKey, 0)(
   153  		"another-namespace", "test-tq", 0)
   154  	s.Equal(expectedValue, v)
   155  	expectedValue = 1005
   156  	v = s.collection.GetIntPropertyFilteredByTaskQueueInfo(testGetIntPropertyKey, 0)(
   157  		"another-namespace", "other-test-tq", 0)
   158  	s.Equal(expectedValue, v)
   159  }
   160  
   161  func (s *fileBasedClientSuite) TestGetIntValue_FilterByTQ_MatchFallback() {
   162  	// should return 1001 as the most specific match
   163  	v1 := s.collection.GetIntPropertyFilteredByTaskQueueInfo(testGetIntPropertyKey, 1001)(
   164  		"global-samples-namespace", "test-tq", enumspb.TASK_QUEUE_TYPE_WORKFLOW)
   165  	v2 := s.collection.GetIntPropertyFilteredByTaskQueueInfo(testGetIntPropertyKey, 0)(
   166  		"global-samples-namespace", "test-tq", enumspb.TASK_QUEUE_TYPE_WORKFLOW)
   167  	s.Equal(v1, v2)
   168  }
   169  
   170  func (s *fileBasedClientSuite) TestGetFloatValue() {
   171  	v := s.collection.GetFloat64Property(testGetFloat64PropertyKey, 1)()
   172  	s.Equal(12.0, v)
   173  }
   174  
   175  func (s *fileBasedClientSuite) TestGetFloatValue_WrongType() {
   176  	defaultValue := 1.0
   177  	v := s.collection.GetFloatPropertyFilteredByNamespace(testGetFloat64PropertyKey, defaultValue)("samples-namespace")
   178  	s.Equal(defaultValue, v)
   179  }
   180  
   181  func (s *fileBasedClientSuite) TestGetBoolValue() {
   182  	v := s.collection.GetBoolProperty(testGetBoolPropertyKey, true)()
   183  	s.Equal(false, v)
   184  }
   185  
   186  func (s *fileBasedClientSuite) TestGetStringValue() {
   187  	v := s.collection.GetStringPropertyFnWithNamespaceFilter(testGetStringPropertyKey, "defaultString")("random-namespace")
   188  	s.Equal("constrained-string", v)
   189  }
   190  
   191  func (s *fileBasedClientSuite) TestGetMapValue() {
   192  	var defaultVal map[string]interface{}
   193  	v := s.collection.GetMapProperty(testGetMapPropertyKey, defaultVal)()
   194  	expectedVal := map[string]interface{}{
   195  		"key1": "1",
   196  		"key2": 1,
   197  		"key3": []interface{}{
   198  			false,
   199  			map[string]interface{}{
   200  				"key4": true,
   201  				"key5": 2.1,
   202  			},
   203  		},
   204  	}
   205  	s.Equal(expectedVal, v)
   206  }
   207  
   208  func (s *fileBasedClientSuite) TestGetMapValue_WrongType() {
   209  	var defaultVal map[string]interface{}
   210  	v := s.collection.GetMapPropertyFnWithNamespaceFilter(testGetMapPropertyKey, defaultVal)("random-namespace")
   211  	s.Equal(defaultVal, v)
   212  }
   213  
   214  func (s *fileBasedClientSuite) TestGetDurationValue() {
   215  	v := s.collection.GetDurationProperty(testGetDurationPropertyKey, time.Second)()
   216  	s.Equal(time.Minute, v)
   217  }
   218  
   219  func (s *fileBasedClientSuite) TestGetDurationValue_DefaultSeconds() {
   220  	v := s.collection.GetDurationPropertyFilteredByNamespace(testGetDurationPropertyKey, time.Second)("samples-namespace")
   221  	s.Equal(2*time.Second, v)
   222  }
   223  
   224  func (s *fileBasedClientSuite) TestGetDurationValue_NotStringRepresentation() {
   225  	v := s.collection.GetDurationPropertyFilteredByNamespace(testGetDurationPropertyKey, time.Second)("broken-namespace")
   226  	s.Equal(time.Second, v)
   227  }
   228  
   229  func (s *fileBasedClientSuite) TestGetDurationValue_ParseFailed() {
   230  	v := s.collection.GetDurationPropertyFilteredByTaskQueueInfo(testGetDurationPropertyKey, time.Second)(
   231  		"samples-namespace", "longIdleTimeTaskqueue", enumspb.TASK_QUEUE_TYPE_WORKFLOW)
   232  	s.Equal(time.Second, v)
   233  }
   234  
   235  func (s *fileBasedClientSuite) TestGetDurationValue_FilteredByTaskTypeQueue() {
   236  	expectedValue := time.Second * 10
   237  	v := s.collection.GetDurationPropertyFilteredByTaskType(testGetDurationPropertyFilteredByTaskTypeKey, 0)(
   238  		enumsspb.TASK_TYPE_ACTIVITY_RETRY_TIMER,
   239  	)
   240  	s.Equal(expectedValue, v)
   241  	v = s.collection.GetDurationPropertyFilteredByTaskType(testGetDurationPropertyFilteredByTaskTypeKey, 0)(
   242  		enumsspb.TASK_TYPE_REPLICATION_HISTORY,
   243  	)
   244  	s.Equal(expectedValue, v)
   245  }
   246  
   247  func (s *fileBasedClientSuite) TestValidateConfig_ConfigNotExist() {
   248  	_, err := NewFileBasedClient(nil, nil, nil)
   249  	s.Error(err)
   250  }
   251  
   252  func (s *fileBasedClientSuite) TestValidateConfig_FileNotExist() {
   253  	_, err := NewFileBasedClient(&FileBasedClientConfig{
   254  		Filepath:     "file/not/exist.yaml",
   255  		PollInterval: time.Second * 10,
   256  	}, nil, nil)
   257  	s.Error(err)
   258  }
   259  
   260  func (s *fileBasedClientSuite) TestValidateConfig_ShortPollInterval() {
   261  	_, err := NewFileBasedClient(&FileBasedClientConfig{
   262  		Filepath:     "config/testConfig.yaml",
   263  		PollInterval: time.Second,
   264  	}, nil, nil)
   265  	s.Error(err)
   266  }
   267  
   268  type MockFileInfo struct {
   269  	FileName     string
   270  	IsDirectory  bool
   271  	ModTimeValue time.Time
   272  }
   273  
   274  func (mfi MockFileInfo) Name() string       { return mfi.FileName }
   275  func (mfi MockFileInfo) Size() int64        { return int64(8) }
   276  func (mfi MockFileInfo) Mode() os.FileMode  { return os.ModePerm }
   277  func (mfi MockFileInfo) ModTime() time.Time { return mfi.ModTimeValue }
   278  func (mfi MockFileInfo) IsDir() bool        { return mfi.IsDirectory }
   279  func (mfi MockFileInfo) Sys() interface{}   { return nil }
   280  
   281  func (s *fileBasedClientSuite) TestUpdate_ChangedValue() {
   282  	ctrl := gomock.NewController(s.T())
   283  	defer ctrl.Finish()
   284  
   285  	doneCh := make(chan interface{})
   286  	reader := NewMockfileReader(ctrl)
   287  	mockLogger := log.NewMockLogger(ctrl)
   288  
   289  	updateInterval := time.Minute * 5
   290  	originFileInfo := &MockFileInfo{ModTimeValue: time.Now()}
   291  	updatedFileInfo := &MockFileInfo{ModTimeValue: originFileInfo.ModTimeValue.Add(updateInterval + time.Second)}
   292  
   293  	originFileData := []byte(`
   294  testGetFloat64PropertyKey:
   295  - value: 12
   296    constraints: {}
   297  
   298  testGetIntPropertyKey:
   299  - value: 1000
   300    constraints: {}
   301  
   302  testGetBoolPropertyKey:
   303  - value: false
   304    constraints: {}
   305  - value: true
   306    constraints:
   307      namespace: global-samples-namespace
   308  - value: true
   309    constraints:
   310      namespace: samples-namespace
   311  `)
   312  	updatedFileData := []byte(`
   313  testGetFloat64PropertyKey:
   314  - value: 13
   315    constraints: {}
   316  
   317  testGetIntPropertyKey:
   318  - value: 2000
   319    constraints: {}
   320  
   321  testGetBoolPropertyKey:
   322  - value: true
   323    constraints: {}
   324  - value: false
   325    constraints:
   326      namespace: global-samples-namespace
   327  - value: true
   328    constraints:
   329      namespace: samples-namespace
   330  `)
   331  
   332  	reader.EXPECT().Stat(gomock.Any()).Return(originFileInfo, nil).Times(2)
   333  	reader.EXPECT().ReadFile(gomock.Any()).Return(originFileData, nil)
   334  
   335  	mockLogger.EXPECT().Info(gomock.Any()).Times(6)
   336  	client, err := NewFileBasedClientWithReader(reader,
   337  		&FileBasedClientConfig{
   338  			Filepath:     "anyValue",
   339  			PollInterval: updateInterval,
   340  		}, mockLogger, s.doneCh)
   341  	s.NoError(err)
   342  
   343  	reader.EXPECT().Stat(gomock.Any()).Return(updatedFileInfo, nil)
   344  	reader.EXPECT().ReadFile(gomock.Any()).Return(updatedFileData, nil)
   345  
   346  	mockLogger.EXPECT().Info("dynamic config changed for the key: testgetfloat64propertykey oldValue: { constraints: {} value: 12 } newValue: { constraints: {} value: 13 }", gomock.Any())
   347  	mockLogger.EXPECT().Info("dynamic config changed for the key: testgetintpropertykey oldValue: { constraints: {} value: 1000 } newValue: { constraints: {} value: 2000 }", gomock.Any())
   348  	mockLogger.EXPECT().Info("dynamic config changed for the key: testgetboolpropertykey oldValue: { constraints: {} value: false } newValue: { constraints: {} value: true }", gomock.Any())
   349  	mockLogger.EXPECT().Info("dynamic config changed for the key: testgetboolpropertykey oldValue: { constraints: {{Namespace:global-samples-namespace}} value: true } newValue: { constraints: {{Namespace:global-samples-namespace}} value: false }", gomock.Any())
   350  	mockLogger.EXPECT().Info(gomock.Any())
   351  	s.NoError(client.update())
   352  	s.NoError(err)
   353  	close(doneCh)
   354  }
   355  
   356  func (s *fileBasedClientSuite) TestUpdate_ChangedMapValue() {
   357  	ctrl := gomock.NewController(s.T())
   358  	defer ctrl.Finish()
   359  
   360  	doneCh := make(chan interface{})
   361  	reader := NewMockfileReader(ctrl)
   362  	mockLogger := log.NewMockLogger(ctrl)
   363  
   364  	updateInterval := time.Minute * 5
   365  	originFileInfo := &MockFileInfo{ModTimeValue: time.Now()}
   366  	updatedFileInfo := &MockFileInfo{ModTimeValue: originFileInfo.ModTimeValue.Add(updateInterval + time.Second)}
   367  
   368  	originFileData := []byte(`
   369  history.defaultActivityRetryPolicy:
   370  - value:
   371      InitialIntervalInSeconds: 1
   372      MaximumIntervalCoefficient: 100.0
   373      BackoffCoefficient: 3.0
   374      MaximumAttempts: 0
   375  `)
   376  	updatedFileData := []byte(`
   377  history.defaultActivityRetryPolicy:
   378  - value:
   379      InitialIntervalInSeconds: 3
   380      MaximumIntervalCoefficient: 100.0
   381      BackoffCoefficient: 2.0
   382      MaximumAttempts: 0
   383  `)
   384  
   385  	reader.EXPECT().Stat(gomock.Any()).Return(originFileInfo, nil).Times(2)
   386  	reader.EXPECT().ReadFile(gomock.Any()).Return(originFileData, nil)
   387  
   388  	mockLogger.EXPECT().Info(gomock.Any()).Times(2)
   389  	client, err := NewFileBasedClientWithReader(reader,
   390  		&FileBasedClientConfig{
   391  			Filepath:     "anyValue",
   392  			PollInterval: updateInterval,
   393  		}, mockLogger, s.doneCh)
   394  	s.NoError(err)
   395  
   396  	reader.EXPECT().Stat(gomock.Any()).Return(updatedFileInfo, nil)
   397  	reader.EXPECT().ReadFile(gomock.Any()).Return(updatedFileData, nil)
   398  
   399  	mockLogger.EXPECT().Info("dynamic config changed for the key: history.defaultactivityretrypolicy oldValue: { constraints: {} value: map[BackoffCoefficient:3 InitialIntervalInSeconds:1 MaximumAttempts:0 MaximumIntervalCoefficient:100] } newValue: { constraints: {} value: map[BackoffCoefficient:2 InitialIntervalInSeconds:3 MaximumAttempts:0 MaximumIntervalCoefficient:100] }", gomock.Any())
   400  	mockLogger.EXPECT().Info(gomock.Any())
   401  	s.NoError(client.update())
   402  	s.NoError(err)
   403  	close(doneCh)
   404  }
   405  
   406  func (s *fileBasedClientSuite) TestUpdate_NewEntry() {
   407  	ctrl := gomock.NewController(s.T())
   408  	defer ctrl.Finish()
   409  
   410  	doneCh := make(chan interface{})
   411  	reader := NewMockfileReader(ctrl)
   412  	mockLogger := log.NewMockLogger(ctrl)
   413  
   414  	updateInterval := time.Minute * 5
   415  	originFileInfo := &MockFileInfo{ModTimeValue: time.Now()}
   416  	updatedFileInfo := &MockFileInfo{ModTimeValue: originFileInfo.ModTimeValue.Add(updateInterval + time.Second)}
   417  
   418  	originFileData := []byte(`
   419  testGetFloat64PropertyKey:
   420  - value: 12
   421    constraints: {}
   422  `)
   423  	updatedFileData := []byte(`
   424  testGetFloat64PropertyKey:
   425  - value: 12
   426    constraints: {}
   427  - value: 22
   428    constraints:
   429      namespace: samples-namespace
   430  
   431  testGetIntPropertyKey:
   432  - value: 2000
   433    constraints: {}
   434  `)
   435  
   436  	reader.EXPECT().Stat(gomock.Any()).Return(originFileInfo, nil).Times(2)
   437  	reader.EXPECT().ReadFile(gomock.Any()).Return(originFileData, nil)
   438  
   439  	mockLogger.EXPECT().Info("dynamic config changed for the key: testgetfloat64propertykey oldValue: nil newValue: { constraints: {} value: 12 }", gomock.Any())
   440  	mockLogger.EXPECT().Info(gomock.Any())
   441  	client, err := NewFileBasedClientWithReader(reader,
   442  		&FileBasedClientConfig{
   443  			Filepath:     "anyValue",
   444  			PollInterval: updateInterval,
   445  		}, mockLogger, s.doneCh)
   446  	s.NoError(err)
   447  
   448  	reader.EXPECT().Stat(gomock.Any()).Return(updatedFileInfo, nil)
   449  	reader.EXPECT().ReadFile(gomock.Any()).Return(updatedFileData, nil)
   450  
   451  	mockLogger.EXPECT().Info("dynamic config changed for the key: testgetfloat64propertykey oldValue: nil newValue: { constraints: {{Namespace:samples-namespace}} value: 22 }", gomock.Any())
   452  	mockLogger.EXPECT().Info("dynamic config changed for the key: testgetintpropertykey oldValue: nil newValue: { constraints: {} value: 2000 }", gomock.Any())
   453  	mockLogger.EXPECT().Info(gomock.Any())
   454  	s.NoError(client.update())
   455  	s.NoError(err)
   456  	close(doneCh)
   457  }
   458  
   459  func (s *fileBasedClientSuite) TestUpdate_ChangeOrder_ShouldNotWriteLog() {
   460  	ctrl := gomock.NewController(s.T())
   461  	defer ctrl.Finish()
   462  
   463  	doneCh := make(chan interface{})
   464  	reader := NewMockfileReader(ctrl)
   465  	mockLogger := log.NewMockLogger(ctrl)
   466  
   467  	updateInterval := time.Minute * 5
   468  	originFileInfo := &MockFileInfo{ModTimeValue: time.Now()}
   469  	updatedFileInfo := &MockFileInfo{ModTimeValue: originFileInfo.ModTimeValue.Add(updateInterval + time.Second)}
   470  
   471  	originFileData := []byte(`
   472  testGetFloat64PropertyKey:
   473  - value: 12
   474    constraints: {}
   475  - value: 22
   476    constraints:
   477      namespace: samples-namespace
   478  
   479  testGetIntPropertyKey:
   480  - value: 2000
   481    constraints: {}
   482  `)
   483  	updatedFileData := []byte(`
   484  testGetIntPropertyKey:
   485  - value: 2000
   486    constraints: {}
   487  
   488  testGetFloat64PropertyKey:
   489  - value: 22
   490    constraints:
   491      namespace: samples-namespace
   492  - value: 12
   493    constraints: {}
   494  `)
   495  
   496  	reader.EXPECT().Stat(gomock.Any()).Return(originFileInfo, nil).Times(2)
   497  	reader.EXPECT().ReadFile(gomock.Any()).Return(originFileData, nil)
   498  
   499  	mockLogger.EXPECT().Info(gomock.Any()).Times(4)
   500  	client, err := NewFileBasedClientWithReader(reader,
   501  		&FileBasedClientConfig{
   502  			Filepath:     "anyValue",
   503  			PollInterval: updateInterval,
   504  		}, mockLogger, s.doneCh)
   505  	s.NoError(err)
   506  
   507  	reader.EXPECT().Stat(gomock.Any()).Return(updatedFileInfo, nil)
   508  	reader.EXPECT().ReadFile(gomock.Any()).Return(updatedFileData, nil)
   509  
   510  	mockLogger.EXPECT().Info(gomock.Any()).Times(1)
   511  	s.NoError(client.update())
   512  	s.NoError(err)
   513  	close(doneCh)
   514  }