fizzy-cli 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/.env +1 -0
  2. package/.github/workflows/release.yml +29 -0
  3. package/.github/workflows/tests.yml +24 -0
  4. package/AGENTS.md +33 -0
  5. package/CHANGELOG.md +69 -0
  6. package/Makefile +20 -9
  7. package/README.md +88 -1
  8. package/bin/fizzy +0 -0
  9. package/cmd/account.go +14 -0
  10. package/cmd/account_list.go +44 -0
  11. package/cmd/account_list_test.go +118 -0
  12. package/cmd/board.go +38 -12
  13. package/cmd/board_create.go +60 -0
  14. package/cmd/board_create_test.go +158 -0
  15. package/cmd/board_list.go +18 -32
  16. package/cmd/board_list_test.go +115 -0
  17. package/cmd/board_test.go +92 -0
  18. package/cmd/card.go +24 -0
  19. package/cmd/card_close.go +46 -0
  20. package/cmd/card_close_test.go +92 -0
  21. package/cmd/card_create.go +73 -0
  22. package/cmd/card_create_test.go +206 -0
  23. package/cmd/card_delete.go +46 -0
  24. package/cmd/card_delete_test.go +92 -0
  25. package/cmd/card_list.go +53 -0
  26. package/cmd/card_list_test.go +148 -0
  27. package/cmd/card_reopen.go +46 -0
  28. package/cmd/card_reopen_test.go +92 -0
  29. package/cmd/card_show.go +46 -0
  30. package/cmd/card_show_test.go +92 -0
  31. package/cmd/card_update.go +74 -0
  32. package/cmd/card_update_test.go +147 -0
  33. package/cmd/column.go +14 -0
  34. package/cmd/column_create.go +80 -0
  35. package/cmd/column_create_test.go +196 -0
  36. package/cmd/column_list.go +44 -0
  37. package/cmd/column_list_test.go +138 -0
  38. package/cmd/login.go +61 -4
  39. package/cmd/login_test.go +98 -0
  40. package/cmd/root.go +15 -4
  41. package/cmd/use.go +85 -0
  42. package/cmd/use_test.go +186 -0
  43. package/docs/API.md +1168 -0
  44. package/go.mod +23 -2
  45. package/go.sum +43 -0
  46. package/internal/api/client.go +463 -0
  47. package/internal/app/app.go +49 -0
  48. package/internal/colors/colors.go +32 -0
  49. package/internal/config/config.go +69 -0
  50. package/internal/testutil/client.go +26 -0
  51. package/internal/ui/account_list.go +14 -0
  52. package/internal/ui/account_selector.go +63 -0
  53. package/internal/ui/board_list.go +14 -0
  54. package/internal/ui/card_list.go +14 -0
  55. package/internal/ui/card_show.go +23 -0
  56. package/internal/ui/column_list.go +28 -0
  57. package/internal/ui/format.go +14 -0
  58. package/main.go +1 -1
  59. package/package.json +1 -1
  60. package/scripts/postinstall.js +5 -1
