X Tutup
Skip to content

Commit cb7b535

Browse files
committed
Add tests for delete
1 parent d2113e3 commit cb7b535

File tree

4 files changed

+425
-7
lines changed

4 files changed

+425
-7
lines changed

cmd/ghcs/delete.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ type deleteOptions struct {
2727
prompter prompter
2828
}
2929

30+
//go:generate moq -fmt goimports -rm -out mock_prompter.go . prompter
3031
type prompter interface {
3132
Confirm(message string) (bool, error)
3233
}
3334

35+
//go:generate moq -fmt goimports -rm -out mock_api.go . apiClient
3436
type apiClient interface {
3537
GetUser(ctx context.Context) (*api.User, error)
3638
ListCodespaces(ctx context.Context, user string) ([]*api.Codespace, error)
@@ -57,9 +59,9 @@ func newDeleteCmd() *cobra.Command {
5759
},
5860
}
5961

60-
deleteCmd.Flags().StringVarP(&opts.codespaceName, "codespace", "c", "", "Delete codespace by `name`")
62+
deleteCmd.Flags().StringVarP(&opts.codespaceName, "codespace", "c", "", "The `name` of the codespace to delete")
6163
deleteCmd.Flags().BoolVar(&opts.deleteAll, "all", false, "Delete all codespaces")
62-
deleteCmd.Flags().StringVarP(&opts.repoFilter, "repo", "r", "", "Delete codespaces for a repository")
64+
deleteCmd.Flags().StringVarP(&opts.repoFilter, "repo", "r", "", "Delete codespaces for a `repository`")
6365
deleteCmd.Flags().BoolVarP(&opts.skipConfirm, "force", "f", false, "Skip confirmation for codespaces that contain unsaved changes")
6466
deleteCmd.Flags().Uint16Var(&opts.keepDays, "days", 0, "Delete codespaces older than `N` days")
6567

@@ -116,6 +118,10 @@ func delete(ctx context.Context, opts deleteOptions) error {
116118
codespacesToDelete = append(codespacesToDelete, c)
117119
}
118120

