github.com/google/go-github/v42@v42.0.0/github/search_test.go (about) 1 // Copyright 2013 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 "fmt" 11 "net/http" 12 "testing" 13 14 "github.com/google/go-cmp/cmp" 15 ) 16 17 func TestSearchService_Repositories(t *testing.T) { 18 client, mux, _, teardown := setup() 19 defer teardown() 20 21 mux.HandleFunc("/search/repositories", func(w http.ResponseWriter, r *http.Request) { 22 testMethod(t, r, "GET") 23 testFormValues(t, r, values{ 24 "q": "blah", 25 "sort": "forks", 26 "order": "desc", 27 "page": "2", 28 "per_page": "2", 29 }) 30 31 fmt.Fprint(w, `{"total_count": 4, "incomplete_results": false, "items": [{"id":1},{"id":2}]}`) 32 }) 33 34 opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} 35 ctx := context.Background() 36 result, _, err := client.Search.Repositories(ctx, "blah", opts) 37 if err != nil { 38 t.Errorf("Search.Repositories returned error: %v", err) 39 } 40 41 want := &RepositoriesSearchResult{ 42 Total: Int(4), 43 IncompleteResults: Bool(false), 44 Repositories: []*Repository{{ID: Int64(1)}, {ID: Int64(2)}}, 45 } 46 if !cmp.Equal(result, want) { 47 t.Errorf("Search.Repositories returned %+v, want %+v", result, want) 48 } 49 } 50 51 func TestSearchService_Repositories_coverage(t *testing.T) { 52 client, _, _, teardown := setup() 53 defer teardown() 54 55 ctx := context.Background() 56 57 const methodName = "Repositories" 58 testBadOptions(t, methodName, func() (err error) { 59 _, _, err = client.Search.Repositories(ctx, "\n", nil) 60 return err 61 }) 62 } 63 64 func TestSearchService_Topics(t *testing.T) { 65 client, mux, _, teardown := setup() 66 defer teardown() 67 68 mux.HandleFunc("/search/topics", func(w http.ResponseWriter, r *http.Request) { 69 testMethod(t, r, "GET") 70 testFormValues(t, r, values{ 71 "q": "blah", 72 "page": "2", 73 "per_page": "2", 74 }) 75 76 fmt.Fprint(w, `{"total_count": 4, "incomplete_results": false, "items": [{"name":"blah"},{"name":"blahblah"}]}`) 77 }) 78 79 opts := &SearchOptions{ListOptions: ListOptions{Page: 2, PerPage: 2}} 80 ctx := context.Background() 81 result, _, err := client.Search.Topics(ctx, "blah", opts) 82 if err != nil { 83 t.Errorf("Search.Topics returned error: %v", err) 84 } 85 86 want := &TopicsSearchResult{ 87 Total: Int(4), 88 IncompleteResults: Bool(false), 89 Topics: []*TopicResult{{Name: String("blah")}, {Name: String("blahblah")}}, 90 } 91 if !cmp.Equal(result, want) { 92 t.Errorf("Search.Topics returned %+v, want %+v", result, want) 93 } 94 } 95 96 func TestSearchService_Topics_coverage(t *testing.T) { 97 client, _, _, teardown := setup() 98 defer teardown() 99 100 ctx := context.Background() 101 102 const methodName = "Topics" 103 testBadOptions(t, methodName, func() (err error) { 104 _, _, err = client.Search.Topics(ctx, "\n", nil) 105 return err 106 }) 107 } 108 109 func TestSearchService_Commits(t *testing.T) { 110 client, mux, _, teardown := setup() 111 defer teardown() 112 113 mux.HandleFunc("/search/commits", func(w http.ResponseWriter, r *http.Request) { 114 testMethod(t, r, "GET") 115 testFormValues(t, r, values{ 116 "q": "blah", 117 "sort": "author-date", 118 "order": "desc", 119 }) 120 121 fmt.Fprint(w, `{"total_count": 4, "incomplete_results": false, "items": [{"sha":"random_hash1"},{"sha":"random_hash2"}]}`) 122 }) 123 124 opts := &SearchOptions{Sort: "author-date", Order: "desc"} 125 ctx := context.Background() 126 result, _, err := client.Search.Commits(ctx, "blah", opts) 127 if err != nil { 128 t.Errorf("Search.Commits returned error: %v", err) 129 } 130 131 want := &CommitsSearchResult{ 132 Total: Int(4), 133 IncompleteResults: Bool(false), 134 Commits: []*CommitResult{{SHA: String("random_hash1")}, {SHA: String("random_hash2")}}, 135 } 136 if !cmp.Equal(result, want) { 137 t.Errorf("Search.Commits returned %+v, want %+v", result, want) 138 } 139 } 140 141 func TestSearchService_Commits_coverage(t *testing.T) { 142 client, _, _, teardown := setup() 143 defer teardown() 144 145 ctx := context.Background() 146 147 const methodName = "Commits" 148 testBadOptions(t, methodName, func() (err error) { 149 _, _, err = client.Search.Commits(ctx, "\n", nil) 150 return err 151 }) 152 } 153 154 func TestSearchService_Issues(t *testing.T) { 155 client, mux, _, teardown := setup() 156 defer teardown() 157 158 mux.HandleFunc("/search/issues", func(w http.ResponseWriter, r *http.Request) { 159 testMethod(t, r, "GET") 160 testFormValues(t, r, values{ 161 "q": "blah", 162 "sort": "forks", 163 "order": "desc", 164 "page": "2", 165 "per_page": "2", 166 }) 167 168 fmt.Fprint(w, `{"total_count": 4, "incomplete_results": true, "items": [{"number":1},{"number":2}]}`) 169 }) 170 171 opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} 172 ctx := context.Background() 173 result, _, err := client.Search.Issues(ctx, "blah", opts) 174 if err != nil { 175 t.Errorf("Search.Issues returned error: %v", err) 176 } 177 178 want := &IssuesSearchResult{ 179 Total: Int(4), 180 IncompleteResults: Bool(true), 181 Issues: []*Issue{{Number: Int(1)}, {Number: Int(2)}}, 182 } 183 if !cmp.Equal(result, want) { 184 t.Errorf("Search.Issues returned %+v, want %+v", result, want) 185 } 186 } 187 188 func TestSearchService_Issues_coverage(t *testing.T) { 189 client, _, _, teardown := setup() 190 defer teardown() 191 192 ctx := context.Background() 193 194 const methodName = "Issues" 195 testBadOptions(t, methodName, func() (err error) { 196 _, _, err = client.Search.Issues(ctx, "\n", nil) 197 return err 198 }) 199 } 200 201 func TestSearchService_Issues_withQualifiersNoOpts(t *testing.T) { 202 client, mux, _, teardown := setup() 203 defer teardown() 204 205 const q = "gopher is:issue label:bug language:c++ pushed:>=2018-01-01 stars:>=200" 206 207 var requestURI string 208 mux.HandleFunc("/search/issues", func(w http.ResponseWriter, r *http.Request) { 209 testMethod(t, r, "GET") 210 testFormValues(t, r, values{ 211 "q": q, 212 }) 213 requestURI = r.RequestURI 214 215 fmt.Fprint(w, `{"total_count": 4, "incomplete_results": true, "items": [{"number":1},{"number":2}]}`) 216 }) 217 218 opts := &SearchOptions{} 219 ctx := context.Background() 220 result, _, err := client.Search.Issues(ctx, q, opts) 221 if err != nil { 222 t.Errorf("Search.Issues returned error: %v", err) 223 } 224 225 if want := "/api-v3/search/issues?q=gopher+is%3Aissue+label%3Abug+language%3Ac%2B%2B+pushed%3A%3E%3D2018-01-01+stars%3A%3E%3D200"; requestURI != want { 226 t.Fatalf("URI encoding failed: got %v, want %v", requestURI, want) 227 } 228 229 want := &IssuesSearchResult{ 230 Total: Int(4), 231 IncompleteResults: Bool(true), 232 Issues: []*Issue{{Number: Int(1)}, {Number: Int(2)}}, 233 } 234 if !cmp.Equal(result, want) { 235 t.Errorf("Search.Issues returned %+v, want %+v", result, want) 236 } 237 } 238 239 func TestSearchService_Issues_withQualifiersAndOpts(t *testing.T) { 240 client, mux, _, teardown := setup() 241 defer teardown() 242 243 const q = "gopher is:issue label:bug language:c++ pushed:>=2018-01-01 stars:>=200" 244 245 var requestURI string 246 mux.HandleFunc("/search/issues", func(w http.ResponseWriter, r *http.Request) { 247 testMethod(t, r, "GET") 248 testFormValues(t, r, values{ 249 "q": q, 250 "sort": "forks", 251 }) 252 requestURI = r.RequestURI 253 254 fmt.Fprint(w, `{"total_count": 4, "incomplete_results": true, "items": [{"number":1},{"number":2}]}`) 255 }) 256 257 opts := &SearchOptions{Sort: "forks"} 258 ctx := context.Background() 259 result, _, err := client.Search.Issues(ctx, q, opts) 260 if err != nil { 261 t.Errorf("Search.Issues returned error: %v", err) 262 } 263 264 if want := "/api-v3/search/issues?q=gopher+is%3Aissue+label%3Abug+language%3Ac%2B%2B+pushed%3A%3E%3D2018-01-01+stars%3A%3E%3D200&sort=forks"; requestURI != want { 265 t.Fatalf("URI encoding failed: got %v, want %v", requestURI, want) 266 } 267 268 want := &IssuesSearchResult{ 269 Total: Int(4), 270 IncompleteResults: Bool(true), 271 Issues: []*Issue{{Number: Int(1)}, {Number: Int(2)}}, 272 } 273 if !cmp.Equal(result, want) { 274 t.Errorf("Search.Issues returned %+v, want %+v", result, want) 275 } 276 } 277 278 func TestSearchService_Users(t *testing.T) { 279 client, mux, _, teardown := setup() 280 defer teardown() 281 282 mux.HandleFunc("/search/users", func(w http.ResponseWriter, r *http.Request) { 283 testMethod(t, r, "GET") 284 testFormValues(t, r, values{ 285 "q": "blah", 286 "sort": "forks", 287 "order": "desc", 288 "page": "2", 289 "per_page": "2", 290 }) 291 292 fmt.Fprint(w, `{"total_count": 4, "incomplete_results": false, "items": [{"id":1},{"id":2}]}`) 293 }) 294 295 opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} 296 ctx := context.Background() 297 result, _, err := client.Search.Users(ctx, "blah", opts) 298 if err != nil { 299 t.Errorf("Search.Issues returned error: %v", err) 300 } 301 302 want := &UsersSearchResult{ 303 Total: Int(4), 304 IncompleteResults: Bool(false), 305 Users: []*User{{ID: Int64(1)}, {ID: Int64(2)}}, 306 } 307 if !cmp.Equal(result, want) { 308 t.Errorf("Search.Users returned %+v, want %+v", result, want) 309 } 310 } 311 312 func TestSearchService_Users_coverage(t *testing.T) { 313 client, _, _, teardown := setup() 314 defer teardown() 315 316 ctx := context.Background() 317 318 const methodName = "Users" 319 testBadOptions(t, methodName, func() (err error) { 320 _, _, err = client.Search.Users(ctx, "\n", nil) 321 return err 322 }) 323 } 324 325 func TestSearchService_Code(t *testing.T) { 326 client, mux, _, teardown := setup() 327 defer teardown() 328 329 mux.HandleFunc("/search/code", func(w http.ResponseWriter, r *http.Request) { 330 testMethod(t, r, "GET") 331 testFormValues(t, r, values{ 332 "q": "blah", 333 "sort": "forks", 334 "order": "desc", 335 "page": "2", 336 "per_page": "2", 337 }) 338 339 fmt.Fprint(w, `{"total_count": 4, "incomplete_results": false, "items": [{"name":"1"},{"name":"2"}]}`) 340 }) 341 342 opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} 343 ctx := context.Background() 344 result, _, err := client.Search.Code(ctx, "blah", opts) 345 if err != nil { 346 t.Errorf("Search.Code returned error: %v", err) 347 } 348 349 want := &CodeSearchResult{ 350 Total: Int(4), 351 IncompleteResults: Bool(false), 352 CodeResults: []*CodeResult{{Name: String("1")}, {Name: String("2")}}, 353 } 354 if !cmp.Equal(result, want) { 355 t.Errorf("Search.Code returned %+v, want %+v", result, want) 356 } 357 } 358 359 func TestSearchService_Code_coverage(t *testing.T) { 360 client, _, _, teardown := setup() 361 defer teardown() 362 363 ctx := context.Background() 364 365 const methodName = "Code" 366 testBadOptions(t, methodName, func() (err error) { 367 _, _, err = client.Search.Code(ctx, "\n", nil) 368 return err 369 }) 370 } 371 372 func TestSearchService_CodeTextMatch(t *testing.T) { 373 client, mux, _, teardown := setup() 374 defer teardown() 375 376 mux.HandleFunc("/search/code", func(w http.ResponseWriter, r *http.Request) { 377 testMethod(t, r, "GET") 378 379 textMatchResponse := ` 380 { 381 "total_count": 1, 382 "incomplete_results": false, 383 "items": [ 384 { 385 "name":"gopher1", 386 "text_matches": [ 387 { 388 "fragment": "I'm afraid my friend what you have found\nIs a gopher who lives to feed", 389 "matches": [ 390 { 391 "text": "gopher", 392 "indices": [ 393 14, 394 21 395 ] 396 } 397 ] 398 } 399 ] 400 } 401 ] 402 } 403 ` 404 405 fmt.Fprint(w, textMatchResponse) 406 }) 407 408 opts := &SearchOptions{Sort: "forks", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}, TextMatch: true} 409 ctx := context.Background() 410 result, _, err := client.Search.Code(ctx, "blah", opts) 411 if err != nil { 412 t.Errorf("Search.Code returned error: %v", err) 413 } 414 415 wantedCodeResult := &CodeResult{ 416 Name: String("gopher1"), 417 TextMatches: []*TextMatch{{ 418 Fragment: String("I'm afraid my friend what you have found\nIs a gopher who lives to feed"), 419 Matches: []*Match{{Text: String("gopher"), Indices: []int{14, 21}}}, 420 }, 421 }, 422 } 423 424 want := &CodeSearchResult{ 425 Total: Int(1), 426 IncompleteResults: Bool(false), 427 CodeResults: []*CodeResult{wantedCodeResult}, 428 } 429 if !cmp.Equal(result, want) { 430 t.Errorf("Search.Code returned %+v, want %+v", result, want) 431 } 432 } 433 434 func TestSearchService_Labels(t *testing.T) { 435 client, mux, _, teardown := setup() 436 defer teardown() 437 438 mux.HandleFunc("/search/labels", func(w http.ResponseWriter, r *http.Request) { 439 testMethod(t, r, "GET") 440 testFormValues(t, r, values{ 441 "repository_id": "1234", 442 "q": "blah", 443 "sort": "updated", 444 "order": "desc", 445 "page": "2", 446 "per_page": "2", 447 }) 448 449 fmt.Fprint(w, `{"total_count": 4, "incomplete_results": false, "items": [{"id": 1234, "name":"bug", "description": "some text"},{"id": 4567, "name":"feature"}]}`) 450 }) 451 452 opts := &SearchOptions{Sort: "updated", Order: "desc", ListOptions: ListOptions{Page: 2, PerPage: 2}} 453 ctx := context.Background() 454 result, _, err := client.Search.Labels(ctx, 1234, "blah", opts) 455 if err != nil { 456 t.Errorf("Search.Code returned error: %v", err) 457 } 458 459 want := &LabelsSearchResult{ 460 Total: Int(4), 461 IncompleteResults: Bool(false), 462 Labels: []*LabelResult{ 463 {ID: Int64(1234), Name: String("bug"), Description: String("some text")}, 464 {ID: Int64(4567), Name: String("feature")}, 465 }, 466 } 467 if !cmp.Equal(result, want) { 468 t.Errorf("Search.Labels returned %+v, want %+v", result, want) 469 } 470 } 471 472 func TestSearchService_Labels_coverage(t *testing.T) { 473 client, _, _, teardown := setup() 474 defer teardown() 475 476 ctx := context.Background() 477 478 const methodName = "Labels" 479 testBadOptions(t, methodName, func() (err error) { 480 _, _, err = client.Search.Labels(ctx, -1234, "\n", nil) 481 return err 482 }) 483 } 484 485 func TestMatch_Marshal(t *testing.T) { 486 testJSONMarshal(t, &Match{}, "{}") 487 488 u := &Match{ 489 Text: String("txt"), 490 Indices: []int{1}, 491 } 492 493 want := `{ 494 "text": "txt", 495 "indices": [1] 496 }` 497 498 testJSONMarshal(t, u, want) 499 } 500 501 func TestTextMatch_Marshal(t *testing.T) { 502 testJSONMarshal(t, &TextMatch{}, "{}") 503 504 u := &TextMatch{ 505 ObjectURL: String("ourl"), 506 ObjectType: String("otype"), 507 Property: String("prop"), 508 Fragment: String("fragment"), 509 Matches: []*Match{ 510 { 511 Text: String("txt"), 512 Indices: []int{1}, 513 }, 514 }, 515 } 516 517 want := `{ 518 "object_url": "ourl", 519 "object_type": "otype", 520 "property": "prop", 521 "fragment": "fragment", 522 "matches": [{ 523 "text": "txt", 524 "indices": [1] 525 }] 526 }` 527 528 testJSONMarshal(t, u, want) 529 } 530 531 func TestTopicResult_Marshal(t *testing.T) { 532 testJSONMarshal(t, &TopicResult{}, "{}") 533 534 u := &TopicResult{ 535 Name: String("name"), 536 DisplayName: String("displayName"), 537 ShortDescription: String("shortDescription"), 538 Description: String("description"), 539 CreatedBy: String("createdBy"), 540 UpdatedAt: String("2021-10-26"), 541 Featured: Bool(false), 542 Curated: Bool(true), 543 Score: Float64(99.9), 544 } 545 546 want := `{ 547 "name": "name", 548 "display_name": "displayName", 549 "short_description": "shortDescription", 550 "description": "description", 551 "created_by": "createdBy", 552 "updated_at": "2021-10-26", 553 "featured": false, 554 "curated": true, 555 "score": 99.9 556 }` 557 558 testJSONMarshal(t, u, want) 559 } 560 561 func TestRepositoriesSearchResult_Marshal(t *testing.T) { 562 testJSONMarshal(t, &RepositoriesSearchResult{}, "{}") 563 564 u := &RepositoriesSearchResult{ 565 Total: Int(0), 566 IncompleteResults: Bool(true), 567 Repositories: []*Repository{{ID: Int64(1)}}, 568 } 569 570 want := `{ 571 "total_count" : 0, 572 "incomplete_results" : true, 573 "items" : [{"id":1}] 574 }` 575 576 testJSONMarshal(t, u, want) 577 } 578 579 func TestCommitsSearchResult_Marshal(t *testing.T) { 580 testJSONMarshal(t, &CommitsSearchResult{}, "{}") 581 582 c := &CommitsSearchResult{ 583 Total: Int(0), 584 IncompleteResults: Bool(true), 585 Commits: []*CommitResult{{ 586 SHA: String("s"), 587 }}, 588 } 589 590 want := `{ 591 "total_count" : 0, 592 "incomplete_results" : true, 593 "items" : [{"sha" : "s"}] 594 }` 595 596 testJSONMarshal(t, c, want) 597 } 598 599 func TestTopicsSearchResult_Marshal(t *testing.T) { 600 testJSONMarshal(t, &TopicsSearchResult{}, "{}") 601 602 u := &TopicsSearchResult{ 603 Total: Int(2), 604 IncompleteResults: Bool(false), 605 Topics: []*TopicResult{ 606 { 607 Name: String("t1"), 608 DisplayName: String("tt"), 609 ShortDescription: String("t desc"), 610 Description: String("desc"), 611 CreatedBy: String("mi"), 612 CreatedAt: &Timestamp{referenceTime}, 613 UpdatedAt: String("2006-01-02T15:04:05Z"), 614 Featured: Bool(true), 615 Curated: Bool(true), 616 Score: Float64(123), 617 }, 618 }, 619 } 620 621 want := `{ 622 "total_count" : 2, 623 "incomplete_results" : false, 624 "items" : [ 625 { 626 "name" : "t1", 627 "display_name":"tt", 628 "short_description":"t desc", 629 "description":"desc", 630 "created_by":"mi", 631 "created_at":` + referenceTimeStr + `, 632 "updated_at":"2006-01-02T15:04:05Z", 633 "featured":true, 634 "curated":true, 635 "score":123 636 } 637 ] 638 }` 639 640 testJSONMarshal(t, u, want) 641 } 642 643 func TestLabelResult_Marshal(t *testing.T) { 644 testJSONMarshal(t, &LabelResult{}, "{}") 645 646 u := &LabelResult{ 647 ID: Int64(11), 648 URL: String("url"), 649 Name: String("label"), 650 Color: String("green"), 651 Default: Bool(true), 652 Description: String("desc"), 653 Score: Float64(123), 654 } 655 656 want := `{ 657 "id":11, 658 "url":"url", 659 "name":"label", 660 "color":"green", 661 "default":true, 662 "description":"desc", 663 "score":123 664 }` 665 666 testJSONMarshal(t, u, want) 667 } 668 669 func TestSearchOptions_Marshal(t *testing.T) { 670 testJSONMarshal(t, &SearchOptions{}, "{}") 671 672 u := &SearchOptions{ 673 Sort: "author-date", 674 Order: "asc", 675 TextMatch: false, 676 ListOptions: ListOptions{ 677 Page: int(1), 678 PerPage: int(10), 679 }, 680 } 681 682 want := `{ 683 "sort": "author-date", 684 "order": "asc", 685 "page": 1, 686 "perPage": 10 687 }` 688 689 testJSONMarshal(t, u, want) 690 } 691 692 func TestIssuesSearchResult_Marshal(t *testing.T) { 693 testJSONMarshal(t, &IssuesSearchResult{}, "{}") 694 695 u := &IssuesSearchResult{ 696 Total: Int(48), 697 IncompleteResults: Bool(false), 698 Issues: []*Issue{ 699 { 700 ID: Int64(1), 701 Number: Int(1), 702 State: String("s"), 703 Locked: Bool(false), 704 Title: String("title"), 705 Body: String("body"), 706 AuthorAssociation: String("aa"), 707 User: &User{ID: Int64(1)}, 708 Labels: []*Label{{ID: Int64(1)}}, 709 Assignee: &User{ID: Int64(1)}, 710 Comments: Int(1), 711 ClosedAt: &referenceTime, 712 CreatedAt: &referenceTime, 713 UpdatedAt: &referenceTime, 714 ClosedBy: &User{ID: Int64(1)}, 715 URL: String("url"), 716 HTMLURL: String("hurl"), 717 CommentsURL: String("curl"), 718 EventsURL: String("eurl"), 719 LabelsURL: String("lurl"), 720 RepositoryURL: String("rurl"), 721 Milestone: &Milestone{ID: Int64(1)}, 722 PullRequestLinks: &PullRequestLinks{URL: String("url")}, 723 Repository: &Repository{ID: Int64(1)}, 724 Reactions: &Reactions{TotalCount: Int(1)}, 725 Assignees: []*User{{ID: Int64(1)}}, 726 NodeID: String("nid"), 727 TextMatches: []*TextMatch{{ObjectURL: String("ourl")}}, 728 ActiveLockReason: String("alr"), 729 }, 730 }, 731 } 732 733 want := `{ 734 "total_count": 48, 735 "incomplete_results": false, 736 "items": [ 737 { 738 "id": 1, 739 "number": 1, 740 "state": "s", 741 "locked": false, 742 "title": "title", 743 "body": "body", 744 "author_association": "aa", 745 "user": { 746 "id": 1 747 }, 748 "labels": [ 749 { 750 "id": 1 751 } 752 ], 753 "assignee": { 754 "id": 1 755 }, 756 "comments": 1, 757 "closed_at": ` + referenceTimeStr + `, 758 "created_at": ` + referenceTimeStr + `, 759 "updated_at": ` + referenceTimeStr + `, 760 "closed_by": { 761 "id": 1 762 }, 763 "url": "url", 764 "html_url": "hurl", 765 "comments_url": "curl", 766 "events_url": "eurl", 767 "labels_url": "lurl", 768 "repository_url": "rurl", 769 "milestone": { 770 "id": 1 771 }, 772 "pull_request": { 773 "url": "url" 774 }, 775 "repository": { 776 "id": 1 777 }, 778 "reactions": { 779 "total_count": 1 780 }, 781 "assignees": [ 782 { 783 "id": 1 784 } 785 ], 786 "node_id": "nid", 787 "text_matches": [ 788 { 789 "object_url": "ourl" 790 } 791 ], 792 "active_lock_reason": "alr" 793 } 794 ] 795 }` 796 797 testJSONMarshal(t, u, want) 798 }