Skip to content

Commit 887c614

Browse files
committed
feat(REL-15756) updating agent friendly and improved rich text output
1 parent 39fa0f9 commit 887c614

File tree

14 files changed

+1208
-27
lines changed

14 files changed

+1208
-27
lines changed

cmd/flags/archive.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ func makeArchiveRequest(client resources.Client) func(*cobra.Command, []string)
5151
return output.NewCmdOutputError(err, cliflags.GetOutputKind(cmd))
5252
}
5353

54-
output, err := output.CmdOutput("update", cliflags.GetOutputKind(cmd), res, cliflags.GetFields(cmd))
54+
output, err := output.CmdOutput("update", cliflags.GetOutputKind(cmd), res, cliflags.GetFields(cmd), output.CmdOutputOpts{
55+
ResourceName: "flags",
56+
})
5557
if err != nil {
5658
return errors.NewError(err.Error())
5759
}

cmd/flags/archive_test.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ func TestArchive(t *testing.T) {
3939

4040
require.NoError(t, err)
4141
assert.Equal(t, `[{"op": "replace", "path": "/archived", "value": true}]`, string(mockClient.Input))
42-
assert.Equal(t, "Successfully updated test flag (test-flag)\n", string(output))
42+
assert.Contains(t, string(output), "Successfully updated\n\nKey:")
43+
assert.Contains(t, string(output), "test-flag")
44+
assert.Contains(t, string(output), "Name:")
45+
assert.Contains(t, string(output), "test flag")
46+
assert.NotContains(t, string(output), "* ")
4347
})
4448

4549
t.Run("succeeds with JSON output", func(t *testing.T) {
@@ -146,7 +150,9 @@ func TestArchive(t *testing.T) {
146150
)
147151

148152
require.NoError(t, err)
149-
assert.Equal(t, "Successfully updated test flag (test-flag)\n", string(output))
153+
assert.Contains(t, string(output), "Successfully updated")
154+
assert.Contains(t, string(output), "Key:")
155+
assert.Contains(t, string(output), "test-flag")
150156
})
151157

