@@ -996,6 +996,184 @@ func TestGetTeamDoneStateID(t *testing.T) {
996996 }
997997}
998998
999+ func TestListIssues_FollowsCursorPastFirstPage (t * testing.T ) {
1000+ pageOf := func (ids []string , hasNext bool , endCursor string ) string {
1001+ nodes := ""
1002+ for _ , id := range ids {
1003+ nodes += fmt .Sprintf (`{
1004+ "id": "%s", "identifier": "%s", "title": "t", "description": "",
1005+ "priority": 0,
1006+ "state": {"id": "s", "name": "Todo", "type": "unstarted"},
1007+ "labels": {"nodes": []},
1008+ "assignee": null, "project": null,
1009+ "team": {"id": "team-1", "name": "Eng", "key": "ENG"},
1010+ "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z"
1011+ },` , id , id )
1012+ }
1013+ nodes = nodes [:len (nodes )- 1 ] // trim trailing comma
1014+ return fmt .Sprintf (`{"issues": {"nodes": [%s], "pageInfo": {"hasNextPage": %v, "endCursor": "%s"}}}` , nodes , hasNext , endCursor )
1015+ }
1016+
1017+ var calls int
1018+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
1019+ var reqBody GraphQLRequest
1020+ if err := json .NewDecoder (r .Body ).Decode (& reqBody ); err != nil {
1021+ t .Fatalf ("failed to decode request body: %v" , err )
1022+ }
1023+ calls ++
1024+
1025+ after , _ := reqBody .Variables ["after" ].(string )
1026+ var body string
1027+ switch after {
1028+ case "" :
1029+ body = pageOf ([]string {"i1" , "i2" }, true , "cursor-1" )
1030+ case "cursor-1" :
1031+ body = pageOf ([]string {"i3" , "i4" }, true , "cursor-2" )
1032+ case "cursor-2" :
1033+ body = pageOf ([]string {"i5" }, false , "" )
1034+ default :
1035+ t .Fatalf ("unexpected after cursor: %q" , after )
1036+ }
1037+
1038+ w .Header ().Set ("Content-Type" , "application/json" )
1039+ _ = json .NewEncoder (w ).Encode (GraphQLResponse {Data : json .RawMessage (body )})
1040+ }))
1041+ defer server .Close ()
1042+
1043+ client := NewClientWithBaseURL (testutil .FakeLinearToken , server .URL )
1044+
1045+ issues , err := client .ListIssues (context .Background (), & ListIssuesOptions {TeamID : "ENG" , Label : "bug" })
1046+ if err != nil {
1047+ t .Fatalf ("ListIssues failed: %v" , err )
1048+ }
1049+
1050+ if calls != 3 {
1051+ t .Errorf ("expected 3 paginated requests, got %d" , calls )
1052+ }
1053+ if len (issues ) != 5 {
1054+ t .Fatalf ("expected 5 issues across 3 pages, got %d" , len (issues ))
1055+ }
1056+ want := []string {"i1" , "i2" , "i3" , "i4" , "i5" }
1057+ for i , id := range want {
1058+ if issues [i ].ID != id {
1059+ t .Errorf ("issues[%d].ID = %s, want %s" , i , issues [i ].ID , id )
1060+ }
1061+ }
1062+ }
1063+
1064+ func TestListIssues_SinglePage_NoBehaviorChange (t * testing.T ) {
1065+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
1066+ var reqBody GraphQLRequest
1067+ if err := json .NewDecoder (r .Body ).Decode (& reqBody ); err != nil {
1068+ t .Fatalf ("failed to decode request body: %v" , err )
1069+ }
1070+ if after , ok := reqBody .Variables ["after" ]; ok && after != "" {
1071+ t .Fatalf ("unexpected after cursor on first request: %v" , after )
1072+ }
1073+
1074+ resp := GraphQLResponse {Data : json .RawMessage (`{
1075+ "issues": {
1076+ "nodes": [
1077+ {
1078+ "id": "issue-1", "identifier": "ENG-1", "title": "t", "description": "",
1079+ "priority": 0,
1080+ "state": {"id": "s", "name": "Todo", "type": "unstarted"},
1081+ "labels": {"nodes": []},
1082+ "assignee": null, "project": null,
1083+ "team": {"id": "team-1", "name": "Eng", "key": "ENG"},
1084+ "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T00:00:00Z"
1085+ }
1086+ ],
1087+ "pageInfo": {"hasNextPage": false, "endCursor": ""}
1088+ }
1089+ }` )}
1090+ w .Header ().Set ("Content-Type" , "application/json" )
1091+ _ = json .NewEncoder (w ).Encode (resp )
1092+ }))
1093+ defer server .Close ()
1094+
1095+ client := NewClientWithBaseURL (testutil .FakeLinearToken , server .URL )
1096+
1097+ issues , err := client .ListIssues (context .Background (), & ListIssuesOptions {TeamID : "ENG" , Label : "bug" })
1098+ if err != nil {
1099+ t .Fatalf ("ListIssues failed: %v" , err )
1100+ }
1101+ if len (issues ) != 1 {
1102+ t .Fatalf ("expected 1 issue, got %d" , len (issues ))
1103+ }
1104+ if issues [0 ].ID != "issue-1" {
1105+ t .Errorf ("issues[0].ID = %s, want issue-1" , issues [0 ].ID )
1106+ }
1107+ }
1108+
1109+ func TestListIssuesSince_FiltersAndFollowsCursor (t * testing.T ) {
1110+ since := time .Date (2024 , 6 , 1 , 0 , 0 , 0 , 0 , time .UTC )
1111+
1112+ var calls int
1113+ var sawSinceFilter bool
1114+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
1115+ var reqBody GraphQLRequest
1116+ if err := json .NewDecoder (r .Body ).Decode (& reqBody ); err != nil {
1117+ t .Fatalf ("failed to decode request body: %v" , err )
1118+ }
1119+ calls ++
1120+
1121+ if sinceVar , _ := reqBody .Variables ["since" ].(string ); sinceVar == since .Format (time .RFC3339 ) {
1122+ sawSinceFilter = true
1123+ }
1124+ if ! contains (reqBody .Query , "updatedAt: { gt: $since }" ) {
1125+ t .Errorf ("query should filter on updatedAt gt $since, got: %s" , reqBody .Query )
1126+ }
1127+ if ! contains (reqBody .Query , "orderBy: updatedAt" ) {
1128+ t .Errorf ("query should order by updatedAt, got: %s" , reqBody .Query )
1129+ }
1130+
1131+ after , _ := reqBody .Variables ["after" ].(string )
1132+ var body string
1133+ if after == "" {
1134+ body = `{"issues": {"nodes": [
1135+ {"id": "i1", "identifier": "ENG-1", "title": "t", "description": "",
1136+ "priority": 0, "state": {"id": "s", "name": "Todo", "type": "unstarted"},
1137+ "labels": {"nodes": []}, "assignee": null, "project": null,
1138+ "team": {"id": "team-1", "name": "Eng", "key": "ENG"},
1139+ "createdAt": "2024-06-02T00:00:00Z", "updatedAt": "2024-06-02T00:00:00Z"}
1140+ ], "pageInfo": {"hasNextPage": true, "endCursor": "cursor-1"}}}`
1141+ } else {
1142+ body = `{"issues": {"nodes": [
1143+ {"id": "i2", "identifier": "ENG-2", "title": "t", "description": "",
1144+ "priority": 0, "state": {"id": "s", "name": "Todo", "type": "unstarted"},
1145+ "labels": {"nodes": []}, "assignee": null, "project": null,
1146+ "team": {"id": "team-1", "name": "Eng", "key": "ENG"},
1147+ "createdAt": "2024-06-03T00:00:00Z", "updatedAt": "2024-06-03T00:00:00Z"}
1148+ ], "pageInfo": {"hasNextPage": false, "endCursor": ""}}}`
1149+ }
1150+
1151+ w .Header ().Set ("Content-Type" , "application/json" )
1152+ _ = json .NewEncoder (w ).Encode (GraphQLResponse {Data : json .RawMessage (body )})
1153+ }))
1154+ defer server .Close ()
1155+
1156+ client := NewClientWithBaseURL (testutil .FakeLinearToken , server .URL )
1157+
1158+ issues , err := client .ListIssuesSince (context .Background (), & ListIssuesSinceOptions {TeamID : "ENG" , Since : since })
1159+ if err != nil {
1160+ t .Fatalf ("ListIssuesSince failed: %v" , err )
1161+ }
1162+
1163+ if calls != 2 {
1164+ t .Errorf ("expected 2 paginated requests, got %d" , calls )
1165+ }
1166+ if ! sawSinceFilter {
1167+ t .Error ("expected since variable to be sent as RFC3339 formatted string" )
1168+ }
1169+ if len (issues ) != 2 {
1170+ t .Fatalf ("expected 2 issues across 2 pages, got %d" , len (issues ))
1171+ }
1172+ if issues [0 ].ID != "i1" || issues [1 ].ID != "i2" {
1173+ t .Errorf ("issues = %v, want [i1, i2] in order" , issues )
1174+ }
1175+ }
1176+
9991177func TestGetTeamDoneStateID_Cache (t * testing.T ) {
10001178 callCount := 0
10011179 server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
0 commit comments