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