@@ -0,0 +1,138 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "net/http"
7
+ "net/http/httptest"
8
+ "testing"
9
+
10
+ "github.com/rogeriopvl/fizzy/internal/api"
11
+ "github.com/rogeriopvl/fizzy/internal/app"
12
+ "github.com/rogeriopvl/fizzy/internal/testutil"
13
+ )
14
+
15
+ func TestColumnListCommand(t *testing.T) {
16
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17
+ if r.URL.Path != "/boards/board-123/columns" {
18
+ t.Errorf("expected /boards/board-123/columns, got %s", r.URL.Path)
19
+ }
20
+ if r.Method != http.MethodGet {
21
+ t.Errorf("expected GET, got %s", r.Method)
22
+ }
23
+
24
+ auth := r.Header.Get("Authorization")
25
+ if auth != "Bearer test-token" {
26
+ t.Errorf("expected Bearer test-token, got %s", auth)
27
+ }
28
+
29
+ if r.Header.Get("Accept") != "application/json" {
30
+ t.Errorf("expected Accept: application/json, got %s", r.Header.Get("Accept"))
31
+ }
32
+
33
+ w.Header().Set("Content-Type", "application/json")
34
+ response := []api.Column{
35
+ {
36
+ ID: "col-123",
37
+ Name: "Todo",
38
+ Color: api.ColorObject{
39
+ Name: "Blue",
40
+ Value: "var(--color-card-default)",
41
+ },
42
+ CreatedAt: "2025-01-01T00:00:00Z",
43
+ },
44
+ {
45
+ ID: "col-456",
46
+ Name: "In Progress",
47
+ Color: api.ColorObject{
48
+ Name: "Lime",
49
+ Value: "var(--color-card-4)",
50
+ },
51
+ CreatedAt: "2025-01-02T00:00:00Z",
52
+ },
53
+ }
54
+ json.NewEncoder(w).Encode(response)
55
+ }))
56
+ defer server.Close()
57
+
58
+ client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
59
+ testApp := &app.App{Client: client}
60
+
61
+ cmd := columnListCmd
62
+ cmd.SetContext(testApp.ToContext(context.Background()))
63
+
64
+ if err := handleListColumns(cmd); err != nil {
65
+ t.Fatalf("handleListColumns failed: %v", err)
66
+ }
67
+ }
68
+
69
+ func TestColumnListCommandNoColumns(t *testing.T) {
70
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
71
+ w.Header().Set("Content-Type", "application/json")
72
+ json.NewEncoder(w).Encode([]api.Column{})
73
+ }))
74
+ defer server.Close()
75
+
76
+ client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
77
+ testApp := &app.App{Client: client}
78
+
79
+ cmd := columnListCmd
80
+ cmd.SetContext(testApp.ToContext(context.Background()))
81
+
82
+ if err := handleListColumns(cmd); err != nil {
83
+ t.Fatalf("handleListColumns failed: %v", err)
84
+ }
85
+ }
86
+
87
+ func TestColumnListCommandAPIError(t *testing.T) {
88
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
89
+ w.WriteHeader(http.StatusInternalServerError)
90
+ w.Write([]byte("Internal Server Error"))
91
+ }))
92
+ defer server.Close()
93
+
94
+ client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
95
+ testApp := &app.App{Client: client}
96
+
97
+ cmd := columnListCmd
98
+ cmd.SetContext(testApp.ToContext(context.Background()))
99
+
100
+ err := handleListColumns(cmd)
101
+ if err == nil {
102
+ t.Errorf("expected error for API failure")
103
+ }
104
+ if err.Error() != "fetching columns: unexpected status code 500: Internal Server Error" {
105
+ t.Errorf("expected API error, got %v", err)
106
+ }
107
+ }
108
+
109
+ func TestColumnListCommandNoBoard(t *testing.T) {
110
+ client := testutil.NewTestClient("http://localhost", "", "", "test-token")
111
+ testApp := &app.App{Client: client}
112
+
113
+ cmd := columnListCmd
114
+ cmd.SetContext(testApp.ToContext(context.Background()))
115
+
116
+ err := handleListColumns(cmd)
117
+ if err == nil {
118
+ t.Errorf("expected error when board not selected")
119
+ }
120
+ if err.Error() != "fetching columns: please select a board first with 'fizzy use --board <board_name>'" {
121
+ t.Errorf("expected 'board not selected' error, got %v", err)
122
+ }
123
+ }
124
+
125
+ func TestColumnListCommandNoClient(t *testing.T) {
126
+ testApp := &app.App{}
127
+
128
+ cmd := columnListCmd
129
+ cmd.SetContext(testApp.ToContext(context.Background()))
130
+
131
+ err := handleListColumns(cmd)
132
+ if err == nil {
133
+ t.Errorf("expected error when client not available")
134
+ }
135
+ if err.Error() != "API client not available" {
136
+ t.Errorf("expected 'client not available' error, got %v", err)
137
+ }
138
+ }
package/cmd/login.go CHANGED
@@ -1,8 +1,13 @@
1
1
  package cmd
2
2
 
3
3
  import (
4
+ "context"
4
5
  "fmt"
6
+ "os"
5
7
 
8
+ "github.com/rogeriopvl/fizzy/internal/api"
9
+ "github.com/rogeriopvl/fizzy/internal/app"
10
+ "github.com/rogeriopvl/fizzy/internal/ui"
6
11
  "github.com/spf13/cobra"
7
12
  )
8
13
 
