github.com/google/go-github/v70@v70.0.0/github/actions_workflow_jobs_test.go (about)

     1  // Copyright 2020 The go-github AUTHORS. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package github
     7  
     8  import (
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"net/http"
    13  	"net/url"
    14  	"strings"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/google/go-cmp/cmp"
    19  )
    20  
    21  func TestActionsService_ListWorkflowJobs(t *testing.T) {
    22  	t.Parallel()
    23  	client, mux, _ := setup(t)
    24  
    25  	mux.HandleFunc("/repos/o/r/actions/runs/29679449/jobs", func(w http.ResponseWriter, r *http.Request) {
    26  		testMethod(t, r, "GET")
    27  		testFormValues(t, r, values{"per_page": "2", "page": "2"})
    28  		fmt.Fprint(w, `{"total_count":4,"jobs":[{"id":399444496,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"},{"id":399444497,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"}]}`)
    29  	})
    30  
    31  	opts := &ListWorkflowJobsOptions{ListOptions: ListOptions{Page: 2, PerPage: 2}}
    32  	ctx := context.Background()
    33  	jobs, _, err := client.Actions.ListWorkflowJobs(ctx, "o", "r", 29679449, opts)
    34  	if err != nil {
    35  		t.Errorf("Actions.ListWorkflowJobs returned error: %v", err)
    36  	}
    37  
    38  	want := &Jobs{
    39  		TotalCount: Ptr(4),
    40  		Jobs: []*WorkflowJob{
    41  			{ID: Ptr(int64(399444496)), RunID: Ptr(int64(29679449)), StartedAt: &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}},
    42  			{ID: Ptr(int64(399444497)), RunID: Ptr(int64(29679449)), StartedAt: &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}},
    43  		},
    44  	}
    45  	if !cmp.Equal(jobs, want) {
    46  		t.Errorf("Actions.ListWorkflowJobs returned %+v, want %+v", jobs, want)
    47  	}
    48  
    49  	const methodName = "ListWorkflowJobs"
    50  	testBadOptions(t, methodName, func() (err error) {
    51  		_, _, err = client.Actions.ListWorkflowJobs(ctx, "\n", "\n", 29679449, opts)
    52  		return err
    53  	})
    54  
    55  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
    56  		got, resp, err := client.Actions.ListWorkflowJobs(ctx, "o", "r", 29679449, opts)
    57  		if got != nil {
    58  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
    59  		}
    60  		return resp, err
    61  	})
    62  }
    63  
    64  func TestActionsService_ListWorkflowJobs_Filter(t *testing.T) {
    65  	t.Parallel()
    66  	client, mux, _ := setup(t)
    67  
    68  	mux.HandleFunc("/repos/o/r/actions/runs/29679449/jobs", func(w http.ResponseWriter, r *http.Request) {
    69  		testMethod(t, r, "GET")
    70  		testFormValues(t, r, values{"filter": "all", "per_page": "2", "page": "2"})
    71  		fmt.Fprint(w, `{"total_count":4,"jobs":[{"id":399444496,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"},{"id":399444497,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"}]}`)
    72  	})
    73  
    74  	opts := &ListWorkflowJobsOptions{Filter: "all", ListOptions: ListOptions{Page: 2, PerPage: 2}}
    75  	ctx := context.Background()
    76  	jobs, _, err := client.Actions.ListWorkflowJobs(ctx, "o", "r", 29679449, opts)
    77  	if err != nil {
    78  		t.Errorf("Actions.ListWorkflowJobs returned error: %v", err)
    79  	}
    80  
    81  	want := &Jobs{
    82  		TotalCount: Ptr(4),
    83  		Jobs: []*WorkflowJob{
    84  			{ID: Ptr(int64(399444496)), RunID: Ptr(int64(29679449)), StartedAt: &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}},
    85  			{ID: Ptr(int64(399444497)), RunID: Ptr(int64(29679449)), StartedAt: &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}, CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)}},
    86  		},
    87  	}
    88  	if !cmp.Equal(jobs, want) {
    89  		t.Errorf("Actions.ListWorkflowJobs returned %+v, want %+v", jobs, want)
    90  	}
    91  }
    92  
    93  func TestActionsService_ListWorkflowJobsAttempt(t *testing.T) {
    94  	t.Parallel()
    95  	client, mux, _ := setup(t)
    96  
    97  	mux.HandleFunc("/repos/o/r/actions/runs/29679449/attempts/1/jobs", func(w http.ResponseWriter, r *http.Request) {
    98  		testMethod(t, r, "GET")
    99  		testFormValues(t, r, values{"per_page": "2", "page": "2"})
   100  		fmt.Fprint(w, `{"total_count":4,"jobs":[{"id":399444496,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z","run_attempt":2},{"id":399444497,"run_id":29679449,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z","run_attempt":2}]}`)
   101  	})
   102  	opts := &ListOptions{Page: 2, PerPage: 2}
   103  	ctx := context.Background()
   104  	jobs, _, err := client.Actions.ListWorkflowJobsAttempt(ctx, "o", "r", 29679449, 1, opts)
   105  	if err != nil {
   106  		t.Errorf("Actions.ListWorkflowJobsAttempt returned error: %v", err)
   107  	}
   108  
   109  	want := &Jobs{
   110  		TotalCount: Ptr(4),
   111  		Jobs: []*WorkflowJob{
   112  			{
   113  				ID:          Ptr(int64(399444496)),
   114  				RunID:       Ptr(int64(29679449)),
   115  				StartedAt:   &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)},
   116  				CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)},
   117  				RunAttempt:  Ptr(int64(2)),
   118  			},
   119  			{
   120  				ID:          Ptr(int64(399444497)),
   121  				RunID:       Ptr(int64(29679449)),
   122  				StartedAt:   &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)},
   123  				CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)},
   124  				RunAttempt:  Ptr(int64(2)),
   125  			},
   126  		},
   127  	}
   128  	if !cmp.Equal(jobs, want) {
   129  		t.Errorf("Actions.ListWorkflowJobsAttempt returned %+v, want %+v", jobs, want)
   130  	}
   131  
   132  	const methodName = "ListWorkflowJobsAttempt"
   133  	testBadOptions(t, methodName, func() (err error) {
   134  		_, _, err = client.Actions.ListWorkflowJobsAttempt(ctx, "\n", "\n", 29679449, 1, opts)
   135  		return err
   136  	})
   137  
   138  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   139  		got, resp, err := client.Actions.ListWorkflowJobsAttempt(ctx, "o", "r", 29679449, 1, opts)
   140  		if got != nil {
   141  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   142  		}
   143  		return resp, err
   144  	})
   145  }
   146  
   147  func TestActionsService_GetWorkflowJobByID(t *testing.T) {
   148  	t.Parallel()
   149  	client, mux, _ := setup(t)
   150  
   151  	mux.HandleFunc("/repos/o/r/actions/jobs/399444496", func(w http.ResponseWriter, r *http.Request) {
   152  		testMethod(t, r, "GET")
   153  		fmt.Fprint(w, `{"id":399444496,"started_at":"2019-01-02T15:04:05Z","completed_at":"2020-01-02T15:04:05Z"}`)
   154  	})
   155  
   156  	ctx := context.Background()
   157  	job, _, err := client.Actions.GetWorkflowJobByID(ctx, "o", "r", 399444496)
   158  	if err != nil {
   159  		t.Errorf("Actions.GetWorkflowJobByID returned error: %v", err)
   160  	}
   161  
   162  	want := &WorkflowJob{
   163  		ID:          Ptr(int64(399444496)),
   164  		StartedAt:   &Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)},
   165  		CompletedAt: &Timestamp{time.Date(2020, time.January, 02, 15, 04, 05, 0, time.UTC)},
   166  	}
   167  	if !cmp.Equal(job, want) {
   168  		t.Errorf("Actions.GetWorkflowJobByID returned %+v, want %+v", job, want)
   169  	}
   170  
   171  	const methodName = "GetWorkflowJobByID"
   172  	testBadOptions(t, methodName, func() (err error) {
   173  		_, _, err = client.Actions.GetWorkflowJobByID(ctx, "\n", "\n", 399444496)
   174  		return err
   175  	})
   176  
   177  	testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
   178  		got, resp, err := client.Actions.GetWorkflowJobByID(ctx, "o", "r", 399444496)
   179  		if got != nil {
   180  			t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
   181  		}
   182  		return resp, err
   183  	})
   184  }
   185  
   186  func TestActionsService_GetWorkflowJobLogs(t *testing.T) {
   187  	t.Parallel()
   188  	tcs := []struct {
   189  		name              string
   190  		respectRateLimits bool
   191  	}{
   192  		{
   193  			name:              "withoutRateLimits",
   194  			respectRateLimits: false,
   195  		},
   196  		{
   197  			name:              "withRateLimits",
   198  			respectRateLimits: true,
   199  		},
   200  	}
   201  
   202  	for _, tc := range tcs {
   203  		tc := tc
   204  		t.Run(tc.name, func(t *testing.T) {
   205  			t.Parallel()
   206  			client, mux, _ := setup(t)
   207  			client.RateLimitRedirectionalEndpoints = tc.respectRateLimits
   208  
   209  			mux.HandleFunc("/repos/o/r/actions/jobs/399444496/logs", func(w http.ResponseWriter, r *http.Request) {
   210  				testMethod(t, r, "GET")
   211  				http.Redirect(w, r, "http://github.com/a", http.StatusFound)
   212  			})
   213  
   214  			ctx := context.Background()
   215  			url, resp, err := client.Actions.GetWorkflowJobLogs(ctx, "o", "r", 399444496, 1)
   216  			if err != nil {
   217  				t.Errorf("Actions.GetWorkflowJobLogs returned error: %v", err)
   218  			}
   219  			if resp.StatusCode != http.StatusFound {
   220  				t.Errorf("Actions.GetWorkflowJobLogs returned status: %d, want %d", resp.StatusCode, http.StatusFound)
   221  			}
   222  			want := "http://github.com/a"
   223  			if url.String() != want {
   224  				t.Errorf("Actions.GetWorkflowJobLogs returned %+v, want %+v", url.String(), want)
   225  			}
   226  
   227  			const methodName = "GetWorkflowJobLogs"
   228  			testBadOptions(t, methodName, func() (err error) {
   229  				_, _, err = client.Actions.GetWorkflowJobLogs(ctx, "\n", "\n", 399444496, 1)
   230  				return err
   231  			})
   232  
   233  			// Add custom round tripper
   234  			client.client.Transport = roundTripperFunc(func(r *http.Request) (*http.Response, error) {
   235  				return nil, errors.New("failed to get workflow logs")
   236  			})
   237  			// propagate custom round tripper to client without CheckRedirect
   238  			client.initialize()
   239  			testBadOptions(t, methodName, func() (err error) {
   240  				_, _, err = client.Actions.GetWorkflowJobLogs(ctx, "o", "r", 399444496, 1)
   241  				return err
   242  			})
   243  		})
   244  	}
   245  }
   246  
   247  func TestActionsService_GetWorkflowJobLogs_StatusMovedPermanently_dontFollowRedirects(t *testing.T) {
   248  	t.Parallel()
   249  	tcs := []struct {
   250  		name              string
   251  		respectRateLimits bool
   252  	}{
   253  		{
   254  			name:              "withoutRateLimits",
   255  			respectRateLimits: false,
   256  		},
   257  		{
   258  			name:              "withRateLimits",
   259  			respectRateLimits: true,
   260  		},
   261  	}
   262  
   263  	for _, tc := range tcs {
   264  		tc := tc
   265  		t.Run(tc.name, func(t *testing.T) {
   266  			t.Parallel()
   267  			client, mux, _ := setup(t)
   268  			client.RateLimitRedirectionalEndpoints = tc.respectRateLimits
   269  
   270  			mux.HandleFunc("/repos/o/r/actions/jobs/399444496/logs", func(w http.ResponseWriter, r *http.Request) {
   271  				testMethod(t, r, "GET")
   272  				http.Redirect(w, r, "http://github.com/a", http.StatusMovedPermanently)
   273  			})
   274  
   275  			ctx := context.Background()
   276  			_, resp, _ := client.Actions.GetWorkflowJobLogs(ctx, "o", "r", 399444496, 0)
   277  			if resp.StatusCode != http.StatusMovedPermanently {
   278  				t.Errorf("Actions.GetWorkflowJobLogs returned status: %d, want %d", resp.StatusCode, http.StatusMovedPermanently)
   279  			}
   280  		})
   281  	}
   282  }
   283  
   284  func TestActionsService_GetWorkflowJobLogs_StatusMovedPermanently_followRedirects(t *testing.T) {
   285  	t.Parallel()
   286  	tcs := []struct {
   287  		name              string
   288  		respectRateLimits bool
   289  	}{
   290  		{
   291  			name:              "withoutRateLimits",
   292  			respectRateLimits: false,
   293  		},
   294  		{
   295  			name:              "withRateLimits",
   296  			respectRateLimits: true,
   297  		},
   298  	}
   299  
   300  	for _, tc := range tcs {
   301  		tc := tc
   302  		t.Run(tc.name, func(t *testing.T) {
   303  			t.Parallel()
   304  			client, mux, serverURL := setup(t)
   305  			client.RateLimitRedirectionalEndpoints = tc.respectRateLimits
   306  
   307  			// Mock a redirect link, which leads to an archive link
   308  			mux.HandleFunc("/repos/o/r/actions/jobs/399444496/logs", func(w http.ResponseWriter, r *http.Request) {
   309  				testMethod(t, r, "GET")
   310  				redirectURL, _ := url.Parse(serverURL + baseURLPath + "/redirect")
   311  				http.Redirect(w, r, redirectURL.String(), http.StatusMovedPermanently)
   312  			})
   313  
   314  			mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
   315  				testMethod(t, r, "GET")
   316  				http.Redirect(w, r, "http://github.com/a", http.StatusFound)
   317  			})
   318  
   319  			ctx := context.Background()
   320  			url, resp, err := client.Actions.GetWorkflowJobLogs(ctx, "o", "r", 399444496, 1)
   321  			if err != nil {
   322  				t.Errorf("Actions.GetWorkflowJobLogs returned error: %v", err)
   323  			}
   324  
   325  			if resp.StatusCode != http.StatusFound {
   326  				t.Errorf("Actions.GetWorkflowJobLogs returned status: %d, want %d", resp.StatusCode, http.StatusFound)
   327  			}
   328  
   329  			want := "http://github.com/a"
   330  			if url.String() != want {
   331  				t.Errorf("Actions.GetWorkflowJobLogs returned %+v, want %+v", url.String(), want)
   332  			}
   333  		})
   334  	}
   335  }
   336  
   337  func TestActionsService_GetWorkflowJobLogs_unexpectedCode(t *testing.T) {
   338  	t.Parallel()
   339  	tcs := []struct {
   340  		name              string
   341  		respectRateLimits bool
   342  	}{
   343  		{
   344  			name:              "withoutRateLimits",
   345  			respectRateLimits: false,
   346  		},
   347  		{
   348  			name:              "withRateLimits",
   349  			respectRateLimits: true,
   350  		},
   351  	}
   352  
   353  	for _, tc := range tcs {
   354  		tc := tc
   355  		t.Run(tc.name, func(t *testing.T) {
   356  			t.Parallel()
   357  			client, mux, serverURL := setup(t)
   358  			client.RateLimitRedirectionalEndpoints = tc.respectRateLimits
   359  
   360  			// Mock a redirect link, which leads to an archive link
   361  			mux.HandleFunc("/repos/o/r/actions/jobs/399444496/logs", func(w http.ResponseWriter, r *http.Request) {
   362  				testMethod(t, r, "GET")
   363  				redirectURL, _ := url.Parse(serverURL + baseURLPath + "/redirect")
   364  				http.Redirect(w, r, redirectURL.String(), http.StatusMovedPermanently)
   365  			})
   366  
   367  			mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
   368  				testMethod(t, r, "GET")
   369  				w.WriteHeader(http.StatusNoContent)
   370  			})
   371  
   372  			ctx := context.Background()
   373  			url, resp, err := client.Actions.GetWorkflowJobLogs(ctx, "o", "r", 399444496, 1)
   374  			if err == nil {
   375  				t.Fatalf("Actions.GetWorkflowJobLogs should return error on unexpected code")
   376  			}
   377  			if !strings.Contains(err.Error(), "unexpected status code") {
   378  				t.Error("Actions.GetWorkflowJobLogs should return unexpected status code")
   379  			}
   380  			if got, want := resp.Response.StatusCode, http.StatusNoContent; got != want {
   381  				t.Errorf("Actions.GetWorkflowJobLogs return status %d, want %d", got, want)
   382  			}
   383  			if url != nil {
   384  				t.Errorf("Actions.GetWorkflowJobLogs return %+v, want nil", url)
   385  			}
   386  		})
   387  	}
   388  }
   389  
   390  func TestTaskStep_Marshal(t *testing.T) {
   391  	t.Parallel()
   392  	testJSONMarshal(t, &TaskStep{}, "{}")
   393  
   394  	u := &TaskStep{
   395  		Name:        Ptr("n"),
   396  		Status:      Ptr("s"),
   397  		Conclusion:  Ptr("c"),
   398  		Number:      Ptr(int64(1)),
   399  		StartedAt:   &Timestamp{referenceTime},
   400  		CompletedAt: &Timestamp{referenceTime},
   401  	}
   402  
   403  	want := `{
   404  		"name": "n",
   405  		"status": "s",
   406  		"conclusion": "c",
   407  		"number": 1,
   408  		"started_at": ` + referenceTimeStr + `,
   409  		"completed_at": ` + referenceTimeStr + `
   410  	}`
   411  
   412  	testJSONMarshal(t, u, want)
   413  }
   414  
   415  func TestWorkflowJob_Marshal(t *testing.T) {
   416  	t.Parallel()
   417  	testJSONMarshal(t, &WorkflowJob{}, "{}")
   418  
   419  	u := &WorkflowJob{
   420  		ID:          Ptr(int64(1)),
   421  		RunID:       Ptr(int64(1)),
   422  		RunURL:      Ptr("r"),
   423  		NodeID:      Ptr("n"),
   424  		HeadBranch:  Ptr("b"),
   425  		HeadSHA:     Ptr("h"),
   426  		URL:         Ptr("u"),
   427  		HTMLURL:     Ptr("h"),
   428  		Status:      Ptr("s"),
   429  		Conclusion:  Ptr("c"),
   430  		CreatedAt:   &Timestamp{referenceTime},
   431  		StartedAt:   &Timestamp{referenceTime},
   432  		CompletedAt: &Timestamp{referenceTime},
   433  		Name:        Ptr("n"),
   434  		Steps: []*TaskStep{
   435  			{
   436  				Name:        Ptr("n"),
   437  				Status:      Ptr("s"),
   438  				Conclusion:  Ptr("c"),
   439  				Number:      Ptr(int64(1)),
   440  				StartedAt:   &Timestamp{referenceTime},
   441  				CompletedAt: &Timestamp{referenceTime},
   442  			},
   443  		},
   444  		CheckRunURL:  Ptr("c"),
   445  		WorkflowName: Ptr("w"),
   446  	}
   447  
   448  	want := `{
   449  		"id": 1,
   450  		"run_id": 1,
   451  		"run_url": "r",
   452  		"node_id": "n",
   453  		"head_branch": "b",
   454  		"head_sha": "h",
   455  		"url": "u",
   456  		"html_url": "h",
   457  		"status": "s",
   458  		"conclusion": "c",
   459  		"created_at": ` + referenceTimeStr + `,
   460  		"started_at": ` + referenceTimeStr + `,
   461  		"completed_at": ` + referenceTimeStr + `,
   462  		"name": "n",
   463  		"steps": [{
   464  			"name": "n",
   465  			"status": "s",
   466  			"conclusion": "c",
   467  			"number": 1,
   468  			"started_at": ` + referenceTimeStr + `,
   469  			"completed_at": ` + referenceTimeStr + `
   470  		}],
   471  		"check_run_url": "c",
   472  		"workflow_name": "w"
   473  	}`
   474  
   475  	testJSONMarshal(t, u, want)
   476  }
   477  
   478  func TestJobs_Marshal(t *testing.T) {
   479  	t.Parallel()
   480  	testJSONMarshal(t, &Jobs{}, "{}")
   481  
   482  	u := &Jobs{
   483  		TotalCount: Ptr(1),
   484  		Jobs: []*WorkflowJob{
   485  			{
   486  				ID:          Ptr(int64(1)),
   487  				RunID:       Ptr(int64(1)),
   488  				RunURL:      Ptr("r"),
   489  				NodeID:      Ptr("n"),
   490  				HeadBranch:  Ptr("b"),
   491  				HeadSHA:     Ptr("h"),
   492  				URL:         Ptr("u"),
   493  				HTMLURL:     Ptr("h"),
   494  				Status:      Ptr("s"),
   495  				Conclusion:  Ptr("c"),
   496  				CreatedAt:   &Timestamp{referenceTime},
   497  				StartedAt:   &Timestamp{referenceTime},
   498  				CompletedAt: &Timestamp{referenceTime},
   499  				Name:        Ptr("n"),
   500  				Steps: []*TaskStep{
   501  					{
   502  						Name:        Ptr("n"),
   503  						Status:      Ptr("s"),
   504  						Conclusion:  Ptr("c"),
   505  						Number:      Ptr(int64(1)),
   506  						StartedAt:   &Timestamp{referenceTime},
   507  						CompletedAt: &Timestamp{referenceTime},
   508  					},
   509  				},
   510  				CheckRunURL:  Ptr("c"),
   511  				RunAttempt:   Ptr(int64(2)),
   512  				WorkflowName: Ptr("w"),
   513  			},
   514  		},
   515  	}
   516  
   517  	want := `{
   518  		"total_count": 1,
   519  		"jobs": [{
   520  			"id": 1,
   521  			"run_id": 1,
   522  			"run_url": "r",
   523  			"node_id": "n",
   524  			"head_branch": "b",
   525  			"head_sha": "h",
   526  			"url": "u",
   527  			"html_url": "h",
   528  			"status": "s",
   529  			"conclusion": "c",
   530  			"created_at": ` + referenceTimeStr + `,
   531  			"started_at": ` + referenceTimeStr + `,
   532  			"completed_at": ` + referenceTimeStr + `,
   533  			"name": "n",
   534  			"steps": [{
   535  				"name": "n",
   536  				"status": "s",
   537  				"conclusion": "c",
   538  				"number": 1,
   539  				"started_at": ` + referenceTimeStr + `,
   540  				"completed_at": ` + referenceTimeStr + `
   541  			}],
   542  			"check_run_url": "c",
   543  			"run_attempt": 2,
   544  			"workflow_name": "w"
   545  		}]
   546  	}`
   547  
   548  	testJSONMarshal(t, u, want)
   549  }