From 1f4b14634fe34a1eef28aa81d7ef3dae8ab75ef7 Mon Sep 17 00:00:00 2001 From: Rita Zerrizuela Date: Wed, 12 Apr 2023 05:41:13 -0300 Subject: [PATCH] Add unit tests for more helper funcs (#728) --- internal/auth0/mock/user.go | 247 +++++++++++++++++++++ internal/cli/custom_domains_test.go | 203 +++++++++++++++++ internal/cli/users_roles.go | 101 ++++----- internal/cli/users_roles_test.go | 332 ++++++++++++++++++++++++++++ 4 files changed, 830 insertions(+), 53 deletions(-) create mode 100644 internal/auth0/mock/user.go create mode 100644 internal/cli/custom_domains_test.go create mode 100644 internal/cli/users_roles_test.go diff --git a/internal/auth0/mock/user.go b/internal/auth0/mock/user.go new file mode 100644 index 000000000..d364f6ff0 --- /dev/null +++ b/internal/auth0/mock/user.go @@ -0,0 +1,247 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./internal/auth0/user.go + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + management "github.com/auth0/go-auth0/management" + gomock "github.com/golang/mock/gomock" +) + +// MockUserAPI is a mock of UserAPI interface. +type MockUserAPI struct { + ctrl *gomock.Controller + recorder *MockUserAPIMockRecorder +} + +// MockUserAPIMockRecorder is the mock recorder for MockUserAPI. +type MockUserAPIMockRecorder struct { + mock *MockUserAPI +} + +// NewMockUserAPI creates a new mock instance. +func NewMockUserAPI(ctrl *gomock.Controller) *MockUserAPI { + mock := &MockUserAPI{ctrl: ctrl} + mock.recorder = &MockUserAPIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockUserAPI) EXPECT() *MockUserAPIMockRecorder { + return m.recorder +} + +// AssignRoles mocks base method. +func (m *MockUserAPI) AssignRoles(id string, roles []*management.Role, opts ...management.RequestOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{id, roles} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AssignRoles", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// AssignRoles indicates an expected call of AssignRoles. +func (mr *MockUserAPIMockRecorder) AssignRoles(id, roles interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id, roles}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssignRoles", reflect.TypeOf((*MockUserAPI)(nil).AssignRoles), varargs...) +} + +// Blocks mocks base method. +func (m *MockUserAPI) Blocks(id string, opts ...management.RequestOption) ([]*management.UserBlock, error) { + m.ctrl.T.Helper() + varargs := []interface{}{id} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Blocks", varargs...) + ret0, _ := ret[0].([]*management.UserBlock) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Blocks indicates an expected call of Blocks. +func (mr *MockUserAPIMockRecorder) Blocks(id interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Blocks", reflect.TypeOf((*MockUserAPI)(nil).Blocks), varargs...) +} + +// Create mocks base method. +func (m *MockUserAPI) Create(u *management.User, opts ...management.RequestOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{u} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Create", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockUserAPIMockRecorder) Create(u interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{u}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockUserAPI)(nil).Create), varargs...) +} + +// Delete mocks base method. +func (m *MockUserAPI) Delete(id string, opts ...management.RequestOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{id} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Delete", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockUserAPIMockRecorder) Delete(id interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockUserAPI)(nil).Delete), varargs...) +} + +// List mocks base method. +func (m *MockUserAPI) List(opts ...management.RequestOption) (*management.UserList, error) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].(*management.UserList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockUserAPIMockRecorder) List(opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockUserAPI)(nil).List), opts...) +} + +// Read mocks base method. +func (m *MockUserAPI) Read(id string, opts ...management.RequestOption) (*management.User, error) { + m.ctrl.T.Helper() + varargs := []interface{}{id} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Read", varargs...) + ret0, _ := ret[0].(*management.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Read indicates an expected call of Read. +func (mr *MockUserAPIMockRecorder) Read(id interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockUserAPI)(nil).Read), varargs...) +} + +// RemoveRoles mocks base method. +func (m *MockUserAPI) RemoveRoles(id string, roles []*management.Role, opts ...management.RequestOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{id, roles} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "RemoveRoles", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveRoles indicates an expected call of RemoveRoles. +func (mr *MockUserAPIMockRecorder) RemoveRoles(id, roles interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id, roles}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveRoles", reflect.TypeOf((*MockUserAPI)(nil).RemoveRoles), varargs...) +} + +// Roles mocks base method. +func (m *MockUserAPI) Roles(id string, opts ...management.RequestOption) (*management.RoleList, error) { + m.ctrl.T.Helper() + varargs := []interface{}{id} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Roles", varargs...) + ret0, _ := ret[0].(*management.RoleList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Roles indicates an expected call of Roles. +func (mr *MockUserAPIMockRecorder) Roles(id interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Roles", reflect.TypeOf((*MockUserAPI)(nil).Roles), varargs...) +} + +// Search mocks base method. +func (m *MockUserAPI) Search(opts ...management.RequestOption) (*management.UserList, error) { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Search", varargs...) + ret0, _ := ret[0].(*management.UserList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Search indicates an expected call of Search. +func (mr *MockUserAPIMockRecorder) Search(opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockUserAPI)(nil).Search), opts...) +} + +// Unblock mocks base method. +func (m *MockUserAPI) Unblock(id string, opts ...management.RequestOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{id} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Unblock", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Unblock indicates an expected call of Unblock. +func (mr *MockUserAPIMockRecorder) Unblock(id interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unblock", reflect.TypeOf((*MockUserAPI)(nil).Unblock), varargs...) +} + +// Update mocks base method. +func (m *MockUserAPI) Update(id string, u *management.User, opts ...management.RequestOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{id, u} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockUserAPIMockRecorder) Update(id, u interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{id, u}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockUserAPI)(nil).Update), varargs...) +} diff --git a/internal/cli/custom_domains_test.go b/internal/cli/custom_domains_test.go new file mode 100644 index 000000000..89845340a --- /dev/null +++ b/internal/cli/custom_domains_test.go @@ -0,0 +1,203 @@ +package cli + +import ( + "net/http" + "testing" + + "github.com/auth0/go-auth0/management" + "github.com/golang/mock/gomock" + + "github.com/stretchr/testify/assert" + + "github.com/auth0/auth0-cli/internal/auth0" + "github.com/auth0/auth0-cli/internal/auth0/mock" +) + +type mockManagementError struct { + statusCode int + error +} + +func (m mockManagementError) Status() int { + return m.statusCode +} + +func TestAPIProvisioningTypeFor(t *testing.T) { + t.Run("maps the 'auth0' provisioning type", func(t *testing.T) { + provisioningType := "auth0" + result := apiProvisioningTypeFor(provisioningType) + + assert.Equal(t, *result, customDomainProvisioningTypeAuth0) + }) + + t.Run("maps the 'self' provisioning type", func(t *testing.T) { + provisioningType := "self" + result := apiProvisioningTypeFor(provisioningType) + + assert.Equal(t, *result, customDomainProvisioningTypeSelf) + }) + + t.Run("returns the input string when the provisioning type is unknown", func(t *testing.T) { + provisioningType := "foo" + result := apiProvisioningTypeFor(provisioningType) + + assert.Equal(t, *result, provisioningType) + }) +} + +func TestAPIPVerificationMethodFor(t *testing.T) { + t.Run("maps the 'txt' verification method", func(t *testing.T) { + verificationMethod := "txt" + result := apiVerificationMethodFor(verificationMethod) + + assert.Equal(t, *result, customDomainVerificationMethodTxt) + }) + + t.Run("returns the input string when the verification method is unknown", func(t *testing.T) { + verificationMethod := "foo" + result := apiVerificationMethodFor(verificationMethod) + + assert.Equal(t, *result, verificationMethod) + }) +} + +func TestAPITLSPolicyFor(t *testing.T) { + t.Run("maps the 'recommended' TLS policy", func(t *testing.T) { + tlsPolicy := "recommended" + result := apiTLSPolicyFor(tlsPolicy) + + assert.Equal(t, *result, customDomainTLSPolicyRecommended) + }) + + t.Run("maps the 'recommended' TLS policy", func(t *testing.T) { + tlsPolicy := "compatible" + result := apiTLSPolicyFor(tlsPolicy) + + assert.Equal(t, *result, customDomainTLSPolicyCompatible) + }) + + t.Run("returns the input string when the TLS policy is unknown", func(t *testing.T) { + tlsPolicy := "foo" + result := apiTLSPolicyFor(tlsPolicy) + + assert.Equal(t, *result, tlsPolicy) + }) +} + +func TestCustomDomainsPickerOptions(t *testing.T) { + tests := []struct { + name string + customDomains []*management.CustomDomain + apiError error + assertOutput func(t testing.TB, options pickerOptions) + assertError func(t testing.TB, err error) + }{ + { + name: "happy path", + customDomains: []*management.CustomDomain{ + { + ID: auth0.String("some-id-1"), + Domain: auth0.String("some-domain-1"), + Status: auth0.String("ready"), + }, + { + ID: auth0.String("some-id-2"), + Domain: auth0.String("some-domain-2"), + Status: auth0.String("ready"), + }, + }, + assertOutput: func(t testing.TB, options pickerOptions) { + assert.Len(t, options, 2) + assert.Equal(t, "some-domain-1 (some-id-1)", options[0].label) + assert.Equal(t, "some-id-1", options[0].value) + assert.Equal(t, "some-domain-2 (some-id-2)", options[1].label) + assert.Equal(t, "some-id-2", options[1].value) + }, + assertError: func(t testing.TB, err error) { + t.Fail() + }, + }, + { + name: "custom domains with a non-ready status", + customDomains: []*management.CustomDomain{ + { + ID: auth0.String("some-id-1"), + Domain: auth0.String("some-domain-1"), + Status: auth0.String("foo"), + }, + { + ID: auth0.String("some-id-2"), + Domain: auth0.String("some-domain-2"), + Status: auth0.String("ready"), + }, + { + ID: auth0.String("some-id-3"), + Domain: auth0.String("some-domain-3"), + Status: auth0.String("bar"), + }, + }, + assertOutput: func(t testing.TB, options pickerOptions) { + assert.Len(t, options, 1) + assert.Equal(t, "some-domain-2 (some-id-2)", options[0].label) + assert.Equal(t, "some-id-2", options[0].value) + }, + assertError: func(t testing.TB, err error) { + t.Fail() + }, + }, + { + name: "no custom domains", + customDomains: []*management.CustomDomain{}, + assertOutput: func(t testing.TB, options pickerOptions) { + t.Fail() + }, + assertError: func(t testing.TB, err error) { + assert.ErrorIs(t, err, errNoCustomDomains) + }, + }, + { + name: "custom domains disabled", + apiError: &mockManagementError{statusCode: http.StatusForbidden}, + assertOutput: func(t testing.TB, options pickerOptions) { + t.Fail() + }, + assertError: func(t testing.TB, err error) { + assert.ErrorIs(t, err, errNoCustomDomains) + }, + }, + { + name: "API error", + apiError: &mockManagementError{statusCode: http.StatusServiceUnavailable}, + assertOutput: func(t testing.TB, options pickerOptions) { + t.Fail() + }, + assertError: func(t testing.TB, err error) { + assert.Error(t, err) + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + customDomainAPI := mock.NewMockCustomDomainAPI(ctrl) + customDomainAPI.EXPECT(). + List(gomock.Any()). + Return(test.customDomains, test.apiError) + + cli := &cli{ + api: &auth0.API{CustomDomain: customDomainAPI}, + } + + options, err := cli.customDomainsPickerOptions() + + if err != nil { + test.assertError(t, err) + } else { + test.assertOutput(t, options) + } + }) + } +} diff --git a/internal/cli/users_roles.go b/internal/cli/users_roles.go index 02702236a..9712cab47 100644 --- a/internal/cli/users_roles.go +++ b/internal/cli/users_roles.go @@ -40,6 +40,9 @@ type userRolesInput struct { Roles []string } +type userRolesFetcher func(cli *cli, userID string) ([]string, error) +type userRolesSelector func(options []string) ([]string, error) + func userRolesCmd(cli *cli) *cobra.Command { cmd := &cobra.Command{ Use: "roles", @@ -141,7 +144,7 @@ func addUserRolesCmd(cli *cli) *cobra.Command { } if len(inputs.Roles) == 0 { - if err := cli.pickUserRolesToAdd(&inputs); err != nil { + if err := cli.getUserRoles(&inputs, userRolesToAddPickerOptions, pickUserRoles); err != nil { return err } } @@ -201,7 +204,7 @@ func removeUserRolesCmd(cli *cli) *cobra.Command { } if len(inputs.Roles) == 0 { - if err := cli.pickUserRolesToRemove(&inputs); err != nil { + if err := cli.getUserRoles(&inputs, userRolesToRemovePickerOptions, pickUserRoles); err != nil { return err } } @@ -239,35 +242,33 @@ func removeUserRolesCmd(cli *cli) *cobra.Command { return cmd } -func (cli *cli) pickUserRolesToAdd(inputs *userRolesInput) error { - var currentUserRoleList *management.RoleList +func (cli *cli) getUserRoles(inputs *userRolesInput, fetchUserRoles userRolesFetcher, selectUserRoles userRolesSelector) error { + var options []string if err := ansi.Waiting(func() (err error) { - currentUserRoleList, err = cli.api.User.Roles(inputs.ID, management.PerPage(100)) + options, err = fetchUserRoles(cli, inputs.ID) return err }); err != nil { - return fmt.Errorf("failed to find the current roles for user with ID %s: %w", inputs.ID, err) + return err } - var roleList *management.RoleList - if err := ansi.Waiting(func() (err error) { - roleList, err = cli.api.Role.List() + selectedRoles, err := selectUserRoles(options) + if err != nil { return err - }); err != nil { - return fmt.Errorf("failed to list all roles: %w", err) } - if len(roleList.Roles) == len(currentUserRoleList.Roles) { - return fmt.Errorf("the user with ID %q has all roles assigned already", inputs.ID) + for _, selectedRole := range selectedRoles { + indexOfFirstEmptySpace := strings.Index(selectedRole, " ") + inputs.Roles = append(inputs.Roles, selectedRole[:indexOfFirstEmptySpace]) } - const emptySpace = " " - var options []string - for _, role := range roleList.Roles { - if !containsRole(currentUserRoleList.Roles, role.GetID()) { - options = append(options, fmt.Sprintf("%s%s(Name: %s)", role.GetID(), emptySpace, role.GetName())) - } + if len(inputs.Roles) == 0 { + return errNoRolesSelected } + return err +} + +func pickUserRoles(options []string) ([]string, error) { rolesPrompt := &survey.MultiSelect{ Message: "Roles", Options: options, @@ -275,56 +276,50 @@ func (cli *cli) pickUserRolesToAdd(inputs *userRolesInput) error { var selectedRoles []string if err := survey.AskOne(rolesPrompt, &selectedRoles); err != nil { - return err - } - - for _, selectedRole := range selectedRoles { - indexOfFirstEmptySpace := strings.Index(selectedRole, emptySpace) - inputs.Roles = append(inputs.Roles, selectedRole[:indexOfFirstEmptySpace]) - } - - if len(inputs.Roles) == 0 { - return errNoRolesSelected + return nil, err } - return nil + return selectedRoles, nil } -func (cli *cli) pickUserRolesToRemove(inputs *userRolesInput) error { - var currentUserRoleList *management.RoleList - if err := ansi.Waiting(func() (err error) { - currentUserRoleList, err = cli.api.User.Roles(inputs.ID) - return err - }); err != nil { - return fmt.Errorf("failed to find the current roles for user with ID %s: %w", inputs.ID, err) +func userRolesToAddPickerOptions(cli *cli, userID string) ([]string, error) { + currentUserRoleList, err := cli.api.User.Roles(userID, management.PerPage(100)) + if err != nil { + return nil, fmt.Errorf("Failed to find the current roles for user with ID %q: %w.", userID, err) } - const emptySpace = " " - var options []string - for _, role := range currentUserRoleList.Roles { - options = append(options, fmt.Sprintf("%s%s(Name: %s)", role.GetID(), emptySpace, role.GetName())) + var roleList *management.RoleList + roleList, err = cli.api.Role.List() + if err != nil { + return nil, fmt.Errorf("Failed to list all roles: %w.", err) } - rolesPrompt := &survey.MultiSelect{ - Message: "Roles", - Options: options, + if len(roleList.Roles) == len(currentUserRoleList.Roles) { + return nil, fmt.Errorf("The user with ID %q has all roles assigned already.", userID) } - var selectedRoles []string - if err := survey.AskOne(rolesPrompt, &selectedRoles); err != nil { - return err + var options []string + for _, role := range roleList.Roles { + if !containsRole(currentUserRoleList.Roles, role.GetID()) { + options = append(options, fmt.Sprintf("%s (Name: %s)", role.GetID(), role.GetName())) + } } - for _, selectedRole := range selectedRoles { - indexOfFirstEmptySpace := strings.Index(selectedRole, emptySpace) - inputs.Roles = append(inputs.Roles, selectedRole[:indexOfFirstEmptySpace]) + return options, nil +} + +func userRolesToRemovePickerOptions(cli *cli, userID string) ([]string, error) { + currentUserRoleList, err := cli.api.User.Roles(userID, management.PerPage(100)) + if err != nil { + return nil, fmt.Errorf("Failed to find the current roles for user with ID %q: %w.", userID, err) } - if len(inputs.Roles) == 0 { - return errNoRolesSelected + var options []string + for _, role := range currentUserRoleList.Roles { + options = append(options, fmt.Sprintf("%s (Name: %s)", role.GetID(), role.GetName())) } - return nil + return options, nil } func containsRole(roles []*management.Role, roleID string) bool { diff --git a/internal/cli/users_roles_test.go b/internal/cli/users_roles_test.go new file mode 100644 index 000000000..4db9ef493 --- /dev/null +++ b/internal/cli/users_roles_test.go @@ -0,0 +1,332 @@ +package cli + +import ( + "errors" + "testing" + + "github.com/auth0/go-auth0/management" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + + "github.com/auth0/auth0-cli/internal/auth0" + "github.com/auth0/auth0-cli/internal/auth0/mock" +) + +func TestGetUserRoles(t *testing.T) { + t.Run("gets user roles", func(t *testing.T) { + inputs := &userRolesInput{ + ID: "some-id", + Roles: []string{}, + } + userRolesFetcher := func(cli *cli, userID string) ([]string, error) { + assert.Equal(t, userID, "some-id") + return []string{"some-id-1", "some-id-2"}, nil + } + userRolesSelector := func(options []string) ([]string, error) { + assert.Equal(t, options, []string{"some-id-1", "some-id-2"}) + return []string{"some-id-3 (Name: some-name-3)", "some-id-4 (Name: some-name-4)"}, nil + } + cli := &cli{} + err := cli.getUserRoles(inputs, userRolesFetcher, userRolesSelector) + + assert.Equal(t, inputs.Roles, []string{"some-id-3", "some-id-4"}) + assert.Nil(t, err) + }) + + t.Run("returns error when user roles fetcher fails", func(t *testing.T) { + inputs := &userRolesInput{Roles: []string{}} + userRolesFetcher := func(cli *cli, userID string) ([]string, error) { + return nil, errors.New("error") + } + cli := &cli{} + err := cli.getUserRoles(inputs, userRolesFetcher, nil) + + assert.Error(t, err) + }) + + t.Run("returns error when user roles selector fails", func(t *testing.T) { + inputs := &userRolesInput{Roles: []string{}} + userRolesFetcher := func(cli *cli, userID string) ([]string, error) { + return []string{}, nil + } + userRolesSelector := func(options []string) ([]string, error) { + return nil, errors.New("error") + } + cli := &cli{} + err := cli.getUserRoles(inputs, userRolesFetcher, userRolesSelector) + + assert.Error(t, err) + }) + + t.Run("returns error when no roles where selected", func(t *testing.T) { + inputs := &userRolesInput{Roles: []string{}} + userRolesFetcher := func(cli *cli, userID string) ([]string, error) { + return []string{"some-id-1", "some-id-2"}, nil + } + userRolesSelector := func(options []string) ([]string, error) { + return []string{}, nil + } + cli := &cli{} + err := cli.getUserRoles(inputs, userRolesFetcher, userRolesSelector) + + assert.ErrorIs(t, err, errNoRolesSelected) + }) +} + +func TestUserRolesToAddPickerOptions(t *testing.T) { + tests := []struct { + name string + userID string + userRoles []*management.Role + allRoles []*management.Role + userAPIError error + roleAPIError error + assertOutput func(t testing.TB, options []string) + assertError func(t testing.TB, err error) + }{ + { + name: "happy path", + userRoles: []*management.Role{ + { + ID: auth0.String("some-id-1"), + Name: auth0.String("some-name-1"), + }, + { + ID: auth0.String("some-id-2"), + Name: auth0.String("some-name-2"), + }, + }, + allRoles: []*management.Role{ + { + ID: auth0.String("some-id-1"), + Name: auth0.String("some-name-1"), + }, + { + ID: auth0.String("some-id-2"), + Name: auth0.String("some-name-2"), + }, + { + ID: auth0.String("some-id-3"), + Name: auth0.String("some-name-3"), + }, + { + ID: auth0.String("some-id-4"), + Name: auth0.String("some-name-4"), + }, + }, + assertOutput: func(t testing.TB, options []string) { + assert.Len(t, options, 2) + assert.Equal(t, "some-id-3 (Name: some-name-3)", options[0]) + assert.Equal(t, "some-id-4 (Name: some-name-4)", options[1]) + }, + assertError: func(t testing.TB, err error) { + t.Fail() + }, + }, + { + name: "user API error", + userID: "some-id", + userAPIError: errors.New("error"), + assertOutput: func(t testing.TB, options []string) { + t.Fail() + }, + assertError: func(t testing.TB, err error) { + assert.ErrorContains(t, err, "Failed to find the current roles for user with ID \"some-id\": error.") + }, + }, + { + name: "role API error", + roleAPIError: errors.New("error"), + assertOutput: func(t testing.TB, options []string) { + t.Fail() + }, + assertError: func(t testing.TB, err error) { + assert.ErrorContains(t, err, "Failed to list all roles: error.") + }, + }, + { + name: "user already has all roles assigned", + userID: "some-id", + userRoles: []*management.Role{ + { + ID: auth0.String("some-id-1"), + Name: auth0.String("some-name-1"), + }, + { + ID: auth0.String("some-id-2"), + Name: auth0.String("some-name-2"), + }, + }, + allRoles: []*management.Role{ + { + ID: auth0.String("some-id-1"), + Name: auth0.String("some-name-1"), + }, + { + ID: auth0.String("some-id-2"), + Name: auth0.String("some-name-2"), + }, + }, + assertOutput: func(t testing.TB, options []string) { + t.Fail() + }, + assertError: func(t testing.TB, err error) { + assert.ErrorContains(t, err, "The user with ID \"some-id\" has all roles assigned already.") + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + userAPI := mock.NewMockUserAPI(ctrl) + userAPI.EXPECT(). + Roles(gomock.Eq(test.userID), gomock.Any()). + Return(&management.RoleList{ + Roles: test.userRoles}, test.userAPIError) + + timesRolesAPIShouldBeCalled := 1 + + if test.allRoles == nil && test.roleAPIError == nil { + timesRolesAPIShouldBeCalled = 0 + } + + roleAPI := mock.NewMockRoleAPI(ctrl) + roleAPI.EXPECT(). + List(gomock.Any()). + Return(&management.RoleList{Roles: test.allRoles}, test.roleAPIError). + Times(timesRolesAPIShouldBeCalled) + + cli := &cli{ + api: &auth0.API{User: userAPI, Role: roleAPI}, + } + + options, err := userRolesToAddPickerOptions(cli, test.userID) + + if err != nil { + test.assertError(t, err) + } else { + test.assertOutput(t, options) + } + }) + } +} + +func TestUserRolesToRemovePickerOptions(t *testing.T) { + tests := []struct { + name string + userID string + userRoles []*management.Role + apiError error + assertOutput func(t testing.TB, options []string) + assertError func(t testing.TB, err error) + }{ + { + name: "happy path", + userRoles: []*management.Role{ + { + ID: auth0.String("some-id-1"), + Name: auth0.String("some-name-1"), + }, + { + ID: auth0.String("some-id-2"), + Name: auth0.String("some-name-2"), + }, + }, + assertOutput: func(t testing.TB, options []string) { + assert.Len(t, options, 2) + assert.Equal(t, "some-id-1 (Name: some-name-1)", options[0]) + assert.Equal(t, "some-id-2 (Name: some-name-2)", options[1]) + }, + assertError: func(t testing.TB, err error) { + t.Fail() + }, + }, + { + name: "no roles for user", + userRoles: []*management.Role{}, + assertOutput: func(t testing.TB, options []string) { + assert.Empty(t, options) + }, + assertError: func(t testing.TB, err error) { + t.Fail() + }, + }, + { + name: "API error", + userID: "some-id", + apiError: errors.New("error"), + assertOutput: func(t testing.TB, options []string) { + t.Fail() + }, + assertError: func(t testing.TB, err error) { + assert.ErrorContains(t, err, "Failed to find the current roles for user with ID \"some-id\": error.") + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + userAPI := mock.NewMockUserAPI(ctrl) + userAPI.EXPECT(). + Roles(gomock.Eq(test.userID), gomock.Any()). + Return(&management.RoleList{Roles: test.userRoles}, test.apiError) + + cli := &cli{ + api: &auth0.API{User: userAPI}, + } + + options, err := userRolesToRemovePickerOptions(cli, test.userID) + + if err != nil { + test.assertError(t, err) + } else { + test.assertOutput(t, options) + } + }) + } +} + +func TestContainsRole(t *testing.T) { + t.Run("returns true when role is found", func(t *testing.T) { + roles := []*management.Role{ + { + ID: auth0.String("some-id-1"), + }, + { + ID: auth0.String("some-id-2"), + }, + } + + result := containsRole(roles, "some-id-2") + + assert.True(t, result) + }) + + t.Run("returns false when role is not found", func(t *testing.T) { + roles := []*management.Role{ + { + ID: auth0.String("some-id-1"), + }, + { + ID: auth0.String("some-id-2"), + }, + } + + result := containsRole(roles, "some-other-id") + + assert.False(t, result) + }) + + t.Run("returns false when there are no roles", func(t *testing.T) { + roles := []*management.Role{} + result := containsRole(roles, "some-id") + + assert.False(t, result) + }) +}