Skip to content

Commit b15efd1

Browse files
authored
Merge pull request #157 from basecamp/add-token-commands
Add personal access token commands
2 parents bff7af2 + 5b00475 commit b15efd1

9 files changed

Lines changed: 364 additions & 3 deletions

File tree

SURFACE.txt

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ ARG fizzy signup help 00 [command]
2424
ARG fizzy skill help 00 [command]
2525
ARG fizzy step help 00 [command]
2626
ARG fizzy tag help 00 [command]
27+
ARG fizzy token help 00 [command]
2728
ARG fizzy upload help 00 [command]
2829
ARG fizzy user help 00 [command]
2930
ARG fizzy webhook help 00 [command]
@@ -194,6 +195,13 @@ CMD fizzy tag
194195
CMD fizzy tag help
195196
CMD fizzy tag list
196197
CMD fizzy tag ls
198+
CMD fizzy token
199+
CMD fizzy token create
200+
CMD fizzy token delete
201+
CMD fizzy token help
202+
CMD fizzy token list
203+
CMD fizzy token ls
204+
CMD fizzy token rm
197205
CMD fizzy upload
198206
CMD fizzy upload file
199207
CMD fizzy upload help
@@ -2737,6 +2745,106 @@ FLAG fizzy tag ls --quiet type=bool
27372745
FLAG fizzy tag ls --styled type=bool
27382746
FLAG fizzy tag ls --token type=string
27392747
FLAG fizzy tag ls --verbose type=bool
2748+
FLAG fizzy token --agent type=bool
2749+
FLAG fizzy token --api-url type=string
2750+
FLAG fizzy token --count type=bool
2751+
FLAG fizzy token --help type=bool
2752+
FLAG fizzy token --ids-only type=bool
2753+
FLAG fizzy token --jq type=string
2754+
FLAG fizzy token --json type=bool
2755+
FLAG fizzy token --limit type=int
2756+
FLAG fizzy token --markdown type=bool
2757+
FLAG fizzy token --profile type=string
2758+
FLAG fizzy token --quiet type=bool
2759+
FLAG fizzy token --styled type=bool
2760+
FLAG fizzy token --token type=string
2761+
FLAG fizzy token --verbose type=bool
2762+
FLAG fizzy token create --agent type=bool
2763+
FLAG fizzy token create --api-url type=string
2764+
FLAG fizzy token create --count type=bool
2765+
FLAG fizzy token create --description type=string
2766+
FLAG fizzy token create --help type=bool
2767+
FLAG fizzy token create --ids-only type=bool
2768+
FLAG fizzy token create --jq type=string
2769+
FLAG fizzy token create --json type=bool
2770+
FLAG fizzy token create --limit type=int
2771+
FLAG fizzy token create --markdown type=bool
2772+
FLAG fizzy token create --permission type=string
2773+
FLAG fizzy token create --profile type=string
2774+
FLAG fizzy token create --quiet type=bool
2775+
FLAG fizzy token create --styled type=bool
2776+
FLAG fizzy token create --token type=string
2777+
FLAG fizzy token create --verbose type=bool
2778+
FLAG fizzy token delete --agent type=bool
2779+
FLAG fizzy token delete --api-url type=string
2780+
FLAG fizzy token delete --count type=bool
2781+
FLAG fizzy token delete --help type=bool
2782+
FLAG fizzy token delete --ids-only type=bool
2783+
FLAG fizzy token delete --jq type=string
2784+
FLAG fizzy token delete --json type=bool
2785+
FLAG fizzy token delete --limit type=int
2786+
FLAG fizzy token delete --markdown type=bool
2787+
FLAG fizzy token delete --profile type=string
2788+
FLAG fizzy token delete --quiet type=bool
2789+
FLAG fizzy token delete --styled type=bool
2790+
FLAG fizzy token delete --token type=string
2791+
FLAG fizzy token delete --verbose type=bool
2792+
FLAG fizzy token help --agent type=bool
2793+
FLAG fizzy token help --api-url type=string
2794+
FLAG fizzy token help --count type=bool
2795+
FLAG fizzy token help --help type=bool
2796+
FLAG fizzy token help --ids-only type=bool
2797+
FLAG fizzy token help --jq type=string
2798+
FLAG fizzy token help --json type=bool
2799+
FLAG fizzy token help --limit type=int
2800+
FLAG fizzy token help --markdown type=bool
2801+
FLAG fizzy token help --profile type=string
2802+
FLAG fizzy token help --quiet type=bool
2803+
FLAG fizzy token help --styled type=bool
2804+
FLAG fizzy token help --token type=string
2805+
FLAG fizzy token help --verbose type=bool
2806+
FLAG fizzy token list --agent type=bool
2807+
FLAG fizzy token list --api-url type=string
2808+
FLAG fizzy token list --count type=bool
2809+
FLAG fizzy token list --help type=bool
2810+
FLAG fizzy token list --ids-only type=bool
2811+
FLAG fizzy token list --jq type=string
2812+
FLAG fizzy token list --json type=bool
2813+
FLAG fizzy token list --limit type=int
2814+
FLAG fizzy token list --markdown type=bool
2815+
FLAG fizzy token list --profile type=string
2816+
FLAG fizzy token list --quiet type=bool
2817+
FLAG fizzy token list --styled type=bool
2818+
FLAG fizzy token list --token type=string
2819+
FLAG fizzy token list --verbose type=bool
2820+
FLAG fizzy token ls --agent type=bool
2821+
FLAG fizzy token ls --api-url type=string
2822+
FLAG fizzy token ls --count type=bool
2823+
FLAG fizzy token ls --help type=bool
2824+
FLAG fizzy token ls --ids-only type=bool
2825+
FLAG fizzy token ls --jq type=string
2826+
FLAG fizzy token ls --json type=bool
2827+
FLAG fizzy token ls --limit type=int
2828+
FLAG fizzy token ls --markdown type=bool
2829+
FLAG fizzy token ls --profile type=string
2830+
FLAG fizzy token ls --quiet type=bool
2831+
FLAG fizzy token ls --styled type=bool
2832+
FLAG fizzy token ls --token type=string
2833+
FLAG fizzy token ls --verbose type=bool
2834+
FLAG fizzy token rm --agent type=bool
2835+
FLAG fizzy token rm --api-url type=string
2836+
FLAG fizzy token rm --count type=bool
2837+
FLAG fizzy token rm --help type=bool
2838+
FLAG fizzy token rm --ids-only type=bool
2839+
FLAG fizzy token rm --jq type=string
2840+
FLAG fizzy token rm --json type=bool
2841+
FLAG fizzy token rm --limit type=int
2842+
FLAG fizzy token rm --markdown type=bool
2843+
FLAG fizzy token rm --profile type=string
2844+
FLAG fizzy token rm --quiet type=bool
2845+
FLAG fizzy token rm --styled type=bool
2846+
FLAG fizzy token rm --token type=string
2847+
FLAG fizzy token rm --verbose type=bool
27402848
FLAG fizzy upload --agent type=bool
27412849
FLAG fizzy upload --api-url type=string
27422850
FLAG fizzy upload --count type=bool
@@ -3385,6 +3493,13 @@ SUB fizzy tag
33853493
SUB fizzy tag help
33863494
SUB fizzy tag list
33873495
SUB fizzy tag ls
3496+
SUB fizzy token
3497+
SUB fizzy token create
3498+
SUB fizzy token delete
3499+
SUB fizzy token help
3500+
SUB fizzy token list
3501+
SUB fizzy token ls
3502+
SUB fizzy token rm
33883503
SUB fizzy upload
33893504
SUB fizzy upload file
33903505
SUB fizzy upload help

