github.com/andrewhsu/cli/v2@v2.0.1-0.20210910131313-d4b4061f5b89/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/andrewhsu/cli/v2/internal/ghrepo"
    12  	"github.com/andrewhsu/cli/v2/pkg/cmd/run/shared"
    13  	"github.com/andrewhsu/cli/v2/pkg/cmdutil"
    14  	"github.com/andrewhsu/cli/v2/pkg/httpmock"
    15  	"github.com/andrewhsu/cli/v2/pkg/iostreams"
    16  	"github.com/andrewhsu/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  			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: "already completed, exit status",
   195  			opts: &WatchOptions{
   196  				RunID:      "1234",
   197  				ExitStatus: true,
   198  			},
   199  			httpStubs: func(reg *httpmock.Registry) {
   200  				reg.Register(
   201  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
   202  					httpmock.JSONResponse(shared.FailedRun))
   203  			},
   204  			wantOut: "Run failed (1234) has already completed with 'failure'\n",
   205  			wantErr: true,
   206  			errMsg:  "SilentError",
   207  		},
   208  		{
   209  			name: "prompt, no in progress runs",
   210  			tty:  true,
   211  			opts: &WatchOptions{
   212  				Prompt: true,
   213  			},
   214  			wantErr: true,
   215  			errMsg:  "found no in progress runs to watch",
   216  			httpStubs: func(reg *httpmock.Registry) {
   217  				reg.Register(
   218  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
   219  					httpmock.JSONResponse(shared.RunsPayload{
   220  						WorkflowRuns: []shared.Run{
   221  							shared.FailedRun,
   222  							shared.SuccessfulRun,
   223  						},
   224  					}))
   225  			},
   226  		},
   227  		{
   228  			name:        "interval respected",
   229  			skipWindows: true,
   230  			tty:         true,
   231  			opts: &WatchOptions{
   232  				Interval: 0,
   233  				Prompt:   true,
   234  			},
   235  			httpStubs: successfulRunStubs,
   236  			askStubs: func(as *prompt.AskStubber) {
   237  				as.StubOne(1)
   238  			},
   239  			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",
   240  		},
   241  		{
   242  			name:        "interval respected, windows",
   243  			onlyWindows: true,
   244  			opts: &WatchOptions{
   245  				Interval: 0,
   246  				Prompt:   true,
   247  			},
   248  			httpStubs: successfulRunStubs,
   249  			askStubs: func(as *prompt.AskStubber) {
   250  				as.StubOne(1)
   251  			},
   252  			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",
   253  		},
   254  		{
   255  			name:        "exit status respected",
   256  			tty:         true,
   257  			skipWindows: true,
   258  			opts: &WatchOptions{
   259  				Interval:   0,
   260  				Prompt:     true,
   261  				ExitStatus: true,
   262  			},
   263  			httpStubs: failedRunStubs,
   264  			askStubs: func(as *prompt.AskStubber) {
   265  				as.StubOne(1)
   266  			},
   267  			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",
   268  			wantErr: true,
   269  			errMsg:  "SilentError",
   270  		},
   271  		{
   272  			name:        "exit status respected, windows",
   273  			onlyWindows: true,
   274  			opts: &WatchOptions{
   275  				Interval:   0,
   276  				Prompt:     true,
   277  				ExitStatus: true,
   278  			},
   279  			httpStubs: failedRunStubs,
   280  			askStubs: func(as *prompt.AskStubber) {
   281  				as.StubOne(1)
   282  			},
   283  			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",
   284  			wantErr: true,
   285  			errMsg:  "SilentError",
   286  		},
   287  	}
   288  
   289  	for _, tt := range tests {
   290  		if runtime.GOOS == "windows" {
   291  			if tt.skipWindows {
   292  				continue
   293  			}
   294  		} else if tt.onlyWindows {
   295  			continue
   296  		}
   297  
   298  		reg := &httpmock.Registry{}
   299  		tt.httpStubs(reg)
   300  		tt.opts.HttpClient = func() (*http.Client, error) {
   301  			return &http.Client{Transport: reg}, nil
   302  		}
   303  
   304  		tt.opts.Now = func() time.Time {
   305  			notnow, _ := time.Parse("2006-01-02 15:04:05", "2021-02-23 05:50:00")
   306  			return notnow
   307  		}
   308  
   309  		io, _, stdout, _ := iostreams.Test()
   310  		io.SetStdoutTTY(tt.tty)
   311  		tt.opts.IO = io
   312  		tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
   313  			return ghrepo.FromFullName("OWNER/REPO")
   314  		}
   315  
   316  		as, teardown := prompt.InitAskStubber()
   317  		defer teardown()
   318  		if tt.askStubs != nil {
   319  			tt.askStubs(as)
   320  		}
   321  
   322  		t.Run(tt.name, func(t *testing.T) {
   323  			err := watchRun(tt.opts)
   324  			if tt.wantErr {
   325  				assert.EqualError(t, err, tt.errMsg)
   326  			} else {
   327  				assert.NoError(t, err)
   328  			}
   329  			assert.Equal(t, tt.wantOut, stdout.String())
   330  			reg.Verify(t)
   331  		})
   332  	}
   333  }