github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/pr/close/close_test.go (about) 1 package close 2 3 import ( 4 "bytes" 5 "io" 6 "net/http" 7 "strings" 8 "testing" 9 10 "github.com/MakeNowJust/heredoc" 11 "github.com/ungtb10d/cli/v2/api" 12 "github.com/ungtb10d/cli/v2/git" 13 "github.com/ungtb10d/cli/v2/internal/ghrepo" 14 "github.com/ungtb10d/cli/v2/internal/run" 15 "github.com/ungtb10d/cli/v2/pkg/cmd/pr/shared" 16 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 17 "github.com/ungtb10d/cli/v2/pkg/httpmock" 18 "github.com/ungtb10d/cli/v2/pkg/iostreams" 19 "github.com/ungtb10d/cli/v2/test" 20 "github.com/google/shlex" 21 "github.com/stretchr/testify/assert" 22 ) 23 24 // repo: either "baseOwner/baseRepo" or "baseOwner/baseRepo:defaultBranch" 25 // prHead: "headOwner/headRepo:headBranch" 26 func stubPR(repo, prHead string) (ghrepo.Interface, *api.PullRequest) { 27 defaultBranch := "" 28 if idx := strings.IndexRune(repo, ':'); idx >= 0 { 29 defaultBranch = repo[idx+1:] 30 repo = repo[:idx] 31 } 32 baseRepo, err := ghrepo.FromFullName(repo) 33 if err != nil { 34 panic(err) 35 } 36 if defaultBranch != "" { 37 baseRepo = api.InitRepoHostname(&api.Repository{ 38 Name: baseRepo.RepoName(), 39 Owner: api.RepositoryOwner{Login: baseRepo.RepoOwner()}, 40 DefaultBranchRef: api.BranchRef{Name: defaultBranch}, 41 }, baseRepo.RepoHost()) 42 } 43 44 idx := strings.IndexRune(prHead, ':') 45 headRefName := prHead[idx+1:] 46 headRepo, err := ghrepo.FromFullName(prHead[:idx]) 47 if err != nil { 48 panic(err) 49 } 50 51 return baseRepo, &api.PullRequest{ 52 ID: "THE-ID", 53 Number: 96, 54 State: "OPEN", 55 HeadRefName: headRefName, 56 HeadRepositoryOwner: api.Owner{Login: headRepo.RepoOwner()}, 57 HeadRepository: &api.PRRepository{Name: headRepo.RepoName()}, 58 IsCrossRepository: !ghrepo.IsSame(baseRepo, headRepo), 59 } 60 } 61 62 func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, error) { 63 ios, _, stdout, stderr := iostreams.Test() 64 ios.SetStdoutTTY(isTTY) 65 ios.SetStdinTTY(isTTY) 66 ios.SetStderrTTY(isTTY) 67 68 factory := &cmdutil.Factory{ 69 IOStreams: ios, 70 HttpClient: func() (*http.Client, error) { 71 return &http.Client{Transport: rt}, nil 72 }, 73 Branch: func() (string, error) { 74 return "trunk", nil 75 }, 76 GitClient: &git.Client{GitPath: "some/path/git"}, 77 } 78 79 cmd := NewCmdClose(factory, nil) 80 81 argv, err := shlex.Split(cli) 82 if err != nil { 83 return nil, err 84 } 85 cmd.SetArgs(argv) 86 87 cmd.SetIn(&bytes.Buffer{}) 88 cmd.SetOut(io.Discard) 89 cmd.SetErr(io.Discard) 90 91 _, err = cmd.ExecuteC() 92 return &test.CmdOut{ 93 OutBuf: stdout, 94 ErrBuf: stderr, 95 }, err 96 } 97 98 func TestNoArgs(t *testing.T) { 99 http := &httpmock.Registry{} 100 defer http.Verify(t) 101 102 _, err := runCommand(http, true, "") 103 104 assert.EqualError(t, err, "cannot close pull request: number, url, or branch required") 105 } 106 107 func TestPrClose(t *testing.T) { 108 http := &httpmock.Registry{} 109 defer http.Verify(t) 110 111 baseRepo, pr := stubPR("OWNER/REPO", "OWNER/REPO:feature") 112 pr.Title = "The title of the PR" 113 shared.RunCommandFinder("96", pr, baseRepo) 114 115 http.Register( 116 httpmock.GraphQL(`mutation PullRequestClose\b`), 117 httpmock.GraphQLMutation(`{"id": "THE-ID"}`, 118 func(inputs map[string]interface{}) { 119 assert.Equal(t, inputs["pullRequestId"], "THE-ID") 120 }), 121 ) 122 123 output, err := runCommand(http, true, "96") 124 assert.NoError(t, err) 125 assert.Equal(t, "", output.String()) 126 assert.Equal(t, "✓ Closed pull request #96 (The title of the PR)\n", output.Stderr()) 127 } 128 129 func TestPrClose_alreadyClosed(t *testing.T) { 130 http := &httpmock.Registry{} 131 defer http.Verify(t) 132 133 baseRepo, pr := stubPR("OWNER/REPO", "OWNER/REPO:feature") 134 pr.State = "CLOSED" 135 pr.Title = "The title of the PR" 136 shared.RunCommandFinder("96", pr, baseRepo) 137 138 output, err := runCommand(http, true, "96") 139 assert.NoError(t, err) 140 assert.Equal(t, "", output.String()) 141 assert.Equal(t, "! Pull request #96 (The title of the PR) is already closed\n", output.Stderr()) 142 } 143 144 func TestPrClose_deleteBranch_sameRepo(t *testing.T) { 145 http := &httpmock.Registry{} 146 defer http.Verify(t) 147 148 baseRepo, pr := stubPR("OWNER/REPO", "OWNER/REPO:blueberries") 149 pr.Title = "The title of the PR" 150 shared.RunCommandFinder("96", pr, baseRepo) 151 152 http.Register( 153 httpmock.GraphQL(`mutation PullRequestClose\b`), 154 httpmock.GraphQLMutation(`{"id": "THE-ID"}`, 155 func(inputs map[string]interface{}) { 156 assert.Equal(t, inputs["pullRequestId"], "THE-ID") 157 }), 158 ) 159 http.Register( 160 httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/blueberries"), 161 httpmock.StringResponse(`{}`)) 162 163 cs, cmdTeardown := run.Stub() 164 defer cmdTeardown(t) 165 166 cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "") 167 cs.Register(`git branch -D blueberries`, 0, "") 168 169 output, err := runCommand(http, true, `96 --delete-branch`) 170 assert.NoError(t, err) 171 assert.Equal(t, "", output.String()) 172 assert.Equal(t, heredoc.Doc(` 173 ✓ Closed pull request #96 (The title of the PR) 174 ✓ Deleted branch blueberries 175 `), output.Stderr()) 176 } 177 178 func TestPrClose_deleteBranch_crossRepo(t *testing.T) { 179 http := &httpmock.Registry{} 180 defer http.Verify(t) 181 182 baseRepo, pr := stubPR("OWNER/REPO", "hubot/REPO:blueberries") 183 pr.Title = "The title of the PR" 184 shared.RunCommandFinder("96", pr, baseRepo) 185 186 http.Register( 187 httpmock.GraphQL(`mutation PullRequestClose\b`), 188 httpmock.GraphQLMutation(`{"id": "THE-ID"}`, 189 func(inputs map[string]interface{}) { 190 assert.Equal(t, inputs["pullRequestId"], "THE-ID") 191 }), 192 ) 193 194 cs, cmdTeardown := run.Stub() 195 defer cmdTeardown(t) 196 197 cs.Register(`git rev-parse --verify refs/heads/blueberries`, 0, "") 198 cs.Register(`git branch -D blueberries`, 0, "") 199 200 output, err := runCommand(http, true, `96 --delete-branch`) 201 assert.NoError(t, err) 202 assert.Equal(t, "", output.String()) 203 assert.Equal(t, heredoc.Doc(` 204 ✓ Closed pull request #96 (The title of the PR) 205 ! Skipped deleting the remote branch of a pull request from fork 206 ✓ Deleted branch blueberries 207 `), output.Stderr()) 208 } 209 210 func TestPrClose_deleteBranch_sameBranch(t *testing.T) { 211 http := &httpmock.Registry{} 212 defer http.Verify(t) 213 214 baseRepo, pr := stubPR("OWNER/REPO:main", "OWNER/REPO:trunk") 215 pr.Title = "The title of the PR" 216 shared.RunCommandFinder("96", pr, baseRepo) 217 218 http.Register( 219 httpmock.GraphQL(`mutation PullRequestClose\b`), 220 httpmock.GraphQLMutation(`{"id": "THE-ID"}`, 221 func(inputs map[string]interface{}) { 222 assert.Equal(t, inputs["pullRequestId"], "THE-ID") 223 }), 224 ) 225 http.Register( 226 httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/trunk"), 227 httpmock.StringResponse(`{}`)) 228 229 cs, cmdTeardown := run.Stub() 230 defer cmdTeardown(t) 231 232 cs.Register(`git checkout main`, 0, "") 233 cs.Register(`git rev-parse --verify refs/heads/trunk`, 0, "") 234 cs.Register(`git branch -D trunk`, 0, "") 235 236 output, err := runCommand(http, true, `96 --delete-branch`) 237 assert.NoError(t, err) 238 assert.Equal(t, "", output.String()) 239 assert.Equal(t, heredoc.Doc(` 240 ✓ Closed pull request #96 (The title of the PR) 241 ✓ Deleted branch trunk and switched to branch main 242 `), output.Stderr()) 243 } 244 245 func TestPrClose_deleteBranch_notInGitRepo(t *testing.T) { 246 http := &httpmock.Registry{} 247 defer http.Verify(t) 248 249 baseRepo, pr := stubPR("OWNER/REPO:main", "OWNER/REPO:trunk") 250 pr.Title = "The title of the PR" 251 shared.RunCommandFinder("96", pr, baseRepo) 252 253 http.Register( 254 httpmock.GraphQL(`mutation PullRequestClose\b`), 255 httpmock.GraphQLMutation(`{"id": "THE-ID"}`, 256 func(inputs map[string]interface{}) { 257 assert.Equal(t, inputs["pullRequestId"], "THE-ID") 258 }), 259 ) 260 http.Register( 261 httpmock.REST("DELETE", "repos/OWNER/REPO/git/refs/heads/trunk"), 262 httpmock.StringResponse(`{}`)) 263 264 cs, cmdTeardown := run.Stub() 265 defer cmdTeardown(t) 266 267 cs.Register(`git rev-parse --verify refs/heads/trunk`, 128, "could not determine current branch: fatal: not a git repository (or any of the parent directories): .git") 268 269 output, err := runCommand(http, true, `96 --delete-branch`) 270 assert.NoError(t, err) 271 assert.Equal(t, "", output.String()) 272 assert.Equal(t, heredoc.Doc(` 273 ✓ Closed pull request #96 (The title of the PR) 274 ! Skipped deleting the local branch since current directory is not a git repository 275 ✓ Deleted branch trunk 276 `), output.Stderr()) 277 } 278 279 func TestPrClose_withComment(t *testing.T) { 280 http := &httpmock.Registry{} 281 defer http.Verify(t) 282 283 baseRepo, pr := stubPR("OWNER/REPO", "OWNER/REPO:feature") 284 pr.Title = "The title of the PR" 285 shared.RunCommandFinder("96", pr, baseRepo) 286 287 http.Register( 288 httpmock.GraphQL(`mutation CommentCreate\b`), 289 httpmock.GraphQLMutation(` 290 { "data": { "addComment": { "commentEdge": { "node": { 291 "url": "https://github.com/OWNER/REPO/issues/123#issuecomment-456" 292 } } } } }`, 293 func(inputs map[string]interface{}) { 294 assert.Equal(t, "THE-ID", inputs["subjectId"]) 295 assert.Equal(t, "closing comment", inputs["body"]) 296 }), 297 ) 298 http.Register( 299 httpmock.GraphQL(`mutation PullRequestClose\b`), 300 httpmock.GraphQLMutation(`{"id": "THE-ID"}`, 301 func(inputs map[string]interface{}) { 302 assert.Equal(t, inputs["pullRequestId"], "THE-ID") 303 }), 304 ) 305 306 output, err := runCommand(http, true, "96 --comment 'closing comment'") 307 assert.NoError(t, err) 308 assert.Equal(t, "", output.String()) 309 assert.Equal(t, "✓ Closed pull request #96 (The title of the PR)\n", output.Stderr()) 310 }