fizzy-cli 0.8.0 → 1.0.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.
Files changed (159) hide show
  1. package/.golangci.yml +56 -0
  2. package/IMPLEMENTATION_PLAN.md +58 -0
  3. package/docs/API.md +89 -0
  4. package/package.json +2 -1
  5. package/.env +0 -1
  6. package/.github/workflows/release.yml +0 -29
  7. package/.github/workflows/tests.yml +0 -24
  8. package/AGENTS.md +0 -33
  9. package/CHANGELOG.md +0 -194
  10. package/Makefile +0 -43
  11. package/bin/fizzy +0 -0
  12. package/cmd/account.go +0 -14
  13. package/cmd/account_list.go +0 -44
  14. package/cmd/account_list_test.go +0 -118
  15. package/cmd/board.go +0 -49
  16. package/cmd/board_create.go +0 -59
  17. package/cmd/board_create_test.go +0 -141
  18. package/cmd/board_delete.go +0 -40
  19. package/cmd/board_delete_test.go +0 -121
  20. package/cmd/board_list.go +0 -44
  21. package/cmd/board_list_test.go +0 -115
  22. package/cmd/board_show.go +0 -40
  23. package/cmd/board_show_test.go +0 -113
  24. package/cmd/board_test.go +0 -92
  25. package/cmd/board_update.go +0 -72
  26. package/cmd/board_update_test.go +0 -233
  27. package/cmd/card.go +0 -24
  28. package/cmd/card_assign.go +0 -55
  29. package/cmd/card_assign_test.go +0 -130
  30. package/cmd/card_close.go +0 -46
  31. package/cmd/card_close_test.go +0 -92
  32. package/cmd/card_create.go +0 -72
  33. package/cmd/card_create_test.go +0 -186
  34. package/cmd/card_delete.go +0 -46
  35. package/cmd/card_delete_test.go +0 -92
  36. package/cmd/card_golden.go +0 -46
  37. package/cmd/card_golden_test.go +0 -92
  38. package/cmd/card_list.go +0 -114
  39. package/cmd/card_list_test.go +0 -373
  40. package/cmd/card_not_now.go +0 -46
  41. package/cmd/card_not_now_test.go +0 -92
  42. package/cmd/card_reaction.go +0 -13
  43. package/cmd/card_reaction_create.go +0 -46
  44. package/cmd/card_reaction_create_test.go +0 -148
  45. package/cmd/card_reaction_delete.go +0 -46
  46. package/cmd/card_reaction_delete_test.go +0 -112
  47. package/cmd/card_reaction_list.go +0 -51
  48. package/cmd/card_reaction_list_test.go +0 -127
  49. package/cmd/card_reopen.go +0 -46
  50. package/cmd/card_reopen_test.go +0 -92
  51. package/cmd/card_show.go +0 -46
  52. package/cmd/card_show_test.go +0 -92
  53. package/cmd/card_tag.go +0 -51
  54. package/cmd/card_tag_test.go +0 -112
  55. package/cmd/card_triage.go +0 -46
  56. package/cmd/card_ungolden.go +0 -46
  57. package/cmd/card_ungolden_test.go +0 -92
  58. package/cmd/card_untriage.go +0 -46
  59. package/cmd/card_untriage_test.go +0 -92
  60. package/cmd/card_unwatch.go +0 -46
  61. package/cmd/card_unwatch_test.go +0 -92
  62. package/cmd/card_update.go +0 -82
  63. package/cmd/card_update_test.go +0 -149
  64. package/cmd/card_watch.go +0 -46
  65. package/cmd/card_watch_test.go +0 -92
  66. package/cmd/column.go +0 -14
  67. package/cmd/column_create.go +0 -79
  68. package/cmd/column_create_test.go +0 -178
  69. package/cmd/column_delete.go +0 -40
  70. package/cmd/column_delete_test.go +0 -121
  71. package/cmd/column_list.go +0 -44
  72. package/cmd/column_list_test.go +0 -138
  73. package/cmd/column_show.go +0 -40
  74. package/cmd/column_show_test.go +0 -111
  75. package/cmd/column_update.go +0 -67
  76. package/cmd/column_update_test.go +0 -198
  77. package/cmd/comment.go +0 -14
  78. package/cmd/comment_create.go +0 -51
  79. package/cmd/comment_create_test.go +0 -129
  80. package/cmd/comment_delete.go +0 -46
  81. package/cmd/comment_delete_test.go +0 -92
  82. package/cmd/comment_list.go +0 -51
  83. package/cmd/comment_list_test.go +0 -132
  84. package/cmd/comment_show.go +0 -46
  85. package/cmd/comment_show_test.go +0 -104
  86. package/cmd/comment_update.go +0 -51
  87. package/cmd/comment_update_test.go +0 -130
  88. package/cmd/login.go +0 -81
  89. package/cmd/login_test.go +0 -98
  90. package/cmd/notification.go +0 -14
  91. package/cmd/notification_list.go +0 -69
  92. package/cmd/notification_list_test.go +0 -288
  93. package/cmd/notification_read.go +0 -51
  94. package/cmd/notification_read_all.go +0 -38
  95. package/cmd/notification_read_all_test.go +0 -75
  96. package/cmd/notification_read_test.go +0 -138
  97. package/cmd/notification_unread.go +0 -44
  98. package/cmd/notification_unread_test.go +0 -99
  99. package/cmd/reaction.go +0 -13
  100. package/cmd/reaction_create.go +0 -46
  101. package/cmd/reaction_create_test.go +0 -113
  102. package/cmd/reaction_delete.go +0 -46
  103. package/cmd/reaction_delete_test.go +0 -92
  104. package/cmd/reaction_list.go +0 -51
  105. package/cmd/reaction_list_test.go +0 -125
  106. package/cmd/root.go +0 -38
  107. package/cmd/step.go +0 -14
  108. package/cmd/step_create.go +0 -53
  109. package/cmd/step_create_test.go +0 -171
  110. package/cmd/step_delete.go +0 -46
  111. package/cmd/step_delete_test.go +0 -92
  112. package/cmd/step_update.go +0 -66
  113. package/cmd/step_update_test.go +0 -190
  114. package/cmd/tag.go +0 -15
  115. package/cmd/tag_list.go +0 -47
  116. package/cmd/tag_list_test.go +0 -109
  117. package/cmd/use.go +0 -85
  118. package/cmd/use_test.go +0 -186
  119. package/cmd/user.go +0 -22
  120. package/cmd/user_deactivate.go +0 -40
  121. package/cmd/user_deactivate_test.go +0 -121
  122. package/cmd/user_list.go +0 -44
  123. package/cmd/user_list_test.go +0 -126
  124. package/cmd/user_show.go +0 -40
  125. package/cmd/user_show_test.go +0 -110
  126. package/cmd/user_update.go +0 -71
  127. package/cmd/user_update_test.go +0 -177
  128. package/go.mod +0 -31
  129. package/go.sum +0 -53
  130. package/internal/api/boards.go +0 -93
  131. package/internal/api/cards.go +0 -322
  132. package/internal/api/client.go +0 -99
  133. package/internal/api/columns.go +0 -113
  134. package/internal/api/comments.go +0 -108
  135. package/internal/api/identity.go +0 -24
  136. package/internal/api/notifications.go +0 -89
  137. package/internal/api/reactions.go +0 -130
  138. package/internal/api/steps.go +0 -101
  139. package/internal/api/tags.go +0 -24
  140. package/internal/api/types.go +0 -195
  141. package/internal/api/users.go +0 -75
  142. package/internal/app/app.go +0 -49
  143. package/internal/colors/colors.go +0 -32
  144. package/internal/config/config.go +0 -70
  145. package/internal/testutil/client.go +0 -26
  146. package/internal/ui/account_list.go +0 -14
  147. package/internal/ui/account_selector.go +0 -63
  148. package/internal/ui/board_list.go +0 -14
  149. package/internal/ui/board_show.go +0 -17
  150. package/internal/ui/card_list.go +0 -14
  151. package/internal/ui/card_show.go +0 -23
  152. package/internal/ui/column_list.go +0 -28
  153. package/internal/ui/column_show.go +0 -16
  154. package/internal/ui/comment_list.go +0 -25
  155. package/internal/ui/format.go +0 -27
  156. package/internal/ui/notification_list.go +0 -27
  157. package/internal/ui/reaction_list.go +0 -14
  158. package/internal/ui/user_list.go +0 -19
  159. package/internal/ui/user_show.go +0 -23
