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.
- package/.golangci.yml +56 -0
- package/IMPLEMENTATION_PLAN.md +58 -0
- package/docs/API.md +89 -0
- package/package.json +2 -1
- package/.env +0 -1
- package/.github/workflows/release.yml +0 -29
- package/.github/workflows/tests.yml +0 -24
- package/AGENTS.md +0 -33
- package/CHANGELOG.md +0 -194
- package/Makefile +0 -43
- package/bin/fizzy +0 -0
- package/cmd/account.go +0 -14
- package/cmd/account_list.go +0 -44
- package/cmd/account_list_test.go +0 -118
- package/cmd/board.go +0 -49
- package/cmd/board_create.go +0 -59
- package/cmd/board_create_test.go +0 -141
- package/cmd/board_delete.go +0 -40
- package/cmd/board_delete_test.go +0 -121
- package/cmd/board_list.go +0 -44
- package/cmd/board_list_test.go +0 -115
- package/cmd/board_show.go +0 -40
- package/cmd/board_show_test.go +0 -113
- package/cmd/board_test.go +0 -92
- package/cmd/board_update.go +0 -72
- package/cmd/board_update_test.go +0 -233
- package/cmd/card.go +0 -24
- package/cmd/card_assign.go +0 -55
- package/cmd/card_assign_test.go +0 -130
- package/cmd/card_close.go +0 -46
- package/cmd/card_close_test.go +0 -92
- package/cmd/card_create.go +0 -72
- package/cmd/card_create_test.go +0 -186
- package/cmd/card_delete.go +0 -46
- package/cmd/card_delete_test.go +0 -92
- package/cmd/card_golden.go +0 -46
- package/cmd/card_golden_test.go +0 -92
- package/cmd/card_list.go +0 -114
- package/cmd/card_list_test.go +0 -373
- package/cmd/card_not_now.go +0 -46
- package/cmd/card_not_now_test.go +0 -92
- package/cmd/card_reaction.go +0 -13
- package/cmd/card_reaction_create.go +0 -46
- package/cmd/card_reaction_create_test.go +0 -148
- package/cmd/card_reaction_delete.go +0 -46
- package/cmd/card_reaction_delete_test.go +0 -112
- package/cmd/card_reaction_list.go +0 -51
- package/cmd/card_reaction_list_test.go +0 -127
- package/cmd/card_reopen.go +0 -46
- package/cmd/card_reopen_test.go +0 -92
- package/cmd/card_show.go +0 -46
- package/cmd/card_show_test.go +0 -92
- package/cmd/card_tag.go +0 -51
- package/cmd/card_tag_test.go +0 -112
- package/cmd/card_triage.go +0 -46
- package/cmd/card_ungolden.go +0 -46
- package/cmd/card_ungolden_test.go +0 -92
- package/cmd/card_untriage.go +0 -46
- package/cmd/card_untriage_test.go +0 -92
- package/cmd/card_unwatch.go +0 -46
- package/cmd/card_unwatch_test.go +0 -92
- package/cmd/card_update.go +0 -82
- package/cmd/card_update_test.go +0 -149
- package/cmd/card_watch.go +0 -46
- package/cmd/card_watch_test.go +0 -92
- package/cmd/column.go +0 -14
- package/cmd/column_create.go +0 -79
- package/cmd/column_create_test.go +0 -178
- package/cmd/column_delete.go +0 -40
- package/cmd/column_delete_test.go +0 -121
- package/cmd/column_list.go +0 -44
- package/cmd/column_list_test.go +0 -138
- package/cmd/column_show.go +0 -40
- package/cmd/column_show_test.go +0 -111
- package/cmd/column_update.go +0 -67
- package/cmd/column_update_test.go +0 -198
- package/cmd/comment.go +0 -14
- package/cmd/comment_create.go +0 -51
- package/cmd/comment_create_test.go +0 -129
- package/cmd/comment_delete.go +0 -46
- package/cmd/comment_delete_test.go +0 -92
- package/cmd/comment_list.go +0 -51
- package/cmd/comment_list_test.go +0 -132
- package/cmd/comment_show.go +0 -46
- package/cmd/comment_show_test.go +0 -104
- package/cmd/comment_update.go +0 -51
- package/cmd/comment_update_test.go +0 -130
- package/cmd/login.go +0 -81
- package/cmd/login_test.go +0 -98
- package/cmd/notification.go +0 -14
- package/cmd/notification_list.go +0 -69
- package/cmd/notification_list_test.go +0 -288
- package/cmd/notification_read.go +0 -51
- package/cmd/notification_read_all.go +0 -38
- package/cmd/notification_read_all_test.go +0 -75
- package/cmd/notification_read_test.go +0 -138
- package/cmd/notification_unread.go +0 -44
- package/cmd/notification_unread_test.go +0 -99
- package/cmd/reaction.go +0 -13
- package/cmd/reaction_create.go +0 -46
- package/cmd/reaction_create_test.go +0 -113
- package/cmd/reaction_delete.go +0 -46
- package/cmd/reaction_delete_test.go +0 -92
- package/cmd/reaction_list.go +0 -51
- package/cmd/reaction_list_test.go +0 -125
- package/cmd/root.go +0 -38
- package/cmd/step.go +0 -14
- package/cmd/step_create.go +0 -53
- package/cmd/step_create_test.go +0 -171
- package/cmd/step_delete.go +0 -46
- package/cmd/step_delete_test.go +0 -92
- package/cmd/step_update.go +0 -66
- package/cmd/step_update_test.go +0 -190
- package/cmd/tag.go +0 -15
- package/cmd/tag_list.go +0 -47
- package/cmd/tag_list_test.go +0 -109
- package/cmd/use.go +0 -85
- package/cmd/use_test.go +0 -186
- package/cmd/user.go +0 -22
- package/cmd/user_deactivate.go +0 -40
- package/cmd/user_deactivate_test.go +0 -121
- package/cmd/user_list.go +0 -44
- package/cmd/user_list_test.go +0 -126
- package/cmd/user_show.go +0 -40
- package/cmd/user_show_test.go +0 -110
- package/cmd/user_update.go +0 -71
- package/cmd/user_update_test.go +0 -177
- package/go.mod +0 -31
- package/go.sum +0 -53
- package/internal/api/boards.go +0 -93
- package/internal/api/cards.go +0 -322
- package/internal/api/client.go +0 -99
- package/internal/api/columns.go +0 -113
- package/internal/api/comments.go +0 -108
- package/internal/api/identity.go +0 -24
- package/internal/api/notifications.go +0 -89
- package/internal/api/reactions.go +0 -130
- package/internal/api/steps.go +0 -101
- package/internal/api/tags.go +0 -24
- package/internal/api/types.go +0 -195
- package/internal/api/users.go +0 -75
- package/internal/app/app.go +0 -49
- package/internal/colors/colors.go +0 -32
- package/internal/config/config.go +0 -70
- package/internal/testutil/client.go +0 -26
- package/internal/ui/account_list.go +0 -14
- package/internal/ui/account_selector.go +0 -63
- package/internal/ui/board_list.go +0 -14
- package/internal/ui/board_show.go +0 -17
- package/internal/ui/card_list.go +0 -14
- package/internal/ui/card_show.go +0 -23
- package/internal/ui/column_list.go +0 -28
- package/internal/ui/column_show.go +0 -16
- package/internal/ui/comment_list.go +0 -25
- package/internal/ui/format.go +0 -27
- package/internal/ui/notification_list.go +0 -27
- package/internal/ui/reaction_list.go +0 -14
- package/internal/ui/user_list.go +0 -19
- package/internal/ui/user_show.go +0 -23
package/cmd/user_list_test.go
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
package cmd
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"bytes"
|
|
5
|
-
"context"
|
|
6
|
-
"encoding/json"
|
|
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 TestUserListCommand(t *testing.T) {
|
|
17
|
-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
18
|
-
if r.URL.Path != "/users" {
|
|
19
|
-
t.Errorf("expected /users, 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 == "" {
|
|
27
|
-
t.Error("missing Authorization header")
|
|
28
|
-
}
|
|
29
|
-
if auth != "Bearer test-token" {
|
|
30
|
-
t.Errorf("expected Bearer test-token, got %s", auth)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
w.Header().Set("Content-Type", "application/json")
|
|
34
|
-
response := []api.User{
|
|
35
|
-
{
|
|
36
|
-
ID: "user-123",
|
|
37
|
-
Name: "John Doe",
|
|
38
|
-
Email: "john@example.com",
|
|
39
|
-
Role: "admin",
|
|
40
|
-
Active: true,
|
|
41
|
-
CreatedAt: "2025-01-01T00:00:00Z",
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
ID: "user-456",
|
|
45
|
-
Name: "Jane Smith",
|
|
46
|
-
Email: "jane@example.com",
|
|
47
|
-
Role: "member",
|
|
48
|
-
Active: false,
|
|
49
|
-
CreatedAt: "2025-01-02T00:00:00Z",
|
|
50
|
-
},
|
|
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 := userListCmd
|
|
60
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
61
|
-
cmd.SetOut(&bytes.Buffer{})
|
|
62
|
-
|
|
63
|
-
if err := handleListUsers(cmd); err != nil {
|
|
64
|
-
t.Fatalf("handleListUsers failed: %v", err)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
func TestUserListCommandNoUsers(t *testing.T) {
|
|
69
|
-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
70
|
-
w.Header().Set("Content-Type", "application/json")
|
|
71
|
-
json.NewEncoder(w).Encode([]api.User{})
|
|
72
|
-
}))
|
|
73
|
-
defer server.Close()
|
|
74
|
-
|
|
75
|
-
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
76
|
-
testApp := &app.App{Client: client}
|
|
77
|
-
|
|
78
|
-
cmd := userListCmd
|
|
79
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
80
|
-
cmd.SetOut(&bytes.Buffer{})
|
|
81
|
-
|
|
82
|
-
if err := handleListUsers(cmd); err != nil {
|
|
83
|
-
t.Fatalf("handleListUsers failed: %v", err)
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
func TestUserListCommandAPIError(t *testing.T) {
|
|
88
|
-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
89
|
-
w.WriteHeader(http.StatusInternalServerError)
|
|
90
|
-
w.Write([]byte("Internal Server Error"))
|
|
91
|
-
}))
|
|
92
|
-
defer server.Close()
|
|
93
|
-
|
|
94
|
-
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
95
|
-
testApp := &app.App{Client: client}
|
|
96
|
-
|
|
97
|
-
cmd := userListCmd
|
|
98
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
99
|
-
|
|
100
|
-
cmd.SetOut(&bytes.Buffer{})
|
|
101
|
-
|
|
102
|
-
err := handleListUsers(cmd)
|
|
103
|
-
if err == nil {
|
|
104
|
-
t.Errorf("expected error for API failure")
|
|
105
|
-
}
|
|
106
|
-
if err.Error() != "fetching users: unexpected status code 500: Internal Server Error" {
|
|
107
|
-
t.Errorf("expected API error, got %v", err)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
func TestUserListCommandNoClient(t *testing.T) {
|
|
112
|
-
testApp := &app.App{}
|
|
113
|
-
|
|
114
|
-
cmd := userListCmd
|
|
115
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
116
|
-
|
|
117
|
-
cmd.SetOut(&bytes.Buffer{})
|
|
118
|
-
|
|
119
|
-
err := handleListUsers(cmd)
|
|
120
|
-
if err == nil {
|
|
121
|
-
t.Errorf("expected error when client not available")
|
|
122
|
-
}
|
|
123
|
-
if err.Error() != "API client not available" {
|
|
124
|
-
t.Errorf("expected 'client not available' error, got %v", err)
|
|
125
|
-
}
|
|
126
|
-
}
|
package/cmd/user_show.go
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
package cmd
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"context"
|
|
5
|
-
"fmt"
|
|
6
|
-
|
|
7
|
-
"github.com/rogeriopvl/fizzy/internal/app"
|
|
8
|
-
"github.com/rogeriopvl/fizzy/internal/ui"
|
|
9
|
-
"github.com/spf13/cobra"
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
var userShowCmd = &cobra.Command{
|
|
13
|
-
Use: "show <user_id>",
|
|
14
|
-
Short: "Show user details",
|
|
15
|
-
Long: `Retrieve and display detailed information about a specific user`,
|
|
16
|
-
Args: cobra.ExactArgs(1),
|
|
17
|
-
Run: func(cmd *cobra.Command, args []string) {
|
|
18
|
-
if err := handleShowUser(cmd, args[0]); err != nil {
|
|
19
|
-
fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
func handleShowUser(cmd *cobra.Command, userID string) 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
|
-
user, err := a.Client.GetUser(context.Background(), userID)
|
|
31
|
-
if err != nil {
|
|
32
|
-
return fmt.Errorf("fetching user: %w", err)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return ui.DisplayUser(cmd.OutOrStdout(), user)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
func init() {
|
|
39
|
-
userCmd.AddCommand(userShowCmd)
|
|
40
|
-
}
|
package/cmd/user_show_test.go
DELETED
|
@@ -1,110 +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/testutil"
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
func TestUserShowCommand(t *testing.T) {
|
|
16
|
-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
17
|
-
if r.URL.Path != "/users/user-123" {
|
|
18
|
-
t.Errorf("expected /users/user-123, 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.User{
|
|
31
|
-
ID: "user-123",
|
|
32
|
-
Name: "John Doe",
|
|
33
|
-
Email: "john@example.com",
|
|
34
|
-
Role: "admin",
|
|
35
|
-
Active: true,
|
|
36
|
-
CreatedAt: "2025-01-01T00:00:00Z",
|
|
37
|
-
}
|
|
38
|
-
json.NewEncoder(w).Encode(response)
|
|
39
|
-
}))
|
|
40
|
-
defer server.Close()
|
|
41
|
-
|
|
42
|
-
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
43
|
-
testApp := &app.App{Client: client}
|
|
44
|
-
|
|
45
|
-
cmd := userShowCmd
|
|
46
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
47
|
-
|
|
48
|
-
if err := handleShowUser(cmd, "user-123"); err != nil {
|
|
49
|
-
t.Fatalf("handleShowUser failed: %v", err)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
func TestUserShowCommandNotFound(t *testing.T) {
|
|
54
|
-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
55
|
-
w.WriteHeader(http.StatusNotFound)
|
|
56
|
-
w.Write([]byte("User not found"))
|
|
57
|
-
}))
|
|
58
|
-
defer server.Close()
|
|
59
|
-
|
|
60
|
-
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
61
|
-
testApp := &app.App{Client: client}
|
|
62
|
-
|
|
63
|
-
cmd := userShowCmd
|
|
64
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
65
|
-
|
|
66
|
-
err := handleShowUser(cmd, "nonexistent-user")
|
|
67
|
-
if err == nil {
|
|
68
|
-
t.Errorf("expected error for user not found")
|
|
69
|
-
}
|
|
70
|
-
if err.Error() != "fetching user: unexpected status code 404: User not found" {
|
|
71
|
-
t.Errorf("expected user not found error, got %v", err)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
func TestUserShowCommandAPIError(t *testing.T) {
|
|
76
|
-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
77
|
-
w.WriteHeader(http.StatusInternalServerError)
|
|
78
|
-
w.Write([]byte("Internal Server Error"))
|
|
79
|
-
}))
|
|
80
|
-
defer server.Close()
|
|
81
|
-
|
|
82
|
-
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
83
|
-
testApp := &app.App{Client: client}
|
|
84
|
-
|
|
85
|
-
cmd := userShowCmd
|
|
86
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
87
|
-
|
|
88
|
-
err := handleShowUser(cmd, "user-123")
|
|
89
|
-
if err == nil {
|
|
90
|
-
t.Errorf("expected error for API failure")
|
|
91
|
-
}
|
|
92
|
-
if err.Error() != "fetching user: unexpected status code 500: Internal Server Error" {
|
|
93
|
-
t.Errorf("expected API error, got %v", err)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
func TestUserShowCommandNoClient(t *testing.T) {
|
|
98
|
-
testApp := &app.App{}
|
|
99
|
-
|
|
100
|
-
cmd := userShowCmd
|
|
101
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
102
|
-
|
|
103
|
-
err := handleShowUser(cmd, "user-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
|
-
}
|
package/cmd/user_update.go
DELETED
|
@@ -1,71 +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/spf13/cobra"
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
var userUpdateCmd = &cobra.Command{
|
|
13
|
-
Use: "update <user_id>",
|
|
14
|
-
Short: "Update a user",
|
|
15
|
-
Long: `Update user settings such as name and avatar.
|
|
16
|
-
|
|
17
|
-
Avatar must be provided as a URL (e.g., https://example.com/avatar.jpg).
|
|
18
|
-
|
|
19
|
-
Example:
|
|
20
|
-
fizzy user update user-123 --name "John Doe"
|
|
21
|
-
fizzy user update user-123 --avatar https://example.com/avatar.jpg`,
|
|
22
|
-
Args: cobra.ExactArgs(1),
|
|
23
|
-
Run: func(cmd *cobra.Command, args []string) {
|
|
24
|
-
if err := handleUpdateUser(cmd, args[0]); err != nil {
|
|
25
|
-
fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
func handleUpdateUser(cmd *cobra.Command, userID string) error {
|
|
31
|
-
if !cmd.Flags().Changed("name") && !cmd.Flags().Changed("avatar") {
|
|
32
|
-
return fmt.Errorf("at least one flag must be provided (--name or --avatar)")
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
a := app.FromContext(cmd.Context())
|
|
36
|
-
if a == nil || a.Client == nil {
|
|
37
|
-
return fmt.Errorf("API client not available")
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
payload := api.UpdateUserPayload{}
|
|
41
|
-
|
|
42
|
-
if cmd.Flags().Changed("name") {
|
|
43
|
-
name, err := cmd.Flags().GetString("name")
|
|
44
|
-
if err != nil {
|
|
45
|
-
return fmt.Errorf("invalid name flag: %w", err)
|
|
46
|
-
}
|
|
47
|
-
payload.Name = name
|
|
48
|
-
}
|
|
49
|
-
if cmd.Flags().Changed("avatar") {
|
|
50
|
-
avatar, err := cmd.Flags().GetString("avatar")
|
|
51
|
-
if err != nil {
|
|
52
|
-
return fmt.Errorf("invalid avatar flag: %w", err)
|
|
53
|
-
}
|
|
54
|
-
payload.Avatar = avatar
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
err := a.Client.PutUser(context.Background(), userID, payload)
|
|
58
|
-
if err != nil {
|
|
59
|
-
return fmt.Errorf("updating user: %w", err)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
fmt.Fprintf(cmd.OutOrStdout(), "✓ User '%s' updated successfully\n", userID)
|
|
63
|
-
return nil
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
func init() {
|
|
67
|
-
userUpdateCmd.Flags().StringP("name", "n", "", "User name")
|
|
68
|
-
userUpdateCmd.Flags().String("avatar", "", "Avatar URL (e.g., https://example.com/avatar.jpg)")
|
|
69
|
-
|
|
70
|
-
userCmd.AddCommand(userUpdateCmd)
|
|
71
|
-
}
|
package/cmd/user_update_test.go
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
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
|
-
"github.com/spf13/cobra"
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
func TestUserUpdateCommandSuccess(t *testing.T) {
|
|
18
|
-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
19
|
-
if r.URL.Path != "/users/user-123" {
|
|
20
|
-
t.Errorf("expected /users/user-123, got %s", r.URL.Path)
|
|
21
|
-
}
|
|
22
|
-
if r.Method != http.MethodPut {
|
|
23
|
-
t.Errorf("expected PUT, got %s", r.Method)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
auth := r.Header.Get("Authorization")
|
|
27
|
-
if auth != "Bearer test-token" {
|
|
28
|
-
t.Errorf("expected Bearer test-token, got %s", auth)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
body, _ := io.ReadAll(r.Body)
|
|
32
|
-
var payload map[string]api.UpdateUserPayload
|
|
33
|
-
if err := json.Unmarshal(body, &payload); err != nil {
|
|
34
|
-
t.Fatalf("failed to unmarshal request body: %v", err)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
userPayload := payload["user"]
|
|
38
|
-
if userPayload.Name != "Updated Name" {
|
|
39
|
-
t.Errorf("expected name 'Updated Name', got %s", userPayload.Name)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
w.WriteHeader(http.StatusNoContent)
|
|
43
|
-
}))
|
|
44
|
-
defer server.Close()
|
|
45
|
-
|
|
46
|
-
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
47
|
-
testApp := &app.App{Client: client}
|
|
48
|
-
|
|
49
|
-
cmd := userUpdateCmd
|
|
50
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
51
|
-
cmd.ParseFlags([]string{"--name", "Updated Name"})
|
|
52
|
-
|
|
53
|
-
if err := handleUpdateUser(cmd, "user-123"); err != nil {
|
|
54
|
-
t.Fatalf("handleUpdateUser failed: %v", err)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
func TestUserUpdateCommandWithAllFlags(t *testing.T) {
|
|
59
|
-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
60
|
-
if r.Method != http.MethodPut {
|
|
61
|
-
t.Errorf("expected PUT, got %s", r.Method)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
body, _ := io.ReadAll(r.Body)
|
|
65
|
-
var payload map[string]api.UpdateUserPayload
|
|
66
|
-
json.Unmarshal(body, &payload)
|
|
67
|
-
|
|
68
|
-
userPayload := payload["user"]
|
|
69
|
-
if userPayload.Name != "Updated Name" {
|
|
70
|
-
t.Errorf("expected name 'Updated Name', got %s", userPayload.Name)
|
|
71
|
-
}
|
|
72
|
-
if userPayload.Avatar != "https://example.com/avatar.png" {
|
|
73
|
-
t.Errorf("expected avatar 'https://example.com/avatar.png', got %s", userPayload.Avatar)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
w.WriteHeader(http.StatusNoContent)
|
|
77
|
-
}))
|
|
78
|
-
defer server.Close()
|
|
79
|
-
|
|
80
|
-
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
81
|
-
testApp := &app.App{Client: client}
|
|
82
|
-
|
|
83
|
-
cmd := userUpdateCmd
|
|
84
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
85
|
-
cmd.ParseFlags([]string{
|
|
86
|
-
"--name", "Updated Name",
|
|
87
|
-
"--avatar", "https://example.com/avatar.png",
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
if err := handleUpdateUser(cmd, "user-123"); err != nil {
|
|
91
|
-
t.Fatalf("handleUpdateUser failed: %v", err)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
func TestUserUpdateCommandNoFlags(t *testing.T) {
|
|
96
|
-
client := testutil.NewTestClient("http://localhost", "", "", "test-token")
|
|
97
|
-
testApp := &app.App{Client: client}
|
|
98
|
-
|
|
99
|
-
cmd := &cobra.Command{
|
|
100
|
-
Use: "update <user_id>",
|
|
101
|
-
Args: cobra.ExactArgs(1),
|
|
102
|
-
}
|
|
103
|
-
cmd.Flags().String("name", "", "User name")
|
|
104
|
-
cmd.Flags().String("avatar", "", "Avatar URL or file path")
|
|
105
|
-
|
|
106
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
107
|
-
|
|
108
|
-
err := handleUpdateUser(cmd, "user-123")
|
|
109
|
-
if err == nil {
|
|
110
|
-
t.Errorf("expected error when no flags provided")
|
|
111
|
-
}
|
|
112
|
-
if err.Error() != "at least one flag must be provided (--name or --avatar)" {
|
|
113
|
-
t.Errorf("expected flag requirement error, got %v", err)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
func TestUserUpdateCommandNotFound(t *testing.T) {
|
|
118
|
-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
119
|
-
w.WriteHeader(http.StatusNotFound)
|
|
120
|
-
w.Write([]byte("User not found"))
|
|
121
|
-
}))
|
|
122
|
-
defer server.Close()
|
|
123
|
-
|
|
124
|
-
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
125
|
-
testApp := &app.App{Client: client}
|
|
126
|
-
|
|
127
|
-
cmd := userUpdateCmd
|
|
128
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
129
|
-
cmd.ParseFlags([]string{"--name", "Updated Name"})
|
|
130
|
-
|
|
131
|
-
err := handleUpdateUser(cmd, "nonexistent-user")
|
|
132
|
-
if err == nil {
|
|
133
|
-
t.Errorf("expected error for user not found")
|
|
134
|
-
}
|
|
135
|
-
if err.Error() != "updating user: unexpected status code 404: User not found" {
|
|
136
|
-
t.Errorf("expected user not found error, got %v", err)
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
func TestUserUpdateCommandAPIError(t *testing.T) {
|
|
141
|
-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
142
|
-
w.WriteHeader(http.StatusInternalServerError)
|
|
143
|
-
w.Write([]byte("Internal Server Error"))
|
|
144
|
-
}))
|
|
145
|
-
defer server.Close()
|
|
146
|
-
|
|
147
|
-
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
148
|
-
testApp := &app.App{Client: client}
|
|
149
|
-
|
|
150
|
-
cmd := userUpdateCmd
|
|
151
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
152
|
-
cmd.ParseFlags([]string{"--name", "Updated Name"})
|
|
153
|
-
|
|
154
|
-
err := handleUpdateUser(cmd, "user-123")
|
|
155
|
-
if err == nil {
|
|
156
|
-
t.Errorf("expected error for API failure")
|
|
157
|
-
}
|
|
158
|
-
if err.Error() != "updating user: unexpected status code 500: Internal Server Error" {
|
|
159
|
-
t.Errorf("expected API error, got %v", err)
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
func TestUserUpdateCommandNoClient(t *testing.T) {
|
|
164
|
-
testApp := &app.App{}
|
|
165
|
-
|
|
166
|
-
cmd := userUpdateCmd
|
|
167
|
-
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
168
|
-
cmd.ParseFlags([]string{"--name", "Updated Name"})
|
|
169
|
-
|
|
170
|
-
err := handleUpdateUser(cmd, "user-123")
|
|
171
|
-
if err == nil {
|
|
172
|
-
t.Errorf("expected error when client not available")
|
|
173
|
-
}
|
|
174
|
-
if err.Error() != "API client not available" {
|
|
175
|
-
t.Errorf("expected 'client not available' error, got %v", err)
|
|
176
|
-
}
|
|
177
|
-
}
|
package/go.mod
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
module github.com/rogeriopvl/fizzy
|
|
2
|
-
|
|
3
|
-
go 1.25.1
|
|
4
|
-
|
|
5
|
-
require (
|
|
6
|
-
github.com/charmbracelet/bubbletea v1.3.10
|
|
7
|
-
github.com/charmbracelet/lipgloss v1.1.0
|
|
8
|
-
github.com/spf13/cobra v1.10.2
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
require (
|
|
12
|
-
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
|
13
|
-
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
|
14
|
-
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
|
15
|
-
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
|
16
|
-
github.com/charmbracelet/x/term v0.2.1 // indirect
|
|
17
|
-
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
|
18
|
-
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
|
19
|
-
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
|
20
|
-
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
21
|
-
github.com/mattn/go-localereader v0.0.1 // indirect
|
|
22
|
-
github.com/mattn/go-runewidth v0.0.16 // indirect
|
|
23
|
-
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
|
24
|
-
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
25
|
-
github.com/muesli/termenv v0.16.0 // indirect
|
|
26
|
-
github.com/rivo/uniseg v0.4.7 // indirect
|
|
27
|
-
github.com/spf13/pflag v1.0.9 // indirect
|
|
28
|
-
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
|
29
|
-
golang.org/x/sys v0.36.0 // indirect
|
|
30
|
-
golang.org/x/text v0.3.8 // indirect
|
|
31
|
-
)
|
package/go.sum
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
|
2
|
-
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
|
3
|
-
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
|
4
|
-
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
|
5
|
-
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
|
6
|
-
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
|
7
|
-
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
|
8
|
-
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
|
9
|
-
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
|
10
|
-
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
|
11
|
-
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
|
12
|
-
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
|
13
|
-
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
|
14
|
-
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
|
15
|
-
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
|
16
|
-
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
|
17
|
-
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
|
18
|
-
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
|
19
|
-
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
|
20
|
-
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
|
21
|
-
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
|
22
|
-
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
23
|
-
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
24
|
-
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
|
25
|
-
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
|
26
|
-
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
|
27
|
-
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
28
|
-
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
|
29
|
-
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
|
30
|
-
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
|
31
|
-
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
|
32
|
-
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
|
33
|
-
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
|
34
|
-
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
35
|
-
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|
36
|
-
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
37
|
-
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
38
|
-
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
|
39
|
-
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
|
40
|
-
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
|
|
41
|
-
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
42
|
-
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
|
43
|
-
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
|
44
|
-
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
|
45
|
-
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
|
|
46
|
-
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
|
47
|
-
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
48
|
-
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
49
|
-
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
|
50
|
-
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
51
|
-
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
|
52
|
-
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|
53
|
-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|