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