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  }