github.com/argoproj/argo-cd@v1.8.7/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/pkg/apis/application/v1alpha1"
    14  	appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
    15  	"github.com/argoproj/argo-cd/reposerver/apiclient"
    16  	cacheutil "github.com/argoproj/argo-cd/util/cache"
    17  )
    18  
    19  type fixtures struct {
    20  	*Cache
    21  }
    22  
    23  func newFixtures() *fixtures {
    24  	return &fixtures{NewCache(
    25  		cacheutil.NewCache(cacheutil.NewInMemoryCache(1*time.Hour)),
    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  	value := &CachedManifestResponse{}
    74  	err := cache.GetManifests("my-revision", &ApplicationSource{}, "my-namespace", "my-app-label-key", "my-app-label-value", value)
    75  	assert.Equal(t, ErrCacheMiss, err)
    76  	// populate cache
    77  	res := &CachedManifestResponse{ManifestResponse: &apiclient.ManifestResponse{SourceType: "my-source-type"}}
    78  	err = cache.SetManifests("my-revision", &ApplicationSource{}, "my-namespace", "my-app-label-key", "my-app-label-value", res)
    79  	assert.NoError(t, err)
    80  	// cache miss
    81  	err = cache.GetManifests("other-revision", &ApplicationSource{}, "my-namespace", "my-app-label-key", "my-app-label-value", value)
    82  	assert.Equal(t, ErrCacheMiss, err)
    83  	// cache miss
    84  	err = cache.GetManifests("my-revision", &ApplicationSource{Path: "other-path"}, "my-namespace", "my-app-label-key", "my-app-label-value", value)
    85  	assert.Equal(t, ErrCacheMiss, err)
    86  	// cache miss
    87  	err = cache.GetManifests("my-revision", &ApplicationSource{}, "other-namespace", "my-app-label-key", "my-app-label-value", value)
    88  	assert.Equal(t, ErrCacheMiss, err)
    89  	// cache miss
    90  	err = cache.GetManifests("my-revision", &ApplicationSource{}, "my-namespace", "other-app-label-key", "my-app-label-value", value)
    91  	assert.Equal(t, ErrCacheMiss, err)
    92  	// cache miss
    93  	err = cache.GetManifests("my-revision", &ApplicationSource{}, "my-namespace", "my-app-label-key", "other-app-label-value", value)
    94  	assert.Equal(t, ErrCacheMiss, err)
    95  	// cache hit
    96  	err = cache.GetManifests("my-revision", &ApplicationSource{}, "my-namespace", "my-app-label-key", "my-app-label-value", value)
    97  	assert.NoError(t, err)
    98  	assert.Equal(t, &CachedManifestResponse{ManifestResponse: &apiclient.ManifestResponse{SourceType: "my-source-type"}}, value)
    99  }
   100  
   101  func TestCache_GetAppDetails(t *testing.T) {
   102  	cache := newFixtures().Cache
   103  	// cache miss
   104  	value := &apiclient.RepoAppDetailsResponse{}
   105  	err := cache.GetAppDetails("my-revision", &ApplicationSource{}, value)
   106  	assert.Equal(t, ErrCacheMiss, err)
   107  	res := &apiclient.RepoAppDetailsResponse{Type: "my-type"}
   108  	err = cache.SetAppDetails("my-revision", &ApplicationSource{}, res)
   109  	assert.NoError(t, err)
   110  	//cache miss
   111  	err = cache.GetAppDetails("other-revision", &ApplicationSource{}, value)
   112  	assert.Equal(t, ErrCacheMiss, err)
   113  	//cache miss
   114  	err = cache.GetAppDetails("my-revision", &ApplicationSource{Path: "other-path"}, value)
   115  	assert.Equal(t, ErrCacheMiss, err)
   116  	// cache hit
   117  	err = cache.GetAppDetails("my-revision", &ApplicationSource{}, value)
   118  	assert.NoError(t, err)
   119  	assert.Equal(t, &apiclient.RepoAppDetailsResponse{Type: "my-type"}, value)
   120  }
   121  
   122  func TestAddCacheFlagsToCmd(t *testing.T) {
   123  	cache, err := AddCacheFlagsToCmd(&cobra.Command{})()
   124  	assert.NoError(t, err)
   125  	assert.Equal(t, 24*time.Hour, cache.repoCacheExpiration)
   126  }
   127  
   128  func TestCachedManifestResponse_HashBehavior(t *testing.T) {
   129  
   130  	inMemCache := cacheutil.NewInMemoryCache(1 * time.Hour)
   131  
   132  	repoCache := NewCache(
   133  		cacheutil.NewCache(inMemCache),
   134  		1*time.Minute,
   135  	)
   136  
   137  	response := apiclient.ManifestResponse{
   138  		Namespace: "default",
   139  		Revision:  "revision",
   140  		Manifests: []string{"sample-text"},
   141  	}
   142  	appSrc := &appv1.ApplicationSource{}
   143  	appKey := "key"
   144  	appValue := "value"
   145  
   146  	// Set the value in the cache
   147  	store := &CachedManifestResponse{
   148  		FirstFailureTimestamp:           0,
   149  		ManifestResponse:                &response,
   150  		MostRecentError:                 "",
   151  		NumberOfCachedResponsesReturned: 0,
   152  		NumberOfConsecutiveFailures:     0,
   153  	}
   154  	err := repoCache.SetManifests(response.Revision, appSrc, response.Namespace, appKey, appValue, store)
   155  	if err != nil {
   156  		t.Fatal(err)
   157  	}
   158  
   159  	// Get the cache entry of the set value directly from the in memory cache, and check the values
   160  	var cacheKey string
   161  	var cmr *CachedManifestResponse
   162  	{
   163  
   164  		items := getInMemoryCacheContents(t, inMemCache)
   165  
   166  		assert.Equal(t, len(items), 1)
   167  
   168  		for key, val := range items {
   169  			cmr = val
   170  			cacheKey = key
   171  		}
   172  		assert.NotEmpty(t, cmr.CacheEntryHash)
   173  		assert.NotNil(t, cmr.ManifestResponse)
   174  		assert.Equal(t, cmr.ManifestResponse, store.ManifestResponse)
   175  
   176  		regeneratedHash, err := cmr.generateCacheEntryHash()
   177  		if err != nil {
   178  			t.Fatal(err)
   179  		}
   180  		assert.Equal(t, cmr.CacheEntryHash, regeneratedHash)
   181  	}
   182  
   183  	// Retrieve the value using 'GetManifests' and confirm it works
   184  	retrievedVal := &CachedManifestResponse{}
   185  	err = repoCache.GetManifests(response.Revision, appSrc, response.Namespace, appKey, appValue, retrievedVal)
   186  	if err != nil {
   187  		t.Fatal(err)
   188  	}
   189  	assert.Equal(t, retrievedVal, store)
   190  
   191  	// Corrupt the hash so that it doesn't match
   192  	{
   193  		newCmr := cmr.shallowCopy()
   194  		newCmr.CacheEntryHash = "!bad-hash!"
   195  
   196  		err := inMemCache.Set(&cacheutil.Item{
   197  			Key:    cacheKey,
   198  			Object: &newCmr,
   199  		})
   200  		if err != nil {
   201  			t.Fatal(err)
   202  		}
   203  
   204  	}
   205  
   206  	// Retrieve the value using GetManifests and confirm it returns a cache miss
   207  	retrievedVal = &CachedManifestResponse{}
   208  	err = repoCache.GetManifests(response.Revision, appSrc, response.Namespace, appKey, appValue, retrievedVal)
   209  
   210  	assert.True(t, err == cacheutil.ErrCacheMiss)
   211  
   212  	// Verify that the hash mismatch item has been deleted
   213  	items := getInMemoryCacheContents(t, inMemCache)
   214  	assert.Equal(t, len(items), 0)
   215  
   216  }
   217  
   218  func getInMemoryCacheContents(t *testing.T, inMemCache *cacheutil.InMemoryCache) map[string]*CachedManifestResponse {
   219  	items, err := inMemCache.Items(func() interface{} { return &CachedManifestResponse{} })
   220  	if err != nil {
   221  		t.Fatal(err)
   222  	}
   223  
   224  	result := map[string]*CachedManifestResponse{}
   225  	for key, val := range items {
   226  		obj, ok := val.(*CachedManifestResponse)
   227  		if !ok {
   228  			t.Fatal(errors.New("Unexpected type in cache"))
   229  		}
   230  
   231  		result[key] = obj
   232  	}
   233  
   234  	return result
   235  }
   236  
   237  func TestCachedManifestResponse_ShallowCopy(t *testing.T) {
   238  
   239  	pre := &CachedManifestResponse{
   240  		CacheEntryHash:        "value",
   241  		FirstFailureTimestamp: 1,
   242  		ManifestResponse: &apiclient.ManifestResponse{
   243  			Manifests: []string{"one", "two"},
   244  		},
   245  		MostRecentError:                 "error",
   246  		NumberOfCachedResponsesReturned: 2,
   247  		NumberOfConsecutiveFailures:     3,
   248  	}
   249  
   250  	post := pre.shallowCopy()
   251  	assert.Equal(t, pre, post)
   252  
   253  	unequal := &CachedManifestResponse{
   254  		CacheEntryHash:        "diff-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  	assert.NotEqual(t, pre, unequal)
   264  }
   265  
   266  func TestCachedManifestResponse_ShallowCopyExpectedFields(t *testing.T) {
   267  
   268  	// Attempt to ensure that the developer updated CachedManifestResponse.shallowCopy(), by doing a sanity test of the structure here
   269  
   270  	val := &CachedManifestResponse{}
   271  
   272  	str, err := json.Marshal(val)
   273  	if err != nil {
   274  		assert.FailNow(t, "Unable to marshal", err)
   275  		return
   276  	}
   277  
   278  	jsonMap := map[string]interface{}{}
   279  	err = json.Unmarshal(str, &jsonMap)
   280  	if err != nil {
   281  		assert.FailNow(t, "Unable to unmarshal", err)
   282  		return
   283  	}
   284  
   285  	expectedFields := []string{"cacheEntryHash", "manifestResponse", "mostRecentError", "firstFailureTimestamp",
   286  		"numberOfConsecutiveFailures", "numberOfCachedResponsesReturned"}
   287  
   288  	assert.Equal(t, len(jsonMap), len(expectedFields))
   289  
   290  	// If this test failed, you probably also forgot to update CachedManifestResponse.shallowCopy(), so
   291  	// go do that first :)
   292  
   293  	for _, expectedField := range expectedFields {
   294  		assert.Truef(t, strings.Contains(string(str), "\""+expectedField+"\""), "Missing field: %s", expectedField)
   295  	}
   296  
   297  }