From 4962d66a0486223cea6472ba89c039bc611fdf4f Mon Sep 17 00:00:00 2001 From: maxwelbm Date: Mon, 21 Oct 2024 20:12:01 -0300 Subject: [PATCH 1/2] test: added test command login and rest api --- components/mdz/Makefile | 4 + .../internal/domain/repository/auth_mock.go | 70 +++++++ components/mdz/internal/rest/auth_test.go | 184 ++++++++++++++++++ components/mdz/pkg/cmd/login/login.go | 24 ++- components/mdz/pkg/cmd/login/login_test.go | 146 ++++++++++++++ components/mdz/pkg/tui/input.go | 1 + components/mdz/pkg/tui/password.go | 1 + go.mod | 5 +- go.sum | 7 + 9 files changed, 430 insertions(+), 12 deletions(-) create mode 100644 components/mdz/internal/domain/repository/auth_mock.go create mode 100644 components/mdz/internal/rest/auth_test.go create mode 100644 components/mdz/pkg/cmd/login/login_test.go diff --git a/components/mdz/Makefile b/components/mdz/Makefile index e64d6d84..db264f6d 100644 --- a/components/mdz/Makefile +++ b/components/mdz/Makefile @@ -37,6 +37,10 @@ get-perfsprint-deps: perfsprint: get-perfsprint-deps perfsprint ./... +.PHONY: gotext +test: + go test ./... + .PHONY : build build: go version diff --git a/components/mdz/internal/domain/repository/auth_mock.go b/components/mdz/internal/domain/repository/auth_mock.go new file mode 100644 index 00000000..af4e90da --- /dev/null +++ b/components/mdz/internal/domain/repository/auth_mock.go @@ -0,0 +1,70 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: /home/max/Workspace/midaz/components/mdz/internal/domain/repository/auth.go +// +// Generated by this command: +// +// mockgen -source=/home/max/Workspace/midaz/components/mdz/internal/domain/repository/auth.go -destination=/home/max/Workspace/midaz/components/mdz/internal/domain/repository/auth_mock.go -package repository +// + +// Package repository is a generated GoMock package. +package repository + +import ( + reflect "reflect" + + model "github.com/LerianStudio/midaz/components/mdz/internal/model" + gomock "go.uber.org/mock/gomock" +) + +// MockAuth is a mock of Auth interface. +type MockAuth struct { + ctrl *gomock.Controller + recorder *MockAuthMockRecorder +} + +// MockAuthMockRecorder is the mock recorder for MockAuth. +type MockAuthMockRecorder struct { + mock *MockAuth +} + +// NewMockAuth creates a new mock instance. +func NewMockAuth(ctrl *gomock.Controller) *MockAuth { + mock := &MockAuth{ctrl: ctrl} + mock.recorder = &MockAuthMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAuth) EXPECT() *MockAuthMockRecorder { + return m.recorder +} + +// AuthenticateWithCredentials mocks base method. +func (m *MockAuth) AuthenticateWithCredentials(username, password string) (*model.TokenResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AuthenticateWithCredentials", username, password) + ret0, _ := ret[0].(*model.TokenResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AuthenticateWithCredentials indicates an expected call of AuthenticateWithCredentials. +func (mr *MockAuthMockRecorder) AuthenticateWithCredentials(username, password any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthenticateWithCredentials", reflect.TypeOf((*MockAuth)(nil).AuthenticateWithCredentials), username, password) +} + +// ExchangeToken mocks base method. +func (m *MockAuth) ExchangeToken(code string) (*model.TokenResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExchangeToken", code) + ret0, _ := ret[0].(*model.TokenResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExchangeToken indicates an expected call of ExchangeToken. +func (mr *MockAuthMockRecorder) ExchangeToken(code any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExchangeToken", reflect.TypeOf((*MockAuth)(nil).ExchangeToken), code) +} diff --git a/components/mdz/internal/rest/auth_test.go b/components/mdz/internal/rest/auth_test.go new file mode 100644 index 00000000..a9bd82e4 --- /dev/null +++ b/components/mdz/internal/rest/auth_test.go @@ -0,0 +1,184 @@ +package rest + +import ( + "net/http" + "testing" + + "github.com/LerianStudio/midaz/components/mdz/internal/model" + "github.com/LerianStudio/midaz/components/mdz/pkg/environment" + "github.com/LerianStudio/midaz/components/mdz/pkg/factory" + "github.com/jarcoal/httpmock" + "github.com/stretchr/testify/assert" +) + +func TestAuthenticateWithCredentials(t *testing.T) { + tests := []struct { + name string + username string + password string + mockResponse string + mockStatusCode int + expectError bool + expectedToken *model.TokenResponse + }{ + { + name: "success", + username: "testuser", + password: "testpassword", + mockResponse: `{ + "access_token": "mock_access_token", + "id_token": "mock_id_token", + "refresh_token": "mock_refresh_token", + "token_type": "Bearer", + "expires_in": 3600, + "scope": "read write" + }`, + mockStatusCode: 200, + expectError: false, + expectedToken: &model.TokenResponse{ + AccessToken: "mock_access_token", + IDToken: "mock_id_token", + RefreshToken: "mock_refresh_token", + TokenType: "Bearer", + ExpiresIn: 3600, + Scope: "read write", + }, + }, + { + name: "invalid credentials", + username: "invaliduser", + password: "wrongpassword", + mockResponse: `{"error": "invalid_credentials"}`, + mockStatusCode: 401, + expectError: true, + expectedToken: nil, + }, + { + name: "server error", + username: "testuser", + password: "testpassword", + mockResponse: `{"error": "internal_server_error"}`, + mockStatusCode: 500, + expectError: true, + expectedToken: nil, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("POST", "https://mock-api.com/api/login/oauth/access_token", + httpmock.NewStringResponder(tc.mockStatusCode, tc.mockResponse)) + + factory := &factory.Factory{ + HTTPClient: &http.Client{}, + Env: &environment.Env{ + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + URLAPIAuth: "https://mock-api.com", + }, + } + + authInstance := &Auth{ + Factory: factory, + } + + token, err := authInstance.AuthenticateWithCredentials(tc.username, tc.password) + + if tc.expectError { + assert.Error(t, err) + assert.Nil(t, token) + } else { + assert.NoError(t, err) + assert.NotNil(t, token) + assert.Equal(t, tc.expectedToken.AccessToken, token.AccessToken) + assert.Equal(t, tc.expectedToken.IDToken, token.IDToken) + assert.Equal(t, tc.expectedToken.RefreshToken, token.RefreshToken) + assert.Equal(t, tc.expectedToken.TokenType, token.TokenType) + assert.Equal(t, tc.expectedToken.ExpiresIn, token.ExpiresIn) + assert.Equal(t, tc.expectedToken.Scope, token.Scope) + } + + info := httpmock.GetCallCountInfo() + assert.Equal(t, 1, info["POST https://mock-api.com/api/login/oauth/access_token"]) + }) + } +} + +func TestExchangeToken(t *testing.T) { + tests := []struct { + name string + code string + mockResponse string + mockStatusCode int + expectError bool + expectedToken *model.TokenResponse + }{ + { + name: "success", + code: "valid_code", + mockResponse: `{ + "access_token": "mock_access_token", + "id_token": "mock_id_token", + "refresh_token": "mock_refresh_token", + "token_type": "Bearer", + "expires_in": 3600, + "scope": "read write" + }`, + mockStatusCode: 200, + expectError: false, + expectedToken: &model.TokenResponse{ + AccessToken: "mock_access_token", + IDToken: "mock_id_token", + RefreshToken: "mock_refresh_token", + TokenType: "Bearer", + ExpiresIn: 3600, + Scope: "read write", + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + httpmock.RegisterResponder("POST", "https://mock-api.com/api/login/oauth/access_token", + httpmock.NewStringResponder(tc.mockStatusCode, tc.mockResponse)) + + factory := &factory.Factory{ + HTTPClient: &http.Client{}, + Env: &environment.Env{ + ClientID: "test-client-id", + ClientSecret: "test-client-secret", + URLAPIAuth: "https://mock-api.com", + }, + } + + authInstance := &Auth{ + Factory: factory, + } + + token, err := authInstance.ExchangeToken(tc.code) + + if tc.expectError { + assert.Error(t, err) + assert.Nil(t, token) + } else { + assert.NoError(t, err) + assert.NotNil(t, token) + assert.Equal(t, tc.expectedToken.AccessToken, token.AccessToken) + assert.Equal(t, tc.expectedToken.IDToken, token.IDToken) + assert.Equal(t, tc.expectedToken.RefreshToken, token.RefreshToken) + assert.Equal(t, tc.expectedToken.TokenType, token.TokenType) + assert.Equal(t, tc.expectedToken.ExpiresIn, token.ExpiresIn) + assert.Equal(t, tc.expectedToken.Scope, token.Scope) + } + + info := httpmock.GetCallCountInfo() + assert.Equal(t, 1, info["POST https://mock-api.com/api/login/oauth/access_token"]) + }) + } +} diff --git a/components/mdz/pkg/cmd/login/login.go b/components/mdz/pkg/cmd/login/login.go index 4f791228..d4de8b92 100644 --- a/components/mdz/pkg/cmd/login/login.go +++ b/components/mdz/pkg/cmd/login/login.go @@ -17,21 +17,24 @@ import ( ) type factoryLogin struct { - factory *factory.Factory - username string - password string - token string - browser browser - auth repository.Auth + factory *factory.Factory + username string + password string + token string + browser browser + auth repository.Auth + tuiSelect func(message string, options []string) (string, error) } func validateCredentials(username, password string) error { if len(username) == 0 { return errors.New("username must not be empty") } + if len(password) == 0 { return errors.New("password must not be empty") } + return nil } @@ -41,14 +44,14 @@ func (l *factoryLogin) runE(cmd *cobra.Command, _ []string) error { return err } - r := rest.Auth{Factory: l.factory} - _, err := r.AuthenticateWithCredentials(l.username, l.password) + _, err := l.auth.AuthenticateWithCredentials(l.username, l.password) if err != nil { return err } output.Printf(l.factory.IOStreams.Out, color.New(color.Bold).Sprint("Successfully logged in")) + return nil } @@ -113,8 +116,9 @@ func (l *factoryLogin) execMethodLogin(answer string) error { func NewCmdLogin(f *factory.Factory) *cobra.Command { fVersion := factoryLogin{ - factory: f, - auth: rest.NewAuth(f), + factory: f, + auth: rest.NewAuth(f), + tuiSelect: tui.Select, } cmd := &cobra.Command{ diff --git a/components/mdz/pkg/cmd/login/login_test.go b/components/mdz/pkg/cmd/login/login_test.go new file mode 100644 index 00000000..aa4f3dd5 --- /dev/null +++ b/components/mdz/pkg/cmd/login/login_test.go @@ -0,0 +1,146 @@ +package login + +import ( + "bytes" + "errors" + "testing" + + "github.com/LerianStudio/midaz/components/mdz/internal/domain/repository" + "github.com/LerianStudio/midaz/components/mdz/internal/model" + "github.com/LerianStudio/midaz/components/mdz/pkg/environment" + "github.com/LerianStudio/midaz/components/mdz/pkg/factory" + "github.com/LerianStudio/midaz/components/mdz/pkg/iostreams" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" +) + +func TestRunE(t *testing.T) { + // Define test cases + tests := []struct { + name string + username string + password string + expectError bool + mockAuthSetup func(mockAuth *repository.MockAuth) + expectedOutput string + }{ + { + name: "Successful login", + username: "testuser", + password: "testpass", + expectError: false, + mockAuthSetup: func(mockAuth *repository.MockAuth) { + mockAuth. + EXPECT(). + AuthenticateWithCredentials("testuser", "testpass"). + Return(&model.TokenResponse{AccessToken: "mock-token"}, nil) + }, + expectedOutput: "Successfully logged in", + }, + { + name: "Invalid credentials", + username: "invaliduser", + password: "invalidpass", + expectError: true, + mockAuthSetup: func(mockAuth *repository.MockAuth) { + mockAuth. + EXPECT(). + AuthenticateWithCredentials("invaliduser", "invalidpass"). + Return(nil, errors.New("invalid credentials")) // Return an error, not a string + }, + expectedOutput: "", + }, + { + name: "Empty username", + username: "", + password: "somepass", + expectError: true, + mockAuthSetup: func(mockAuth *repository.MockAuth) { + // No call expected since validation should fail before calling auth + }, + expectedOutput: "", + }, + { + name: "Empty password", + username: "someuser", + password: "", + expectError: true, + mockAuthSetup: func(mockAuth *repository.MockAuth) { + // No call expected since validation should fail before calling auth + }, + expectedOutput: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockAuth := repository.NewMockAuth(ctrl) + + if tt.mockAuthSetup != nil { + tt.mockAuthSetup(mockAuth) + } + + outBuf := &bytes.Buffer{} + errBuf := &bytes.Buffer{} + + f := &factory.Factory{ + IOStreams: &iostreams.IOStreams{ + Out: outBuf, + Err: errBuf, + }, + } + + l := &factoryLogin{ + factory: f, + username: tt.username, + password: tt.password, + auth: mockAuth, + tuiSelect: nil, + } + + cmd := &cobra.Command{} + cmd.Flags().String("username", "", "") + cmd.Flags().String("password", "", "") + cmd.Flags().Set("username", tt.username) + cmd.Flags().Set("password", tt.password) + + cmd.Flags().Lookup("username").Changed = true + cmd.Flags().Lookup("password").Changed = true + + err := l.runE(cmd, []string{}) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Contains(t, outBuf.String(), tt.expectedOutput) + } + }) + } +} + +func TestNewCmdLogin(t *testing.T) { + type args struct { + f *factory.Factory + } + tests := []struct { + name string + args args + }{ + { + name: "success", + args: args{ + f: factory.NewFactory(&environment.Env{}), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + NewCmdLogin(tt.args.f) + }) + } +} diff --git a/components/mdz/pkg/tui/input.go b/components/mdz/pkg/tui/input.go index 78f3e4c4..38ee6381 100644 --- a/components/mdz/pkg/tui/input.go +++ b/components/mdz/pkg/tui/input.go @@ -61,6 +61,7 @@ func (m inputModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } m.textInput, cmd = m.textInput.Update(msg) + return m, cmd } diff --git a/components/mdz/pkg/tui/password.go b/components/mdz/pkg/tui/password.go index 0bb36c1c..0cc861b1 100644 --- a/components/mdz/pkg/tui/password.go +++ b/components/mdz/pkg/tui/password.go @@ -63,6 +63,7 @@ func (m passwordModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } m.textInput, cmd = m.textInput.Update(msg) + return m, cmd } diff --git a/go.mod b/go.mod index 88f2759c..f26a4cff 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 go.mongodb.org/mongo-driver v1.17.1 - go.uber.org/mock v0.4.0 + go.uber.org/mock v0.5.0 google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.35.1 ) @@ -43,6 +43,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jarcoal/httpmock v1.3.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect @@ -64,7 +65,7 @@ require ( github.com/swaggo/swag v1.16.3 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + golang.org/x/tools v0.22.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index 7dc05056..efba1c85 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,8 @@ github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= +github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -250,6 +252,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= @@ -268,6 +272,7 @@ golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= @@ -324,6 +329,8 @@ golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58 golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= From dfeeaa33c7332f442a887d9c9dd83867981cd3ea Mon Sep 17 00:00:00 2001 From: Maxwel Mazur <52722027+maxwelbm@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:17:27 -0300 Subject: [PATCH 2/2] chore: update command test makefile Co-authored-by: Gabriel Brecci <34200450+qnen@users.noreply.github.com> --- components/mdz/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/mdz/Makefile b/components/mdz/Makefile index db264f6d..6b0d53fe 100644 --- a/components/mdz/Makefile +++ b/components/mdz/Makefile @@ -37,7 +37,7 @@ get-perfsprint-deps: perfsprint: get-perfsprint-deps perfsprint ./... -.PHONY: gotext +.PHONY: test test: go test ./...