github.com/google/go-github/v74@v74.0.0/github/issues_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 "encoding/json" 11 "fmt" 12 "net/http" 13 "testing" 14 "time" 15 16 "github.com/google/go-cmp/cmp" 17 ) 18 19 func TestIssuesService_List_all(t *testing.T) { 20 t.Parallel() 21 client, mux, _ := setup(t) 22 23 mux.HandleFunc("/issues", func(w http.ResponseWriter, r *http.Request) { 24 testMethod(t, r, "GET") 25 testHeader(t, r, "Accept", mediaTypeReactionsPreview) 26 testFormValues(t, r, values{ 27 "filter": "all", 28 "state": "closed", 29 "labels": "a,b", 30 "sort": "updated", 31 "direction": "asc", 32 "since": "2002-02-10T15:30:00Z", 33 "page": "1", 34 "per_page": "2", 35 "before": "foo", 36 "after": "bar", 37 }) 38 fmt.Fprint(w, `[{"number":1}]`) 39 }) 40 41 opt := &IssueListOptions{ 42 "all", "closed", []string{"a", "b"}, "updated", "asc", 43 time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), 44 ListCursorOptions{Before: "foo", After: "bar"}, ListOptions{Page: 1, PerPage: 2}, 45 } 46 ctx := context.Background() 47 issues, _, err := client.Issues.List(ctx, true, opt) 48 if err != nil { 49 t.Errorf("Issues.List returned error: %v", err) 50 } 51 52 want := []*Issue{{Number: Ptr(1)}} 53 if !cmp.Equal(issues, want) { 54 t.Errorf("Issues.List returned %+v, want %+v", issues, want) 55 } 56 57 const methodName = "List" 58 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 59 got, resp, err := client.Issues.List(ctx, true, opt) 60 if got != nil { 61 t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) 62 } 63 return resp, err 64 }) 65 } 66 67 func TestIssuesService_List_owned(t *testing.T) { 68 t.Parallel() 69 client, mux, _ := setup(t) 70 71 mux.HandleFunc("/user/issues", func(w http.ResponseWriter, r *http.Request) { 72 testMethod(t, r, "GET") 73 testHeader(t, r, "Accept", mediaTypeReactionsPreview) 74 fmt.Fprint(w, `[{"number":1}]`) 75 }) 76 77 ctx := context.Background() 78 issues, _, err := client.Issues.List(ctx, false, nil) 79 if err != nil { 80 t.Errorf("Issues.List returned error: %v", err) 81 } 82 83 want := []*Issue{{Number: Ptr(1)}} 84 if !cmp.Equal(issues, want) { 85 t.Errorf("Issues.List returned %+v, want %+v", issues, want) 86 } 87 } 88 89 func TestIssuesService_ListByOrg(t *testing.T) { 90 t.Parallel() 91 client, mux, _ := setup(t) 92 93 mux.HandleFunc("/orgs/o/issues", func(w http.ResponseWriter, r *http.Request) { 94 testMethod(t, r, "GET") 95 testHeader(t, r, "Accept", mediaTypeReactionsPreview) 96 fmt.Fprint(w, `[{"number":1}]`) 97 }) 98 99 ctx := context.Background() 100 issues, _, err := client.Issues.ListByOrg(ctx, "o", nil) 101 if err != nil { 102 t.Errorf("Issues.ListByOrg returned error: %v", err) 103 } 104 105 want := []*Issue{{Number: Ptr(1)}} 106 if !cmp.Equal(issues, want) { 107 t.Errorf("Issues.List returned %+v, want %+v", issues, want) 108 } 109 110 const methodName = "ListByOrg" 111 testBadOptions(t, methodName, func() (err error) { 112 _, _, err = client.Issues.ListByOrg(ctx, "\n", nil) 113 return err 114 }) 115 116 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 117 got, resp, err := client.Issues.ListByOrg(ctx, "o", nil) 118 if got != nil { 119 t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) 120 } 121 return resp, err 122 }) 123 } 124 125 func TestIssuesService_ListByOrg_invalidOrg(t *testing.T) { 126 t.Parallel() 127 client, _, _ := setup(t) 128 129 ctx := context.Background() 130 _, _, err := client.Issues.ListByOrg(ctx, "%", nil) 131 testURLParseError(t, err) 132 } 133 134 func TestIssuesService_ListByOrg_badOrg(t *testing.T) { 135 t.Parallel() 136 client, _, _ := setup(t) 137 138 ctx := context.Background() 139 _, _, err := client.Issues.ListByOrg(ctx, "\n", nil) 140 testURLParseError(t, err) 141 } 142 143 func TestIssuesService_ListByRepo(t *testing.T) { 144 t.Parallel() 145 client, mux, _ := setup(t) 146 147 mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) { 148 testMethod(t, r, "GET") 149 testHeader(t, r, "Accept", mediaTypeReactionsPreview) 150 testFormValues(t, r, values{ 151 "milestone": "*", 152 "state": "closed", 153 "assignee": "a", 154 "creator": "c", 155 "mentioned": "m", 156 "labels": "a,b", 157 "sort": "updated", 158 "direction": "asc", 159 "since": "2002-02-10T15:30:00Z", 160 "per_page": "1", 161 "before": "foo", 162 "after": "bar", 163 }) 164 fmt.Fprint(w, `[{"number":1}]`) 165 }) 166 167 opt := &IssueListByRepoOptions{ 168 "*", "closed", "a", "c", "m", []string{"a", "b"}, "updated", "asc", 169 time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC), 170 ListCursorOptions{PerPage: 1, Before: "foo", After: "bar"}, ListOptions{0, 0}, 171 } 172 ctx := context.Background() 173 issues, _, err := client.Issues.ListByRepo(ctx, "o", "r", opt) 174 if err != nil { 175 t.Errorf("Issues.ListByOrg returned error: %v", err) 176 } 177 178 want := []*Issue{{Number: Ptr(1)}} 179 if !cmp.Equal(issues, want) { 180 t.Errorf("Issues.List returned %+v, want %+v", issues, want) 181 } 182 183 const methodName = "ListByRepo" 184 testBadOptions(t, methodName, func() (err error) { 185 _, _, err = client.Issues.ListByRepo(ctx, "\n", "\n", opt) 186 return err 187 }) 188 189 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 190 got, resp, err := client.Issues.ListByRepo(ctx, "o", "r", opt) 191 if got != nil { 192 t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) 193 } 194 return resp, err 195 }) 196 } 197 198 func TestIssuesService_ListByRepo_invalidOwner(t *testing.T) { 199 t.Parallel() 200 client, _, _ := setup(t) 201 202 ctx := context.Background() 203 _, _, err := client.Issues.ListByRepo(ctx, "%", "r", nil) 204 testURLParseError(t, err) 205 } 206 207 func TestIssuesService_Get(t *testing.T) { 208 t.Parallel() 209 client, mux, _ := setup(t) 210 211 mux.HandleFunc("/repos/o/r/issues/1", func(w http.ResponseWriter, r *http.Request) { 212 testMethod(t, r, "GET") 213 testHeader(t, r, "Accept", mediaTypeReactionsPreview) 214 fmt.Fprint(w, `{"number":1, "author_association": "MEMBER","labels": [{"url": "u", "name": "n", "color": "c"}]}`) 215 }) 216 217 ctx := context.Background() 218 issue, _, err := client.Issues.Get(ctx, "o", "r", 1) 219 if err != nil { 220 t.Errorf("Issues.Get returned error: %v", err) 221 } 222 223 want := &Issue{ 224 Number: Ptr(1), 225 AuthorAssociation: Ptr("MEMBER"), 226 Labels: []*Label{{ 227 URL: Ptr("u"), 228 Name: Ptr("n"), 229 Color: Ptr("c"), 230 }}, 231 } 232 if !cmp.Equal(issue, want) { 233 t.Errorf("Issues.Get returned %+v, want %+v", issue, want) 234 } 235 236 const methodName = "Get" 237 testBadOptions(t, methodName, func() (err error) { 238 _, _, err = client.Issues.Get(ctx, "\n", "\n", 1) 239 return err 240 }) 241 242 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 243 got, resp, err := client.Issues.Get(ctx, "o", "r", 1) 244 if got != nil { 245 t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) 246 } 247 return resp, err 248 }) 249 } 250 251 func TestIssuesService_Get_invalidOwner(t *testing.T) { 252 t.Parallel() 253 client, _, _ := setup(t) 254 255 ctx := context.Background() 256 _, _, err := client.Issues.Get(ctx, "%", "r", 1) 257 testURLParseError(t, err) 258 } 259 260 func TestIssuesService_Create(t *testing.T) { 261 t.Parallel() 262 client, mux, _ := setup(t) 263 264 input := &IssueRequest{ 265 Title: Ptr("t"), 266 Body: Ptr("b"), 267 Assignee: Ptr("a"), 268 Labels: &[]string{"l1", "l2"}, 269 } 270 271 mux.HandleFunc("/repos/o/r/issues", func(w http.ResponseWriter, r *http.Request) { 272 v := new(IssueRequest) 273 assertNilError(t, json.NewDecoder(r.Body).Decode(v)) 274 275 testMethod(t, r, "POST") 276 if !cmp.Equal(v, input) { 277 t.Errorf("Request body = %+v, want %+v", v, input) 278 } 279 280 fmt.Fprint(w, `{"number":1}`) 281 }) 282 283 ctx := context.Background() 284 issue, _, err := client.Issues.Create(ctx, "o", "r", input) 285 if err != nil { 286 t.Errorf("Issues.Create returned error: %v", err) 287 } 288 289 want := &Issue{Number: Ptr(1)} 290 if !cmp.Equal(issue, want) { 291 t.Errorf("Issues.Create returned %+v, want %+v", issue, want) 292 } 293 294 const methodName = "Create" 295 testBadOptions(t, methodName, func() (err error) { 296 _, _, err = client.Issues.Create(ctx, "\n", "\n", input) 297 return err 298 }) 299 300 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 301 got, resp, err := client.Issues.Create(ctx, "o", "r", input) 302 if got != nil { 303 t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) 304 } 305 return resp, err 306 }) 307 } 308 309 func TestIssuesService_Create_invalidOwner(t *testing.T) { 310 t.Parallel() 311 client, _, _ := setup(t) 312 313 ctx := context.Background() 314 _, _, err := client.Issues.Create(ctx, "%", "r", nil) 315 testURLParseError(t, err) 316 } 317 318 func TestIssuesService_Edit(t *testing.T) { 319 t.Parallel() 320 client, mux, _ := setup(t) 321 322 input := &IssueRequest{Title: Ptr("t"), Type: Ptr("bug")} 323 324 mux.HandleFunc("/repos/o/r/issues/1", func(w http.ResponseWriter, r *http.Request) { 325 v := new(IssueRequest) 326 assertNilError(t, json.NewDecoder(r.Body).Decode(v)) 327 328 testMethod(t, r, "PATCH") 329 if !cmp.Equal(v, input) { 330 t.Errorf("Request body = %+v, want %+v", v, input) 331 } 332 333 fmt.Fprint(w, `{"number":1, "type": {"name": "bug"}}`) 334 }) 335 336 ctx := context.Background() 337 issue, _, err := client.Issues.Edit(ctx, "o", "r", 1, input) 338 if err != nil { 339 t.Errorf("Issues.Edit returned error: %v", err) 340 } 341 342 want := &Issue{Number: Ptr(1), Type: &IssueType{Name: Ptr("bug")}} 343 if !cmp.Equal(issue, want) { 344 t.Errorf("Issues.Edit returned %+v, want %+v", issue, want) 345 } 346 347 const methodName = "Edit" 348 testBadOptions(t, methodName, func() (err error) { 349 _, _, err = client.Issues.Edit(ctx, "\n", "\n", -1, input) 350 return err 351 }) 352 353 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 354 got, resp, err := client.Issues.Edit(ctx, "o", "r", 1, input) 355 if got != nil { 356 t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) 357 } 358 return resp, err 359 }) 360 } 361 362 func TestIssuesService_RemoveMilestone(t *testing.T) { 363 t.Parallel() 364 client, mux, _ := setup(t) 365 366 mux.HandleFunc("/repos/o/r/issues/1", func(w http.ResponseWriter, r *http.Request) { 367 testMethod(t, r, "PATCH") 368 fmt.Fprint(w, `{"number":1}`) 369 }) 370 371 ctx := context.Background() 372 issue, _, err := client.Issues.RemoveMilestone(ctx, "o", "r", 1) 373 if err != nil { 374 t.Errorf("Issues.RemoveMilestone returned error: %v", err) 375 } 376 377 want := &Issue{Number: Ptr(1)} 378 if !cmp.Equal(issue, want) { 379 t.Errorf("Issues.RemoveMilestone returned %+v, want %+v", issue, want) 380 } 381 382 const methodName = "RemoveMilestone" 383 testBadOptions(t, methodName, func() (err error) { 384 _, _, err = client.Issues.RemoveMilestone(ctx, "\n", "\n", -1) 385 return err 386 }) 387 388 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 389 got, resp, err := client.Issues.RemoveMilestone(ctx, "o", "r", 1) 390 if got != nil { 391 t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) 392 } 393 return resp, err 394 }) 395 } 396 397 func TestIssuesService_Edit_invalidOwner(t *testing.T) { 398 t.Parallel() 399 client, _, _ := setup(t) 400 401 ctx := context.Background() 402 _, _, err := client.Issues.Edit(ctx, "%", "r", 1, nil) 403 testURLParseError(t, err) 404 } 405 406 func TestIssuesService_Lock(t *testing.T) { 407 t.Parallel() 408 client, mux, _ := setup(t) 409 410 mux.HandleFunc("/repos/o/r/issues/1/lock", func(w http.ResponseWriter, r *http.Request) { 411 testMethod(t, r, "PUT") 412 413 w.WriteHeader(http.StatusNoContent) 414 }) 415 416 ctx := context.Background() 417 if _, err := client.Issues.Lock(ctx, "o", "r", 1, nil); err != nil { 418 t.Errorf("Issues.Lock returned error: %v", err) 419 } 420 421 const methodName = "Lock" 422 testBadOptions(t, methodName, func() (err error) { 423 _, err = client.Issues.Lock(ctx, "\n", "\n", -1, nil) 424 return err 425 }) 426 427 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 428 return client.Issues.Lock(ctx, "o", "r", 1, nil) 429 }) 430 } 431 432 func TestIssuesService_LockWithReason(t *testing.T) { 433 t.Parallel() 434 client, mux, _ := setup(t) 435 436 mux.HandleFunc("/repos/o/r/issues/1/lock", func(w http.ResponseWriter, r *http.Request) { 437 testMethod(t, r, "PUT") 438 w.WriteHeader(http.StatusNoContent) 439 }) 440 441 opt := &LockIssueOptions{LockReason: "off-topic"} 442 443 ctx := context.Background() 444 if _, err := client.Issues.Lock(ctx, "o", "r", 1, opt); err != nil { 445 t.Errorf("Issues.Lock returned error: %v", err) 446 } 447 } 448 449 func TestIssuesService_Unlock(t *testing.T) { 450 t.Parallel() 451 client, mux, _ := setup(t) 452 453 mux.HandleFunc("/repos/o/r/issues/1/lock", func(w http.ResponseWriter, r *http.Request) { 454 testMethod(t, r, "DELETE") 455 456 w.WriteHeader(http.StatusNoContent) 457 }) 458 459 ctx := context.Background() 460 if _, err := client.Issues.Unlock(ctx, "o", "r", 1); err != nil { 461 t.Errorf("Issues.Unlock returned error: %v", err) 462 } 463 464 const methodName = "Unlock" 465 testBadOptions(t, methodName, func() (err error) { 466 _, err = client.Issues.Unlock(ctx, "\n", "\n", 1) 467 return err 468 }) 469 470 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 471 return client.Issues.Unlock(ctx, "o", "r", 1) 472 }) 473 } 474 475 func TestIsPullRequest(t *testing.T) { 476 t.Parallel() 477 i := new(Issue) 478 if i.IsPullRequest() { 479 t.Errorf("expected i.IsPullRequest (%v) to return false, got true", i) 480 } 481 i.PullRequestLinks = &PullRequestLinks{URL: Ptr("http://example.com")} 482 if !i.IsPullRequest() { 483 t.Errorf("expected i.IsPullRequest (%v) to return true, got false", i) 484 } 485 } 486 487 func TestLockIssueOptions_Marshal(t *testing.T) { 488 t.Parallel() 489 testJSONMarshal(t, &LockIssueOptions{}, "{}") 490 491 u := &LockIssueOptions{ 492 LockReason: "lr", 493 } 494 495 want := `{ 496 "lock_reason": "lr" 497 }` 498 499 testJSONMarshal(t, u, want) 500 } 501 502 func TestPullRequestLinks_Marshal(t *testing.T) { 503 t.Parallel() 504 testJSONMarshal(t, &PullRequestLinks{}, "{}") 505 506 u := &PullRequestLinks{ 507 URL: Ptr("url"), 508 HTMLURL: Ptr("hurl"), 509 DiffURL: Ptr("durl"), 510 PatchURL: Ptr("purl"), 511 MergedAt: &Timestamp{referenceTime}, 512 } 513 514 want := `{ 515 "url": "url", 516 "html_url": "hurl", 517 "diff_url": "durl", 518 "patch_url": "purl", 519 "merged_at": ` + referenceTimeStr + ` 520 }` 521 522 testJSONMarshal(t, u, want) 523 } 524 525 func TestIssueRequest_Marshal(t *testing.T) { 526 t.Parallel() 527 testJSONMarshal(t, &IssueRequest{}, "{}") 528 529 u := &IssueRequest{ 530 Title: Ptr("url"), 531 Body: Ptr("url"), 532 Labels: &[]string{"l"}, 533 Assignee: Ptr("url"), 534 State: Ptr("url"), 535 Milestone: Ptr(1), 536 Assignees: &[]string{"a"}, 537 Type: Ptr("issue_type"), 538 } 539 540 want := `{ 541 "title": "url", 542 "body": "url", 543 "labels": [ 544 "l" 545 ], 546 "assignee": "url", 547 "state": "url", 548 "milestone": 1, 549 "assignees": [ 550 "a" 551 ], 552 "type": "issue_type" 553 }` 554 555 testJSONMarshal(t, u, want) 556 } 557 558 func TestIssue_Marshal(t *testing.T) { 559 t.Parallel() 560 testJSONMarshal(t, &Issue{}, "{}") 561 562 u := &Issue{ 563 ID: Ptr(int64(1)), 564 Number: Ptr(1), 565 State: Ptr("s"), 566 Locked: Ptr(false), 567 Title: Ptr("title"), 568 Body: Ptr("body"), 569 AuthorAssociation: Ptr("aa"), 570 User: &User{ID: Ptr(int64(1))}, 571 Labels: []*Label{{ID: Ptr(int64(1))}}, 572 Assignee: &User{ID: Ptr(int64(1))}, 573 Comments: Ptr(1), 574 ClosedAt: &Timestamp{referenceTime}, 575 CreatedAt: &Timestamp{referenceTime}, 576 UpdatedAt: &Timestamp{referenceTime}, 577 ClosedBy: &User{ID: Ptr(int64(1))}, 578 URL: Ptr("url"), 579 HTMLURL: Ptr("hurl"), 580 CommentsURL: Ptr("curl"), 581 EventsURL: Ptr("eurl"), 582 LabelsURL: Ptr("lurl"), 583 RepositoryURL: Ptr("rurl"), 584 Milestone: &Milestone{ID: Ptr(int64(1))}, 585 PullRequestLinks: &PullRequestLinks{URL: Ptr("url")}, 586 Repository: &Repository{ID: Ptr(int64(1))}, 587 Reactions: &Reactions{TotalCount: Ptr(1)}, 588 Assignees: []*User{{ID: Ptr(int64(1))}}, 589 NodeID: Ptr("nid"), 590 TextMatches: []*TextMatch{{ObjectURL: Ptr("ourl")}}, 591 ActiveLockReason: Ptr("alr"), 592 Type: &IssueType{Name: Ptr("bug")}, 593 } 594 595 want := `{ 596 "id": 1, 597 "number": 1, 598 "state": "s", 599 "locked": false, 600 "title": "title", 601 "body": "body", 602 "author_association": "aa", 603 "user": { 604 "id": 1 605 }, 606 "labels": [ 607 { 608 "id": 1 609 } 610 ], 611 "assignee": { 612 "id": 1 613 }, 614 "comments": 1, 615 "closed_at": ` + referenceTimeStr + `, 616 "created_at": ` + referenceTimeStr + `, 617 "updated_at": ` + referenceTimeStr + `, 618 "closed_by": { 619 "id": 1 620 }, 621 "url": "url", 622 "html_url": "hurl", 623 "comments_url": "curl", 624 "events_url": "eurl", 625 "labels_url": "lurl", 626 "repository_url": "rurl", 627 "milestone": { 628 "id": 1 629 }, 630 "pull_request": { 631 "url": "url" 632 }, 633 "repository": { 634 "id": 1 635 }, 636 "reactions": { 637 "total_count": 1 638 }, 639 "assignees": [ 640 { 641 "id": 1 642 } 643 ], 644 "node_id": "nid", 645 "text_matches": [ 646 { 647 "object_url": "ourl" 648 } 649 ], 650 "active_lock_reason": "alr", 651 "type": { 652 "name": "bug" 653 } 654 }` 655 656 testJSONMarshal(t, u, want) 657 }