package github
import (
"context"
"net/url"
"testing"
"time"
"github.com/pkg/errors"
"github.com/shurcooL/githubv4"
m "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/MichaelMure/git-bug/bridge/github/mocks"
"github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/entities/bug"
"github.com/MichaelMure/git-bug/repository"
"github.com/MichaelMure/git-bug/util/interrupt"
)
// 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(t, false)
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()
}