@@ -1,46 +0,0 @@
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.Fprintf(cmd.OutOrStdout(), "✓ Card #%d marked as golden\n", cardNum)
41
- return nil
42
- }
43
-
44
- func init() {
45
- cardCmd.AddCommand(cardGoldenCmd)
46
- }
@@ -1,92 +0,0 @@
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
- }
package/cmd/card_list.go DELETED
@@ -1,114 +0,0 @@
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 cardListCmd = &cobra.Command{
14
- Use: "list",
15
- Short: "List all cards",
16
- Long: `Retrieve and display cards from Fizzy with optional filters.
17
-
18
- Filter options:
19
- --tag <id> Filter by tag ID (can be used multiple times)
20
- --assignee <id> Filter by assignee user ID (can be used multiple times)
21
- --creator <id> Filter by creator user ID (can be used multiple times)
22
- --closer <id> Filter by user who closed the card (can be used multiple times)
23
- --card <id> Filter to specific card ID (can be used multiple times)
24
- --indexed-by Filter by status: all, closed, not_now, stalled, postponing_soon, golden
25
- --sorted-by Sort order: latest, newest, oldest
26
- --unassigned Show only unassigned cards
27
- --created-in Filter by creation date: today, yesterday, thisweek, lastweek, thismonth, lastmonth, thisyear, lastyear
28
- --closed-in Filter by closure date: today, yesterday, thisweek, lastweek, thismonth, lastmonth, thisyear, lastyear
29
- --search Search terms (can be used multiple times)`,
30
- Run: func(cmd *cobra.Command, args []string) {
31
- if err := handleListCards(cmd); err != nil {
32
- fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
33
- }
34
- },
35
- }
36
-
37
- func handleListCards(cmd *cobra.Command) error {
38
- a := app.FromContext(cmd.Context())
39
- if a == nil || a.Client == nil {
40
- return fmt.Errorf("API client not available")
41
- }
42
-
43
- if a.Config.SelectedBoard == "" {
44
- return fmt.Errorf("no board selected")
45
- }
46
-
47
- filters := api.CardFilters{
48
- BoardIDs: []string{a.Config.SelectedBoard},
49
- }
50
-
51
- if tags, _ := cmd.Flags().GetStringSlice("tag"); len(tags) > 0 {
52
- filters.TagIDs = tags
53
- }
54
- if assignees, _ := cmd.Flags().GetStringSlice("assignee"); len(assignees) > 0 {
55
- filters.AssigneeIDs = assignees
56
- }
57
- if creators, _ := cmd.Flags().GetStringSlice("creator"); len(creators) > 0 {
58
- filters.CreatorIDs = creators
59
- }
60
- if closers, _ := cmd.Flags().GetStringSlice("closer"); len(closers) > 0 {
61
- filters.CloserIDs = closers
62
- }
63
- if cardIDs, _ := cmd.Flags().GetStringSlice("card"); len(cardIDs) > 0 {
64
- filters.CardIDs = cardIDs
65
- }
66
- if searches, _ := cmd.Flags().GetStringSlice("search"); len(searches) > 0 {
67
- filters.Terms = searches
68
- }
69
-
70
- if indexedBy, _ := cmd.Flags().GetString("indexed-by"); indexedBy != "" {
71
- filters.IndexedBy = indexedBy
72
- }
73
- if sortedBy, _ := cmd.Flags().GetString("sorted-by"); sortedBy != "" {
74
- filters.SortedBy = sortedBy
75
- }
76
- if createdIn, _ := cmd.Flags().GetString("created-in"); createdIn != "" {
77
- filters.CreationStatus = createdIn
78
- }
79
- if closedIn, _ := cmd.Flags().GetString("closed-in"); closedIn != "" {
80
- filters.ClosureStatus = closedIn
81
- }
82
-
83
- if unassigned, _ := cmd.Flags().GetBool("unassigned"); unassigned {
84
- filters.AssignmentStatus = "unassigned"
85
- }
86
-
87
- cards, err := a.Client.GetCards(context.Background(), filters)
88
- if err != nil {
89
- return fmt.Errorf("fetching cards: %w", err)
90
- }
91
-
92
- if len(cards) == 0 {
93
- fmt.Println("No cards found")
94
- return nil
95
- }
96
-
97
- return ui.DisplayCards(cards)
98
- }
99
-
100
- func init() {
101
- cardListCmd.Flags().StringSliceP("tag", "t", []string{}, "Filter by tag ID (can be used multiple times)")
102
- cardListCmd.Flags().StringSliceP("assignee", "a", []string{}, "Filter by assignee user ID (can be used multiple times)")
103
- cardListCmd.Flags().StringSlice("creator", []string{}, "Filter by creator user ID (can be used multiple times)")
104
- cardListCmd.Flags().StringSlice("closer", []string{}, "Filter by closer user ID (can be used multiple times)")
105
- cardListCmd.Flags().StringSlice("card", []string{}, "Filter to specific card ID (can be used multiple times)")
106
- cardListCmd.Flags().String("indexed-by", "", "Filter by status: all, closed, not_now, stalled, postponing_soon, golden")
107
- cardListCmd.Flags().String("sorted-by", "", "Sort order: latest, newest, oldest")
108
- cardListCmd.Flags().BoolP("unassigned", "u", false, "Show only unassigned cards")
109
- cardListCmd.Flags().String("created-in", "", "Filter by creation date")
110
- cardListCmd.Flags().String("closed-in", "", "Filter by closure date")
111
- cardListCmd.Flags().StringSliceP("search", "s", []string{}, "Search terms (can be used multiple times)")
112
-
113
- cardCmd.AddCommand(cardListCmd)
114
- }
@@ -1,373 +0,0 @@
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
- "github.com/spf13/cobra"
15
- )
16
-
17
- func newCardListCmd() *cobra.Command {
18
- cmd := &cobra.Command{}
19
- cmd.Flags().StringSliceP("tag", "t", []string{}, "Filter by tag ID (can be used multiple times)")
20
- cmd.Flags().StringSliceP("assignee", "a", []string{}, "Filter by assignee user ID (can be used multiple times)")
21
- cmd.Flags().StringSlice("creator", []string{}, "Filter by creator user ID (can be used multiple times)")
22
- cmd.Flags().StringSlice("closer", []string{}, "Filter by closer user ID (can be used multiple times)")
23
- cmd.Flags().StringSlice("card", []string{}, "Filter to specific card ID (can be used multiple times)")
24
- cmd.Flags().String("indexed-by", "", "Filter by status: all, closed, not_now, stalled, postponing_soon, golden")
25
- cmd.Flags().String("sorted-by", "", "Sort order: latest, newest, oldest")
26
- cmd.Flags().BoolP("unassigned", "u", false, "Show only unassigned cards")
27
- cmd.Flags().String("created-in", "", "Filter by creation date")
28
- cmd.Flags().String("closed-in", "", "Filter by closure date")
29
- cmd.Flags().StringSliceP("search", "s", []string{}, "Search terms (can be used multiple times)")
30
- return cmd
31
- }
32
-
33
- func TestCardListCommand(t *testing.T) {
34
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
35
- if r.URL.Path != "/cards" {
36
- t.Errorf("expected /cards, got %s", r.URL.Path)
37
- }
38
- if r.Method != http.MethodGet {
39
- t.Errorf("expected GET, got %s", r.Method)
40
- }
41
-
42
- boardIDs := r.URL.Query()["board_ids[]"]
43
- if len(boardIDs) == 0 || boardIDs[0] != "board-123" {
44
- t.Errorf("expected board_ids[]=board-123 in query, got %v", boardIDs)
45
- }
46
-
47
- auth := r.Header.Get("Authorization")
48
- if auth != "Bearer test-token" {
49
- t.Errorf("expected Bearer test-token, got %s", auth)
50
- }
51
-
52
- w.Header().Set("Content-Type", "application/json")
53
- response := []api.Card{
54
- {
55
- ID: "card-123",
56
- Number: 1,
57
- Title: "Implement feature",
58
- Status: "in_progress",
59
- CreatedAt: "2025-01-01T00:00:00Z",
60
- },
61
- {
62
- ID: "card-456",
63
- Number: 2,
64
- Title: "Fix bug",
65
- Status: "todo",
66
- CreatedAt: "2025-01-02T00:00:00Z",
67
- },
68
- }
69
- json.NewEncoder(w).Encode(response)
70
- }))
71
- defer server.Close()
72
-
73
- client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
74
- testApp := &app.App{
75
- Client: client,
76
- Config: &config.Config{SelectedBoard: "board-123"},
77
- }
78
-
79
- cmd := cardListCmd
80
- cmd.SetContext(testApp.ToContext(context.Background()))
81
-
82
- if err := handleListCards(cmd); err != nil {
83
- t.Fatalf("handleListCards failed: %v", err)
84
- }
85
- }
86
-
87
- func TestCardListCommandNoCards(t *testing.T) {
88
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
89
- w.Header().Set("Content-Type", "application/json")
90
- json.NewEncoder(w).Encode([]api.Card{})
91
- }))
92
- defer server.Close()
93
-
94
- client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
95
- testApp := &app.App{
96
- Client: client,
97
- Config: &config.Config{SelectedBoard: "board-123"},
98
- }
99
-
100
- cmd := cardListCmd
101
- cmd.SetContext(testApp.ToContext(context.Background()))
102
-
103
- if err := handleListCards(cmd); err != nil {
104
- t.Fatalf("handleListCards failed: %v", err)
105
- }
106
- }
107
-
108
- func TestCardListCommandAPIError(t *testing.T) {
109
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
110
- w.WriteHeader(http.StatusInternalServerError)
111
- w.Write([]byte("Internal Server Error"))
112
- }))
113
- defer server.Close()
114
-
115
- client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
116
- testApp := &app.App{
117
- Client: client,
118
- Config: &config.Config{SelectedBoard: "board-123"},
119
- }
120
-
121
- cmd := cardListCmd
122
- cmd.SetContext(testApp.ToContext(context.Background()))
123
-
124
- err := handleListCards(cmd)
125
- if err == nil {
126
- t.Errorf("expected error for API failure")
127
- }
128
- if err.Error() != "fetching cards: unexpected status code 500: Internal Server Error" {
129
- t.Errorf("expected API error, got %v", err)
130
- }
131
- }
132
-
133
- func TestCardListCommandNoBoard(t *testing.T) {
134
- client := testutil.NewTestClient("http://localhost", "", "", "test-token")
135
- testApp := &app.App{
136
- Client: client,
137
- Config: &config.Config{SelectedBoard: ""},
138
- }
139
-
140
- cmd := cardListCmd
141
- cmd.SetContext(testApp.ToContext(context.Background()))
142
-
143
- err := handleListCards(cmd)
144
- if err == nil {
145
- t.Errorf("expected error when board not selected")
146
- }
147
- if err.Error() != "no board selected" {
148
- t.Errorf("expected 'no board selected' error, got %v", err)
149
- }
150
- }
151
-
152
- func TestCardListCommandNoClient(t *testing.T) {
153
- testApp := &app.App{}
154
-
155
- cmd := cardListCmd
156
- cmd.SetContext(testApp.ToContext(context.Background()))
157
-
158
- err := handleListCards(cmd)
159
- if err == nil {
160
- t.Errorf("expected error when client not available")
161
- }
162
- if err.Error() != "API client not available" {
163
- t.Errorf("expected 'client not available' error, got %v", err)
164
- }
165
- }
166
-
167
- func TestCardListCommandWithTagFilter(t *testing.T) {
168
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
169
- tagIDs := r.URL.Query()["tag_ids[]"]
170
- if len(tagIDs) != 2 || tagIDs[0] != "tag-123" || tagIDs[1] != "tag-456" {
171
- t.Errorf("expected tag_ids[]=tag-123&tag_ids[]=tag-456, got %v", tagIDs)
172
- }
173
-
174
- w.Header().Set("Content-Type", "application/json")
175
- json.NewEncoder(w).Encode([]api.Card{})
176
- }))
177
- defer server.Close()
178
-
179
- client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
180
- testApp := &app.App{
181
- Client: client,
182
- Config: &config.Config{SelectedBoard: "board-123"},
183
- }
184
-
185
- cmd := newCardListCmd()
186
- cmd.SetContext(testApp.ToContext(context.Background()))
187
- cmd.ParseFlags([]string{"--tag", "tag-123", "--tag", "tag-456"})
188
-
189
- if err := handleListCards(cmd); err != nil {
190
- t.Fatalf("handleListCards failed: %v", err)
191
- }
192
- }
193
-
194
- func TestCardListCommandWithAssigneeFilter(t *testing.T) {
195
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
196
- assigneeIDs := r.URL.Query()["assignee_ids[]"]
197
- if len(assigneeIDs) != 1 || assigneeIDs[0] != "user-123" {
198
- t.Errorf("expected assignee_ids[]=user-123, got %v", assigneeIDs)
199
- }
200
-
201
- w.Header().Set("Content-Type", "application/json")
202
- json.NewEncoder(w).Encode([]api.Card{})
203
- }))
204
- defer server.Close()
205
-
206
- client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
207
- testApp := &app.App{
208
- Client: client,
209
- Config: &config.Config{SelectedBoard: "board-123"},
210
- }
211
-
212
- cmd := newCardListCmd()
213
- cmd.SetContext(testApp.ToContext(context.Background()))
214
- cmd.ParseFlags([]string{"--assignee", "user-123"})
215
-
216
- if err := handleListCards(cmd); err != nil {
217
- t.Fatalf("handleListCards failed: %v", err)
218
- }
219
- }
220
-
221
- func TestCardListCommandWithIndexedByFilter(t *testing.T) {
222
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
223
- indexedBy := r.URL.Query().Get("indexed_by")
224
- if indexedBy != "closed" {
225
- t.Errorf("expected indexed_by=closed, got %s", indexedBy)
226
- }
227
-
228
- w.Header().Set("Content-Type", "application/json")
229
- json.NewEncoder(w).Encode([]api.Card{})
230
- }))
231
- defer server.Close()
232
-
233
- client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
234
- testApp := &app.App{
235
- Client: client,
236
- Config: &config.Config{SelectedBoard: "board-123"},
237
- }
238
-
239
- cmd := newCardListCmd()
240
- cmd.SetContext(testApp.ToContext(context.Background()))
241
- cmd.ParseFlags([]string{"--indexed-by", "closed"})
242
-
243
- if err := handleListCards(cmd); err != nil {
244
- t.Fatalf("handleListCards failed: %v", err)
245
- }
246
- }
247
-
248
- func TestCardListCommandWithSortedByFilter(t *testing.T) {
249
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
250
- sortedBy := r.URL.Query().Get("sorted_by")
251
- if sortedBy != "newest" {
252
- t.Errorf("expected sorted_by=newest, got %s", sortedBy)
253
- }
254
-
255
- w.Header().Set("Content-Type", "application/json")
256
- json.NewEncoder(w).Encode([]api.Card{})
257
- }))
258
- defer server.Close()
259
-
260
- client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
261
- testApp := &app.App{
262
- Client: client,
263
- Config: &config.Config{SelectedBoard: "board-123"},
264
- }
265
-
266
- cmd := newCardListCmd()
267
- cmd.SetContext(testApp.ToContext(context.Background()))
268
- cmd.ParseFlags([]string{"--sorted-by", "newest"})
269
-
270
- if err := handleListCards(cmd); err != nil {
271
- t.Fatalf("handleListCards failed: %v", err)
272
- }
273
- }
274
-
275
- func TestCardListCommandWithUnassignedFilter(t *testing.T) {
276
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
277
- assignmentStatus := r.URL.Query().Get("assignment_status")
278
- if assignmentStatus != "unassigned" {
279
- t.Errorf("expected assignment_status=unassigned, got %s", assignmentStatus)
280
- }
281
-
282
- w.Header().Set("Content-Type", "application/json")
283
- json.NewEncoder(w).Encode([]api.Card{})
284
- }))
285
- defer server.Close()
286
-
287
- client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
288
- testApp := &app.App{
289
- Client: client,
290
- Config: &config.Config{SelectedBoard: "board-123"},
291
- }
292
-
293
- cmd := newCardListCmd()
294
- cmd.SetContext(testApp.ToContext(context.Background()))
295
- cmd.ParseFlags([]string{"--unassigned"})
296
-
297
- if err := handleListCards(cmd); err != nil {
298
- t.Fatalf("handleListCards failed: %v", err)
299
- }
300
- }
301
-
302
- func TestCardListCommandWithSearchFilter(t *testing.T) {
303
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
304
- terms := r.URL.Query()["terms[]"]
305
- if len(terms) != 2 || terms[0] != "bug" || terms[1] != "critical" {
306
- t.Errorf("expected terms[]=bug&terms[]=critical, got %v", terms)
307
- }
308
-
309
- w.Header().Set("Content-Type", "application/json")
310
- json.NewEncoder(w).Encode([]api.Card{})
311
- }))
312
- defer server.Close()
313
-
314
- client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
315
- testApp := &app.App{
316
- Client: client,
317
- Config: &config.Config{SelectedBoard: "board-123"},
318
- }
319
-
320
- cmd := newCardListCmd()
321
- cmd.SetContext(testApp.ToContext(context.Background()))
322
- cmd.ParseFlags([]string{"--search", "bug", "--search", "critical"})
323
-
324
- if err := handleListCards(cmd); err != nil {
325
- t.Fatalf("handleListCards failed: %v", err)
326
- }
327
- }
328
-
329
- func TestCardListCommandWithMultipleFilters(t *testing.T) {
330
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
331
- boardIDs := r.URL.Query()["board_ids[]"]
332
- if len(boardIDs) == 0 || boardIDs[0] != "board-123" {
333
- t.Errorf("expected board_ids[]=board-123, got %v", boardIDs)
334
- }
335
-
336
- tagIDs := r.URL.Query()["tag_ids[]"]
337
- if len(tagIDs) != 1 || tagIDs[0] != "tag-123" {
338
- t.Errorf("expected tag_ids[]=tag-123, got %v", tagIDs)
339
- }
340
-
341
- assigneeIDs := r.URL.Query()["assignee_ids[]"]
342
- if len(assigneeIDs) != 1 || assigneeIDs[0] != "user-456" {
343
- t.Errorf("expected assignee_ids[]=user-456, got %v", assigneeIDs)
344
- }
345
-
346
- sortedBy := r.URL.Query().Get("sorted_by")
347
- if sortedBy != "latest" {
348
- t.Errorf("expected sorted_by=latest, got %s", sortedBy)
349
- }
350
-
351
- w.Header().Set("Content-Type", "application/json")
352
- json.NewEncoder(w).Encode([]api.Card{})
353
- }))
354
- defer server.Close()
355
-
356
- client := testutil.NewTestClient(server.URL, "", "board-123", "test-token")
357
- testApp := &app.App{
358
- Client: client,
359
- Config: &config.Config{SelectedBoard: "board-123"},
360
- }
361
-
362
- cmd := newCardListCmd()
363
- cmd.SetContext(testApp.ToContext(context.Background()))
364
- cmd.ParseFlags([]string{
365
- "--tag", "tag-123",
366
- "--assignee", "user-456",
367
- "--sorted-by", "latest",
368
- })
369
-
370
- if err := handleListCards(cmd); err != nil {
371
- t.Fatalf("handleListCards failed: %v", err)
372
- }
373
- }
@@ -1,46 +0,0 @@
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.Fprintf(cmd.OutOrStdout(), "✓ Card #%d moved to Not Now successfully\n", cardNum)
41
- return nil
42
- }
43
-
44
- func init() {
45
- cardCmd.AddCommand(cardNotNowCmd)
46
- }