github.com/nilium/gitlab-runner@v12.5.0+incompatible/cache/cache_test.go (about)

     1  package cache
     2  
     3  import (
     4  	"errors"
     5  	"net/url"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/sirupsen/logrus/hooks/test"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/mock"
    12  	"github.com/stretchr/testify/require"
    13  
    14  	"gitlab.com/gitlab-org/gitlab-runner/common"
    15  )
    16  
    17  type cacheOperationTest struct {
    18  	key                    string
    19  	configExists           bool
    20  	adapterExists          bool
    21  	errorOnAdapterCreation bool
    22  	adapterURL             *url.URL
    23  	expectedURL            *url.URL
    24  	expectedOutput         []string
    25  }
    26  
    27  func prepareFakeCreateAdapter(t *testing.T, operationName string, tc cacheOperationTest) func() {
    28  	assertAdapterExpectations := func(t mock.TestingT) bool { return true }
    29  
    30  	var cacheAdapter Adapter
    31  	if tc.adapterExists {
    32  		a := new(MockAdapter)
    33  
    34  		if tc.adapterURL != nil {
    35  			a.On(operationName).Return(tc.adapterURL)
    36  		}
    37  
    38  		assertAdapterExpectations = a.AssertExpectations
    39  		cacheAdapter = a
    40  	}
    41  
    42  	var cacheAdapterCreationError error
    43  	if tc.errorOnAdapterCreation {
    44  		cacheAdapterCreationError = errors.New("test error")
    45  	}
    46  
    47  	oldCreateAdapter := createAdapter
    48  	createAdapter = func(cacheConfig *common.CacheConfig, timeout time.Duration, objectName string) (Adapter, error) {
    49  		return cacheAdapter, cacheAdapterCreationError
    50  	}
    51  
    52  	return func() {
    53  		createAdapter = oldCreateAdapter
    54  		assertAdapterExpectations(t)
    55  	}
    56  }
    57  
    58  func prepareFakeBuild(tc cacheOperationTest) *common.Build {
    59  	build := &common.Build{
    60  		Runner: &common.RunnerConfig{
    61  			RunnerSettings: common.RunnerSettings{},
    62  		},
    63  	}
    64  
    65  	if tc.configExists {
    66  		build.Runner.Cache = &common.CacheConfig{}
    67  	}
    68  
    69  	return build
    70  }
    71  
    72  func testCacheOperation(t *testing.T, operationName string, operation func(build *common.Build, key string) *url.URL, tc cacheOperationTest) {
    73  	t.Run(operationName, func(t *testing.T) {
    74  		hook := test.NewGlobal()
    75  
    76  		cleanupCreateAdapter := prepareFakeCreateAdapter(t, operationName, tc)
    77  		defer cleanupCreateAdapter()
    78  
    79  		build := prepareFakeBuild(tc)
    80  		generatedURL := operation(build, tc.key)
    81  		assert.Equal(t, tc.expectedURL, generatedURL)
    82  
    83  		if len(tc.expectedOutput) == 0 {
    84  			assert.Len(t, hook.AllEntries(), 0)
    85  		} else {
    86  			for _, expectedOutput := range tc.expectedOutput {
    87  				message, err := hook.LastEntry().String()
    88  				require.NoError(t, err)
    89  				assert.Contains(t, message, expectedOutput)
    90  			}
    91  		}
    92  	})
    93  }
    94  
    95  func TestCacheOperations(t *testing.T) {
    96  	exampleURL, err := url.Parse("example.com")
    97  	require.NoError(t, err)
    98  
    99  	tests := map[string]cacheOperationTest{
   100  		"no-config": {
   101  			key:            "key",
   102  			adapterExists:  true,
   103  			adapterURL:     nil,
   104  			expectedURL:    nil,
   105  			expectedOutput: []string{"Cache config not defined. Skipping cache operation."},
   106  		},
   107  		"key-not-specified": {
   108  			configExists:   true,
   109  			adapterExists:  true,
   110  			adapterURL:     nil,
   111  			expectedURL:    nil,
   112  			expectedOutput: []string{"Empty cache key. Skipping adapter selection."},
   113  		},
   114  		"adapter-doesnt-exists": {
   115  			key:           "key",
   116  			configExists:  true,
   117  			adapterExists: false,
   118  			adapterURL:    exampleURL,
   119  			expectedURL:   nil,
   120  		},
   121  		"adapter-error-on-factorization": {
   122  			key:                    "key",
   123  			configExists:           true,
   124  			errorOnAdapterCreation: true,
   125  			adapterURL:             exampleURL,
   126  			expectedURL:            nil,
   127  			expectedOutput: []string{
   128  				"Could not create cache adapter",
   129  				"test error",
   130  			},
   131  		},
   132  		"adapter-exists": {
   133  			key:           "key",
   134  			configExists:  true,
   135  			adapterExists: true,
   136  			adapterURL:    exampleURL,
   137  			expectedURL:   exampleURL,
   138  		},
   139  	}
   140  
   141  	for name, tc := range tests {
   142  		t.Run(name, func(t *testing.T) {
   143  			testCacheOperation(t, "GetDownloadURL", GetCacheDownloadURL, tc)
   144  			testCacheOperation(t, "GetUploadURL", GetCacheUploadURL, tc)
   145  		})
   146  	}
   147  }
   148  
   149  func defaultCacheConfig() *common.CacheConfig {
   150  	return &common.CacheConfig{
   151  		Type: "test",
   152  	}
   153  }
   154  
   155  func defaultBuild(cacheConfig *common.CacheConfig) *common.Build {
   156  	return &common.Build{
   157  		JobResponse: common.JobResponse{
   158  			JobInfo: common.JobInfo{
   159  				ProjectID: 10,
   160  			},
   161  			RunnerInfo: common.RunnerInfo{
   162  				Timeout: 3600,
   163  			},
   164  		},
   165  		Runner: &common.RunnerConfig{
   166  			RunnerCredentials: common.RunnerCredentials{
   167  				Token: "longtoken",
   168  			},
   169  			RunnerSettings: common.RunnerSettings{
   170  				Cache: cacheConfig,
   171  			},
   172  		},
   173  	}
   174  }
   175  
   176  type generateObjectNameTestCase struct {
   177  	cache *common.CacheConfig
   178  	build *common.Build
   179  
   180  	key    string
   181  	path   string
   182  	shared bool
   183  
   184  	expectedObjectName string
   185  	expectedError      string
   186  }
   187  
   188  func TestGenerateObjectName(t *testing.T) {
   189  	cache := defaultCacheConfig()
   190  	cacheBuild := defaultBuild(cache)
   191  
   192  	tests := map[string]generateObjectNameTestCase{
   193  		"default usage": {
   194  			cache:              cache,
   195  			build:              cacheBuild,
   196  			key:                "key",
   197  			expectedObjectName: "runner/longtoke/project/10/key",
   198  		},
   199  		"empty key": {
   200  			cache:              cache,
   201  			build:              cacheBuild,
   202  			key:                "",
   203  			expectedObjectName: "",
   204  		},
   205  		"short path is set": {
   206  			cache:              cache,
   207  			build:              cacheBuild,
   208  			key:                "key",
   209  			path:               "whatever",
   210  			expectedObjectName: "whatever/runner/longtoke/project/10/key",
   211  		},
   212  		"multiple segment path is set": {
   213  			cache:              cache,
   214  			build:              cacheBuild,
   215  			key:                "key",
   216  			path:               "some/other/path/goes/here",
   217  			expectedObjectName: "some/other/path/goes/here/runner/longtoke/project/10/key",
   218  		},
   219  		"path is empty": {
   220  			cache:              cache,
   221  			build:              cacheBuild,
   222  			key:                "key",
   223  			path:               "",
   224  			expectedObjectName: "runner/longtoke/project/10/key",
   225  		},
   226  		"shared flag is set to true": {
   227  			cache:              cache,
   228  			build:              cacheBuild,
   229  			key:                "key",
   230  			shared:             true,
   231  			expectedObjectName: "project/10/key",
   232  		},
   233  		"shared flag is set to false": {
   234  			cache:              cache,
   235  			build:              cacheBuild,
   236  			key:                "key",
   237  			shared:             false,
   238  			expectedObjectName: "runner/longtoke/project/10/key",
   239  		},
   240  		"key escapes project namespace": {
   241  			cache:              cache,
   242  			build:              cacheBuild,
   243  			key:                "../9/key",
   244  			expectedObjectName: "",
   245  			expectedError:      "computed cache path outside of project bucket. Please remove `../` from cache key",
   246  		},
   247  	}
   248  
   249  	for name, test := range tests {
   250  		t.Run(name, func(t *testing.T) {
   251  			cache.Path = test.path
   252  			cache.Shared = test.shared
   253  
   254  			objectName, err := generateObjectName(test.build, test.cache, test.key)
   255  
   256  			assert.Equal(t, test.expectedObjectName, objectName)
   257  			if len(test.expectedError) == 0 {
   258  				assert.NoError(t, err)
   259  			} else {
   260  				assert.EqualError(t, err, test.expectedError)
   261  			}
   262  		})
   263  	}
   264  }