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