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