diff --git a/CHANGELOG.md b/CHANGELOG.md index 332c54cc318..1ce67f396b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ 1. [16509](https://github.com/influxdata/influxdb/pull/16509): Add support for applying an influx package via a public facing URL 1. [16511](https://github.com/influxdata/influxdb/pull/16511): Add jsonnet support for influx packages 1. [14782](https://github.com/influxdata/influxdb/pull/16336): Add view page for Check +1. [16537](https://github.com/influxdata/influxdb/pull/16537): Add update password for CLI ### Bug Fixes @@ -68,6 +69,7 @@ 1. [16491](https://github.com/influxdata/influxdb/pull/16491): Add missing env vals to influx cli usage and fixes precedence of flag/env var priority ### UI Improvements + 1. [16444](https://github.com/influxdata/influxdb/pull/16444): Add honeybadger reporting to create checks ## v2.0.0-alpha.21 [2019-12-13] diff --git a/cmd/influx/bucket_test.go b/cmd/influx/bucket_test.go index db2fb31e515..5e32ba69461 100644 --- a/cmd/influx/bucket_test.go +++ b/cmd/influx/bucket_test.go @@ -7,6 +7,7 @@ import ( "os" "reflect" "testing" + "io/ioutil" "time" "github.com/influxdata/influxdb" @@ -102,7 +103,7 @@ func TestCmdBucket(t *testing.T) { return nil } - builder := newCmdBucketBuilder(fakeSVCFn(svc), out(new(bytes.Buffer))) + builder := newCmdBucketBuilder(fakeSVCFn(svc), out(ioutil.Discard)) cmd := builder.cmdCreate() cmd.RunE = builder.cmdCreateRunEFn return cmd @@ -151,7 +152,7 @@ func TestCmdBucket(t *testing.T) { return nil } - builder := newCmdBucketBuilder(fakeSVCFn(svc), out(new(bytes.Buffer))) + builder := newCmdBucketBuilder(fakeSVCFn(svc), out(ioutil.Discard)) cmd := builder.cmdDelete() cmd.RunE = builder.cmdDeleteRunEFn return cmd @@ -258,7 +259,7 @@ func TestCmdBucket(t *testing.T) { return nil, 0, nil } - builder := newCmdBucketBuilder(fakeSVCFn(svc), in(new(bytes.Buffer)), out(new(bytes.Buffer))) + builder := newCmdBucketBuilder(fakeSVCFn(svc), in(new(bytes.Buffer)), out(ioutil.Discard)) cmd := builder.cmdFind() cmd.RunE = builder.cmdFindRunEFn return cmd, calls @@ -352,7 +353,7 @@ func TestCmdBucket(t *testing.T) { return &influxdb.Bucket{}, nil } - builder := newCmdBucketBuilder(fakeSVCFn(svc), out(new(bytes.Buffer))) + builder := newCmdBucketBuilder(fakeSVCFn(svc), out(ioutil.Discard)) cmd := builder.cmdUpdate() cmd.RunE = builder.cmdUpdateRunEFn return cmd diff --git a/cmd/influx/main.go b/cmd/influx/main.go index c2fde04ebc3..d546e59a6fd 100644 --- a/cmd/influx/main.go +++ b/cmd/influx/main.go @@ -128,7 +128,7 @@ func influxCmd(opts ...genericCLIOptFn) *cobra.Command { cmdREPL(), cmdSetup(), cmdTask(), - cmdUser(), + cmdUser(runEWrapper), cmdWrite(), ) diff --git a/cmd/influx/organization_test.go b/cmd/influx/organization_test.go index bededa0257a..22d33bab3e5 100644 --- a/cmd/influx/organization_test.go +++ b/cmd/influx/organization_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "io/ioutil" "reflect" "testing" @@ -63,7 +64,7 @@ func TestCmdOrg(t *testing.T) { return nil } - builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), out(new(bytes.Buffer))) + builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), out(ioutil.Discard)) cmd := builder.cmdCreate() return cmd } @@ -109,7 +110,7 @@ func TestCmdOrg(t *testing.T) { return nil } - builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), out(new(bytes.Buffer))) + builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), out(ioutil.Discard)) cmd := builder.cmdDelete() return cmd } @@ -181,7 +182,7 @@ func TestCmdOrg(t *testing.T) { return nil, 0, nil } - builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), in(new(bytes.Buffer)), out(new(bytes.Buffer))) + builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), in(new(bytes.Buffer)), out(ioutil.Discard)) cmd := builder.cmdFind() return cmd, calls } @@ -268,7 +269,7 @@ func TestCmdOrg(t *testing.T) { return &influxdb.Organization{}, nil } - builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), out(new(bytes.Buffer))) + builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), out(ioutil.Discard)) cmd := builder.cmdUpdate() return cmd } @@ -366,7 +367,7 @@ func TestCmdOrg(t *testing.T) { return &influxdb.Organization{ID: 1}, nil } - builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), in(new(bytes.Buffer)), out(new(bytes.Buffer))) + builder := newCmdOrgBuilder(fakeOrgSVCFn(svc), in(new(bytes.Buffer)), out(ioutil.Discard)) cmd := builder.cmdMemberList() return cmd, calls } @@ -394,7 +395,7 @@ func TestCmdOrg(t *testing.T) { return nil } - builder := newCmdOrgBuilder(fakeOrgUrmSVCsFn(svc, urmSVC), in(new(bytes.Buffer)), out(new(bytes.Buffer))) + builder := newCmdOrgBuilder(fakeOrgUrmSVCsFn(svc, urmSVC), in(new(bytes.Buffer)), out(ioutil.Discard)) cmd := builder.cmdMemberAdd() return cmd, calls } @@ -457,7 +458,7 @@ func TestCmdOrg(t *testing.T) { return nil } - builder := newCmdOrgBuilder(fakeOrgUrmSVCsFn(svc, urmSVC), in(new(bytes.Buffer)), out(new(bytes.Buffer))) + builder := newCmdOrgBuilder(fakeOrgUrmSVCsFn(svc, urmSVC), in(new(bytes.Buffer)), out(ioutil.Discard)) cmd := builder.cmdMemberRemove() return cmd, calls } diff --git a/cmd/influx/setup.go b/cmd/influx/setup.go index 61ae5af35e3..8d04240e328 100644 --- a/cmd/influx/setup.go +++ b/cmd/influx/setup.go @@ -88,7 +88,7 @@ func setupF(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to write token to path %q: %v", dPath, err) } - fmt.Println(promptWithColor("Your token has been stored in "+dPath+".", colorCyan)) + fmt.Println(string(promptWithColor("Your token has been stored in "+dPath+".", colorCyan))) w := internal.NewTabWriter(os.Stdout) w.WriteHeaders( @@ -144,7 +144,7 @@ func interactive() (req *platform.OnboardingRequest, err error) { Reader: os.Stdin, } req = new(platform.OnboardingRequest) - fmt.Println(promptWithColor(`Welcome to InfluxDB 2.0!`, colorYellow)) + fmt.Println(string(promptWithColor(`Welcome to InfluxDB 2.0!`, colorYellow))) if setupFlags.username != "" { req.User = setupFlags.username } else { @@ -153,7 +153,7 @@ func interactive() (req *platform.OnboardingRequest, err error) { if setupFlags.password != "" { req.Password = setupFlags.password } else { - req.Password = getPassword(ui) + req.Password = getPassword(ui, false) } if setupFlags.token != "" { req.Token = setupFlags.token @@ -200,8 +200,9 @@ var ( keyReset = []byte{keyEscape, '[', '0', 'm'} ) -func promptWithColor(s string, color []byte) string { - return string(color) + s + string(keyReset) +func promptWithColor(s string, color []byte) []byte { + bb := append(color, []byte(s)...) + return append(bb, keyReset...) } func getConfirm(ui *input.UI, or *platform.OnboardingRequest) bool { @@ -211,14 +212,14 @@ func getConfirm(ui *input.UI, or *platform.OnboardingRequest) bool { if or.RetentionPeriod > 0 { rp = fmt.Sprintf("%d hrs", or.RetentionPeriod) } - fmt.Print(promptWithColor(fmt.Sprintf(` + ui.Writer.Write(promptWithColor(fmt.Sprintf(` You have entered: Username: %s Organization: %s Bucket: %s Retention Period: %s `, or.User, or.Org, or.Bucket, rp), colorCyan)) - result, err := ui.Ask(prompt, &input.Options{ + result, err := ui.Ask(string(prompt), &input.Options{ HideOrder: true, }) if err != nil { @@ -240,15 +241,20 @@ var ( errPasswordIsTooShort = fmt.Errorf("passwords is too short") ) -func getPassword(ui *input.UI) (password string) { +func getPassword(ui *input.UI, showNew bool) (password string) { + newStr := "" + if showNew { + newStr = " new" + } var err error enterPasswd: - query := promptWithColor("Please type your password", colorCyan) + query := string(promptWithColor("Please type your"+newStr+" password", colorCyan)) for { password, err = ui.Ask(query, &input.Options{ Required: true, HideOrder: true, Hide: true, + Mask: false, ValidateFunc: func(s string) error { if len(s) < 8 { return errPasswordIsTooShort @@ -260,7 +266,7 @@ enterPasswd: case input.ErrInterrupted: os.Exit(1) case errPasswordIsTooShort: - fmt.Println(promptWithColor("Password too short - minimum length is 8 characters!", colorRed)) + ui.Writer.Write(promptWithColor("Password too short - minimum length is 8 characters!", colorRed)) goto enterPasswd default: if password = strings.TrimSpace(password); password == "" { @@ -269,7 +275,7 @@ enterPasswd: } break } - query = promptWithColor("Please type your password again", colorCyan) + query = string(promptWithColor("Please type your"+newStr+" password again", colorCyan)) for { _, err = ui.Ask(query, &input.Options{ Required: true, @@ -288,7 +294,7 @@ enterPasswd: case nil: // Nothing. default: - fmt.Println(promptWithColor("Passwords do not match!", colorRed)) + ui.Writer.Write(promptWithColor("Passwords do not match!\n", colorRed)) goto enterPasswd } break @@ -305,7 +311,7 @@ func getInput(ui *input.UI, prompt, defaultValue string) string { option.Default = defaultValue option.HideDefault = true } - prompt = promptWithColor(prompt, colorCyan) + prompt = string(promptWithColor(prompt, colorCyan)) for { line, err := ui.Ask(prompt, option) switch err { diff --git a/cmd/influx/user.go b/cmd/influx/user.go index fbf089a5ed6..927f710c7d7 100644 --- a/cmd/influx/user.go +++ b/cmd/influx/user.go @@ -3,50 +3,96 @@ package main import ( "context" "errors" + "fmt" "os" - platform "github.com/influxdata/influxdb" + "github.com/influxdata/influxdb" "github.com/influxdata/influxdb/cmd/influx/internal" "github.com/influxdata/influxdb/http" "github.com/spf13/cobra" + input "github.com/tcnksm/go-input" ) -func cmdUser() *cobra.Command { - cmd := &cobra.Command{ - Use: "user", - Short: "User management commands", - Run: seeHelp, +type userSVCsFn func() ( + cmdUserDeps, + error, +) + +type cmdUserDeps struct { + userSVC influxdb.UserService + orgSvc influxdb.OrganizationService + passSVC influxdb.PasswordsService + urmSVC influxdb.UserResourceMappingService + getPassFn func(*input.UI, bool) string +} + +func cmdUser(opts ...genericCLIOptFn) *cobra.Command { + return newCmdUserBuilder(newUserSVC, opts...).cmd() +} + +type cmdUserBuilder struct { + genericCLIOpts + + svcFn userSVCsFn + + id string + name string + password string + org organization +} + +func newCmdUserBuilder(svcsFn userSVCsFn, opts ...genericCLIOptFn) *cmdUserBuilder { + opt := genericCLIOpts{ + in: os.Stdin, + w: os.Stdout, + } + for _, o := range opts { + o(&opt) } + + return &cmdUserBuilder{ + genericCLIOpts: opt, + svcFn: svcsFn, + } +} + +func (b *cmdUserBuilder) cmd() *cobra.Command { + cmd := b.newCmd("user", nil) + cmd.Short = "User management commands" + cmd.Run = seeHelp cmd.AddCommand( - userCreateCmd(), - userDeleteCmd(), - userFindCmd(), - userUpdateCmd(), + b.cmdCreate(), + b.cmdDelete(), + b.cmdFind(), + b.cmdUpdate(), + b.cmdPassword(), ) return cmd } -var userUpdateFlags struct { - id string - name string +func (b *cmdUserBuilder) cmdPassword() *cobra.Command { + cmd := b.newCmd("password", b.cmdPasswordRunEFn) + cmd.Short = "Update user password" + + cmd.Flags().StringVarP(&b.id, "id", "i", "", "The user ID") + cmd.Flags().StringVarP(&b.name, "name", "n", "", "The user name") + + return cmd } -func userUpdateCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "update", - Short: "Update user", - RunE: wrapCheckSetup(userUpdateF), - } +func (b *cmdUserBuilder) cmdUpdate() *cobra.Command { + cmd := b.newCmd("update", b.cmdUpdateRunEFn) + cmd.Short = "Update user" - cmd.Flags().StringVarP(&userUpdateFlags.id, "id", "i", "", "The user ID (required)") - cmd.Flags().StringVarP(&userUpdateFlags.name, "name", "n", "", "The user name") + cmd.Flags().StringVarP(&b.id, "id", "i", "", "The user ID (required)") + cmd.Flags().StringVarP(&b.name, "name", "n", "", "The user name") cmd.MarkFlagRequired("id") return cmd } -func newUserService() (platform.UserService, error) { +func newUserService() (influxdb.UserService, error) { if flags.local { return newLocalKVService() } @@ -60,28 +106,85 @@ func newUserService() (platform.UserService, error) { }, nil } -func userUpdateF(cmd *cobra.Command, args []string) error { - s, err := newUserService() +func newUserSVC() ( + cmdUserDeps, + error) { + httpClient, err := newHTTPClient() + if err != nil { + return cmdUserDeps{}, err + } + userSvc := &http.UserService{Client: httpClient} + orgSvc := &http.OrganizationService{Client: httpClient} + passSvc := &http.PasswordService{Client: httpClient} + urmSvc := &http.UserResourceMappingService{Client: httpClient} + getPassFn := getPassword + + return cmdUserDeps{ + userSVC: userSvc, + orgSvc: orgSvc, + passSVC: passSvc, + urmSVC: urmSvc, + getPassFn: getPassFn, + }, nil +} + +func (b *cmdUserBuilder) cmdPasswordRunEFn(cmd *cobra.Command, args []string) error { + ctx := context.Background() + dep, err := b.svcFn() + if err != nil { + return err + } + + filter := influxdb.UserFilter{} + if b.name != "" { + filter.Name = &b.name + } + if b.id != "" { + id, err := influxdb.IDFromString(b.id) + if err != nil { + return err + } + filter.ID = id + } + u, err := dep.userSVC.FindUser(ctx, filter) + if err != nil { + return err + } + ui := &input.UI{ + Writer: b.genericCLIOpts.w, + Reader: b.genericCLIOpts.in, + } + password := dep.getPassFn(ui, true) + + if err = dep.passSVC.SetPassword(ctx, u.ID, password); err != nil { + return err + } + fmt.Fprintln(b.w, "Your password has been successfully updated.") + return nil +} + +func (b *cmdUserBuilder) cmdUpdateRunEFn(cmd *cobra.Command, args []string) error { + dep, err := b.svcFn() if err != nil { return err } - var id platform.ID - if err := id.DecodeFromString(userUpdateFlags.id); err != nil { + var id influxdb.ID + if err := id.DecodeFromString(b.id); err != nil { return err } - update := platform.UserUpdate{} - if userUpdateFlags.name != "" { - update.Name = &userUpdateFlags.name + update := influxdb.UserUpdate{} + if b.name != "" { + update.Name = &b.name } - user, err := s.UpdateUser(context.Background(), id, update) + user, err := dep.userSVC.UpdateUser(context.Background(), id, update) if err != nil { return err } - w := internal.NewTabWriter(os.Stdout) + w := internal.NewTabWriter(b.w) w.WriteHeaders( "ID", "Name", @@ -95,42 +198,43 @@ func userUpdateF(cmd *cobra.Command, args []string) error { return nil } -var userCreateFlags struct { - name string - password string - org organization -} +func (b *cmdUserBuilder) cmdCreate() *cobra.Command { + cmd := b.newCmd("create", b.cmdCreateRunEFn) + cmd.Short = "Create user" -func userCreateCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "create", - Short: "Create user", - RunE: wrapCheckSetup(userCreateF), + opts := flagOpts{ + { + DestP: &b.name, + Flag: "name", + Short: 'n', + Desc: "The user name (required)", + Required: true, + }, } + opts.mustRegister(cmd) - userCreateFlags.org.register(cmd, false) - cmd.Flags().StringVarP(&userCreateFlags.name, "name", "n", "", "The user name (required)") - cmd.MarkFlagRequired("name") - cmd.Flags().StringVarP(&userCreateFlags.password, "password", "p", "", "The user password") + cmd.Flags().StringVarP(&b.password, "password", "p", "", "The user password") + b.org.register(cmd, false) return cmd } -func userCreateF(cmd *cobra.Command, args []string) error { - if err := userCreateFlags.org.validOrgFlags(); err != nil { +func (b *cmdUserBuilder) cmdCreateRunEFn(*cobra.Command, []string) error { + ctx := context.Background() + if err := b.org.validOrgFlags(); err != nil { return err } - s, err := newUserService() + dep, err := b.svcFn() if err != nil { return err } - user := &platform.User{ - Name: userCreateFlags.name, + user := &influxdb.User{ + Name: b.name, } - if err := s.CreateUser(context.Background(), user); err != nil { + if err := dep.userSVC.CreateUser(ctx, user); err != nil { return err } @@ -143,7 +247,7 @@ func userCreateF(cmd *cobra.Command, args []string) error { for i, h := range headers { m[h] = vals[i] } - w := internal.NewTabWriter(os.Stdout) + w := internal.NewTabWriter(b.w) w.WriteHeaders(headers...) w.Write(m) w.Flush() @@ -151,17 +255,12 @@ func userCreateF(cmd *cobra.Command, args []string) error { return nil } - orgSVC, err := newOrganizationService() + orgID, err := b.org.getID(dep.orgSvc) if err != nil { return err } - orgID, err := userCreateFlags.org.getID(orgSVC) - if err != nil { - return err - } - - pass := userCreateFlags.password + pass := b.password if orgID == 0 && pass == "" { return writeOutput([]string{"ID", "Name"}, user.ID.String(), user.Name) } @@ -170,77 +269,57 @@ func userCreateF(cmd *cobra.Command, args []string) error { return errors.New("an org id is required when providing a user password") } - c, err := newHTTPClient() - if err != nil { - return err - } - - userResMapSVC := &http.UserResourceMappingService{ - Client: c, - } - - err = userResMapSVC.CreateUserResourceMapping(context.Background(), &platform.UserResourceMapping{ + err = dep.urmSVC.CreateUserResourceMapping(context.Background(), &influxdb.UserResourceMapping{ UserID: user.ID, - UserType: platform.Member, - ResourceType: platform.OrgsResourceType, + UserType: influxdb.Member, + ResourceType: influxdb.OrgsResourceType, ResourceID: orgID, }) if err != nil { return err } - passSVC := &http.PasswordService{Client: c} - - ctx := context.Background() - if err := passSVC.SetPassword(ctx, user.ID, pass); err != nil { + if err := dep.passSVC.SetPassword(ctx, user.ID, pass); err != nil { return err } return writeOutput([]string{"ID", "Name", "Organization ID"}, user.ID.String(), user.Name, orgID.String()) } -var userFindFlags struct { - id string - name string -} - -func userFindCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "find", - Short: "Find user", - RunE: wrapCheckSetup(userFindF), - } +func (b *cmdUserBuilder) cmdFind() *cobra.Command { + cmd := b.newCmd("find", b.cmdFindRunEFn) + cmd.Short = "Find user" - cmd.Flags().StringVarP(&userFindFlags.id, "id", "i", "", "The user ID") - cmd.Flags().StringVarP(&userFindFlags.name, "name", "n", "", "The user name") + cmd.Flags().StringVarP(&b.id, "id", "i", "", "The user ID") + cmd.Flags().StringVarP(&b.name, "name", "n", "", "The user name") return cmd } -func userFindF(cmd *cobra.Command, args []string) error { - s, err := newUserService() +func (b *cmdUserBuilder) cmdFindRunEFn(*cobra.Command, []string) error { + dep, err := b.svcFn() if err != nil { return err } - filter := platform.UserFilter{} - if userFindFlags.name != "" { - filter.Name = &userFindFlags.name + filter := influxdb.UserFilter{} + if b.name != "" { + filter.Name = &b.name } - if userFindFlags.id != "" { - id, err := platform.IDFromString(userFindFlags.id) + if b.id != "" { + id, err := influxdb.IDFromString(b.id) if err != nil { return err } filter.ID = id } - users, _, err := s.FindUsers(context.Background(), filter) + users, _, err := dep.userSVC.FindUsers(context.Background(), filter) if err != nil { return err } - w := internal.NewTabWriter(os.Stdout) + w := internal.NewTabWriter(b.w) w.WriteHeaders( "ID", "Name", @@ -256,45 +335,38 @@ func userFindF(cmd *cobra.Command, args []string) error { return nil } -var userDeleteFlags struct { - id string -} - -func userDeleteCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "delete", - Short: "Delete user", - RunE: wrapCheckSetup(userDeleteF), - } +func (b *cmdUserBuilder) cmdDelete() *cobra.Command { + cmd := b.newCmd("delete", b.cmdDeleteRunEFn) + cmd.Short = "Delete user" - cmd.Flags().StringVarP(&userDeleteFlags.id, "id", "i", "", "The user ID (required)") + cmd.Flags().StringVarP(&b.id, "id", "i", "", "The user ID (required)") cmd.MarkFlagRequired("id") return cmd } -func userDeleteF(cmd *cobra.Command, args []string) error { - s, err := newUserService() +func (b *cmdUserBuilder) cmdDeleteRunEFn(cmd *cobra.Command, args []string) error { + dep, err := b.svcFn() if err != nil { return err } - var id platform.ID - if err := id.DecodeFromString(userDeleteFlags.id); err != nil { + var id influxdb.ID + if err := id.DecodeFromString(b.id); err != nil { return err } ctx := context.Background() - u, err := s.FindUserByID(ctx, id) + u, err := dep.userSVC.FindUserByID(ctx, id) if err != nil { return err } - if err := s.DeleteUser(ctx, id); err != nil { + if err := dep.userSVC.DeleteUser(ctx, id); err != nil { return err } - w := internal.NewTabWriter(os.Stdout) + w := internal.NewTabWriter(b.w) w.WriteHeaders( "ID", "Name", diff --git a/cmd/influx/user_test.go b/cmd/influx/user_test.go new file mode 100644 index 00000000000..9670d90d2f4 --- /dev/null +++ b/cmd/influx/user_test.go @@ -0,0 +1,399 @@ +package main + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "reflect" + "testing" + + "github.com/influxdata/influxdb" + platform "github.com/influxdata/influxdb" + "github.com/influxdata/influxdb/mock" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + input "github.com/tcnksm/go-input" +) + +func newCMDUserDeps( + userSVC influxdb.UserService, + passSVC influxdb.PasswordsService, + getPassFn func(*input.UI, bool) string, +) cmdUserDeps { + return cmdUserDeps{ + userSVC: userSVC, + orgSvc: &mock.OrganizationService{ + FindOrganizationF: func(ctx context.Context, filter influxdb.OrganizationFilter) (*influxdb.Organization, error) { + return &influxdb.Organization{ID: influxdb.ID(9000), Name: "influxdata"}, nil + }, + }, + passSVC: passSVC, + urmSVC: &mock.UserResourceMappingService{ + CreateMappingFn: func(context.Context, *platform.UserResourceMapping) error { + return nil + }, + }, + getPassFn: getPassFn, + } +} + +func TestCmdUser(t *testing.T) { + setViperOptions() + + type userResult struct { + user influxdb.User + password string + } + + fakeSVCFn := func(dep cmdUserDeps) userSVCsFn { + return func() ( + cmdUserDeps, + error) { + return dep, nil + } + } + + t.Run("create", func(t *testing.T) { + tests := []struct { + name string + expected userResult + flags []string + envVars map[string]string + }{ + { + name: "basic just name", + flags: []string{"--name=new name", "--org=org name"}, + expected: userResult{ + user: influxdb.User{ + Name: "new name", + }, + }, + }, + { + name: "with password", + flags: []string{ + "--name=new name", + "--password=pass1", + "--org=org name", + }, + expected: userResult{ + user: influxdb.User{ + Name: "new name", + }, + password: "pass1", + }, + }, + { + name: "with password and env", + flags: []string{ + "--name=new name", + "--password=pass1", + }, + envVars: map[string]string{ + "INFLUX_ORG_ID": influxdb.ID(1).String(), + }, + expected: userResult{ + user: influxdb.User{ + Name: "new name", + }, + password: "pass1", + }, + }, + { + name: "shorts", + flags: []string{ + "-n=new name", + "-o=org name", + }, + expected: userResult{ + user: influxdb.User{ + Name: "new name", + }, + }, + }, + } + + cmdFn := func(expected userResult) *cobra.Command { + svc := mock.NewUserService() + svc.CreateUserFn = func(ctx context.Context, User *influxdb.User) error { + if expected.user != *User { + return fmt.Errorf("unexpected User;\n\twant= %+v\n\tgot= %+v", expected, *User) + } + return nil + } + passSVC := mock.NewPasswordsService() + passSVC.SetPasswordFn = func(ctx context.Context, id influxdb.ID, password string) error { + if expected.password != password { + return fmt.Errorf("unexpected password;\n\twant= %+v\n\tgot= %+v", expected.password, password) + } + return nil + } + + builder := newCmdUserBuilder(fakeSVCFn(newCMDUserDeps(svc, passSVC, nil)), out(ioutil.Discard)) + cmd := builder.cmdCreate() + cmd.RunE = builder.cmdCreateRunEFn + return cmd + } + + for _, tt := range tests { + fn := func(t *testing.T) { + defer addEnvVars(t, tt.envVars)() + cmd := cmdFn(tt.expected) + cmd.LocalFlags().Parse(tt.flags) + err := cmd.Execute() + require.NoError(t, err) + } + + t.Run(tt.name, fn) + } + }) + + t.Run("delete", func(t *testing.T) { + tests := []struct { + name string + expectedID influxdb.ID + flag string + }{ + { + name: "long id", + expectedID: influxdb.ID(1), + flag: "--id=", + }, + { + name: "shorts", + expectedID: influxdb.ID(1), + flag: "-i=", + }, + } + + cmdFn := func(expectedID influxdb.ID) *cobra.Command { + svc := mock.NewUserService() + svc.FindUserByIDFn = func(ctx context.Context, id influxdb.ID) (*influxdb.User, error) { + return &influxdb.User{ID: id}, nil + } + svc.DeleteUserFn = func(ctx context.Context, id influxdb.ID) error { + if expectedID != id { + return fmt.Errorf("unexpected id:\n\twant= %s\n\tgot= %s", expectedID, id) + } + return nil + } + + builder := newCmdUserBuilder(fakeSVCFn(newCMDUserDeps(svc, nil, nil)), out(ioutil.Discard)) + cmd := builder.cmdDelete() + cmd.RunE = builder.cmdDeleteRunEFn + return cmd + } + + for _, tt := range tests { + fn := func(t *testing.T) { + cmd := cmdFn(tt.expectedID) + idFlag := tt.flag + tt.expectedID.String() + cmd.LocalFlags().Parse([]string{idFlag}) + require.NoError(t, cmd.Execute()) + } + + t.Run(tt.name, fn) + } + }) + + t.Run("find", func(t *testing.T) { + type called struct { + name string + id influxdb.ID + } + + tests := []struct { + name string + expected called + flags []string + }{ + { + name: "id", + flags: []string{ + "--id=" + influxdb.ID(2).String(), + }, + expected: called{ + id: 2, + }, + }, + { + name: "name", + flags: []string{"--name=name1"}, + expected: called{name: "name1"}, + }, + { + name: "shorts", + flags: []string{ + "-n=name1", + "-i=" + influxdb.ID(1).String(), + }, + expected: called{name: "name1", id: 1}, + }, + } + + cmdFn := func() (*cobra.Command, *called) { + calls := new(called) + + svc := mock.NewUserService() + svc.FindUsersFn = func(ctx context.Context, f influxdb.UserFilter, opt ...influxdb.FindOptions) ([]*influxdb.User, int, error) { + if f.ID != nil { + calls.id = *f.ID + } + if f.Name != nil { + calls.name = *f.Name + } + return nil, 0, nil + } + + builder := newCmdUserBuilder(fakeSVCFn(newCMDUserDeps(svc, nil, nil)), in(new(bytes.Buffer)), out(ioutil.Discard)) + cmd := builder.cmdFind() + cmd.RunE = builder.cmdFindRunEFn + return cmd, calls + } + + for _, tt := range tests { + fn := func(t *testing.T) { + cmd, calls := cmdFn() + cmd.LocalFlags().Parse(tt.flags) + + require.NoError(t, cmd.Execute()) + assert.Equal(t, tt.expected, *calls) + } + + t.Run(tt.name, fn) + } + }) + + t.Run("update", func(t *testing.T) { + tests := []struct { + name string + expected influxdb.UserUpdate + flags []string + }{ + { + name: "basic just name", + flags: []string{ + "--id=" + influxdb.ID(3).String(), + "--name=new name", + }, + expected: influxdb.UserUpdate{ + Name: strPtr("new name"), + }, + }, + { + name: "with all fields", + flags: []string{ + "--id=" + influxdb.ID(3).String(), + "--name=new name", + }, + expected: influxdb.UserUpdate{ + Name: strPtr("new name"), + }, + }, + { + name: "shorts", + flags: []string{ + "-i=" + influxdb.ID(3).String(), + "-n=new name", + }, + expected: influxdb.UserUpdate{ + Name: strPtr("new name"), + }, + }, + } + + cmdFn := func(expectedUpdate influxdb.UserUpdate) *cobra.Command { + svc := mock.NewUserService() + svc.UpdateUserFn = func(ctx context.Context, id influxdb.ID, upd influxdb.UserUpdate) (*influxdb.User, error) { + if id != 3 { + return nil, fmt.Errorf("unexpecte id:\n\twant= %s\n\tgot= %s", influxdb.ID(3), id) + } + if !reflect.DeepEqual(expectedUpdate, upd) { + return nil, fmt.Errorf("unexpected User update;\n\twant= %+v\n\tgot= %+v", expectedUpdate, upd) + } + return &influxdb.User{}, nil + } + + builder := newCmdUserBuilder(fakeSVCFn(newCMDUserDeps(svc, nil, nil)), out(ioutil.Discard)) + cmd := builder.cmdUpdate() + cmd.RunE = builder.cmdUpdateRunEFn + return cmd + } + + for _, tt := range tests { + fn := func(t *testing.T) { + cmd := cmdFn(tt.expected) + cmd.LocalFlags().Parse(tt.flags) + require.NoError(t, cmd.Execute()) + } + + t.Run(tt.name, fn) + } + }) + + t.Run("password", func(t *testing.T) { + tests := []struct { + name string + expected string + flags []string + }{ + { + name: "basic id", + flags: []string{ + "--id=" + influxdb.ID(3).String(), + }, + expected: "pass1", + }, + { + name: "shorts", + flags: []string{ + "-i=" + influxdb.ID(3).String(), + "-n=new name", + }, + expected: "pass1", + }, + } + + cmdFn := func(expected string) *cobra.Command { + svc := mock.NewUserService() + svc.FindUserFn = func(ctx context.Context, f influxdb.UserFilter) (*influxdb.User, error) { + usr := new(influxdb.User) + if id := f.ID; id != nil { + usr.ID = *id + } + if name := f.Name; name != nil { + usr.Name = *name + } + return usr, nil + } + passSVC := mock.NewPasswordsService() + passSVC.SetPasswordFn = func(ctx context.Context, id influxdb.ID, pass string) error { + if pass != expected { + return fmt.Errorf("unexpecte id:\n\twant= %s\n\tgot= %s", pass, expected) + } + return nil + } + + getPassFn := func(*input.UI, bool) string { + return expected + } + builder := newCmdUserBuilder(fakeSVCFn(newCMDUserDeps(svc, passSVC, getPassFn)), + out(ioutil.Discard)) + cmd := builder.cmdPassword() + cmd.RunE = builder.cmdPasswordRunEFn + return cmd + } + + for _, tt := range tests { + fn := func(t *testing.T) { + cmd := cmdFn(tt.expected) + cmd.LocalFlags().Parse(tt.flags) + require.NoError(t, cmd.Execute()) + } + + t.Run(tt.name, fn) + } + }) +}