github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/issue/close/close_test.go (about) 1 package close 2 3 import ( 4 "bytes" 5 "net/http" 6 "testing" 7 8 fd "github.com/ungtb10d/cli/v2/internal/featuredetection" 9 "github.com/ungtb10d/cli/v2/internal/ghrepo" 10 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 11 "github.com/ungtb10d/cli/v2/pkg/httpmock" 12 "github.com/ungtb10d/cli/v2/pkg/iostreams" 13 "github.com/google/shlex" 14 "github.com/stretchr/testify/assert" 15 ) 16 17 func TestNewCmdClose(t *testing.T) { 18 tests := []struct { 19 name string 20 input string 21 output CloseOptions 22 wantErr bool 23 errMsg string 24 }{ 25 { 26 name: "no argument", 27 input: "", 28 wantErr: true, 29 errMsg: "accepts 1 arg(s), received 0", 30 }, 31 { 32 name: "issue number", 33 input: "123", 34 output: CloseOptions{ 35 SelectorArg: "123", 36 }, 37 }, 38 { 39 name: "issue url", 40 input: "https://github.com/ungtb10d/cli/3", 41 output: CloseOptions{ 42 SelectorArg: "https://github.com/ungtb10d/cli/3", 43 }, 44 }, 45 { 46 name: "comment", 47 input: "123 --comment 'closing comment'", 48 output: CloseOptions{ 49 SelectorArg: "123", 50 Comment: "closing comment", 51 }, 52 }, 53 { 54 name: "reason", 55 input: "123 --reason 'not planned'", 56 output: CloseOptions{ 57 SelectorArg: "123", 58 Reason: "not planned", 59 }, 60 }, 61 } 62 for _, tt := range tests { 63 t.Run(tt.name, func(t *testing.T) { 64 ios, _, _, _ := iostreams.Test() 65 f := &cmdutil.Factory{ 66 IOStreams: ios, 67 } 68 argv, err := shlex.Split(tt.input) 69 assert.NoError(t, err) 70 var gotOpts *CloseOptions 71 cmd := NewCmdClose(f, func(opts *CloseOptions) error { 72 gotOpts = opts 73 return nil 74 }) 75 cmd.SetArgs(argv) 76 cmd.SetIn(&bytes.Buffer{}) 77 cmd.SetOut(&bytes.Buffer{}) 78 cmd.SetErr(&bytes.Buffer{}) 79 80 _, err = cmd.ExecuteC() 81 if tt.wantErr { 82 assert.Error(t, err) 83 assert.Equal(t, tt.errMsg, err.Error()) 84 return 85 } 86 87 assert.NoError(t, err) 88 assert.Equal(t, tt.output.SelectorArg, gotOpts.SelectorArg) 89 assert.Equal(t, tt.output.Comment, gotOpts.Comment) 90 assert.Equal(t, tt.output.Reason, gotOpts.Reason) 91 }) 92 } 93 } 94 95 func TestCloseRun(t *testing.T) { 96 tests := []struct { 97 name string 98 opts *CloseOptions 99 httpStubs func(*httpmock.Registry) 100 wantStderr string 101 wantErr bool 102 errMsg string 103 }{ 104 { 105 name: "close issue by number", 106 opts: &CloseOptions{ 107 SelectorArg: "13", 108 }, 109 httpStubs: func(reg *httpmock.Registry) { 110 reg.Register( 111 httpmock.GraphQL(`query IssueByNumber\b`), 112 httpmock.StringResponse(` 113 { "data": { "repository": { 114 "hasIssuesEnabled": true, 115 "issue": { "id": "THE-ID", "number": 13, "title": "The title of the issue"} 116 } } }`), 117 ) 118 reg.Register( 119 httpmock.GraphQL(`mutation IssueClose\b`), 120 httpmock.GraphQLMutation(`{"id": "THE-ID"}`, 121 func(inputs map[string]interface{}) { 122 assert.Equal(t, "THE-ID", inputs["issueId"]) 123 }), 124 ) 125 }, 126 wantStderr: "✓ Closed issue #13 (The title of the issue)\n", 127 }, 128 { 129 name: "close issue with comment", 130 opts: &CloseOptions{ 131 SelectorArg: "13", 132 Comment: "closing comment", 133 }, 134 httpStubs: func(reg *httpmock.Registry) { 135 reg.Register( 136 httpmock.GraphQL(`query IssueByNumber\b`), 137 httpmock.StringResponse(` 138 { "data": { "repository": { 139 "hasIssuesEnabled": true, 140 "issue": { "id": "THE-ID", "number": 13, "title": "The title of the issue"} 141 } } }`), 142 ) 143 reg.Register( 144 httpmock.GraphQL(`mutation CommentCreate\b`), 145 httpmock.GraphQLMutation(` 146 { "data": { "addComment": { "commentEdge": { "node": { 147 "url": "https://github.com/OWNER/REPO/issues/123#issuecomment-456" 148 } } } } }`, 149 func(inputs map[string]interface{}) { 150 assert.Equal(t, "THE-ID", inputs["subjectId"]) 151 assert.Equal(t, "closing comment", inputs["body"]) 152 }), 153 ) 154 reg.Register( 155 httpmock.GraphQL(`mutation IssueClose\b`), 156 httpmock.GraphQLMutation(`{"id": "THE-ID"}`, 157 func(inputs map[string]interface{}) { 158 assert.Equal(t, "THE-ID", inputs["issueId"]) 159 }), 160 ) 161 }, 162 wantStderr: "✓ Closed issue #13 (The title of the issue)\n", 163 }, 164 { 165 name: "close issue with reason", 166 opts: &CloseOptions{ 167 SelectorArg: "13", 168 Reason: "not planned", 169 Detector: &fd.EnabledDetectorMock{}, 170 }, 171 httpStubs: func(reg *httpmock.Registry) { 172 reg.Register( 173 httpmock.GraphQL(`query IssueByNumber\b`), 174 httpmock.StringResponse(` 175 { "data": { "repository": { 176 "hasIssuesEnabled": true, 177 "issue": { "id": "THE-ID", "number": 13, "title": "The title of the issue"} 178 } } }`), 179 ) 180 reg.Register( 181 httpmock.GraphQL(`mutation IssueClose\b`), 182 httpmock.GraphQLMutation(`{"id": "THE-ID"}`, 183 func(inputs map[string]interface{}) { 184 assert.Equal(t, 2, len(inputs)) 185 assert.Equal(t, "THE-ID", inputs["issueId"]) 186 assert.Equal(t, "NOT_PLANNED", inputs["stateReason"]) 187 }), 188 ) 189 }, 190 wantStderr: "✓ Closed issue #13 (The title of the issue)\n", 191 }, 192 { 193 name: "close issue with reason when reason is not supported", 194 opts: &CloseOptions{ 195 SelectorArg: "13", 196 Reason: "not planned", 197 Detector: &fd.DisabledDetectorMock{}, 198 }, 199 httpStubs: func(reg *httpmock.Registry) { 200 reg.Register( 201 httpmock.GraphQL(`query IssueByNumber\b`), 202 httpmock.StringResponse(` 203 { "data": { "repository": { 204 "hasIssuesEnabled": true, 205 "issue": { "id": "THE-ID", "number": 13, "title": "The title of the issue"} 206 } } }`), 207 ) 208 reg.Register( 209 httpmock.GraphQL(`mutation IssueClose\b`), 210 httpmock.GraphQLMutation(`{"id": "THE-ID"}`, 211 func(inputs map[string]interface{}) { 212 assert.Equal(t, 1, len(inputs)) 213 assert.Equal(t, "THE-ID", inputs["issueId"]) 214 }), 215 ) 216 }, 217 wantStderr: "✓ Closed issue #13 (The title of the issue)\n", 218 }, 219 { 220 name: "issue already closed", 221 opts: &CloseOptions{ 222 SelectorArg: "13", 223 }, 224 httpStubs: func(reg *httpmock.Registry) { 225 reg.Register( 226 httpmock.GraphQL(`query IssueByNumber\b`), 227 httpmock.StringResponse(` 228 { "data": { "repository": { 229 "hasIssuesEnabled": true, 230 "issue": { "number": 13, "title": "The title of the issue", "state": "CLOSED"} 231 } } }`), 232 ) 233 }, 234 wantStderr: "! Issue #13 (The title of the issue) is already closed\n", 235 }, 236 { 237 name: "issues disabled", 238 opts: &CloseOptions{ 239 SelectorArg: "13", 240 }, 241 httpStubs: func(reg *httpmock.Registry) { 242 reg.Register( 243 httpmock.GraphQL(`query IssueByNumber\b`), 244 httpmock.StringResponse(`{ 245 "data": { "repository": { "hasIssuesEnabled": false, "issue": null } }, 246 "errors": [ { "type": "NOT_FOUND", "path": [ "repository", "issue" ], 247 "message": "Could not resolve to an issue or pull request with the number of 13." 248 } ] }`), 249 ) 250 }, 251 wantErr: true, 252 errMsg: "the 'OWNER/REPO' repository has disabled issues", 253 }, 254 } 255 for _, tt := range tests { 256 reg := &httpmock.Registry{} 257 if tt.httpStubs != nil { 258 tt.httpStubs(reg) 259 } 260 tt.opts.HttpClient = func() (*http.Client, error) { 261 return &http.Client{Transport: reg}, nil 262 } 263 ios, _, _, stderr := iostreams.Test() 264 tt.opts.IO = ios 265 tt.opts.BaseRepo = func() (ghrepo.Interface, error) { 266 return ghrepo.FromFullName("OWNER/REPO") 267 } 268 t.Run(tt.name, func(t *testing.T) { 269 defer reg.Verify(t) 270 271 err := closeRun(tt.opts) 272 if tt.wantErr { 273 assert.EqualError(t, err, tt.errMsg) 274 return 275 } 276 277 assert.NoError(t, err) 278 assert.Equal(t, tt.wantStderr, stderr.String()) 279 }) 280 } 281 }