Skip to content

Commit 5a66328

Browse files
committed
feat(cloud): migrate search to v3 API
1 parent 51c7813 commit 5a66328

File tree

5 files changed

+99
-87
lines changed

5 files changed

+99
-87
lines changed

cloud/board_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func TestBoardService_GetAllBoards_WithFilter(t *testing.T) {
4545
testMux.HandleFunc(testapiEndpoint, func(w http.ResponseWriter, r *http.Request) {
4646
testMethod(t, r, http.MethodGet)
4747
testRequestURL(t, r, testapiEndpoint)
48-
testRequestParams(t, r, map[string]string{"type": "scrum", "name": "Test", "startAt": "1", "maxResults": "10", "projectKeyOrId": "TE"})
48+
testRequestParams(t, r, map[string]string{"type": "scrum", "name": "Test", "maxResults": "10", "projectKeyOrId": "TE"})
4949
fmt.Fprint(w, string(raw))
5050
})
5151

@@ -54,7 +54,6 @@ func TestBoardService_GetAllBoards_WithFilter(t *testing.T) {
5454
Name: "Test",
5555
ProjectKeyOrID: "TE",
5656
}
57-
boardsListOptions.StartAt = 1
5857
boardsListOptions.MaxResults = 10
5958

6059
projects, _, err := testClient.Board.GetAllBoards(context.Background(), boardsListOptions)

cloud/examples/pagination/main.go

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,26 @@ import (
1212
// You may have usecase where you need to get all the issues according to jql
1313
// This is where this example comes in.
1414
func GetAllIssues(client *jira.Client, searchString string) ([]jira.Issue, error) {
15-
last := 0
1615
var issues []jira.Issue
17-
for {
18-
opt := &jira.SearchOptions{
19-
MaxResults: 1000, // Max results can go up to 1000
20-
StartAt: last,
21-
}
16+
opt := &jira.SearchOptions{
17+
MaxResults: 1000, // Max results can go up to 1000
18+
}
2219

20+
for {
2321
chunk, resp, err := client.Issue.Search(context.Background(), searchString, opt)
2422
if err != nil {
2523
return nil, err
2624
}
2725

28-
total := resp.Total
29-
if issues == nil {
30-
issues = make([]jira.Issue, 0, total)
31-
}
3226
issues = append(issues, chunk...)
33-
last = resp.StartAt + len(chunk)
34-
if last >= total {
27+
28+
if resp.IsLast {
3529
return issues, nil
3630
}
37-
}
3831

32+
// Set the next page token for the next iteration
33+
opt.NextPageToken = resp.NextPageToken
34+
}
3935
}
4036

4137
func main() {

cloud/issue.go

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -517,24 +517,31 @@ type CommentVisibility struct {
517517
// A request to a pages API will result in a values array wrapped in a JSON object with some paging metadata
518518
// Default Pagination options
519519
type SearchOptions struct {
520-
// StartAt: The starting index of the returned projects. Base index: 0.
521-
StartAt int `url:"startAt,omitempty"`
520+
// NextPageToken: The token for a page to fetch that is not the first page.
521+
// The first page has a nextPageToken of null. Use the nextPageToken to fetch the next page of issues.
522+
NextPageToken string `url:"nextPageToken,omitempty"`
522523
// MaxResults: The maximum number of projects to return per page. Default: 50.
523524
MaxResults int `url:"maxResults,omitempty"`
524-
// Expand: Expand specific sections in the returned issues
525+
// Expand: Expand specific sections in the returned issues.
525526
Expand string `url:"expand,omitempty"`
526-
Fields []string
527-
// ValidateQuery: The validateQuery param offers control over whether to validate and how strictly to treat the validation. Default: strict.
528-
ValidateQuery string `url:"validateQuery,omitempty"`
527+
// Fields: A list of fields to return for each issue, use it to retrieve a subset of fields.
528+
Fields []string // comma-joined
529+
// Properties: A list of up to 5 issue properties to include in the results.
530+
Properties []string
531+
// FieldsByKeys: Reference fields by their key (rather than ID). The default is false.
532+
FieldsByKeys bool `url:"fieldsByKeys,omitempty"`
533+
// FailFast: Fail this request early if we can't retrieve all field data.
534+
FailFast bool `url:"failFast,omitempty"`
535+
// ReconcileIssues: Strong consistency issue ids to be reconciled with search results. Accepts max 50 ids.
536+
ReconcileIssues []int `url:"reconcileIssues,omitempty"`
529537
}
530538

531539
// searchResult is only a small wrapper around the Search (with JQL) method
532540
// to be able to parse the results
533541
type searchResult struct {
534-
Issues []Issue `json:"issues" structs:"issues"`
535-
StartAt int `json:"startAt" structs:"startAt"`
536-
MaxResults int `json:"maxResults" structs:"maxResults"`
537-
Total int `json:"total" structs:"total"`
542+
Issues []Issue `json:"issues"`
543+
IsLast bool `json:"isLast"`
544+
NextPageToken string `json:"nextPageToken,omitempty"`
538545
}
539546

540547
// GetQueryOptions specifies the optional parameters for the Get Issue methods
@@ -1040,24 +1047,21 @@ func (s *IssueService) AddLink(ctx context.Context, issueLink *IssueLink) (*Resp
10401047

10411048
// Search will search for tickets according to the jql
10421049
//
1043-
// Jira API docs: https://developer.atlassian.com/jiradev/jira-apis/jira-rest-apis/jira-rest-api-tutorials/jira-rest-api-example-query-issues
1044-
//
1045-
// TODO Double check this method if this works as expected, is using the latest API and the response is complete
1046-
// This double check effort is done for v2 - Remove this two lines if this is completed.
1050+
// Jira API docs: https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-search/#api-rest-api-3-search-jql-get
10471051
func (s *IssueService) Search(ctx context.Context, jql string, options *SearchOptions) ([]Issue, *Response, error) {
10481052
u := url.URL{
1049-
Path: "rest/api/2/search",
1053+
Path: "rest/api/3/search/jql",
10501054
}
10511055
uv := url.Values{}
10521056
if jql != "" {
10531057
uv.Add("jql", jql)
10541058
}
10551059

10561060
if options != nil {
1057-
if options.StartAt != 0 {
1058-
uv.Add("startAt", strconv.Itoa(options.StartAt))
1061+
if options.NextPageToken != "" {
1062+
uv.Add("nextPageToken", options.NextPageToken)
10591063
}
1060-
if options.MaxResults != 0 {
1064+
if options.MaxResults > 0 {
10611065
uv.Add("maxResults", strconv.Itoa(options.MaxResults))
10621066
}
10631067
if options.Expand != "" {
@@ -1066,8 +1070,19 @@ func (s *IssueService) Search(ctx context.Context, jql string, options *SearchOp
10661070
if strings.Join(options.Fields, ",") != "" {
10671071
uv.Add("fields", strings.Join(options.Fields, ","))
10681072
}
1069-
if options.ValidateQuery != "" {
1070-
uv.Add("validateQuery", options.ValidateQuery)
1073+
if len(options.Properties) > 0 {
1074+
uv.Add("properties", strings.Join(options.Properties, ","))
1075+
}
1076+
if options.FieldsByKeys {
1077+
uv.Add("fieldsByKeys", "true")
1078+
}
1079+
if options.FailFast {
1080+
uv.Add("failFast", "true")
1081+
}
1082+
if len(options.ReconcileIssues) > 0 {
1083+
for _, id := range options.ReconcileIssues {
1084+
uv.Add("reconcileIssues", strconv.Itoa(id))
1085+
}
10711086
}
10721087
}
10731088

@@ -1095,7 +1110,6 @@ func (s *IssueService) Search(ctx context.Context, jql string, options *SearchOp
10951110
func (s *IssueService) SearchPages(ctx context.Context, jql string, options *SearchOptions, f func(Issue) error) error {
10961111
if options == nil {
10971112
options = &SearchOptions{
1098-
StartAt: 0,
10991113
MaxResults: 50,
11001114
}
11011115
}
@@ -1121,11 +1135,11 @@ func (s *IssueService) SearchPages(ctx context.Context, jql string, options *Sea
11211135
}
11221136
}
11231137

1124-
if resp.StartAt+resp.MaxResults >= resp.Total {
1138+
if resp == nil || resp.IsLast || resp.NextPageToken == "" {
11251139
return nil
11261140
}
11271141

1128-
options.StartAt += resp.MaxResults
1142+
options.NextPageToken = resp.NextPageToken
11291143
issues, resp, err = s.Search(ctx, jql, options)
11301144
if err != nil {
11311145
return err

cloud/issue_test.go

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -620,15 +620,15 @@ func TestIssueService_DeleteLink(t *testing.T) {
620620
func TestIssueService_Search(t *testing.T) {
621621
setup()
622622
defer teardown()
623-
testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) {
623+
testMux.HandleFunc("/rest/api/3/search/jql", func(w http.ResponseWriter, r *http.Request) {
624624
testMethod(t, r, http.MethodGet)
625-
testRequestURL(t, r, "/rest/api/2/search?expand=foo&jql=type+%3D+Bug+and+Status+NOT+IN+%28Resolved%29&maxResults=40&startAt=1")
625+
testRequestURL(t, r, "/rest/api/3/search/jql?expand=foo&jql=type+%3D+Bug+and+Status+NOT+IN+%28Resolved%29&maxResults=40")
626626
w.WriteHeader(http.StatusOK)
627-
fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 40,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`)
627+
fmt.Fprint(w, `{"issues": [{"id": "10068"},{"id": "10067"},{"id": "10066"}],"nextPageToken": "CAEaAggD"}`)
628628
})
629629

630-
opt := &SearchOptions{StartAt: 1, MaxResults: 40, Expand: "foo"}
631-
_, resp, err := testClient.Issue.Search(context.Background(), "type = Bug and Status NOT IN (Resolved)", opt)
630+
opt := &SearchOptions{MaxResults: 40, Expand: "foo"}
631+
issues, resp, err := testClient.Issue.Search(context.Background(), "type = Bug and Status NOT IN (Resolved)", opt)
632632

633633
if resp == nil {
634634
t.Errorf("Response given: %+v", resp)
@@ -637,29 +637,31 @@ func TestIssueService_Search(t *testing.T) {
637637
t.Errorf("Error given: %s", err)
638638
}
639639

640-
if resp.StartAt != 1 {
641-
t.Errorf("StartAt should populate with 1, %v given", resp.StartAt)
640+
if len(issues) != 3 {
641+
t.Errorf("Expected 3 issues, got %d", len(issues))
642642
}
643-
if resp.MaxResults != 40 {
644-
t.Errorf("MaxResults should populate with 40, %v given", resp.MaxResults)
643+
644+
if resp.NextPageToken != "CAEaAggD" {
645+
t.Errorf("NextPageToken should be 'CAEaAggD', got %v", resp.NextPageToken)
645646
}
646-
if resp.Total != 6 {
647-
t.Errorf("Total should populate with 6, %v given", resp.Total)
647+
648+
if resp.IsLast != false {
649+
t.Errorf("IsLast should be false when nextPageToken is present, got %v", resp.IsLast)
648650
}
649651
}
650652

651653
func TestIssueService_SearchEmptyJQL(t *testing.T) {
652654
setup()
653655
defer teardown()
654-
testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) {
656+
testMux.HandleFunc("/rest/api/3/search/jql", func(w http.ResponseWriter, r *http.Request) {
655657
testMethod(t, r, http.MethodGet)
656-
testRequestURL(t, r, "/rest/api/2/search?expand=foo&maxResults=40&startAt=1")
658+
testRequestURL(t, r, "/rest/api/3/search/jql?expand=foo&maxResults=40")
657659
w.WriteHeader(http.StatusOK)
658-
fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 40,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`)
660+
fmt.Fprint(w, `{"issues": [{"id": "10230"},{"id": "10004"}],"isLast": true}`)
659661
})
660662

661-
opt := &SearchOptions{StartAt: 1, MaxResults: 40, Expand: "foo"}
662-
_, resp, err := testClient.Issue.Search(context.Background(), "", opt)
663+
opt := &SearchOptions{MaxResults: 40, Expand: "foo"}
664+
issues, resp, err := testClient.Issue.Search(context.Background(), "", opt)
663665

664666
if resp == nil {
665667
t.Errorf("Response given: %+v", resp)
@@ -668,25 +670,23 @@ func TestIssueService_SearchEmptyJQL(t *testing.T) {
668670
t.Errorf("Error given: %s", err)
669671
}
670672

671-
if resp.StartAt != 1 {
672-
t.Errorf("StartAt should populate with 1, %v given", resp.StartAt)
673-
}
674-
if resp.MaxResults != 40 {
675-
t.Errorf("StartAt should populate with 40, %v given", resp.MaxResults)
673+
if len(issues) != 2 {
674+
t.Errorf("Expected 2 issues, got %d", len(issues))
676675
}
677-
if resp.Total != 6 {
678-
t.Errorf("StartAt should populate with 6, %v given", resp.Total)
676+
677+
if resp.IsLast != true {
678+
t.Errorf("IsLast should be true when no nextPageToken, got %v", resp.IsLast)
679679
}
680680
}
681681

682682
func TestIssueService_Search_WithoutPaging(t *testing.T) {
683683
setup()
684684
defer teardown()
685-
testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) {
685+
testMux.HandleFunc("/rest/api/3/search/jql", func(w http.ResponseWriter, r *http.Request) {
686686
testMethod(t, r, http.MethodGet)
687-
testRequestURL(t, r, "/rest/api/2/search?jql=something")
687+
testRequestURL(t, r, "/rest/api/3/search/jql?jql=something")
688688
w.WriteHeader(http.StatusOK)
689-
fmt.Fprint(w, `{"expand": "schema,names","startAt": 0,"maxResults": 50,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`)
689+
fmt.Fprint(w, `{"issues": [{"id": "10230"},{"id": "10004"}],"isLast": true}`)
690690
})
691691
_, resp, err := testClient.Issue.Search(context.Background(), "something", nil)
692692

@@ -697,40 +697,37 @@ func TestIssueService_Search_WithoutPaging(t *testing.T) {
697697
t.Errorf("Error given: %s", err)
698698
}
699699

700-
if resp.StartAt != 0 {
701-
t.Errorf("StartAt should populate with 0, %v given", resp.StartAt)
702-
}
703-
if resp.MaxResults != 50 {
704-
t.Errorf("StartAt should populate with 50, %v given", resp.MaxResults)
700+
if !resp.IsLast {
701+
t.Errorf("IsLast should populate with true, %v given", resp.IsLast)
705702
}
706-
if resp.Total != 6 {
707-
t.Errorf("StartAt should populate with 6, %v given", resp.Total)
703+
if resp.NextPageToken != "" {
704+
t.Errorf("NextPageToken should be empty when isLast=true, %v given", resp.NextPageToken)
708705
}
709706
}
710707

711708
func TestIssueService_SearchPages(t *testing.T) {
712709
setup()
713710
defer teardown()
714-
testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) {
711+
testMux.HandleFunc("/rest/api/3/search/jql", func(w http.ResponseWriter, r *http.Request) {
715712
testMethod(t, r, http.MethodGet)
716-
if r.URL.String() == "/rest/api/2/search?expand=foo&jql=something&maxResults=2&startAt=1&validateQuery=warn" {
713+
if r.URL.String() == "/rest/api/3/search/jql?expand=foo&jql=something&maxResults=2" {
717714
w.WriteHeader(http.StatusOK)
718-
fmt.Fprint(w, `{"expand": "schema,names","startAt": 1,"maxResults": 2,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`)
715+
fmt.Fprint(w, `{"issues": [{"id": "10001"},{"id": "10002"}],"nextPageToken": "page2token"}`)
719716
return
720-
} else if r.URL.String() == "/rest/api/2/search?expand=foo&jql=something&maxResults=2&startAt=3&validateQuery=warn" {
717+
} else if r.URL.String() == "/rest/api/3/search/jql?expand=foo&jql=something&maxResults=2&nextPageToken=page2token" {
721718
w.WriteHeader(http.StatusOK)
722-
fmt.Fprint(w, `{"expand": "schema,names","startAt": 3,"maxResults": 2,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}},{"expand": "html","id": "10004","self": "http://kelpie9:8081/rest/api/2/issue/BULK-47","key": "BULK-47","fields": {"summary": "Cheese v1 2.0 issue","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/3","id": "3","description": "A task that needs to be done.","iconUrl": "http://kelpie9:8081/images/icons/task.gif","name": "Task","subtask": false}}}]}`)
719+
fmt.Fprint(w, `{"issues": [{"id": "10003"},{"id": "10004"}],"nextPageToken": "page3token"}`)
723720
return
724-
} else if r.URL.String() == "/rest/api/2/search?expand=foo&jql=something&maxResults=2&startAt=5&validateQuery=warn" {
721+
} else if r.URL.String() == "/rest/api/3/search/jql?expand=foo&jql=something&maxResults=2&nextPageToken=page3token" {
725722
w.WriteHeader(http.StatusOK)
726-
fmt.Fprint(w, `{"expand": "schema,names","startAt": 5,"maxResults": 2,"total": 6,"issues": [{"expand": "html","id": "10230","self": "http://kelpie9:8081/rest/api/2/issue/BULK-62","key": "BULK-62","fields": {"summary": "testing","timetracking": null,"issuetype": {"self": "http://kelpie9:8081/rest/api/2/issuetype/5","id": "5","description": "The sub-task of the issue","iconUrl": "http://kelpie9:8081/images/icons/issue_subtask.gif","name": "Sub-task","subtask": true},"customfield_10071": null}}]}`)
723+
fmt.Fprint(w, `{"issues": [{"id": "10005"}],"isLast": true}`)
727724
return
728725
}
729726

730727
t.Errorf("Unexpected URL: %v", r.URL)
731728
})
732729

733-
opt := &SearchOptions{StartAt: 1, MaxResults: 2, Expand: "foo", ValidateQuery: "warn"}
730+
opt := &SearchOptions{MaxResults: 2, Expand: "foo"}
734731
issues := make([]Issue, 0)
735732
err := testClient.Issue.SearchPages(context.Background(), "something", opt, func(issue Issue) error {
736733
issues = append(issues, issue)
@@ -749,19 +746,19 @@ func TestIssueService_SearchPages(t *testing.T) {
749746
func TestIssueService_SearchPages_EmptyResult(t *testing.T) {
750747
setup()
751748
defer teardown()
752-
testMux.HandleFunc("/rest/api/2/search", func(w http.ResponseWriter, r *http.Request) {
749+
testMux.HandleFunc("/rest/api/3/search/jql", func(w http.ResponseWriter, r *http.Request) {
753750
testMethod(t, r, http.MethodGet)
754-
if r.URL.String() == "/rest/api/2/search?expand=foo&jql=something&maxResults=50&startAt=1&validateQuery=warn" {
751+
if r.URL.String() == "/rest/api/3/search/jql?expand=foo&jql=something&maxResults=50" {
755752
w.WriteHeader(http.StatusOK)
756-
// This is what Jira outputs when the &maxResult= issue occurs. It used to cause SearchPages to go into an endless loop.
757-
fmt.Fprint(w, `{"expand": "schema,names","startAt": 0,"maxResults": 0,"total": 6,"issues": []}`)
753+
// This is what Jira outputs for empty results in API v3. This test ensures SearchPages handles empty results correctly.
754+
fmt.Fprint(w, `{"issues": [],"isLast": true}`)
758755
return
759756
}
760757

761758
t.Errorf("Unexpected URL: %v", r.URL)
762759
})
763760

764-
opt := &SearchOptions{StartAt: 1, MaxResults: 50, Expand: "foo", ValidateQuery: "warn"}
761+
opt := &SearchOptions{MaxResults: 50, Expand: "foo"}
765762
issues := make([]Issue, 0)
766763
err := testClient.Issue.SearchPages(context.Background(), "something", opt, func(issue Issue) error {
767764
issues = append(issues, issue)
@@ -772,6 +769,9 @@ func TestIssueService_SearchPages_EmptyResult(t *testing.T) {
772769
t.Errorf("Error given: %s", err)
773770
}
774771

772+
if len(issues) != 0 {
773+
t.Errorf("Expected 0 issues for empty result, %v given", len(issues))
774+
}
775775
}
776776

777777
func TestIssueService_GetCustomFields(t *testing.T) {

0 commit comments

Comments
 (0)