github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/run/view/view_test.go (about)

     1  package view
     2  
     3  import (
     4  	"archive/zip"
     5  	"bytes"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/MakeNowJust/heredoc"
    13  	"github.com/ungtb10d/cli/v2/internal/browser"
    14  	"github.com/ungtb10d/cli/v2/internal/ghrepo"
    15  	"github.com/ungtb10d/cli/v2/pkg/cmd/run/shared"
    16  	workflowShared "github.com/ungtb10d/cli/v2/pkg/cmd/workflow/shared"
    17  	"github.com/ungtb10d/cli/v2/pkg/cmdutil"
    18  	"github.com/ungtb10d/cli/v2/pkg/httpmock"
    19  	"github.com/ungtb10d/cli/v2/pkg/iostreams"
    20  	"github.com/ungtb10d/cli/v2/pkg/prompt"
    21  	"github.com/google/shlex"
    22  	"github.com/stretchr/testify/assert"
    23  )
    24  
    25  func TestNewCmdView(t *testing.T) {
    26  	tests := []struct {
    27  		name     string
    28  		cli      string
    29  		tty      bool
    30  		wants    ViewOptions
    31  		wantsErr bool
    32  	}{
    33  		{
    34  			name:     "blank nontty",
    35  			wantsErr: true,
    36  		},
    37  		{
    38  			name: "blank tty",
    39  			tty:  true,
    40  			wants: ViewOptions{
    41  				Prompt: true,
    42  			},
    43  		},
    44  		{
    45  			name: "web tty",
    46  			tty:  true,
    47  			cli:  "--web",
    48  			wants: ViewOptions{
    49  				Prompt: true,
    50  				Web:    true,
    51  			},
    52  		},
    53  		{
    54  			name: "web nontty",
    55  			cli:  "1234 --web",
    56  			wants: ViewOptions{
    57  				Web:   true,
    58  				RunID: "1234",
    59  			},
    60  		},
    61  		{
    62  			name:     "disallow web and log",
    63  			tty:      true,
    64  			cli:      "-w --log",
    65  			wantsErr: true,
    66  		},
    67  		{
    68  			name:     "disallow log and log-failed",
    69  			tty:      true,
    70  			cli:      "--log --log-failed",
    71  			wantsErr: true,
    72  		},
    73  		{
    74  			name: "exit status",
    75  			cli:  "--exit-status 1234",
    76  			wants: ViewOptions{
    77  				RunID:      "1234",
    78  				ExitStatus: true,
    79  			},
    80  		},
    81  		{
    82  			name: "verbosity",
    83  			cli:  "-v",
    84  			tty:  true,
    85  			wants: ViewOptions{
    86  				Verbose: true,
    87  				Prompt:  true,
    88  			},
    89  		},
    90  		{
    91  			name: "with arg nontty",
    92  			cli:  "1234",
    93  			wants: ViewOptions{
    94  				RunID: "1234",
    95  			},
    96  		},
    97  		{
    98  			name: "job id passed",
    99  			cli:  "--job 1234",
   100  			wants: ViewOptions{
   101  				JobID: "1234",
   102  			},
   103  		},
   104  		{
   105  			name: "log passed",
   106  			tty:  true,
   107  			cli:  "--log",
   108  			wants: ViewOptions{
   109  				Prompt: true,
   110  				Log:    true,
   111  			},
   112  		},
   113  		{
   114  			name: "tolerates both run and job id",
   115  			cli:  "1234 --job 4567",
   116  			wants: ViewOptions{
   117  				JobID: "4567",
   118  			},
   119  		},
   120  	}
   121  
   122  	for _, tt := range tests {
   123  		t.Run(tt.name, func(t *testing.T) {
   124  			ios, _, _, _ := iostreams.Test()
   125  			ios.SetStdinTTY(tt.tty)
   126  			ios.SetStdoutTTY(tt.tty)
   127  
   128  			f := &cmdutil.Factory{
   129  				IOStreams: ios,
   130  			}
   131  
   132  			argv, err := shlex.Split(tt.cli)
   133  			assert.NoError(t, err)
   134  
   135  			var gotOpts *ViewOptions
   136  			cmd := NewCmdView(f, func(opts *ViewOptions) error {
   137  				gotOpts = opts
   138  				return nil
   139  			})
   140  			cmd.SetArgs(argv)
   141  			cmd.SetIn(&bytes.Buffer{})
   142  			cmd.SetOut(io.Discard)
   143  			cmd.SetErr(io.Discard)
   144  
   145  			_, err = cmd.ExecuteC()
   146  			if tt.wantsErr {
   147  				assert.Error(t, err)
   148  				return
   149  			}
   150  
   151  			assert.NoError(t, err)
   152  
   153  			assert.Equal(t, tt.wants.RunID, gotOpts.RunID)
   154  			assert.Equal(t, tt.wants.Prompt, gotOpts.Prompt)
   155  			assert.Equal(t, tt.wants.ExitStatus, gotOpts.ExitStatus)
   156  			assert.Equal(t, tt.wants.Verbose, gotOpts.Verbose)
   157  		})
   158  	}
   159  }
   160  
   161  func TestViewRun(t *testing.T) {
   162  	tests := []struct {
   163  		name       string
   164  		httpStubs  func(*httpmock.Registry)
   165  		askStubs   func(*prompt.AskStubber)
   166  		opts       *ViewOptions
   167  		tty        bool
   168  		wantErr    bool
   169  		wantOut    string
   170  		browsedURL string
   171  		errMsg     string
   172  	}{
   173  		{
   174  			name: "associate with PR",
   175  			tty:  true,
   176  			opts: &ViewOptions{
   177  				RunID:  "3",
   178  				Prompt: false,
   179  			},
   180  			httpStubs: func(reg *httpmock.Registry) {
   181  				reg.Register(
   182  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
   183  					httpmock.JSONResponse(shared.SuccessfulRun))
   184  				reg.Register(
   185  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   186  					httpmock.JSONResponse(shared.TestWorkflow))
   187  				reg.Register(
   188  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/artifacts"),
   189  					httpmock.StringResponse(`{}`))
   190  				reg.Register(
   191  					httpmock.GraphQL(`query PullRequestForRun`),
   192  					httpmock.StringResponse(`{"data": {
   193  		"repository": {
   194  			"pullRequests": {
   195  				"nodes": [
   196  					{"number": 2898,
   197  						"headRepository": {
   198  						"owner": {
   199  						"login": "OWNER"
   200  						},
   201  												"name": "REPO"}}
   202  				]}}}}`))
   203  				reg.Register(
   204  					httpmock.REST("GET", "runs/3/jobs"),
   205  					httpmock.JSONResponse(shared.JobsPayload{
   206  						Jobs: []shared.Job{
   207  							shared.SuccessfulJob,
   208  						},
   209  					}))
   210  				reg.Register(
   211  					httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"),
   212  					httpmock.JSONResponse([]shared.Annotation{}))
   213  			},
   214  			wantOut: "\n✓ trunk CI #2898 · 3\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n\nFor more information about the job, try: gh run view --job=10\nView this run on GitHub: https://github.com/runs/3\n",
   215  		},
   216  		{
   217  			name: "exit status, failed run",
   218  			opts: &ViewOptions{
   219  				RunID:      "1234",
   220  				ExitStatus: true,
   221  			},
   222  			httpStubs: func(reg *httpmock.Registry) {
   223  				reg.Register(
   224  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
   225  					httpmock.JSONResponse(shared.FailedRun))
   226  				reg.Register(
   227  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   228  					httpmock.JSONResponse(shared.TestWorkflow))
   229  				reg.Register(
   230  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/artifacts"),
   231  					httpmock.StringResponse(`{}`))
   232  				reg.Register(
   233  					httpmock.GraphQL(`query PullRequestForRun`),
   234  					httpmock.StringResponse(``))
   235  				reg.Register(
   236  					httpmock.REST("GET", "runs/1234/jobs"),
   237  					httpmock.JSONResponse(shared.JobsPayload{
   238  						Jobs: []shared.Job{
   239  							shared.FailedJob,
   240  						},
   241  					}))
   242  				reg.Register(
   243  					httpmock.REST("GET", "repos/OWNER/REPO/check-runs/20/annotations"),
   244  					httpmock.JSONResponse(shared.FailedJobAnnotations))
   245  			},
   246  			wantOut: "\nX trunk CI · 1234\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\nTo see what failed, try: gh run view 1234 --log-failed\nView this run on GitHub: https://github.com/runs/1234\n",
   247  			wantErr: true,
   248  		},
   249  		{
   250  			name: "with artifacts",
   251  			opts: &ViewOptions{
   252  				RunID: "3",
   253  			},
   254  			httpStubs: func(reg *httpmock.Registry) {
   255  				reg.Register(
   256  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
   257  					httpmock.JSONResponse(shared.SuccessfulRun))
   258  				reg.Register(
   259  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/artifacts"),
   260  					httpmock.JSONResponse(map[string][]shared.Artifact{
   261  						"artifacts": {
   262  							shared.Artifact{Name: "artifact-1", Expired: false},
   263  							shared.Artifact{Name: "artifact-2", Expired: true},
   264  							shared.Artifact{Name: "artifact-3", Expired: false},
   265  						},
   266  					}))
   267  				reg.Register(
   268  					httpmock.GraphQL(`query PullRequestForRun`),
   269  					httpmock.StringResponse(``))
   270  				reg.Register(
   271  					httpmock.REST("GET", "runs/3/jobs"),
   272  					httpmock.JSONResponse(shared.JobsPayload{}))
   273  				reg.Register(
   274  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   275  					httpmock.JSONResponse(shared.TestWorkflow))
   276  			},
   277  			wantOut: heredoc.Doc(`
   278  
   279  				✓ trunk CI · 3
   280  				Triggered via push about 59 minutes ago
   281  
   282  				JOBS
   283  
   284  
   285  				ARTIFACTS
   286  				artifact-1
   287  				artifact-2 (expired)
   288  				artifact-3
   289  
   290  				For more information about a job, try: gh run view --job=<job-id>
   291  				View this run on GitHub: https://github.com/runs/3
   292  			`),
   293  		},
   294  		{
   295  			name: "exit status, successful run",
   296  			opts: &ViewOptions{
   297  				RunID:      "3",
   298  				ExitStatus: true,
   299  			},
   300  			httpStubs: func(reg *httpmock.Registry) {
   301  				reg.Register(
   302  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
   303  					httpmock.JSONResponse(shared.SuccessfulRun))
   304  				reg.Register(
   305  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/artifacts"),
   306  					httpmock.StringResponse(`{}`))
   307  				reg.Register(
   308  					httpmock.GraphQL(`query PullRequestForRun`),
   309  					httpmock.StringResponse(``))
   310  				reg.Register(
   311  					httpmock.REST("GET", "runs/3/jobs"),
   312  					httpmock.JSONResponse(shared.JobsPayload{
   313  						Jobs: []shared.Job{
   314  							shared.SuccessfulJob,
   315  						},
   316  					}))
   317  				reg.Register(
   318  					httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"),
   319  					httpmock.JSONResponse([]shared.Annotation{}))
   320  				reg.Register(
   321  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   322  					httpmock.JSONResponse(shared.TestWorkflow))
   323  			},
   324  			wantOut: "\n✓ trunk CI · 3\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n\nFor more information about the job, try: gh run view --job=10\nView this run on GitHub: https://github.com/runs/3\n",
   325  		},
   326  		{
   327  			name: "verbose",
   328  			tty:  true,
   329  			opts: &ViewOptions{
   330  				RunID:   "1234",
   331  				Prompt:  false,
   332  				Verbose: true,
   333  			},
   334  			httpStubs: func(reg *httpmock.Registry) {
   335  				reg.Register(
   336  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
   337  					httpmock.JSONResponse(shared.FailedRun))
   338  				reg.Register(
   339  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/artifacts"),
   340  					httpmock.StringResponse(`{}`))
   341  				reg.Register(
   342  					httpmock.GraphQL(`query PullRequestForRun`),
   343  					httpmock.StringResponse(``))
   344  				reg.Register(
   345  					httpmock.REST("GET", "runs/1234/jobs"),
   346  					httpmock.JSONResponse(shared.JobsPayload{
   347  						Jobs: []shared.Job{
   348  							shared.SuccessfulJob,
   349  							shared.FailedJob,
   350  						},
   351  					}))
   352  				reg.Register(
   353  					httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"),
   354  					httpmock.JSONResponse([]shared.Annotation{}))
   355  				reg.Register(
   356  					httpmock.REST("GET", "repos/OWNER/REPO/check-runs/20/annotations"),
   357  					httpmock.JSONResponse(shared.FailedJobAnnotations))
   358  				reg.Register(
   359  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   360  					httpmock.JSONResponse(shared.TestWorkflow))
   361  			},
   362  			wantOut: "\nX trunk CI · 1234\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n  ✓ fob the barz\n  ✓ barz the fob\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\nTo see what failed, try: gh run view 1234 --log-failed\nView this run on GitHub: https://github.com/runs/1234\n",
   363  		},
   364  		{
   365  			name: "prompts for choice, one job",
   366  			tty:  true,
   367  			httpStubs: func(reg *httpmock.Registry) {
   368  				reg.Register(
   369  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
   370  					httpmock.JSONResponse(shared.RunsPayload{
   371  						WorkflowRuns: shared.TestRuns,
   372  					}))
   373  				reg.Register(
   374  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
   375  					httpmock.JSONResponse(shared.SuccessfulRun))
   376  				reg.Register(
   377  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/artifacts"),
   378  					httpmock.StringResponse(`{}`))
   379  				reg.Register(
   380  					httpmock.GraphQL(`query PullRequestForRun`),
   381  					httpmock.StringResponse(``))
   382  				reg.Register(
   383  					httpmock.REST("GET", "runs/3/jobs"),
   384  					httpmock.JSONResponse(shared.JobsPayload{
   385  						Jobs: []shared.Job{
   386  							shared.SuccessfulJob,
   387  						},
   388  					}))
   389  				reg.Register(
   390  					httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"),
   391  					httpmock.JSONResponse([]shared.Annotation{}))
   392  				reg.Register(
   393  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
   394  					httpmock.JSONResponse(workflowShared.WorkflowsPayload{
   395  						Workflows: []workflowShared.Workflow{
   396  							shared.TestWorkflow,
   397  						},
   398  					}))
   399  				reg.Register(
   400  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   401  					httpmock.JSONResponse(shared.TestWorkflow))
   402  			},
   403  			askStubs: func(as *prompt.AskStubber) {
   404  				//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
   405  				as.StubOne(2)
   406  			},
   407  			opts: &ViewOptions{
   408  				Prompt: true,
   409  			},
   410  			wantOut: "\n✓ trunk CI · 3\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n\nFor more information about the job, try: gh run view --job=10\nView this run on GitHub: https://github.com/runs/3\n",
   411  		},
   412  		{
   413  			name: "interactive with log",
   414  			tty:  true,
   415  			opts: &ViewOptions{
   416  				Prompt: true,
   417  				Log:    true,
   418  			},
   419  			httpStubs: func(reg *httpmock.Registry) {
   420  				reg.Register(
   421  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
   422  					httpmock.JSONResponse(shared.RunsPayload{
   423  						WorkflowRuns: shared.TestRuns,
   424  					}))
   425  				reg.Register(
   426  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
   427  					httpmock.JSONResponse(shared.SuccessfulRun))
   428  				reg.Register(
   429  					httpmock.REST("GET", "runs/3/jobs"),
   430  					httpmock.JSONResponse(shared.JobsPayload{
   431  						Jobs: []shared.Job{
   432  							shared.SuccessfulJob,
   433  							shared.FailedJob,
   434  						},
   435  					}))
   436  				reg.Register(
   437  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
   438  					httpmock.FileResponse("./fixtures/run_log.zip"))
   439  				reg.Register(
   440  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   441  					httpmock.JSONResponse(shared.TestWorkflow))
   442  				reg.Register(
   443  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
   444  					httpmock.JSONResponse(workflowShared.WorkflowsPayload{
   445  						Workflows: []workflowShared.Workflow{
   446  							shared.TestWorkflow,
   447  						},
   448  					}))
   449  			},
   450  			askStubs: func(as *prompt.AskStubber) {
   451  				//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
   452  				as.StubOne(2)
   453  				//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
   454  				as.StubOne(1)
   455  			},
   456  			wantOut: coolJobRunLogOutput,
   457  		},
   458  		{
   459  			name: "noninteractive with log",
   460  			opts: &ViewOptions{
   461  				JobID: "10",
   462  				Log:   true,
   463  			},
   464  			httpStubs: func(reg *httpmock.Registry) {
   465  				reg.Register(
   466  					httpmock.REST("GET", "repos/OWNER/REPO/actions/jobs/10"),
   467  					httpmock.JSONResponse(shared.SuccessfulJob))
   468  				reg.Register(
   469  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
   470  					httpmock.JSONResponse(shared.SuccessfulRun))
   471  				reg.Register(
   472  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
   473  					httpmock.FileResponse("./fixtures/run_log.zip"))
   474  				reg.Register(
   475  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   476  					httpmock.JSONResponse(shared.TestWorkflow))
   477  			},
   478  			wantOut: coolJobRunLogOutput,
   479  		},
   480  		{
   481  			name: "interactive with run log",
   482  			tty:  true,
   483  			opts: &ViewOptions{
   484  				Prompt: true,
   485  				Log:    true,
   486  			},
   487  			httpStubs: func(reg *httpmock.Registry) {
   488  				reg.Register(
   489  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
   490  					httpmock.JSONResponse(shared.RunsPayload{
   491  						WorkflowRuns: shared.TestRuns,
   492  					}))
   493  				reg.Register(
   494  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
   495  					httpmock.JSONResponse(shared.SuccessfulRun))
   496  				reg.Register(
   497  					httpmock.REST("GET", "runs/3/jobs"),
   498  					httpmock.JSONResponse(shared.JobsPayload{
   499  						Jobs: []shared.Job{
   500  							shared.SuccessfulJob,
   501  							shared.FailedJob,
   502  						},
   503  					}))
   504  				reg.Register(
   505  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
   506  					httpmock.FileResponse("./fixtures/run_log.zip"))
   507  				reg.Register(
   508  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   509  					httpmock.JSONResponse(shared.TestWorkflow))
   510  				reg.Register(
   511  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
   512  					httpmock.JSONResponse(workflowShared.WorkflowsPayload{
   513  						Workflows: []workflowShared.Workflow{
   514  							shared.TestWorkflow,
   515  						},
   516  					}))
   517  			},
   518  			askStubs: func(as *prompt.AskStubber) {
   519  				//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
   520  				as.StubOne(2)
   521  				//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
   522  				as.StubOne(0)
   523  			},
   524  			wantOut: expectedRunLogOutput,
   525  		},
   526  		{
   527  			name: "noninteractive with run log",
   528  			tty:  true,
   529  			opts: &ViewOptions{
   530  				RunID: "3",
   531  				Log:   true,
   532  			},
   533  			httpStubs: func(reg *httpmock.Registry) {
   534  				reg.Register(
   535  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
   536  					httpmock.JSONResponse(shared.SuccessfulRun))
   537  				reg.Register(
   538  					httpmock.REST("GET", "runs/3/jobs"),
   539  					httpmock.JSONResponse(shared.JobsPayload{
   540  						Jobs: []shared.Job{
   541  							shared.SuccessfulJob,
   542  							shared.FailedJob,
   543  						},
   544  					}))
   545  				reg.Register(
   546  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
   547  					httpmock.FileResponse("./fixtures/run_log.zip"))
   548  				reg.Register(
   549  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   550  					httpmock.JSONResponse(shared.TestWorkflow))
   551  			},
   552  			wantOut: expectedRunLogOutput,
   553  		},
   554  		{
   555  			name: "interactive with log-failed",
   556  			tty:  true,
   557  			opts: &ViewOptions{
   558  				Prompt:    true,
   559  				LogFailed: true,
   560  			},
   561  			httpStubs: func(reg *httpmock.Registry) {
   562  				reg.Register(
   563  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
   564  					httpmock.JSONResponse(shared.RunsPayload{
   565  						WorkflowRuns: shared.TestRuns,
   566  					}))
   567  				reg.Register(
   568  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
   569  					httpmock.JSONResponse(shared.FailedRun))
   570  				reg.Register(
   571  					httpmock.REST("GET", "runs/1234/jobs"),
   572  					httpmock.JSONResponse(shared.JobsPayload{
   573  						Jobs: []shared.Job{
   574  							shared.SuccessfulJob,
   575  							shared.FailedJob,
   576  						},
   577  					}))
   578  				reg.Register(
   579  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
   580  					httpmock.FileResponse("./fixtures/run_log.zip"))
   581  				reg.Register(
   582  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   583  					httpmock.JSONResponse(shared.TestWorkflow))
   584  				reg.Register(
   585  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
   586  					httpmock.JSONResponse(workflowShared.WorkflowsPayload{
   587  						Workflows: []workflowShared.Workflow{
   588  							shared.TestWorkflow,
   589  						},
   590  					}))
   591  			},
   592  			askStubs: func(as *prompt.AskStubber) {
   593  				//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
   594  				as.StubOne(4)
   595  				//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
   596  				as.StubOne(2)
   597  			},
   598  			wantOut: quuxTheBarfLogOutput,
   599  		},
   600  		{
   601  			name: "noninteractive with log-failed",
   602  			opts: &ViewOptions{
   603  				JobID:     "20",
   604  				LogFailed: true,
   605  			},
   606  			httpStubs: func(reg *httpmock.Registry) {
   607  				reg.Register(
   608  					httpmock.REST("GET", "repos/OWNER/REPO/actions/jobs/20"),
   609  					httpmock.JSONResponse(shared.FailedJob))
   610  				reg.Register(
   611  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
   612  					httpmock.JSONResponse(shared.FailedRun))
   613  				reg.Register(
   614  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
   615  					httpmock.FileResponse("./fixtures/run_log.zip"))
   616  				reg.Register(
   617  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   618  					httpmock.JSONResponse(shared.TestWorkflow))
   619  			},
   620  			wantOut: quuxTheBarfLogOutput,
   621  		},
   622  		{
   623  			name: "interactive with run log-failed",
   624  			tty:  true,
   625  			opts: &ViewOptions{
   626  				Prompt:    true,
   627  				LogFailed: true,
   628  			},
   629  			httpStubs: func(reg *httpmock.Registry) {
   630  				reg.Register(
   631  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
   632  					httpmock.JSONResponse(shared.RunsPayload{
   633  						WorkflowRuns: shared.TestRuns,
   634  					}))
   635  				reg.Register(
   636  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
   637  					httpmock.JSONResponse(shared.FailedRun))
   638  				reg.Register(
   639  					httpmock.REST("GET", "runs/1234/jobs"),
   640  					httpmock.JSONResponse(shared.JobsPayload{
   641  						Jobs: []shared.Job{
   642  							shared.SuccessfulJob,
   643  							shared.FailedJob,
   644  						},
   645  					}))
   646  				reg.Register(
   647  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
   648  					httpmock.FileResponse("./fixtures/run_log.zip"))
   649  				reg.Register(
   650  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
   651  					httpmock.JSONResponse(workflowShared.WorkflowsPayload{
   652  						Workflows: []workflowShared.Workflow{
   653  							shared.TestWorkflow,
   654  						},
   655  					}))
   656  				reg.Register(
   657  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   658  					httpmock.JSONResponse(shared.TestWorkflow))
   659  			},
   660  			askStubs: func(as *prompt.AskStubber) {
   661  				//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
   662  				as.StubOne(4)
   663  				//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
   664  				as.StubOne(0)
   665  			},
   666  			wantOut: quuxTheBarfLogOutput,
   667  		},
   668  		{
   669  			name: "noninteractive with run log-failed",
   670  			tty:  true,
   671  			opts: &ViewOptions{
   672  				RunID:     "1234",
   673  				LogFailed: true,
   674  			},
   675  			httpStubs: func(reg *httpmock.Registry) {
   676  				reg.Register(
   677  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
   678  					httpmock.JSONResponse(shared.FailedRun))
   679  				reg.Register(
   680  					httpmock.REST("GET", "runs/1234/jobs"),
   681  					httpmock.JSONResponse(shared.JobsPayload{
   682  						Jobs: []shared.Job{
   683  							shared.SuccessfulJob,
   684  							shared.FailedJob,
   685  						},
   686  					}))
   687  				reg.Register(
   688  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
   689  					httpmock.FileResponse("./fixtures/run_log.zip"))
   690  				reg.Register(
   691  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   692  					httpmock.JSONResponse(shared.TestWorkflow))
   693  			},
   694  			wantOut: quuxTheBarfLogOutput,
   695  		},
   696  		{
   697  			name: "run log but run is not done",
   698  			tty:  true,
   699  			opts: &ViewOptions{
   700  				RunID: "2",
   701  				Log:   true,
   702  			},
   703  			httpStubs: func(reg *httpmock.Registry) {
   704  				reg.Register(
   705  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/2"),
   706  					httpmock.JSONResponse(shared.TestRun(2, shared.InProgress, "")))
   707  				reg.Register(
   708  					httpmock.REST("GET", "runs/2/jobs"),
   709  					httpmock.JSONResponse(shared.JobsPayload{
   710  						Jobs: []shared.Job{},
   711  					}))
   712  				reg.Register(
   713  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   714  					httpmock.JSONResponse(shared.TestWorkflow))
   715  			},
   716  			wantErr: true,
   717  			errMsg:  "run 2 is still in progress; logs will be available when it is complete",
   718  		},
   719  		{
   720  			name: "job log but job is not done",
   721  			tty:  true,
   722  			opts: &ViewOptions{
   723  				JobID: "20",
   724  				Log:   true,
   725  			},
   726  			httpStubs: func(reg *httpmock.Registry) {
   727  				reg.Register(
   728  					httpmock.REST("GET", "repos/OWNER/REPO/actions/jobs/20"),
   729  					httpmock.JSONResponse(shared.Job{
   730  						ID:     20,
   731  						Status: shared.InProgress,
   732  						RunID:  2,
   733  					}))
   734  				reg.Register(
   735  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/2"),
   736  					httpmock.JSONResponse(shared.TestRun(2, shared.InProgress, "")))
   737  				reg.Register(
   738  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   739  					httpmock.JSONResponse(shared.TestWorkflow))
   740  			},
   741  			wantErr: true,
   742  			errMsg:  "job 20 is still in progress; logs will be available when it is complete",
   743  		},
   744  		{
   745  			name: "noninteractive with job",
   746  			opts: &ViewOptions{
   747  				JobID: "10",
   748  			},
   749  			httpStubs: func(reg *httpmock.Registry) {
   750  				reg.Register(
   751  					httpmock.REST("GET", "repos/OWNER/REPO/actions/jobs/10"),
   752  					httpmock.JSONResponse(shared.SuccessfulJob))
   753  				reg.Register(
   754  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
   755  					httpmock.JSONResponse(shared.SuccessfulRun))
   756  				reg.Register(
   757  					httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"),
   758  					httpmock.JSONResponse([]shared.Annotation{}))
   759  				reg.Register(
   760  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   761  					httpmock.JSONResponse(shared.TestWorkflow))
   762  			},
   763  			wantOut: "\n✓ trunk CI · 3\nTriggered via push about 59 minutes ago\n\n✓ cool job in 4m34s (ID 10)\n  ✓ fob the barz\n  ✓ barz the fob\n\nTo see the full job log, try: gh run view --log --job=10\nView this run on GitHub: https://github.com/runs/3\n",
   764  		},
   765  		{
   766  			name: "interactive, multiple jobs, choose all jobs",
   767  			tty:  true,
   768  			opts: &ViewOptions{
   769  				Prompt: true,
   770  			},
   771  			httpStubs: func(reg *httpmock.Registry) {
   772  				reg.Register(
   773  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
   774  					httpmock.JSONResponse(shared.RunsPayload{
   775  						WorkflowRuns: shared.TestRuns,
   776  					}))
   777  				reg.Register(
   778  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
   779  					httpmock.JSONResponse(shared.SuccessfulRun))
   780  				reg.Register(
   781  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/artifacts"),
   782  					httpmock.StringResponse(`{}`))
   783  				reg.Register(
   784  					httpmock.REST("GET", "runs/3/jobs"),
   785  					httpmock.JSONResponse(shared.JobsPayload{
   786  						Jobs: []shared.Job{
   787  							shared.SuccessfulJob,
   788  							shared.FailedJob,
   789  						},
   790  					}))
   791  				reg.Register(
   792  					httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"),
   793  					httpmock.JSONResponse([]shared.Annotation{}))
   794  				reg.Register(
   795  					httpmock.REST("GET", "repos/OWNER/REPO/check-runs/20/annotations"),
   796  					httpmock.JSONResponse(shared.FailedJobAnnotations))
   797  				reg.Register(
   798  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
   799  					httpmock.JSONResponse(workflowShared.WorkflowsPayload{
   800  						Workflows: []workflowShared.Workflow{
   801  							shared.TestWorkflow,
   802  						},
   803  					}))
   804  				reg.Register(
   805  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   806  					httpmock.JSONResponse(shared.TestWorkflow))
   807  			},
   808  			askStubs: func(as *prompt.AskStubber) {
   809  				//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
   810  				as.StubOne(2)
   811  				//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
   812  				as.StubOne(0)
   813  			},
   814  			wantOut: "\n✓ trunk CI · 3\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\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\nFor more information about a job, try: gh run view --job=<job-id>\nView this run on GitHub: https://github.com/runs/3\n",
   815  		},
   816  		{
   817  			name: "interactive, multiple jobs, choose specific jobs",
   818  			tty:  true,
   819  			opts: &ViewOptions{
   820  				Prompt: true,
   821  			},
   822  			httpStubs: func(reg *httpmock.Registry) {
   823  				reg.Register(
   824  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
   825  					httpmock.JSONResponse(shared.RunsPayload{
   826  						WorkflowRuns: shared.TestRuns,
   827  					}))
   828  				reg.Register(
   829  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
   830  					httpmock.JSONResponse(shared.SuccessfulRun))
   831  				reg.Register(
   832  					httpmock.REST("GET", "runs/3/jobs"),
   833  					httpmock.JSONResponse(shared.JobsPayload{
   834  						Jobs: []shared.Job{
   835  							shared.SuccessfulJob,
   836  							shared.FailedJob,
   837  						},
   838  					}))
   839  				reg.Register(
   840  					httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"),
   841  					httpmock.JSONResponse([]shared.Annotation{}))
   842  				reg.Register(
   843  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
   844  					httpmock.JSONResponse(workflowShared.WorkflowsPayload{
   845  						Workflows: []workflowShared.Workflow{
   846  							shared.TestWorkflow,
   847  						},
   848  					}))
   849  				reg.Register(
   850  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   851  					httpmock.JSONResponse(shared.TestWorkflow))
   852  			},
   853  			askStubs: func(as *prompt.AskStubber) {
   854  				//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
   855  				as.StubOne(2)
   856  				//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
   857  				as.StubOne(1)
   858  			},
   859  			wantOut: "\n✓ trunk CI · 3\nTriggered via push about 59 minutes ago\n\n✓ cool job in 4m34s (ID 10)\n  ✓ fob the barz\n  ✓ barz the fob\n\nTo see the full job log, try: gh run view --log --job=10\nView this run on GitHub: https://github.com/runs/3\n",
   860  		},
   861  		{
   862  			name: "web run",
   863  			tty:  true,
   864  			opts: &ViewOptions{
   865  				RunID: "3",
   866  				Web:   true,
   867  			},
   868  			httpStubs: func(reg *httpmock.Registry) {
   869  				reg.Register(
   870  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
   871  					httpmock.JSONResponse(shared.SuccessfulRun))
   872  				reg.Register(
   873  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   874  					httpmock.JSONResponse(shared.TestWorkflow))
   875  			},
   876  			browsedURL: "https://github.com/runs/3",
   877  			wantOut:    "Opening github.com/runs/3 in your browser.\n",
   878  		},
   879  		{
   880  			name: "web job",
   881  			tty:  true,
   882  			opts: &ViewOptions{
   883  				JobID: "10",
   884  				Web:   true,
   885  			},
   886  			httpStubs: func(reg *httpmock.Registry) {
   887  				reg.Register(
   888  					httpmock.REST("GET", "repos/OWNER/REPO/actions/jobs/10"),
   889  					httpmock.JSONResponse(shared.SuccessfulJob))
   890  				reg.Register(
   891  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
   892  					httpmock.JSONResponse(shared.SuccessfulRun))
   893  				reg.Register(
   894  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   895  					httpmock.JSONResponse(shared.TestWorkflow))
   896  			},
   897  			browsedURL: "https://github.com/jobs/10?check_suite_focus=true",
   898  			wantOut:    "Opening github.com/jobs/10 in your browser.\n",
   899  		},
   900  		{
   901  			name: "hide job header, failure",
   902  			tty:  true,
   903  			opts: &ViewOptions{
   904  				RunID: "123",
   905  			},
   906  			httpStubs: func(reg *httpmock.Registry) {
   907  				reg.Register(
   908  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/123"),
   909  					httpmock.JSONResponse(shared.TestRun(123, shared.Completed, shared.Failure)))
   910  				reg.Register(
   911  					httpmock.REST("GET", "runs/123/jobs"),
   912  					httpmock.JSONResponse(shared.JobsPayload{Jobs: []shared.Job{}}))
   913  				reg.Register(
   914  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/123/artifacts"),
   915  					httpmock.StringResponse(`{}`))
   916  				reg.Register(
   917  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   918  					httpmock.JSONResponse(shared.TestWorkflow))
   919  			},
   920  			wantOut: "\nX trunk CI · 123\nTriggered via push about 59 minutes ago\n\nX This run likely failed because of a workflow file issue.\n\nFor more information, see: https://github.com/runs/123\n",
   921  		},
   922  		{
   923  			name: "hide job header, startup_failure",
   924  			tty:  true,
   925  			opts: &ViewOptions{
   926  				RunID: "123",
   927  			},
   928  			httpStubs: func(reg *httpmock.Registry) {
   929  				reg.Register(
   930  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/123"),
   931  					httpmock.JSONResponse(shared.TestRun(123, shared.Completed, shared.StartupFailure)))
   932  				reg.Register(
   933  					httpmock.REST("GET", "runs/123/jobs"),
   934  					httpmock.JSONResponse(shared.JobsPayload{Jobs: []shared.Job{}}))
   935  				reg.Register(
   936  					httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/123/artifacts"),
   937  					httpmock.StringResponse(`{}`))
   938  				reg.Register(
   939  					httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
   940  					httpmock.JSONResponse(shared.TestWorkflow))
   941  			},
   942  			wantOut: "\nX trunk CI · 123\nTriggered via push about 59 minutes ago\n\nX This run likely failed because of a workflow file issue.\n\nFor more information, see: https://github.com/runs/123\n",
   943  		},
   944  	}
   945  
   946  	for _, tt := range tests {
   947  		reg := &httpmock.Registry{}
   948  		tt.httpStubs(reg)
   949  		tt.opts.HttpClient = func() (*http.Client, error) {
   950  			return &http.Client{Transport: reg}, nil
   951  		}
   952  
   953  		tt.opts.Now = func() time.Time {
   954  			notnow, _ := time.Parse("2006-01-02 15:04:05", "2021-02-23 05:50:00")
   955  			return notnow
   956  		}
   957  
   958  		ios, _, stdout, _ := iostreams.Test()
   959  		ios.SetStdoutTTY(tt.tty)
   960  		tt.opts.IO = ios
   961  		tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
   962  			return ghrepo.FromFullName("OWNER/REPO")
   963  		}
   964  
   965  		//nolint:staticcheck // SA1019: prompt.InitAskStubber is deprecated: use NewAskStubber
   966  		as, teardown := prompt.InitAskStubber()
   967  		defer teardown()
   968  		if tt.askStubs != nil {
   969  			tt.askStubs(as)
   970  		}
   971  
   972  		browser := &browser.Stub{}
   973  		tt.opts.Browser = browser
   974  		rlc := testRunLogCache{}
   975  		tt.opts.RunLogCache = rlc
   976  
   977  		t.Run(tt.name, func(t *testing.T) {
   978  			err := runView(tt.opts)
   979  			if tt.wantErr {
   980  				assert.Error(t, err)
   981  				if tt.errMsg != "" {
   982  					assert.Equal(t, tt.errMsg, err.Error())
   983  				}
   984  				if !tt.opts.ExitStatus {
   985  					return
   986  				}
   987  			}
   988  			if !tt.opts.ExitStatus {
   989  				assert.NoError(t, err)
   990  			}
   991  			assert.Equal(t, tt.wantOut, stdout.String())
   992  			if tt.browsedURL != "" {
   993  				assert.Equal(t, tt.browsedURL, browser.BrowsedURL())
   994  			}
   995  			reg.Verify(t)
   996  		})
   997  	}
   998  }
   999  
  1000  // Structure of fixture zip file
  1001  //
  1002  //	run log/
  1003  //	├── cool job/
  1004  //	│   ├── 1_fob the barz.txt
  1005  //	│   └── 2_barz the fob.txt
  1006  //	└── sad job/
  1007  //	    ├── 1_barf the quux.txt
  1008  //	    └── 2_quux the barf.txt
  1009  func Test_attachRunLog(t *testing.T) {
  1010  	tests := []struct {
  1011  		name         string
  1012  		job          shared.Job
  1013  		wantMatch    bool
  1014  		wantFilename string
  1015  	}{
  1016  		{
  1017  			name: "matching job name and step number 1",
  1018  			job: shared.Job{
  1019  				Name: "cool job",
  1020  				Steps: []shared.Step{{
  1021  					Name:   "fob the barz",
  1022  					Number: 1,
  1023  				}},
  1024  			},
  1025  			wantMatch:    true,
  1026  			wantFilename: "cool job/1_fob the barz.txt",
  1027  		},
  1028  		{
  1029  			name: "matching job name and step number 2",
  1030  			job: shared.Job{
  1031  				Name: "cool job",
  1032  				Steps: []shared.Step{{
  1033  					Name:   "barz the fob",
  1034  					Number: 2,
  1035  				}},
  1036  			},
  1037  			wantMatch:    true,
  1038  			wantFilename: "cool job/2_barz the fob.txt",
  1039  		},
  1040  		{
  1041  			name: "matching job name and step number and mismatch step name",
  1042  			job: shared.Job{
  1043  				Name: "cool job",
  1044  				Steps: []shared.Step{{
  1045  					Name:   "mismatch",
  1046  					Number: 1,
  1047  				}},
  1048  			},
  1049  			wantMatch:    true,
  1050  			wantFilename: "cool job/1_fob the barz.txt",
  1051  		},
  1052  		{
  1053  			name: "matching job name and mismatch step number",
  1054  			job: shared.Job{
  1055  				Name: "cool job",
  1056  				Steps: []shared.Step{{
  1057  					Name:   "fob the barz",
  1058  					Number: 3,
  1059  				}},
  1060  			},
  1061  			wantMatch: false,
  1062  		},
  1063  		{
  1064  			name: "escape metacharacters in job name",
  1065  			job: shared.Job{
  1066  				Name: "metacharacters .+*?()|[]{}^$ job",
  1067  				Steps: []shared.Step{{
  1068  					Name:   "fob the barz",
  1069  					Number: 0,
  1070  				}},
  1071  			},
  1072  			wantMatch: false,
  1073  		},
  1074  		{
  1075  			name: "mismatching job name",
  1076  			job: shared.Job{
  1077  				Name: "mismatch",
  1078  				Steps: []shared.Step{{
  1079  					Name:   "fob the barz",
  1080  					Number: 1,
  1081  				}},
  1082  			},
  1083  			wantMatch: false,
  1084  		},
  1085  	}
  1086  	rlz, _ := zip.OpenReader("./fixtures/run_log.zip")
  1087  	defer rlz.Close()
  1088  	for _, tt := range tests {
  1089  		t.Run(tt.name, func(t *testing.T) {
  1090  			attachRunLog(rlz, []shared.Job{tt.job})
  1091  			for _, step := range tt.job.Steps {
  1092  				log := step.Log
  1093  				logPresent := log != nil
  1094  				assert.Equal(t, tt.wantMatch, logPresent)
  1095  				if logPresent {
  1096  					assert.Equal(t, tt.wantFilename, log.Name)
  1097  				}
  1098  			}
  1099  		})
  1100  	}
  1101  }
  1102  
  1103  type testRunLogCache struct{}
  1104  
  1105  func (testRunLogCache) Exists(path string) bool {
  1106  	return false
  1107  }
  1108  func (testRunLogCache) Create(path string, content io.ReadCloser) error {
  1109  	return nil
  1110  }
  1111  func (testRunLogCache) Open(path string) (*zip.ReadCloser, error) {
  1112  	return zip.OpenReader("./fixtures/run_log.zip")
  1113  }
  1114  
  1115  var barfTheFobLogOutput = heredoc.Doc(`
  1116  cool job	barz the fob	log line 1
  1117  cool job	barz the fob	log line 2
  1118  cool job	barz the fob	log line 3
  1119  `)
  1120  
  1121  var fobTheBarzLogOutput = heredoc.Doc(`
  1122  cool job	fob the barz	log line 1
  1123  cool job	fob the barz	log line 2
  1124  cool job	fob the barz	log line 3
  1125  `)
  1126  
  1127  var barfTheQuuxLogOutput = heredoc.Doc(`
  1128  sad job	barf the quux	log line 1
  1129  sad job	barf the quux	log line 2
  1130  sad job	barf the quux	log line 3
  1131  `)
  1132  
  1133  var quuxTheBarfLogOutput = heredoc.Doc(`
  1134  sad job	quux the barf	log line 1
  1135  sad job	quux the barf	log line 2
  1136  sad job	quux the barf	log line 3
  1137  `)
  1138  
  1139  var coolJobRunLogOutput = fmt.Sprintf("%s%s", fobTheBarzLogOutput, barfTheFobLogOutput)
  1140  var sadJobRunLogOutput = fmt.Sprintf("%s%s", barfTheQuuxLogOutput, quuxTheBarfLogOutput)
  1141  var expectedRunLogOutput = fmt.Sprintf("%s%s", coolJobRunLogOutput, sadJobRunLogOutput)