@@ -11,13 +16,65 @@ var loginCmd = &cobra.Command{
11
16
  Short: "Prints instructions on how to authenticate with the Fizzy API",
12
17
  Long: `Prints intructions on how to authenticate with the Fizzy API`,
13
18
  Run: func(cmd *cobra.Command, args []string) {
14
- fmt.Println("To authenticate with Fizzy's API you need an access token.")
15
- fmt.Println("\nGo to https://app.fizzy.do/<account_slug>/my/access_tokens and follow the instructions...")
16
- fmt.Println("(Replace <account_slug> with your account slug/id)")
17
- fmt.Println("\nThen export it as an environment variable in your shell, with the name FIZZY_ACCESS_TOKEN")
19
+ if err := handleLogin(cmd); err != nil {
20
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
21
+ }
18
22
  },
19
23
  }
20
24
 
25
+ func handleLogin(cmd *cobra.Command) error {
26
+ token, isSet := os.LookupEnv("FIZZY_ACCESS_TOKEN")
27
+ if !isSet || token == "" {
28
+ return printAuthInstructions()
29
+ }
30
+
31
+ fmt.Printf("✓ Authenticated with access token: %s\n", token[:6]+"...")
32
+
33
+ a := app.FromContext(cmd.Context())
34
+ if a == nil || a.Client == nil {
35
+ return fmt.Errorf("API client not available")
36
+ }
37
+
38
+ identity, err := a.Client.GetMyIdentity(context.Background())
39
+ if err != nil {
40
+ return fmt.Errorf("fetching identity: %w", err)
41
+ }
42
+
43
+ selected, err := chooseAccount(identity.Accounts)
44
+ if err != nil {
45
+ return err
46
+ }
47
+
48
+ fmt.Printf("\nSelected account: %s (%s)\n", selected.Name, selected.Slug)
49
+
50
+ // Save the selected account to config
51
+ a.Config.SelectedAccount = selected.Slug
52
+ if err := a.Config.Save(); err != nil {
53
+ return fmt.Errorf("saving config: %w", err)
54
+ }
55
+
56
+ return nil
57
+ }
58
+
59
+ func chooseAccount(accounts []api.Account) (api.Account, error) {
60
+ if len(accounts) == 1 {
61
+ selected := accounts[0]
62
+ return selected, nil
63
+ }
64
+
65
+ fmt.Println("\nAvailable accounts:")
66
+ return ui.SelectAccount(accounts)
67
+ }
68
+
69
+ func printAuthInstructions() error {
70
+ fmt.Println("To authenticate with Fizzy's API you need an access token.")
71
+ fmt.Printf("\nGo to https://app.fizzy.do/<account_slug>/my/access_tokens and follow the instructions...\n")
72
+ fmt.Println("(Replace <account_slug> with your account slug)")
73
+ fmt.Printf("\nThen export it as an environment variable in your shell, with the name FIZZY_ACCESS_TOKEN\n")
74
+ fmt.Println("And re-run this command.")
75
+ return nil
76
+ }
77
+
21
78
  func init() {
22
79
  rootCmd.AddCommand(loginCmd)
23
80
  }
