From 2ca75afd08a702aeaa19beae824d2d70db67d455 Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Tue, 23 Apr 2024 13:27:15 -0700 Subject: [PATCH 1/8] Show plaintext response for projects create --- cmd/projects/create.go | 11 ++++++++++- cmd/projects/create_test.go | 14 ++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/cmd/projects/create.go b/cmd/projects/create.go index cd5cd382..64bd367c 100644 --- a/cmd/projects/create.go +++ b/cmd/projects/create.go @@ -10,6 +10,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/output" "ldcli/internal/projects" ) @@ -60,7 +61,15 @@ func runCreate(client projects.Client) func(*cobra.Command, []string) error { return err } - fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + output, err := output.CmdOutput( + viper.GetString(cliflags.OutputFlag), + output.NewSingularOutputterFn(response), + ) + if err != nil { + return err + } + + fmt.Fprintf(cmd.OutOrStdout(), string(output)+"\n") return nil } diff --git a/cmd/projects/create_test.go b/cmd/projects/create_test.go index aebf0e71..6e20204a 100644 --- a/cmd/projects/create_test.go +++ b/cmd/projects/create_test.go @@ -20,12 +20,16 @@ func TestCreate(t *testing.T) { "test-name", "test-key", } + stubbedSuccessResponse := `{ + "key": "test-key", + "name": "test-name" + }` t.Run("with valid flags calls API", func(t *testing.T) { client := projects.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(stubbedSuccessResponse), nil) clients := cmd.APIClients{ ProjectsClient: &client, } @@ -34,6 +38,7 @@ func TestCreate(t *testing.T) { "create", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-d", `{"key": "test-key", "name": "test-name"}`, } @@ -41,7 +46,7 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, stubbedSuccessResponse, string(output)) }) t.Run("with valid flags from environment variables calls API", func(t *testing.T) { @@ -50,13 +55,14 @@ func TestCreate(t *testing.T) { client := projects.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(stubbedSuccessResponse), nil) clients := cmd.APIClients{ ProjectsClient: &client, } args := []string{ "projects", "create", + "--output", "json", "-d", `{"key": "test-key", "name": "test-name"}`, } @@ -64,7 +70,7 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, stubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { From 390a943e7542773a2c580be96d2ffdec9df07c37 Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Tue, 23 Apr 2024 13:31:06 -0700 Subject: [PATCH 2/8] Show plaintext response for flags create --- cmd/environments/get.go | 2 +- cmd/flags/create.go | 11 ++++++++++- cmd/flags/create_test.go | 14 ++++++++++---- cmd/projects/create.go | 2 +- cmd/projects/list.go | 2 +- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/cmd/environments/get.go b/cmd/environments/get.go index 8e052412..bc3366ef 100644 --- a/cmd/environments/get.go +++ b/cmd/environments/get.go @@ -74,7 +74,7 @@ func runGet( return err } - fmt.Fprintf(cmd.OutOrStdout(), string(output)+"\n") + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/flags/create.go b/cmd/flags/create.go index a8bc0bbf..18e54874 100644 --- a/cmd/flags/create.go +++ b/cmd/flags/create.go @@ -11,6 +11,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" "ldcli/internal/flags" + "ldcli/internal/output" ) func NewCreateCmd(client flags.Client) (*cobra.Command, error) { @@ -74,7 +75,15 @@ func runCreate(client flags.Client) func(*cobra.Command, []string) error { return err } - fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + output, err := output.CmdOutput( + viper.GetString(cliflags.OutputFlag), + output.NewSingularOutputterFn(response), + ) + if err != nil { + return err + } + + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/flags/create_test.go b/cmd/flags/create_test.go index 8403ce33..a6c13295 100644 --- a/cmd/flags/create_test.go +++ b/cmd/flags/create_test.go @@ -21,12 +21,16 @@ func TestCreate(t *testing.T) { "test-key", "test-proj-key", } + stubbedSuccessResponse := `{ + "key": "test-key", + "name": "test-name" + }` t.Run("with valid flags calls API", func(t *testing.T) { client := flags.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(stubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -34,6 +38,7 @@ func TestCreate(t *testing.T) { "flags", "create", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-d", `{"key": "test-key", "name": "test-name"}`, "--project", "test-proj-key", } @@ -41,7 +46,7 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, stubbedSuccessResponse, string(output)) }) t.Run("with valid flags from environment variables calls API", func(t *testing.T) { @@ -50,12 +55,13 @@ func TestCreate(t *testing.T) { client := flags.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(stubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } args := []string{ "flags", "create", + "--output", "json", "-d", `{"key": "test-key", "name": "test-name"}`, "--project", "test-proj-key", } @@ -63,7 +69,7 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, stubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { diff --git a/cmd/projects/create.go b/cmd/projects/create.go index 64bd367c..90e7ea3a 100644 --- a/cmd/projects/create.go +++ b/cmd/projects/create.go @@ -69,7 +69,7 @@ func runCreate(client projects.Client) func(*cobra.Command, []string) error { return err } - fmt.Fprintf(cmd.OutOrStdout(), string(output)+"\n") + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/projects/list.go b/cmd/projects/list.go index 5cd2f874..aa0dac20 100644 --- a/cmd/projects/list.go +++ b/cmd/projects/list.go @@ -44,7 +44,7 @@ func runList(client projects.Client) func(*cobra.Command, []string) error { return err } - fmt.Fprintf(cmd.OutOrStdout(), string(output)+"\n") + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } From 98e661ac346f581063d23de0365b3b60ee588d14 Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Tue, 23 Apr 2024 13:37:05 -0700 Subject: [PATCH 3/8] Show plaintext response for flags get --- cmd/cmdtest.go | 4 ++++ cmd/flags/get.go | 11 ++++++++++- cmd/flags/get_test.go | 15 ++++++++++----- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/cmd/cmdtest.go b/cmd/cmdtest.go index 38a3edd0..624e2544 100644 --- a/cmd/cmdtest.go +++ b/cmd/cmdtest.go @@ -12,6 +12,10 @@ import ( ) var ValidResponse = `{"valid": true}` +var StubbedSuccessResponse = `{ + "key": "test-key", + "name": "test-name" +}` func CallCmd( t *testing.T, diff --git a/cmd/flags/get.go b/cmd/flags/get.go index de18ba8d..49912574 100644 --- a/cmd/flags/get.go +++ b/cmd/flags/get.go @@ -10,6 +10,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" "ldcli/internal/flags" + "ldcli/internal/output" ) func NewGetCmd(client flags.Client) (*cobra.Command, error) { @@ -73,7 +74,15 @@ func runGet(client flags.Client) func(*cobra.Command, []string) error { return err } - fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + output, err := output.CmdOutput( + viper.GetString(cliflags.OutputFlag), + output.NewSingularOutputterFn(response), + ) + if err != nil { + return err + } + + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/flags/get_test.go b/cmd/flags/get_test.go index 636db908..11527792 100644 --- a/cmd/flags/get_test.go +++ b/cmd/flags/get_test.go @@ -25,7 +25,7 @@ func TestGet(t *testing.T) { client := flags.MockClient{} client. On("Get", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -33,6 +33,7 @@ func TestGet(t *testing.T) { "flags", "get", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "--flag", "test-key", "--project", "test-proj-key", "--environment", "test-env-key", @@ -41,7 +42,7 @@ func TestGet(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with valid flags from environment variables calls API", func(t *testing.T) { @@ -50,13 +51,14 @@ func TestGet(t *testing.T) { client := flags.MockClient{} client. On("Get", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } args := []string{ "flags", "get", "--flag", "test-key", + "--output", "json", "--project", "test-proj-key", "--environment", "test-env-key", } @@ -64,7 +66,7 @@ func TestGet(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { @@ -79,6 +81,7 @@ func TestGet(t *testing.T) { "flags", "get", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "--flag", "test-key", "--project", "test-proj-key", "--environment", "test-env-key", @@ -129,13 +132,14 @@ func TestGet(t *testing.T) { "base-uri", "environment", "flag", + "output", "project", }) client := flags.MockClient{} client. On("Get", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -144,6 +148,7 @@ func TestGet(t *testing.T) { "flags", "get", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "--flag", "test-key", "--project", "test-proj-key", "--environment", "test-env-key", From 7e3c9c6427dc33fdd05648746f1dafae0bbb02dd Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Tue, 23 Apr 2024 13:40:54 -0700 Subject: [PATCH 4/8] Show plaintext response for flags update --- cmd/flags/create_test.go | 12 ++++-------- cmd/flags/update.go | 11 ++++++++++- cmd/flags/update_test.go | 19 +++++++++++++------ 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/cmd/flags/create_test.go b/cmd/flags/create_test.go index a6c13295..5c6a8daa 100644 --- a/cmd/flags/create_test.go +++ b/cmd/flags/create_test.go @@ -21,16 +21,12 @@ func TestCreate(t *testing.T) { "test-key", "test-proj-key", } - stubbedSuccessResponse := `{ - "key": "test-key", - "name": "test-name" - }` t.Run("with valid flags calls API", func(t *testing.T) { client := flags.MockClient{} client. On("Create", mockArgs...). - Return([]byte(stubbedSuccessResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -46,7 +42,7 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, stubbedSuccessResponse, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with valid flags from environment variables calls API", func(t *testing.T) { @@ -55,7 +51,7 @@ func TestCreate(t *testing.T) { client := flags.MockClient{} client. On("Create", mockArgs...). - Return([]byte(stubbedSuccessResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -69,7 +65,7 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, stubbedSuccessResponse, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { diff --git a/cmd/flags/update.go b/cmd/flags/update.go index 21775144..ed3ed815 100644 --- a/cmd/flags/update.go +++ b/cmd/flags/update.go @@ -11,6 +11,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" "ldcli/internal/flags" + "ldcli/internal/output" ) func NewUpdateCmd(client flags.Client) (*cobra.Command, error) { @@ -144,7 +145,15 @@ func runUpdate(client flags.Client) func(*cobra.Command, []string) error { return err } - fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + output, err := output.CmdOutput( + viper.GetString(cliflags.OutputFlag), + output.NewSingularOutputterFn(response), + ) + if err != nil { + return err + } + + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/flags/update_test.go b/cmd/flags/update_test.go index c290f7e5..151ee854 100644 --- a/cmd/flags/update_test.go +++ b/cmd/flags/update_test.go @@ -32,7 +32,7 @@ func TestUpdate(t *testing.T) { client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -40,6 +40,7 @@ func TestUpdate(t *testing.T) { "flags", "update", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-d", `[{"op": "replace", "path": "/name", "value": "new-name"}]`, "--flag", "test-key", "--project", "test-proj-key", @@ -48,7 +49,7 @@ func TestUpdate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with valid flags from environment variables calls API", func(t *testing.T) { @@ -57,12 +58,13 @@ func TestUpdate(t *testing.T) { client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } args := []string{ "flags", "update", + "--output", "json", "-d", `[{"op": "replace", "path": "/name", "value": "new-name"}]`, "--flag", "test-key", "--project", "test-proj-key", @@ -71,7 +73,7 @@ func TestUpdate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { @@ -135,6 +137,7 @@ func TestUpdate(t *testing.T) { "base-uri", "data", "flag", + "output", "project", }) @@ -149,6 +152,7 @@ func TestUpdate(t *testing.T) { "flags", "update", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-d", `[{"op": "replace", "path": "/name", "value": "new-name"}]`, "--flag", "test-key", "--project", "test-proj-key", @@ -179,7 +183,7 @@ func TestToggle(t *testing.T) { client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -187,6 +191,7 @@ func TestToggle(t *testing.T) { "flags", "toggle-on", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "--flag", "test-flag-key", "--project", "test-proj-key", "--environment", "test-env-key", @@ -195,7 +200,7 @@ func TestToggle(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { @@ -260,6 +265,7 @@ func TestToggle(t *testing.T) { "base-uri", "environment", "flag", + "output", "project", }) @@ -274,6 +280,7 @@ func TestToggle(t *testing.T) { "flags", "toggle-on", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "--flag", "test-flag-key", "--project", "test-proj-key", "--environment", "test-env-key", From 3b3e2b9b4a7762ec83f3d651f38ba424632608aa Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Tue, 23 Apr 2024 13:43:26 -0700 Subject: [PATCH 5/8] Show plaintext response for members create --- cmd/members/create.go | 11 ++++++++++- cmd/members/create_test.go | 14 +++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/cmd/members/create.go b/cmd/members/create.go index f3d3406a..19f92823 100644 --- a/cmd/members/create.go +++ b/cmd/members/create.go @@ -11,6 +11,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" "ldcli/internal/members" + "ldcli/internal/output" ) func NewCreateCmd(client members.Client) (*cobra.Command, error) { @@ -54,7 +55,15 @@ func runCreate(client members.Client) func(*cobra.Command, []string) error { return err } - fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + output, err := output.CmdOutput( + viper.GetString(cliflags.OutputFlag), + output.NewSingularOutputterFn(response), + ) + if err != nil { + return err + } + + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/members/create_test.go b/cmd/members/create_test.go index 4244f716..64dc4522 100644 --- a/cmd/members/create_test.go +++ b/cmd/members/create_test.go @@ -25,7 +25,7 @@ func TestCreate(t *testing.T) { client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } @@ -34,6 +34,7 @@ func TestCreate(t *testing.T) { "create", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-d", `[{"email": "testemail@test.com", "role": "writer"}]`, } @@ -41,7 +42,7 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with valid flags from environment variables calls API", func(t *testing.T) { @@ -50,16 +51,17 @@ func TestCreate(t *testing.T) { client := members.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } args := []string{ "members", "create", + "--output", "json", "-d", `[{"email": "testemail@test.com", "role": "writer"}]`, } @@ -67,7 +69,7 @@ func TestCreate(t *testing.T) { output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { @@ -129,6 +131,7 @@ func TestCreate(t *testing.T) { "access-token", "base-uri", "data", + "output", }) client := members.MockClient{} @@ -143,6 +146,7 @@ func TestCreate(t *testing.T) { "create", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-d", `[{"email": "testemail@test.com", "role": "writer"}]`, } From 2dc7598e52776cbbc10cf58e5394ebc6ae637dd0 Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Tue, 23 Apr 2024 13:47:13 -0700 Subject: [PATCH 6/8] Show plaintext response for members invite --- cmd/members/invite.go | 11 ++++++- cmd/members/invite_test.go | 64 ++++++++++++++++++-------------------- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/cmd/members/invite.go b/cmd/members/invite.go index cc118e68..6190d4d7 100644 --- a/cmd/members/invite.go +++ b/cmd/members/invite.go @@ -10,6 +10,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" "ldcli/internal/members" + "ldcli/internal/output" ) func NewInviteCmd(client members.Client) (*cobra.Command, error) { @@ -63,7 +64,15 @@ func runInvite(client members.Client) func(*cobra.Command, []string) error { return err } - fmt.Fprintf(cmd.OutOrStdout(), string(response)+"\n") + output, err := output.CmdOutput( + viper.GetString(cliflags.OutputFlag), + output.NewSingularOutputterFn(response), + ) + if err != nil { + return err + } + + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") return nil } diff --git a/cmd/members/invite_test.go b/cmd/members/invite_test.go index 603ba3e1..85dac256 100644 --- a/cmd/members/invite_test.go +++ b/cmd/members/invite_test.go @@ -28,7 +28,7 @@ func TestInvite(t *testing.T) { client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } @@ -37,14 +37,14 @@ func TestInvite(t *testing.T) { "invite", "--access-token", "testAccessToken", "--base-uri", "http://test.com", - "-e", - `testemail1@test.com,testemail2@test.com`, + "--output", "json", + "-e", `testemail1@test.com,testemail2@test.com`, } output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with valid flags from environment variables calls API", func(t *testing.T) { @@ -53,24 +53,24 @@ func TestInvite(t *testing.T) { client := members.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } args := []string{ "members", "invite", - "-e", - `testemail1@test.com,testemail2@test.com`, + "--output", "json", + "-e", `testemail1@test.com,testemail2@test.com`, } output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with an error response is an error", func(t *testing.T) { @@ -86,8 +86,8 @@ func TestInvite(t *testing.T) { "invite", "--access-token", "testAccessToken", "--base-uri", "http://test.com", - "-e", - `testemail1@test.com,testemail2@test.com`, + "--output", "json", + "-e", `testemail1@test.com,testemail2@test.com`, } _, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) @@ -132,12 +132,13 @@ func TestInvite(t *testing.T) { "access-token", "base-uri", "emails", + "output", }) client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } @@ -146,6 +147,7 @@ func TestInvite(t *testing.T) { "invite", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-e", `testemail1@test.com,testemail2@test.com`, } @@ -170,54 +172,48 @@ func TestInviteWithOptionalRole(t *testing.T) { client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } args := []string{ "members", "invite", - "--access-token", - "testAccessToken", - "--base-uri", - "http://test.com", - "-e", - `testemail1@test.com,testemail2@test.com`, - "--role", - "writer", + "--access-token", "testAccessToken", + "--base-uri", "http://test.com", + "--output", "json", + "-e", `testemail1@test.com,testemail2@test.com`, + "--role", "writer", } output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("with valid optional short form flag calls members API", func(t *testing.T) { client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } args := []string{ "members", "invite", - "--access-token", - "testAccessToken", - "--base-uri", - "http://test.com", - "-e", - `testemail1@test.com,testemail2@test.com`, - "-r", - "writer", + "--access-token", "testAccessToken", + "--base-uri", "http://test.com", + "--output", "json", + "-e", `testemail1@test.com,testemail2@test.com`, + "-r", "writer", } output, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) require.NoError(t, err) - assert.JSONEq(t, `{"valid": true}`, string(output)) + assert.JSONEq(t, cmd.StubbedSuccessResponse, string(output)) }) t.Run("will track analytics for CLI Command Run event", func(t *testing.T) { @@ -228,13 +224,14 @@ func TestInviteWithOptionalRole(t *testing.T) { "access-token", "base-uri", "emails", + "output", "role", }) client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } @@ -242,6 +239,7 @@ func TestInviteWithOptionalRole(t *testing.T) { "members", "invite", "--access-token", "testAccessToken", "--base-uri", "http://test.com", + "--output", "json", "-e", `testemail1@test.com,testemail2@test.com`, "--role", "writer", } From 947cf1de8b55f0f0c0d0a1d2fe913f533ec57073 Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Tue, 23 Apr 2024 13:50:39 -0700 Subject: [PATCH 7/8] Replace stubbed response value --- cmd/cmdtest.go | 1 - cmd/environments/get_test.go | 2 +- cmd/flags/create_test.go | 2 +- cmd/flags/update_test.go | 4 ++-- cmd/members/create_test.go | 2 +- cmd/projects/create_test.go | 2 +- cmd/projects/list_test.go | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/cmd/cmdtest.go b/cmd/cmdtest.go index 624e2544..5c98b384 100644 --- a/cmd/cmdtest.go +++ b/cmd/cmdtest.go @@ -11,7 +11,6 @@ import ( "ldcli/internal/analytics" ) -var ValidResponse = `{"valid": true}` var StubbedSuccessResponse = `{ "key": "test-key", "name": "test-name" diff --git a/cmd/environments/get_test.go b/cmd/environments/get_test.go index 74cc9fb1..4c0a2dd3 100644 --- a/cmd/environments/get_test.go +++ b/cmd/environments/get_test.go @@ -178,7 +178,7 @@ func TestGet(t *testing.T) { client := environments.MockClient{} client. On("Get", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ EnvironmentsClient: &client, } diff --git a/cmd/flags/create_test.go b/cmd/flags/create_test.go index 5c6a8daa..9518b573 100644 --- a/cmd/flags/create_test.go +++ b/cmd/flags/create_test.go @@ -161,7 +161,7 @@ func TestCreate(t *testing.T) { client := flags.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } diff --git a/cmd/flags/update_test.go b/cmd/flags/update_test.go index 151ee854..14937239 100644 --- a/cmd/flags/update_test.go +++ b/cmd/flags/update_test.go @@ -144,7 +144,7 @@ func TestUpdate(t *testing.T) { client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } @@ -272,7 +272,7 @@ func TestToggle(t *testing.T) { client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ FlagsClient: &client, } diff --git a/cmd/members/create_test.go b/cmd/members/create_test.go index 64dc4522..0605eb46 100644 --- a/cmd/members/create_test.go +++ b/cmd/members/create_test.go @@ -137,7 +137,7 @@ func TestCreate(t *testing.T) { client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ MembersClient: &client, } diff --git a/cmd/projects/create_test.go b/cmd/projects/create_test.go index 6e20204a..0be76c9b 100644 --- a/cmd/projects/create_test.go +++ b/cmd/projects/create_test.go @@ -167,7 +167,7 @@ func TestCreate(t *testing.T) { client := projects.MockClient{} client. On("Create", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ ProjectsClient: &client, } diff --git a/cmd/projects/list_test.go b/cmd/projects/list_test.go index 652aad48..5b072adf 100644 --- a/cmd/projects/list_test.go +++ b/cmd/projects/list_test.go @@ -143,7 +143,7 @@ func TestList(t *testing.T) { client := projects.MockClient{} client. On("List", mockArgs...). - Return([]byte(cmd.ValidResponse), nil) + Return([]byte(cmd.StubbedSuccessResponse), nil) clients := cmd.APIClients{ ProjectsClient: &client, } From 5de73ec01239ca90f73715794c6ff5277d9642eb Mon Sep 17 00:00:00 2001 From: Danny Olson Date: Wed, 24 Apr 2024 14:31:46 -0700 Subject: [PATCH 8/8] feat: output flag config cmd (#202) * Show plaintext response for config --list * Use map for output type * Refactor types * Organization * Remove extra type * feat: output flag errors (#204) * Add plain text error handling * Output flag errors as plain text or JSON * Remove unused code * Update all commands to return plaintext or JSON * Refactor outputter * Backfill tests * Reorganize * Refactor * Remove comments * Renamed functions --- cmd/config/config.go | 12 ++- cmd/environments/get.go | 19 ++++- cmd/environments/get_test.go | 3 +- cmd/flags/create.go | 23 +++-- cmd/flags/create_test.go | 2 +- cmd/flags/get.go | 24 ++++-- cmd/flags/get_test.go | 3 +- cmd/flags/update.go | 24 ++++-- cmd/flags/update_test.go | 4 +- cmd/members/create.go | 21 +++-- cmd/members/create_test.go | 2 +- cmd/members/invite.go | 19 ++++- cmd/members/invite_test.go | 3 +- cmd/projects/create.go | 19 ++++- cmd/projects/create_test.go | 14 ++-- cmd/projects/list.go | 19 ++++- cmd/projects/list_test.go | 4 +- internal/output/multiple_outputter.go | 49 ----------- internal/output/multiple_outputter_test.go | 59 ------------- internal/output/output.go | 61 +++++++++----- internal/output/output_test.go | 98 ++++++++++++++++++++++ internal/output/outputters.go | 42 ++++++++++ internal/output/plaintext_fns.go | 56 +++++++++++++ internal/output/singular_outputter.go | 49 ----------- internal/output/singular_outputter_test.go | 41 --------- 25 files changed, 382 insertions(+), 288 deletions(-) delete mode 100644 internal/output/multiple_outputter.go delete mode 100644 internal/output/multiple_outputter_test.go create mode 100644 internal/output/output_test.go create mode 100644 internal/output/outputters.go create mode 100644 internal/output/plaintext_fns.go delete mode 100644 internal/output/singular_outputter.go delete mode 100644 internal/output/singular_outputter_test.go diff --git a/cmd/config/config.go b/cmd/config/config.go index f9779e62..831401d2 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -13,6 +13,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/internal/analytics" "ldcli/internal/config" + "ldcli/internal/output" ) const ( @@ -63,11 +64,16 @@ func run() func(*cobra.Command, []string) error { return err } - if string(configJSON) == "{}" { - return nil + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + configJSON, + output.ConfigPlaintextOutputFn, + ) + if err != nil { + return err } - fmt.Fprint(cmd.OutOrStdout(), string(configJSON)+"\n") + fmt.Fprintf(cmd.OutOrStdout(), output+"\n") case viper.GetBool(SetFlag): // flag needs two arguments: a key and value if len(args)%2 != 0 { diff --git a/cmd/environments/get.go b/cmd/environments/get.go index bc3366ef..e169adc4 100644 --- a/cmd/environments/get.go +++ b/cmd/environments/get.go @@ -10,6 +10,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" "ldcli/internal/environments" + "ldcli/internal/errors" "ldcli/internal/output" ) @@ -63,15 +64,25 @@ func runGet( viper.GetString(cliflags.ProjectFlag), ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) } - output, err := output.CmdOutput( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutputterFn(response), + response, + output.SingularPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/environments/get_test.go b/cmd/environments/get_test.go index 4c0a2dd3..7c7c4ce2 100644 --- a/cmd/environments/get_test.go +++ b/cmd/environments/get_test.go @@ -72,7 +72,7 @@ func TestGet(t *testing.T) { client := environments.MockClient{} client. On("Get", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ EnvironmentsClient: &client, } @@ -80,7 +80,6 @@ func TestGet(t *testing.T) { "environments", "get", "--access-token", "testAccessToken", "--base-uri", "http://test.com", - "--output", "json", "--environment", "test-env", "--project", "test-proj", } diff --git a/cmd/flags/create.go b/cmd/flags/create.go index 18e54874..e9180e1d 100644 --- a/cmd/flags/create.go +++ b/cmd/flags/create.go @@ -10,6 +10,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/flags" "ldcli/internal/output" ) @@ -53,10 +54,6 @@ type inputData struct { func runCreate(client flags.Client) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { - // rebind flags used in other subcommands - _ = viper.BindPFlag(cliflags.DataFlag, cmd.Flags().Lookup(cliflags.DataFlag)) - _ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag)) - var data inputData err := json.Unmarshal([]byte(viper.GetString(cliflags.DataFlag)), &data) if err != nil { @@ -72,15 +69,25 @@ func runCreate(client flags.Client) func(*cobra.Command, []string) error { viper.GetString(cliflags.ProjectFlag), ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) } - output, err := output.CmdOutput( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutputterFn(response), + response, + output.SingularPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/flags/create_test.go b/cmd/flags/create_test.go index 9518b573..799ee4aa 100644 --- a/cmd/flags/create_test.go +++ b/cmd/flags/create_test.go @@ -72,7 +72,7 @@ func TestCreate(t *testing.T) { client := flags.MockClient{} client. On("Create", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ FlagsClient: &client, } diff --git a/cmd/flags/get.go b/cmd/flags/get.go index 49912574..77912d51 100644 --- a/cmd/flags/get.go +++ b/cmd/flags/get.go @@ -9,6 +9,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/flags" "ldcli/internal/output" ) @@ -57,11 +58,6 @@ func NewGetCmd(client flags.Client) (*cobra.Command, error) { func runGet(client flags.Client) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { - // rebind flags used in other subcommands - _ = viper.BindPFlag(cliflags.FlagFlag, cmd.Flags().Lookup(cliflags.FlagFlag)) - _ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag)) - _ = viper.BindPFlag(cliflags.EnvironmentFlag, cmd.Flags().Lookup(cliflags.EnvironmentFlag)) - response, err := client.Get( context.Background(), viper.GetString(cliflags.AccessTokenFlag), @@ -71,15 +67,25 @@ func runGet(client flags.Client) func(*cobra.Command, []string) error { viper.GetString(cliflags.EnvironmentFlag), ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) } - output, err := output.CmdOutput( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutputterFn(response), + response, + output.SingularPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/flags/get_test.go b/cmd/flags/get_test.go index 11527792..ee65f641 100644 --- a/cmd/flags/get_test.go +++ b/cmd/flags/get_test.go @@ -73,7 +73,7 @@ func TestGet(t *testing.T) { client := flags.MockClient{} client. On("Get", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ FlagsClient: &client, } @@ -81,7 +81,6 @@ func TestGet(t *testing.T) { "flags", "get", "--access-token", "testAccessToken", "--base-uri", "http://test.com", - "--output", "json", "--flag", "test-key", "--project", "test-proj-key", "--environment", "test-env-key", diff --git a/cmd/flags/update.go b/cmd/flags/update.go index ed3ed815..f2275cfe 100644 --- a/cmd/flags/update.go +++ b/cmd/flags/update.go @@ -10,6 +10,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/flags" "ldcli/internal/output" ) @@ -116,11 +117,6 @@ func setToggleCommandFlags(cmd *cobra.Command) (*cobra.Command, error) { func runUpdate(client flags.Client) func(*cobra.Command, []string) error { return func(cmd *cobra.Command, args []string) error { - // rebind flags used in other subcommands - _ = viper.BindPFlag(cliflags.DataFlag, cmd.Flags().Lookup(cliflags.DataFlag)) - _ = viper.BindPFlag(cliflags.ProjectFlag, cmd.Flags().Lookup(cliflags.ProjectFlag)) - _ = viper.BindPFlag(cliflags.FlagFlag, cmd.Flags().Lookup(cliflags.FlagFlag)) - var patch []flags.UpdateInput if cmd.CalledAs() == "toggle-on" || cmd.CalledAs() == "toggle-off" { _ = viper.BindPFlag(cliflags.EnvironmentFlag, cmd.Flags().Lookup(cliflags.EnvironmentFlag)) @@ -142,15 +138,25 @@ func runUpdate(client flags.Client) func(*cobra.Command, []string) error { patch, ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) } - output, err := output.CmdOutput( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutputterFn(response), + response, + output.SingularPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/flags/update_test.go b/cmd/flags/update_test.go index 14937239..70a1fde3 100644 --- a/cmd/flags/update_test.go +++ b/cmd/flags/update_test.go @@ -80,7 +80,7 @@ func TestUpdate(t *testing.T) { client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ FlagsClient: &client, } @@ -207,7 +207,7 @@ func TestToggle(t *testing.T) { client := flags.MockClient{} client. On("Update", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ FlagsClient: &client, } diff --git a/cmd/members/create.go b/cmd/members/create.go index 19f92823..154c06d8 100644 --- a/cmd/members/create.go +++ b/cmd/members/create.go @@ -10,6 +10,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/members" "ldcli/internal/output" ) @@ -42,7 +43,7 @@ func runCreate(client members.Client) func(*cobra.Command, []string) error { // TODO: why does viper.GetString(cliflags.DataFlag) not work? err := json.Unmarshal([]byte(cmd.Flags().Lookup(cliflags.DataFlag).Value.String()), &data) if err != nil { - return err + return errors.NewError(err.Error()) } response, err := client.Create( @@ -52,15 +53,25 @@ func runCreate(client members.Client) func(*cobra.Command, []string) error { data, ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) } - output, err := output.CmdOutput( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutputterFn(response), + response, + output.SingularPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/members/create_test.go b/cmd/members/create_test.go index 0605eb46..feb2efcb 100644 --- a/cmd/members/create_test.go +++ b/cmd/members/create_test.go @@ -76,7 +76,7 @@ func TestCreate(t *testing.T) { client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ MembersClient: &client, } diff --git a/cmd/members/invite.go b/cmd/members/invite.go index 6190d4d7..450d6412 100644 --- a/cmd/members/invite.go +++ b/cmd/members/invite.go @@ -9,6 +9,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/members" "ldcli/internal/output" ) @@ -61,15 +62,25 @@ func runInvite(client members.Client) func(*cobra.Command, []string) error { memberInputs, ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) } - output, err := output.CmdOutput( + output, err := output.CmdOutputMultiple( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutputterFn(response), + response, + output.MultipleEmailPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/members/invite_test.go b/cmd/members/invite_test.go index 85dac256..c98197d4 100644 --- a/cmd/members/invite_test.go +++ b/cmd/members/invite_test.go @@ -77,7 +77,7 @@ func TestInvite(t *testing.T) { client := members.MockClient{} client. On("Create", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ MembersClient: &client, } @@ -86,7 +86,6 @@ func TestInvite(t *testing.T) { "invite", "--access-token", "testAccessToken", "--base-uri", "http://test.com", - "--output", "json", "-e", `testemail1@test.com,testemail2@test.com`, } diff --git a/cmd/projects/create.go b/cmd/projects/create.go index 90e7ea3a..408984fa 100644 --- a/cmd/projects/create.go +++ b/cmd/projects/create.go @@ -10,6 +10,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/output" "ldcli/internal/projects" ) @@ -58,15 +59,25 @@ func runCreate(client projects.Client) func(*cobra.Command, []string) error { data.Key, ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) } - output, err := output.CmdOutput( + output, err := output.CmdOutputSingular( viper.GetString(cliflags.OutputFlag), - output.NewSingularOutputterFn(response), + response, + output.SingularPlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/projects/create_test.go b/cmd/projects/create_test.go index 0be76c9b..06cdeb10 100644 --- a/cmd/projects/create_test.go +++ b/cmd/projects/create_test.go @@ -77,19 +77,15 @@ func TestCreate(t *testing.T) { client := projects.MockClient{} client. On("Create", mockArgs...). - Return([]byte(`{}`), errors.NewError("An error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ ProjectsClient: &client, } args := []string{ - "projects", - "create", - "--access-token", - "testAccessToken", - "--base-uri", - "http://test.com", - "-d", - `{"key": "test-key", "name": "test-name"}`, + "projects", "create", + "--access-token", "testAccessToken", + "--base-uri", "http://test.com", + "-d", `{"key": "test-key", "name": "test-name"}`, } _, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) diff --git a/cmd/projects/list.go b/cmd/projects/list.go index aa0dac20..4a038995 100644 --- a/cmd/projects/list.go +++ b/cmd/projects/list.go @@ -9,6 +9,7 @@ import ( "ldcli/cmd/cliflags" "ldcli/cmd/validators" + "ldcli/internal/errors" "ldcli/internal/output" "ldcli/internal/projects" ) @@ -33,15 +34,25 @@ func runList(client projects.Client) func(*cobra.Command, []string) error { viper.GetString(cliflags.BaseURIFlag), ) if err != nil { - return err + output, err := output.CmdOutputSingular( + viper.GetString(cliflags.OutputFlag), + []byte(err.Error()), + output.ErrorPlaintextOutputFn, + ) + if err != nil { + return errors.NewError(err.Error()) + } + + return errors.NewError(output) } - output, err := output.CmdOutput( + output, err := output.CmdOutputMultiple( viper.GetString(cliflags.OutputFlag), - output.NewMultipleOutputterFn(response), + response, + output.MultiplePlaintextOutputFn, ) if err != nil { - return err + return errors.NewError(err.Error()) } fmt.Fprintf(cmd.OutOrStdout(), output+"\n") diff --git a/cmd/projects/list_test.go b/cmd/projects/list_test.go index 5b072adf..d0ec5381 100644 --- a/cmd/projects/list_test.go +++ b/cmd/projects/list_test.go @@ -74,7 +74,7 @@ func TestList(t *testing.T) { client := projects.MockClient{} client. On("List", mockArgs...). - Return([]byte(`{}`), errors.NewError("an error")) + Return([]byte(`{}`), errors.NewError(`{"message": "An error"}`)) clients := cmd.APIClients{ ProjectsClient: &client, } @@ -86,7 +86,7 @@ func TestList(t *testing.T) { _, err := cmd.CallCmd(t, clients, &analytics.NoopClient{}, args) - require.EqualError(t, err, "an error") + require.EqualError(t, err, "An error") }) t.Run("with missing required flags is an error", func(t *testing.T) { diff --git a/internal/output/multiple_outputter.go b/internal/output/multiple_outputter.go deleted file mode 100644 index 97e24505..00000000 --- a/internal/output/multiple_outputter.go +++ /dev/null @@ -1,49 +0,0 @@ -package output - -import ( - "encoding/json" - "fmt" -) - -var multiplePlaintextOutputFn = func(r resource) string { - return fmt.Sprintf("* %s (%s)", r.Name, r.Key) -} - -// TODO: rename this to be "cleaner"? -- NewMultipleOutput() -func NewMultipleOutputterFn(input []byte) multipleOutputterFn { - return multipleOutputterFn{ - input: input, - } -} - -type multipleOutputterFn struct { - input []byte -} - -func (o multipleOutputterFn) New() (Outputter, error) { - var r resources - err := json.Unmarshal(o.input, &r) - if err != nil { - return MultipleOutputter{}, err - } - - return MultipleOutputter{ - outputFn: multiplePlaintextOutputFn, - resources: r, - resourceJSON: o.input, - }, nil -} - -type MultipleOutputter struct { - outputFn PlaintextOutputFn - resources resources - resourceJSON []byte -} - -func (o MultipleOutputter) JSON() string { - return string(o.resourceJSON) -} - -func (o MultipleOutputter) String() string { - return formatColl(o.resources.Items, o.outputFn) -} diff --git a/internal/output/multiple_outputter_test.go b/internal/output/multiple_outputter_test.go deleted file mode 100644 index b22f449e..00000000 --- a/internal/output/multiple_outputter_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package output_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "ldcli/internal/output" -) - -func TestMultipleOutputter_JSON(t *testing.T) { - input := []byte(`{ - "items": [ - { - "key": "test-key1", - "name": "test-name1", - "other": "another-value2" - }, - { - "key": "test-key2", - "name": "test-name2", - "other": "another-value2" - } - ] - }`) - output, err := output.CmdOutput( - "json", - output.NewMultipleOutputterFn(input), - ) - - require.NoError(t, err) - assert.JSONEq(t, output, string(input)) -} - -func TestMultipleOutputter_String(t *testing.T) { - input := []byte(`{ - "items": [ - { - "key": "test-key1", - "name": "test-name1", - "other": "another-value2" - }, - { - "key": "test-key2", - "name": "test-name2", - "other": "another-value2" - } - ] - }`) - expected := "* test-name1 (test-key1)\n* test-name2 (test-key2)" - output, err := output.CmdOutput( - "plaintext", - output.NewMultipleOutputterFn(input), - ) - - require.NoError(t, err) - assert.Equal(t, expected, output) -} diff --git a/internal/output/output.go b/internal/output/output.go index eaea59e4..4d9e188e 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -1,7 +1,7 @@ package output import ( - "strings" + "encoding/json" "ldcli/internal/errors" ) @@ -27,10 +27,8 @@ type PlaintextOutputFn func(resource) string // resource is the subset of data we need to display a command's plain text response for a single // resource. -type resource struct { - Key string `json:"key"` - Name string `json:"name"` -} +// We're trading off type safety for easy of use instead of defining a type for each expected resource. +type resource map[string]interface{} // resources is the subset of data we need to display a command's plain text response for a list // of resources. @@ -38,13 +36,49 @@ type resources struct { Items []resource `json:"items"` } -// CmdOutput returns a command's response as a string formatted based on the user's requested type. -func CmdOutput(outputKind string, outputter OutputterFn) (string, error) { - o, err := outputter.New() +// resourcesBare is for responses that return a list of resources at the top level of the response, +// not as a value of an "items" property. +type resourcesBare []resource + +// CmdOutputSingular builds a command response based on the flag the user provided and the shape of +// the input. The expected shape is a single JSON object. +func CmdOutputSingular(outputKind string, input []byte, fn PlaintextOutputFn) (string, error) { + var r resource + err := json.Unmarshal(input, &r) if err != nil { return "", err } + return outputFromKind(outputKind, SingularOutputter{ + outputFn: fn, + resource: r, + resourceJSON: input, + }) +} + +// CmdOutputMultiple builds a command response based on the flag the user provided and the shape of +// the input. The expected shape is a list of JSON objects. +func CmdOutputMultiple(outputKind string, input []byte, fn PlaintextOutputFn) (string, error) { + var r resources + err := json.Unmarshal(input, &r) + if err != nil { + // sometimes a response doesn't include each item in an "items" property + var rr resourcesBare + err := json.Unmarshal(input, &rr) + if err != nil { + return "", err + } + r.Items = rr + } + + return outputFromKind(outputKind, MultipleOutputter{ + outputFn: fn, + resources: r, + resourceJSON: input, + }) +} + +func outputFromKind(outputKind string, o Outputter) (string, error) { switch outputKind { case "json": return o.JSON(), nil @@ -54,14 +88,3 @@ func CmdOutput(outputKind string, outputter OutputterFn) (string, error) { return "", ErrInvalidOutputKind } - -// FormatColl applies a formatting function to every element in the collection and returns it as a -// string. -func formatColl[T any](coll []T, formatFn func(T) string) string { - lst := make([]string, 0, len(coll)) - for _, c := range coll { - lst = append(lst, formatFn(c)) - } - - return strings.Join(lst, "\n") -} diff --git a/internal/output/output_test.go b/internal/output/output_test.go new file mode 100644 index 00000000..5a16069f --- /dev/null +++ b/internal/output/output_test.go @@ -0,0 +1,98 @@ +package output_test + +import ( + "ldcli/internal/output" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCmdOutputResource(t *testing.T) { + tests := map[string]struct { + expected string + fn output.PlaintextOutputFn + input string + }{ + "with config file data": { + expected: "key: value\nkey2: value2", + fn: output.ConfigPlaintextOutputFn, + input: `{"key": "value", "key2": "value2"}`, + }, + "with an error with a code and message": { + expected: "test-message (code: test-code)", + fn: output.ErrorPlaintextOutputFn, + input: `{"code": "test-code", "message": "test-message"}`, + }, + "with an error with only a code": { + expected: "an error occurred (code: test-code)", + fn: output.ErrorPlaintextOutputFn, + input: `{"code": "test-code", "message": ""}`, + }, + "with an error with only a message": { + expected: "test-message", + fn: output.ErrorPlaintextOutputFn, + input: `{"message": "test-message"}`, + }, + "with an error without a code or message": { + expected: "unknown error occurred", + fn: output.ErrorPlaintextOutputFn, + input: `{"message": ""}`, + }, + "with an error without a response body": { + expected: "unknown error occurred", + fn: output.ErrorPlaintextOutputFn, + input: `{}`, + }, + "with a singular resource": { + expected: "test-name (test-key)", + fn: output.SingularPlaintextOutputFn, + input: `{"key": "test-key", "name": "test-name"}`, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + output, err := output.CmdOutputSingular( + "plaintext", + []byte(tt.input), + tt.fn, + ) + + require.NoError(t, err) + assert.Equal(t, tt.expected, output) + }) + } +} + +func TestCmdOutputResources(t *testing.T) { + tests := map[string]struct { + expected string + fn output.PlaintextOutputFn + input string + }{ + "with multiple emails not as items property": { + expected: "* test-email1 (test-id1)\n* test-email2 (test-id2)", + fn: output.MultipleEmailPlaintextOutputFn, + input: `[{"_id": "test-id1", "email": "test-email1"}, {"_id": "test-id2", "email": "test-email2"}]`, + }, + "with multiple items": { + expected: "* test-name1 (test-key1)\n* test-name2 (test-key2)", + fn: output.MultiplePlaintextOutputFn, + input: `{"items": [{"key": "test-key1", "name": "test-name1"}, {"key": "test-key2", "name": "test-name2"}]}`, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + output, err := output.CmdOutputMultiple( + "plaintext", + []byte(tt.input), + tt.fn, + ) + + require.NoError(t, err) + assert.Equal(t, tt.expected, output) + }) + } +} diff --git a/internal/output/outputters.go b/internal/output/outputters.go new file mode 100644 index 00000000..a45e0467 --- /dev/null +++ b/internal/output/outputters.go @@ -0,0 +1,42 @@ +package output + +import "strings" + +type MultipleOutputter struct { + outputFn PlaintextOutputFn + resources resources + resourceJSON []byte +} + +func (o MultipleOutputter) JSON() string { + return string(o.resourceJSON) +} + +func (o MultipleOutputter) String() string { + return formatColl(o.resources.Items, o.outputFn) +} + +type SingularOutputter struct { + outputFn PlaintextOutputFn + resource resource + resourceJSON []byte +} + +func (o SingularOutputter) JSON() string { + return string(o.resourceJSON) +} + +func (o SingularOutputter) String() string { + return formatColl([]resource{o.resource}, o.outputFn) +} + +// formatColl applies a formatting function to every element in the collection and returns it as a +// string. +func formatColl[T any](coll []T, formatFn func(T) string) string { + lst := make([]string, 0, len(coll)) + for _, c := range coll { + lst = append(lst, formatFn(c)) + } + + return strings.Join(lst, "\n") +} diff --git a/internal/output/plaintext_fns.go b/internal/output/plaintext_fns.go new file mode 100644 index 00000000..af1c8232 --- /dev/null +++ b/internal/output/plaintext_fns.go @@ -0,0 +1,56 @@ +package output + +import ( + "fmt" + "sort" + "strings" +) + +// ConfigPlaintextOutputFn converts the resource to plain text specifically for data from the +// config file. +var ConfigPlaintextOutputFn = func(r resource) string { + keys := make([]string, 0) + for k := range r { + keys = append(keys, k) + } + sort.Strings(keys) + + lst := make([]string, 0) + for _, k := range keys { + lst = append(lst, fmt.Sprintf("%s: %s", k, r[k])) + } + + return strings.Join(lst, "\n") +} + +// ErrorPlaintextOutputFn converts the resource to plain text specifically for data from the +// error file. +// An error response could have a code and message or just a message. It's also possible that +// there isn't either property. +var ErrorPlaintextOutputFn = func(r resource) string { + switch { + case r["code"] == nil && (r["message"] == "" || r["message"] == nil): + return "unknown error occurred" + case r["code"] == nil: + return r["message"].(string) + case r["message"] == "": + return fmt.Sprintf("an error occurred (code: %s)", r["code"]) + default: + return fmt.Sprintf("%s (code: %s)", r["message"], r["code"]) + } +} + +// MultipleEmailPlaintextOutputFn converts the resource to plain text specifically for member data. +var MultipleEmailPlaintextOutputFn = func(r resource) string { + return fmt.Sprintf("* %s (%s)", r["email"], r["_id"]) +} + +// MultiplePlaintextOutputFn converts the resource to plain text based on its name and key in a list. +var MultiplePlaintextOutputFn = func(r resource) string { + return fmt.Sprintf("* %s (%s)", r["name"], r["key"]) +} + +// SingularPlaintextOutputFn converts the resource to plain text based on its name and key. +var SingularPlaintextOutputFn = func(r resource) string { + return fmt.Sprintf("%s (%s)", r["name"], r["key"]) +} diff --git a/internal/output/singular_outputter.go b/internal/output/singular_outputter.go deleted file mode 100644 index 4c953c55..00000000 --- a/internal/output/singular_outputter.go +++ /dev/null @@ -1,49 +0,0 @@ -package output - -import ( - "encoding/json" - "fmt" -) - -var singularPlaintextOutputFn = func(r resource) string { - return fmt.Sprintf("%s (%s)", r.Name, r.Key) -} - -// TODO: rename this to be "cleaner"? -- NewSingularOutput() -func NewSingularOutputterFn(input []byte) singularOutputterFn { - return singularOutputterFn{ - input: input, - } -} - -type singularOutputterFn struct { - input []byte -} - -func (o singularOutputterFn) New() (Outputter, error) { - var r resource - err := json.Unmarshal(o.input, &r) - if err != nil { - return SingularOutputter{}, err - } - - return SingularOutputter{ - outputFn: singularPlaintextOutputFn, - resource: r, - resourceJSON: o.input, - }, nil -} - -type SingularOutputter struct { - outputFn PlaintextOutputFn - resource resource - resourceJSON []byte -} - -func (o SingularOutputter) JSON() string { - return string(o.resourceJSON) -} - -func (o SingularOutputter) String() string { - return formatColl([]resource{o.resource}, o.outputFn) -} diff --git a/internal/output/singular_outputter_test.go b/internal/output/singular_outputter_test.go deleted file mode 100644 index 81be51a1..00000000 --- a/internal/output/singular_outputter_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package output_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "ldcli/internal/output" -) - -func TestSingularOutputter_JSON(t *testing.T) { - input := []byte(`{ - "key": "test-key", - "name": "test-name", - "other": "another-value" - }`) - output, err := output.CmdOutput( - "json", - output.NewSingularOutputterFn(input), - ) - - require.NoError(t, err) - assert.JSONEq(t, output, string(input)) -} - -func TestSingularOutputter_String(t *testing.T) { - input := []byte(`{ - "key": "test-key", - "name": "test-name", - "other": "another-value" - }`) - expected := "test-name (test-key)" - output, err := output.CmdOutput( - "plaintext", - output.NewSingularOutputterFn(input), - ) - - require.NoError(t, err) - assert.Equal(t, expected, output) -}