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