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  }