fizzy-cli 0.2.0 → 0.3.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/.env +1 -0
- package/CHANGELOG.md +22 -0
- package/bin/fizzy +0 -0
- package/cmd/board.go +1 -1
- package/cmd/card_assign.go +55 -0
- package/cmd/card_assign_test.go +130 -0
- package/cmd/card_triage.go +46 -0
- package/cmd/card_update.go +0 -1
- package/cmd/card_update_test.go +0 -2
- package/cmd/login.go +2 -1
- package/cmd/notification.go +14 -0
- package/cmd/notification_list.go +69 -0
- package/cmd/notification_list_test.go +288 -0
- package/cmd/notification_read.go +51 -0
- package/cmd/notification_read_all.go +38 -0
- package/cmd/notification_read_all_test.go +75 -0
- package/cmd/notification_read_test.go +138 -0
- package/cmd/notification_unread.go +44 -0
- package/cmd/notification_unread_test.go +99 -0
- package/docs/API.md +144 -1
- package/go.mod +1 -1
- package/internal/api/client.go +137 -0
- package/internal/config/config.go +1 -0
- package/internal/ui/notification_list.go +27 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +5 -1
- package/bin/fizzy-darwin-amd64 +0 -0
- package/bin/fizzy-darwin-arm64 +0 -0
- package/bin/fizzy-linux-amd64 +0 -0
- package/bin/fizzy-linux-arm64 +0 -0
- package/bin/fizzy-windows-amd64.exe +0 -0
package/.env
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
FIZZY_ACCESS_TOKEN=S7FiVcT1K3WgwXc7uzBdQRim
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 - 2026-01-11
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
#### Notification Management
|
|
8
|
+
|
|
9
|
+
- `fizzy notification list` - List all notifications with optional filtering
|
|
10
|
+
- `fizzy notification read <notification_id>` - Mark a notification as read and display it
|
|
11
|
+
- `fizzy notification unread <notification_id>` - Mark a notification as unread
|
|
12
|
+
- `fizzy notification read-all` - Mark all unread notifications as read
|
|
13
|
+
|
|
14
|
+
#### Card Management
|
|
15
|
+
|
|
16
|
+
- `fizzy card assign <card_number> <user_id>` - Assign or unassign a user to/from a card
|
|
17
|
+
- `fizzy card triage <card_number> <column_id>` - Move a card from triage into a column
|
|
18
|
+
|
|
19
|
+
## [0.2.1] - 2025-12-20
|
|
20
|
+
|
|
21
|
+
### Fixes
|
|
22
|
+
|
|
23
|
+
- NPM package publishing script
|
|
24
|
+
|
|
3
25
|
## [0.2.0] - 2025-12-20
|
|
4
26
|
|
|
5
27
|
### Features
|
package/bin/fizzy
CHANGED
|
Binary file
|
package/cmd/board.go
CHANGED
|
@@ -13,7 +13,7 @@ import (
|
|
|
13
13
|
var boardCmd = &cobra.Command{
|
|
14
14
|
Use: "board",
|
|
15
15
|
Short: "Show the currently selected board",
|
|
16
|
-
Long:
|
|
16
|
+
Long: `Display the name and ID of the currently selected board.
|
|
17
17
|
|
|
18
18
|
Use subcommands to list, create, or manage boards:
|
|
19
19
|
fizzy board list List all boards
|
|
@@ -0,0 +1,55 @@
|
|
|
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 cardAssignCmd = &cobra.Command{
|
|
13
|
+
Use: "assign <card_number> <user_id>",
|
|
14
|
+
Short: "Assign a user to a card",
|
|
15
|
+
Long: `Assign or unassign a user to/from a card.
|
|
16
|
+
|
|
17
|
+
Use "me" as the user_id to assign the card to yourself.`,
|
|
18
|
+
Args: cobra.ExactArgs(2),
|
|
19
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
20
|
+
if err := handleAssignCard(cmd, args[0], args[1]); err != nil {
|
|
21
|
+
fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func handleAssignCard(cmd *cobra.Command, cardNumber, userID string) error {
|
|
27
|
+
cardNum, err := strconv.Atoi(cardNumber)
|
|
28
|
+
if err != nil {
|
|
29
|
+
return fmt.Errorf("invalid card number: %w", err)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
a := app.FromContext(cmd.Context())
|
|
33
|
+
if a == nil || a.Client == nil {
|
|
34
|
+
return fmt.Errorf("API client not available")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if userID == "me" {
|
|
38
|
+
if a.Config.CurrentUserID == "" {
|
|
39
|
+
return fmt.Errorf("current user ID not available, please run 'fizzy login' first")
|
|
40
|
+
}
|
|
41
|
+
userID = a.Config.CurrentUserID
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
_, err = a.Client.PostCardAssignments(context.Background(), cardNum, userID)
|
|
45
|
+
if err != nil {
|
|
46
|
+
return fmt.Errorf("assigning card: %w", err)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fmt.Printf("✓ Card #%d assignment toggled for user %s\n", cardNum, userID)
|
|
50
|
+
return nil
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func init() {
|
|
54
|
+
cardCmd.AddCommand(cardAssignCmd)
|
|
55
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
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/config"
|
|
11
|
+
"github.com/rogeriopvl/fizzy/internal/testutil"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
func TestCardAssignCommandSuccess(t *testing.T) {
|
|
15
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
16
|
+
if r.URL.Path != "/cards/123/assignments" {
|
|
17
|
+
t.Errorf("expected /cards/123/assignments, got %s", r.URL.Path)
|
|
18
|
+
}
|
|
19
|
+
if r.Method != http.MethodPost {
|
|
20
|
+
t.Errorf("expected POST, got %s", r.Method)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
auth := r.Header.Get("Authorization")
|
|
24
|
+
if auth != "Bearer test-token" {
|
|
25
|
+
t.Errorf("expected Bearer test-token, got %s", auth)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
w.WriteHeader(http.StatusNoContent)
|
|
29
|
+
}))
|
|
30
|
+
defer server.Close()
|
|
31
|
+
|
|
32
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
33
|
+
testApp := &app.App{Client: client}
|
|
34
|
+
|
|
35
|
+
cmd := cardAssignCmd
|
|
36
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
37
|
+
|
|
38
|
+
if err := handleAssignCard(cmd, "123", "user-id-123"); err != nil {
|
|
39
|
+
t.Fatalf("handleAssignCard failed: %v", err)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func TestCardAssignCommandWithMe(t *testing.T) {
|
|
44
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
45
|
+
if r.URL.Path != "/cards/123/assignments" {
|
|
46
|
+
t.Errorf("expected /cards/123/assignments, got %s", r.URL.Path)
|
|
47
|
+
}
|
|
48
|
+
w.WriteHeader(http.StatusNoContent)
|
|
49
|
+
}))
|
|
50
|
+
defer server.Close()
|
|
51
|
+
|
|
52
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
53
|
+
cfg := &config.Config{CurrentUserID: "my-user-id"}
|
|
54
|
+
testApp := &app.App{Client: client, Config: cfg}
|
|
55
|
+
|
|
56
|
+
cmd := cardAssignCmd
|
|
57
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
58
|
+
|
|
59
|
+
if err := handleAssignCard(cmd, "123", "me"); err != nil {
|
|
60
|
+
t.Fatalf("handleAssignCard with 'me' failed: %v", err)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func TestCardAssignCommandAPIError(t *testing.T) {
|
|
65
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
66
|
+
w.WriteHeader(http.StatusNotFound)
|
|
67
|
+
w.Write([]byte("Card not found"))
|
|
68
|
+
}))
|
|
69
|
+
defer server.Close()
|
|
70
|
+
|
|
71
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
72
|
+
testApp := &app.App{Client: client}
|
|
73
|
+
|
|
74
|
+
cmd := cardAssignCmd
|
|
75
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
76
|
+
|
|
77
|
+
err := handleAssignCard(cmd, "999", "user-id-123")
|
|
78
|
+
if err == nil {
|
|
79
|
+
t.Errorf("expected error for API failure")
|
|
80
|
+
}
|
|
81
|
+
if err.Error() != "assigning card: unexpected status code 404: Card not found" {
|
|
82
|
+
t.Errorf("expected API error, got %v", err)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
func TestCardAssignCommandInvalidCardNumber(t *testing.T) {
|
|
87
|
+
testApp := &app.App{}
|
|
88
|
+
|
|
89
|
+
cmd := cardAssignCmd
|
|
90
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
91
|
+
|
|
92
|
+
err := handleAssignCard(cmd, "not-a-number", "user-id-123")
|
|
93
|
+
if err == nil {
|
|
94
|
+
t.Errorf("expected error for invalid card number")
|
|
95
|
+
}
|
|
96
|
+
if err.Error() != "invalid card number: strconv.Atoi: parsing \"not-a-number\": invalid syntax" {
|
|
97
|
+
t.Errorf("expected invalid card number error, got %v", err)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
func TestCardAssignCommandNoClient(t *testing.T) {
|
|
102
|
+
testApp := &app.App{}
|
|
103
|
+
|
|
104
|
+
cmd := cardAssignCmd
|
|
105
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
106
|
+
|
|
107
|
+
err := handleAssignCard(cmd, "123", "user-id-123")
|
|
108
|
+
if err == nil {
|
|
109
|
+
t.Errorf("expected error when client not available")
|
|
110
|
+
}
|
|
111
|
+
if err.Error() != "API client not available" {
|
|
112
|
+
t.Errorf("expected 'client not available' error, got %v", err)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func TestCardAssignCommandMeWithoutUserID(t *testing.T) {
|
|
117
|
+
client := testutil.NewTestClient("http://localhost", "", "", "test-token")
|
|
118
|
+
testApp := &app.App{Client: client, Config: &config.Config{}}
|
|
119
|
+
|
|
120
|
+
cmd := cardAssignCmd
|
|
121
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
122
|
+
|
|
123
|
+
err := handleAssignCard(cmd, "123", "me")
|
|
124
|
+
if err == nil {
|
|
125
|
+
t.Errorf("expected error when using 'me' without current user ID")
|
|
126
|
+
}
|
|
127
|
+
if err.Error() != "current user ID not available, please run 'fizzy login' first" {
|
|
128
|
+
t.Errorf("expected 'current user ID not available' error, got %v", err)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -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 cardTriageCmd = &cobra.Command{
|
|
13
|
+
Use: "triage <card_number> <column_id>",
|
|
14
|
+
Short: "Move a card from triage into a column",
|
|
15
|
+
Long: `Move a card from triage into a specified column`,
|
|
16
|
+
Args: cobra.ExactArgs(2),
|
|
17
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
18
|
+
if err := handleTriageCard(cmd, args[0], args[1]); err != nil {
|
|
19
|
+
fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func handleTriageCard(cmd *cobra.Command, cardNumber string, columnID 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.PostCardTriage(context.Background(), cardNum, columnID)
|
|
36
|
+
if err != nil {
|
|
37
|
+
return fmt.Errorf("triaging card: %w", err)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fmt.Printf("✓ Card #%d moved to column successfully\n", cardNum)
|
|
41
|
+
return nil
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func init() {
|
|
45
|
+
cardCmd.AddCommand(cardTriageCmd)
|
|
46
|
+
}
|
package/cmd/card_update.go
CHANGED
|
@@ -41,7 +41,6 @@ func handleUpdateCard(cmd *cobra.Command, cardNumber string) error {
|
|
|
41
41
|
return fmt.Errorf("API client not available")
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// Validate that at least one field is provided for update
|
|
45
44
|
if updateTitle == "" && updateDescription == "" && updateStatus == "" && len(updateTagIDs) == 0 && updateLastActiveAt == "" {
|
|
46
45
|
return fmt.Errorf("must provide at least one flag to update (--title, --description, --status, --tag-id, or --last-active-at)")
|
|
47
46
|
}
|
package/cmd/card_update_test.go
CHANGED
|
@@ -49,7 +49,6 @@ func TestCardUpdateCommand(t *testing.T) {
|
|
|
49
49
|
cmd := cardUpdateCmd
|
|
50
50
|
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
51
51
|
|
|
52
|
-
// Reset flags to defaults
|
|
53
52
|
updateTitle = "Updated card title"
|
|
54
53
|
updateDescription = "Updated description"
|
|
55
54
|
updateStatus = "published"
|
|
@@ -130,7 +129,6 @@ func TestCardUpdateCommandNoFlags(t *testing.T) {
|
|
|
130
129
|
cmd := cardUpdateCmd
|
|
131
130
|
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
132
131
|
|
|
133
|
-
// Reset all flags
|
|
134
132
|
updateTitle = ""
|
|
135
133
|
updateDescription = ""
|
|
136
134
|
updateStatus = ""
|
package/cmd/login.go
CHANGED
|
@@ -47,8 +47,9 @@ func handleLogin(cmd *cobra.Command) error {
|
|
|
47
47
|
|
|
48
48
|
fmt.Printf("\nSelected account: %s (%s)\n", selected.Name, selected.Slug)
|
|
49
49
|
|
|
50
|
-
// Save the selected account to config
|
|
50
|
+
// Save the selected account and current user ID to config
|
|
51
51
|
a.Config.SelectedAccount = selected.Slug
|
|
52
|
+
a.Config.CurrentUserID = selected.User.ID
|
|
52
53
|
if err := a.Config.Save(); err != nil {
|
|
53
54
|
return fmt.Errorf("saving config: %w", err)
|
|
54
55
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Package cmd
|
|
2
|
+
package cmd
|
|
3
|
+
|
|
4
|
+
import "github.com/spf13/cobra"
|
|
5
|
+
|
|
6
|
+
var notificationCmd = &cobra.Command{
|
|
7
|
+
Use: "notification",
|
|
8
|
+
Short: "Manage notifications",
|
|
9
|
+
Long: `Manage notifications in Fizzy`,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
func init() {
|
|
13
|
+
rootCmd.AddCommand(notificationCmd)
|
|
14
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
package cmd
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"fmt"
|
|
6
|
+
|
|
7
|
+
"github.com/rogeriopvl/fizzy/internal/api"
|
|
8
|
+
"github.com/rogeriopvl/fizzy/internal/app"
|
|
9
|
+
"github.com/rogeriopvl/fizzy/internal/ui"
|
|
10
|
+
"github.com/spf13/cobra"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
var notificationListCmd = &cobra.Command{
|
|
14
|
+
Use: "list",
|
|
15
|
+
Short: "List all notifications",
|
|
16
|
+
Long: `Retrieve and display all notifications from Fizzy`,
|
|
17
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
18
|
+
if err := handleListNotifications(cmd); err != nil {
|
|
19
|
+
fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func handleListNotifications(cmd *cobra.Command) error {
|
|
25
|
+
a := app.FromContext(cmd.Context())
|
|
26
|
+
if a == nil || a.Client == nil {
|
|
27
|
+
return fmt.Errorf("API client not available")
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
notifications, err := a.Client.GetNotifications(context.Background())
|
|
31
|
+
if err != nil {
|
|
32
|
+
return fmt.Errorf("fetching notifications: %w", err)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
read, _ := cmd.Flags().GetBool("read")
|
|
36
|
+
unread, _ := cmd.Flags().GetBool("unread")
|
|
37
|
+
|
|
38
|
+
filtered := filterNotifications(notifications, read, unread)
|
|
39
|
+
|
|
40
|
+
if len(filtered) == 0 {
|
|
41
|
+
fmt.Println("No notifications found")
|
|
42
|
+
return nil
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return ui.DisplayNotifications(filtered)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func filterNotifications(notifications []api.Notification, read bool, unread bool) []api.Notification {
|
|
49
|
+
if !read && !unread {
|
|
50
|
+
return notifications
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
var filtered []api.Notification
|
|
54
|
+
for _, notification := range notifications {
|
|
55
|
+
if read && notification.Read {
|
|
56
|
+
filtered = append(filtered, notification)
|
|
57
|
+
} else if unread && !notification.Read {
|
|
58
|
+
filtered = append(filtered, notification)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return filtered
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func init() {
|
|
66
|
+
notificationCmd.AddCommand(notificationListCmd)
|
|
67
|
+
notificationListCmd.Flags().BoolP("read", "r", false, "Show only read notifications")
|
|
68
|
+
notificationListCmd.Flags().BoolP("unread", "u", false, "Show only unread notifications")
|
|
69
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
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/config"
|
|
13
|
+
"github.com/rogeriopvl/fizzy/internal/testutil"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
func TestNotificationListCommand(t *testing.T) {
|
|
17
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
18
|
+
if r.URL.Path != "/notifications" {
|
|
19
|
+
t.Errorf("expected /notifications, got %s", r.URL.Path)
|
|
20
|
+
}
|
|
21
|
+
if r.Method != http.MethodGet {
|
|
22
|
+
t.Errorf("expected GET, 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
|
+
w.Header().Set("Content-Type", "application/json")
|
|
31
|
+
response := []api.Notification{
|
|
32
|
+
{
|
|
33
|
+
ID: "notif-123",
|
|
34
|
+
Read: false,
|
|
35
|
+
ReadAt: "",
|
|
36
|
+
CreatedAt: "2025-01-01T00:00:00Z",
|
|
37
|
+
Title: "Plain text mentions",
|
|
38
|
+
Body: "Assigned to self",
|
|
39
|
+
Creator: api.User{
|
|
40
|
+
ID: "user-123",
|
|
41
|
+
Name: "David Heinemeier Hansson",
|
|
42
|
+
Email: "david@example.com",
|
|
43
|
+
Role: "owner",
|
|
44
|
+
Active: true,
|
|
45
|
+
CreatedAt: "2025-12-05T19:36:35.401Z",
|
|
46
|
+
URL: "http://fizzy.localhost:3006/897362094/users/03f5v9zjw7pz8717a4no1h8a7",
|
|
47
|
+
},
|
|
48
|
+
Card: api.CardReference{
|
|
49
|
+
ID: "card-123",
|
|
50
|
+
Title: "Plain text mentions",
|
|
51
|
+
Status: "published",
|
|
52
|
+
URL: "http://fizzy.localhost:3006/897362094/cards/3",
|
|
53
|
+
},
|
|
54
|
+
URL: "http://fizzy.localhost:3006/897362094/notifications/03f5va03bpuvkcjemcxl73ho2",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
ID: "notif-456",
|
|
58
|
+
Read: true,
|
|
59
|
+
ReadAt: "2025-01-02T00:00:00Z",
|
|
60
|
+
CreatedAt: "2025-01-01T12:00:00Z",
|
|
61
|
+
Title: "Comment reply",
|
|
62
|
+
Body: "Someone replied to your comment",
|
|
63
|
+
Creator: api.User{
|
|
64
|
+
ID: "user-456",
|
|
65
|
+
Name: "Jason Fried",
|
|
66
|
+
Email: "jason@example.com",
|
|
67
|
+
Role: "member",
|
|
68
|
+
Active: true,
|
|
69
|
+
CreatedAt: "2025-12-05T19:36:35.419Z",
|
|
70
|
+
URL: "http://fizzy.localhost:3006/897362094/users/03f5v9zjysoy0fqs9yg0ei3hq",
|
|
71
|
+
},
|
|
72
|
+
Card: api.CardReference{
|
|
73
|
+
ID: "card-456",
|
|
74
|
+
Title: "Fix bug",
|
|
75
|
+
Status: "in_progress",
|
|
76
|
+
URL: "http://fizzy.localhost:3006/897362094/cards/4",
|
|
77
|
+
},
|
|
78
|
+
URL: "http://fizzy.localhost:3006/897362094/notifications/notif-456",
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
json.NewEncoder(w).Encode(response)
|
|
82
|
+
}))
|
|
83
|
+
defer server.Close()
|
|
84
|
+
|
|
85
|
+
client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
|
|
86
|
+
testApp := &app.App{
|
|
87
|
+
Client: client,
|
|
88
|
+
Config: &config.Config{SelectedBoard: "board-123"},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
cmd := notificationListCmd
|
|
92
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
93
|
+
|
|
94
|
+
if err := handleListNotifications(cmd); err != nil {
|
|
95
|
+
t.Fatalf("handleListNotifications failed: %v", err)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func TestNotificationListCommandNoNotifications(t *testing.T) {
|
|
100
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
101
|
+
w.Header().Set("Content-Type", "application/json")
|
|
102
|
+
json.NewEncoder(w).Encode([]api.Notification{})
|
|
103
|
+
}))
|
|
104
|
+
defer server.Close()
|
|
105
|
+
|
|
106
|
+
client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
|
|
107
|
+
testApp := &app.App{
|
|
108
|
+
Client: client,
|
|
109
|
+
Config: &config.Config{SelectedBoard: "board-123"},
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
cmd := notificationListCmd
|
|
113
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
114
|
+
|
|
115
|
+
if err := handleListNotifications(cmd); err != nil {
|
|
116
|
+
t.Fatalf("handleListNotifications failed: %v", err)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
func TestNotificationListCommandAPIError(t *testing.T) {
|
|
121
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
122
|
+
w.WriteHeader(http.StatusInternalServerError)
|
|
123
|
+
w.Write([]byte("Internal Server Error"))
|
|
124
|
+
}))
|
|
125
|
+
defer server.Close()
|
|
126
|
+
|
|
127
|
+
client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
|
|
128
|
+
testApp := &app.App{
|
|
129
|
+
Client: client,
|
|
130
|
+
Config: &config.Config{SelectedBoard: "board-123"},
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
cmd := notificationListCmd
|
|
134
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
135
|
+
|
|
136
|
+
err := handleListNotifications(cmd)
|
|
137
|
+
if err == nil {
|
|
138
|
+
t.Errorf("expected error for API failure")
|
|
139
|
+
}
|
|
140
|
+
if err.Error() != "fetching notifications: unexpected status code 500: Internal Server Error" {
|
|
141
|
+
t.Errorf("expected API error, got %v", err)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
func TestNotificationListCommandNoClient(t *testing.T) {
|
|
146
|
+
testApp := &app.App{}
|
|
147
|
+
|
|
148
|
+
cmd := notificationListCmd
|
|
149
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
150
|
+
|
|
151
|
+
err := handleListNotifications(cmd)
|
|
152
|
+
if err == nil {
|
|
153
|
+
t.Errorf("expected error when client not available")
|
|
154
|
+
}
|
|
155
|
+
if err.Error() != "API client not available" {
|
|
156
|
+
t.Errorf("expected 'client not available' error, got %v", err)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
func TestNotificationListCommandWithReadFilter(t *testing.T) {
|
|
161
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
162
|
+
w.Header().Set("Content-Type", "application/json")
|
|
163
|
+
response := []api.Notification{
|
|
164
|
+
{
|
|
165
|
+
ID: "notif-123",
|
|
166
|
+
Read: false,
|
|
167
|
+
CreatedAt: "2025-01-01T00:00:00Z",
|
|
168
|
+
Title: "Unread notification",
|
|
169
|
+
Body: "This should be filtered out",
|
|
170
|
+
Creator: api.User{
|
|
171
|
+
ID: "user-123",
|
|
172
|
+
Name: "David Heinemeier Hansson",
|
|
173
|
+
Email: "david@example.com",
|
|
174
|
+
Role: "owner",
|
|
175
|
+
Active: true,
|
|
176
|
+
CreatedAt: "2025-12-05T19:36:35.401Z",
|
|
177
|
+
},
|
|
178
|
+
Card: api.CardReference{
|
|
179
|
+
ID: "card-123",
|
|
180
|
+
Title: "Test card",
|
|
181
|
+
Status: "published",
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
ID: "notif-456",
|
|
186
|
+
Read: true,
|
|
187
|
+
ReadAt: "2025-01-02T00:00:00Z",
|
|
188
|
+
CreatedAt: "2025-01-01T12:00:00Z",
|
|
189
|
+
Title: "Read notification",
|
|
190
|
+
Body: "This should be shown",
|
|
191
|
+
Creator: api.User{
|
|
192
|
+
ID: "user-456",
|
|
193
|
+
Name: "Jason Fried",
|
|
194
|
+
Email: "jason@example.com",
|
|
195
|
+
Role: "member",
|
|
196
|
+
Active: true,
|
|
197
|
+
CreatedAt: "2025-12-05T19:36:35.419Z",
|
|
198
|
+
},
|
|
199
|
+
Card: api.CardReference{
|
|
200
|
+
ID: "card-456",
|
|
201
|
+
Title: "Another card",
|
|
202
|
+
Status: "in_progress",
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
json.NewEncoder(w).Encode(response)
|
|
207
|
+
}))
|
|
208
|
+
defer server.Close()
|
|
209
|
+
|
|
210
|
+
client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
|
|
211
|
+
testApp := &app.App{
|
|
212
|
+
Client: client,
|
|
213
|
+
Config: &config.Config{SelectedBoard: "board-123"},
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
cmd := notificationListCmd
|
|
217
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
218
|
+
cmd.Flags().Set("read", "true")
|
|
219
|
+
|
|
220
|
+
if err := handleListNotifications(cmd); err != nil {
|
|
221
|
+
t.Fatalf("handleListNotifications failed: %v", err)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
func TestNotificationListCommandWithUnreadFilter(t *testing.T) {
|
|
226
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
227
|
+
w.Header().Set("Content-Type", "application/json")
|
|
228
|
+
response := []api.Notification{
|
|
229
|
+
{
|
|
230
|
+
ID: "notif-123",
|
|
231
|
+
Read: false,
|
|
232
|
+
CreatedAt: "2025-01-01T00:00:00Z",
|
|
233
|
+
Title: "Unread notification",
|
|
234
|
+
Body: "This should be shown",
|
|
235
|
+
Creator: api.User{
|
|
236
|
+
ID: "user-123",
|
|
237
|
+
Name: "David Heinemeier Hansson",
|
|
238
|
+
Email: "david@example.com",
|
|
239
|
+
Role: "owner",
|
|
240
|
+
Active: true,
|
|
241
|
+
CreatedAt: "2025-12-05T19:36:35.401Z",
|
|
242
|
+
},
|
|
243
|
+
Card: api.CardReference{
|
|
244
|
+
ID: "card-123",
|
|
245
|
+
Title: "Test card",
|
|
246
|
+
Status: "published",
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
ID: "notif-456",
|
|
251
|
+
Read: true,
|
|
252
|
+
ReadAt: "2025-01-02T00:00:00Z",
|
|
253
|
+
CreatedAt: "2025-01-01T12:00:00Z",
|
|
254
|
+
Title: "Read notification",
|
|
255
|
+
Body: "This should be filtered out",
|
|
256
|
+
Creator: api.User{
|
|
257
|
+
ID: "user-456",
|
|
258
|
+
Name: "Jason Fried",
|
|
259
|
+
Email: "jason@example.com",
|
|
260
|
+
Role: "member",
|
|
261
|
+
Active: true,
|
|
262
|
+
CreatedAt: "2025-12-05T19:36:35.419Z",
|
|
263
|
+
},
|
|
264
|
+
Card: api.CardReference{
|
|
265
|
+
ID: "card-456",
|
|
266
|
+
Title: "Another card",
|
|
267
|
+
Status: "in_progress",
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
json.NewEncoder(w).Encode(response)
|
|
272
|
+
}))
|
|
273
|
+
defer server.Close()
|
|
274
|
+
|
|
275
|
+
client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
|
|
276
|
+
testApp := &app.App{
|
|
277
|
+
Client: client,
|
|
278
|
+
Config: &config.Config{SelectedBoard: "board-123"},
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
cmd := notificationListCmd
|
|
282
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
283
|
+
cmd.Flags().Set("unread", "true")
|
|
284
|
+
|
|
285
|
+
if err := handleListNotifications(cmd); err != nil {
|
|
286
|
+
t.Fatalf("handleListNotifications failed: %v", err)
|
|
287
|
+
}
|
|
288
|
+
}
|