aboutsummaryrefslogblamecommitdiffstats
path: root/bridge/github/import_integration_test.go
blob: 3349f3f5e3b5aa897ab2da4aea7fa9abf7ef71f0 (plain) (tree)































































































































































































































































































































































































                                                                                                                                             
package github

import (
	"context"
	"net/url"
	"testing"
	"time"

	"github.com/MichaelMure/git-bug/bridge/github/mocks"
	"github.com/MichaelMure/git-bug/bug"
	"github.com/MichaelMure/git-bug/cache"
	"github.com/MichaelMure/git-bug/repository"
	"github.com/MichaelMure/git-bug/util/interrupt"
	"github.com/pkg/errors"
	"github.com/shurcooL/githubv4"
	m "github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
)

// using testify/mock and mockery

var userName = githubv4.String("marcus")
var userEmail = githubv4.String("marcus@rom.com")
var unedited = githubv4.String("unedited")
var edited = githubv4.String("edited")

func TestGithubImporterIntegration(t *testing.T) {
	// mock
	clientMock := &mocks.Client{}
	setupExpectations(t, clientMock)
	importer := githubImporter{}
	importer.client = &rateLimitHandlerClient{sc: clientMock}

	// arrange
	repo := repository.CreateGoGitTestRepo(false)
	defer repository.CleanupTestRepos(repo)
	backend, err := cache.NewRepoCache(repo)
	require.NoError(t, err)
	defer backend.Close()
	interrupt.RegisterCleaner(backend.Close)
	require.NoError(t, err)

	// act
	events, err := importer.ImportAll(context.Background(), backend, time.Time{})

	// assert
	require.NoError(t, err)
	for e := range events {
		require.NoError(t, e.Err)
	}
	require.Len(t, backend.AllBugsIds(), 5)
	require.Len(t, backend.AllIdentityIds(), 2)

	b1, err := backend.ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/1")
	require.NoError(t, err)
	ops1 := b1.Snapshot().Operations
	require.Equal(t, "marcus", ops1[0].Author().Name())
	require.Equal(t, "title 1", ops1[0].(*bug.CreateOperation).Title)
	require.Equal(t, "body text 1", ops1[0].(*bug.CreateOperation).Message)

	b3, err := backend.ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/3")
	require.NoError(t, err)
	ops3 := b3.Snapshot().Operations
	require.Equal(t, "issue 3 comment 1", ops3[1].(*bug.AddCommentOperation).Message)
	require.Equal(t, "issue 3 comment 2", ops3[2].(*bug.AddCommentOperation).Message)
	require.Equal(t, []bug.Label{"bug"}, ops3[3].(*bug.LabelChangeOperation).Added)
	require.Equal(t, "title 3, edit 1", ops3[4].(*bug.SetTitleOperation).Title)

	b4, err := backend.ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/4")
	require.NoError(t, err)
	ops4 := b4.Snapshot().Operations
	require.Equal(t, "edited", ops4[1].(*bug.EditCommentOperation).Message)

}

func setupExpectations(t *testing.T, mock *mocks.Client) {
	rateLimitingError(mock)
	expectIssueQuery1(mock)
	expectIssueQuery2(mock)
	expectIssueQuery3(mock)
	expectUserQuery(t, mock)
}

func rateLimitingError(mock *mocks.Client) {
	mock.On("Query", m.Anything, m.AnythingOfType("*github.issueQuery"), m.Anything).Return(errors.New("API rate limit exceeded")).Once()
	mock.On("Query", m.Anything, m.AnythingOfType("*github.rateLimitQuery"), m.Anything).Return(nil).Run(
		func(args m.Arguments) {
			retVal := args.Get(1).(*rateLimitQuery)
			retVal.RateLimit.ResetAt.Time = time.Now().Add(time.Millisecond * 200)
		},
	).Once()
}

func expectIssueQuery1(mock *mocks.Client) {
	mock.On("Query", m.Anything, m.AnythingOfType("*github.issueQuery"), m.Anything).Return(nil).Run(
		func(args m.Arguments) {
			retVal := args.Get(1).(*issueQuery)
			retVal.Repository.Issues.Nodes = []issueNode{
				{
					issue: issue{
						authorEvent: authorEvent{
							Id: 1,
							Author: &actor{
								Typename: "User",
								User: userActor{
									Name:  &userName,
									Email: userEmail,
								},
							},
						},
						Title:  "title 1",
						Number: 1,
						Body:   "body text 1",
						Url: githubv4.URI{
							URL: &url.URL{
								Scheme: "https",
								Host:   "github.com",
								Path:   "marcus/to-himself/issues/1",
							},
						},
					},
					UserContentEdits: userContentEditConnection{},
					TimelineItems:    timelineItemsConnection{},
				},
				{
					issue: issue{
						authorEvent: authorEvent{
							Id: 2,
							Author: &actor{
								Typename: "User",
								User: userActor{
									Name:  &userName,
									Email: userEmail,
								},
							},
						},
						Title:  "title 2",
						Number: 2,
						Body:   "body text 2",
						Url: githubv4.URI{
							URL: &url.URL{
								Scheme: "https",
								Host:   "github.com",
								Path:   "marcus/to-himself/issues/2",
							},
						},
					},
					UserContentEdits: userContentEditConnection{},
					TimelineItems:    timelineItemsConnection{},
				},
			}
			retVal.Repository.Issues.PageInfo = pageInfo{
				EndCursor:   "end-cursor-1",
				HasNextPage: true,
			}
		},
	).Once()
}

