github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/run/watch/watch_test.go (about) 1 package watch 2 3 import ( 4 "bytes" 5 "io" 6 "net/http" 7 "testing" 8 "time" 9 10 "github.com/ungtb10d/cli/v2/internal/ghrepo" 11 "github.com/ungtb10d/cli/v2/pkg/cmd/run/shared" 12 workflowShared "github.com/ungtb10d/cli/v2/pkg/cmd/workflow/shared" 13 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 14 "github.com/ungtb10d/cli/v2/pkg/httpmock" 15 "github.com/ungtb10d/cli/v2/pkg/iostreams" 16 "github.com/ungtb10d/cli/v2/pkg/prompt" 17 "github.com/google/shlex" 18 "github.com/stretchr/testify/assert" 19 ) 20 21 func TestNewCmdWatch(t *testing.T) { 22 tests := []struct { 23 name string 24 cli string 25 tty bool 26 wants WatchOptions 27 wantsErr bool 28 }{ 29 { 30 name: "blank nontty", 31 wantsErr: true, 32 }, 33 { 34 name: "blank tty", 35 tty: true, 36 wants: WatchOptions{ 37 Prompt: true, 38 Interval: defaultInterval, 39 }, 40 }, 41 { 42 name: "interval", 43 tty: true, 44 cli: "-i10", 45 wants: WatchOptions{ 46 Interval: 10, 47 Prompt: true, 48 }, 49 }, 50 { 51 name: "exit status", 52 cli: "1234 --exit-status", 53 wants: WatchOptions{ 54 Interval: defaultInterval, 55 RunID: "1234", 56 ExitStatus: true, 57 }, 58 }, 59 } 60 61 for _, tt := range tests { 62 t.Run(tt.name, func(t *testing.T) { 63 ios, _, _, _ := iostreams.Test() 64 ios.SetStdinTTY(tt.tty) 65 ios.SetStdoutTTY(tt.tty) 66 67 f := &cmdutil.Factory{ 68 IOStreams: ios, 69 } 70 71 argv, err := shlex.Split(tt.cli) 72 assert.NoError(t, err) 73 74 var gotOpts *WatchOptions 75 cmd := NewCmdWatch(f, func(opts *WatchOptions) error { 76 gotOpts = opts 77 return nil 78 }) 79 cmd.SetArgs(argv) 80 cmd.SetIn(&bytes.Buffer{}) 81 cmd.SetOut(io.Discard) 82 cmd.SetErr(io.Discard) 83 84 _, err = cmd.ExecuteC() 85 if tt.wantsErr { 86 assert.Error(t, err) 87 return 88 } 89 90 assert.NoError(t, err) 91 92 assert.Equal(t, tt.wants.RunID, gotOpts.RunID) 93 assert.Equal(t, tt.wants.Prompt, gotOpts.Prompt) 94 assert.Equal(t, tt.wants.ExitStatus, gotOpts.ExitStatus) 95 assert.Equal(t, tt.wants.Interval, gotOpts.Interval) 96 }) 97 } 98 } 99 100 func TestWatchRun(t *testing.T) { 101 failedRunStubs := func(reg *httpmock.Registry) { 102 inProgressRun := shared.TestRunWithCommit(2, shared.InProgress, "", "commit2") 103 completedRun := shared.TestRun(2, shared.Completed, shared.Failure) 104 reg.Register( 105 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"), 106 httpmock.JSONResponse(shared.RunsPayload{ 107 WorkflowRuns: []shared.Run{ 108 shared.TestRunWithCommit(1, shared.InProgress, "", "commit1"), 109 inProgressRun, 110 }, 111 })) 112 reg.Register( 113 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/2"), 114 httpmock.JSONResponse(inProgressRun)) 115 reg.Register( 116 httpmock.REST("GET", "runs/2/jobs"), 117 httpmock.JSONResponse(shared.JobsPayload{ 118 Jobs: []shared.Job{}})) 119 reg.Register( 120 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/2"), 121 httpmock.JSONResponse(completedRun)) 122 reg.Register( 123 httpmock.REST("GET", "runs/2/jobs"), 124 httpmock.JSONResponse(shared.JobsPayload{ 125 Jobs: []shared.Job{ 126 shared.FailedJob, 127 }, 128 })) 129 reg.Register( 130 httpmock.REST("GET", "repos/OWNER/REPO/check-runs/20/annotations"), 131 httpmock.JSONResponse(shared.FailedJobAnnotations)) 132 reg.Register( 133 httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"), 134 httpmock.JSONResponse(workflowShared.WorkflowsPayload{ 135 Workflows: []workflowShared.Workflow{ 136 shared.TestWorkflow, 137 }, 138 })) 139 reg.Register( 140 httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"), 141 httpmock.JSONResponse(shared.TestWorkflow)) 142 reg.Register( 143 httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"), 144 httpmock.JSONResponse(shared.TestWorkflow)) 145 } 146 successfulRunStubs := func(reg *httpmock.Registry) { 147 inProgressRun := shared.TestRunWithCommit(2, shared.InProgress, "", "commit2") 148 completedRun := shared.TestRun(2, shared.Completed, shared.Success) 149 reg.Register( 150 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"), 151 httpmock.JSONResponse(shared.RunsPayload{ 152 WorkflowRuns: []shared.Run{ 153 shared.TestRunWithCommit(1, shared.InProgress, "", "commit1"), 154 inProgressRun, 155 }, 156 })) 157 reg.Register( 158 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/2"), 159 httpmock.JSONResponse(inProgressRun)) 160 reg.Register( 161 httpmock.REST("GET", "runs/2/jobs"), 162 httpmock.JSONResponse(shared.JobsPayload{ 163 Jobs: []shared.Job{ 164 shared.SuccessfulJob, 165 }, 166 })) 167 reg.Register( 168 httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"), 169 httpmock.JSONResponse([]shared.Annotation{})) 170 reg.Register( 171 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/2"), 172 httpmock.JSONResponse(completedRun)) 173 reg.Register( 174 httpmock.REST("GET", "runs/2/jobs"), 175 httpmock.JSONResponse(shared.JobsPayload{ 176 Jobs: []shared.Job{ 177 shared.SuccessfulJob, 178 }, 179 })) 180 reg.Register( 181 httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"), 182 httpmock.JSONResponse(workflowShared.WorkflowsPayload{ 183 Workflows: []workflowShared.Workflow{ 184 shared.TestWorkflow, 185 }, 186 })) 187 reg.Register( 188 httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"), 189 httpmock.JSONResponse(shared.TestWorkflow)) 190 reg.Register( 191 httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"), 192 httpmock.JSONResponse(shared.TestWorkflow)) 193 } 194 195 tests := []struct { 196 name string 197 httpStubs func(*httpmock.Registry) 198 askStubs func(*prompt.AskStubber) 199 opts *WatchOptions 200 tty bool 201 wantErr bool 202 errMsg string 203 wantOut string 204 }{ 205 { 206 name: "run ID provided run already completed", 207 opts: &WatchOptions{ 208 RunID: "1234", 209 }, 210 httpStubs: func(reg *httpmock.Registry) { 211 reg.Register( 212 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"), 213 httpmock.JSONResponse(shared.FailedRun)) 214 reg.Register( 215 httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"), 216 httpmock.JSONResponse(shared.TestWorkflow)) 217 }, 218 wantOut: "Run CI (1234) has already completed with 'failure'\n", 219 }, 220 { 221 name: "already completed, exit status", 222 opts: &WatchOptions{ 223 RunID: "1234", 224 ExitStatus: true, 225 }, 226 httpStubs: func(reg *httpmock.Registry) { 227 reg.Register( 228 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"), 229 httpmock.JSONResponse(shared.FailedRun)) 230 reg.Register( 231 httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"), 232 httpmock.JSONResponse(shared.TestWorkflow)) 233 }, 234 wantOut: "Run CI (1234) has already completed with 'failure'\n", 235 wantErr: true, 236 errMsg: "SilentError", 237 }, 238 { 239 name: "prompt, no in progress runs", 240 tty: true, 241 opts: &WatchOptions{ 242 Prompt: true, 243 }, 244 wantErr: true, 245 errMsg: "found no in progress runs to watch", 246 httpStubs: func(reg *httpmock.Registry) { 247 reg.Register( 248 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"), 249 httpmock.JSONResponse(shared.RunsPayload{ 250 WorkflowRuns: []shared.Run{ 251 shared.FailedRun, 252 shared.SuccessfulRun, 253 }, 254 })) 255 reg.Register( 256 httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"), 257 httpmock.JSONResponse(workflowShared.WorkflowsPayload{ 258 Workflows: []workflowShared.Workflow{ 259 shared.TestWorkflow, 260 }, 261 })) 262 }, 263 }, 264 { 265 name: "interval respected", 266 tty: true, 267 opts: &WatchOptions{ 268 Interval: 0, 269 Prompt: true, 270 }, 271 httpStubs: successfulRunStubs, 272 askStubs: func(as *prompt.AskStubber) { 273 as.StubPrompt("Select a workflow run"). 274 AssertOptions([]string{"* commit1, CI (trunk) Feb 23, 2021", "* commit2, CI (trunk) Feb 23, 2021"}). 275 AnswerWith("* commit2, CI (trunk) Feb 23, 2021") 276 }, 277 wantOut: "\x1b[?1049h\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk CI · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\x1b[?1049l✓ trunk CI · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\n✓ Run CI (2) completed with 'success'\n", 278 }, 279 { 280 name: "exit status respected", 281 tty: true, 282 opts: &WatchOptions{ 283 Interval: 0, 284 Prompt: true, 285 ExitStatus: true, 286 }, 287 httpStubs: failedRunStubs, 288 askStubs: func(as *prompt.AskStubber) { 289 as.StubPrompt("Select a workflow run"). 290 AssertOptions([]string{"* commit1, CI (trunk) Feb 23, 2021", "* commit2, CI (trunk) Feb 23, 2021"}). 291 AnswerWith("* commit2, CI (trunk) Feb 23, 2021") 292 }, 293 wantOut: "\x1b[?1049h\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk CI · 2\nTriggered via push about 59 minutes ago\n\n\x1b[?1049lX trunk CI · 2\nTriggered via push about 59 minutes ago\n\nJOBS\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n\nX Run CI (2) completed with 'failure'\n", 294 wantErr: true, 295 errMsg: "SilentError", 296 }, 297 } 298 299 for _, tt := range tests { 300 reg := &httpmock.Registry{} 301 tt.httpStubs(reg) 302 tt.opts.HttpClient = func() (*http.Client, error) { 303 return &http.Client{Transport: reg}, nil 304 } 305 306 tt.opts.Now = func() time.Time { 307 notnow, _ := time.Parse("2006-01-02 15:04:05", "2021-02-23 05:50:00") 308 return notnow 309 } 310 311 ios, _, stdout, _ := iostreams.Test() 312 ios.SetStdoutTTY(tt.tty) 313 ios.SetAlternateScreenBufferEnabled(tt.tty) 314 tt.opts.IO = ios 315 tt.opts.BaseRepo = func() (ghrepo.Interface, error) { 316 return ghrepo.FromFullName("OWNER/REPO") 317 } 318 319 t.Run(tt.name, func(t *testing.T) { 320 //nolint:staticcheck // SA1019: prompt.NewAskStubber is deprecated: use PrompterMock 321 as := prompt.NewAskStubber(t) 322 if tt.askStubs != nil { 323 tt.askStubs(as) 324 } 325 326 err := watchRun(tt.opts) 327 if tt.wantErr { 328 assert.EqualError(t, err, tt.errMsg) 329 } else { 330 assert.NoError(t, err) 331 } 332 // avoiding using `assert.Equal` here because it would print raw escape sequences to stdout 333 if got := stdout.String(); got != tt.wantOut { 334 t.Errorf("got stdout:\n%q\nwant:\n%q", got, tt.wantOut) 335 } 336 reg.Verify(t) 337 }) 338 } 339 }