From 8152719cfb72b0bc451d53487852a4be2a399c7a Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Thu, 9 Oct 2025 18:54:04 +0100 Subject: [PATCH 1/4] wip --- internal/cli/auth/auth.go | 1 + internal/cli/auth/token.go | 92 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 internal/cli/auth/token.go diff --git a/internal/cli/auth/auth.go b/internal/cli/auth/auth.go index 697ed711a9..b675e3b10c 100644 --- a/internal/cli/auth/auth.go +++ b/internal/cli/auth/auth.go @@ -29,6 +29,7 @@ func Builder() *cobra.Command { WhoAmIBuilder(), LogoutBuilder(), RegisterBuilder(), + TokenBuilder(), ) return cmd diff --git a/internal/cli/auth/token.go b/internal/cli/auth/token.go new file mode 100644 index 0000000000..f64ed20c59 --- /dev/null +++ b/internal/cli/auth/token.go @@ -0,0 +1,92 @@ +// Copyright 2025 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth + +import ( + "fmt" + + "github.com/mongodb/atlas-cli-core/config" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/require" + "github.com/spf13/cobra" +) + +type tokenOpts struct { + name string + cli.OutputOpts +} + +var tokenTemplate = `{{.access_token}}` + +func (*tokenOpts) GetConfig(configStore config.Store, profileName string) (map[string]string, error) { + profileMap := configStore.GetProfileStringMap(profileName) + + if v := configStore.GetProfileValue(profileName, "access_token"); v != nil && v != "" { + profileMap["access_token"] = v.(string) + } else { + return nil, fmt.Errorf("no access token found for profile %s", profileName) + } + + return profileMap, nil +} + +func (opts *tokenOpts) Run() error { + // Create a new config proxy store + configStore, err := config.NewStoreWithEnvOption(false) + if err != nil { + return fmt.Errorf("could not create config store: %w", err) + } + + // get curren tprofile + profile := config.Default() + opts.name = profile.Name() + + mapConfig, err := opts.GetConfig(configStore, opts.name) + if err != nil { + return err + } + + return opts.Print(mapConfig) +} + +func TokenBuilder() *cobra.Command { + opts := &tokenOpts{} + opts.Template = tokenTemplate + cmd := &cobra.Command{ + Use: "token", + Hidden: true, + Short: "Return the token for the current profile.", + Example: ` # Return the token for the current profile: + atlas auth token + + # Return the token for the current profile and save it to a file: + atlas auth token > token.txt + + # Return the token for a specific profile: + atlas auth token --profile + `, + Args: require.NoArgs, + PreRun: func(cmd *cobra.Command, _ []string) { + opts.OutWriter = cmd.OutOrStdout() + }, + RunE: func(_ *cobra.Command, args []string) error { + return opts.Run() + }, + } + + opts.AddOutputOptFlags(cmd) + + return cmd +} From a6b131ab06d31e75657bbd26c8e18f481329bbbf Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Thu, 9 Oct 2025 21:27:39 +0100 Subject: [PATCH 2/4] add token command --- internal/cli/auth/token.go | 49 ++++++----- internal/cli/auth/token_mock_test.go | 116 +++++++++++++++++++++++++++ internal/cli/auth/token_test.go | 101 +++++++++++++++++++++++ 3 files changed, 240 insertions(+), 26 deletions(-) create mode 100644 internal/cli/auth/token_mock_test.go create mode 100644 internal/cli/auth/token_test.go diff --git a/internal/cli/auth/token.go b/internal/cli/auth/token.go index f64ed20c59..3b8897136b 100644 --- a/internal/cli/auth/token.go +++ b/internal/cli/auth/token.go @@ -23,42 +23,31 @@ import ( "github.com/spf13/cobra" ) +//go:generate go tool go.uber.org/mock/mockgen -typed -destination=token_mock_test.go -package=auth . TokenConfig + +type TokenConfig interface { + AccessToken() string + Name() string +} + type tokenOpts struct { - name string cli.OutputOpts + config TokenConfig } var tokenTemplate = `{{.access_token}}` -func (*tokenOpts) GetConfig(configStore config.Store, profileName string) (map[string]string, error) { - profileMap := configStore.GetProfileStringMap(profileName) - - if v := configStore.GetProfileValue(profileName, "access_token"); v != nil && v != "" { - profileMap["access_token"] = v.(string) - } else { - return nil, fmt.Errorf("no access token found for profile %s", profileName) - } - - return profileMap, nil -} - func (opts *tokenOpts) Run() error { - // Create a new config proxy store - configStore, err := config.NewStoreWithEnvOption(false) - if err != nil { - return fmt.Errorf("could not create config store: %w", err) + accessToken := opts.config.AccessToken() + if accessToken == "" { + return fmt.Errorf("no access token found for profile %s", opts.config.Name()) } - // get curren tprofile - profile := config.Default() - opts.name = profile.Name() - - mapConfig, err := opts.GetConfig(configStore, opts.name) - if err != nil { - return err + tokenMap := map[string]string{ + "access_token": accessToken, } - return opts.Print(mapConfig) + return opts.Print(tokenMap) } func TokenBuilder() *cobra.Command { @@ -78,8 +67,16 @@ func TokenBuilder() *cobra.Command { atlas auth token --profile `, Args: require.NoArgs, - PreRun: func(cmd *cobra.Command, _ []string) { + PreRunE: func(cmd *cobra.Command, _ []string) error { opts.OutWriter = cmd.OutOrStdout() + // If the profile is set in the context, use it instead of the default profile + profile, ok := config.ProfileFromContext(cmd.Context()) + if ok { + opts.config = profile + } else { + opts.config = config.Default() + } + return nil }, RunE: func(_ *cobra.Command, args []string) error { return opts.Run() diff --git a/internal/cli/auth/token_mock_test.go b/internal/cli/auth/token_mock_test.go new file mode 100644 index 0000000000..5fa7aa8c68 --- /dev/null +++ b/internal/cli/auth/token_mock_test.go @@ -0,0 +1,116 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/auth (interfaces: TokenConfig) +// +// Generated by this command: +// +// mockgen -typed -destination=token_mock_test.go -package=auth . TokenConfig +// + +// Package auth is a generated GoMock package. +package auth + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockTokenConfig is a mock of TokenConfig interface. +type MockTokenConfig struct { + ctrl *gomock.Controller + recorder *MockTokenConfigMockRecorder + isgomock struct{} +} + +// MockTokenConfigMockRecorder is the mock recorder for MockTokenConfig. +type MockTokenConfigMockRecorder struct { + mock *MockTokenConfig +} + +// NewMockTokenConfig creates a new mock instance. +func NewMockTokenConfig(ctrl *gomock.Controller) *MockTokenConfig { + mock := &MockTokenConfig{ctrl: ctrl} + mock.recorder = &MockTokenConfigMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTokenConfig) EXPECT() *MockTokenConfigMockRecorder { + return m.recorder +} + +// AccessToken mocks base method. +func (m *MockTokenConfig) AccessToken() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AccessToken") + ret0, _ := ret[0].(string) + return ret0 +} + +// AccessToken indicates an expected call of AccessToken. +func (mr *MockTokenConfigMockRecorder) AccessToken() *MockTokenConfigAccessTokenCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AccessToken", reflect.TypeOf((*MockTokenConfig)(nil).AccessToken)) + return &MockTokenConfigAccessTokenCall{Call: call} +} + +// MockTokenConfigAccessTokenCall wrap *gomock.Call +type MockTokenConfigAccessTokenCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockTokenConfigAccessTokenCall) Return(arg0 string) *MockTokenConfigAccessTokenCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockTokenConfigAccessTokenCall) Do(f func() string) *MockTokenConfigAccessTokenCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockTokenConfigAccessTokenCall) DoAndReturn(f func() string) *MockTokenConfigAccessTokenCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// Name mocks base method. +func (m *MockTokenConfig) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name. +func (mr *MockTokenConfigMockRecorder) Name() *MockTokenConfigNameCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockTokenConfig)(nil).Name)) + return &MockTokenConfigNameCall{Call: call} +} + +// MockTokenConfigNameCall wrap *gomock.Call +type MockTokenConfigNameCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockTokenConfigNameCall) Return(arg0 string) *MockTokenConfigNameCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockTokenConfigNameCall) Do(f func() string) *MockTokenConfigNameCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockTokenConfigNameCall) DoAndReturn(f func() string) *MockTokenConfigNameCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/internal/cli/auth/token_test.go b/internal/cli/auth/token_test.go new file mode 100644 index 0000000000..4abcb0fbed --- /dev/null +++ b/internal/cli/auth/token_test.go @@ -0,0 +1,101 @@ +// Copyright 2025 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth + +import ( + "bytes" + "testing" + + "github.com/mongodb/atlas-cli-core/config" + "github.com/mongodb/atlas-cli-core/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" +) + +func Test_tokenOpts_Run_Success(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockConfig := NewMockTokenConfig(ctrl) + buf := new(bytes.Buffer) + + opts := &tokenOpts{ + config: mockConfig, + } + opts.OutWriter = buf + opts.Template = tokenTemplate + opts.Output = "template" // Set output format to template + + mockConfig.EXPECT().AccessToken().Return("test-access-token").Times(1) + + err := opts.Run() + require.NoError(t, err) + assert.Equal(t, "test-access-token", buf.String()) +} + +func Test_tokenOpts_Run_NoToken(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockConfig := NewMockTokenConfig(ctrl) + buf := new(bytes.Buffer) + + opts := &tokenOpts{ + config: mockConfig, + } + opts.OutWriter = buf + + mockConfig.EXPECT().AccessToken().Return("").Times(1) + mockConfig.EXPECT().Name().Return("test-profile").Times(1) + + err := opts.Run() + require.Error(t, err) + assert.Contains(t, err.Error(), "no access token found for profile test-profile") +} + +func TestTokenBuilder_PreRunE_DefaultConfig(t *testing.T) { + cmd := TokenBuilder() + + // Test that PreRunE uses config.Default() when no profile in context + err := cmd.PreRunE(cmd, []string{}) + + // Should not error - just sets up the default config + require.NoError(t, err) +} + +func TestTokenBuilder_PreRunE_ProfileFromContext(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockStore := mocks.NewMockStore(ctrl) + + // Create a test profile + testProfile := config.NewProfile("test-profile", mockStore) + + cmd := TokenBuilder() + + // Add profile to context and execute the command with that context + ctx := config.WithProfile(t.Context(), testProfile) + cmd.SetContext(ctx) + + mockStore.EXPECT(). + GetHierarchicalValue("test-profile", gomock.Any()). + Return(""). + AnyTimes() + + err := cmd.PreRunE(cmd, []string{}) + require.NoError(t, err) +} From f4cbf26d4f800c6ac9037d536fb8d53613457b02 Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Fri, 10 Oct 2025 06:45:57 +0100 Subject: [PATCH 3/4] update --- internal/cli/auth/token.go | 12 ++---------- internal/cli/auth/token_test.go | 1 - 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/internal/cli/auth/token.go b/internal/cli/auth/token.go index 3b8897136b..7ca0061f82 100644 --- a/internal/cli/auth/token.go +++ b/internal/cli/auth/token.go @@ -35,24 +35,16 @@ type tokenOpts struct { config TokenConfig } -var tokenTemplate = `{{.access_token}}` - func (opts *tokenOpts) Run() error { accessToken := opts.config.AccessToken() if accessToken == "" { return fmt.Errorf("no access token found for profile %s", opts.config.Name()) } - - tokenMap := map[string]string{ - "access_token": accessToken, - } - - return opts.Print(tokenMap) + return opts.Print(accessToken) } func TokenBuilder() *cobra.Command { opts := &tokenOpts{} - opts.Template = tokenTemplate cmd := &cobra.Command{ Use: "token", Hidden: true, @@ -78,7 +70,7 @@ func TokenBuilder() *cobra.Command { } return nil }, - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { return opts.Run() }, } diff --git a/internal/cli/auth/token_test.go b/internal/cli/auth/token_test.go index 4abcb0fbed..88d3f23f91 100644 --- a/internal/cli/auth/token_test.go +++ b/internal/cli/auth/token_test.go @@ -36,7 +36,6 @@ func Test_tokenOpts_Run_Success(t *testing.T) { config: mockConfig, } opts.OutWriter = buf - opts.Template = tokenTemplate opts.Output = "template" // Set output format to template mockConfig.EXPECT().AccessToken().Return("test-access-token").Times(1) From cdd7fd013d62a0f17fe148e33fb7ad11f87629ca Mon Sep 17 00:00:00 2001 From: Bianca Lisle Date: Fri, 10 Oct 2025 06:53:21 +0100 Subject: [PATCH 4/4] update --- internal/cli/auth/token_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cli/auth/token_test.go b/internal/cli/auth/token_test.go index 88d3f23f91..000b0742a2 100644 --- a/internal/cli/auth/token_test.go +++ b/internal/cli/auth/token_test.go @@ -42,7 +42,7 @@ func Test_tokenOpts_Run_Success(t *testing.T) { err := opts.Run() require.NoError(t, err) - assert.Equal(t, "test-access-token", buf.String()) + assert.Equal(t, "test-access-token\n", buf.String()) } func Test_tokenOpts_Run_NoToken(t *testing.T) {