diff --git a/cmd/cliflags/flags.go b/cmd/cliflags/flags.go index bc2d5eea..448d41ac 100644 --- a/cmd/cliflags/flags.go +++ b/cmd/cliflags/flags.go @@ -5,4 +5,5 @@ const ( BaseURIFlag = "base-uri" FlagFlag = "flag" ProjectFlag = "project" + EmailsFlag = "emails" ) diff --git a/cmd/members/create.go b/cmd/members/create.go index ac2ac517..3d7af554 100644 --- a/cmd/members/create.go +++ b/cmd/members/create.go @@ -53,7 +53,7 @@ func runCreate(client members.Client) func(*cobra.Command, []string) error { context.Background(), viper.GetString(cliflags.APITokenFlag), viper.GetString(cliflags.BaseURIFlag), - data.Email, + []string{data.Email}, data.Role, ) if err != nil { diff --git a/cmd/members/create_test.go b/cmd/members/create_test.go index 4cab392d..b9b49b0e 100644 --- a/cmd/members/create_test.go +++ b/cmd/members/create_test.go @@ -16,7 +16,7 @@ func TestCreate(t *testing.T) { mockArgs := []interface{}{ "testAccessToken", "http://test.com", - "testemail@test.com", + []string{"testemail@test.com"}, "writer", } t.Run("with valid flags calls members API", func(t *testing.T) { diff --git a/cmd/members/invite.go b/cmd/members/invite.go new file mode 100644 index 00000000..8a6a9c7d --- /dev/null +++ b/cmd/members/invite.go @@ -0,0 +1,57 @@ +package members + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "ldcli/cmd/cliflags" + "ldcli/cmd/validators" + "ldcli/internal/members" +) + +const defaultRole = "reader" + +func NewInviteCmd(client members.Client) (*cobra.Command, error) { + cmd := &cobra.Command{ + Args: validators.Validate(), + Long: "Invite new members", + RunE: runInvite(client), + Short: "Invite new members", + Use: "invite", + } + + cmd.Flags().StringSliceP("emails", "e", []string{}, "A comma separated list of emails") + err := cmd.MarkFlagRequired("emails") + if err != nil { + return nil, err + } + err = viper.BindPFlag("emails", cmd.Flags().Lookup("emails")) + if err != nil { + return nil, err + } + + return cmd, nil +} + +func runInvite(client members.Client) func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + + response, err := client.Create( + context.Background(), + viper.GetString(cliflags.APITokenFlag), + viper.GetString(cliflags.BaseURIFlag), + viper.GetStringSlice(cliflags.EmailsFlag), + defaultRole, + ) + if err != nil { + return err + } + + fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + + return nil + } +} diff --git a/cmd/members/invite_test.go b/cmd/members/invite_test.go new file mode 100644 index 00000000..31ca8427 --- /dev/null +++ b/cmd/members/invite_test.go @@ -0,0 +1,87 @@ +package members_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "ldcli/cmd" + "ldcli/internal/errors" + "ldcli/internal/members" +) + +func TestInvite(t *testing.T) { + errorHelp := ". See `ldcli members invite --help` for supported flags and usage." + mockArgs := []interface{}{ + "testAccessToken", + "http://test.com", + []string{"testemail1@test.com", "testemail2@test.com"}, + "reader", + } + t.Run("with valid flags calls members API", func(t *testing.T) { + client := members.MockClient{} + client. + On("Create", mockArgs...). + Return([]byte(cmd.ValidResponse), nil) + args := []string{ + "members", + "invite", + "--api-token", + "testAccessToken", + "--base-uri", + "http://test.com", + "-e", + `testemail1@test.com,testemail2@test.com`, + } + + output, err := cmd.CallCmd(t, nil, &client, nil, args) + + require.NoError(t, err) + assert.JSONEq(t, `{"valid": true}`, string(output)) + }) + + t.Run("with an error response is an error", func(t *testing.T) { + client := members.MockClient{} + client. + On("Create", mockArgs...). + Return([]byte(`{}`), errors.NewError("An error")) + args := []string{ + "members", + "invite", + "--api-token", + "testAccessToken", + "--base-uri", + "http://test.com", + "-e", + `testemail1@test.com,testemail2@test.com`, + } + + _, err := cmd.CallCmd(t, nil, &client, nil, args) + + require.EqualError(t, err, "An error") + }) + + t.Run("with missing required flags is an error", func(t *testing.T) { + args := []string{ + "members", + "invite", + } + + _, err := cmd.CallCmd(t, nil, &members.MockClient{}, nil, args) + + assert.EqualError(t, err, `required flag(s) "api-token", "emails" not set`+errorHelp) + }) + + t.Run("with invalid base-uri is an error", func(t *testing.T) { + args := []string{ + "members", + "invite", + "--base-uri", "invalid", + } + + _, err := cmd.CallCmd(t, nil, &members.MockClient{}, nil, args) + + assert.EqualError(t, err, "base-uri is invalid"+errorHelp) + }) +} diff --git a/cmd/members/members.go b/cmd/members/members.go index 5aa698b7..34a994cb 100644 --- a/cmd/members/members.go +++ b/cmd/members/members.go @@ -18,7 +18,13 @@ func NewMembersCmd(client members.Client) (*cobra.Command, error) { return nil, err } + inviteCmd, err := NewInviteCmd(client) + if err != nil { + return nil, err + } + cmd.AddCommand(createCmd) + cmd.AddCommand(inviteCmd) return cmd, nil diff --git a/internal/members/members.go b/internal/members/members.go index e17eb1ac..22cac43b 100644 --- a/internal/members/members.go +++ b/internal/members/members.go @@ -11,7 +11,7 @@ import ( ) type Client interface { - Create(ctx context.Context, accessToken, baseURI, email, role string) ([]byte, error) + Create(ctx context.Context, accessToken string, baseURI string, emails []string, role string) ([]byte, error) } type MembersClient struct{} @@ -20,14 +20,18 @@ func NewClient() Client { return MembersClient{} } -func (c MembersClient) Create(ctx context.Context, accessToken, baseURI, email, role string) ([]byte, error) { +func (c MembersClient) Create(ctx context.Context, accessToken string, baseURI string, emails []string, role string) ([]byte, error) { client := client.New(accessToken, baseURI) - memberForm := ldapi.NewMemberForm{Email: email, Role: &role} - members, _, err := client.AccountMembersApi.PostMembers(ctx).NewMemberForm([]ldapi.NewMemberForm{memberForm}).Execute() + 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 { return nil, errors.NewLDAPIError(err) } - memberJson, err := json.Marshal(members.Items[0]) + memberJson, err := json.Marshal(members.Items) if err != nil { return nil, err } diff --git a/internal/members/mock.go b/internal/members/mock.go index d34b5190..de5874d6 100644 --- a/internal/members/mock.go +++ b/internal/members/mock.go @@ -14,12 +14,12 @@ var _ Client = &MockClient{} func (c *MockClient) Create( ctx context.Context, - accessToken, - baseURI, - email, + accessToken string, + baseURI string, + emails []string, role string, ) ([]byte, error) { - args := c.Called(accessToken, baseURI, email, role) + args := c.Called(accessToken, baseURI, emails, role) return args.Get(0).([]byte), args.Error(1) }