From a4d8c6d3f499285d9115aa4141cc7a4e0d2811ab Mon Sep 17 00:00:00 2001 From: Anuj Chaudhari Date: Fri, 4 Oct 2024 15:52:17 -0700 Subject: [PATCH] Add unit tests Signed-off-by: Anuj Chaudhari --- pkg/auth/uaa/tanzu.go | 2 +- pkg/command/apitoken.go | 3 +- pkg/command/apitoken_test.go | 201 +++++++++++++++++++++++++++++++++++ pkg/utils/time/time.go | 9 ++ 4 files changed, 213 insertions(+), 2 deletions(-) create mode 100644 pkg/command/apitoken_test.go create mode 100644 pkg/utils/time/time.go diff --git a/pkg/auth/uaa/tanzu.go b/pkg/auth/uaa/tanzu.go index 1b0c9d207..76f367eaa 100644 --- a/pkg/auth/uaa/tanzu.go +++ b/pkg/auth/uaa/tanzu.go @@ -31,7 +31,7 @@ func getIssuerEndpoints(issuerURL string) common.IssuerEndPoints { } } -func TanzuLogin(issuerURL string, opts ...common.LoginOption) (*common.Token, error) { +var TanzuLogin = func(issuerURL string, opts ...common.LoginOption) (*common.Token, error) { issuerEndpoints := getIssuerEndpoints(issuerURL) h := common.NewTanzuLoginHandler(issuerURL, issuerEndpoints.AuthURL, issuerEndpoints.TokenURL, TanzuCLIClientID, tanzuCLIClientSecret, defaultListenAddress, defaultCallbackPath, config.UAAIdpType, nil, nil, term.IsTerminal) diff --git a/pkg/command/apitoken.go b/pkg/command/apitoken.go index 6010ce32e..7400bac41 100644 --- a/pkg/command/apitoken.go +++ b/pkg/command/apitoken.go @@ -19,6 +19,7 @@ import ( "github.com/vmware-tanzu/tanzu-cli/pkg/auth/uaa" "github.com/vmware-tanzu/tanzu-cli/pkg/cli" "github.com/vmware-tanzu/tanzu-cli/pkg/constants" + timeutils "github.com/vmware-tanzu/tanzu-cli/pkg/utils/time" ) func newAPITokenCmd() *cobra.Command { @@ -80,7 +81,7 @@ func createAPIToken(cmd *cobra.Command, _ []string) (err error) { fmt.Fprint(cmd.OutOrStdout(), bold.Sprint("==\n\n")) fmt.Fprintf(cmd.OutOrStdout(), "%s Your generated API token is: %s\n\n", bold.Sprint("API Token Generation Successful!"), cyanBold.Sprint(token.RefreshToken)) - fmt.Fprintf(cmd.OutOrStdout(), "%s The token will expire on %s.\n", bold.Sprint("Token Expiration:"), bold.Sprint(time.Now().Local().Add(time.Second*time.Duration(token.ExpiresIn)))) + fmt.Fprintf(cmd.OutOrStdout(), "%s The token will expire on %s.\n", bold.Sprint("Token Expiration:"), bold.Sprint(timeutils.Now().Local().Add(time.Second*time.Duration(token.ExpiresIn)))) fmt.Fprint(cmd.OutOrStdout(), "Please copy and save your token securely. Note that you will need to regenerate a new token before expiration time and login again to continue using the CLI.\n\n") fmt.Fprintf(cmd.OutOrStdout(), "For non-interactive login use the API token as follow: %s\n", cyanBold.Sprint("TANZU_API_TOKEN= tanzu login --endpoint ")) diff --git a/pkg/command/apitoken_test.go b/pkg/command/apitoken_test.go new file mode 100644 index 000000000..76ed63de2 --- /dev/null +++ b/pkg/command/apitoken_test.go @@ -0,0 +1,201 @@ +package command + +import ( + "bytes" + "os" + "path/filepath" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/otiai10/copy" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/vmware-tanzu/tanzu-plugin-runtime/config" + configtypes "github.com/vmware-tanzu/tanzu-plugin-runtime/config/types" + + "github.com/vmware-tanzu/tanzu-cli/pkg/auth/common" + "github.com/vmware-tanzu/tanzu-cli/pkg/auth/uaa" + timeutils "github.com/vmware-tanzu/tanzu-cli/pkg/utils/time" +) + +// mockTanzuLogin is a mock implementation of the TanzuLogin function +type mockTanzuLogin struct { + mock.Mock +} + +func (m *mockTanzuLogin) TanzuLogin(issuerURL string, opts ...common.LoginOption) (*common.Token, error) { + args := m.Called(issuerURL, opts) + return args.Get(0).(*common.Token), args.Error(1) +} + +type mockTimeService struct{} + +func (ts *mockTimeService) Now() time.Time { + // Return a mock time + return time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) +} + +func TestCreateAPIToken(t *testing.T) { + var configFile, configFileNG *os.File + var err error + + setupEnv := func() { + configFile, err = os.CreateTemp("", "config") + assert.NoError(t, err) + + err = copy.Copy(filepath.Join("..", "fakes", "config", "tanzu_config.yaml"), configFile.Name()) + assert.NoError(t, err) + + os.Setenv("TANZU_CONFIG", configFile.Name()) + + configFileNG, err = os.CreateTemp("", "config_ng") + assert.NoError(t, err) + + os.Setenv("TANZU_CONFIG_NEXT_GEN", configFileNG.Name()) + err = copy.Copy(filepath.Join("..", "fakes", "config", "tanzu_config_ng.yaml"), configFileNG.Name()) + assert.NoError(t, err) + } + + teardownEnv := func() { + os.Unsetenv("TANZU_CONFIG") + os.Unsetenv("TANZU_CONFIG_NEXT_GEN") + os.RemoveAll(configFile.Name()) + os.RemoveAll(configFileNG.Name()) + } + + tests := []struct { + name string + context *configtypes.Context + tanzuLoginErr error + wantErr bool + errMsg string + output string + }{ + { + name: "success", + context: &configtypes.Context{ + Name: "fakecontext", + ContextType: configtypes.ContextTypeTanzu, + GlobalOpts: &configtypes.GlobalServer{ + Auth: configtypes.GlobalServerAuth{ + Issuer: "https://example.com", + }, + }, + AdditionalMetadata: map[string]interface{}{ + config.TanzuIdpTypeKey: config.UAAIdpType, + }, + }, + tanzuLoginErr: nil, + wantErr: false, + output: `== + +API Token Generation Successful! Your generated API token is: refresh-token + +Token Expiration: The token will expire on 2024-01-01 05:00:00 -0800 PST. +Please copy and save your token securely. Note that you will need to regenerate a new token before expiration time and login again to continue using the CLI. + +For non-interactive login use the API token as follow: TANZU_API_TOKEN= tanzu login --endpoint +`, + }, + { + name: "no active context", + context: nil, + tanzuLoginErr: nil, + wantErr: true, + errMsg: "No active context of type `tanzu`. Please login to Tanzu Platform first to generate the API token", + }, + { + name: "invalid active context", + context: &configtypes.Context{ + Name: "fakecontext", + ContextType: configtypes.ContextTypeTanzu, + AdditionalMetadata: map[string]interface{}{ + config.TanzuIdpTypeKey: config.UAAIdpType, + }, + }, + tanzuLoginErr: nil, + wantErr: true, + errMsg: "Invalid active context of type `tanzu`. Please login to Tanzu Platform first to generate the API token", + }, + { + name: "invalid IDP type", + context: &configtypes.Context{ + Name: "fakecontext", + ContextType: configtypes.ContextTypeTanzu, + GlobalOpts: &configtypes.GlobalServer{ + Auth: configtypes.GlobalServerAuth{ + Issuer: "https://example.com", + }, + }, + AdditionalMetadata: map[string]interface{}{ + config.TanzuIdpTypeKey: "other", + }, + }, + tanzuLoginErr: nil, + wantErr: true, + errMsg: "invalid IDP type for the active context. Only UAA IDP type is supported for generating API token", + }, + { + name: "TanzuLogin error", + context: &configtypes.Context{ + Name: "fakecontext", + ContextType: configtypes.ContextTypeTanzu, + GlobalOpts: &configtypes.GlobalServer{ + Auth: configtypes.GlobalServerAuth{ + Issuer: "https://example.com", + }, + }, + AdditionalMetadata: map[string]interface{}{ + config.TanzuIdpTypeKey: config.UAAIdpType, + }, + }, + tanzuLoginErr: errors.New("TanzuLogin error"), + wantErr: true, + errMsg: "unable to login, TanzuLogin error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + setupEnv() + defer teardownEnv() + + cmd := &cobra.Command{} + outputBuf := &bytes.Buffer{} + cmd.SetOutput(outputBuf) + + mockTimeService := &mockTimeService{} + timeutils.Now = mockTimeService.Now + defer func() { timeutils.Now = time.Now }() + + mockTanzuLogin := &mockTanzuLogin{} + uaa.TanzuLogin = mockTanzuLogin.TanzuLogin + if tt.context != nil && tt.context.GlobalOpts != nil && tt.context.AdditionalMetadata[config.TanzuIdpTypeKey] == config.UAAIdpType { + mockTanzuLogin.On("TanzuLogin", tt.context.GlobalOpts.Auth.Issuer, mock.Anything). + Return(&common.Token{RefreshToken: "refresh-token", ExpiresIn: 3600}, tt.tanzuLoginErr) + } + + if tt.context != nil { + err = config.SetContext(tt.context, true) + assert.NoError(t, err) + } + + err := createAPIToken(cmd, nil) + + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + if diff := cmp.Diff(outputBuf.String(), tt.output); diff != "" { + t.Errorf("Unexpected output (-expected, +actual): %s", diff) + } + } + + mockTanzuLogin.AssertExpectations(t) + }) + } +} diff --git a/pkg/utils/time/time.go b/pkg/utils/time/time.go new file mode 100644 index 000000000..4416d06d9 --- /dev/null +++ b/pkg/utils/time/time.go @@ -0,0 +1,9 @@ +// Copyright 2024 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Package time contains time specific functions and variable to make it to overwrite for unit tests +package time + +import "time" + +var Now = time.Now