github.com/google/go-github/v69@v69.2.0/github/actions_artifacts_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 17 "github.com/google/go-cmp/cmp" 18 ) 19 20 func TestActionsService_ListArtifacts(t *testing.T) { 21 t.Parallel() 22 client, mux, _ := setup(t) 23 24 mux.HandleFunc("/repos/o/r/actions/artifacts", func(w http.ResponseWriter, r *http.Request) { 25 testMethod(t, r, "GET") 26 testFormValues(t, r, values{"page": "2", "name": "TheArtifact"}) 27 fmt.Fprint(w, 28 `{ 29 "total_count":1, 30 "artifacts":[{"id":1}] 31 }`, 32 ) 33 }) 34 35 opts := &ListArtifactsOptions{ 36 Name: Ptr("TheArtifact"), 37 ListOptions: ListOptions{Page: 2}, 38 } 39 ctx := context.Background() 40 artifacts, _, err := client.Actions.ListArtifacts(ctx, "o", "r", opts) 41 if err != nil { 42 t.Errorf("Actions.ListArtifacts returned error: %v", err) 43 } 44 45 want := &ArtifactList{TotalCount: Ptr(int64(1)), Artifacts: []*Artifact{{ID: Ptr(int64(1))}}} 46 if !cmp.Equal(artifacts, want) { 47 t.Errorf("Actions.ListArtifacts returned %+v, want %+v", artifacts, want) 48 } 49 50 const methodName = "ListArtifacts" 51 testBadOptions(t, methodName, func() (err error) { 52 _, _, err = client.Actions.ListArtifacts(ctx, "\n", "\n", opts) 53 return err 54 }) 55 56 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 57 got, resp, err := client.Actions.ListArtifacts(ctx, "o", "r", opts) 58 if got != nil { 59 t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) 60 } 61 return resp, err 62 }) 63 } 64 65 func TestActionsService_ListArtifacts_invalidOwner(t *testing.T) { 66 t.Parallel() 67 client, _, _ := setup(t) 68 69 ctx := context.Background() 70 _, _, err := client.Actions.ListArtifacts(ctx, "%", "r", nil) 71 testURLParseError(t, err) 72 } 73 74 func TestActionsService_ListArtifacts_invalidRepo(t *testing.T) { 75 t.Parallel() 76 client, _, _ := setup(t) 77 78 ctx := context.Background() 79 _, _, err := client.Actions.ListArtifacts(ctx, "o", "%", nil) 80 testURLParseError(t, err) 81 } 82 83 func TestActionsService_ListArtifacts_notFound(t *testing.T) { 84 t.Parallel() 85 client, mux, _ := setup(t) 86 87 mux.HandleFunc("/repos/o/r/actions/artifacts", func(w http.ResponseWriter, r *http.Request) { 88 testMethod(t, r, "GET") 89 w.WriteHeader(http.StatusNotFound) 90 }) 91 92 ctx := context.Background() 93 artifacts, resp, err := client.Actions.ListArtifacts(ctx, "o", "r", nil) 94 if err == nil { 95 t.Errorf("Expected HTTP 404 response") 96 } 97 if got, want := resp.Response.StatusCode, http.StatusNotFound; got != want { 98 t.Errorf("Actions.ListArtifacts return status %d, want %d", got, want) 99 } 100 if artifacts != nil { 101 t.Errorf("Actions.ListArtifacts return %+v, want nil", artifacts) 102 } 103 } 104 105 func TestActionsService_ListWorkflowRunArtifacts(t *testing.T) { 106 t.Parallel() 107 client, mux, _ := setup(t) 108 109 mux.HandleFunc("/repos/o/r/actions/runs/1/artifacts", func(w http.ResponseWriter, r *http.Request) { 110 testMethod(t, r, "GET") 111 testFormValues(t, r, values{"page": "2"}) 112 fmt.Fprint(w, 113 `{ 114 "total_count":1, 115 "artifacts":[{"id":1}] 116 }`, 117 ) 118 }) 119 120 opts := &ListOptions{Page: 2} 121 ctx := context.Background() 122 artifacts, _, err := client.Actions.ListWorkflowRunArtifacts(ctx, "o", "r", 1, opts) 123 if err != nil { 124 t.Errorf("Actions.ListWorkflowRunArtifacts returned error: %v", err) 125 } 126 127 want := &ArtifactList{TotalCount: Ptr(int64(1)), Artifacts: []*Artifact{{ID: Ptr(int64(1))}}} 128 if !cmp.Equal(artifacts, want) { 129 t.Errorf("Actions.ListWorkflowRunArtifacts returned %+v, want %+v", artifacts, want) 130 } 131 132 const methodName = "ListWorkflowRunArtifacts" 133 testBadOptions(t, methodName, func() (err error) { 134 _, _, err = client.Actions.ListWorkflowRunArtifacts(ctx, "\n", "\n", -1, opts) 135 return err 136 }) 137 138 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 139 got, resp, err := client.Actions.ListWorkflowRunArtifacts(ctx, "o", "r", 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_ListWorkflowRunArtifacts_invalidOwner(t *testing.T) { 148 t.Parallel() 149 client, _, _ := setup(t) 150 151 ctx := context.Background() 152 _, _, err := client.Actions.ListWorkflowRunArtifacts(ctx, "%", "r", 1, nil) 153 testURLParseError(t, err) 154 } 155 156 func TestActionsService_ListWorkflowRunArtifacts_invalidRepo(t *testing.T) { 157 t.Parallel() 158 client, _, _ := setup(t) 159 160 ctx := context.Background() 161 _, _, err := client.Actions.ListWorkflowRunArtifacts(ctx, "o", "%", 1, nil) 162 testURLParseError(t, err) 163 } 164 165 func TestActionsService_ListWorkflowRunArtifacts_notFound(t *testing.T) { 166 t.Parallel() 167 client, mux, _ := setup(t) 168 169 mux.HandleFunc("/repos/o/r/actions/runs/1/artifacts", func(w http.ResponseWriter, r *http.Request) { 170 testMethod(t, r, "GET") 171 w.WriteHeader(http.StatusNotFound) 172 }) 173 174 ctx := context.Background() 175 artifacts, resp, err := client.Actions.ListWorkflowRunArtifacts(ctx, "o", "r", 1, nil) 176 if err == nil { 177 t.Errorf("Expected HTTP 404 response") 178 } 179 if got, want := resp.Response.StatusCode, http.StatusNotFound; got != want { 180 t.Errorf("Actions.ListWorkflowRunArtifacts return status %d, want %d", got, want) 181 } 182 if artifacts != nil { 183 t.Errorf("Actions.ListWorkflowRunArtifacts return %+v, want nil", artifacts) 184 } 185 } 186 187 func TestActionsService_GetArtifact(t *testing.T) { 188 t.Parallel() 189 client, mux, _ := setup(t) 190 191 mux.HandleFunc("/repos/o/r/actions/artifacts/1", func(w http.ResponseWriter, r *http.Request) { 192 testMethod(t, r, "GET") 193 fmt.Fprint(w, `{ 194 "id":1, 195 "node_id":"xyz", 196 "name":"a", 197 "size_in_bytes":5, 198 "archive_download_url":"u" 199 }`) 200 }) 201 202 ctx := context.Background() 203 artifact, _, err := client.Actions.GetArtifact(ctx, "o", "r", 1) 204 if err != nil { 205 t.Errorf("Actions.GetArtifact returned error: %v", err) 206 } 207 208 want := &Artifact{ 209 ID: Ptr(int64(1)), 210 NodeID: Ptr("xyz"), 211 Name: Ptr("a"), 212 SizeInBytes: Ptr(int64(5)), 213 ArchiveDownloadURL: Ptr("u"), 214 } 215 if !cmp.Equal(artifact, want) { 216 t.Errorf("Actions.GetArtifact returned %+v, want %+v", artifact, want) 217 } 218 219 const methodName = "GetArtifact" 220 testBadOptions(t, methodName, func() (err error) { 221 _, _, err = client.Actions.GetArtifact(ctx, "\n", "\n", -1) 222 return err 223 }) 224 225 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 226 got, resp, err := client.Actions.GetArtifact(ctx, "o", "r", 1) 227 if got != nil { 228 t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) 229 } 230 return resp, err 231 }) 232 } 233 234 func TestActionsService_GetArtifact_invalidOwner(t *testing.T) { 235 t.Parallel() 236 client, _, _ := setup(t) 237 238 ctx := context.Background() 239 _, _, err := client.Actions.GetArtifact(ctx, "%", "r", 1) 240 testURLParseError(t, err) 241 } 242 243 func TestActionsService_GetArtifact_invalidRepo(t *testing.T) { 244 t.Parallel() 245 client, _, _ := setup(t) 246 247 ctx := context.Background() 248 _, _, err := client.Actions.GetArtifact(ctx, "o", "%", 1) 249 testURLParseError(t, err) 250 } 251 252 func TestActionsService_GetArtifact_notFound(t *testing.T) { 253 t.Parallel() 254 client, mux, _ := setup(t) 255 256 mux.HandleFunc("/repos/o/r/actions/artifacts/1", func(w http.ResponseWriter, r *http.Request) { 257 testMethod(t, r, "GET") 258 w.WriteHeader(http.StatusNotFound) 259 }) 260 261 ctx := context.Background() 262 artifact, resp, err := client.Actions.GetArtifact(ctx, "o", "r", 1) 263 if err == nil { 264 t.Errorf("Expected HTTP 404 response") 265 } 266 if got, want := resp.Response.StatusCode, http.StatusNotFound; got != want { 267 t.Errorf("Actions.GetArtifact return status %d, want %d", got, want) 268 } 269 if artifact != nil { 270 t.Errorf("Actions.GetArtifact return %+v, want nil", artifact) 271 } 272 } 273 274 func TestActionsService_DownloadArtifact(t *testing.T) { 275 t.Parallel() 276 277 tcs := []struct { 278 name string 279 respectRateLimits bool 280 }{ 281 { 282 name: "withoutRateLimits", 283 respectRateLimits: false, 284 }, 285 { 286 name: "withRateLimits", 287 respectRateLimits: true, 288 }, 289 } 290 291 for _, tc := range tcs { 292 tc := tc 293 t.Run(tc.name, func(t *testing.T) { 294 t.Parallel() 295 client, mux, _ := setup(t) 296 client.RateLimitRedirectionalEndpoints = tc.respectRateLimits 297 298 mux.HandleFunc("/repos/o/r/actions/artifacts/1/zip", func(w http.ResponseWriter, r *http.Request) { 299 testMethod(t, r, "GET") 300 http.Redirect(w, r, "https://github.com/artifact", http.StatusFound) 301 }) 302 303 ctx := context.Background() 304 url, resp, err := client.Actions.DownloadArtifact(ctx, "o", "r", 1, 1) 305 if err != nil { 306 t.Errorf("Actions.DownloadArtifact returned error: %v", err) 307 } 308 if resp.StatusCode != http.StatusFound { 309 t.Errorf("Actions.DownloadArtifact returned status: %d, want %d", resp.StatusCode, http.StatusFound) 310 } 311 312 want := "https://github.com/artifact" 313 if url.String() != want { 314 t.Errorf("Actions.DownloadArtifact returned %+v, want %+v", url.String(), want) 315 } 316 317 const methodName = "DownloadArtifact" 318 testBadOptions(t, methodName, func() (err error) { 319 _, _, err = client.Actions.DownloadArtifact(ctx, "\n", "\n", -1, 1) 320 return err 321 }) 322 323 // Add custom round tripper 324 client.client.Transport = roundTripperFunc(func(r *http.Request) (*http.Response, error) { 325 return nil, errors.New("failed to download artifact") 326 }) 327 // propagate custom round tripper to client without CheckRedirect 328 client.initialize() 329 testBadOptions(t, methodName, func() (err error) { 330 _, _, err = client.Actions.DownloadArtifact(ctx, "o", "r", 1, 1) 331 return err 332 }) 333 }) 334 } 335 } 336 337 func TestActionsService_DownloadArtifact_invalidOwner(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, _, _ := setup(t) 358 client.RateLimitRedirectionalEndpoints = tc.respectRateLimits 359 360 ctx := context.Background() 361 _, _, err := client.Actions.DownloadArtifact(ctx, "%", "r", 1, 1) 362 testURLParseError(t, err) 363 }) 364 } 365 } 366 367 func TestActionsService_DownloadArtifact_invalidRepo(t *testing.T) { 368 t.Parallel() 369 tcs := []struct { 370 name string 371 respectRateLimits bool 372 }{ 373 { 374 name: "withoutRateLimits", 375 respectRateLimits: false, 376 }, 377 { 378 name: "withRateLimits", 379 respectRateLimits: true, 380 }, 381 } 382 383 for _, tc := range tcs { 384 tc := tc 385 t.Run(tc.name, func(t *testing.T) { 386 t.Parallel() 387 client, _, _ := setup(t) 388 client.RateLimitRedirectionalEndpoints = tc.respectRateLimits 389 390 ctx := context.Background() 391 _, _, err := client.Actions.DownloadArtifact(ctx, "o", "%", 1, 1) 392 testURLParseError(t, err) 393 }) 394 } 395 } 396 397 func TestActionsService_DownloadArtifact_StatusMovedPermanently_dontFollowRedirects(t *testing.T) { 398 t.Parallel() 399 tcs := []struct { 400 name string 401 respectRateLimits bool 402 }{ 403 { 404 name: "withoutRateLimits", 405 respectRateLimits: false, 406 }, 407 { 408 name: "withRateLimits", 409 respectRateLimits: true, 410 }, 411 } 412 413 for _, tc := range tcs { 414 tc := tc 415 t.Run(tc.name, func(t *testing.T) { 416 t.Parallel() 417 client, mux, _ := setup(t) 418 client.RateLimitRedirectionalEndpoints = tc.respectRateLimits 419 420 mux.HandleFunc("/repos/o/r/actions/artifacts/1/zip", func(w http.ResponseWriter, r *http.Request) { 421 testMethod(t, r, "GET") 422 http.Redirect(w, r, "https://github.com/artifact", http.StatusMovedPermanently) 423 }) 424 425 ctx := context.Background() 426 _, resp, _ := client.Actions.DownloadArtifact(ctx, "o", "r", 1, 0) 427 if resp.StatusCode != http.StatusMovedPermanently { 428 t.Errorf("Actions.DownloadArtifact return status %d, want %d", resp.StatusCode, http.StatusMovedPermanently) 429 } 430 }) 431 } 432 } 433 434 func TestActionsService_DownloadArtifact_StatusMovedPermanently_followRedirects(t *testing.T) { 435 t.Parallel() 436 tcs := []struct { 437 name string 438 respectRateLimits bool 439 }{ 440 { 441 name: "withoutRateLimits", 442 respectRateLimits: false, 443 }, 444 { 445 name: "withRateLimits", 446 respectRateLimits: true, 447 }, 448 } 449 450 for _, tc := range tcs { 451 tc := tc 452 t.Run(tc.name, func(t *testing.T) { 453 t.Parallel() 454 client, mux, serverURL := setup(t) 455 client.RateLimitRedirectionalEndpoints = tc.respectRateLimits 456 457 mux.HandleFunc("/repos/o/r/actions/artifacts/1/zip", func(w http.ResponseWriter, r *http.Request) { 458 testMethod(t, r, "GET") 459 redirectURL, _ := url.Parse(serverURL + baseURLPath + "/redirect") 460 http.Redirect(w, r, redirectURL.String(), http.StatusMovedPermanently) 461 }) 462 mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) { 463 testMethod(t, r, "GET") 464 http.Redirect(w, r, "http://github.com/artifact", http.StatusFound) 465 }) 466 467 ctx := context.Background() 468 url, resp, err := client.Actions.DownloadArtifact(ctx, "o", "r", 1, 1) 469 if err != nil { 470 t.Errorf("Actions.DownloadArtifact return error: %v", err) 471 } 472 if resp.StatusCode != http.StatusFound { 473 t.Errorf("Actions.DownloadArtifact return status %d, want %d", resp.StatusCode, http.StatusFound) 474 } 475 want := "http://github.com/artifact" 476 if url.String() != want { 477 t.Errorf("Actions.DownloadArtifact returned %+v, want %+v", url.String(), want) 478 } 479 }) 480 } 481 } 482 483 func TestActionsService_DownloadArtifact_unexpectedCode(t *testing.T) { 484 t.Parallel() 485 tcs := []struct { 486 name string 487 respectRateLimits bool 488 }{ 489 { 490 name: "withoutRateLimits", 491 respectRateLimits: false, 492 }, 493 { 494 name: "withRateLimits", 495 respectRateLimits: true, 496 }, 497 } 498 499 for _, tc := range tcs { 500 tc := tc 501 t.Run(tc.name, func(t *testing.T) { 502 t.Parallel() 503 client, mux, serverURL := setup(t) 504 client.RateLimitRedirectionalEndpoints = tc.respectRateLimits 505 506 mux.HandleFunc("/repos/o/r/actions/artifacts/1/zip", func(w http.ResponseWriter, r *http.Request) { 507 testMethod(t, r, "GET") 508 redirectURL, _ := url.Parse(serverURL + baseURLPath + "/redirect") 509 http.Redirect(w, r, redirectURL.String(), http.StatusMovedPermanently) 510 }) 511 mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) { 512 testMethod(t, r, "GET") 513 w.WriteHeader(http.StatusNoContent) 514 }) 515 516 ctx := context.Background() 517 url, resp, err := client.Actions.DownloadArtifact(ctx, "o", "r", 1, 1) 518 if err == nil { 519 t.Fatalf("Actions.DownloadArtifact should return error on unexpected code") 520 } 521 if !strings.Contains(err.Error(), "unexpected status code") { 522 t.Error("Actions.DownloadArtifact should return unexpected status code") 523 } 524 if got, want := resp.Response.StatusCode, http.StatusNoContent; got != want { 525 t.Errorf("Actions.DownloadArtifact return status %d, want %d", got, want) 526 } 527 if url != nil { 528 t.Errorf("Actions.DownloadArtifact return %+v, want nil", url) 529 } 530 }) 531 } 532 } 533 534 func TestActionsService_DeleteArtifact(t *testing.T) { 535 t.Parallel() 536 client, mux, _ := setup(t) 537 538 mux.HandleFunc("/repos/o/r/actions/artifacts/1", func(w http.ResponseWriter, r *http.Request) { 539 testMethod(t, r, "DELETE") 540 }) 541 542 ctx := context.Background() 543 _, err := client.Actions.DeleteArtifact(ctx, "o", "r", 1) 544 if err != nil { 545 t.Errorf("Actions.DeleteArtifact return error: %v", err) 546 } 547 548 const methodName = "DeleteArtifact" 549 testBadOptions(t, methodName, func() (err error) { 550 _, err = client.Actions.DeleteArtifact(ctx, "\n", "\n", -1) 551 return err 552 }) 553 554 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 555 return client.Actions.DeleteArtifact(ctx, "o", "r", 1) 556 }) 557 } 558 559 func TestActionsService_DeleteArtifact_invalidOwner(t *testing.T) { 560 t.Parallel() 561 client, _, _ := setup(t) 562 563 ctx := context.Background() 564 _, err := client.Actions.DeleteArtifact(ctx, "%", "r", 1) 565 testURLParseError(t, err) 566 } 567 568 func TestActionsService_DeleteArtifact_invalidRepo(t *testing.T) { 569 t.Parallel() 570 client, _, _ := setup(t) 571 572 ctx := context.Background() 573 _, err := client.Actions.DeleteArtifact(ctx, "o", "%", 1) 574 testURLParseError(t, err) 575 } 576 577 func TestActionsService_DeleteArtifact_notFound(t *testing.T) { 578 t.Parallel() 579 client, mux, _ := setup(t) 580 581 mux.HandleFunc("/repos/o/r/actions/artifacts/1", func(w http.ResponseWriter, r *http.Request) { 582 testMethod(t, r, "DELETE") 583 w.WriteHeader(http.StatusNotFound) 584 }) 585 586 ctx := context.Background() 587 resp, err := client.Actions.DeleteArtifact(ctx, "o", "r", 1) 588 if err == nil { 589 t.Errorf("Expected HTTP 404 response") 590 } 591 if got, want := resp.Response.StatusCode, http.StatusNotFound; got != want { 592 t.Errorf("Actions.DeleteArtifact return status %d, want %d", got, want) 593 } 594 } 595 596 func TestArtifact_Marshal(t *testing.T) { 597 t.Parallel() 598 testJSONMarshal(t, &Artifact{}, "{}") 599 600 u := &Artifact{ 601 ID: Ptr(int64(1)), 602 NodeID: Ptr("nid"), 603 Name: Ptr("n"), 604 SizeInBytes: Ptr(int64(1)), 605 URL: Ptr("u"), 606 ArchiveDownloadURL: Ptr("a"), 607 Expired: Ptr(false), 608 CreatedAt: &Timestamp{referenceTime}, 609 UpdatedAt: &Timestamp{referenceTime}, 610 ExpiresAt: &Timestamp{referenceTime}, 611 WorkflowRun: &ArtifactWorkflowRun{ 612 ID: Ptr(int64(1)), 613 RepositoryID: Ptr(int64(1)), 614 HeadRepositoryID: Ptr(int64(1)), 615 HeadBranch: Ptr("b"), 616 HeadSHA: Ptr("s"), 617 }, 618 } 619 620 want := `{ 621 "id": 1, 622 "node_id": "nid", 623 "name": "n", 624 "size_in_bytes": 1, 625 "url": "u", 626 "archive_download_url": "a", 627 "expired": false, 628 "created_at": ` + referenceTimeStr + `, 629 "updated_at": ` + referenceTimeStr + `, 630 "expires_at": ` + referenceTimeStr + `, 631 "workflow_run": { 632 "id": 1, 633 "repository_id": 1, 634 "head_repository_id": 1, 635 "head_branch": "b", 636 "head_sha": "s" 637 } 638 }` 639 640 testJSONMarshal(t, u, want) 641 } 642 643 func TestArtifactList_Marshal(t *testing.T) { 644 t.Parallel() 645 testJSONMarshal(t, &ArtifactList{}, "{}") 646 647 u := &ArtifactList{ 648 TotalCount: Ptr(int64(1)), 649 Artifacts: []*Artifact{ 650 { 651 ID: Ptr(int64(1)), 652 NodeID: Ptr("nid"), 653 Name: Ptr("n"), 654 SizeInBytes: Ptr(int64(1)), 655 URL: Ptr("u"), 656 ArchiveDownloadURL: Ptr("a"), 657 Expired: Ptr(false), 658 CreatedAt: &Timestamp{referenceTime}, 659 UpdatedAt: &Timestamp{referenceTime}, 660 ExpiresAt: &Timestamp{referenceTime}, 661 WorkflowRun: &ArtifactWorkflowRun{ 662 ID: Ptr(int64(1)), 663 RepositoryID: Ptr(int64(1)), 664 HeadRepositoryID: Ptr(int64(1)), 665 HeadBranch: Ptr("b"), 666 HeadSHA: Ptr("s"), 667 }, 668 }, 669 }, 670 } 671 672 want := `{ 673 "total_count": 1, 674 "artifacts": [{ 675 "id": 1, 676 "node_id": "nid", 677 "name": "n", 678 "size_in_bytes": 1, 679 "url": "u", 680 "archive_download_url": "a", 681 "expired": false, 682 "created_at": ` + referenceTimeStr + `, 683 "updated_at": ` + referenceTimeStr + `, 684 "expires_at": ` + referenceTimeStr + `, 685 "workflow_run": { 686 "id": 1, 687 "repository_id": 1, 688 "head_repository_id": 1, 689 "head_branch": "b", 690 "head_sha": "s" 691 } 692 }] 693 }` 694 695 testJSONMarshal(t, u, want) 696 }