152158
t.Run("returns error with missing flags", func(t *testing.T) {

cmd/flags/toggle.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ func runE(client resources.Client) func(*cobra.Command, []string) error {
7373
return output.NewCmdOutputError(err, cliflags.GetOutputKind(cmd))
7474
}
7575

76-
output, err := output.CmdOutput("update", cliflags.GetOutputKind(cmd), res, cliflags.GetFields(cmd))
76+
output, err := output.CmdOutput("update", cliflags.GetOutputKind(cmd), res, cliflags.GetFields(cmd), output.CmdOutputOpts{
77+
ResourceName: "flags",
78+
})
7779
if err != nil {
7880
return errors.NewError(err.Error())
7981
}

cmd/flags/toggle_test.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ func TestToggleOn(t *testing.T) {
4040

4141
require.NoError(t, err)
4242
assert.Equal(t, `[{"op": "replace", "path": "/environments/test-env/on", "value": true}]`, string(mockClient.Input))
43-
assert.Equal(t, "Successfully updated test flag (test-flag)\n", string(output))
43+
assert.Contains(t, string(output), "Successfully updated\n\nKey:")
44+
assert.Contains(t, string(output), "test-flag")
45+
assert.Contains(t, string(output), "Name:")
46+
assert.Contains(t, string(output), "test flag")
47+
assert.NotContains(t, string(output), "* ")
4448
})
4549

4650
t.Run("succeeds with JSON output", func(t *testing.T) {
@@ -152,7 +156,9 @@ func TestToggleOn(t *testing.T) {
152156
)
153157

154158
require.NoError(t, err)
155-
assert.Equal(t, "Successfully updated test flag (test-flag)\n", string(output))
159+
assert.Contains(t, string(output), "Successfully updated")
160+
assert.Contains(t, string(output), "Key:")
161+
assert.Contains(t, string(output), "test-flag")
156162
})
157163

158164
t.Run("returns error with missing required flags", func(t *testing.T) {
@@ -205,7 +211,11 @@ func TestToggleOff(t *testing.T) {
205211

206212
require.NoError(t, err)
207213
assert.Equal(t, `[{"op": "replace", "path": "/environments/test-env/on", "value": false}]`, string(mockClient.Input))
208-
assert.Equal(t, "Successfully updated test flag (test-flag)\n", string(output))
214+
assert.Contains(t, string(output), "Successfully updated\n\nKey:")
215+
assert.Contains(t, string(output), "test-flag")
216+
assert.Contains(t, string(output), "Name:")
217+
assert.Contains(t, string(output), "test flag")
218+
assert.NotContains(t, string(output), "* ")
209219
})
210220

211221
t.Run("succeeds with JSON output", func(t *testing.T) {
@@ -317,7 +327,9 @@ func TestToggleOff(t *testing.T) {
317327
)
318328

319329
require.NoError(t, err)
320-
assert.Equal(t, "Successfully updated test flag (test-flag)\n", string(output))
330+
assert.Contains(t, string(output), "Successfully updated")
331+
assert.Contains(t, string(output), "Key:")
332+
assert.Contains(t, string(output), "test-flag")
321333
})
322334

323335
t.Run("returns error with missing required flags", func(t *testing.T) {

cmd/members/invite.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ func runE(client resources.Client) func(*cobra.Command, []string) error {
6363
return output.NewCmdOutputError(err, cliflags.GetOutputKind(cmd))
6464
}
6565

66-
output, err := output.CmdOutput("update", cliflags.GetOutputKind(cmd), res, cliflags.GetFields(cmd))
66+
output, err := output.CmdOutput("update", cliflags.GetOutputKind(cmd), res, cliflags.GetFields(cmd), output.CmdOutputOpts{
67+
ResourceName: "members",
68+
})
6769
if err != nil {
6870
return errors.NewError(err.Error())
6971
}

cmd/members/invite_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,11 @@ func TestInvite(t *testing.T) {
4545

4646
require.NoError(t, err)
4747
assert.Equal(t, `[{"email":"test1@test.com","role":"writer"},{"email":"test2@test.com","role":"writer"}]`, string(mockClient.Input))
48-
assert.Equal(t, "Successfully updated\n* test1@test.com (000000000000000000000001)\n* test2@test.com (000000000000000000000002)\n", string(output))
48+
assert.Contains(t, string(output), "Successfully updated")
49+
assert.Contains(t, string(output), "EMAIL")
50+
assert.Contains(t, string(output), "ROLE")
51+
assert.Contains(t, string(output), "test1@test.com")
52+
assert.Contains(t, string(output), "test2@test.com")
53+
assert.Contains(t, string(output), "writer")
54+
assert.NotContains(t, string(output), "* ")
4955
}

cmd/resources/resources.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,9 @@ func (op *OperationCmd) makeRequest(cmd *cobra.Command, args []string) error {
349349
res = []byte(fmt.Sprintf(`{"key": %q}`, urlParms[len(urlParms)-1]))
350350
}
351351

352-
output, err := output.CmdOutput(cmd.Use, cliflags.GetOutputKind(cmd), res, cliflags.GetFields(cmd))
352+
output, err := output.CmdOutput(cmd.Use, cliflags.GetOutputKind(cmd), res, cliflags.GetFields(cmd), output.CmdOutputOpts{
353+
ResourceName: cmd.Parent().Name(),
354+
})
353355
if err != nil {
354356
return errors.NewError(err.Error())
355357
}

cmd/root_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ func TestOutputFlags(t *testing.T) {
101101
)
102102

103103
require.NoError(t, err)
104-
assert.Contains(t, string(output), "Successfully updated")
105-
assert.Contains(t, string(output), "test-name (test-key)")
104+
assert.Contains(t, string(output), "Successfully updated\n\nKey:")
105+
assert.Contains(t, string(output), "test-key")
106106
})
107107
}
108108

@@ -205,7 +205,8 @@ func TestTTYDefaultOutput(t *testing.T) {
205205

206206
out := execNonTTYCmd(t, mockClient)
207207
assert.Contains(t, string(out), "Successfully updated")
208-
assert.Contains(t, string(out), "test-name (test-key)")
208+
assert.Contains(t, string(out), "Key:")
209+
assert.Contains(t, string(out), "test-key")
209210
})
210211

