2021-09-14 23:24:34 +03:00
|
|
|
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"
|
2022-08-19 00:34:05 +03:00
|
|
|
|
|
|
|
"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"
|
2021-09-14 23:24:34 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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
|
2022-06-16 16:02:52 +03:00
|
|
|
repo := repository.CreateGoGitTestRepo(t, false)
|
2022-12-23 01:19:31 +03:00
|
|
|
backend, err := cache.NewRepoCacheNoEvents(repo)
|
2021-09-14 23:24:34 +03:00
|
|
|
require.NoError(t, err)
|
2022-12-23 01:19:31 +03:00
|
|
|
|
2021-09-14 23:24:34 +03:00
|
|
|
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)
|
|
|
|
}
|
2022-12-21 23:54:36 +03:00
|
|
|
require.Len(t, backend.Bugs().AllIds(), 5)
|
|
|
|
require.Len(t, backend.Identities().AllIds(), 2)
|
2021-09-14 23:24:34 +03:00
|
|
|
|
2022-12-21 23:54:36 +03:00
|
|
|
b1, err := backend.Bugs().ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/1")
|
2021-09-14 23:24:34 +03:00
|
|
|
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)
|
|
|
|
|
2022-12-21 23:54:36 +03:00
|
|
|
b3, err := backend.Bugs().ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/3")
|
2021-09-14 23:24:34 +03:00
|
|
|
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)
|
|
|
|
|
2022-12-21 23:54:36 +03:00
|
|
|
b4, err := backend.Bugs().ResolveBugCreateMetadata(metaKeyGithubUrl, "https://github.com/marcus/to-himself/issues/4")
|
2021-09-14 23:24:34 +03:00
|
|
|
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()
|
|
|
|
}
|