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, stderr := cache.NewTestRepoCache(t, repo)
 38	defer backend.Close()
 39	interrupt.RegisterCleaner(backend.Close)
 40
 41	// act
 42	events, err := importer.ImportAll(context.Background(), backend, time.Time{})
 43
 44	// assert
 45	require.NoError(t, err)
 46	for e := range events {
 47		require.NoError(t, e.Err)
 48	}
 49	require.Len(t, backend.AllBugsIds(), 5)
 50	require.Len(t, backend.AllIdentityIds(), 2)
 51
 52	b1, err := backend.ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/1")
 53	require.NoError(t, err)
 54	ops1 := b1.Snapshot().Operations
 55	require.Equal(t, "marcus", ops1[0].Author().Name())
 56	require.Equal(t, "title 1", ops1[0].(*bug.CreateOperation).Title)
 57	require.Equal(t, "body text 1", ops1[0].(*bug.CreateOperation).Message)
 58
 59	b3, err := backend.ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/3")
 60	require.NoError(t, err)
 61	ops3 := b3.Snapshot().Operations
 62	require.Equal(t, "issue 3 comment 1", ops3[1].(*bug.AddCommentOperation).Message)
 63	require.Equal(t, "issue 3 comment 2", ops3[2].(*bug.AddCommentOperation).Message)
 64	require.Equal(t, []bug.Label{"bug"}, ops3[3].(*bug.LabelChangeOperation).Added)
 65	require.Equal(t, "title 3, edit 1", ops3[4].(*bug.SetTitleOperation).Title)
 66
 67	b4, err := backend.ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/4")
 68	require.NoError(t, err)
 69	ops4 := b4.Snapshot().Operations
 70	require.Equal(t, "edited", ops4[1].(*bug.EditCommentOperation).Message)
 71
 72	require.Empty(t, stderr.String())
 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}