func expectIssueQuery2(mock *mocks.Client) {
	mock.On("Query", m.Anything, m.AnythingOfType("*github.issueQuery"), m.Anything).Return(nil).Run(
		func(args m.Arguments) {
			retVal := args.Get(1).(*issueQuery)
			retVal.Repository.Issues.Nodes = []issueNode{
				{
					issue: issue{
						authorEvent: authorEvent{
							Id: 3,
							Author: &actor{
								Typename: "User",
								User: userActor{
									Name:  &userName,
									Email: userEmail,
								},
							},
						},
						Title:  "title 3",
						Number: 3,
						Body:   "body text 3",
						Url: githubv4.URI{
							URL: &url.URL{
								Scheme: "https",
								Host:   "github.com",
								Path:   "marcus/to-himself/issues/3",
							},
						},
					},
					UserContentEdits: userContentEditConnection{},
					TimelineItems: timelineItemsConnection{
						Nodes: []timelineItem{
							{
								Typename: "IssueComment",
								IssueComment: issueComment{
									authorEvent: authorEvent{
										Id: 301,
										Author: &actor{
											Typename: "User",
											User: userActor{
												Name:  &userName,
												Email: userEmail,
											},
										},
									},
									Body: "issue 3 comment 1",
									Url: githubv4.URI{
										URL: &url.URL{
											Scheme: "https",
											Host:   "github.com",
											Path:   "marcus/to-himself/issues/3#issuecomment-1",
										},
									},
									UserContentEdits: userContentEditConnection{},
								},
							},
							{
								Typename: "IssueComment",
								IssueComment: issueComment{
									authorEvent: authorEvent{
										Id: 302,
										Author: &actor{
											Typename: "User",
											User: userActor{
												Name:  &userName,
												Email: userEmail,
											},
										},
									},
									Body: "issue 3 comment 2",
									Url: githubv4.URI{
										URL: &url.URL{
											Scheme: "https",
											Host:   "github.com",
											Path:   "marcus/to-himself/issues/3#issuecomment-2",
										},
									},
									UserContentEdits: userContentEditConnection{},
								},
							},
							{
								Typename: "LabeledEvent",
								LabeledEvent: labeledEvent{
									actorEvent: actorEvent{
										Id: 303,
										Actor: &actor{
											Typename: "User",
											User: userActor{
												Name:  &userName,
												Email: userEmail,
											},
										},
									},
									Label: label{
										Name: "bug",
									},
								},
							},
							{
								Typename: "RenamedTitleEvent",
								RenamedTitleEvent: renamedTitleEvent{
									actorEvent: actorEvent{
										Id: 304,
										Actor: &actor{
											Typename: "User",
											User: userActor{
												Name:  &userName,
												Email: userEmail,
											},
										},
									},
									CurrentTitle: "title 3, edit 1",
								},
							},
						},
						PageInfo: pageInfo{},
					},
				},
				{
					issue: issue{
						authorEvent: authorEvent{
							Id: 4,
							Author: &actor{
								Typename: "User",
								User: userActor{
									Name:  &userName,
									Email: userEmail,
								},
							},
						},
						Title:  "title 4",
						Number: 4,
						Body:   unedited,
						Url: githubv4.URI{
							URL: &url.URL{
								Scheme: "https",
								Host:   "github.com",
								Path:   "marcus/to-himself/issues/4",
							},
						},
					},
					UserContentEdits: userContentEditConnection{
						Nodes: []userContentEdit{
							// Github is weird: here the order is reversed chronological
							{
								Id: 402,
								Editor: &actor{
									Typename: "User",
									User: userActor{
										Name:  &userName,
										Email: userEmail,
									},
								},
								Diff: &edited,
							},
							{
								Id: 401,
								Editor: &actor{
									Typename: "User",
									User: userActor{
										Name:  &userName,
										Email: userEmail,
									},
								},
								// Github is weird: whenever an issue has issue edits, then the first item
								// (issue edit) holds the original (unedited) content and the second item
								// (issue edit) holds the (first) edited content.
								Diff: &unedited,
							},
						},
						PageInfo: pageInfo{},
					},
					TimelineItems: timelineItemsConnection{},
				},
			}
			retVal.Repository.Issues.PageInfo = pageInfo{
				EndCursor:   "end-cursor-2",
				HasNextPage: true,
			}
		},
	).Once()
}

func expectIssueQuery3(mock *mocks.Client) {
	mock.On("Query", m.Anything, m.AnythingOfType("*github.issueQuery"), m.Anything).Return(nil).Run(
		func(args m.Arguments) {
			retVal := args.Get(1).(*issueQuery)
			retVal.Repository.Issues.Nodes = []issueNode{
				{
					issue: issue{
						authorEvent: authorEvent{
							Author: nil,
						},
						Title:  "title 5",
						Number: 5,
						Body:   "body text 5",
						Url: githubv4.URI{
							URL: &url.URL{
								Scheme: "https",
								Host:   "github.com",
								Path:   "marcus/to-himself/issues/5",
							},
						},
					},
					UserContentEdits: userContentEditConnection{},
					TimelineItems:    timelineItemsConnection{},
				},
			}
			retVal.Repository.Issues.PageInfo = pageInfo{}
		},
	).Once()
}

func expectUserQuery(t *testing.T, mock *mocks.Client) {
	mock.On("Query", m.Anything, m.AnythingOfType("*github.userQuery"), m.AnythingOfType("map[string]interface {}")).Return(nil).Run(
		func(args m.Arguments) {
			vars := args.Get(2).(map[string]interface{})
			ghost := githubv4.String("ghost")
			require.Equal(t, ghost, vars["login"])

			retVal := args.Get(1).(*userQuery)
			retVal.User.Name = &ghost
			retVal.User.Login = "ghost-login"
		},
	).Once()
}