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..7ca0061f82 --- /dev/null +++ b/internal/cli/auth/token.go @@ -0,0 +1,81 @@ +// 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" +) + +//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 { + cli.OutputOpts + config TokenConfig +} + +func (opts *tokenOpts) Run() error { + accessToken := opts.config.AccessToken() + if accessToken == "" { + return fmt.Errorf("no access token found for profile %s", opts.config.Name()) + } + return opts.Print(accessToken) +} + +func TokenBuilder() *cobra.Command { + opts := &tokenOpts{} + 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, + 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, _ []string) error { + return opts.Run() + }, + } + + opts.AddOutputOptFlags(cmd) + + return cmd +} 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..000b0742a2 --- /dev/null +++ b/internal/cli/auth/token_test.go @@ -0,0 +1,100 @@ +// 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.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\n", 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) +}