github.com/google/go-github/v49@v49.1.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 client, mux, _, teardown := setup() 21 defer teardown() 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: Int(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 client, mux, _, teardown := setup() 67 defer teardown() 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: Int(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 client, mux, _, teardown := setup() 89 defer teardown() 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: Int(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 client, _, _, teardown := setup() 125 defer teardown() 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 client, _, _, teardown := setup() 134 defer teardown() 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 client, mux, _, teardown := setup() 143 defer teardown() 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: Int(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 client, _, _, teardown := setup() 195 defer teardown() 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 client, mux, _, teardown := setup() 204 defer teardown() 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: Int(1), 220 AuthorAssociation: String("MEMBER"), 221 Labels: []*Label{{ 222 URL: String("u"), 223 Name: String("n"), 224 Color: String("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 client, _, _, teardown := setup() 248 defer teardown() 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 client, mux, _, teardown := setup() 257 defer teardown() 258 259 input := &IssueRequest{ 260 Title: String("t"), 261 Body: String("b"), 262 Assignee: String("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 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: Int(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 client, _, _, teardown := setup() 306 defer teardown() 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 client, mux, _, teardown := setup() 315 defer teardown() 316 317 input := &IssueRequest{Title: String("t")} 318 319 mux.HandleFunc("/repos/o/r/issues/1", func(w http.ResponseWriter, r *http.Request) { 320 v := new(IssueRequest) 321 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: Int(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 client, mux, _, teardown := setup() 359 defer teardown() 360 mux.HandleFunc("/repos/o/r/issues/1", func(w http.ResponseWriter, r *http.Request) { 361 testMethod(t, r, "PATCH") 362 fmt.Fprint(w, `{"number":1}`) 363 }) 364 365 ctx := context.Background() 366 issue, _, err := client.Issues.RemoveMilestone(ctx, "o", "r", 1) 367 if err != nil { 368 t.Errorf("Issues.RemoveMilestone returned error: %v", err) 369 } 370 371 want := &Issue{Number: Int(1)} 372 if !cmp.Equal(issue, want) { 373 t.Errorf("Issues.RemoveMilestone returned %+v, want %+v", issue, want) 374 } 375 376 const methodName = "RemoveMilestone" 377 testBadOptions(t, methodName, func() (err error) { 378 _, _, err = client.Issues.RemoveMilestone(ctx, "\n", "\n", -1) 379 return err 380 }) 381 382 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 383 got, resp, err := client.Issues.RemoveMilestone(ctx, "o", "r", 1) 384 if got != nil { 385 t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) 386 } 387 return resp, err 388 }) 389 } 390 391 func TestIssuesService_Edit_invalidOwner(t *testing.T) { 392 client, _, _, teardown := setup() 393 defer teardown() 394 395 ctx := context.Background() 396 _, _, err := client.Issues.Edit(ctx, "%", "r", 1, nil) 397 testURLParseError(t, err) 398 } 399 400 func TestIssuesService_Lock(t *testing.T) { 401 client, mux, _, teardown := setup() 402 defer teardown() 403 404 mux.HandleFunc("/repos/o/r/issues/1/lock", func(w http.ResponseWriter, r *http.Request) { 405 testMethod(t, r, "PUT") 406 407 w.WriteHeader(http.StatusNoContent) 408 }) 409 410 ctx := context.Background() 411 if _, err := client.Issues.Lock(ctx, "o", "r", 1, nil); err != nil { 412 t.Errorf("Issues.Lock returned error: %v", err) 413 } 414 415 const methodName = "Lock" 416 testBadOptions(t, methodName, func() (err error) { 417 _, err = client.Issues.Lock(ctx, "\n", "\n", -1, nil) 418 return err 419 }) 420 421 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 422 return client.Issues.Lock(ctx, "o", "r", 1, nil) 423 }) 424 } 425 426 func TestIssuesService_LockWithReason(t *testing.T) { 427 client, mux, _, teardown := setup() 428 defer teardown() 429 430 mux.HandleFunc("/repos/o/r/issues/1/lock", func(w http.ResponseWriter, r *http.Request) { 431 testMethod(t, r, "PUT") 432 w.WriteHeader(http.StatusNoContent) 433 }) 434 435 opt := &LockIssueOptions{LockReason: "off-topic"} 436 437 ctx := context.Background() 438 if _, err := client.Issues.Lock(ctx, "o", "r", 1, opt); err != nil { 439 t.Errorf("Issues.Lock returned error: %v", err) 440 } 441 } 442 443 func TestIssuesService_Unlock(t *testing.T) { 444 client, mux, _, teardown := setup() 445 defer teardown() 446 447 mux.HandleFunc("/repos/o/r/issues/1/lock", func(w http.ResponseWriter, r *http.Request) { 448 testMethod(t, r, "DELETE") 449 450 w.WriteHeader(http.StatusNoContent) 451 }) 452 453 ctx := context.Background() 454 if _, err := client.Issues.Unlock(ctx, "o", "r", 1); err != nil { 455 t.Errorf("Issues.Unlock returned error: %v", err) 456 } 457 458 const methodName = "Unlock" 459 testBadOptions(t, methodName, func() (err error) { 460 _, err = client.Issues.Unlock(ctx, "\n", "\n", 1) 461 return err 462 }) 463 464 testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { 465 return client.Issues.Unlock(ctx, "o", "r", 1) 466 }) 467 } 468 469 func TestIsPullRequest(t *testing.T) { 470 i := new(Issue) 471 if i.IsPullRequest() == true { 472 t.Errorf("expected i.IsPullRequest (%v) to return false, got true", i) 473 } 474 i.PullRequestLinks = &PullRequestLinks{URL: String("http://example.com")} 475 if i.IsPullRequest() == false { 476 t.Errorf("expected i.IsPullRequest (%v) to return true, got false", i) 477 } 478 } 479 480 func TestLockIssueOptions_Marshal(t *testing.T) { 481 testJSONMarshal(t, &LockIssueOptions{}, "{}") 482 483 u := &LockIssueOptions{ 484 LockReason: "lr", 485 } 486 487 want := `{ 488 "lock_reason": "lr" 489 }` 490 491 testJSONMarshal(t, u, want) 492 } 493 494 func TestPullRequestLinks_Marshal(t *testing.T) { 495 testJSONMarshal(t, &PullRequestLinks{}, "{}") 496 497 u := &PullRequestLinks{ 498 URL: String("url"), 499 HTMLURL: String("hurl"), 500 DiffURL: String("durl"), 501 PatchURL: String("purl"), 502 } 503 504 want := `{ 505 "url": "url", 506 "html_url": "hurl", 507 "diff_url": "durl", 508 "patch_url": "purl" 509 }` 510 511 testJSONMarshal(t, u, want) 512 } 513 514 func TestIssueRequest_Marshal(t *testing.T) { 515 testJSONMarshal(t, &IssueRequest{}, "{}") 516 517 u := &IssueRequest{ 518 Title: String("url"), 519 Body: String("url"), 520 Labels: &[]string{"l"}, 521 Assignee: String("url"), 522 State: String("url"), 523 Milestone: Int(1), 524 Assignees: &[]string{"a"}, 525 } 526 527 want := `{ 528 "title": "url", 529 "body": "url", 530 "labels": [ 531 "l" 532 ], 533 "assignee": "url", 534 "state": "url", 535 "milestone": 1, 536 "assignees": [ 537 "a" 538 ] 539 }` 540 541 testJSONMarshal(t, u, want) 542 } 543 544 func TestIssue_Marshal(t *testing.T) { 545 testJSONMarshal(t, &Issue{}, "{}") 546 547 u := &Issue{ 548 ID: Int64(1), 549 Number: Int(1), 550 State: String("s"), 551 Locked: Bool(false), 552 Title: String("title"), 553 Body: String("body"), 554 AuthorAssociation: String("aa"), 555 User: &User{ID: Int64(1)}, 556 Labels: []*Label{{ID: Int64(1)}}, 557 Assignee: &User{ID: Int64(1)}, 558 Comments: Int(1), 559 ClosedAt: &referenceTime, 560 CreatedAt: &referenceTime, 561 UpdatedAt: &referenceTime, 562 ClosedBy: &User{ID: Int64(1)}, 563 URL: String("url"), 564 HTMLURL: String("hurl"), 565 CommentsURL: String("curl"), 566 EventsURL: String("eurl"), 567 LabelsURL: String("lurl"), 568 RepositoryURL: String("rurl"), 569 Milestone: &Milestone{ID: Int64(1)}, 570 PullRequestLinks: &PullRequestLinks{URL: String("url")}, 571 Repository: &Repository{ID: Int64(1)}, 572 Reactions: &Reactions{TotalCount: Int(1)}, 573 Assignees: []*User{{ID: Int64(1)}}, 574 NodeID: String("nid"), 575 TextMatches: []*TextMatch{{ObjectURL: String("ourl")}}, 576 ActiveLockReason: String("alr"), 577 } 578 579 want := `{ 580 "id": 1, 581 "number": 1, 582 "state": "s", 583 "locked": false, 584 "title": "title", 585 "body": "body", 586 "author_association": "aa", 587 "user": { 588 "id": 1 589 }, 590 "labels": [ 591 { 592 "id": 1 593 } 594 ], 595 "assignee": { 596 "id": 1 597 }, 598 "comments": 1, 599 "closed_at": ` + referenceTimeStr + `, 600 "created_at": ` + referenceTimeStr + `, 601 "updated_at": ` + referenceTimeStr + `, 602 "closed_by": { 603 "id": 1 604 }, 605 "url": "url", 606 "html_url": "hurl", 607 "comments_url": "curl", 608 "events_url": "eurl", 609 "labels_url": "lurl", 610 "repository_url": "rurl", 611 "milestone": { 612 "id": 1 613 }, 614 "pull_request": { 615 "url": "url" 616 }, 617 "repository": { 618 "id": 1 619 }, 620 "reactions": { 621 "total_count": 1 622 }, 623 "assignees": [ 624 { 625 "id": 1 626 } 627 ], 628 "node_id": "nid", 629 "text_matches": [ 630 { 631 "object_url": "ourl" 632 } 633 ], 634 "active_lock_reason": "alr" 635 }` 636 637 testJSONMarshal(t, u, want) 638 }