fizzy-cli 0.6.1 → 0.8.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 +46 -0
- package/bin/fizzy +0 -0
- package/cmd/board.go +1 -1
- package/cmd/board_create.go +1 -1
- package/cmd/board_delete.go +40 -0
- package/cmd/board_delete_test.go +121 -0
- package/cmd/board_show.go +40 -0
- package/cmd/board_show_test.go +113 -0
- package/cmd/board_update.go +72 -0
- package/cmd/board_update_test.go +233 -0
- package/cmd/card_assign.go +1 -1
- package/cmd/card_close.go +1 -1
- package/cmd/card_create.go +1 -1
- package/cmd/card_delete.go +1 -1
- package/cmd/card_golden.go +1 -1
- package/cmd/card_list.go +62 -1
- package/cmd/card_list_test.go +225 -0
- package/cmd/card_not_now.go +1 -1
- package/cmd/card_reaction.go +13 -0
- package/cmd/card_reaction_create.go +46 -0
- package/cmd/card_reaction_create_test.go +148 -0
- package/cmd/card_reaction_delete.go +46 -0
- package/cmd/card_reaction_delete_test.go +112 -0
- package/cmd/card_reaction_list.go +51 -0
- package/cmd/card_reaction_list_test.go +127 -0
- package/cmd/card_reopen.go +1 -1
- package/cmd/card_tag.go +1 -1
- package/cmd/card_triage.go +1 -1
- package/cmd/card_ungolden.go +1 -1
- package/cmd/card_untriage.go +1 -1
- package/cmd/card_unwatch.go +1 -1
- package/cmd/card_update.go +1 -1
- package/cmd/card_watch.go +1 -1
- package/cmd/column_create.go +1 -1
- package/cmd/column_delete.go +40 -0
- package/cmd/column_delete_test.go +121 -0
- package/cmd/column_show.go +40 -0
- package/cmd/column_show_test.go +111 -0
- package/cmd/column_update.go +67 -0
- package/cmd/column_update_test.go +198 -0
- package/cmd/comment_create.go +1 -1
- package/cmd/comment_delete.go +1 -1
- package/cmd/comment_update.go +1 -1
- package/cmd/login.go +12 -12
- package/cmd/notification_unread.go +1 -1
- package/cmd/reaction.go +2 -2
- package/cmd/reaction_create.go +1 -1
- package/cmd/reaction_delete.go +1 -1
- package/cmd/step_create.go +1 -1
- package/cmd/step_delete.go +1 -1
- package/cmd/step_update.go +1 -1
- package/cmd/user.go +22 -0
- package/cmd/user_deactivate.go +40 -0
- package/cmd/user_deactivate_test.go +121 -0
- package/cmd/user_list.go +44 -0
- package/cmd/user_list_test.go +126 -0
- package/cmd/user_show.go +40 -0
- package/cmd/user_show_test.go +110 -0
- package/cmd/user_update.go +71 -0
- package/cmd/user_update_test.go +177 -0
- package/docs/API.md +63 -2
- package/internal/api/boards.go +34 -0
- package/internal/api/cards.go +40 -6
- package/internal/api/columns.go +63 -0
- package/internal/api/reactions.go +61 -0
- package/internal/api/types.go +17 -0
- package/internal/api/users.go +75 -0
- package/internal/ui/board_show.go +17 -0
- package/internal/ui/column_show.go +16 -0
- package/internal/ui/format.go +14 -1
- package/internal/ui/user_list.go +19 -0
- package/internal/ui/user_show.go +23 -0
- package/package.json +1 -1
|
@@ -0,0 +1,121 @@
|
|
|
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 TestUserDeactivateCommandSuccess(t *testing.T) {
|
|
14
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
15
|
+
if r.URL.Path != "/users/user-123" {
|
|
16
|
+
t.Errorf("expected /users/user-123, got %s", r.URL.Path)
|
|
17
|
+
}
|
|
18
|
+
if r.Method != http.MethodDelete {
|
|
19
|
+
t.Errorf("expected DELETE, 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 := userDeactivateCmd
|
|
35
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
36
|
+
|
|
37
|
+
if err := handleDeactivateUser(cmd, "user-123"); err != nil {
|
|
38
|
+
t.Fatalf("handleDeactivateUser failed: %v", err)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func TestUserDeactivateCommandNotFound(t *testing.T) {
|
|
43
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
44
|
+
w.WriteHeader(http.StatusNotFound)
|
|
45
|
+
w.Write([]byte("User not found"))
|
|
46
|
+
}))
|
|
47
|
+
defer server.Close()
|
|
48
|
+
|
|
49
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
50
|
+
testApp := &app.App{Client: client}
|
|
51
|
+
|
|
52
|
+
cmd := userDeactivateCmd
|
|
53
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
54
|
+
|
|
55
|
+
err := handleDeactivateUser(cmd, "nonexistent-user")
|
|
56
|
+
if err == nil {
|
|
57
|
+
t.Errorf("expected error for user not found")
|
|
58
|
+
}
|
|
59
|
+
if err.Error() != "deactivating user: unexpected status code 404: User not found" {
|
|
60
|
+
t.Errorf("expected user not found error, got %v", err)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
func TestUserDeactivateCommandForbidden(t *testing.T) {
|
|
65
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
66
|
+
w.WriteHeader(http.StatusForbidden)
|
|
67
|
+
w.Write([]byte("You don't have permission to deactivate this user"))
|
|
68
|
+
}))
|
|
69
|
+
defer server.Close()
|
|
70
|
+
|
|
71
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
72
|
+
testApp := &app.App{Client: client}
|
|
73
|
+
|
|
74
|
+
cmd := userDeactivateCmd
|
|
75
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
76
|
+
|
|
77
|
+
err := handleDeactivateUser(cmd, "user-123")
|
|
78
|
+
if err == nil {
|
|
79
|
+
t.Errorf("expected error for forbidden access")
|
|
80
|
+
}
|
|
81
|
+
if err.Error() != "deactivating user: unexpected status code 403: You don't have permission to deactivate this user" {
|
|
82
|
+
t.Errorf("expected permission error, got %v", err)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
func TestUserDeactivateCommandAPIError(t *testing.T) {
|
|
87
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
88
|
+
w.WriteHeader(http.StatusInternalServerError)
|
|
89
|
+
w.Write([]byte("Internal Server Error"))
|
|
90
|
+
}))
|
|
91
|
+
defer server.Close()
|
|
92
|
+
|
|
93
|
+
client := testutil.NewTestClient(server.URL, "", "", "test-token")
|
|
94
|
+
testApp := &app.App{Client: client}
|
|
95
|
+
|
|
96
|
+
cmd := userDeactivateCmd
|
|
97
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
98
|
+
|
|
99
|
+
err := handleDeactivateUser(cmd, "user-123")
|
|
100
|
+
if err == nil {
|
|
101
|
+
t.Errorf("expected error for API failure")
|
|
102
|
+
}
|
|
103
|
+
if err.Error() != "deactivating user: unexpected status code 500: Internal Server Error" {
|
|
104
|
+
t.Errorf("expected API error, got %v", err)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
func TestUserDeactivateCommandNoClient(t *testing.T) {
|
|
109
|
+
testApp := &app.App{}
|
|
110
|
+
|
|
111
|
+
cmd := userDeactivateCmd
|
|
112
|
+
cmd.SetContext(testApp.ToContext(context.Background()))
|
|
113
|
+
|
|
114
|
+
err := handleDeactivateUser(cmd, "user-123")
|
|
115
|
+
if err == nil {
|
|
116
|
+
t.Errorf("expected error when client not available")
|
|
117
|
+
}
|
|
118
|
+
if err.Error() != "API client not available" {
|
|
119
|
+
t.Errorf("expected 'client not available' error, got %v", err)
|
|
120
|
+
}
|
|
121
|
+
}
|
package/cmd/user_list.go
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
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 userListCmd = &cobra.Command{
|
|
13
|
+
Use: "list",
|
|
14
|
+
Short: "List all users",
|
|
15
|
+
Long: `Retrieve and display all users from the current account`,
|
|
16
|
+
Run: func(cmd *cobra.Command, args []string) {
|
|
17
|
+
if err := handleListUsers(cmd); err != nil {
|
|
18
|
+
fmt.Fprintf(cmd.OutOrStderr(), "Error: %v\n", err)
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func handleListUsers(cmd *cobra.Command) error {
|
|
24
|
+
a := app.FromContext(cmd.Context())
|
|
25
|
+
if a == nil || a.Client == nil {
|
|
26
|
+
return fmt.Errorf("API client not available")
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
users, err := a.Client.GetUsers(context.Background())
|
|
30
|
+
if err != nil {
|
|
31
|
+
return fmt.Errorf("fetching users: %w", err)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if len(users) == 0 {
|
|
35
|
+
fmt.Println("No users found")
|
|
36
|
+
return nil
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return ui.DisplayUsers(cmd.OutOrStdout(), users)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func init() {
|
|
43
|
+
userCmd.AddCommand(userListCmd)
|
|
44
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
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
|
+
}
|