121+
if len(codespacesToDelete) == 0 {
122+
return errors.New("no codespaces to delete")
123+
}
124+
119125
g := errgroup.Group{}
120126
for _, c := range codespacesToDelete {
121127
codespaceName := c.Name

cmd/ghcs/delete_test.go

Lines changed: 164 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,187 @@ package ghcs
22

33
import (
44
"context"
5+
"fmt"
6+
"sort"
57
"testing"
8+
"time"
9+
10+
"github.com/github/ghcs/internal/api"
611
)
712

813
func TestDelete(t *testing.T) {
14+
user := &api.User{Login: "hubot"}
15+
now, _ := time.Parse(time.RFC3339, "2021-09-22T00:00:00Z")
16+
daysAgo := func(n int) string {
17+
return now.Add(time.Hour * -time.Duration(24*n)).Format(time.RFC3339)
18+
}
19+
920
tests := []struct {
10-
name string
11-
opts deleteOptions
12-
wantErr bool
21+
name string
22+
opts deleteOptions
23+
codespaces []*api.Codespace
24+
confirms map[string]bool
25+
wantErr bool
26+
wantDeleted []string
1327
}{
1428
{
1529
name: "by name",
1630
opts: deleteOptions{
17-
codespaceName: "foo-bar-123",
31+
codespaceName: "hubot-robawt-abc",
32+
},
33+
codespaces: []*api.Codespace{
34+
{
35+
Name: "monalisa-spoonknife-123",
36+
},
37+
{
38+
Name: "hubot-robawt-abc",
39+
},
40+
},
41+
wantDeleted: []string{"hubot-robawt-abc"},
42+
},
43+
{
44+
name: "by repo",
45+
opts: deleteOptions{
46+
repoFilter: "monalisa/spoon-knife",
47+
},
48+
codespaces: []*api.Codespace{
49+
{
50+
Name: "monalisa-spoonknife-123",
51+
RepositoryNWO: "monalisa/Spoon-Knife",
52+
},
53+
{
54+
Name: "hubot-robawt-abc",
55+
RepositoryNWO: "hubot/ROBAWT",
56+
},
57+
{
58+
Name: "monalisa-spoonknife-c4f3",
59+
RepositoryNWO: "monalisa/Spoon-Knife",
60+
},
61+
},
62+
wantDeleted: []string{"monalisa-spoonknife-123", "monalisa-spoonknife-c4f3"},
63+
},
64+
{
65+
name: "unused",
66+
opts: deleteOptions{
67+
deleteAll: true,
68+
keepDays: 3,
69+
},
70+
codespaces: []*api.Codespace{
71+
{
72+
Name: "monalisa-spoonknife-123",
73+
LastUsedAt: daysAgo(1),
74+
},
75+
{
76+
Name: "hubot-robawt-abc",
77+
LastUsedAt: daysAgo(4),
78+
},
79+
{
80+
Name: "monalisa-spoonknife-c4f3",
81+
LastUsedAt: daysAgo(10),
82+
},
83+
},
84+
wantDeleted: []string{"hubot-robawt-abc", "monalisa-spoonknife-c4f3"},
85+
},
86+
{
87+
name: "with confirm",
88+
opts: deleteOptions{
89+
isInteractive: true,
90+
deleteAll: true,
91+
skipConfirm: false,
92+
},
93+
codespaces: []*api.Codespace{
94+
{
95+
Name: "monalisa-spoonknife-123",
96+
Environment: api.CodespaceEnvironment{
97+
GitStatus: api.CodespaceEnvironmentGitStatus{
98+
HasUnpushedChanges: true,
99+
},
100+
},
101+
},
102+
{
103+
Name: "hubot-robawt-abc",
104+
Environment: api.CodespaceEnvironment{
105+
GitStatus: api.CodespaceEnvironmentGitStatus{
106+
HasUncommitedChanges: true,
107+
},
108+
},
109+
},
110+
{
111+
Name: "monalisa-spoonknife-c4f3",
112+
Environment: api.CodespaceEnvironment{
113+
GitStatus: api.CodespaceEnvironmentGitStatus{
114+
HasUnpushedChanges: false,
115+
HasUncommitedChanges: false,
116+
},
117+
},
118+
},
18119
},
120+
confirms: map[string]bool{
121+
"Codespace monalisa-spoonknife-123 has unsaved changes. OK to delete?": false,
122+
"Codespace hubot-robawt-abc has unsaved changes. OK to delete?": true,
123+
},
124+
wantDeleted: []string{"hubot-robawt-abc", "monalisa-spoonknife-c4f3"},
19125
},
20126
}
21127
for _, tt := range tests {
22128
t.Run(tt.name, func(t *testing.T) {
23-
err := delete(context.Background(), tt.opts)
129+
apiMock := &apiClientMock{
130+
GetUserFunc: func(_ context.Context) (*api.User, error) {
131+
return user, nil
132+
},
133+
ListCodespacesFunc: func(_ context.Context, userLogin string) ([]*api.Codespace, error) {
134+
if userLogin != user.Login {
135+
return nil, fmt.Errorf("unexpected user %q", userLogin)
136+
}
137+
return tt.codespaces, nil
138+
},
139+
DeleteCodespaceFunc: func(_ context.Context, userLogin, name string) error {
140+
if userLogin != user.Login {
141+
return fmt.Errorf("unexpected user %q", userLogin)
142+
}
143+
return nil
144+
},
145+
}
146+
opts := tt.opts
147+
opts.apiClient = apiMock
148+
opts.now = func() time.Time { return now }
149+
opts.prompter = &prompterMock{
150+
ConfirmFunc: func(msg string) (bool, error) {
151+
res, found := tt.confirms[msg]
152+
if !found {
153+
return false, fmt.Errorf("unexpected prompt %q", msg)
154+
}
155+
return res, nil
156+
},
157+
}
158+
159+
err := delete(context.Background(), opts)
24160
if (err != nil) != tt.wantErr {
25161
t.Errorf("delete() error = %v, wantErr %v", err, tt.wantErr)
26162
}
163+
if n := len(apiMock.GetUserCalls()); n != 1 {
164+
t.Errorf("GetUser invoked %d times, expected %d", n, 1)
165+
}
166+
var gotDeleted []string
167+
for _, delArgs := range apiMock.DeleteCodespaceCalls() {
168+
gotDeleted = append(gotDeleted, delArgs.Name)
169+
}
170+
sort.Strings(gotDeleted)
171+
if !sliceEquals(gotDeleted, tt.wantDeleted) {
172+
t.Errorf("deleted %q, want %q", gotDeleted, tt.wantDeleted)
173+
}
27174
})
28175
}
29176
}
177+
178+
func sliceEquals(a, b []string) bool {
179+
if len(a) != len(b) {
180+
return false
181+
}
182+
for i := range a {
183+
if a[i] != b[i] {
184+
return false
185+
}
186+
}
187+
return true
188+
}

0 commit comments

Comments
 (0)
X Tutup