1 package graphql
2
3 import (
4 "net/url"
5 "testing"
6 "time"
7 )
8
9 func TestConstructQuery(t *testing.T) {
10 tests := []struct {
11 inV any
12 inVariables map[string]any
13 want string
14 }{
15 {
16 inV: struct {
17 Viewer struct {
18 Login String
19 CreatedAt DateTime
20 ID ID
21 DatabaseID Int
22 }
23 RateLimit struct {
24 Cost Int
25 Limit Int
26 Remaining Int
27 ResetAt DateTime
28 }
29 }{},
30 want: `{viewer{login,createdAt,id,databaseId},rateLimit{cost,limit,remaining,resetAt}}`,
31 },
32 {
33 inV: struct {
34 Repository struct {
35 DatabaseID Int
36 URL URI
37
38 Issue struct {
39 Comments struct {
40 Edges []struct {
41 Node struct {
42 Body String
43 Author struct {
44 Login String
45 }
46 Editor struct {
47 Login String
48 }
49 }
50 Cursor String
51 }
52 } `graphql:"comments(first:1after:\"Y3Vyc29yOjE5NTE4NDI1Ng==\")"`
53 } `graphql:"issue(number:1)"`
54 } `graphql:"repository(owner:\"shurcooL-test\"name:\"test-repo\")"`
55 }{},
56 want: `{repository(owner:"shurcooL-test"name:"test-repo"){databaseId,url,issue(number:1){comments(first:1after:"Y3Vyc29yOjE5NTE4NDI1Ng=="){edges{node{body,author{login},editor{login}},cursor}}}}}`,
57 },
58 {
59 inV: func() any {
60 type actor struct {
61 Login String
62 AvatarURL URI
63 URL URI
64 }
65
66 return struct {
67 Repository struct {
68 DatabaseID Int
69 URL URI
70
71 Issue struct {
72 Comments struct {
73 Edges []struct {
74 Node struct {
75 DatabaseID Int
76 Author actor
77 PublishedAt DateTime
78 LastEditedAt *DateTime
79 Editor *actor
80 Body String
81 ViewerCanUpdate Boolean
82 }
83 Cursor String
84 }
85 } `graphql:"comments(first:1)"`
86 } `graphql:"issue(number:1)"`
87 } `graphql:"repository(owner:\"shurcooL-test\"name:\"test-repo\")"`
88 }{}
89 }(),
90 want: `{repository(owner:"shurcooL-test"name:"test-repo"){databaseId,url,issue(number:1){comments(first:1){edges{node{databaseId,author{login,avatarUrl,url},publishedAt,lastEditedAt,editor{login,avatarUrl,url},body,viewerCanUpdate},cursor}}}}}`,
91 },
92 {
93 inV: func() any {
94 type actor struct {
95 Login String
96 AvatarURL URI `graphql:"avatarUrl(size:72)"`
97 URL URI
98 }
99
100 return struct {
101 Repository struct {
102 Issue struct {
103 Author actor
104 PublishedAt DateTime
105 LastEditedAt *DateTime
106 Editor *actor
107 Body String
108 ReactionGroups []struct {
109 Content ReactionContent
110 Users struct {
111 TotalCount Int
112 }
113 ViewerHasReacted Boolean
114 }
115 ViewerCanUpdate Boolean
116
117 Comments struct {
118 Nodes []struct {
119 DatabaseID Int
120 Author actor
121 PublishedAt DateTime
122 LastEditedAt *DateTime
123 Editor *actor
124 Body String
125 ReactionGroups []struct {
126 Content ReactionContent
127 Users struct {
128 TotalCount Int
129 }
130 ViewerHasReacted Boolean
131 }
132 ViewerCanUpdate Boolean
133 }
134 PageInfo struct {
135 EndCursor String
136 HasNextPage Boolean
137 }
138 } `graphql:"comments(first:1)"`
139 } `graphql:"issue(number:1)"`
140 } `graphql:"repository(owner:\"shurcooL-test\"name:\"test-repo\")"`
141 }{}
142 }(),
143 want: `{repository(owner:"shurcooL-test"name:"test-repo"){issue(number:1){author{login,avatarUrl(size:72),url},publishedAt,lastEditedAt,editor{login,avatarUrl(size:72),url},body,reactionGroups{content,users{totalCount},viewerHasReacted},viewerCanUpdate,comments(first:1){nodes{databaseId,author{login,avatarUrl(size:72),url},publishedAt,lastEditedAt,editor{login,avatarUrl(size:72),url},body,reactionGroups{content,users{totalCount},viewerHasReacted},viewerCanUpdate},pageInfo{endCursor,hasNextPage}}}}}`,
144 },
145 {
146 inV: struct {
147 Repository struct {
148 Issue struct {
149 Body String
150 } `graphql:"issue(number: 1)"`
151 } `graphql:"repository(owner:\"shurcooL-test\"name:\"test-repo\")"`
152 }{},
153 want: `{repository(owner:"shurcooL-test"name:"test-repo"){issue(number: 1){body}}}`,
154 },
155 {
156 inV: struct {
157 Repository struct {
158 Issue struct {
159 Body String
160 } `graphql:"issue(number: $issueNumber)"`
161 } `graphql:"repository(owner: $repositoryOwner, name: $repositoryName)"`
162 }{},
163 inVariables: map[string]any{
164 "repositoryOwner": String("shurcooL-test"),
165 "repositoryName": String("test-repo"),
166 "issueNumber": Int(1),
167 },
168 want: `query($issueNumber:Int!$repositoryName:String!$repositoryOwner:String!){repository(owner: $repositoryOwner, name: $repositoryName){issue(number: $issueNumber){body}}}`,
169 },
170 {
171 inV: struct {
172 Repository struct {
173 Issue struct {
174 ReactionGroups []struct {
175 Users struct {
176 Nodes []struct {
177 Login String
178 }
179 } `graphql:"users(first:10)"`
180 }
181 } `graphql:"issue(number: $issueNumber)"`
182 } `graphql:"repository(owner: $repositoryOwner, name: $repositoryName)"`
183 }{},
184 inVariables: map[string]any{
185 "repositoryOwner": String("shurcooL-test"),
186 "repositoryName": String("test-repo"),
187 "issueNumber": Int(1),
188 },
189 want: `query($issueNumber:Int!$repositoryName:String!$repositoryOwner:String!){repository(owner: $repositoryOwner, name: $repositoryName){issue(number: $issueNumber){reactionGroups{users(first:10){nodes{login}}}}}}`,
190 },
191
192 {
193 inV: func() any {
194 type actor struct {
195 Login String
196 AvatarURL URI
197 URL URI
198 }
199 type event struct {
200 Actor actor
201 CreatedAt DateTime
202 }
203 type IssueComment struct {
204 Body String
205 }
206 return struct {
207 event
208 IssueComment `graphql:"... on IssueComment"`
209 CurrentTitle String
210 PreviousTitle String
211 Label struct {
212 Name String
213 Color String
214 }
215 }{}
216 }(),
217 want: `{actor{login,avatarUrl,url},createdAt,... on IssueComment{body},currentTitle,previousTitle,label{name,color}}`,
218 },
219 {
220 inV: struct {
221 Viewer struct {
222 Login string
223 CreatedAt time.Time
224 ID any
225 DatabaseID int
226 }
227 }{},
228 want: `{viewer{login,createdAt,id,databaseId}}`,
229 },
230 }
231 for _, tc := range tests {
232 got := constructQuery(tc.inV, tc.inVariables, "")
233 if got != tc.want {
234 t.Errorf("\ngot: %q\nwant: %q\n", got, tc.want)
235 }
236 }
237 }
238
239 func TestConstructMutation(t *testing.T) {
240 tests := []struct {
241 inV any
242 inVariables map[string]any
243 want string
244 }{
245 {
246 inV: struct {
247 AddReaction struct {
248 Subject struct {
249 ReactionGroups []struct {
250 Users struct {
251 TotalCount Int
252 }
253 }
254 }
255 } `graphql:"addReaction(input:$input)"`
256 }{},
257 inVariables: map[string]any{
258 "input": AddReactionInput{
259 SubjectID: "MDU6SXNzdWUyMzE1MjcyNzk=",
260 Content: ReactionContentThumbsUp,
261 },
262 },
263 want: `mutation($input:AddReactionInput!){addReaction(input:$input){subject{reactionGroups{users{totalCount}}}}}`,
264 },
265 }
266 for _, tc := range tests {
267 got := constructMutation(tc.inV, tc.inVariables, "")
268 if got != tc.want {
269 t.Errorf("\ngot: %q\nwant: %q\n", got, tc.want)
270 }
271 }
272 }
273
274 func TestQueryArguments(t *testing.T) {
275 tests := []struct {
276 in map[string]any
277 want string
278 }{
279 {
280 in: map[string]any{"a": Int(123), "b": NewBoolean(true)},
281 want: "$a:Int!$b:Boolean",
282 },
283 {
284 in: map[string]any{
285 "required": []IssueState{IssueStateOpen, IssueStateClosed},
286 "optional": &[]IssueState{IssueStateOpen, IssueStateClosed},
287 },
288 want: "$optional:[IssueState!]$required:[IssueState!]!",
289 },
290 {
291 in: map[string]any{
292 "required": []IssueState(nil),
293 "optional": (*[]IssueState)(nil),
294 },
295 want: "$optional:[IssueState!]$required:[IssueState!]!",
296 },
297 {
298 in: map[string]any{
299 "required": [...]IssueState{IssueStateOpen, IssueStateClosed},
300 "optional": &[...]IssueState{IssueStateOpen, IssueStateClosed},
301 },
302 want: "$optional:[IssueState!]$required:[IssueState!]!",
303 },
304 {
305 in: map[string]any{"id": ID("someID")},
306 want: "$id:ID!",
307 },
308 {
309 in: map[string]any{"ids": []ID{"someID", "anotherID"}},
310 want: `$ids:[ID!]!`,
311 },
312 {
313 in: map[string]any{"ids": &[]ID{"someID", "anotherID"}},
314 want: `$ids:[ID!]`,
315 },
316 }
317 for i, tc := range tests {
318 got := queryArguments(tc.in)
319 if got != tc.want {
320 t.Errorf("test case %d:\n got: %q\nwant: %q", i, got, tc.want)
321 }
322 }
323 }
324
325
326 type (
327
328 DateTime struct{ time.Time }
329
330
331 URI struct{ *url.URL }
332 )
333
334 func (u *URI) UnmarshalJSON(data []byte) error { panic("mock implementation") }
335
336
337 type IssueState string
338
339
340 const (
341 IssueStateOpen IssueState = "OPEN"
342 IssueStateClosed IssueState = "CLOSED"
343 )
344
345
346 type ReactionContent string
347
348
349 const (
350 ReactionContentThumbsUp ReactionContent = "THUMBS_UP"
351 ReactionContentThumbsDown ReactionContent = "THUMBS_DOWN"
352 ReactionContentLaugh ReactionContent = "LAUGH"
353 ReactionContentHooray ReactionContent = "HOORAY"
354 ReactionContentConfused ReactionContent = "CONFUSED"
355 ReactionContentHeart ReactionContent = "HEART"
356 )
357
358
359 type AddReactionInput struct {
360
361 SubjectID ID `json:"subjectId"`
362
363 Content ReactionContent `json:"content"`
364
365
366 ClientMutationID *String `json:"clientMutationId,omitempty"`
367 }
368
View as plain text