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  }