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/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}