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