@@ -0,0 +1,98 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "net/http"
7
+ "net/http/httptest"
8
+ "os"
9
+ "testing"
10
+
11
+ "github.com/rogeriopvl/fizzy/internal/api"
12
+ "github.com/rogeriopvl/fizzy/internal/app"
13
+ "github.com/rogeriopvl/fizzy/internal/config"
14
+ "github.com/rogeriopvl/fizzy/internal/testutil"
15
+ )
16
+
17
+ func TestLoginCommand(t *testing.T) {
18
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
19
+ if r.URL.Path != "/my/identity" {
20
+ t.Errorf("expected /my/identity, got %s", r.URL.Path)
21
+ }
22
+ if r.Method != http.MethodGet {
23
+ t.Errorf("expected GET, got %s", r.Method)
24
+ }
25
+
26
+ auth := r.Header.Get("Authorization")
27
+ if auth == "" {
28
+ t.Error("missing Authorization header")
29
+ w.WriteHeader(http.StatusUnauthorized)
30
+ return
31
+ }
32
+ if auth != "Bearer test-token" {
33
+ t.Errorf("expected Bearer test-token, got %s", auth)
34
+ }
35
+
36
+ if r.Header.Get("Accept") != "application/json" {
37
+ t.Errorf("expected Accept: application/json, got %s", r.Header.Get("Accept"))
38
+ }
39
+
40
+ w.Header().Set("Content-Type", "application/json")
41
+ response := api.GetMyIdentityResponse{
42
+ Accounts: []api.Account{
43
+ {
44
+ ID: "acc-123",
45
+ Name: "Test Account",
46
+ Slug: "test-account",
47
+ User: api.User{
48
+ ID: "user-123",
49
+ Email: "test@example.com",
50
+ Name: "Test User",
51
+ },
52
+ },
53
+ },
54
+ }
55
+ json.NewEncoder(w).Encode(response)
56
+ }))
57
+ defer server.Close()
58
+
59
+ tmpDir := t.TempDir()
60
+ configPath := tmpDir + "/.config/fizzy-cli/config.json"
61
+
62
+ t.Setenv("FIZZY_ACCESS_TOKEN", "test-token")
63
+ t.Setenv("HOME", tmpDir)
64
+
65
+ client := testutil.NewTestClient(server.URL, "", "", "test-token")
66
+
67
+ cfg := &config.Config{}
68
+ testApp := &app.App{
69
+ Client: client,
70
+ Config: cfg,
71
+ }
72
+
73
+ cmd := loginCmd
74
+ cmd.SetContext(testApp.ToContext(context.Background()))
75
+
76
+ if err := handleLogin(cmd); err != nil {
77
+ t.Fatalf("handleLogin failed: %v", err)
78
+ }
79
+
80
+ if cfg.SelectedAccount != "test-account" {
81
+ t.Errorf("expected SelectedAccount=test-account, got %s", cfg.SelectedAccount)
82
+ }
83
+
84
+ if _, err := os.Stat(configPath); err != nil {
85
+ t.Errorf("config file not created at %s: %v", configPath, err)
86
+ }
87
+ }
88
+
89
+ func TestLoginCommandWithoutToken(t *testing.T) {
90
+ t.Setenv("FIZZY_ACCESS_TOKEN", "")
91
+
92
+ cmd := loginCmd
93
+ err := handleLogin(cmd)
94
+
95
+ if err != nil {
96
+ t.Errorf("expected no error when token is missing, got %v", err)
97
+ }
98
+ }
package/cmd/root.go CHANGED
@@ -1,16 +1,26 @@
1
1
  package cmd
2
2
 
3
3
  import (
4
+ "fmt"
4
5
  "os"
5
6
 
7
+ "github.com/rogeriopvl/fizzy/internal/app"
6
8
  "github.com/spf13/cobra"
7
9
  )
8
10
 
11
+ var Version = "dev"
12
+
9
13
  var rootCmd = &cobra.Command{
10
- Use: "fizzy-cli",
11
- Short: "Fizzy CLI",
12
- Long: `Fizzy CLI`,
13
- // Run: func(cmd *cobra.Command, args []string) { },
14
+ Use: "fizzy-cli",
15
+ Short: "Fizzy CLI",
16
+ Long: `Fizzy CLI`,
17
+ Version: Version,
18
+ PersistentPreRun: func(cmd *cobra.Command, args []string) {
19
+ a, _ := app.New()
20
+ if a != nil {
21
+ cmd.SetContext(a.ToContext(cmd.Context()))
22
+ }
23
+ },
14
24
  }
15
25
 
