fizzy-cli 0.3.0 → 0.5.0

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.
@@ -0,0 +1,46 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "strconv"
7
+
8
+ "github.com/rogeriopvl/fizzy/internal/app"
9
+ "github.com/spf13/cobra"
10
+ )
11
+
12
+ var cardWatchCmd = &cobra.Command{
13
+ Use: "watch <card_number>",
14
+ Short: "Subscribe to card notifications",
15
+ Long: `Subscribe to notifications for an existing card`,
16
+ Args: cobra.ExactArgs(1),
17
+ Run: func(cmd *cobra.Command, args []string) {
18
+ if err := handleWatchCard(cmd, args[0]); err != nil {
19
+ fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
20
+ }
21
+ },
22
+ }
23
+
24
+ func handleWatchCard(cmd *cobra.Command, cardNumber string) error {
25
+ cardNum, err := strconv.Atoi(cardNumber)
26
+ if err != nil {
27
+ return fmt.Errorf("invalid card number: %w", err)
28
+ }
29
+
30
+ a := app.FromContext(cmd.Context())
31
+ if a == nil || a.Client == nil {
32
+ return fmt.Errorf("API client not available")
33
+ }
34
+
35
+ _, err = a.Client.PostCardWatch(context.Background(), cardNum)
36
+ if err != nil {
37
+ return fmt.Errorf("watching card: %w", err)
38
+ }
39
+
40
+ fmt.Printf("✓ Now watching card #%d\n", cardNum)
41
+ return nil
42
+ }
43
+
44
+ func init() {
45
+ cardCmd.AddCommand(cardWatchCmd)
46
+ }
@@ -0,0 +1,92 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "net/http"
6
+ "net/http/httptest"
7
+ "testing"
8
+
9
+ "github.com/rogeriopvl/fizzy/internal/app"
10
+ "github.com/rogeriopvl/fizzy/internal/testutil"
11
+ )
12
+
13
+ func TestCardWatchCommandSuccess(t *testing.T) {
14
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
15
+ if r.URL.Path != "/cards/123/watch" {
16
+ t.Errorf("expected /cards/123/watch, got %s", r.URL.Path)
17
+ }
18
+ if r.Method != http.MethodPost {
19
+ t.Errorf("expected POST, got %s", r.Method)
20
+ }
21
+
22
+ auth := r.Header.Get("Authorization")
23
+ if auth != "Bearer test-token" {
24
+ t.Errorf("expected Bearer test-token, got %s", auth)
25
+ }
26
+
27
+ w.WriteHeader(http.StatusNoContent)
28
+ }))
29
+ defer server.Close()
30
+
31
+ client := testutil.NewTestClient(server.URL, "", "", "test-token")
32
+ testApp := &app.App{Client: client}
33
+
34
+ cmd := cardWatchCmd
35
+ cmd.SetContext(testApp.ToContext(context.Background()))
36
+
37
+ if err := handleWatchCard(cmd, "123"); err != nil {
38
+ t.Fatalf("handleWatchCard failed: %v", err)
39
+ }
40
+ }
41
+
42
+ func TestCardWatchCommandInvalidCardNumber(t *testing.T) {
43
+ testApp := &app.App{}
44
+
45
+ cmd := cardWatchCmd
46
+ cmd.SetContext(testApp.ToContext(context.Background()))
47
+
48
+ err := handleWatchCard(cmd, "not-a-number")
49
+ if err == nil {
50
+ t.Errorf("expected error for invalid card number")
51
+ }
52
+ if err.Error() != "invalid card number: strconv.Atoi: parsing \"not-a-number\": invalid syntax" {
53
+ t.Errorf("expected invalid card number error, got %v", err)
54
+ }
55
+ }
56
+
57
+ func TestCardWatchCommandNoClient(t *testing.T) {
58
+ testApp := &app.App{}
59
+
60
+ cmd := cardWatchCmd
61
+ cmd.SetContext(testApp.ToContext(context.Background()))
62
+
63
+ err := handleWatchCard(cmd, "123")
64
+ if err == nil {
65
+ t.Errorf("expected error when client not available")
66
+ }
67
+ if err.Error() != "API client not available" {
68
+ t.Errorf("expected 'client not available' error, got %v", err)
69
+ }
70
+ }
71
+
72
+ func TestCardWatchCommandAPIError(t *testing.T) {
73
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
74
+ w.WriteHeader(http.StatusInternalServerError)
75
+ w.Write([]byte("Internal Server Error"))
76
+ }))
77
+ defer server.Close()
78
+
79
+ client := testutil.NewTestClient(server.URL, "", "", "test-token")
80
+ testApp := &app.App{Client: client}
81
+
82
+ cmd := cardWatchCmd
83
+ cmd.SetContext(testApp.ToContext(context.Background()))
84
+
85
+ err := handleWatchCard(cmd, "123")
86
+ if err == nil {
87
+ t.Errorf("expected error for API failure")
88
+ }
89
+ if err.Error() != "watching card: unexpected status code 500: Internal Server Error" {
90
+ t.Errorf("expected API error, got %v", err)
91
+ }
92
+ }
@@ -11,11 +11,6 @@ import (
11
11
  "github.com/spf13/cobra"
12
12
  )