e2e/cli_tests/token_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package clitests
2+
3+
import (
4+
"strconv"
5+
"testing"
6+
"time"
7+
8+
"github.com/basecamp/fizzy-cli/e2e/harness"
9+
)
10+
11+
func TestAccessTokenCRUD(t *testing.T) {
12+
h := newHarness(t)
13+
description := "CLI Test Token " + strconv.FormatInt(time.Now().UnixNano(), 10)
14+
15+
create := h.Run("token", "create", "--description", description, "--permission", "read")
16+
assertOK(t, create)
17+
tokenID := create.GetDataString("id")
18+
if tokenID == "" {
19+
t.Fatal("no token ID in create response")
20+
}
21+
deleted := false
22+
t.Cleanup(func() {
23+
if deleted {
24+
return
25+
}
26+
cleanupDelete := newHarness(t).Run("token", "delete", tokenID)
27+
if cleanupDelete.ExitCode != harness.ExitSuccess {
28+
t.Errorf("cleanup failed deleting token %q: exit=%d stdout=%s stderr=%s", tokenID, cleanupDelete.ExitCode, cleanupDelete.Stdout, cleanupDelete.Stderr)
29+
return
30+
}
31+
if !cleanupDelete.GetDataBool("deleted") {
32+
t.Errorf("cleanup delete for token %q did not report deleted=true", tokenID)
33+
}
34+
})
35+
if create.GetDataString("token") == "" {
36+
t.Fatal("expected raw token value in create response")
37+
}
38+
39+
list := h.Run("token", "list")
40+
assertOK(t, list)
41+
found := false
42+
for _, item := range list.GetDataArray() {
43+
if mapValueString(asMap(item), "id") == tokenID {
44+
found = true
45+
break
46+
}
47+
}
48+
if !found {
49+
t.Fatalf("expected token list to include %q", tokenID)
50+
}
51+
52+
deleteResult := h.Run("token", "delete", tokenID)
53+
assertOK(t, deleteResult)
54+
deleted = true
55+
if !deleteResult.GetDataBool("deleted") {
56+
t.Fatal("expected deleted=true")
57+
}
58+
}

