package cache import ( "errors" "reflect" "strings" "testing" "time" kerrs "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/auth/authenticator" "k8s.io/kubernetes/pkg/auth/user" ) type testRequest struct { Token string Offset time.Duration ExpectedUserName string ExpectedOK bool ExpectedErr bool } func TestCache(t *testing.T) { tokenAuthInvocations := []string{} tokenAuth := authenticator.TokenFunc(func(token string) (user.Info, bool, error) { tokenAuthInvocations = append(tokenAuthInvocations, token) switch { case strings.HasPrefix(token, "user"): return &user.DefaultInfo{Name: token}, true, nil case strings.HasPrefix(token, "unauthorized"): return nil, false, kerrs.NewUnauthorized(token) case strings.HasPrefix(token, "error"): return nil, false, errors.New(token) default: return nil, false, nil } }) tests := map[string]struct { TTL time.Duration CacheSize int Requests []testRequest ExpectedInvocations []string ExpectedCacheSize int }{ "miss": { TTL: time.Minute, CacheSize: 1, Requests: []testRequest{ {Token: "user1", ExpectedUserName: "user1", ExpectedOK: true}, }, ExpectedInvocations: []string{"user1"}, ExpectedCacheSize: 1, }, "cache hit user": { TTL: time.Minute, CacheSize: 1, Requests: []testRequest{ {Token: "user1", ExpectedUserName: "user1", ExpectedOK: true}, {Token: "user1", ExpectedUserName: "user1", ExpectedOK: true}, }, ExpectedInvocations: []string{"user1"}, ExpectedCacheSize: 1, }, "cache hit invalid": { TTL: time.Minute, CacheSize: 1, Requests: []testRequest{ {Token: "invalid1", ExpectedOK: false}, {Token: "invalid1", ExpectedOK: false}, }, ExpectedInvocations: []string{"invalid1"}, ExpectedCacheSize: 1, }, "cache hit unauthorized error": { TTL: time.Minute, CacheSize: 1, Requests: []testRequest{ {Token: "unauthorized1", ExpectedErr: true}, {Token: "unauthorized1", ExpectedErr: true}, }, ExpectedInvocations: []string{"unauthorized1"}, ExpectedCacheSize: 1, }, "uncacheable error": { TTL: time.Minute, CacheSize: 1, Requests: []testRequest{ {Token: "error1", ExpectedErr: true}, {Token: "error1", ExpectedErr: true}, }, ExpectedInvocations: []string{"error1", "error1"}, ExpectedCacheSize: 0, }, "expire": { TTL: time.Minute, CacheSize: 1, Requests: []testRequest{ {Token: "user1", ExpectedUserName: "user1", ExpectedOK: true}, {Token: "user1", ExpectedUserName: "user1", ExpectedOK: true, Offset: 2 * time.Minute}, }, ExpectedInvocations: []string{"user1", "user1"}, ExpectedCacheSize: 1, }, "evacuation": { TTL: time.Minute, CacheSize: 2, Requests: []testRequest{ // Request user1 {Token: "user1", ExpectedUserName: "user1", ExpectedOK: true}, // Requests for user2 and user3 evacuate user1 {Token: "user2", ExpectedUserName: "user2", ExpectedOK: true, Offset: 10 * time.Second}, {Token: "user3", ExpectedUserName: "user3", ExpectedOK: true, Offset: 20 * time.Second}, {Token: "user2", ExpectedUserName: "user2", ExpectedOK: true, Offset: 30 * time.Second}, {Token: "user3", ExpectedUserName: "user3", ExpectedOK: true, Offset: 40 * time.Second}, {Token: "user2", ExpectedUserName: "user2", ExpectedOK: true, Offset: 50 * time.Second}, {Token: "user3", ExpectedUserName: "user3", ExpectedOK: true, Offset: 60 * time.Second}, // Request for user1 refetches {Token: "user1", ExpectedUserName: "user1", ExpectedOK: true}, }, ExpectedInvocations: []string{"user1", "user2", "user3", "user1"}, ExpectedCacheSize: 2, }, } for k, tc := range tests { tokenAuthInvocations = []string{} start := time.Now() auth, err := NewAuthenticator(tokenAuth, tc.TTL, tc.CacheSize) if err != nil { t.Errorf("%s: Unexpected error: %v", k, err) } cacheAuth := auth.(*CacheAuthenticator) for i, r := range tc.Requests { cacheAuth.now = func() time.Time { return start.Add(r.Offset) } u, ok, err := cacheAuth.AuthenticateToken(r.Token) if r.ExpectedErr != (err != nil) { t.Errorf("%s: %d: Expected err=%v, got %v", k, i, r.ExpectedErr, err) continue } if ok != r.ExpectedOK { t.Errorf("%s: %d: Expected ok=%v, got %v", k, i, r.ExpectedOK, ok) continue } if ok && u.GetName() != r.ExpectedUserName { t.Errorf("%s: %d: Expected username=%v, got %v", k, i, r.ExpectedUserName, u.GetName()) continue } } if !reflect.DeepEqual(tc.ExpectedInvocations, tokenAuthInvocations) { t.Errorf("%s: Expected invocations=%v, got %v", k, tc.ExpectedInvocations, tokenAuthInvocations) } if cacheAuth.cache.Len() != tc.ExpectedCacheSize { t.Errorf("%s: Expected cache size %d, got %d", k, tc.ExpectedCacheSize, cacheAuth.cache.Len()) } } }