13
13
 
14
- var (
15
- columnName string
16
- columnColor string
17
- )
18
-
19
14
  func buildColorAliases() map[string]api.Color {
20
15
  aliases := make(map[string]api.Color)
21
16
  for _, colorDef := range colors.All {
@@ -49,15 +44,19 @@ func handleCreateColumn(cmd *cobra.Command) error {
49
44
  return fmt.Errorf("API client not available")
50
45
  }
51
46
 
47
+ // Read flag values directly from command
48
+ name, _ := cmd.Flags().GetString("name")
49
+ colorStr, _ := cmd.Flags().GetString("color")
50
+
52
51
  payload := api.CreateColumnPayload{
53
- Name: columnName,
52
+ Name: name,
54
53
  }
55
54
 
56
- if columnColor != "" {
55
+ if colorStr != "" {
57
56
  colorAliases := buildColorAliases()
58
- color, ok := colorAliases[columnColor]
57
+ color, ok := colorAliases[colorStr]
59
58
  if !ok {
60
- return fmt.Errorf("invalid color '%s'. Available colors: %s", columnColor, getAvailableColors())
59
+ return fmt.Errorf("invalid color '%s'. Available colors: %s", colorStr, getAvailableColors())
61
60
  }
62
61
  payload.Color = &color
63
62
  }
@@ -67,14 +66,14 @@ func handleCreateColumn(cmd *cobra.Command) error {
67
66
  return fmt.Errorf("creating column: %w", err)
68
67
  }
69
68
 
70
- fmt.Printf("✓ Column '%s' created successfully\n", columnName)
69
+ fmt.Printf("✓ Column '%s' created successfully\n", name)
71
70
  return nil
72
71
  }
73
72
 
74
73
  func init() {
75
- columnCreateCmd.Flags().StringVarP(&columnName, "name", "n", "", "Column name (required)")
74
+ columnCreateCmd.Flags().StringP("name", "n", "", "Column name (required)")
76
75
  columnCreateCmd.MarkFlagRequired("name")
77
- columnCreateCmd.Flags().StringVar(&columnColor, "color", "", fmt.Sprintf("Column color (optional). Available: %s", getAvailableColors()))
76
+ columnCreateCmd.Flags().String("color", "", fmt.Sprintf("Column color (optional). Available: %s", getAvailableColors()))
78
77
 
79
78
  columnCmd.AddCommand(columnCreateCmd)
80
79
  }
@@ -57,9 +57,6 @@ func TestColumnCreateCommandSuccess(t *testing.T) {
57
57
  cmd.SetContext(testApp.ToContext(context.Background()))
58
58
  cmd.ParseFlags([]string{"--name", "Todo"})
59
59
 
60
- columnName = "Todo"
61
- columnColor = ""
62
-
63
60
  if err := handleCreateColumn(cmd); err != nil {
64
61
  t.Fatalf("handleCreateColumn failed: %v", err)
65
62
  }
@@ -97,9 +94,6 @@ func TestColumnCreateCommandWithColor(t *testing.T) {
97
94
  cmd.SetContext(testApp.ToContext(context.Background()))
98
95
  cmd.ParseFlags([]string{"--name", "In Progress", "--color", "lime"})
99
96
 
100
- columnName = "In Progress"
101
- columnColor = "lime"
102
-
103
97
  if err := handleCreateColumn(cmd); err != nil {
104
98
  t.Fatalf("handleCreateColumn failed: %v", err)
105
99
  }
@@ -113,9 +107,6 @@ func TestColumnCreateCommandInvalidColor(t *testing.T) {
113
107
  cmd.SetContext(testApp.ToContext(context.Background()))
114
108
  cmd.ParseFlags([]string{"--name", "Todo", "--color", "invalid"})
115
109
 
116
- columnName = "Todo"
117
- columnColor = "invalid"
118
-
119
110
  err := handleCreateColumn(cmd)
120
111
  if err == nil {
121
112
  t.Errorf("expected error for invalid color")
@@ -136,10 +127,7 @@ func TestColumnCreateCommandNoBoard(t *testing.T) {
136
127
 
137
128
  cmd := columnCreateCmd
138
129
  cmd.SetContext(testApp.ToContext(context.Background()))
139
- cmd.ParseFlags([]string{"--name", "Todo"})
140
-
141
- columnName = "Todo"
142
- columnColor = ""
130
+ cmd.ParseFlags([]string{"--name", "Todo", "--color", ""})
143
131
 
144
132
  err := handleCreateColumn(cmd)
145
133
  if err == nil {
@@ -155,10 +143,7 @@ func TestColumnCreateCommandNoClient(t *testing.T) {
155
143
 
156
144
  cmd := columnCreateCmd
157
145
  cmd.SetContext(testApp.ToContext(context.Background()))
158
- cmd.ParseFlags([]string{"--name", "Todo"})
159
-
160
- columnName = "Todo"
161
- columnColor = ""
146
+ cmd.ParseFlags([]string{"--name", "Todo", "--color", ""})
162
147
 
163
148
  err := handleCreateColumn(cmd)
164
149
  if err == nil {
@@ -181,10 +166,7 @@ func TestColumnCreateCommandAPIError(t *testing.T) {
181
166
 
182
167
  cmd := columnCreateCmd
183
168
  cmd.SetContext(testApp.ToContext(context.Background()))
184
- cmd.ParseFlags([]string{"--name", "Todo"})
185
-
186
- columnName = "Todo"
187
- columnColor = ""
169
+ cmd.ParseFlags([]string{"--name", "Todo", "--color", ""})
188
170
 
189
171
  err := handleCreateColumn(cmd)
190
172
  if err == nil {
package/cmd/comment.go ADDED
@@ -0,0 +1,14 @@
1
+ // Package cmd
2
+ package cmd
3
+
4
+ import "github.com/spf13/cobra"
5
+
6
+ var commentCmd = &cobra.Command{
7
+ Use: "comment",
8
+ Short: "Manage card comments",
9
+ Long: `Manage comments on cards in Fizzy`,
10
+ }
11
+
12
+ func init() {
13
+ rootCmd.AddCommand(commentCmd)
14
+ }
@@ -0,0 +1,51 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "strconv"
7
+
8
+ "github.com/rogeriopvl/fizzy/internal/app"
9
+ "github.com/spf13/cobra"
10
+ )
11
+
12
+ var commentCreateCmd = &cobra.Command{
13
+ Use: "create <card_number>",
14
+ Short: "Create a new comment",
15
+ Long: `Create a new comment on a card`,
16
+ Args: cobra.ExactArgs(1),
17
+ Run: func(cmd *cobra.Command, args []string) {
18
+ if err := handleCreateComment(cmd, args[0]); err != nil {
19
+ fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
20
+ }
21
+ },
22
+ }
23
+
24
+ func handleCreateComment(cmd *cobra.Command, cardNumber string) error {
25
+ cardNum, err := strconv.Atoi(cardNumber)
26
+ if err != nil {
27
+ return fmt.Errorf("invalid card number: %w", err)
28
+ }
29
+
30
+ a := app.FromContext(cmd.Context())
31
+ if a == nil || a.Client == nil {
32
+ return fmt.Errorf("API client not available")
33
+ }
34
+
35
+ body, _ := cmd.Flags().GetString("body")
36
+
37
+ comment, err := a.Client.PostCardComment(context.Background(), cardNum, body)
38
+ if err != nil {
39
+ return fmt.Errorf("creating comment: %w", err)
40
+ }
41
+
42
+ fmt.Printf("✓ Comment created successfully (id: %s)\n", comment.ID)
43
+ return nil
44
+ }
45
+
46
+ func init() {
47
+ commentCreateCmd.Flags().StringP("body", "b", "", "Comment body (required)")
48
+ commentCreateCmd.MarkFlagRequired("body")
49
+
50
+ commentCmd.AddCommand(commentCreateCmd)
51
+ }
@@ -0,0 +1,129 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "encoding/json"
6
+ "io"
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/testutil"
14
+ )
15
+
16
+ func TestCommentCreateCommandSuccess(t *testing.T) {
17
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18
+ if r.URL.Path != "/cards/123/comments" {
19
+ t.Errorf("expected /cards/123/comments, got %s", r.URL.Path)
20
+ }
21
+ if r.Method != http.MethodPost {
22
+ t.Errorf("expected POST, got %s", r.Method)
23
+ }
24
+
25
+ auth := r.Header.Get("Authorization")
26
+ if auth != "Bearer test-token" {
27
+ t.Errorf("expected Bearer test-token, got %s", auth)
28
+ }
29
+
30
+ if r.Header.Get("Content-Type") != "application/json" {
31
+ t.Errorf("expected Content-Type: application/json, got %s", r.Header.Get("Content-Type"))
32
+ }
33
+
34
+ body, _ := io.ReadAll(r.Body)
35
+ var payload map[string]map[string]string
36
+ if err := json.Unmarshal(body, &payload); err != nil {
37
+ t.Fatalf("failed to unmarshal request body: %v", err)
38
+ }
39
+
40
+ commentPayload := payload["comment"]
41
+ if commentPayload["body"] != "This is a test comment" {
42
+ t.Errorf("expected body 'This is a test comment', got %s", commentPayload["body"])
43
+ }
44
+
45
+ w.Header().Set("Content-Type", "application/json")
46
+ w.WriteHeader(http.StatusCreated)
47
+ response := api.Comment{
48
+ ID: "comment-789",
49
+ CreatedAt: "2025-01-01T00:00:00Z",
50
+ Creator: api.User{ID: "user-1", Name: "John Doe"},
51
+ }
52
+ json.NewEncoder(w).Encode(response)
53
+ }))
54
+ defer server.Close()
55
+
56
+ client := testutil.NewTestClient(server.URL, "", "", "test-token")
57
+ testApp := &app.App{Client: client}
58
+
59
+ cmd := commentCreateCmd
60
+ cmd.SetContext(testApp.ToContext(context.Background()))
61
+ cmd.ParseFlags([]string{
62
+ "--body", "This is a test comment",
63
+ })
64
+
65
+ if err := handleCreateComment(cmd, "123"); err != nil {
66
+ t.Fatalf("handleCreateComment failed: %v", err)
67
+ }
68
+ }
69
+
70
+ func TestCommentCreateCommandInvalidCardNumber(t *testing.T) {
71
+ testApp := &app.App{}
72
+
73
+ cmd := commentCreateCmd
74
+ cmd.SetContext(testApp.ToContext(context.Background()))
75
+ cmd.ParseFlags([]string{
76
+ "--body", "Test comment",
77
+ })
78
+
79
+ err := handleCreateComment(cmd, "not-a-number")
80
+ if err == nil {
81
+ t.Errorf("expected error for invalid card number")
82
+ }
83
+ if err.Error() != "invalid card number: strconv.Atoi: parsing \"not-a-number\": invalid syntax" {
84
+ t.Errorf("expected invalid card number error, got %v", err)
85
+ }
86
+ }
87
+
88
+ func TestCommentCreateCommandNoClient(t *testing.T) {
89
+ testApp := &app.App{}
90
+
91
+ cmd := commentCreateCmd
92
+ cmd.SetContext(testApp.ToContext(context.Background()))
93
+ cmd.ParseFlags([]string{
94
+ "--body", "Test comment",
95
+ })
96
+
97
+ err := handleCreateComment(cmd, "123")
98
+ if err == nil {
99
+ t.Errorf("expected error when client not available")
100
+ }
101
+ if err.Error() != "API client not available" {
102
+ t.Errorf("expected 'client not available' error, got %v", err)
103
+ }
104
+ }
105
+
106
+ func TestCommentCreateCommandAPIError(t *testing.T) {
107
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
108
+ w.WriteHeader(http.StatusInternalServerError)
109
+ w.Write([]byte("Internal Server Error"))
110
+ }))
111
+ defer server.Close()
112
+
113
+ client := testutil.NewTestClient(server.URL, "", "", "test-token")
114
+ testApp := &app.App{Client: client}
115
+
116
+ cmd := commentCreateCmd
117
+ cmd.SetContext(testApp.ToContext(context.Background()))
118
+ cmd.ParseFlags([]string{
119
+ "--body", "Test comment",
120
+ })
121
+
122
+ err := handleCreateComment(cmd, "123")
123
+ if err == nil {
124
+ t.Errorf("expected error for API failure")
125
+ }
126
+ if err.Error() != "creating comment: unexpected status code 500: Internal Server Error" {
127
+ t.Errorf("expected API error, got %v", err)
128
+ }
129
+ }
@@ -0,0 +1,51 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "strconv"
7
+
8
+ "github.com/rogeriopvl/fizzy/internal/app"
9
+ "github.com/rogeriopvl/fizzy/internal/ui"
10
+ "github.com/spf13/cobra"
11
+ )
12
+
13
+ var commentListCmd = &cobra.Command{
14
+ Use: "list <card_number>",
15
+ Short: "List comments on a card",
16
+ Long: `Retrieve and display all comments on a card`,
17
+ Args: cobra.ExactArgs(1),
18
+ Run: func(cmd *cobra.Command, args []string) {
19
+ if err := handleListComments(cmd, args[0]); err != nil {
20
+ fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
21
+ }
22
+ },
23
+ }
24
+
25
+ func handleListComments(cmd *cobra.Command, cardNumber string) error {
26
+ cardNum, err := strconv.Atoi(cardNumber)
27
+ if err != nil {
28
+ return fmt.Errorf("invalid card number: %w", err)
29
+ }
30
+
31
+ a := app.FromContext(cmd.Context())
32
+ if a == nil || a.Client == nil {
33
+ return fmt.Errorf("API client not available")
34
+ }
35
+
36
+ comments, err := a.Client.GetCardComments(context.Background(), cardNum)
37
+ if err != nil {
38
+ return fmt.Errorf("fetching comments: %w", err)
39
+ }
40
+
41
+ if len(comments) == 0 {
42
+ fmt.Println("No comments found")
43
+ return nil
44
+ }
45
+
46
+ return ui.DisplayComments(comments)
47
+ }
48
+
49
+ func init() {
50
+ commentCmd.AddCommand(commentListCmd)
51
+ }
@@ -0,0 +1,132 @@
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 TestCommentListCommandSuccess(t *testing.T) {
16
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17
+ if r.URL.Path != "/cards/123/comments" {
18
+ t.Errorf("expected /cards/123/comments, 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
+ w.Header().Set("Content-Type", "application/json")
30
+ response := []api.Comment{
31
+ {
32
+ ID: "comment-123",
33
+ CreatedAt: "2025-01-01T00:00:00Z",
34
+ UpdatedAt: "2025-01-01T00:00:00Z",
35
+ Creator: api.User{ID: "user-1", Name: "John Doe"},
36
+ },
37
+ {
38
+ ID: "comment-456",
39
+ CreatedAt: "2025-01-02T00:00:00Z",
40
+ UpdatedAt: "2025-01-02T00:00:00Z",
41
+ Creator: api.User{ID: "user-2", Name: "Jane Doe"},
42
+ },
43
+ }
44
+ // Set the body field manually since it's a struct
45
+ response[0].Body.PlainText = "First comment"
46
+ response[0].Body.HTML = "<p>First comment</p>"
47
+ response[1].Body.PlainText = "Second comment"
48
+ response[1].Body.HTML = "<p>Second comment</p>"
49
+ json.NewEncoder(w).Encode(response)
50
+ }))
51
+ defer server.Close()
52
+
53
+ client := testutil.NewTestClient(server.URL, "", "", "test-token")
54
+ testApp := &app.App{Client: client}
55
+
56
+ cmd := commentListCmd
57
+ cmd.SetContext(testApp.ToContext(context.Background()))
58
+
59
+ if err := handleListComments(cmd, "123"); err != nil {
60
+ t.Fatalf("handleListComments failed: %v", err)
61
+ }
62
+ }
63
+
64
+ func TestCommentListCommandNoComments(t *testing.T) {
65
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
66
+ w.Header().Set("Content-Type", "application/json")
67
+ json.NewEncoder(w).Encode([]api.Comment{})
68
+ }))
69
+ defer server.Close()
70
+
71
+ client := testutil.NewTestClient(server.URL, "", "", "test-token")
72
+ testApp := &app.App{Client: client}
73
+
74
+ cmd := commentListCmd
75
+ cmd.SetContext(testApp.ToContext(context.Background()))
76
+
77
+ if err := handleListComments(cmd, "123"); err != nil {
78
+ t.Fatalf("handleListComments failed: %v", err)
79
+ }
80
+ }
81
+
82
+ func TestCommentListCommandInvalidCardNumber(t *testing.T) {
83
+ testApp := &app.App{}
84
+
85
+ cmd := commentListCmd
86
+ cmd.SetContext(testApp.ToContext(context.Background()))
87
+
88
+ err := handleListComments(cmd, "not-a-number")
89
+ if err == nil {
90
+ t.Errorf("expected error for invalid card number")
91
+ }
92
+ if err.Error() != "invalid card number: strconv.Atoi: parsing \"not-a-number\": invalid syntax" {
93
+ t.Errorf("expected invalid card number error, got %v", err)
94
+ }
95
+ }
96
+
97
+ func TestCommentListCommandNoClient(t *testing.T) {
98
+ testApp := &app.App{}
99
+
100
+ cmd := commentListCmd
101
+ cmd.SetContext(testApp.ToContext(context.Background()))
102
+
103
+ err := handleListComments(cmd, "123")
104
+ if err == nil {
105
+ t.Errorf("expected error when client not available")
106
+ }
107
+ if err.Error() != "API client not available" {
108
+ t.Errorf("expected 'client not available' error, got %v", err)
109
+ }
110
+ }
111
+
112
+ func TestCommentListCommandAPIError(t *testing.T) {
113
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
114
+ w.WriteHeader(http.StatusInternalServerError)
115
+ w.Write([]byte("Internal Server Error"))
116
+ }))
117
+ defer server.Close()
118
+
119
+ client := testutil.NewTestClient(server.URL, "", "", "test-token")
120
+ testApp := &app.App{Client: client}
121
+
122
+ cmd := commentListCmd
123
+ cmd.SetContext(testApp.ToContext(context.Background()))
124
+
125
+ err := handleListComments(cmd, "123")
126
+ if err == nil {
127
+ t.Errorf("expected error for API failure")
128
+ }
129
+ if err.Error() != "fetching comments: unexpected status code 500: Internal Server Error" {
130
+ t.Errorf("expected API error, got %v", err)
131
+ }
132
+ }
@@ -0,0 +1,46 @@
1
+ package cmd
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "strconv"
7
+
8
+ "github.com/rogeriopvl/fizzy/internal/app"
9
+ "github.com/rogeriopvl/fizzy/internal/ui"
10
+ "github.com/spf13/cobra"
11
+ )
12
+
13
+ var commentShowCmd = &cobra.Command{
14
+ Use: "show <card_number> <comment_id>",
15
+ Short: "Show a specific comment",
16
+ Long: `Display details of a specific comment on a card`,
17
+ Args: cobra.ExactArgs(2),
18
+ Run: func(cmd *cobra.Command, args []string) {
19
+ if err := handleShowComment(cmd, args[0], args[1]); err != nil {
20
+ fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
21
+ }
22
+ },
23
+ }
24
+
25
+ func handleShowComment(cmd *cobra.Command, cardNumber, commentID string) error {
26
+ cardNum, err := strconv.Atoi(cardNumber)
27
+ if err != nil {
28
+ return fmt.Errorf("invalid card number: %w", err)
29
+ }
30
+
31
+ a := app.FromContext(cmd.Context())
32
+ if a == nil || a.Client == nil {
33
+ return fmt.Errorf("API client not available")
34
+ }
35
+
36
+ comment, err := a.Client.GetCardComment(context.Background(), cardNum, commentID)
37
+ if err != nil {
38
+ return fmt.Errorf("fetching comment: %w", err)
39
+ }
40
+
41
+ return ui.DisplayComment(comment)
42
+ }
43
+
44
+ func init() {
45
+ commentCmd.AddCommand(commentShowCmd)
46
+ }