import_integration_test.go

  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}