From e0c45034f54e0c1116675e43e5b7ba6fb02813ac Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Mon, 1 Apr 2024 11:43:57 -0700 Subject: [PATCH 1/4] members create cmd should invite multiple members --- cmd/members/create.go | 15 +++++---------- cmd/members/create_test.go | 9 +++++---- cmd/members/invite.go | 11 +++++++++-- internal/members/members.go | 8 ++------ internal/members/mock.go | 6 +++--- 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/cmd/members/create.go b/cmd/members/create.go index 119b13e1..a64762c6 100644 --- a/cmd/members/create.go +++ b/cmd/members/create.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" + ldapi "github.com/launchdarkly/api-client-go/v14" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -16,9 +17,9 @@ import ( func NewCreateCmd(client members.Client) (*cobra.Command, error) { cmd := &cobra.Command{ Args: validators.Validate(), - Long: "Create a new member and send them an invitation email", + Long: "Create new members and send them an invitation email", RunE: runCreate(client), - Short: "Create a new member", + Short: "Create new members", Use: "create", } @@ -35,14 +36,9 @@ func NewCreateCmd(client members.Client) (*cobra.Command, error) { return cmd, nil } -type inputData struct { - Email string `json:"email"` - Role string `json:"role"` -} - func runCreate(client members.Client) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { - var data inputData + var data []ldapi.NewMemberForm // TODO: why does viper.GetString(cliflags.DataFlag) not work? err := json.Unmarshal([]byte(cmd.Flags().Lookup(cliflags.DataFlag).Value.String()), &data) if err != nil { @@ -53,8 +49,7 @@ func runCreate(client members.Client) func(*cobra.Command, []string) error { context.Background(), viper.GetString(cliflags.AccessTokenFlag), viper.GetString(cliflags.BaseURIFlag), - []string{data.Email}, - data.Role, + data, ) if err != nil { return err diff --git a/cmd/members/create_test.go b/cmd/members/create_test.go index eea7f2d6..04930286 100644 --- a/cmd/members/create_test.go +++ b/cmd/members/create_test.go @@ -3,6 +3,7 @@ package members_test import ( "testing" + ldapi "github.com/launchdarkly/api-client-go/v14" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -13,11 +14,11 @@ import ( func TestCreate(t *testing.T) { errorHelp := ". See `ldcli members create --help` for supported flags and usage." + role := "writer" mockArgs := []interface{}{ "testAccessToken", "http://test.com", - []string{"testemail@test.com"}, - "writer", + []ldapi.NewMemberForm{{Email: "testemail@test.com", Role: &role}}, } t.Run("with valid flags calls members API", func(t *testing.T) { client := members.MockClient{} @@ -32,7 +33,7 @@ func TestCreate(t *testing.T) { "--base-uri", "http://test.com", "-d", - `{"email": "testemail@test.com", "role": "writer"}`, + `[{"email": "testemail@test.com", "role": "writer"}]`, } output, err := cmd.CallCmd(t, nil, &client, nil, args) @@ -54,7 +55,7 @@ func TestCreate(t *testing.T) { "--base-uri", "http://test.com", "-d", - `{"email": "testemail@test.com", "role": "writer"}`, + `[{"email": "testemail@test.com", "role": "writer"}]`, } _, err := cmd.CallCmd(t, nil, &client, nil, args) diff --git a/cmd/members/invite.go b/cmd/members/invite.go index fdf0fe6c..34772db3 100644 --- a/cmd/members/invite.go +++ b/cmd/members/invite.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + ldapi "github.com/launchdarkly/api-client-go/v14" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -46,13 +47,19 @@ func NewInviteCmd(client members.Client) (*cobra.Command, error) { } func runInvite(client members.Client) func(*cobra.Command, []string) error { + emails := viper.GetStringSlice(cliflags.EmailsFlag) + members := make([]ldapi.NewMemberForm, 0, len(emails)) + for _, e := range emails { + role := viper.GetString(cliflags.RoleFlag) + members = append(members, ldapi.NewMemberForm{Email: e, Role: &role}) + } + return func(cmd *cobra.Command, args []string) error { response, err := client.Create( context.Background(), viper.GetString(cliflags.AccessTokenFlag), viper.GetString(cliflags.BaseURIFlag), - viper.GetStringSlice(cliflags.EmailsFlag), - viper.GetString(cliflags.RoleFlag), + members, ) if err != nil { return err diff --git a/internal/members/members.go b/internal/members/members.go index 3680fc3a..148bcf8c 100644 --- a/internal/members/members.go +++ b/internal/members/members.go @@ -11,7 +11,7 @@ import ( ) type Client interface { - Create(ctx context.Context, accessToken string, baseURI string, emails []string, role string) ([]byte, error) + Create(ctx context.Context, accessToken string, baseURI string, members []ldapi.NewMemberForm) ([]byte, error) } type MembersClient struct { @@ -24,12 +24,8 @@ func NewClient(cliVersion string) Client { } } -func (c MembersClient) Create(ctx context.Context, accessToken string, baseURI string, emails []string, role string) ([]byte, error) { +func (c MembersClient) Create(ctx context.Context, accessToken string, baseURI string, memberForms []ldapi.NewMemberForm) ([]byte, error) { client := client.New(accessToken, baseURI, c.cliVersion) - memberForms := make([]ldapi.NewMemberForm, 0, len(emails)) - for _, e := range emails { - memberForms = append(memberForms, ldapi.NewMemberForm{Email: e, Role: &role}) - } members, _, err := client.AccountMembersApi.PostMembers(ctx).NewMemberForm(memberForms).Execute() if err != nil { diff --git a/internal/members/mock.go b/internal/members/mock.go index de5874d6..069c4ccf 100644 --- a/internal/members/mock.go +++ b/internal/members/mock.go @@ -3,6 +3,7 @@ package members import ( "context" + ldapi "github.com/launchdarkly/api-client-go/v14" "github.com/stretchr/testify/mock" ) @@ -16,10 +17,9 @@ func (c *MockClient) Create( ctx context.Context, accessToken string, baseURI string, - emails []string, - role string, + members []ldapi.NewMemberForm, ) ([]byte, error) { - args := c.Called(accessToken, baseURI, emails, role) + args := c.Called(accessToken, baseURI, members) return args.Get(0).([]byte), args.Error(1) } From 6de07a2d379704a60469749314a4d81f44f8198e Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Mon, 1 Apr 2024 12:11:23 -0700 Subject: [PATCH 2/4] fix member invite tests --- cmd/members/invite.go | 19 +++++++++---------- cmd/members/invite_test.go | 8 ++++++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/cmd/members/invite.go b/cmd/members/invite.go index 34772db3..2bf1d698 100644 --- a/cmd/members/invite.go +++ b/cmd/members/invite.go @@ -22,12 +22,12 @@ func NewInviteCmd(client members.Client) (*cobra.Command, error) { Use: "invite", } - cmd.Flags().StringSliceP("emails", "e", []string{}, "A comma separated list of emails") - err := cmd.MarkFlagRequired("emails") + cmd.Flags().StringSliceP(cliflags.EmailsFlag, "e", []string{}, "A comma separated list of emails") + err := cmd.MarkFlagRequired(cliflags.EmailsFlag) if err != nil { return nil, err } - err = viper.BindPFlag("emails", cmd.Flags().Lookup("emails")) + err = viper.BindPFlag(cliflags.EmailsFlag, cmd.Flags().Lookup(cliflags.EmailsFlag)) if err != nil { return nil, err } @@ -47,14 +47,13 @@ func NewInviteCmd(client members.Client) (*cobra.Command, error) { } func runInvite(client members.Client) func(*cobra.Command, []string) error { - emails := viper.GetStringSlice(cliflags.EmailsFlag) - members := make([]ldapi.NewMemberForm, 0, len(emails)) - for _, e := range emails { - role := viper.GetString(cliflags.RoleFlag) - members = append(members, ldapi.NewMemberForm{Email: e, Role: &role}) - } - return func(cmd *cobra.Command, args []string) error { + emails := viper.GetStringSlice(cliflags.EmailsFlag) + members := make([]ldapi.NewMemberForm, 0, len(emails)) + for _, e := range emails { + role := viper.GetString(cliflags.RoleFlag) + members = append(members, ldapi.NewMemberForm{Email: e, Role: &role}) + } response, err := client.Create( context.Background(), viper.GetString(cliflags.AccessTokenFlag), diff --git a/cmd/members/invite_test.go b/cmd/members/invite_test.go index c546f70b..89bff57f 100644 --- a/cmd/members/invite_test.go +++ b/cmd/members/invite_test.go @@ -3,6 +3,7 @@ package members_test import ( "testing" + ldapi "github.com/launchdarkly/api-client-go/v14" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -13,11 +14,14 @@ import ( func TestInvite(t *testing.T) { errorHelp := ". See `ldcli members invite --help` for supported flags and usage." + readerRole := "reader" mockArgs := []interface{}{ "testAccessToken", "http://test.com", - []string{"testemail1@test.com", "testemail2@test.com"}, - "reader", + []ldapi.NewMemberForm{ + {Email: "testemail1@test.com", Role: &readerRole}, + {Email: "testemail2@test.com", Role: &readerRole}, + }, } t.Run("with valid flags calls members API", func(t *testing.T) { client := members.MockClient{} From e0479bc362d69ad43d24d009a241b8bcc4c12aa9 Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Mon, 1 Apr 2024 14:00:51 -0700 Subject: [PATCH 3/4] add MemberInput type in members client --- cmd/members/create.go | 3 +-- cmd/members/create_test.go | 3 +-- cmd/members/invite.go | 7 +++---- cmd/members/invite_test.go | 7 +++---- internal/members/members.go | 14 +++++++++++--- internal/members/mock.go | 5 ++--- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/cmd/members/create.go b/cmd/members/create.go index a64762c6..f3d3406a 100644 --- a/cmd/members/create.go +++ b/cmd/members/create.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" - ldapi "github.com/launchdarkly/api-client-go/v14" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -38,7 +37,7 @@ func NewCreateCmd(client members.Client) (*cobra.Command, error) { func runCreate(client members.Client) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { - var data []ldapi.NewMemberForm + var data []members.MemberInput // TODO: why does viper.GetString(cliflags.DataFlag) not work? err := json.Unmarshal([]byte(cmd.Flags().Lookup(cliflags.DataFlag).Value.String()), &data) if err != nil { diff --git a/cmd/members/create_test.go b/cmd/members/create_test.go index 04930286..c5c231d0 100644 --- a/cmd/members/create_test.go +++ b/cmd/members/create_test.go @@ -3,7 +3,6 @@ package members_test import ( "testing" - ldapi "github.com/launchdarkly/api-client-go/v14" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,7 +17,7 @@ func TestCreate(t *testing.T) { mockArgs := []interface{}{ "testAccessToken", "http://test.com", - []ldapi.NewMemberForm{{Email: "testemail@test.com", Role: &role}}, + []members.MemberInput{{Email: "testemail@test.com", Role: role}}, } t.Run("with valid flags calls members API", func(t *testing.T) { client := members.MockClient{} diff --git a/cmd/members/invite.go b/cmd/members/invite.go index 2bf1d698..cc118e68 100644 --- a/cmd/members/invite.go +++ b/cmd/members/invite.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - ldapi "github.com/launchdarkly/api-client-go/v14" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -49,16 +48,16 @@ func NewInviteCmd(client members.Client) (*cobra.Command, error) { func runInvite(client members.Client) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { emails := viper.GetStringSlice(cliflags.EmailsFlag) - members := make([]ldapi.NewMemberForm, 0, len(emails)) + memberInputs := make([]members.MemberInput, 0, len(emails)) for _, e := range emails { role := viper.GetString(cliflags.RoleFlag) - members = append(members, ldapi.NewMemberForm{Email: e, Role: &role}) + memberInputs = append(memberInputs, members.MemberInput{Email: e, Role: role}) } response, err := client.Create( context.Background(), viper.GetString(cliflags.AccessTokenFlag), viper.GetString(cliflags.BaseURIFlag), - members, + memberInputs, ) if err != nil { return err diff --git a/cmd/members/invite_test.go b/cmd/members/invite_test.go index 89bff57f..ccbfc3ba 100644 --- a/cmd/members/invite_test.go +++ b/cmd/members/invite_test.go @@ -3,7 +3,6 @@ package members_test import ( "testing" - ldapi "github.com/launchdarkly/api-client-go/v14" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,9 +17,9 @@ func TestInvite(t *testing.T) { mockArgs := []interface{}{ "testAccessToken", "http://test.com", - []ldapi.NewMemberForm{ - {Email: "testemail1@test.com", Role: &readerRole}, - {Email: "testemail2@test.com", Role: &readerRole}, + []members.MemberInput{ + {Email: "testemail1@test.com", Role: readerRole}, + {Email: "testemail2@test.com", Role: readerRole}, }, } t.Run("with valid flags calls members API", func(t *testing.T) { diff --git a/internal/members/members.go b/internal/members/members.go index 148bcf8c..31a2480b 100644 --- a/internal/members/members.go +++ b/internal/members/members.go @@ -3,7 +3,6 @@ package members import ( "context" "encoding/json" - ldapi "github.com/launchdarkly/api-client-go/v14" "ldcli/internal/client" @@ -11,7 +10,7 @@ import ( ) type Client interface { - Create(ctx context.Context, accessToken string, baseURI string, members []ldapi.NewMemberForm) ([]byte, error) + Create(ctx context.Context, accessToken string, baseURI string, memberInputs []MemberInput) ([]byte, error) } type MembersClient struct { @@ -24,8 +23,17 @@ func NewClient(cliVersion string) Client { } } -func (c MembersClient) Create(ctx context.Context, accessToken string, baseURI string, memberForms []ldapi.NewMemberForm) ([]byte, error) { +type MemberInput struct { + Email string `json:"email"` + Role string `json:"role"` +} + +func (c MembersClient) Create(ctx context.Context, accessToken string, baseURI string, memberInputs []MemberInput) ([]byte, error) { client := client.New(accessToken, baseURI, c.cliVersion) + memberForms := make([]ldapi.NewMemberForm, 0, len(memberInputs)) + for _, m := range memberInputs { + memberForms = append(memberForms, ldapi.NewMemberForm{Email: m.Email, Role: &m.Role}) + } members, _, err := client.AccountMembersApi.PostMembers(ctx).NewMemberForm(memberForms).Execute() if err != nil { diff --git a/internal/members/mock.go b/internal/members/mock.go index 069c4ccf..415fc300 100644 --- a/internal/members/mock.go +++ b/internal/members/mock.go @@ -3,7 +3,6 @@ package members import ( "context" - ldapi "github.com/launchdarkly/api-client-go/v14" "github.com/stretchr/testify/mock" ) @@ -17,9 +16,9 @@ func (c *MockClient) Create( ctx context.Context, accessToken string, baseURI string, - members []ldapi.NewMemberForm, + memberInputs []MemberInput, ) ([]byte, error) { - args := c.Called(accessToken, baseURI, members) + args := c.Called(accessToken, baseURI, memberInputs) return args.Get(0).([]byte), args.Error(1) } From ce8f2d0b80d50379eb592506e12ca9ae9d97934c Mon Sep 17 00:00:00 2001 From: Kelly Hofmann Date: Mon, 1 Apr 2024 14:04:02 -0700 Subject: [PATCH 4/4] fix tests --- cmd/members/invite_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/members/invite_test.go b/cmd/members/invite_test.go index ccbfc3ba..3adc2377 100644 --- a/cmd/members/invite_test.go +++ b/cmd/members/invite_test.go @@ -90,11 +90,14 @@ func TestInvite(t *testing.T) { } func TestInviteWithOptionalRole(t *testing.T) { + writerRole := "writer" mockArgs := []interface{}{ "testAccessToken", "http://test.com", - []string{"testemail1@test.com", "testemail2@test.com"}, - "writer", + []members.MemberInput{ + {Email: "testemail1@test.com", Role: writerRole}, + {Email: "testemail2@test.com", Role: writerRole}, + }, } t.Run("with valid optional long form flag calls members API", func(t *testing.T) { client := members.MockClient{}