1package graphql
2
3import (
4 "testing"
5
6 "github.com/99designs/gqlgen/client"
7 "github.com/stretchr/testify/assert"
8 "github.com/stretchr/testify/require"
9
10 "github.com/git-bug/git-bug/api/graphql/models"
11 "github.com/git-bug/git-bug/cache"
12 "github.com/git-bug/git-bug/misc/random_bugs"
13 "github.com/git-bug/git-bug/repository"
14)
15
16func TestQueries(t *testing.T) {
17 repo := repository.CreateGoGitTestRepo(t, false)
18
19 random_bugs.FillRepoWithSeed(repo, 10, 42)
20
21 mrc := cache.NewMultiRepoCache()
22 _, events := mrc.RegisterDefaultRepository(repo)
23 for event := range events {
24 require.NoError(t, event.Err)
25 }
26
27 handler := NewHandler(mrc, ServerConfig{AuthMode: "local"}, nil)
28
29 c := client.New(handler)
30
31 query := `
32 query {
33 repository {
34 allBugs(first: 2) {
35 pageInfo {
36 endCursor
37 hasNextPage
38 startCursor
39 hasPreviousPage
40 }
41 nodes{
42 author {
43 name
44 email
45 avatarUrl
46 }
47
48 createdAt
49 humanId
50 id
51 lastEdit
52 status
53 title
54
55 actors(first: 10) {
56 pageInfo {
57 endCursor
58 hasNextPage
59 startCursor
60 hasPreviousPage
61 }
62 nodes {
63 id
64 humanId
65 name
66 displayName
67 }
68 }
69
70 participants(first: 10) {
71 pageInfo {
72 endCursor
73 hasNextPage
74 startCursor
75 hasPreviousPage
76 }
77 nodes {
78 id
79 humanId
80 name
81 displayName
82 }
83 }
84
85 comments(first: 2) {
86 pageInfo {
87 endCursor
88 hasNextPage
89 startCursor
90 hasPreviousPage
91 }
92 nodes {
93 files
94 message
95 }
96 }
97
98 operations(first: 20) {
99 pageInfo {
100 endCursor
101 hasNextPage
102 startCursor
103 hasPreviousPage
104 }
105 nodes {
106 author {
107 name
108 email
109 avatarUrl
110 }
111 date
112 ... on BugCreateOperation {
113 title
114 message
115 files
116 }
117 ... on BugSetTitleOperation {
118 title
119 was
120 }
121 ... on BugAddCommentOperation {
122 files
123 message
124 }
125 ... on BugSetStatusOperation {
126 status
127 }
128 ... on BugLabelChangeOperation {
129 added {
130 name
131 color {
132 R
133 G
134 B
135 }
136 }
137 removed {
138 name
139 color {
140 R
141 G
142 B
143 }
144 }
145 }
146 }
147 }
148 }
149 }
150 }
151 }`
152
153 type Identity struct {
154 Id string `json:"id"`
155 HumanId string `json:"humanId"`
156 Name string `json:"name"`
157 Email string `json:"email"`
158 AvatarUrl string `json:"avatarUrl"`
159 DisplayName string `json:"displayName"`
160 }
161
162 type Label struct {
163 Name string
164 Color struct {
165 R, G, B int
166 }
167 }
168
169 var resp struct {
170 Repository struct {
171 AllBugs struct {
172 PageInfo models.PageInfo
173 Nodes []struct {
174 Author Identity
175 CreatedAt string `json:"createdAt"`
176 HumanId string `json:"humanId"`
177 Id string
178 LastEdit string `json:"lastEdit"`
179 Status string
180 Title string
181
182 Actors struct {
183 PageInfo models.PageInfo
184 Nodes []Identity
185 }
186
187 Participants struct {
188 PageInfo models.PageInfo
189 Nodes []Identity
190 }
191
192 Comments struct {
193 PageInfo models.PageInfo
194 Nodes []struct {
195 Files []string
196 Message string
197 }
198 }
199
200 Operations struct {
201 PageInfo models.PageInfo
202 Nodes []struct {
203 Author Identity
204 Date string
205 Title string
206 Files []string
207 Message string
208 Was string
209 Status string
210 Added []Label
211 Removed []Label
212 }
213 }
214 }
215 }
216 }
217 }
218
219 err := c.Post(query, &resp)
220 assert.NoError(t, err)
221}
222
223// TestGitBrowseQueries exercises the git-browsing GraphQL fields (commit, blob,
224// tree, commits, lastCommits) against a synthetic fixture repo with the same
225// commit graph used by RepoBrowseTest:
226//
227// c1 ββ c2 ββ c3 refs/heads/main
228// βββββββββ refs/heads/feature
229// c1 βββ refs/tags/v1.0
230func TestGitBrowseQueries(t *testing.T) {
231 repo := repository.CreateGoGitTestRepo(t, false)
232 require.NoError(t, repo.LocalConfig().StoreString("init.defaultBranch", "main"))
233
234 // ββ build fixture βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
235
236 readmeV1 := []byte("# Hello\n")
237 readmeV3 := []byte("# Hello\n\n## Updated\n")
238 mainV1 := []byte("package main\n")
239 mainV2 := []byte("package main\n\n// updated\n")
240 libV1 := []byte("package lib\n")
241 utilV1 := []byte("package util\n")
242
243 hReadmeV1, err := repo.StoreData(readmeV1)
244 require.NoError(t, err)
245 hReadmeV3, err := repo.StoreData(readmeV3)
246 require.NoError(t, err)
247 hMainV1, err := repo.StoreData(mainV1)
248 require.NoError(t, err)
249 hMainV2, err := repo.StoreData(mainV2)
250 require.NoError(t, err)
251 hLibV1, err := repo.StoreData(libV1)
252 require.NoError(t, err)
253 hUtilV1, err := repo.StoreData(utilV1)
254 require.NoError(t, err)
255
256 srcTreeV1, err := repo.StoreTree([]repository.TreeEntry{
257 {ObjectType: repository.Blob, Hash: hLibV1, Name: "lib.go"},
258 })
259 require.NoError(t, err)
260 rootTreeV1, err := repo.StoreTree([]repository.TreeEntry{
261 {ObjectType: repository.Blob, Hash: hReadmeV1, Name: "README.md"},
262 {ObjectType: repository.Blob, Hash: hMainV1, Name: "main.go"},
263 {ObjectType: repository.Tree, Hash: srcTreeV1, Name: "src"},
264 })
265 require.NoError(t, err)
266
267 srcTreeV2, err := repo.StoreTree([]repository.TreeEntry{
268 {ObjectType: repository.Blob, Hash: hLibV1, Name: "lib.go"},
269 {ObjectType: repository.Blob, Hash: hUtilV1, Name: "util.go"},
270 })
271 require.NoError(t, err)
272 rootTreeV2, err := repo.StoreTree([]repository.TreeEntry{
273 {ObjectType: repository.Blob, Hash: hReadmeV1, Name: "README.md"},
274 {ObjectType: repository.Blob, Hash: hMainV2, Name: "main.go"},
275 {ObjectType: repository.Tree, Hash: srcTreeV2, Name: "src"},
276 })
277 require.NoError(t, err)
278
279 rootTreeV3, err := repo.StoreTree([]repository.TreeEntry{
280 {ObjectType: repository.Blob, Hash: hReadmeV3, Name: "README.md"},
281 {ObjectType: repository.Blob, Hash: hMainV2, Name: "main.go"},
282 {ObjectType: repository.Tree, Hash: srcTreeV2, Name: "src"},
283 })
284 require.NoError(t, err)
285
286 c1, err := repo.StoreCommit(rootTreeV1)
287 require.NoError(t, err)
288 c2, err := repo.StoreCommit(rootTreeV2, c1)
289 require.NoError(t, err)
290 c3, err := repo.StoreCommit(rootTreeV3, c2)
291 require.NoError(t, err)
292
293 require.NoError(t, repo.UpdateRef("refs/heads/main", c3))
294 require.NoError(t, repo.UpdateRef("refs/heads/feature", c2))
295 require.NoError(t, repo.UpdateRef("refs/tags/v1.0", c1))
296
297 // ββ set up GraphQL handler βββββββββββββββββββββββββββββββββββββββββββββββββ
298
299 mrc := cache.NewMultiRepoCache()
300 _, events := mrc.RegisterDefaultRepository(repo)
301 for event := range events {
302 require.NoError(t, event.Err)
303 }
304 c := client.New(NewHandler(mrc, ServerConfig{AuthMode: "local"}, nil))
305
306 // ββ commit ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
307
308 t.Run("commit", func(t *testing.T) {
309 var resp struct {
310 Repository struct {
311 Commit struct {
312 Hash string
313 Parents []string
314 }
315 }
316 }
317 require.NoError(t, c.Post(`query($hash: String!) {
318 repository { commit(hash: $hash) { hash parents } }
319 }`, &resp, client.Var("hash", string(c3))))
320 got := resp.Repository.Commit
321 require.Equal(t, string(c3), got.Hash)
322 require.Equal(t, []string{string(c2)}, got.Parents)
323 })
324
325 t.Run("commit_not_found", func(t *testing.T) {
326 var resp struct {
327 Repository struct {
328 Commit *struct{ Hash string }
329 }
330 }
331 require.NoError(t, c.Post(`query {
332 repository { commit(hash: "0000000000000000000000000000000000000000") { hash } }
333 }`, &resp))
334 require.Nil(t, resp.Repository.Commit)
335 })
336
337 // ββ blob ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
338
339 t.Run("blob", func(t *testing.T) {
340 var resp struct {
341 Repository struct {
342 Blob struct {
343 Hash string
344 IsBinary bool
345 Size int
346 Text *string
347 }
348 }
349 }
350 require.NoError(t, c.Post(`query {
351 repository { blob(ref: "main", path: "README.md") { hash isBinary size text } }
352 }`, &resp))
353 got := resp.Repository.Blob
354 require.Equal(t, string(hReadmeV3), got.Hash)
355 require.False(t, got.IsBinary)
356 require.Equal(t, len(readmeV3), got.Size)
357 require.NotNil(t, got.Text)
358 require.Equal(t, string(readmeV3), *got.Text)
359 })
360
361 t.Run("blob_not_found", func(t *testing.T) {
362 var resp struct {
363 Repository struct {
364 Blob *struct{ Hash string }
365 }
366 }
367 require.NoError(t, c.Post(`query {
368 repository { blob(ref: "main", path: "nonexistent.go") { hash } }
369 }`, &resp))
370 require.Nil(t, resp.Repository.Blob)
371 })
372
373 // ββ tree ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
374
375 t.Run("tree", func(t *testing.T) {
376 var resp struct {
377 Repository struct {
378 Tree []struct {
379 Name string
380 Type string `json:"type"`
381 }
382 }
383 }
384 require.NoError(t, c.Post(`query {
385 repository { tree(ref: "main", path: "") { name type } }
386 }`, &resp))
387 byName := make(map[string]string)
388 for _, e := range resp.Repository.Tree {
389 byName[e.Name] = e.Type
390 }
391 require.Equal(t, "BLOB", byName["README.md"])
392 require.Equal(t, "BLOB", byName["main.go"])
393 require.Equal(t, "TREE", byName["src"])
394 })
395
396 // ββ commits βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
397
398 t.Run("commits", func(t *testing.T) {
399 var resp struct {
400 Repository struct {
401 Commits struct {
402 TotalCount int
403 PageInfo struct{ HasNextPage bool }
404 Nodes []struct{ Hash string }
405 }
406 }
407 }
408 require.NoError(t, c.Post(`query {
409 repository {
410 commits(ref: "main", first: 2) {
411 totalCount pageInfo { hasNextPage } nodes { hash }
412 }
413 }
414 }`, &resp))
415 got := resp.Repository.Commits
416 require.Equal(t, 2, got.TotalCount)
417 require.True(t, got.PageInfo.HasNextPage)
418 require.Equal(t, string(c3), got.Nodes[0].Hash)
419 require.Equal(t, string(c2), got.Nodes[1].Hash)
420 })
421
422 // ββ lastCommits βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
423
424 t.Run("lastCommits", func(t *testing.T) {
425 var resp struct {
426 Repository struct {
427 LastCommits []struct {
428 Name string
429 Commit struct{ Hash string }
430 }
431 }
432 }
433 require.NoError(t, c.Post(`query {
434 repository {
435 lastCommits(ref: "main", names: ["README.md", "main.go"]) {
436 name commit { hash }
437 }
438 }
439 }`, &resp))
440 byName := make(map[string]string)
441 for _, lc := range resp.Repository.LastCommits {
442 byName[lc.Name] = lc.Commit.Hash
443 }
444 require.Equal(t, string(c3), byName["README.md"]) // changed in c3
445 require.Equal(t, string(c2), byName["main.go"]) // changed in c2
446 })
447}
448
449func TestBugEventsSubscription(t *testing.T) {
450 repo := repository.CreateGoGitTestRepo(t, false)
451
452 mrc := cache.NewMultiRepoCache()
453 rc, events := mrc.RegisterDefaultRepository(repo)
454 for event := range events {
455 require.NoError(t, event.Err)
456 }
457
458 h := NewHandler(mrc, ServerConfig{AuthMode: "local"}, nil)
459 c := client.New(h)
460
461 sub := c.Websocket(`subscription { bugEvents { type bug { id } } }`)
462 t.Cleanup(func() { _ = sub.Close() })
463
464 rene, err := rc.Identities().New("RenΓ© Descartes", "rene@descartes.fr")
465 require.NoError(t, err)
466 require.NoError(t, rc.SetUserIdentity(rene))
467
468 b, _, err := rc.Bugs().New("test subscription", "body")
469 require.NoError(t, err)
470
471 var resp struct {
472 BugEvents struct {
473 Type string
474 Bug struct{ Id string }
475 }
476 }
477 require.NoError(t, sub.Next(&resp))
478 assert.Equal(t, "CREATED", resp.BugEvents.Type)
479 assert.Equal(t, b.Id().String(), resp.BugEvents.Bug.Id)
480}