203 lines
5.5 KiB
Go
203 lines
5.5 KiB
Go
|
// Copyright 2023 the Pinniped contributors. All Rights Reserved.
|
||
|
// SPDX-License-Identifier: Apache-2.0
|
||
|
|
||
|
package tokenclient
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
"github.com/stretchr/testify/require"
|
||
|
authenticationv1 "k8s.io/api/authentication/v1"
|
||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||
|
"k8s.io/client-go/kubernetes"
|
||
|
"k8s.io/utils/clock"
|
||
|
clocktesting "k8s.io/utils/clock/testing"
|
||
|
|
||
|
"go.pinniped.dev/internal/plog"
|
||
|
)
|
||
|
|
||
|
func TestNew(t *testing.T) {
|
||
|
mockWhatToDoWithTokenFunc := *new(WhatToDoWithTokenFunc)
|
||
|
mockClient := new(kubernetes.Clientset)
|
||
|
mockTime := time.Now()
|
||
|
mockClock := clocktesting.NewFakeClock(mockTime)
|
||
|
var log bytes.Buffer
|
||
|
testLogger := plog.TestLogger(t, &log)
|
||
|
|
||
|
type args struct {
|
||
|
namespace string
|
||
|
serviceAccountName string
|
||
|
k8sClient *kubernetes.Clientset
|
||
|
whatToDoWithToken WhatToDoWithTokenFunc
|
||
|
logger plog.Logger
|
||
|
opts []Opt
|
||
|
}
|
||
|
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
args args
|
||
|
expected *TokenClient
|
||
|
}{
|
||
|
{
|
||
|
name: "defaults",
|
||
|
args: args{
|
||
|
namespace: "namespace",
|
||
|
serviceAccountName: "serviceAccountName",
|
||
|
k8sClient: mockClient,
|
||
|
whatToDoWithToken: mockWhatToDoWithTokenFunc,
|
||
|
logger: testLogger,
|
||
|
},
|
||
|
expected: &TokenClient{
|
||
|
namespace: "namespace",
|
||
|
serviceAccountName: "serviceAccountName",
|
||
|
k8sClient: mockClient,
|
||
|
whatToDoWithToken: mockWhatToDoWithTokenFunc,
|
||
|
expirationSeconds: 600,
|
||
|
clock: clock.RealClock{},
|
||
|
logger: testLogger,
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "with all opts",
|
||
|
args: args{
|
||
|
namespace: "custom-namespace",
|
||
|
serviceAccountName: "custom-serviceAccountName",
|
||
|
k8sClient: mockClient,
|
||
|
whatToDoWithToken: mockWhatToDoWithTokenFunc,
|
||
|
logger: testLogger,
|
||
|
opts: []Opt{
|
||
|
WithExpirationSeconds(777),
|
||
|
withClock(mockClock),
|
||
|
},
|
||
|
},
|
||
|
expected: &TokenClient{
|
||
|
namespace: "custom-namespace",
|
||
|
serviceAccountName: "custom-serviceAccountName",
|
||
|
k8sClient: mockClient,
|
||
|
whatToDoWithToken: mockWhatToDoWithTokenFunc,
|
||
|
expirationSeconds: 777,
|
||
|
clock: mockClock,
|
||
|
logger: testLogger,
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
for _, tt := range tests {
|
||
|
tt := tt
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
actual := New(
|
||
|
tt.args.namespace,
|
||
|
tt.args.serviceAccountName,
|
||
|
tt.args.k8sClient,
|
||
|
tt.args.whatToDoWithToken,
|
||
|
tt.args.logger,
|
||
|
tt.args.opts...,
|
||
|
)
|
||
|
|
||
|
require.Equal(t, tt.expected, actual)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// withClock should only be used for testing.
|
||
|
func withClock(clock clock.Clock) Opt {
|
||
|
return func(client *TokenClient) {
|
||
|
client.clock = clock
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestFetchToken(t *testing.T) {
|
||
|
mockTime := metav1.Now()
|
||
|
|
||
|
type expected struct {
|
||
|
tokenRequestStatus authenticationv1.TokenRequestStatus
|
||
|
ttl metav1.Duration
|
||
|
errMessage string
|
||
|
}
|
||
|
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
expirationSeconds int64
|
||
|
howToFetchTokenFromAPIServer howToFetchTokenFromAPIServer
|
||
|
expected expected
|
||
|
}{
|
||
|
{
|
||
|
name: "happy path",
|
||
|
expirationSeconds: 555,
|
||
|
howToFetchTokenFromAPIServer: func(_ *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
|
||
|
tokenRequest := authenticationv1.TokenRequest{
|
||
|
Status: authenticationv1.TokenRequestStatus{
|
||
|
Token: "token value",
|
||
|
ExpirationTimestamp: metav1.NewTime(mockTime.Add(25 * time.Minute)),
|
||
|
},
|
||
|
}
|
||
|
return &tokenRequest, nil
|
||
|
},
|
||
|
expected: expected{
|
||
|
tokenRequestStatus: authenticationv1.TokenRequestStatus{
|
||
|
Token: "token value",
|
||
|
ExpirationTimestamp: metav1.NewTime(mockTime.Add(25 * time.Minute)),
|
||
|
},
|
||
|
ttl: metav1.Duration{
|
||
|
Duration: 25 * time.Minute,
|
||
|
},
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "returns errors from howToFetchTokenFromAPIServer",
|
||
|
expirationSeconds: 444,
|
||
|
howToFetchTokenFromAPIServer: func(_ *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
|
||
|
return nil, errors.New("has an error")
|
||
|
},
|
||
|
expected: expected{
|
||
|
errMessage: "error creating token: has an error",
|
||
|
},
|
||
|
},
|
||
|
{
|
||
|
name: "errors when howToFetchTokenFromAPIServer returns nil",
|
||
|
expirationSeconds: 333,
|
||
|
howToFetchTokenFromAPIServer: func(_ *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
|
||
|
return nil, nil
|
||
|
},
|
||
|
expected: expected{
|
||
|
errMessage: "tokenRequest is nil after request",
|
||
|
},
|
||
|
},
|
||
|
}
|
||
|
|
||
|
for _, tt := range tests {
|
||
|
tt := tt
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
wrappedFunc := func(tokenRequest *authenticationv1.TokenRequest) (*authenticationv1.TokenRequest, error) {
|
||
|
require.NotNil(t, tokenRequest)
|
||
|
require.Equal(t, tt.expirationSeconds, *tokenRequest.Spec.ExpirationSeconds)
|
||
|
require.Empty(t, tokenRequest.Spec.Audiences)
|
||
|
require.Empty(t, tokenRequest.Spec.BoundObjectRef)
|
||
|
return tt.howToFetchTokenFromAPIServer(tokenRequest)
|
||
|
}
|
||
|
|
||
|
mockClock := clocktesting.NewFakeClock(mockTime.Time)
|
||
|
var log bytes.Buffer
|
||
|
|
||
|
tokenClient := TokenClient{
|
||
|
expirationSeconds: tt.expirationSeconds,
|
||
|
clock: mockClock,
|
||
|
logger: plog.TestLogger(t, &log),
|
||
|
}
|
||
|
|
||
|
tokenRequestStatus, ttl, err := tokenClient.fetchToken(
|
||
|
wrappedFunc,
|
||
|
)
|
||
|
|
||
|
if tt.expected.errMessage != "" {
|
||
|
require.ErrorContains(t, err, tt.expected.errMessage)
|
||
|
} else {
|
||
|
require.Equal(t, tt.expected.tokenRequestStatus, tokenRequestStatus)
|
||
|
require.Equal(t, tt.expected.ttl, ttl)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|