internal/commands/columns.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,11 @@ var (
9191
{Header: "Created", Field: "created_at"},
9292
{Header: "Updated", Field: "updated_at"},
9393
}
94+
95+
tokenColumns = render.Columns{
96+
{Header: "ID", Field: "id"},
97+
{Header: "Description", Field: "description"},
98+
{Header: "Permission", Field: "permission"},
99+
{Header: "Created", Field: "created_at"},
100+
}
94101
)

internal/commands/commands.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ var commandCatalogTitles = map[string]string{
3333
var commandCatalogGroups = map[string][]string{
3434
"core": {"activity", "board", "card", "column", "comment", "search", "step"},
3535
"collaboration": {"notification", "pin", "reaction", "tag", "user"},
36-
"admin": {"auth", "account", "identity", "webhook", "upload", "migrate"},
36+
"admin": {"auth", "account", "identity", "token", "webhook", "upload", "migrate"},
3737
"utilities": {"setup", "signup", "completion", "doctor", "config", "skill", "commands", "version"},
3838
}
3939

internal/commands/commands_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,25 @@ func TestCommandsFilterFindsActivity(t *testing.T) {
7070
}
7171
}
7272

73+
func TestCommandsFilterFindsToken(t *testing.T) {
74+
mock := NewMockClient()
75+
SetTestModeWithSDK(mock)
76+
SetTestFormat(output.FormatStyled)
77+
defer resetTest()
78+
79+
if err := commandsCmd.RunE(commandsCmd, []string{"token"}); err != nil {
80+
t.Fatalf("unexpected error: %v", err)
81+
}
82+
83+
raw := TestOutput()
84+
if !strings.Contains(raw, "token") || !strings.Contains(raw, "create, delete, list") {
85+
t.Fatalf("expected filtered catalog to include token actions, got:\n%s", raw)
86+
}
87+
if strings.Contains(raw, "No commands match") {
88+
t.Fatalf("expected token to be discoverable, got:\n%s", raw)
89+
}
90+
}
91+
7392
func TestCommandsJSONOutputReturnsStructuredCatalog(t *testing.T) {
7493
mock := NewMockClient()
7594
result := SetTestModeWithSDK(mock)

internal/commands/help.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ var rootCommandGroupTitles = map[string]string{
379379
}
380380

381381
var rootCommandGroups = map[string][]string{
382-
"core": {"auth", "activity", "board", "card", "search"},
382+
"core": {"auth", "token", "activity", "board", "card", "search"},
383383
"collaboration": {"comment", "notification"},
384384
"getting-started": {"setup", "signup"},
385385
"discover": {"doctor", "config", "commands", "version"},

internal/commands/help_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func TestRenderRootHelp(t *testing.T) {
1515
renderHelp(rootCmd, &buf)
1616
out := buf.String()
1717

18-
for _, want := range []string{"CORE COMMANDS", "activity", "GETTING STARTED", "DISCOVER", "FLAGS", "--profile", "LEARN MORE", "Use `fizzy commands` to see the full command catalog.", "implies --json"} {
18+
for _, want := range []string{"CORE COMMANDS", "activity", "token", "GETTING STARTED", "DISCOVER", "FLAGS", "--profile", "LEARN MORE", "Use `fizzy commands` to see the full command catalog.", "implies --json"} {
1919
if !strings.Contains(out, want) {
2020
t.Fatalf("expected root help to contain %q, got:\n%s", want, out)
2121
}

0 commit comments

Comments
 (0)