github.com/abdfnx/gh-api@v0.0.0-20210414084727-f5432eec23b8/pkg/cmd/run/watch/watch_test.go (about) 1 package watch 2 3 import ( 4 "bytes" 5 "io/ioutil" 6 "net/http" 7 "runtime" 8 "testing" 9 "time" 10 11 "github.com/abdfnx/gh-api/internal/ghrepo" 12 "github.com/abdfnx/gh-api/pkg/cmd/run/shared" 13 "github.com/abdfnx/gh-api/pkg/cmdutil" 14 "github.com/abdfnx/gh-api/pkg/httpmock" 15 "github.com/abdfnx/gh-api/pkg/iostreams" 16 "github.com/abdfnx/gh-api/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 io, _, _, _ := iostreams.Test() 64 io.SetStdinTTY(tt.tty) 65 io.SetStdoutTTY(tt.tty) 66 67 f := &cmdutil.Factory{ 68 IOStreams: io, 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(ioutil.Discard) 82 cmd.SetErr(ioutil.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.TestRun("more runs", 2, shared.InProgress, "") 103 completedRun := shared.TestRun("more runs", 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.TestRun("run", 1, shared.InProgress, ""), 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 } 133 successfulRunStubs := func(reg *httpmock.Registry) { 134 inProgressRun := shared.TestRun("more runs", 2, shared.InProgress, "") 135 completedRun := shared.TestRun("more runs", 2, shared.Completed, shared.Success) 136 reg.Register( 137 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"), 138 httpmock.JSONResponse(shared.RunsPayload{ 139 WorkflowRuns: []shared.Run{ 140 shared.TestRun("run", 1, shared.InProgress, ""), 141 inProgressRun, 142 }, 143 })) 144 reg.Register( 145 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/2"), 146 httpmock.JSONResponse(inProgressRun)) 147 reg.Register( 148 httpmock.REST("GET", "runs/2/jobs"), 149 httpmock.JSONResponse(shared.JobsPayload{ 150 Jobs: []shared.Job{ 151 shared.SuccessfulJob, 152 }, 153 })) 154 reg.Register( 155 httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"), 156 httpmock.JSONResponse([]shared.Annotation{})) 157 reg.Register( 158 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/2"), 159 httpmock.JSONResponse(completedRun)) 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 } 168 169 tests := []struct { 170 name string 171 httpStubs func(*httpmock.Registry) 172 askStubs func(*prompt.AskStubber) 173 opts *WatchOptions 174 tty bool 175 wantErr bool 176 errMsg string 177 wantOut string 178 onlyWindows bool 179 skipWindows bool 180 }{ 181 { 182 name: "run ID provided run already completed", 183 opts: &WatchOptions{ 184 RunID: "1234", 185 }, 186 httpStubs: func(reg *httpmock.Registry) { 187 reg.Register( 188 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"), 189 httpmock.JSONResponse(shared.FailedRun)) 190 }, 191 wantOut: "Run failed (1234) has already completed with 'failure'\n", 192 }, 193 { 194 name: "prompt, no in progress runs", 195 tty: true, 196 opts: &WatchOptions{ 197 Prompt: true, 198 }, 199 wantErr: true, 200 errMsg: "found no in progress runs to watch", 201 httpStubs: func(reg *httpmock.Registry) { 202 reg.Register( 203 httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"), 204 httpmock.JSONResponse(shared.RunsPayload{ 205 WorkflowRuns: []shared.Run{ 206 shared.FailedRun, 207 shared.SuccessfulRun, 208 }, 209 })) 210 }, 211 }, 212 { 213 name: "interval respected", 214 skipWindows: true, 215 tty: true, 216 opts: &WatchOptions{ 217 Interval: 0, 218 Prompt: true, 219 }, 220 httpStubs: successfulRunStubs, 221 askStubs: func(as *prompt.AskStubber) { 222 as.StubOne(1) 223 }, 224 wantOut: "\x1b[2J\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n- trunk more runs · 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[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n✓ trunk more runs · 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 more runs (2) completed with 'success'\n", 225 }, 226 { 227 name: "interval respected, windows", 228 onlyWindows: true, 229 opts: &WatchOptions{ 230 Interval: 0, 231 Prompt: true, 232 }, 233 httpStubs: successfulRunStubs, 234 askStubs: func(as *prompt.AskStubber) { 235 as.StubOne(1) 236 }, 237 wantOut: "\x1b[2J\x1b[2JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n- trunk more runs · 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[2JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n✓ trunk more runs · 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", 238 }, 239 { 240 name: "exit status respected", 241 tty: true, 242 skipWindows: true, 243 opts: &WatchOptions{ 244 Interval: 0, 245 Prompt: true, 246 ExitStatus: true, 247 }, 248 httpStubs: failedRunStubs, 249 askStubs: func(as *prompt.AskStubber) { 250 as.StubOne(1) 251 }, 252 wantOut: "\x1b[2J\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n- trunk more runs · 2\nTriggered via push about 59 minutes ago\n\n\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\nX trunk more runs · 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 more runs (2) completed with 'failure'\n", 253 wantErr: true, 254 errMsg: "SilentError", 255 }, 256 { 257 name: "exit status respected, windows", 258 onlyWindows: true, 259 opts: &WatchOptions{ 260 Interval: 0, 261 Prompt: true, 262 ExitStatus: true, 263 }, 264 httpStubs: failedRunStubs, 265 askStubs: func(as *prompt.AskStubber) { 266 as.StubOne(1) 267 }, 268 wantOut: "\x1b[2J\x1b[2JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n- trunk more runs · 2\nTriggered via push about 59 minutes ago\n\n\x1b[2JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\nX trunk more runs · 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", 269 wantErr: true, 270 errMsg: "SilentError", 271 }, 272 } 273 274 for _, tt := range tests { 275 if runtime.GOOS == "windows" { 276 if tt.skipWindows { 277 continue 278 } 279 } else if tt.onlyWindows { 280 continue 281 } 282 283 reg := &httpmock.Registry{} 284 tt.httpStubs(reg) 285 tt.opts.HttpClient = func() (*http.Client, error) { 286 return &http.Client{Transport: reg}, nil 287 } 288 289 tt.opts.Now = func() time.Time { 290 notnow, _ := time.Parse("2006-01-02 15:04:05", "2021-02-23 05:50:00") 291 return notnow 292 } 293 294 io, _, stdout, _ := iostreams.Test() 295 io.SetStdoutTTY(tt.tty) 296 tt.opts.IO = io 297 tt.opts.BaseRepo = func() (ghrepo.Interface, error) { 298 return ghrepo.FromFullName("OWNER/REPO") 299 } 300 301 as, teardown := prompt.InitAskStubber() 302 defer teardown() 303 if tt.askStubs != nil { 304 tt.askStubs(as) 305 } 306 307 t.Run(tt.name, func(t *testing.T) { 308 err := watchRun(tt.opts) 309 if tt.wantErr { 310 assert.Error(t, err) 311 assert.Equal(t, tt.errMsg, err.Error()) 312 if !tt.opts.ExitStatus { 313 return 314 } 315 } 316 if !tt.opts.ExitStatus { 317 assert.NoError(t, err) 318 } 319 assert.Equal(t, tt.wantOut, stdout.String()) 320 reg.Verify(t) 321 }) 322 } 323 }