16
26
  func Execute() {
@@ -24,4 +34,5 @@ func init() {
24
34
  // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.fizzy-cli.yaml)")
25
35
 
26
36
  rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
37
+ rootCmd.SetVersionTemplate(fmt.Sprintf("fizzy-cli v%s\n", Version))
27
38
  }
package/cmd/use.go ADDED
@@ -0,0 +1,85 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+
7
+ "github.com/rogeriopvl/fizzy/internal/app"
8
+ "github.com/rogeriopvl/fizzy/internal/config"
9
+ "github.com/spf13/cobra"
10
+ )
11
+
12
+ var useCmd = &cobra.Command{
13
+ Use: "use",
14
+ Short: "Set the active board or account",
15
+ Long: `Set the active board or account to use for subsequent commands`,
16
+ Run: func(cmd *cobra.Command, args []string) {
17
+ if err := handleUse(cmd); err != nil {
18
+ fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
19
+ }
20
+ },
21
+ }
22
+
23
+ func handleUse(cmd *cobra.Command) error {
24
+ board, _ := cmd.Flags().GetString("board")
25
+ account, _ := cmd.Flags().GetString("account")
26
+
27
+ if board == "" && account == "" {
28
+ return fmt.Errorf("must specify either --board or --account")
29
+ }
30
+
31
+ if board != "" && account != "" {
32
+ return fmt.Errorf("cannot specify both --board and --account")
33
+ }
34
+
35
+ cfg, err := config.Load()
36
+ if err != nil {
37
+ return fmt.Errorf("loading config: %w", err)
38
+ }
39
+
40
+ if board != "" {
41
+ a := app.FromContext(cmd.Context())
42
+ if a == nil || a.Client == nil {
43
+ return fmt.Errorf("API client not available")
44
+ }
45
+
46
+ boards, err := a.Client.GetBoards(context.Background())
47
+ if err != nil {
48
+ return fmt.Errorf("fetching boards: %w", err)
49
+ }
50
+
51
+ var boardID string
52
+ for _, b := range boards {
53
+ if b.Name == board {
54
+ boardID = b.ID
55
+ break
56
+ }
57
+ }
58
+
59
+ if boardID == "" {
60
+ return fmt.Errorf("board '%s' not found", board)
61
+ }
62
+
63
+ cfg.SelectedBoard = boardID
64
+ if err := cfg.Save(); err != nil {
65
+ return fmt.Errorf("saving config: %w", err)
66
+ }
67
+ fmt.Printf("Selected board: %s\n", board)
68
+ }
69
+
70
+ if account != "" {
71
+ cfg.SelectedAccount = account
72
+ if err := cfg.Save(); err != nil {
73
+ return fmt.Errorf("saving config: %w", err)
74
+ }
75
+ fmt.Printf("Selected account: %s\n", account)
76
+ }
77
+
78
+ return nil
79
+ }
80
+
81
+ func init() {
82
+ rootCmd.AddCommand(useCmd)
83
+ useCmd.Flags().String("board", "", "Board name to use")
84
+ useCmd.Flags().String("account", "", "Account slug to use")
85
+ }
@@ -0,0 +1,186 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "fmt"
7
+ "net/http"
8
+ "net/http/httptest"
9
+ "testing"
10
+
11
+ "github.com/rogeriopvl/fizzy/internal/api"
12
+ "github.com/rogeriopvl/fizzy/internal/app"
13
+ "github.com/rogeriopvl/fizzy/internal/config"
14
+ "github.com/rogeriopvl/fizzy/internal/testutil"
15
+ "github.com/spf13/cobra"
16
+ )
17
+
18
+ func newUseCmd() *cobra.Command {
19
+ cmd := &cobra.Command{
20
+ Use: "use",
21
+ Short: "Set the active board or account",
22
+ Long: `Set the active board or account to use for subsequent commands`,
23
+ Run: func(cmd *cobra.Command, args []string) {
24
+ if err := handleUse(cmd); err != nil {
25
+ fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
26
+ }
27
+ },
28
+ }
29
+ cmd.Flags().String("board", "", "Board name to use")
30
+ cmd.Flags().String("account", "", "Account slug to use")
31
+ return cmd
32
+ }
33
+
34
+ func TestUseCommandSetBoard(t *testing.T) {
35
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
36
+ if r.URL.Path != "/boards" {
37
+ t.Errorf("expected /boards, got %s", r.URL.Path)
38
+ }
39
+ if r.Method != http.MethodGet {
40
+ t.Errorf("expected GET, got %s", r.Method)
41
+ }
42
+
43
+ auth := r.Header.Get("Authorization")
44
+ if auth == "" {
45
+ t.Error("missing Authorization header")
46
+ }
47
+ if auth != "Bearer test-token" {
48
+ t.Errorf("expected Bearer test-token, got %s", auth)
49
+ }
50
+
51
+ w.Header().Set("Content-Type", "application/json")
52
+ response := []api.Board{
53
+ {
54
+ ID: "board-123",
55
+ Name: "My Project",
56
+ },
57
+ {
58
+ ID: "board-456",
59
+ Name: "Other Board",
60
+ },
61
+ }
62
+ json.NewEncoder(w).Encode(response)
63
+ }))
64
+ defer server.Close()
65
+
66
+ tmpDir := t.TempDir()
67
+ t.Setenv("HOME", tmpDir)
68
+
69
+ client := testutil.NewTestClient(server.URL, "", "", "test-token")
70
+ cfg := &config.Config{}
71
+ testApp := &app.App{
72
+ Client: client,
73
+ Config: cfg,
74
+ }
75
+
76
+ cmd := newUseCmd()
77
+ cmd.SetContext(testApp.ToContext(context.Background()))
78
+ cmd.ParseFlags([]string{"--board", "My Project"})
79
+
80
+ if err := handleUse(cmd); err != nil {
81
+ t.Fatalf("handleUse failed: %v", err)
82
+ }
83
+
84
+ savedCfg, err := config.Load()
85
+ if err != nil {
86
+ t.Fatalf("failed to load config: %v", err)
87
+ }
88
+
89
+ if savedCfg.SelectedBoard != "board-123" {
90
+ t.Errorf("expected SelectedBoard=board-123, got %s", savedCfg.SelectedBoard)
91
+ }
92
+ }
93
+
94
+ func TestUseCommandSetAccount(t *testing.T) {
95
+ tmpDir := t.TempDir()
96
+ t.Setenv("HOME", tmpDir)
97
+
98
+ cmd := newUseCmd()
99
+ cmd.ParseFlags([]string{"--account", "my-company"})
100
+
101
+ cfg := &config.Config{}
102
+ testApp := &app.App{Config: cfg}
103
+ cmd.SetContext(testApp.ToContext(context.Background()))
104
+
105
+ if err := handleUse(cmd); err != nil {
106
+ t.Fatalf("handleUse failed: %v", err)
107
+ }
108
+
109
+ savedCfg, err := config.Load()
110
+ if err != nil {
111
+ t.Fatalf("failed to load config: %v", err)
112
+ }
113
+
114
+ if savedCfg.SelectedAccount != "my-company" {
115
+ t.Errorf("expected SelectedAccount=my-company, got %s", savedCfg.SelectedAccount)
116
+ }
117
+ }
118
+
119
+ func TestUseCommandBoardNotFound(t *testing.T) {
120
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
121
+ if r.Method != http.MethodGet {
122
+ t.Errorf("expected GET, got %s", r.Method)
123
+ }
124
+
125
+ auth := r.Header.Get("Authorization")
126
+ if auth != "Bearer test-token" {
127
+ t.Errorf("expected Bearer test-token, got %s", auth)
128
+ }
129
+
130
+ w.Header().Set("Content-Type", "application/json")
131
+ response := []api.Board{
132
+ {ID: "board-123", Name: "Existing Board"},
133
+ }
134
+ json.NewEncoder(w).Encode(response)
135
+ }))
136
+ defer server.Close()
137
+
138
+ tmpDir := t.TempDir()
139
+ t.Setenv("HOME", tmpDir)
140
+
141
+ client := testutil.NewTestClient(server.URL, "", "", "test-token")
142
+ testApp := &app.App{
143
+ Client: client,
144
+ Config: &config.Config{},
145
+ }
146
+
147
+ cmd := newUseCmd()
148
+ cmd.SetContext(testApp.ToContext(context.Background()))
149
+ cmd.ParseFlags([]string{"--board", "Nonexistent Board"})
150
+
151
+ err := handleUse(cmd)
152
+ if err == nil {
153
+ t.Errorf("expected error for nonexistent board")
154
+ }
155
+ if err.Error() != "board 'Nonexistent Board' not found" {
156
+ t.Errorf("expected 'not found' error, got %v", err)
157
+ }
158
+ }
159
+
160
+ func TestUseCommandBothFlagsError(t *testing.T) {
161
+ cmd := newUseCmd()
162
+ cmd.ParseFlags([]string{"--board", "Board", "--account", "Account"})
163
+ cmd.SetContext(context.Background())
164
+
165
+ err := handleUse(cmd)
166
+ if err == nil {
167
+ t.Errorf("expected error when both flags provided")
168
+ }
169
+ if err.Error() != "cannot specify both --board and --account" {
170
+ t.Errorf("expected 'both flags' error, got %v", err)
171
+ }
172
+ }
173
+
174
+ func TestUseCommandNoFlagsError(t *testing.T) {
175
+ cmd := newUseCmd()
176
+ cmd.SetContext(context.Background())
177
+ cmd.ParseFlags([]string{})
178
+
179
+ err := handleUse(cmd)
180
+ if err == nil {
181
+ t.Errorf("expected error when no flags provided")
182
+ }
183
+ if err.Error() != "must specify either --board or --account" {
184
+ t.Errorf("expected 'no flags' error, got %v", err)
185
+ }
186
+ }