1package github
2
3import (
4 "context"
5 "net/url"
6 "testing"
7 "time"
8
9 "github.com/pkg/errors"
10 "github.com/shurcooL/githubv4"
11 m "github.com/stretchr/testify/mock"
12 "github.com/stretchr/testify/require"
13
14 "github.com/MichaelMure/git-bug/bridge/github/mocks"
15 "github.com/MichaelMure/git-bug/cache"
16 "github.com/MichaelMure/git-bug/entities/bug"
17 "github.com/MichaelMure/git-bug/repository"
18 "github.com/MichaelMure/git-bug/util/interrupt"
19)
20
21// using testify/mock and mockery
22
23var userName = githubv4.String("marcus")
24var userEmail = githubv4.String("marcus@rom.com")
25var unedited = githubv4.String("unedited")
26var edited = githubv4.String("edited")
27
28func TestGithubImporterIntegration(t *testing.T) {
29 // mock
30 clientMock := &mocks.Client{}
31 setupExpectations(t, clientMock)
32 importer := githubImporter{}
33 importer.client = &rateLimitHandlerClient{sc: clientMock}
34
35 // arrange
36 repo := repository.CreateGoGitTestRepo(t, false)
37 backend, err := cache.NewRepoCacheNoEvents(repo)
38 require.NoError(t, err)
39
40 defer backend.Close()
41 interrupt.RegisterCleaner(backend.Close)
42 require.NoError(t, err)
43
44 // act
45 events, err := importer.ImportAll(context.Background(), backend, time.Time{})
46
47 // assert
48 require.NoError(t, err)
49 for e := range events {
50 require.NoError(t, e.Err)
51 }
52 require.Len(t, backend.Bugs().AllIds(), 5)
53 require.Len(t, backend.Identities().AllIds(), 2)
54
55 b1, err := backend.Bugs().ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/1")
56 require.NoError(t, err)
57 ops1 := b1.Compile().Operations
58 require.Equal(t, "marcus", ops1[0].Author().Name())
59 require.Equal(t, "title 1", ops1[0].(*bug.CreateOperation).Title)
60 require.Equal(t, "body text 1", ops1[0].(*bug.CreateOperation).Message)
61
62 b3, err := backend.Bugs().ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/3")
63 require.NoError(t, err)
64 ops3 := b3.Compile().Operations
65 require.Equal(t, "issue 3 comment 1", ops3[1].(*bug.AddCommentOperation).Message)
66 require.Equal(t, "issue 3 comment 2", ops3[2].(*bug.AddCommentOperation).Message)
67 require.Equal(t, []bug.Label{"bug"}, ops3[3].(*bug.LabelChangeOperation).Added)
68 require.Equal(t, "title 3, edit 1", ops3[4].(*bug.SetTitleOperation).Title)
69
70 b4, err := backend.Bugs().ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/4")
71 require.NoError(t, err)
72 ops4 := b4.Compile().Operations
73 require.Equal(t, "edited", ops4[1].(*bug.EditCommentOperation).Message)
74
75}
76
77func setupExpectations(t *testing.T, mock *mocks.Client) {
78 rateLimitingError(mock)
79 expectIssueQuery1(mock)
80 expectIssueQuery2(mock)
81 expectIssueQuery3(mock)
82 expectUserQuery(t, mock)
83}
84
85func rateLimitingError(mock *mocks.Client) {
86 mock.On("Query", m.Anything, m.AnythingOfType("*github.issueQuery"), m.Anything).Return(errors.New("API rate limit exceeded")).Once()
87 mock.On("Query", m.Anything, m.AnythingOfType("*github.rateLimitQuery"), m.Anything).Return(nil).Run(
88 func(args m.Arguments) {
89 retVal := args.Get(1).(*rateLimitQuery)
90 retVal.RateLimit.ResetAt.Time = time.Now().Add(time.Millisecond * 200)
91 },
92 ).Once()
93}
94
95func expectIssueQuery1(mock *mocks.Client) {
96 mock.On("Query", m.Anything, m.AnythingOfType("*github.issueQuery"), m.Anything).Return(nil).Run(
97 func(args m.Arguments) {
98 retVal := args.Get(1).(*issueQuery)
99 retVal.Repository.Issues.Nodes = []issueNode{
100 {
101 issue: issue{
102 authorEvent: authorEvent{
103 Id: 1,
104 Author: &actor{
105 Typename: "User",
106 User: userActor{
107 Name: &userName,
108 Email: userEmail,
109 },
110 },
111 },
112 Title: "title 1",
113 Number: 1,
114 Body: "body text 1",
115 Url: githubv4.URI{
116 URL: &url.URL{
117 Scheme: "https",
118 Host: "github.com",
119 Path: "marcus/to-himself/issues/1",
120 },
121 },
122 },
123 UserContentEdits: userContentEditConnection{},
124 TimelineItems: timelineItemsConnection{},
125 },
126 {
127 issue: issue{
128 authorEvent: authorEvent{
129 Id: 2,
130 Author: &actor{
131 Typename: "User",
132 User: userActor{
133 Name: &userName,
134 Email: userEmail,
135 },
136 },
137 },
138 Title: "title 2",
139 Number: 2,
140 Body: "body text 2",
141 Url: githubv4.URI{
142 URL: &url.URL{
143 Scheme: "https",
144 Host: "github.com",
145 Path: "marcus/to-himself/issues/2",
146 },
147 },
148 },
149 UserContentEdits: userContentEditConnection{},
150 TimelineItems: timelineItemsConnection{},
151 },
152 }
153 retVal.Repository.Issues.PageInfo = pageInfo{
154 EndCursor: "end-cursor-1",
155 HasNextPage: true,
156 }
157 },
158 ).Once()
159}
160
161func expectIssueQuery2(mock *mocks.Client) {
162 mock.On("Query", m.Anything, m.AnythingOfType("*github.issueQuery"), m.Anything).Return(nil).Run(
163 func(args m.Arguments) {
164 retVal := args.Get(1).(*issueQuery)
165 retVal.Repository.Issues.Nodes = []issueNode{
166 {
167 issue: issue{
168 authorEvent: authorEvent{
169 Id: 3,
170 Author: &actor{
171 Typename: "User",
172 User: userActor{
173 Name: &userName,
174 Email: userEmail,
175 },
176 },
177 },
178 Title: "title 3",
179 Number: 3,
180 Body: "body text 3",
181 Url: githubv4.URI{
182 URL: &url.URL{
183 Scheme: "https",
184 Host: "github.com",
185 Path: "marcus/to-himself/issues/3",
186 },
187 },
188 },
189 UserContentEdits: userContentEditConnection{},
190 TimelineItems: timelineItemsConnection{
191 Nodes: []timelineItem{
192 {
193 Typename: "IssueComment",
194 IssueComment: issueComment{
195 authorEvent: authorEvent{
196 Id: 301,
197 Author: &actor{
198 Typename: "User",
199 User: userActor{
200 Name: &userName,
201 Email: userEmail,
202 },
203 },
204 },
205 Body: "issue 3 comment 1",
206 Url: githubv4.URI{
207 URL: &url.URL{
208 Scheme: "https",
209 Host: "github.com",
210 Path: "marcus/to-himself/issues/3#issuecomment-1",
211 },
212 },
213 UserContentEdits: userContentEditConnection{},
214 },
215 },
216 {
217 Typename: "IssueComment",
218 IssueComment: issueComment{
219 authorEvent: authorEvent{
220 Id: 302,
221 Author: &actor{
222 Typename: "User",
223 User: userActor{
224 Name: &userName,
225 Email: userEmail,
226 },
227 },
228 },
229 Body: "issue 3 comment 2",
230 Url: githubv4.URI{
231 URL: &url.URL{
232 Scheme: "https",
233 Host: "github.com",
234 Path: "marcus/to-himself/issues/3#issuecomment-2",
235 },
236 },
237 UserContentEdits: userContentEditConnection{},
238 },
239 },
240 {
241 Typename: "LabeledEvent",
242 LabeledEvent: labeledEvent{
243 actorEvent: actorEvent{
244 Id: 303,
245 Actor: &actor{
246 Typename: "User",
247 User: userActor{
248 Name: &userName,
249 Email: userEmail,
250 },
251 },
252 },
253 Label: label{
254 Name: "bug",
255 },
256 },
257 },
258 {
259 Typename: "RenamedTitleEvent",
260 RenamedTitleEvent: renamedTitleEvent{
261 actorEvent: actorEvent{
262 Id: 304,
263 Actor: &actor{
264 Typename: "User",
265 User: userActor{
266 Name: &userName,
267 Email: userEmail,
268 },
269 },
270 },
271 CurrentTitle: "title 3, edit 1",
272 },
273 },
274 },
275 PageInfo: pageInfo{},
276 },
277 },
278 {
279 issue: issue{
280 authorEvent: authorEvent{
281 Id: 4,
282 Author: &actor{
283 Typename: "User",
284 User: userActor{
285 Name: &userName,
286 Email: userEmail,
287 },
288 },
289 },
290 Title: "title 4",
291 Number: 4,
292 Body: unedited,
293 Url: githubv4.URI{
294 URL: &url.URL{
295 Scheme: "https",
296 Host: "github.com",
297 Path: "marcus/to-himself/issues/4",
298 },
299 },
300 },
301 UserContentEdits: userContentEditConnection{
302 Nodes: []userContentEdit{
303 // Github is weird: here the order is reversed chronological
304 {
305 Id: 402,
306 Editor: &actor{
307 Typename: "User",
308 User: userActor{
309 Name: &userName,
310 Email: userEmail,
311 },
312 },
313 Diff: &edited,
314 },
315 {
316 Id: 401,
317 Editor: &actor{
318 Typename: "User",
319 User: userActor{
320 Name: &userName,
321 Email: userEmail,
322 },
323 },
324 // Github is weird: whenever an issue has issue edits, then the first item
325 // (issue edit) holds the original (unedited) content and the second item
326 // (issue edit) holds the (first) edited content.
327 Diff: &unedited,
328 },
329 },
330 PageInfo: pageInfo{},
331 },
332 TimelineItems: timelineItemsConnection{},
333 },
334 }
335 retVal.Repository.Issues.PageInfo = pageInfo{
336 EndCursor: "end-cursor-2",
337 HasNextPage: true,
338 }
339 },
340 ).Once()
341}
342
343func expectIssueQuery3(mock *mocks.Client) {
344 mock.On("Query", m.Anything, m.AnythingOfType("*github.issueQuery"), m.Anything).Return(nil).Run(
345 func(args m.Arguments) {
346 retVal := args.Get(1).(*issueQuery)
347 retVal.Repository.Issues.Nodes = []issueNode{
348 {
349 issue: issue{
350 authorEvent: authorEvent{
351 Author: nil,
352 },
353 Title: "title 5",
354 Number: 5,
355 Body: "body text 5",
356 Url: githubv4.URI{
357 URL: &url.URL{
358 Scheme: "https",
359 Host: "github.com",
360 Path: "marcus/to-himself/issues/5",
361 },
362 },
363 },
364 UserContentEdits: userContentEditConnection{},
365 TimelineItems: timelineItemsConnection{},
366 },
367 }
368 retVal.Repository.Issues.PageInfo = pageInfo{}
369 },
370 ).Once()
371}
372
373func expectUserQuery(t *testing.T, mock *mocks.Client) {
374 mock.On("Query", m.Anything, m.AnythingOfType("*github.userQuery"), m.AnythingOfType("map[string]interface {}")).Return(nil).Run(
375 func(args m.Arguments) {
376 vars := args.Get(2).(map[string]interface{})
377 ghost := githubv4.String("ghost")
378 require.Equal(t, ghost, vars["login"])
379
380 retVal := args.Get(1).(*userQuery)
381 retVal.User.Name = &ghost
382 retVal.User.Login = "ghost-login"
383 },
384 ).Once()
385}