aboutsummaryrefslogtreecommitdiffstats
path: root/bridge
diff options
context:
space:
mode:
Diffstat (limited to 'bridge')
-rw-r--r--bridge/github/import_integration_test.go384
-rw-r--r--bridge/github/import_query.go56
-rw-r--r--bridge/github/mocks/Client.go44
3 files changed, 461 insertions, 23 deletions
diff --git a/bridge/github/import_integration_test.go b/bridge/github/import_integration_test.go
new file mode 100644
index 00000000..3349f3f5
--- /dev/null
+++ b/bridge/github/import_integration_test.go
@@ -0,0 +1,384 @@
+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()
+}
diff --git a/bridge/github/import_query.go b/bridge/github/import_query.go
index 1428e3fd..bbe0ec96 100644
--- a/bridge/github/import_query.go
+++ b/bridge/github/import_query.go
@@ -113,6 +113,26 @@ type userContentEdit struct {
Diff *githubv4.String
}
+type label struct {
+ Name githubv4.String
+}
+
+type labeledEvent struct {
+ actorEvent
+ Label label
+}
+
+type unlabeledEvent struct {
+ actorEvent
+ Label label
+}
+
+type renamedTitleEvent struct {
+ actorEvent
+ CurrentTitle githubv4.String
+ PreviousTitle githubv4.String
+}
+
type timelineItem struct {
Typename githubv4.String `graphql:"__typename"`
@@ -120,20 +140,8 @@ type timelineItem struct {
IssueComment issueComment `graphql:"... on IssueComment"`
// Label
- LabeledEvent struct {
- actorEvent
- Label struct {
- // Color githubv4.String
- Name githubv4.String
- }
- } `graphql:"... on LabeledEvent"`
- UnlabeledEvent struct {
- actorEvent
- Label struct {
- // Color githubv4.String
- Name githubv4.String
- }
- } `graphql:"... on UnlabeledEvent"`
+ LabeledEvent labeledEvent `graphql:"... on LabeledEvent"`
+ UnlabeledEvent unlabeledEvent `graphql:"... on UnlabeledEvent"`
// Status
ClosedEvent struct {
@@ -145,11 +153,7 @@ type timelineItem struct {
} `graphql:"... on ReopenedEvent"`
// Title
- RenamedTitleEvent struct {
- actorEvent
- CurrentTitle githubv4.String
- PreviousTitle githubv4.String
- } `graphql:"... on RenamedTitleEvent"`
+ RenamedTitleEvent renamedTitleEvent `graphql:"... on RenamedTitleEvent"`
}
type issueComment struct {
@@ -160,14 +164,20 @@ type issueComment struct {
UserContentEdits userContentEditConnection `graphql:"userContentEdits(last: $commentEditLast, before: $commentEditBefore)"`
}
+type userActor struct {
+ Name *githubv4.String
+ Email githubv4.String
+}
+
type actor struct {
Typename githubv4.String `graphql:"__typename"`
Login githubv4.String
AvatarUrl githubv4.String
- User struct {
- Name *githubv4.String
- Email githubv4.String
- } `graphql:"... on User"`
+ // User struct {
+ // Name *githubv4.String
+ // Email githubv4.String
+ // } `graphql:"... on User"`
+ User userActor `graphql:"... on User"`
Organization struct {
Name *githubv4.String
Email *githubv4.String
diff --git a/bridge/github/mocks/Client.go b/bridge/github/mocks/Client.go
new file mode 100644
index 00000000..1270abb8
--- /dev/null
+++ b/bridge/github/mocks/Client.go
@@ -0,0 +1,44 @@
+// Code generated by mockery v1.0.0. DO NOT EDIT.
+
+package mocks
+
+import (
+ context "context"
+
+ githubv4 "github.com/shurcooL/githubv4"
+
+ mock "github.com/stretchr/testify/mock"
+)
+
+// Client is an autogenerated mock type for the Client type
+type Client struct {
+ mock.Mock
+}
+
+// Mutate provides a mock function with given fields: _a0, _a1, _a2, _a3
+func (_m *Client) Mutate(_a0 context.Context, _a1 interface{}, _a2 githubv4.Input, _a3 map[string]interface{}) error {
+ ret := _m.Called(_a0, _a1, _a2, _a3)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(context.Context, interface{}, githubv4.Input, map[string]interface{}) error); ok {
+ r0 = rf(_a0, _a1, _a2, _a3)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// Query provides a mock function with given fields: _a0, _a1, _a2
+func (_m *Client) Query(_a0 context.Context, _a1 interface{}, _a2 map[string]interface{}) error {
+ ret := _m.Called(_a0, _a1, _a2)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(context.Context, interface{}, map[string]interface{}) error); ok {
+ r0 = rf(_a0, _a1, _a2)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}