Skip to content

Commit e9d0501

Browse files
authored
Merge pull request #100 from basecamp/rz/close-sdk-gaps
Close all SDK coverage gaps with 25 new CLI commands
2 parents 9404695 + 3ee5a01 commit e9d0501

18 files changed

Lines changed: 2044 additions & 20 deletions

SURFACE.txt

Lines changed: 350 additions & 0 deletions
Large diffs are not rendered by default.

internal/commands/account.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ var accountShowCmd = &cobra.Command{
3939
breadcrumbs := []Breadcrumb{
4040
breadcrumb("boards", "fizzy board list", "List boards"),
4141
breadcrumb("entropy", "fizzy account entropy --auto_postpone_period_in_days N", "Update auto-postpone period"),
42+
breadcrumb("settings-update", "fizzy account settings-update --name \"name\"", "Update settings"),
4243
}
4344

4445
printDetail(items, summary, breadcrumbs)
@@ -84,6 +85,176 @@ var accountEntropyCmd = &cobra.Command{
8485
},
8586
}
8687

88+
// Account settings update flags
89+
var accountSettingsUpdateName string
90+
91+
var accountSettingsUpdateCmd = &cobra.Command{
92+
Use: "settings-update",
93+
Short: "Update account settings",
94+
Long: "Updates account settings.",
95+
RunE: func(cmd *cobra.Command, args []string) error {
96+
if err := requireAuthAndAccount(); err != nil {
97+
return err
98+
}
99+
100+
if accountSettingsUpdateName == "" {
101+
return newRequiredFlagError("name")
102+
}
103+
104+
_, err := getSDK().Account().UpdateSettings(cmd.Context(), &generated.UpdateAccountSettingsRequest{
105+
Name: accountSettingsUpdateName,
106+
})
107+
if err != nil {
108+
return convertSDKError(err)
109+
}
110+
111+
breadcrumbs := []Breadcrumb{
112+
breadcrumb("show", "fizzy account show", "View account settings"),
113+
}
114+
115+
printMutation(map[string]any{}, "", breadcrumbs)
116+
return nil
117+
},
118+
}
119+
120+
var accountExportCreateCmd = &cobra.Command{
121+
Use: "export-create",
122+
Short: "Create an account export",
123+
Long: "Creates a new account data export.",
124+
RunE: func(cmd *cobra.Command, args []string) error {
125+
if err := requireAuthAndAccount(); err != nil {
126+
return err
127+
}
128+
129+
data, _, err := getSDK().Account().CreateExport(cmd.Context())
130+
if err != nil {
131+
return convertSDKError(err)
132+
}
133+
134+
items := normalizeAny(data)
135+
136+
exportID := ""
137+
if export, ok := items.(map[string]any); ok {
138+
if id, ok := export["id"]; ok {
139+
exportID = fmt.Sprintf("%v", id)
140+
}
141+
}
142+
143+
var breadcrumbs []Breadcrumb
144+
if exportID != "" {
145+
breadcrumbs = []Breadcrumb{
146+
breadcrumb("show", fmt.Sprintf("fizzy account export-show %s", exportID), "View export status"),
147+
}
148+
}
149+
150+
printMutation(items, "", breadcrumbs)
151+
return nil
152+
},
153+
}
154+
155+
var accountExportShowCmd = &cobra.Command{
156+
Use: "export-show EXPORT_ID",
157+
Short: "Show an account export",
158+
Long: "Shows the status of an account data export.",
159+
Args: cobra.ExactArgs(1),
160+
RunE: func(cmd *cobra.Command, args []string) error {
161+
if err := requireAuthAndAccount(); err != nil {
162+
return err
163+
}
164+
165+
data, _, err := getSDK().Account().GetExport(cmd.Context(), args[0])
166+
if err != nil {
167+
return convertSDKError(err)
168+
}
169+
170+
breadcrumbs := []Breadcrumb{
171+
breadcrumb("show", "fizzy account show", "View account settings"),
172+
}
173+
174+
printDetail(normalizeAny(data), "", breadcrumbs)
175+
return nil
176+
},
177+
}
178+
179+
var accountJoinCodeShowCmd = &cobra.Command{
180+
Use: "join-code-show",
181+
Short: "Show the account join code",
182+
Long: "Shows the current join code for inviting new members.",
183+
RunE: func(cmd *cobra.Command, args []string) error {
184+
if err := requireAuthAndAccount(); err != nil {
185+
return err
186+
}
187+
188+
data, _, err := getSDK().Account().GetJoinCode(cmd.Context())
189+
if err != nil {
190+
return convertSDKError(err)
191+
}
192+
193+
breadcrumbs := []Breadcrumb{
194+
breadcrumb("reset", "fizzy account join-code-reset", "Reset join code"),
195+
breadcrumb("show", "fizzy account show", "View account settings"),
196+
}
197+
198+
printDetail(normalizeAny(data), "", breadcrumbs)
199+
return nil
200+
},
201+
}
202+
203+
var accountJoinCodeResetCmd = &cobra.Command{
204+
Use: "join-code-reset",
205+
Short: "Reset the account join code",
206+
Long: "Resets the join code, invalidating the previous one.",
207+
RunE: func(cmd *cobra.Command, args []string) error {
208+
if err := requireAuthAndAccount(); err != nil {
209+
return err
210+
}
211+
212+
_, err := getSDK().Account().ResetJoinCode(cmd.Context())
213+
if err != nil {
214+
return convertSDKError(err)
215+
}
216+
217+
breadcrumbs := []Breadcrumb{
218+
breadcrumb("show", "fizzy account join-code-show", "View new join code"),
219+
}
220+
221+
printMutation(map[string]any{}, "", breadcrumbs)
222+
return nil
223+
},
224+
}
225+
226+
// Account join code update flags
227+
var accountJoinCodeUpdateUsageLimit int
228+
229+
var accountJoinCodeUpdateCmd = &cobra.Command{
230+
Use: "join-code-update",
231+
Short: "Update the account join code",
232+
Long: "Updates the join code settings.",
233+
RunE: func(cmd *cobra.Command, args []string) error {
234+
if err := requireAuthAndAccount(); err != nil {
235+
return err
236+
}
237+
238+
if !cmd.Flags().Changed("usage-limit") {
239+
return newRequiredFlagError("usage-limit")
240+
}
241+
242+
_, err := getSDK().Account().UpdateJoinCode(cmd.Context(), &generated.UpdateJoinCodeRequest{
243+
UsageLimit: int32(accountJoinCodeUpdateUsageLimit),
244+
})
245+
if err != nil {
246+
return convertSDKError(err)
247+
}
248+
249+
breadcrumbs := []Breadcrumb{
250+
breadcrumb("show", "fizzy account join-code-show", "View join code"),
251+
}
252+
253+
printMutation(map[string]any{}, "", breadcrumbs)
254+
return nil
255+
},
256+
}
257+
87258
func init() {
88259
rootCmd.AddCommand(accountCmd)
89260

@@ -93,4 +264,18 @@ func init() {
93264
// Entropy
94265
accountEntropyCmd.Flags().IntVar(&accountEntropyAutoPostponePeriodInDays, "auto_postpone_period_in_days", 0, "Auto postpone period in days ("+validAutoPostponePeriodsHelp+")")
95266
accountCmd.AddCommand(accountEntropyCmd)
267+
268+
// Settings update
269+
accountSettingsUpdateCmd.Flags().StringVar(&accountSettingsUpdateName, "name", "", "Account name (required)")
270+
accountCmd.AddCommand(accountSettingsUpdateCmd)
271+
272+
// Exports
273+
accountCmd.AddCommand(accountExportCreateCmd)
274+
accountCmd.AddCommand(accountExportShowCmd)
275+
276+
// Join code
277+
accountCmd.AddCommand(accountJoinCodeShowCmd)
278+
accountCmd.AddCommand(accountJoinCodeResetCmd)
279+
accountJoinCodeUpdateCmd.Flags().IntVar(&accountJoinCodeUpdateUsageLimit, "usage-limit", 0, "Usage limit (required)")
280+
accountCmd.AddCommand(accountJoinCodeUpdateCmd)
96281
}

internal/commands/account_test.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,158 @@ func TestAccountEntropy(t *testing.T) {
138138
assertExitCode(t, err, errors.ExitForbidden)
139139
})
140140
}
141+
142+
func TestAccountSettingsUpdate(t *testing.T) {
143+
t.Run("updates account settings with name flag", func(t *testing.T) {
144+
mock := NewMockClient()
145+
146+
SetTestModeWithSDK(mock)
147+
SetTestConfig("token", "account", "https://api.example.com")
148+
defer resetTest()
149+
150+
accountSettingsUpdateName = "New Name"
151+
err := accountSettingsUpdateCmd.RunE(accountSettingsUpdateCmd, []string{})
152+
accountSettingsUpdateName = ""
153+
154+
assertExitCode(t, err, 0)
155+
if mock.PatchCalls[0].Path != "/account/settings.json" {
156+
t.Errorf("expected path '/account/settings.json', got '%s'", mock.PatchCalls[0].Path)
157+
}
158+
body := mock.PatchCalls[0].Body.(map[string]any)
159+
if body["name"] != "New Name" {
160+
t.Errorf("expected name 'New Name', got '%v'", body["name"])
161+
}
162+
})
163+
164+
t.Run("requires name flag", func(t *testing.T) {
165+
mock := NewMockClient()
166+
167+
SetTestModeWithSDK(mock)
168+
SetTestConfig("token", "account", "https://api.example.com")
169+
defer resetTest()
170+
171+
accountSettingsUpdateName = ""
172+
err := accountSettingsUpdateCmd.RunE(accountSettingsUpdateCmd, []string{})
173+
174+
assertExitCode(t, err, errors.ExitInvalidArgs)
175+
})
176+
}
177+
178+
func TestAccountExportCreate(t *testing.T) {
179+
t.Run("creates export", func(t *testing.T) {
180+
mock := NewMockClient()
181+
mock.PostResponse = &client.APIResponse{
182+
StatusCode: 201,
183+
Data: map[string]any{"id": "export-1", "status": "pending"},
184+
}
185+
186+
SetTestModeWithSDK(mock)
187+
SetTestConfig("token", "account", "https://api.example.com")
188+
defer resetTest()
189+
190+
err := accountExportCreateCmd.RunE(accountExportCreateCmd, []string{})
191+
assertExitCode(t, err, 0)
192+
if mock.PostCalls[0].Path != "/account/exports.json" {
193+
t.Errorf("expected path '/account/exports.json', got '%s'", mock.PostCalls[0].Path)
194+
}
195+
})
196+
}
197+
198+
func TestAccountExportShow(t *testing.T) {
199+
t.Run("shows export by ID", func(t *testing.T) {
200+
mock := NewMockClient()
201+
mock.GetResponse = &client.APIResponse{
202+
StatusCode: 200,
203+
Data: map[string]any{"id": "export-1", "status": "completed"},
204+
}
205+
206+
SetTestModeWithSDK(mock)
207+
SetTestConfig("token", "account", "https://api.example.com")
208+
defer resetTest()
209+
210+
err := accountExportShowCmd.RunE(accountExportShowCmd, []string{"export-1"})
211+
assertExitCode(t, err, 0)
212+
if mock.GetCalls[0].Path != "/account/exports/export-1" {
213+
t.Errorf("expected path '/account/exports/export-1', got '%s'", mock.GetCalls[0].Path)
214+
}
215+
})
216+
}
217+
218+
func TestAccountJoinCodeShow(t *testing.T) {
219+
t.Run("shows join code", func(t *testing.T) {
220+
mock := NewMockClient()
221+
mock.GetResponse = &client.APIResponse{
222+
StatusCode: 200,
223+
Data: map[string]any{"code": "abc123", "usage_limit": float64(10)},
224+
}
225+
226+
SetTestModeWithSDK(mock)
227+
SetTestConfig("token", "account", "https://api.example.com")
228+
defer resetTest()
229+
230+
err := accountJoinCodeShowCmd.RunE(accountJoinCodeShowCmd, []string{})
231+
assertExitCode(t, err, 0)
232+
if mock.GetCalls[0].Path != "/account/join_code.json" {
233+
t.Errorf("expected path '/account/join_code.json', got '%s'", mock.GetCalls[0].Path)
234+
}
235+
})
236+
}
237+
238+
func TestAccountJoinCodeReset(t *testing.T) {
239+
t.Run("resets join code", func(t *testing.T) {
240+
mock := NewMockClient()
241+
mock.DeleteResponse = &client.APIResponse{
242+
StatusCode: 204,
243+
Data: map[string]any{},
244+
}
245+
246+
SetTestModeWithSDK(mock)
247+
SetTestConfig("token", "account", "https://api.example.com")
248+
defer resetTest()
249+
250+
err := accountJoinCodeResetCmd.RunE(accountJoinCodeResetCmd, []string{})
251+
assertExitCode(t, err, 0)
252+
if mock.DeleteCalls[0].Path != "/account/join_code.json" {
253+
t.Errorf("expected path '/account/join_code.json', got '%s'", mock.DeleteCalls[0].Path)
254+
}
255+
})
256+
}
257+
258+
func TestAccountJoinCodeUpdate(t *testing.T) {
259+
t.Run("updates join code with usage-limit flag", func(t *testing.T) {
260+
mock := NewMockClient()
261+
262+
SetTestModeWithSDK(mock)
263+
SetTestConfig("token", "account", "https://api.example.com")
264+
defer resetTest()
265+
266+
accountJoinCodeUpdateCmd.Flags().Set("usage-limit", "10")
267+
defer func() {
268+
accountJoinCodeUpdateUsageLimit = 0
269+
accountJoinCodeUpdateCmd.Flags().Lookup("usage-limit").Changed = false
270+
}()
271+
err := accountJoinCodeUpdateCmd.RunE(accountJoinCodeUpdateCmd, []string{})
272+
273+
assertExitCode(t, err, 0)
274+
if mock.PatchCalls[0].Path != "/account/join_code.json" {
275+
t.Errorf("expected path '/account/join_code.json', got '%s'", mock.PatchCalls[0].Path)
276+
}
277+
body := mock.PatchCalls[0].Body.(map[string]any)
278+
if body["usage_limit"] == nil {
279+
t.Error("expected body to contain 'usage_limit'")
280+
}
281+
})
282+
283+
t.Run("requires usage-limit flag", func(t *testing.T) {
284+
mock := NewMockClient()
285+
286+
SetTestModeWithSDK(mock)
287+
SetTestConfig("token", "account", "https://api.example.com")
288+
defer resetTest()
289+
290+
accountJoinCodeUpdateUsageLimit = 0
291+
err := accountJoinCodeUpdateCmd.RunE(accountJoinCodeUpdateCmd, []string{})
292+
293+
assertExitCode(t, err, errors.ExitInvalidArgs)
294+
})
295+
}

0 commit comments

Comments
 (0)