// Copyright 2021 the Pinniped contributors. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package execcredcache import ( "os" "testing" "time" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1" "go.pinniped.dev/internal/testutil" ) var ( // validCache should be the same data as `testdata/valid.yaml`. validCache = credCache{ TypeMeta: metav1.TypeMeta{APIVersion: "config.supervisor.pinniped.dev/v1alpha1", Kind: "CredentialCache"}, Entries: []entry{ { Key: "test-key", CreationTimestamp: metav1.NewTime(time.Date(2020, 10, 20, 18, 42, 7, 0, time.UTC).Local()), LastUsedTimestamp: metav1.NewTime(time.Date(2020, 10, 20, 18, 45, 31, 0, time.UTC).Local()), Credential: &clientauthenticationv1beta1.ExecCredentialStatus{ Token: "test-token", ExpirationTimestamp: &expTime, }, }, }, } expTime = metav1.NewTime(time.Date(2020, 10, 20, 19, 46, 30, 0, time.UTC).Local()) ) func TestReadCache(t *testing.T) { t.Parallel() tests := []struct { name string path string want *credCache wantErr string }{ { name: "does not exist", path: "./testdata/does-not-exist.yaml", want: &credCache{ TypeMeta: metav1.TypeMeta{APIVersion: "config.supervisor.pinniped.dev/v1alpha1", Kind: "CredentialCache"}, Entries: []entry{}, }, }, { name: "other file error", path: "./testdata/", wantErr: "could not read cache file: read ./testdata/: is a directory", }, { name: "invalid YAML", path: "./testdata/invalid.yaml", wantErr: "invalid cache file: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type execcredcache.credCache", }, { name: "wrong version", path: "./testdata/wrong-version.yaml", wantErr: `unsupported credential cache version: v1.TypeMeta{Kind:"NotACredentialCache", APIVersion:"config.supervisor.pinniped.dev/v2alpha6"}`, }, { name: "valid", path: "./testdata/valid.yaml", want: &validCache, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got, err := readCache(tt.path) if tt.wantErr != "" { require.EqualError(t, err, tt.wantErr) require.Nil(t, got) return } require.NoError(t, err) require.NotNil(t, got) require.Equal(t, tt.want, got) }) } } func TestEmptyCache(t *testing.T) { t.Parallel() got := emptyCache() require.Equal(t, metav1.TypeMeta{APIVersion: "config.supervisor.pinniped.dev/v1alpha1", Kind: "CredentialCache"}, got.TypeMeta) require.Equal(t, 0, len(got.Entries)) require.Equal(t, 1, cap(got.Entries)) } func TestWriteTo(t *testing.T) { t.Parallel() t.Run("io error", func(t *testing.T) { t.Parallel() tmp := testutil.TempDir(t) + "/credentials.yaml" require.NoError(t, os.Mkdir(tmp, 0700)) err := validCache.writeTo(tmp) require.EqualError(t, err, "open "+tmp+": is a directory") }) t.Run("success", func(t *testing.T) { t.Parallel() require.NoError(t, validCache.writeTo(testutil.TempDir(t)+"/credentials.yaml")) }) } func TestNormalized(t *testing.T) { t.Parallel() t.Run("empty", func(t *testing.T) { t.Parallel() require.Equal(t, emptyCache(), emptyCache().normalized()) }) t.Run("nonempty", func(t *testing.T) { t.Parallel() input := emptyCache() now := time.Now() oneMinuteAgo := metav1.NewTime(now.Add(-1 * time.Minute)) oneHourFromNow := metav1.NewTime(now.Add(1 * time.Hour)) input.Entries = []entry{ // Credential is nil. { Key: "nil-credential-key", LastUsedTimestamp: metav1.NewTime(now), Credential: nil, }, // Credential's expiration is nil. { Key: "nil-expiration-key", LastUsedTimestamp: metav1.NewTime(now), Credential: &clientauthenticationv1beta1.ExecCredentialStatus{}, }, // Credential is expired. { Key: "expired-key", LastUsedTimestamp: metav1.NewTime(now), Credential: &clientauthenticationv1beta1.ExecCredentialStatus{ ExpirationTimestamp: &oneMinuteAgo, Token: "expired-token", }, }, // Credential is still valid but is older than maxCacheDuration. { Key: "too-old-key", LastUsedTimestamp: metav1.NewTime(now), CreationTimestamp: metav1.NewTime(now.Add(-3 * time.Hour)), Credential: &clientauthenticationv1beta1.ExecCredentialStatus{ ExpirationTimestamp: &oneHourFromNow, Token: "too-old-token", }, }, // Two entries that are still valid but are out of order. { Key: "key-two", CreationTimestamp: metav1.NewTime(now.Add(-1 * time.Minute)), LastUsedTimestamp: metav1.NewTime(now), Credential: &clientauthenticationv1beta1.ExecCredentialStatus{ ExpirationTimestamp: &oneHourFromNow, Token: "token-two", }, }, { Key: "key-one", CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Minute)), LastUsedTimestamp: metav1.NewTime(now), Credential: &clientauthenticationv1beta1.ExecCredentialStatus{ ExpirationTimestamp: &oneHourFromNow, Token: "token-one", }, }, } // Expect that all but the last two valid entries are pruned, and that they're sorted. require.Equal(t, &credCache{ TypeMeta: metav1.TypeMeta{APIVersion: "config.supervisor.pinniped.dev/v1alpha1", Kind: "CredentialCache"}, Entries: []entry{ { Key: "key-one", CreationTimestamp: metav1.NewTime(now.Add(-2 * time.Minute)), LastUsedTimestamp: metav1.NewTime(now), Credential: &clientauthenticationv1beta1.ExecCredentialStatus{ ExpirationTimestamp: &oneHourFromNow, Token: "token-one", }, }, { Key: "key-two", CreationTimestamp: metav1.NewTime(now.Add(-1 * time.Minute)), LastUsedTimestamp: metav1.NewTime(now), Credential: &clientauthenticationv1beta1.ExecCredentialStatus{ ExpirationTimestamp: &oneHourFromNow, Token: "token-two", }, }, }, }, input.normalized()) }) }