github.com/argoproj/argo-cd/v2@v2.10.9/reposerver/cache/cache_test.go (about) 1 package cache 2 3 import ( 4 "encoding/json" 5 "errors" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/spf13/cobra" 11 "github.com/stretchr/testify/assert" 12 13 . "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" 14 "github.com/argoproj/argo-cd/v2/reposerver/apiclient" 15 cacheutil "github.com/argoproj/argo-cd/v2/util/cache" 16 ) 17 18 type fixtures struct { 19 *Cache 20 } 21 22 func newFixtures() *fixtures { 23 return &fixtures{NewCache( 24 cacheutil.NewCache(cacheutil.NewInMemoryCache(1*time.Hour)), 25 1*time.Minute, 26 1*time.Minute, 27 )} 28 } 29 30 func TestCache_GetRevisionMetadata(t *testing.T) { 31 cache := newFixtures().Cache 32 // cache miss 33 _, err := cache.GetRevisionMetadata("my-repo-url", "my-revision") 34 assert.Equal(t, ErrCacheMiss, err) 35 // populate cache 36 err = cache.SetRevisionMetadata("my-repo-url", "my-revision", &RevisionMetadata{Message: "my-message"}) 37 assert.NoError(t, err) 38 // cache miss 39 _, err = cache.GetRevisionMetadata("other-repo-url", "my-revision") 40 assert.Equal(t, ErrCacheMiss, err) 41 // cache miss 42 _, err = cache.GetRevisionMetadata("my-repo-url", "other-revision") 43 assert.Equal(t, ErrCacheMiss, err) 44 // cache hit 45 value, err := cache.GetRevisionMetadata("my-repo-url", "my-revision") 46 assert.NoError(t, err) 47 assert.Equal(t, &RevisionMetadata{Message: "my-message"}, value) 48 } 49 50 func TestCache_ListApps(t *testing.T) { 51 cache := newFixtures().Cache 52 // cache miss 53 _, err := cache.ListApps("my-repo-url", "my-revision") 54 assert.Equal(t, ErrCacheMiss, err) 55 // populate cache 56 err = cache.SetApps("my-repo-url", "my-revision", map[string]string{"foo": "bar"}) 57 assert.NoError(t, err) 58 // cache miss 59 _, err = cache.ListApps("other-repo-url", "my-revision") 60 assert.Equal(t, ErrCacheMiss, err) 61 // cache miss 62 _, err = cache.ListApps("my-repo-url", "other-revision") 63 assert.Equal(t, ErrCacheMiss, err) 64 // cache hit 65 value, err := cache.ListApps("my-repo-url", "my-revision") 66 assert.NoError(t, err) 67 assert.Equal(t, map[string]string{"foo": "bar"}, value) 68 } 69 70 func TestCache_GetManifests(t *testing.T) { 71 cache := newFixtures().Cache 72 // cache miss 73 q := &apiclient.ManifestRequest{} 74 value := &CachedManifestResponse{} 75 err := cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil) 76 assert.Equal(t, ErrCacheMiss, err) 77 // populate cache 78 res := &CachedManifestResponse{ManifestResponse: &apiclient.ManifestResponse{SourceType: "my-source-type"}} 79 err = cache.SetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", res, nil) 80 assert.NoError(t, err) 81 t.Run("expect cache miss because of changed revision", func(t *testing.T) { 82 err = cache.GetManifests("other-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil) 83 assert.Equal(t, ErrCacheMiss, err) 84 }) 85 t.Run("expect cache miss because of changed path", func(t *testing.T) { 86 err = cache.GetManifests("my-revision", &ApplicationSource{Path: "other-path"}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil) 87 assert.Equal(t, ErrCacheMiss, err) 88 }) 89 t.Run("expect cache miss because of changed namespace", func(t *testing.T) { 90 err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "other-namespace", "", "my-app-label-key", "my-app-label-value", value, nil) 91 assert.Equal(t, ErrCacheMiss, err) 92 }) 93 t.Run("expect cache miss because of changed app label key", func(t *testing.T) { 94 err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "other-app-label-key", "my-app-label-value", value, nil) 95 assert.Equal(t, ErrCacheMiss, err) 96 }) 97 t.Run("expect cache miss because of changed app label value", func(t *testing.T) { 98 err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "other-app-label-value", value, nil) 99 assert.Equal(t, ErrCacheMiss, err) 100 }) 101 t.Run("expect cache miss because of changed referenced source", func(t *testing.T) { 102 err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "other-app-label-value", value, map[string]string{"my-referenced-source": "my-referenced-revision"}) 103 assert.Equal(t, ErrCacheMiss, err) 104 }) 105 t.Run("expect cache hit", func(t *testing.T) { 106 err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil) 107 assert.NoError(t, err) 108 assert.Equal(t, &CachedManifestResponse{ManifestResponse: &apiclient.ManifestResponse{SourceType: "my-source-type"}}, value) 109 }) 110 } 111 112 func TestCache_GetAppDetails(t *testing.T) { 113 cache := newFixtures().Cache 114 // cache miss 115 value := &apiclient.RepoAppDetailsResponse{} 116 emptyRefSources := map[string]*RefTarget{} 117 err := cache.GetAppDetails("my-revision", &ApplicationSource{}, emptyRefSources, value, "", nil) 118 assert.Equal(t, ErrCacheMiss, err) 119 res := &apiclient.RepoAppDetailsResponse{Type: "my-type"} 120 err = cache.SetAppDetails("my-revision", &ApplicationSource{}, emptyRefSources, res, "", nil) 121 assert.NoError(t, err) 122 //cache miss 123 err = cache.GetAppDetails("other-revision", &ApplicationSource{}, emptyRefSources, value, "", nil) 124 assert.Equal(t, ErrCacheMiss, err) 125 //cache miss 126 err = cache.GetAppDetails("my-revision", &ApplicationSource{Path: "other-path"}, emptyRefSources, value, "", nil) 127 assert.Equal(t, ErrCacheMiss, err) 128 // cache hit 129 err = cache.GetAppDetails("my-revision", &ApplicationSource{}, emptyRefSources, value, "", nil) 130 assert.NoError(t, err) 131 assert.Equal(t, &apiclient.RepoAppDetailsResponse{Type: "my-type"}, value) 132 } 133 134 func TestAddCacheFlagsToCmd(t *testing.T) { 135 cache, err := AddCacheFlagsToCmd(&cobra.Command{})() 136 assert.NoError(t, err) 137 assert.Equal(t, 24*time.Hour, cache.repoCacheExpiration) 138 } 139 140 func TestCachedManifestResponse_HashBehavior(t *testing.T) { 141 142 inMemCache := cacheutil.NewInMemoryCache(1 * time.Hour) 143 144 repoCache := NewCache( 145 cacheutil.NewCache(inMemCache), 146 1*time.Minute, 147 1*time.Minute, 148 ) 149 150 response := apiclient.ManifestResponse{ 151 Namespace: "default", 152 Revision: "revision", 153 Manifests: []string{"sample-text"}, 154 } 155 appSrc := &ApplicationSource{} 156 appKey := "key" 157 appValue := "value" 158 159 // Set the value in the cache 160 store := &CachedManifestResponse{ 161 FirstFailureTimestamp: 0, 162 ManifestResponse: &response, 163 MostRecentError: "", 164 NumberOfCachedResponsesReturned: 0, 165 NumberOfConsecutiveFailures: 0, 166 } 167 q := &apiclient.ManifestRequest{} 168 err := repoCache.SetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, store, nil) 169 if err != nil { 170 t.Fatal(err) 171 } 172 173 // Get the cache entry of the set value directly from the in memory cache, and check the values 174 var cacheKey string 175 var cmr *CachedManifestResponse 176 { 177 178 items := getInMemoryCacheContents(t, inMemCache) 179 180 assert.Equal(t, len(items), 1) 181 182 for key, val := range items { 183 cmr = val 184 cacheKey = key 185 } 186 assert.NotEmpty(t, cmr.CacheEntryHash) 187 assert.NotNil(t, cmr.ManifestResponse) 188 assert.Equal(t, cmr.ManifestResponse, store.ManifestResponse) 189 190 regeneratedHash, err := cmr.generateCacheEntryHash() 191 if err != nil { 192 t.Fatal(err) 193 } 194 assert.Equal(t, cmr.CacheEntryHash, regeneratedHash) 195 } 196 197 // Retrieve the value using 'GetManifests' and confirm it works 198 retrievedVal := &CachedManifestResponse{} 199 err = repoCache.GetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, retrievedVal, nil) 200 if err != nil { 201 t.Fatal(err) 202 } 203 assert.Equal(t, retrievedVal, store) 204 205 // Corrupt the hash so that it doesn't match 206 { 207 newCmr := cmr.shallowCopy() 208 newCmr.CacheEntryHash = "!bad-hash!" 209 210 err := inMemCache.Set(&cacheutil.Item{ 211 Key: cacheKey, 212 Object: &newCmr, 213 }) 214 if err != nil { 215 t.Fatal(err) 216 } 217 218 } 219 220 // Retrieve the value using GetManifests and confirm it returns a cache miss 221 retrievedVal = &CachedManifestResponse{} 222 err = repoCache.GetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, retrievedVal, nil) 223 224 assert.True(t, err == cacheutil.ErrCacheMiss) 225 226 // Verify that the hash mismatch item has been deleted 227 items := getInMemoryCacheContents(t, inMemCache) 228 assert.Equal(t, len(items), 0) 229 230 } 231 232 func getInMemoryCacheContents(t *testing.T, inMemCache *cacheutil.InMemoryCache) map[string]*CachedManifestResponse { 233 items, err := inMemCache.Items(func() interface{} { return &CachedManifestResponse{} }) 234 if err != nil { 235 t.Fatal(err) 236 } 237 238 result := map[string]*CachedManifestResponse{} 239 for key, val := range items { 240 obj, ok := val.(*CachedManifestResponse) 241 if !ok { 242 t.Fatal(errors.New("Unexpected type in cache")) 243 } 244 245 result[key] = obj 246 } 247 248 return result 249 } 250 251 func TestCachedManifestResponse_ShallowCopy(t *testing.T) { 252 253 pre := &CachedManifestResponse{ 254 CacheEntryHash: "value", 255 FirstFailureTimestamp: 1, 256 ManifestResponse: &apiclient.ManifestResponse{ 257 Manifests: []string{"one", "two"}, 258 }, 259 MostRecentError: "error", 260 NumberOfCachedResponsesReturned: 2, 261 NumberOfConsecutiveFailures: 3, 262 } 263 264 post := pre.shallowCopy() 265 assert.Equal(t, pre, post) 266 267 unequal := &CachedManifestResponse{ 268 CacheEntryHash: "diff-value", 269 FirstFailureTimestamp: 1, 270 ManifestResponse: &apiclient.ManifestResponse{ 271 Manifests: []string{"one", "two"}, 272 }, 273 MostRecentError: "error", 274 NumberOfCachedResponsesReturned: 2, 275 NumberOfConsecutiveFailures: 3, 276 } 277 assert.NotEqual(t, pre, unequal) 278 } 279 280 func TestCachedManifestResponse_ShallowCopyExpectedFields(t *testing.T) { 281 282 // Attempt to ensure that the developer updated CachedManifestResponse.shallowCopy(), by doing a sanity test of the structure here 283 284 val := &CachedManifestResponse{} 285 286 str, err := json.Marshal(val) 287 if err != nil { 288 assert.FailNow(t, "Unable to marshal", err) 289 return 290 } 291 292 jsonMap := map[string]interface{}{} 293 err = json.Unmarshal(str, &jsonMap) 294 if err != nil { 295 assert.FailNow(t, "Unable to unmarshal", err) 296 return 297 } 298 299 expectedFields := []string{"cacheEntryHash", "manifestResponse", "mostRecentError", "firstFailureTimestamp", 300 "numberOfConsecutiveFailures", "numberOfCachedResponsesReturned"} 301 302 assert.Equal(t, len(jsonMap), len(expectedFields)) 303 304 // If this test failed, you probably also forgot to update CachedManifestResponse.shallowCopy(), so 305 // go do that first :) 306 307 for _, expectedField := range expectedFields { 308 assert.Truef(t, strings.Contains(string(str), "\""+expectedField+"\""), "Missing field: %s", expectedField) 309 } 310 311 }