211212
t.Run("FORCE_TTY overrides non-TTY detection", func(t *testing.T) {

internal/output/plaintext_fns.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,19 @@ var SingularPlaintextOutputFn = func(r resource) string {
6262

6363
switch {
6464
case name != nil && key != nil:
65-
return fmt.Sprintf("%s (%s)", name.(string), key.(string))
65+
return fmt.Sprintf("%s (%s)", fmt.Sprint(name), fmt.Sprint(key))
6666
case email != nil && id != nil:
67-
return fmt.Sprintf("%s (%s)", email.(string), id.(string))
67+
return fmt.Sprintf("%s (%s)", fmt.Sprint(email), fmt.Sprint(id))
6868
case name != nil && id != nil:
69-
return fmt.Sprintf("%s (%s)", name.(string), id.(string))
69+
return fmt.Sprintf("%s (%s)", fmt.Sprint(name), fmt.Sprint(id))
7070
case key != nil:
71-
return key.(string)
71+
return fmt.Sprint(key)
7272
case email != nil:
73-
return email.(string)
73+
return fmt.Sprint(email)
7474
case id != nil:
75-
return id.(string)
75+
return fmt.Sprint(id)
7676
case name != nil:
77-
return name.(string)
77+
return fmt.Sprint(name)
7878
default:
7979
return "cannot read resource"
8080
}

internal/output/plaintext_fns_internal_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,54 @@ import (
77
)
88

99
func TestErrorPlaintextOutputFn(t *testing.T) {
10+
t.Run("with code and message", func(t *testing.T) {
11+
r := resource{"code": "conflict", "message": "resource already exists"}
12+
13+
result := ErrorPlaintextOutputFn(r)
14+
15+
assert.Equal(t, "resource already exists (code: conflict)", result)
16+
})
17+
18+
t.Run("with only a message", func(t *testing.T) {
19+
r := resource{"message": "something went wrong"}
20+
21+
result := ErrorPlaintextOutputFn(r)
22+
23+
assert.Equal(t, "something went wrong", result)
24+
})
25+
26+
t.Run("with only a code", func(t *testing.T) {
27+
r := resource{"code": "not_found", "message": ""}
28+
29+
result := ErrorPlaintextOutputFn(r)
30+
31+
assert.Equal(t, "an error occurred (code: not_found)", result)
32+
})
33+
34+
t.Run("with neither code nor message", func(t *testing.T) {
35+
r := resource{}
36+
37+
result := ErrorPlaintextOutputFn(r)
38+
39+
assert.Equal(t, "unknown error occurred", result)
40+
})
41+
42+
t.Run("with empty message and nil code", func(t *testing.T) {
43+
r := resource{"message": ""}
44+
45+
result := ErrorPlaintextOutputFn(r)
46+
47+
assert.Equal(t, "unknown error occurred", result)
48+
})
49+
50+
t.Run("with suggestion appends suggestion line", func(t *testing.T) {
51+
r := resource{"code": "not_found", "message": "Not Found", "suggestion": "Try ldcli flags list."}
52+
53+
result := ErrorPlaintextOutputFn(r)
54+
55+
assert.Equal(t, "Not Found (code: not_found)\nSuggestion: Try ldcli flags list.", result)
56+
})
57+
1058
t.Run("with a non-string message does not panic", func(t *testing.T) {
1159
r := resource{"message": float64(404)}
1260

@@ -32,6 +80,91 @@ func TestErrorPlaintextOutputFn(t *testing.T) {
3280
})
3381
}
3482

83+
func TestMultiplePlaintextOutputFn(t *testing.T) {
84+
tests := map[string]struct {
85+
resource resource
86+
expected string
87+
}{
88+
"with a name and key": {
89+
resource: resource{
90+
"key": "test-key",
91+
"name": "test-name",
92+
},
93+
expected: "* test-name (test-key)",
94+
},
95+
"with only a key": {
96+
resource: resource{
97+
"key": "test-key",
98+
},
99+
expected: "* test-key",
100+
},
101+
"with an email and ID": {
102+
resource: resource{
103+
"_id": "test-id",
104+
"email": "test-email",
105+
},
106+
expected: "* test-email (test-id)",
107+
},
108+
"without any valid field": {
109+
resource: resource{
110+
"other": "other-value",
111+
},
112+
expected: "* cannot read resource",
113+
},
114+
}
115+
116+
for name, tt := range tests {
117+
tt := tt
118+
t.Run(name, func(t *testing.T) {
119+
out := MultiplePlaintextOutputFn(tt.resource)
120+
121+
assert.Equal(t, tt.expected, out)
122+
})
123+
}
124+
}
125+
126+
func TestConfigPlaintextOutputFn(t *testing.T) {
127+
tests := map[string]struct {
128+
resource resource
129+
expected string
130+
}{
131+
"with multiple keys sorts alphabetically": {
132+
resource: resource{
133+
"zeta": "last",
134+
"alpha": "first",
135+
"mid": "middle",
136+
},
137+
expected: "alpha: first\nmid: middle\nzeta: last",
138+
},
139+
"with single key": {
140+
resource: resource{
141+
"key": "value",
142+
},
143+
expected: "key: value",
144+
},
145+
"with empty resource": {
146+
resource: resource{},
147+
expected: "",
148+
},
149+
"with non-string values": {
150+
resource: resource{
151+
"count": float64(42),
152+
"enabled": true,
153+
},
154+
expected: "count: 42\nenabled: true",
155+
},
156+
}
157+
158+
for name, tt := range tests {
159+
tt := tt
160+
t.Run(name, func(t *testing.T) {
161+
out := ConfigPlaintextOutputFn(tt.resource)
162+
163+
assert.Equal(t, tt.expected, out)
164+
})
165+
}
166+
}
167+
35168
func TestSingularPlaintextOutputFn(t *testing.T) {
36169
tests := map[string]struct {
37170
resource resource
@@ -77,6 +210,32 @@ func TestSingularPlaintextOutputFn(t *testing.T) {
77210
},
78211
expected: "cannot read resource",
79212
},
213+
"with non-string name and key does not panic": {
214+
resource: resource{
215+
"key": float64(123),
216+
"name": true,
217+
},
218+
expected: "true (123)",
219+
},
220+
"with non-string email and id does not panic": {
221+
resource: resource{
222+
"_id": float64(999),
223+
"email": float64(42),
224+
},
225+
expected: "42 (999)",
226+
},
227+
"with non-string key only does not panic": {
228+
resource: resource{
229+
"key": float64(456),
230+
},
231+
expected: "456",
232+
},
233+
"with non-string name only does not panic": {
234+
resource: resource{
235+
"name": float64(789),
236+
},
237+
expected: "789",
238+
},
80239
}
81240

82241
for name, tt := range tests {

0 commit comments

Comments
 (0)