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.
- package/CHANGELOG.md +28 -0
- package/cmd/board_create.go +15 -16
- package/cmd/board_create_test.go +0 -17
- package/cmd/card_create.go +24 -25
- package/cmd/card_create_test.go +20 -40
- package/cmd/card_golden.go +46 -0
- package/cmd/card_golden_test.go +92 -0
- package/cmd/card_not_now.go +46 -0
- package/cmd/card_not_now_test.go +92 -0
- package/cmd/card_tag.go +51 -0
- package/cmd/card_tag_test.go +112 -0
- package/cmd/card_ungolden.go +46 -0
- package/cmd/card_ungolden_test.go +92 -0
- package/cmd/card_untriage.go +46 -0
- package/cmd/card_untriage_test.go +92 -0
- package/cmd/card_unwatch.go +46 -0
- package/cmd/card_unwatch_test.go +92 -0
- package/cmd/card_update.go +18 -19
- package/cmd/card_update_test.go +28 -24
- package/cmd/card_watch.go +46 -0
- package/cmd/card_watch_test.go +92 -0
- package/cmd/column_create.go +11 -12
- package/cmd/column_create_test.go +3 -21
- package/cmd/comment.go +14 -0
- package/cmd/comment_create.go +51 -0
- package/cmd/comment_create_test.go +129 -0
- package/cmd/comment_list.go +51 -0
- package/cmd/comment_list_test.go +132 -0
- package/cmd/comment_show.go +46 -0
- package/cmd/comment_show_test.go +104 -0
- package/cmd/tag.go +15 -0
- package/cmd/tag_list.go +47 -0
- package/cmd/tag_list_test.go +109 -0
- package/docs/API.md +36 -0
- package/internal/api/client.go +211 -1
- package/internal/ui/comment_list.go +25 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 - 2026-01-25
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
#### Comment Management
|
|
8
|
+
|
|
9
|
+
- `fizzy comment list <card_number>` - List all comments on a card
|
|
10
|
+
- `fizzy comment show <card_number> <comment_id>` - Display a specific comment
|
|
11
|
+
- `fizzy comment add <card_number> <body>` - Create a new comment on a card
|
|
12
|
+
|
|
13
|
+
### Fixes
|
|
14
|
+
|
|
15
|
+
- Fixed error handling in notification read-all command
|
|
16
|
+
- Removed global flag usage in some commands
|
|
17
|
+
|
|
18
|
+
## 0.4.0 - 2026-01-19
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
#### Card Management
|
|
23
|
+
|
|
24
|
+
- `fizzy card not-now <card_number>` - Move a card to "Not Now" status
|
|
25
|
+
- `fizzy card untriage <card_number>` - Send a card back to triage
|
|
26
|
+
- `fizzy card watch <card_number>` - Subscribe to card notifications
|
|
27
|
+
- `fizzy card unwatch <card_number>` - Unsubscribe from card notifications
|
|
28
|
+
- `fizzy card golden <card_number>` - Mark a card as golden
|
|
29
|
+
- `fizzy card ungolden <card_number>` - Remove golden status from a card
|
|
30
|
+
|
|
3
31
|
## 0.3.0 - 2026-01-11
|
|
4
32
|
|
|
5
33
|
### Features
|
package/cmd/board_create.go
CHANGED
|
@@ -9,13 +9,6 @@ import (
|
|
|
9
9
|
"github.com/spf13/cobra"
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
-
var (
|
|
13
|
-
boardName string
|
|
14
|
-
boardAllAccess bool
|
|
15
|
-
boardAutoPostponePeriod int
|
|
16
|
-
boardPublicDescription string
|
|
17
|
-
)
|
|
18
|
-
|
|
19
12
|
var boardCreateCmd = &cobra.Command{
|
|
20
13
|
Use: "create",
|
|
21
14
|
Short: "Create a new board",
|
|
@@ -33,11 +26,17 @@ func handleCreateBoard(cmd *cobra.Command) error {
|
|
|
33
26
|
return fmt.Errorf("API client not available")
|
|
34
27
|
}
|
|
35
28
|
|
|
29
|
+
// Read flag values directly from command
|
|
30
|
+
name, _ := cmd.Flags().GetString("name")
|
|
31
|
+
allAccess, _ := cmd.Flags().GetBool("all-access")
|
|
32
|
+
autoPostponePeriod, _ := cmd.Flags().GetInt("auto-postpone-period")
|
|
33
|
+
publicDescription, _ := cmd.Flags().GetString("description")
|
|
34
|
+
|
|
36
35
|
payload := api.CreateBoardPayload{
|
|
37
|
-
Name:
|
|
38
|
-
AllAccess:
|
|
39
|
-
AutoPostponePeriod:
|
|
40
|
-
PublicDescription:
|
|
36
|
+
Name: name,
|
|
37
|
+
AllAccess: allAccess,
|
|
38
|
+
AutoPostponePeriod: autoPostponePeriod,
|
|
39
|
+
PublicDescription: publicDescription,
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
_, err := a.Client.PostBoards(context.Background(), payload)
|
|
@@ -45,16 +44,16 @@ func handleCreateBoard(cmd *cobra.Command) error {
|
|
|
45
44
|
return fmt.Errorf("creating board: %w", err)
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
fmt.Printf("✓ Board '%s' created successfully\n",
|
|
47
|
+
fmt.Printf("✓ Board '%s' created successfully\n", name)
|
|
49
48
|
return nil
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
func init() {
|
|
53
|
-
boardCreateCmd.Flags().
|
|
52
|
+
boardCreateCmd.Flags().StringP("name", "n", "", "Board name (required)")
|
|
54
53
|
boardCreateCmd.MarkFlagRequired("name")
|
|
55
|
-
boardCreateCmd.Flags().
|
|
56
|
-
boardCreateCmd.Flags().
|
|
57
|
-
boardCreateCmd.Flags().
|
|
54
|
+
boardCreateCmd.Flags().Bool("all-access", false, "Allow all access to the board")
|
|
55
|
+
boardCreateCmd.Flags().Int("auto-postpone-period", 0, "Auto postpone period in days")
|
|
56
|
+
boardCreateCmd.Flags().String("description", "", "Public description of the board")
|
|
58
57
|
|
|
59
58
|
boardCmd.AddCommand(boardCreateCmd)
|
|
60
59
|
}
|
package/cmd/board_create_test.go
CHANGED
|
@@ -54,11 +54,6 @@ func TestBoardCreateCommandSuccess(t *testing.T) {
|
|
|
54
54
|
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
55
55
|
cmd.ParseFlags([]string{"--name", "Test Board"})
|
|
56
56
|
|
|
57
|
-
boardName = "Test Board"
|
|
58
|
-
boardAllAccess = false
|
|
59
|
-
boardAutoPostponePeriod = 0
|
|
60
|
-
boardPublicDescription = ""
|
|
61
|
-
|
|
62
57
|
if err := handleCreateBoard(cmd); err != nil {
|
|
63
58
|
t.Fatalf("handleCreateBoard failed: %v", err)
|
|
64
59
|
}
|
|
@@ -101,11 +96,6 @@ func TestBoardCreateCommandWithAllFlags(t *testing.T) {
|
|
|
101
96
|
"--description", "Team project",
|
|
102
97
|
})
|
|
103
98
|
|
|
104
|
-
boardName = "Test Board"
|
|
105
|
-
boardAllAccess = true
|
|
106
|
-
boardAutoPostponePeriod = 7
|
|
107
|
-
boardPublicDescription = "Team project"
|
|
108
|
-
|
|
109
99
|
if err := handleCreateBoard(cmd); err != nil {
|
|
110
100
|
t.Fatalf("handleCreateBoard failed: %v", err)
|
|
111
101
|
}
|
|
@@ -125,11 +115,6 @@ func TestBoardCreateCommandAPIError(t *testing.T) {
|
|
|
125
115
|
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
126
116
|
cmd.ParseFlags([]string{"--name", "Test Board"})
|
|
127
117
|
|
|
128
|
-
boardName = "Test Board"
|
|
129
|
-
boardAllAccess = false
|
|
130
|
-
boardAutoPostponePeriod = 0
|
|
131
|
-
boardPublicDescription = ""
|
|
132
|
-
|
|
133
118
|
err := handleCreateBoard(cmd)
|
|
134
119
|
if err == nil {
|
|
135
120
|
t.Errorf("expected error for API failure")
|
|
@@ -146,8 +131,6 @@ func TestBoardCreateCommandNoClient(t *testing.T) {
|
|
|
146
131
|
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
147
132
|
cmd.ParseFlags([]string{"--name", "Test Board"})
|
|
148
133
|
|
|
149
|
-
boardName = "Test Board"
|
|
150
|
-
|
|
151
134
|
err := handleCreateBoard(cmd)
|
|
152
135
|
if err == nil {
|
|
153
136
|
t.Errorf("expected error when client not available")
|
package/cmd/card_create.go
CHANGED
|
@@ -9,16 +9,6 @@ import (
|
|
|
9
9
|
"github.com/spf13/cobra"
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
-
var (
|
|
13
|
-
cardTitle string
|
|
14
|
-
cardDescription string
|
|
15
|
-
cardStatus string
|
|
16
|
-
cardImageURL string
|
|
17
|
-
cardTagIDs []string
|
|
18
|
-
cardCreatedAt string
|
|
19
|
-
cardLastActiveAt string
|
|
20
|
-
)
|
|
21
|
-
|
|
22
12
|
var cardCreateCmd = &cobra.Command{
|
|
23
13
|
Use: "create",
|
|
24
14
|
Short: "Create a new card",
|
|
@@ -40,14 +30,23 @@ func handleCreateCard(cmd *cobra.Command) error {
|
|
|
40
30
|
return fmt.Errorf("no board selected")
|
|
41
31
|
}
|
|
42
32
|
|
|
33
|
+
// Read flag values directly from command
|
|
34
|
+
title, _ := cmd.Flags().GetString("title")
|
|
35
|
+
description, _ := cmd.Flags().GetString("description")
|
|
36
|
+
status, _ := cmd.Flags().GetString("status")
|
|
37
|
+
imageURL, _ := cmd.Flags().GetString("image-url")
|
|
38
|
+
tagIDs, _ := cmd.Flags().GetStringSlice("tag-id")
|
|
39
|
+
createdAt, _ := cmd.Flags().GetString("created-at")
|
|
40
|
+
lastActiveAt, _ := cmd.Flags().GetString("last-active-at")
|
|
41
|
+
|
|
43
42
|
payload := api.CreateCardPayload{
|
|
44
|
-
Title:
|
|
45
|
-
Description:
|
|
46
|
-
Status:
|
|
47
|
-
ImageURL:
|
|
48
|
-
TagIDS:
|
|
49
|
-
CreatedAt:
|
|
50
|
-
LastActiveAt:
|
|
43
|
+
Title: title,
|
|
44
|
+
Description: description,
|
|
45
|
+
Status: status,
|
|
46
|
+
ImageURL: imageURL,
|
|
47
|
+
TagIDS: tagIDs,
|
|
48
|
+
CreatedAt: createdAt,
|
|
49
|
+
LastActiveAt: lastActiveAt,
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
_, err := a.Client.PostCards(context.Background(), payload)
|
|
@@ -55,19 +54,19 @@ func handleCreateCard(cmd *cobra.Command) error {
|
|
|
55
54
|
return fmt.Errorf("creating card: %w", err)
|
|
56
55
|
}
|
|
57
56
|
|
|
58
|
-
fmt.Printf("✓ Card '%s' created successfully\n",
|
|
57
|
+
fmt.Printf("✓ Card '%s' created successfully\n", title)
|
|
59
58
|
return nil
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
func init() {
|
|
63
|
-
cardCreateCmd.Flags().
|
|
62
|
+
cardCreateCmd.Flags().StringP("title", "t", "", "Card title (required)")
|
|
64
63
|
cardCreateCmd.MarkFlagRequired("title")
|
|
65
|
-
cardCreateCmd.Flags().
|
|
66
|
-
cardCreateCmd.Flags().
|
|
67
|
-
cardCreateCmd.Flags().
|
|
68
|
-
cardCreateCmd.Flags().
|
|
69
|
-
cardCreateCmd.Flags().
|
|
70
|
-
cardCreateCmd.Flags().
|
|
64
|
+
cardCreateCmd.Flags().StringP("description", "d", "", "Card description")
|
|
65
|
+
cardCreateCmd.Flags().String("status", "", "Card status")
|
|
66
|
+
cardCreateCmd.Flags().String("image-url", "", "Card image URL")
|
|
67
|
+
cardCreateCmd.Flags().StringSlice("tag-id", []string{}, "Tag ID (can be used multiple times)")
|
|
68
|
+
cardCreateCmd.Flags().String("created-at", "", "Creation timestamp")
|
|
69
|
+
cardCreateCmd.Flags().String("last-active-at", "", "Last active timestamp")
|
|
71
70
|
|
|
72
71
|
cardCmd.AddCommand(cardCreateCmd)
|
|
73
72
|
}
|
package/cmd/card_create_test.go
CHANGED
|
@@ -58,14 +58,10 @@ func TestCardCreateCommandSuccess(t *testing.T) {
|
|
|
58
58
|
|
|
59
59
|
cmd := cardCreateCmd
|
|
60
60
|
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
cardImageURL = ""
|
|
66
|
-
cardTagIDs = []string{}
|
|
67
|
-
cardCreatedAt = ""
|
|
68
|
-
cardLastActiveAt = ""
|
|
61
|
+
cmd.ParseFlags([]string{
|
|
62
|
+
"--title", "Implement feature",
|
|
63
|
+
"--description", "A detailed description",
|
|
64
|
+
})
|
|
69
65
|
|
|
70
66
|
if err := handleCreateCard(cmd); err != nil {
|
|
71
67
|
t.Fatalf("handleCreateCard failed: %v", err)
|
|
@@ -108,14 +104,13 @@ func TestCardCreateCommandWithAllFields(t *testing.T) {
|
|
|
108
104
|
|
|
109
105
|
cmd := cardCreateCmd
|
|
110
106
|
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
cardLastActiveAt = ""
|
|
107
|
+
cmd.ParseFlags([]string{
|
|
108
|
+
"--title", "Fix bug",
|
|
109
|
+
"--status", "in_progress",
|
|
110
|
+
"--image-url", "https://example.com/image.jpg",
|
|
111
|
+
"--tag-id", "tag-1",
|
|
112
|
+
"--tag-id", "tag-2",
|
|
113
|
+
})
|
|
119
114
|
|
|
120
115
|
if err := handleCreateCard(cmd); err != nil {
|
|
121
116
|
t.Fatalf("handleCreateCard failed: %v", err)
|
|
@@ -131,14 +126,9 @@ func TestCardCreateCommandNoBoard(t *testing.T) {
|
|
|
131
126
|
|
|
132
127
|
cmd := cardCreateCmd
|
|
133
128
|
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
cardStatus = ""
|
|
138
|
-
cardImageURL = ""
|
|
139
|
-
cardTagIDs = []string{}
|
|
140
|
-
cardCreatedAt = ""
|
|
141
|
-
cardLastActiveAt = ""
|
|
129
|
+
cmd.ParseFlags([]string{
|
|
130
|
+
"--title", "Test",
|
|
131
|
+
})
|
|
142
132
|
|
|
143
133
|
err := handleCreateCard(cmd)
|
|
144
134
|
if err == nil {
|
|
@@ -154,14 +144,9 @@ func TestCardCreateCommandNoClient(t *testing.T) {
|
|
|
154
144
|
|
|
155
145
|
cmd := cardCreateCmd
|
|
156
146
|
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
cardStatus = ""
|
|
161
|
-
cardImageURL = ""
|
|
162
|
-
cardTagIDs = []string{}
|
|
163
|
-
cardCreatedAt = ""
|
|
164
|
-
cardLastActiveAt = ""
|
|
147
|
+
cmd.ParseFlags([]string{
|
|
148
|
+
"--title", "Test",
|
|
149
|
+
})
|
|
165
150
|
|
|
166
151
|
err := handleCreateCard(cmd)
|
|
167
152
|
if err == nil {
|
|
@@ -187,14 +172,9 @@ func TestCardCreateCommandAPIError(t *testing.T) {
|
|
|
187
172
|
|
|
188
173
|
cmd := cardCreateCmd
|
|
189
174
|
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
cardStatus = ""
|
|
194
|
-
cardImageURL = ""
|
|
195
|
-
cardTagIDs = []string{}
|
|
196
|
-
cardCreatedAt = ""
|
|
197
|
-
cardLastActiveAt = ""
|
|
175
|
+
cmd.ParseFlags([]string{
|
|
176
|
+
"--title", "Test",
|
|
177
|
+
})
|
|
198
178
|
|
|
199
179
|
err := handleCreateCard(cmd)
|
|
200
180
|
if err == nil {
|
|
@@ -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 cardGoldenCmd = &cobra.Command{
|
|
13
|
+
Use: "golden <card_number>",
|
|
14
|
+
Short: "Mark a card as golden",
|
|
15
|
+
Long: `Mark an existing card as golden`,
|
|
16
|
+
Args: cobra.ExactArgs(1),
|
|
17
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
18
|
+
if err := handleGoldenCard(cmd, args[0]); err != nil {
|
|
19
|
+
fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func handleGoldenCard(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.PostCardGoldenness(context.Background(), cardNum)
|
|
36
|
+
if err != nil {
|
|
37
|
+
return fmt.Errorf("marking card as golden: %w", err)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fmt.Printf("✓ Card #%d marked as golden\n", cardNum)
|
|
41
|
+
return nil
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func init() {
|
|
45
|
+
cardCmd.AddCommand(cardGoldenCmd)
|
|
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 TestCardGoldenCommandSuccess(t *testing.T) {
|
|
14
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
15
|
+
if r.URL.Path != "/cards/123/goldness" {
|
|
16
|
+
t.Errorf("expected /cards/123/goldness, 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 := cardGoldenCmd
|
|
35
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
36
|
+
|
|
37
|
+
if err := handleGoldenCard(cmd, "123"); err != nil {
|
|
38
|
+
t.Fatalf("handleGoldenCard failed: %v", err)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func TestCardGoldenCommandInvalidCardNumber(t *testing.T) {
|
|
43
|
+
testApp := &app.App{}
|
|
44
|
+
|
|
45
|
+
cmd := cardGoldenCmd
|
|
46
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
47
|
+
|
|
48
|
+
err := handleGoldenCard(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 TestCardGoldenCommandNoClient(t *testing.T) {
|
|
58
|
+
testApp := &app.App{}
|
|
59
|
+
|
|
60
|
+
cmd := cardGoldenCmd
|
|
61
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
62
|
+
|
|
63
|
+
err := handleGoldenCard(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 TestCardGoldenCommandAPIError(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 := cardGoldenCmd
|
|
83
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
84
|
+
|
|
85
|
+
err := handleGoldenCard(cmd, "123")
|
|
86
|
+
if err == nil {
|
|
87
|
+
t.Errorf("expected error for API failure")
|
|
88
|
+
}
|
|
89
|
+
if err.Error() != "marking card as golden: unexpected status code 500: Internal Server Error" {
|
|
90
|
+
t.Errorf("expected API error, got %v", err)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -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 cardNotNowCmd = &cobra.Command{
|
|
13
|
+
Use: "not-now <card_number>",
|
|
14
|
+
Short: "Move a card to Not Now status",
|
|
15
|
+
Long: `Move an existing card to the "Not Now" status`,
|
|
16
|
+
Args: cobra.ExactArgs(1),
|
|
17
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
18
|
+
if err := handleNotNowCard(cmd, args[0]); err != nil {
|
|
19
|
+
fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func handleNotNowCard(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.PostCardNotNow(context.Background(), cardNum)
|
|
36
|
+
if err != nil {
|
|
37
|
+
return fmt.Errorf("moving card to not now: %w", err)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fmt.Printf("✓ Card #%d moved to Not Now successfully\n", cardNum)
|
|
41
|
+
return nil
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func init() {
|
|
45
|
+
cardCmd.AddCommand(cardNotNowCmd)
|
|
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 TestCardNotNowCommandSuccess(t *testing.T) {
|
|
14
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
15
|
+
if r.URL.Path != "/cards/123/not_now" {
|
|
16
|
+
t.Errorf("expected /cards/123/not_now, 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 := cardNotNowCmd
|
|
35
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
36
|
+
|
|
37
|
+
if err := handleNotNowCard(cmd, "123"); err != nil {
|
|
38
|
+
t.Fatalf("handleNotNowCard failed: %v", err)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func TestCardNotNowCommandInvalidCardNumber(t *testing.T) {
|
|
43
|
+
testApp := &app.App{}
|
|
44
|
+
|
|
45
|
+
cmd := cardNotNowCmd
|
|
46
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
47
|
+
|
|
48
|
+
err := handleNotNowCard(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 TestCardNotNowCommandNoClient(t *testing.T) {
|
|
58
|
+
testApp := &app.App{}
|
|
59
|
+
|
|
60
|
+
cmd := cardNotNowCmd
|
|
61
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
62
|
+
|
|
63
|
+
err := handleNotNowCard(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 TestCardNotNowCommandAPIError(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 := cardNotNowCmd
|
|
83
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
84
|
+
|
|
85
|
+
err := handleNotNowCard(cmd, "123")
|
|
86
|
+
if err == nil {
|
|
87
|
+
t.Errorf("expected error for API failure")
|
|
88
|
+
}
|
|
89
|
+
if err.Error() != "moving card to not now: unexpected status code 500: Internal Server Error" {
|
|
90
|
+
t.Errorf("expected API error, got %v", err)
|
|
91
|
+
}
|
|
92
|
+
}
|
package/cmd/card_tag.go
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
"strconv"
|
|
7
|
+
"strings"
|
|
8
|
+
|
|
9
|
+
"github.com/rogeriopvl/fizzy/internal/app"
|
|
10
|
+
"github.com/spf13/cobra"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
var cardTagCmd = &cobra.Command{
|
|
14
|
+
Use: "tag <card_number> <tag_title>",
|
|
15
|
+
Short: "Toggle a tag on or off for a card",
|
|
16
|
+
Long: `Toggle a tag on or off for a card. If the tag doesn't exist, it will be created.
|
|
17
|
+
|
|
18
|
+
The tag title can be specified with or without a leading # symbol.`,
|
|
19
|
+
Args: cobra.ExactArgs(2),
|
|
20
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
21
|
+
if err := handleTagCard(cmd, args[0], args[1]); err != nil {
|
|
22
|
+
fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func handleTagCard(cmd *cobra.Command, cardNumber, tagTitle string) error {
|
|
28
|
+
cardNum, err := strconv.Atoi(cardNumber)
|
|
29
|
+
if err != nil {
|
|
30
|
+
return fmt.Errorf("invalid card number: %w", err)
|
|
31
|
+
}
|
|
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
|
+
tagTitle = strings.TrimPrefix(tagTitle, "#")
|
|
39
|
+
|
|
40
|
+
_, err = a.Client.PostCardTagging(context.Background(), cardNum, tagTitle)
|
|
41
|
+
if err != nil {
|
|
42
|
+
return fmt.Errorf("toggling tag on card: %w", err)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fmt.Printf("✓ Tag '%s' toggled on card #%d\n", tagTitle, cardNum)
|
|
46
|
+
return nil
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func init() {
|
|
50
|
+
cardCmd.AddCommand(cardTagCmd)
|
|
51
|
+
}
|