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 }