diff --git a/server/plugin/test_utils.go b/server/plugin/test_utils.go index e3c44cbe0..cc9978e4a 100644 --- a/server/plugin/test_utils.go +++ b/server/plugin/test_utils.go @@ -24,6 +24,8 @@ const ( MockChannelID = "mockChannelID" MockCreatorID = "mockCreatorID" MockBotID = "mockBotID" + MockOrg = "mockOrg" + MockSender = "mockSender" MockPostMessage = "mockPostMessage" MockOrgRepo = "mockOrg/mockRepo" MockHead = "mockHead" @@ -256,22 +258,25 @@ func GetMockDeleteEventWithInvalidType() *github.DeleteEvent { } } -func GetMockPullRequestReviewEvent(action, state string) *github.PullRequestReviewEvent { +func GetMockPullRequestReviewEvent(action, state, repo string, isPrivate bool, reviewer, author string) *github.PullRequestReviewEvent { return &github.PullRequestReviewEvent{ Action: github.String(action), Repo: &github.Repository{ - Name: github.String(MockRepoName), + Name: github.String(repo), FullName: github.String(MockOrgRepo), - Private: github.Bool(false), + Private: github.Bool(isPrivate), HTMLURL: github.String(fmt.Sprintf("%s%s", GithubBaseURL, MockOrgRepo)), }, + Sender: &github.User{Login: github.String(reviewer)}, Review: &github.PullRequestReview{ + User: &github.User{ + Login: github.String(reviewer), + }, State: github.String(state), }, - Sender: &github.User{ - Login: github.String(MockUserLogin), + PullRequest: &github.PullRequest{ + User: &github.User{Login: github.String(author)}, }, - PullRequest: &github.PullRequest{}, } } @@ -369,3 +374,70 @@ func GetMockPullRequestEvent(action, repoName string, isPrivate bool, sender, us RequestedReviewer: &github.User{Login: github.String(user)}, } } + +func GetMockIssuesEvent(action, repoName string, isPrivate bool, author, sender, assignee string) *github.IssuesEvent { + return &github.IssuesEvent{ + Action: &action, + Repo: &github.Repository{FullName: &repoName, Private: &isPrivate}, + Issue: &github.Issue{User: &github.User{Login: &author}}, + Sender: &github.User{Login: &sender}, + Assignee: func() *github.User { + if assignee == "" { + return nil + } + return &github.User{Login: &assignee} + }(), + } +} + +func GetMockStarEvent(repo, org string, isPrivate bool, sender string) *github.StarEvent { + return &github.StarEvent{ + Repo: &github.Repository{ + Name: github.String(repo), + Private: github.Bool(isPrivate), + FullName: github.String(fmt.Sprintf("%s/%s", repo, org)), + }, + Sender: &github.User{Login: github.String(sender)}, + } +} + +func GetMockReleaseEvent(repo, org, action, sender string) *github.ReleaseEvent { + return &github.ReleaseEvent{ + Action: &action, + Repo: &github.Repository{ + Name: github.String(repo), + Owner: &github.User{Login: github.String(org)}, + FullName: github.String(fmt.Sprintf("%s/%s", repo, org)), + }, + Sender: &github.User{Login: github.String(sender)}, + } +} + +func GetMockDiscussionEvent(repo, org, sender string) *github.DiscussionEvent { + return &github.DiscussionEvent{ + Repo: &github.Repository{ + Name: github.String(repo), + Owner: &github.User{Login: github.String(org)}, + FullName: github.String(fmt.Sprintf("%s/%s", repo, org)), + }, + Sender: &github.User{Login: github.String(sender)}, + Discussion: &github.Discussion{ + Number: github.Int(123), + }, + } +} + +func GetMockDiscussionCommentEvent(repo, org, action, sender string) *github.DiscussionCommentEvent { + return &github.DiscussionCommentEvent{ + Action: &action, + Repo: &github.Repository{ + Name: github.String(repo), + Owner: &github.User{Login: github.String(org)}, + FullName: github.String(fmt.Sprintf("%s/%s", repo, org)), + }, + Sender: &github.User{Login: github.String(sender)}, + Comment: &github.CommentDiscussion{ + ID: github.Int64(456), + }, + } +} diff --git a/server/plugin/webhook_test.go b/server/plugin/webhook_test.go index 3690b973d..1ef4d1043 100644 --- a/server/plugin/webhook_test.go +++ b/server/plugin/webhook_test.go @@ -10,7 +10,6 @@ import ( "strings" "testing" - "github.com/golang/mock/gomock" "github.com/google/go-github/v54/github" "github.com/pkg/errors" "github.com/stretchr/testify/assert" @@ -41,7 +40,10 @@ func TestPostPushEvent(t *testing.T) { name: "No subscription found", pushEvent: GetMockPushEvent(), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).Return(nil).Times(1) }, }, { @@ -74,6 +76,7 @@ func TestPostPushEvent(t *testing.T) { p := getPluginTest(mockAPI, mockKVStore) t.Run(tc.name, func(t *testing.T) { + mockAPI.ExpectedCalls = nil tc.setup(mockAPI, mockKVStore) p.postPushEvent(tc.pushEvent) @@ -93,7 +96,10 @@ func TestPostCreateEvent(t *testing.T) { name: "No subscription found", createEvent: GetMockCreateEvent(), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).Return(nil).Times(1) }, }, { @@ -126,6 +132,7 @@ func TestPostCreateEvent(t *testing.T) { mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) p := getPluginTest(mockAPI, mockKVStore) + mockAPI.ExpectedCalls = nil tc.setup(mockAPI, mockKVStore) p.postCreateEvent(tc.createEvent) @@ -145,7 +152,10 @@ func TestPostDeleteEvent(t *testing.T) { name: "No subscription found", deleteEvent: GetMockDeleteEvent(), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).Return(nil).Times(1) }, }, { @@ -178,6 +188,7 @@ func TestPostDeleteEvent(t *testing.T) { mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) p := getPluginTest(mockAPI, mockKVStore) + mockAPI.ExpectedCalls = nil tc.setup(mockAPI, mockKVStore) p.postDeleteEvent(tc.deleteEvent) @@ -198,7 +209,10 @@ func TestPostIssueCommentEvent(t *testing.T) { name: "No subscriptions found", event: GetMockIssueCommentEvent(actionCreated, "mockBody", "mockUser"), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).Return(nil).Times(1) }, }, { @@ -239,6 +253,7 @@ func TestPostIssueCommentEvent(t *testing.T) { mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) p := getPluginTest(mockAPI, mockKVStore) + mockAPI.ExpectedCalls = nil tc.setup(mockAPI, mockKVStore) p.postIssueCommentEvent(tc.event) @@ -261,7 +276,10 @@ func TestSenderMutedByReceiver(t *testing.T) { userID: "user1", sender: "sender1", setup: func(mockKVStore *mocks.MockKvStore, _ *plugintest.API) { - mockKVStore.EXPECT().Get("user1-muted-users", gomock.Any()).Return(nil).Do(func(key string, value interface{}) { + mockKVStore.EXPECT().Get("user1-muted-users", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).Return(nil).Do(func(key string, value interface{}) { *value.(*[]byte) = []byte("sender1,sender2") }).Times(1) }, @@ -274,7 +292,10 @@ func TestSenderMutedByReceiver(t *testing.T) { userID: "user1", sender: "sender3", setup: func(mockKVStore *mocks.MockKvStore, _ *plugintest.API) { - mockKVStore.EXPECT().Get("user1-muted-users", gomock.Any()).Return(nil).Do(func(key string, value interface{}) { + mockKVStore.EXPECT().Get("user1-muted-users", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).Return(nil).Do(func(key string, value interface{}) { *value.(*[]byte) = []byte("sender1,sender2") }).Times(1) }, @@ -287,7 +308,10 @@ func TestSenderMutedByReceiver(t *testing.T) { userID: "user1", sender: "sender1", setup: func(mockKVStore *mocks.MockKvStore, mockAPI *plugintest.API) { - mockKVStore.EXPECT().Get("user1-muted-users", gomock.Any()).Return(errors.New("store error")).Times(1) + mockKVStore.EXPECT().Get("user1-muted-users", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).Return(errors.New("store error")).Times(1) mockAPI.On("LogWarn", "Failed to get muted users", "userID", "user1").Times(1) }, assert: func(t *testing.T, muted bool) { @@ -300,6 +324,7 @@ func TestSenderMutedByReceiver(t *testing.T) { mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) p := getPluginTest(mockAPI, mockKVStore) + mockAPI.ExpectedCalls = nil tc.setup(mockKVStore, mockAPI) muted := p.senderMutedByReceiver(tc.userID, tc.sender) @@ -318,21 +343,24 @@ func TestPostPullRequestReviewEvent(t *testing.T) { }{ { name: "No subscriptions found", - event: GetMockPullRequestReviewEvent("submitted", "approved"), + event: GetMockPullRequestReviewEvent("submitted", "approved", MockRepo, false, "authorUser", "reviewerUser"), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).Return(nil).Times(1) }, }, { name: "Unsupported action in event", - event: GetMockPullRequestReviewEvent("deleted", "approved"), + event: GetMockPullRequestReviewEvent("deleted", "approved", MockRepo, false, "authorUser", "reviewerUser"), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { mockSubscription(mockKVStore) }, }, { name: "Unsupported review state", - event: GetMockPullRequestReviewEvent("submitted", "canceled"), + event: GetMockPullRequestReviewEvent("submitted", "canceled", MockRepo, false, "authorUser", "reviewerUser"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { mockSubscription(mockKVStore) mockAPI.On("LogDebug", "Unhandled review state", "state", "canceled").Times(1) @@ -340,7 +368,7 @@ func TestPostPullRequestReviewEvent(t *testing.T) { }, { name: "Error creating post", - event: GetMockPullRequestReviewEvent("submitted", "approved"), + event: GetMockPullRequestReviewEvent("submitted", "approved", MockRepo, false, "authorUser", "reviewerUser"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { mockSubscription(mockKVStore) mockAPI.On("CreatePost", mock.Anything).Return(nil, &model.AppError{Message: "error creating post"}).Times(1) @@ -349,7 +377,7 @@ func TestPostPullRequestReviewEvent(t *testing.T) { }, { name: "Successful handling of pull request review event", - event: GetMockPullRequestReviewEvent("submitted", "approved"), + event: GetMockPullRequestReviewEvent("submitted", "approved", MockRepo, false, "authorUser", "reviewerUser"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { mockSubscription(mockKVStore) mockAPI.On("CreatePost", mock.Anything).Return(&model.Post{}, nil).Times(1) @@ -361,6 +389,7 @@ func TestPostPullRequestReviewEvent(t *testing.T) { mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) p := getPluginTest(mockAPI, mockKVStore) + mockAPI.ExpectedCalls = nil tc.setup(mockAPI, mockKVStore) p.postPullRequestReviewEvent(tc.event) @@ -380,7 +409,10 @@ func TestPostPullRequestReviewCommentEvent(t *testing.T) { name: "No subscriptions found", event: GetMockPullRequestReviewCommentEvent(), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).Return(nil).Times(1) }, }, { @@ -406,6 +438,7 @@ func TestPostPullRequestReviewCommentEvent(t *testing.T) { mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) p := getPluginTest(mockAPI, mockKVStore) + mockAPI.ExpectedCalls = nil tc.setup(mockAPI, mockKVStore) p.postPullRequestReviewCommentEvent(tc.event) @@ -440,20 +473,24 @@ func TestHandleCommentMentionNotification(t *testing.T) { name: "Error getting channel details", event: GetMockIssueCommentEvent(actionCreated, "mention @otherUser", "mockUser"), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("otherUser_githubusername", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("otherUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).Return(nil).Times(1) }, }, { name: "Error getting channel details", event: GetMockIssueCommentEvent(actionCreated, "mention @otherUser", "mockUser"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("otherUser_githubusername", gomock.Any()).DoAndReturn(func(key string, value interface{}) error { - if v, ok := value.(*[]byte); ok { - *v = []byte("otherUserID") - } - return nil - }).Times(1) - mockKVStore.EXPECT().Get("otherUserID_githubtoken", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("otherUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("otherUserID")).Times(1) + mockKVStore.EXPECT().Get("otherUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**GitHubUserInfo) + return ok + })).Return(nil).Times(1) mockAPI.On("GetDirectChannel", "otherUserID", "mockBotID").Return(nil, &model.AppError{Message: "error getting channel"}).Times(1) }, }, @@ -461,13 +498,14 @@ func TestHandleCommentMentionNotification(t *testing.T) { name: "Error creating post", event: GetMockIssueCommentEvent(actionCreated, "mention @otherUser", "mockUser"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("otherUser_githubusername", gomock.Any()).DoAndReturn(func(key string, value interface{}) error { - if v, ok := value.(*[]byte); ok { - *v = []byte("otherUserID") - } - return nil - }).Times(1) - mockKVStore.EXPECT().Get("otherUserID_githubtoken", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("otherUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("otherUserID")).Times(1) + mockKVStore.EXPECT().Get("otherUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**GitHubUserInfo) + return ok + })).Return(nil).Times(1) mockAPI.On("GetDirectChannel", "otherUserID", "mockBotID").Return(&model.Channel{Id: "mockChannelID"}, nil).Times(1) mockAPI.On("CreatePost", mock.Anything).Return(nil, &model.AppError{Message: "error creating post"}).Times(1) mockAPI.On("LogWarn", "Error creating mention post", "error", "error creating post").Times(1) @@ -478,13 +516,14 @@ func TestHandleCommentMentionNotification(t *testing.T) { name: "Successful mention notification", event: GetMockIssueCommentEvent(actionCreated, "mention @otherUser", "mockUser"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("otherUser_githubusername", gomock.Any()).DoAndReturn(func(key string, value interface{}) error { - if v, ok := value.(*[]byte); ok { - *v = []byte("otherUserID") - } - return nil - }).Times(1) - mockKVStore.EXPECT().Get("otherUserID_githubtoken", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("otherUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("otherUserID")).Times(1) + mockKVStore.EXPECT().Get("otherUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**GitHubUserInfo) + return ok + })).Return(nil).Times(1) mockAPI.On("GetDirectChannel", "otherUserID", "mockBotID").Return(&model.Channel{Id: "mockChannelID"}, nil).Times(1) mockAPI.On("CreatePost", mock.Anything).Return(&model.Post{}, nil).Times(1) mockAPI.On("LogWarn", "Failed to get github user info", "error", "Must connect user account to GitHub first.") @@ -525,31 +564,30 @@ func TestHandleCommentAuthorNotification(t *testing.T) { name: "Author not mapped to user ID", event: GetMockIssueCommentEvent(actionCreated, "mockBody", "mockUser"), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("issueAuthor_githubusername", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).Return(nil).Times(1) }, }, { name: "Author has no permission to repo", event: GetMockIssueCommentEvent(actionCreated, "mockBody", "mockUser"), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("issueAuthor_githubusername", gomock.Any()).DoAndReturn(func(key string, value interface{}) error { - if v, ok := value.(*[]byte); ok { - *v = []byte("authorUserID") - } - return nil - }).Times(1) + mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("authorUserID")).Times(1) }, }, { name: "Unhandled issue type", event: GetMockIssueCommentEventWithURL(actionCreated, "mockBody", "mockUser", "https://mockurl.com/unhandledType/123"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("issueAuthor_githubusername", gomock.Any()).DoAndReturn(func(key string, value interface{}) error { - if v, ok := value.(*[]byte); ok { - *v = []byte("authorUserID") - } - return nil - }).Times(1) + mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("authorUserID")).Times(1) mockAPI.On("LogDebug", "Unhandled issue type", "type", "unhandledType").Times(1) }, }, @@ -557,14 +595,18 @@ func TestHandleCommentAuthorNotification(t *testing.T) { name: "Error creating post", event: GetMockIssueCommentEventWithURL(actionCreated, "mockBody", "mockUser", "https://mockurl.com/issues/123"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("issueAuthor_githubusername", gomock.Any()).DoAndReturn(func(key string, value interface{}) error { - if v, ok := value.(*[]byte); ok { - *v = []byte("authorUserID") - } - return nil - }).Times(1) - mockKVStore.EXPECT().Get("authorUserID-muted-users", gomock.Any()).Return(nil).Times(1) - mockKVStore.EXPECT().Get("authorUserID_githubtoken", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("authorUserID")).Times(1) + mockKVStore.EXPECT().Get("authorUserID-muted-users", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).Return(nil).Times(1) + mockKVStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**GitHubUserInfo) + return ok + })).Return(nil).Times(1) mockAPI.On("GetDirectChannel", "authorUserID", "mockBotID").Return(&model.Channel{Id: "mockChannelID"}, nil).Times(1) mockAPI.On("CreatePost", mock.Anything).Return(&model.Post{}, nil, &model.AppError{Message: "error creating post"}).Times(1) mockAPI.On("LogWarn", "Failed to get github user info", "error", "Must connect user account to GitHub first.").Times(1) @@ -574,14 +616,18 @@ func TestHandleCommentAuthorNotification(t *testing.T) { name: "Successful notification", event: GetMockIssueCommentEventWithURL(actionCreated, "mockBody", "mockUser", "https://mockurl.com/issues/123"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("issueAuthor_githubusername", gomock.Any()).DoAndReturn(func(key string, value interface{}) error { - if v, ok := value.(*[]byte); ok { - *v = []byte("authorUserID") - } - return nil - }).Times(1) - mockKVStore.EXPECT().Get("authorUserID-muted-users", gomock.Any()).Return(nil).Times(1) - mockKVStore.EXPECT().Get("authorUserID_githubtoken", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("authorUserID")).Times(1) + mockKVStore.EXPECT().Get("authorUserID-muted-users", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).Return(nil).Times(1) + mockKVStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**GitHubUserInfo) + return ok + })).Return(nil).Times(1) mockAPI.On("LogWarn", "Failed to get github user info", "error", "Must connect user account to GitHub first.").Times(1) mockAPI.On("GetDirectChannel", "authorUserID", "mockBotID").Return(&model.Channel{Id: "mockChannelID"}, nil).Times(1) mockAPI.On("CreatePost", mock.Anything).Return(&model.Post{}, nil).Times(1) @@ -619,52 +665,58 @@ func TestHandleCommentAssigneeNotification(t *testing.T) { name: "Assignee is the author", event: GetMockIssueCommentEventWithAssignees("issues", actionCreated, "mockBody", "assigneeUser", []string{"assigneeUser"}), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("assigneeUser_githubusername", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).Return(nil).Times(1) }, }, { name: "Issue author is assignee", event: GetMockIssueCommentEventWithAssignees("issues", actionCreated, "mockBody", "assigneeUser", []string{"issueAuthor"}), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("issueAuthor_githubusername", gomock.Any()).DoAndReturn(func(key string, value interface{}) error { - if v, ok := value.(*[]byte); ok { - *v = []byte("issueAuthor") - } - return nil - }).Times(1) + mockKVStore.EXPECT().Get("issueAuthor_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("issueAuthor")).Times(1) }, }, { name: "Assignee is the sender", event: GetMockIssueCommentEventWithAssignees("issues", actionCreated, "mockBody", "mockUser", []string{"mockUser"}), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("mockUser_githubusername", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("mockUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).Return(nil).Times(1) }, }, { name: "Comment mentions assignee (self-mention)", event: GetMockIssueCommentEventWithAssignees("issues", actionCreated, "mention @assigneeUser", "mockUser", []string{"assigneeUser"}), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("assigneeUser_githubusername", gomock.Any()).DoAndReturn(func(key string, value interface{}) error { - if v, ok := value.(*[]byte); ok { - *v = []byte("assigneeUserID") - } - return nil - }).Times(1) - mockKVStore.EXPECT().Get("assigneeUserID_githubtoken", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("assigneeUserID")).Times(1) + mockKVStore.EXPECT().Get("assigneeUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**GitHubUserInfo) + return ok + })).Return(nil).Times(1) }, }, { name: "No permission to the repo", event: GetMockIssueCommentEventWithAssignees("issues", actionCreated, "mockBody", "mockUser", []string{"assigneeUser"}), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("assigneeUser_githubusername", gomock.Any()).DoAndReturn(func(key string, value interface{}) error { - if v, ok := value.(*[]byte); ok { - *v = []byte("assigneeUserID") - } - return nil - }).Times(1) - mockKVStore.EXPECT().Get("assigneeUserID_githubtoken", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("assigneeUserID")).Times(1) + mockKVStore.EXPECT().Get("assigneeUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**GitHubUserInfo) + return ok + })).Return(nil).Times(1) }, }, } @@ -697,7 +749,10 @@ func TestHandlePullRequestNotification(t *testing.T) { name: "Review requested with no repo permission", event: GetMockPullRequestEvent("review_requested", "mockRepo", true, "senderUser", "requestedReviewer", ""), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("requestedReviewer_githubusername", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("requestedReviewer_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).Return(nil).Times(1) }, }, { @@ -709,13 +764,14 @@ func TestHandlePullRequestNotification(t *testing.T) { name: "Pull request closed successfully", event: GetMockPullRequestEvent(actionClosed, "mockRepo", false, "authorUser", "senderUser", ""), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("senderUser_githubusername", gomock.Any()).DoAndReturn(func(key string, value interface{}) error { - if v, ok := value.(*[]byte); ok { - *v = []byte("authorUserID") - } - return nil - }).Times(1) - mockKVStore.EXPECT().Get("authorUserID_githubtoken", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("senderUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("authorUserID")).Times(1) + mockKVStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**GitHubUserInfo) + return ok + })).Return(nil).Times(1) mockAPI.On("GetDirectChannel", "authorUserID", "mockBotID").Return(&model.Channel{Id: "mockChannelID"}, nil) mockAPI.On("CreatePost", mock.Anything).Return(&model.Post{}, nil).Times(1) mockAPI.On("LogWarn", "Failed to get github user info", "error", "Must connect user account to GitHub first.").Times(1) @@ -725,7 +781,10 @@ func TestHandlePullRequestNotification(t *testing.T) { name: "Pull request reopened with no repo permission", event: GetMockPullRequestEvent(actionReopened, "mockRepo", true, "authorUser", "senderUser", ""), setup: func(_ *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("senderUser_githubusername", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("senderUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).Return(nil).Times(1) }, }, { @@ -737,15 +796,16 @@ func TestHandlePullRequestNotification(t *testing.T) { name: "Pull request assigned successfully", event: GetMockPullRequestEvent(actionAssigned, "mockRepo", false, "senderUser", "assigneeUser", "assigneeUser"), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("assigneeUser_githubusername", gomock.Any()).DoAndReturn(func(key string, value interface{}) error { - if v, ok := value.(*[]byte); ok { - *v = []byte("assigneeUserID") - } - return nil - }).Times(1) + mockKVStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("assigneeUserID")).Times(1) mockAPI.On("GetDirectChannel", "assigneeUserID", "mockBotID").Return(&model.Channel{Id: "mockChannelID"}, nil) mockAPI.On("CreatePost", mock.Anything).Return(&model.Post{}, nil).Times(1) - mockKVStore.EXPECT().Get("assigneeUserID_githubtoken", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("assigneeUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**GitHubUserInfo) + return ok + })).Return(nil).Times(1) mockAPI.On("LogWarn", "Failed to get github user info", "error", "Must connect user account to GitHub first.").Times(1) }, }, @@ -753,15 +813,16 @@ func TestHandlePullRequestNotification(t *testing.T) { name: "Review requested with valid user ID", event: GetMockPullRequestEvent("review_requested", "mockRepo", false, "senderUser", "requestedReviewer", ""), setup: func(mockAPI *plugintest.API, mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get("requestedReviewer_githubusername", gomock.Any()).DoAndReturn(func(key string, value interface{}) error { - if v, ok := value.(*[]byte); ok { - *v = []byte("requestedUserID") - } - return nil - }).Times(1) + mockKVStore.EXPECT().Get("requestedReviewer_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("requestedUserID")).Times(1) mockAPI.On("GetDirectChannel", "requestedUserID", "mockBotID").Return(&model.Channel{Id: "mockChannelID"}, nil) mockAPI.On("CreatePost", mock.Anything).Return(&model.Post{}, nil).Times(1) - mockKVStore.EXPECT().Get("requestedUserID_githubtoken", gomock.Any()).Return(nil).Times(1) + mockKVStore.EXPECT().Get("requestedUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**GitHubUserInfo) + return ok + })).Return(nil).Times(1) mockAPI.On("LogWarn", "Failed to get github user info", "error", "Must connect user account to GitHub first.").Times(1) }, }, @@ -779,6 +840,7 @@ func TestHandlePullRequestNotification(t *testing.T) { mockKVStore, mockAPI, _, _, _ := GetTestSetup(t) p := getPluginTest(mockAPI, mockKVStore) + mockAPI.ExpectedCalls = nil tc.setup(mockAPI, mockKVStore) p.handlePullRequestNotification(tc.event) @@ -788,8 +850,430 @@ func TestHandlePullRequestNotification(t *testing.T) { } } +func TestHandleIssueNotification(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + + tests := []struct { + name string + event *github.IssuesEvent + setup func() + }{ + { + name: "issue closed by author", + event: GetMockIssuesEvent(actionClosed, MockRepo, false, "authorUser", "authorUser", ""), + setup: func() {}, + }, + { + name: "issue closed successfully", + event: GetMockIssuesEvent(actionClosed, MockRepo, true, "authorUser", "senderUser", ""), + setup: func() { + mockKvStore.EXPECT().Get("authorUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("authorUserID")).Times(1) + mockKvStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**GitHubUserInfo) + return ok + })).Return(nil).Times(1) + }, + }, + { + name: "issue reopened with no repo permission", + event: GetMockIssuesEvent(actionReopened, MockRepo, true, "authorUser", "senderUser", ""), + setup: func() { + mockKvStore.EXPECT().Get("authorUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).Return(nil).Times(1) + }, + }, + { + name: "issue assigned to self", + event: GetMockIssuesEvent(actionAssigned, MockRepo, false, "assigneeUser", "assigneeUser", "assigneeUser"), + setup: func() {}, + }, + { + name: "issue assigned successfully", + event: GetMockIssuesEvent(actionAssigned, MockRepo, false, "senderUser", "assigneeUser", "assigneeUser"), + setup: func() { + mockKvStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("assigneeUserID")).Times(1) + }, + }, + { + name: "issue assigned with no repo permission for assignee", + event: GetMockIssuesEvent(actionAssigned, MockRepo, true, "senderUser", "demoassigneeUser", "assigneeUser"), + setup: func() { + mockKvStore.EXPECT().Get("assigneeUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("assigneeUserID")).Times(1) + }, + }, + { + name: "unhandled event action", + event: GetMockIssuesEvent("unsupported_action", MockRepo, false, "senderUser", "", ""), + setup: func() { + mockAPI.On("LogDebug", "Unhandled event action", "action", "unsupported_action").Return(nil).Times(1) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockAPI.ExpectedCalls = nil + tc.setup() + + p.handleIssueNotification(tc.event) + + mockAPI.AssertExpectations(t) + }) + } +} + +func TestHandlePullRequestReviewNotification(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + + tests := []struct { + name string + event *github.PullRequestReviewEvent + setup func() + }{ + { + name: "review submitted by author", + event: GetMockPullRequestReviewEvent(actionSubmitted, "approved", MockRepo, false, "authorUser", "authorUser"), + setup: func() {}, + }, + { + name: "review action not submitted", + event: GetMockPullRequestReviewEvent("dismissed", "approved", MockRepo, false, "authorUser", "reviewerUser"), + setup: func() {}, + }, + { + name: "review with author not mapped to user ID", + event: GetMockPullRequestReviewEvent(actionSubmitted, "approved", MockRepo, false, "unknownAuthor", "reviewerUser"), + setup: func() { + mockKvStore.EXPECT().Get("reviewerUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).Return(nil).Times(1) + }, + }, + { + name: "private repo, no permission for author", + event: GetMockPullRequestReviewEvent(actionSubmitted, "approved", MockRepo, true, "authorUser", "reviewerUser"), + setup: func() { + mockKvStore.EXPECT().Get("reviewerUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("authorUserID")).Times(1) + mockKvStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**GitHubUserInfo) + return ok + })).DoAndReturn(setByteValue("authorUserID")).Times(1) + }, + }, + { + name: "successful review notification", + event: GetMockPullRequestReviewEvent(actionSubmitted, "approved", MockRepo, false, "authorUser", "reviewerUser"), + setup: func() { + mockKvStore.EXPECT().Get("reviewerUser_githubusername", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(*[]uint8) + return ok + })).DoAndReturn(setByteValue("authorUserID")).Times(1) + mockAPI.On("GetDirectChannel", "authorUserID", "mockBotID").Return(nil, &model.AppError{Message: "error getting channel"}).Times(1) + mockAPI.On("LogWarn", "Couldn't get bot's DM channel", "userID", "authorUserID", "error", "error getting channel") + mockKvStore.EXPECT().Get("authorUserID_githubtoken", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**GitHubUserInfo) + return ok + })).Return(nil).Times(1) + mockAPI.On("LogWarn", "Failed to get github user info", "error", "Must connect user account to GitHub first.") + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockAPI.ExpectedCalls = nil + tc.setup() + + p.handlePullRequestReviewNotification(tc.event) + + mockAPI.AssertExpectations(t) + }) + } +} + +func TestPostStarEvent(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + + tests := []struct { + name string + event *github.StarEvent + setup func() + }{ + { + name: "no subscribed channels for repository", + event: GetMockStarEvent(MockRepo, MockOrg, false, MockSender), + setup: func() { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).Return(nil).Times(1) + }, + }, + { + name: "error creating post", + event: GetMockStarEvent(MockRepo, MockOrg, false, MockSender), + setup: func() { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ + "mockrepo/mockorg": { + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureStars, Repository: MockRepo}, + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureDeletes, Repository: MockRepo}, + }, + })).Times(1) + mockAPI.On("CreatePost", mock.Anything).Return(nil, &model.AppError{Message: "error creating post"}).Times(1) + mockAPI.On("LogWarn", "Error webhook post", "post", mock.Anything, "error", "error creating post") + }, + }, + { + name: "successful star event notification", + event: GetMockStarEvent(MockRepo, MockOrg, false, MockSender), + setup: func() { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ + "mockrepo/mockorg": { + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureStars, Repository: MockRepo}, + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureDeletes, Repository: MockRepo}, + }, + })).Times(1) + mockAPI.On("CreatePost", mock.Anything).Return(&model.Post{}, nil).Times(1) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockAPI.ExpectedCalls = nil + tc.setup() + + p.postStarEvent(tc.event) + + mockAPI.AssertExpectations(t) + }) + } +} + +func TestPostReleaseEvent(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + + tests := []struct { + name string + event *github.ReleaseEvent + setup func() + }{ + { + name: "no subscribed channels for repository", + event: GetMockReleaseEvent(MockRepo, MockOrg, "created", MockSender), + setup: func() { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).Return(nil).Times(1) + }, + }, + { + name: "unsupported action", + event: GetMockReleaseEvent(MockRepo, MockOrg, "edited", MockSender), + setup: func() {}, + }, + { + name: "error creating post", + event: GetMockReleaseEvent(MockRepo, MockOrg, "created", MockSender), + setup: func() { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ + "mockrepo/mockorg": { + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureReleases, Repository: MockRepo}, + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureDeletes, Repository: MockRepo}, + }, + })).Times(1) + mockAPI.On("CreatePost", mock.Anything).Return(nil, &model.AppError{Message: "error creating post"}).Times(1) + mockAPI.On("LogWarn", "Error webhook post", "Post", mock.Anything, "Error", "error creating post") + }, + }, + { + name: "successful release event notification", + event: GetMockReleaseEvent(MockRepo, MockOrg, "created", MockSender), + setup: func() { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ + "mockrepo/mockorg": { + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureReleases, Repository: MockRepo}, + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureDeletes, Repository: MockRepo}, + }, + })).Times(1) + mockAPI.On("CreatePost", mock.Anything).Return(&model.Post{}, nil).Times(1) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockAPI.ExpectedCalls = nil + tc.setup() + + p.postReleaseEvent(tc.event) + + mockAPI.AssertExpectations(t) + }) + } +} + +func TestPostDiscussionEvent(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + + tests := []struct { + name string + event *github.DiscussionEvent + setup func() + }{ + { + name: "no subscribed channels for repository", + event: GetMockDiscussionEvent(MockRepo, MockOrg, MockSender), + setup: func() { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).Return(nil).Times(1) + }, + }, + { + name: "error creating discussion post", + event: GetMockDiscussionEvent(MockRepo, MockOrg, MockSender), + setup: func() { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ + "mockrepo/mockorg": { + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureDiscussions, Repository: MockRepo}, + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureDeletes, Repository: MockRepo}, + }, + })).Times(1) + mockAPI.On("CreatePost", mock.Anything).Return(nil, &model.AppError{Message: "error creating post"}).Times(1) + mockAPI.On("LogWarn", "Error creating discussion notification post", "Post", mock.Anything, "Error", "error creating post") + }, + }, + { + name: "successful discussion notification", + event: GetMockDiscussionEvent(MockRepo, MockOrg, MockSender), + setup: func() { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ + "mockrepo/mockorg": { + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureDiscussions, Repository: MockRepo}, + }, + })).Times(1) + mockAPI.On("CreatePost", mock.Anything).Return(&model.Post{}, nil).Times(1) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockAPI.ExpectedCalls = nil + tc.setup() + + p.postDiscussionEvent(tc.event) + + mockAPI.AssertExpectations(t) + }) + } +} + +func TestPostDiscussionCommentEvent(t *testing.T) { + mockKvStore, mockAPI, _, _, _ := GetTestSetup(t) + p := getPluginTest(mockAPI, mockKvStore) + + tests := []struct { + name string + event *github.DiscussionCommentEvent + setup func() + }{ + { + name: "no subscribed channels for repository", + event: GetMockDiscussionCommentEvent(MockRepo, MockOrg, "created", MockSender), + setup: func() { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).Return(nil).Times(1) + }, + }, + { + name: "error creating discussion comment post", + event: GetMockDiscussionCommentEvent(MockRepo, MockOrg, "created", MockSender), + setup: func() { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ + "mockrepo/mockorg": { + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureDiscussionComments, Repository: MockRepo}, + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureDeletes, Repository: MockRepo}, + }, + })).Times(1) + mockAPI.On("CreatePost", mock.Anything).Return(nil, &model.AppError{Message: "error creating post"}).Times(1) + mockAPI.On("LogWarn", "Error creating discussion comment post", "Post", mock.Anything, "Error", "error creating post") + }, + }, + { + name: "successful discussion comment notification", + event: GetMockDiscussionCommentEvent(MockRepo, MockOrg, "created", MockSender), + setup: func() { + mockKvStore.EXPECT().Get("subscriptions", mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).DoAndReturn(setupMockSubscriptions(map[string][]*Subscription{ + "mockrepo/mockorg": { + {ChannelID: MockChannelID, CreatorID: MockCreatorID, Features: featureDiscussionComments, Repository: MockRepo}, + }, + })).Times(1) + mockAPI.On("CreatePost", mock.Anything).Return(&model.Post{}, nil).Times(1) + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockAPI.ExpectedCalls = nil + tc.setup() + + p.postDiscussionCommentEvent(tc.event) + + mockAPI.AssertExpectations(t) + }) + } +} + func mockSubscription(mockKVStore *mocks.MockKvStore) { - mockKVStore.EXPECT().Get(SubscriptionsKey, gomock.Any()).DoAndReturn(func(key string, value interface{}) error { + mockKVStore.EXPECT().Get(SubscriptionsKey, mock.MatchedBy(func(val interface{}) bool { + _, ok := val.(**Subscriptions) + return ok + })).DoAndReturn(func(key string, value interface{}) error { if v, ok := value.(**Subscriptions); ok { *v = GetMockSubscriptions() } @@ -797,6 +1281,26 @@ func mockSubscription(mockKVStore *mocks.MockKvStore) { }).Times(1) } +func setupMockSubscriptions(subs map[string][]*Subscription) func(string, interface{}) error { + return func(_ string, value interface{}) error { + if v, ok := value.(**Subscriptions); ok { + *v = &Subscriptions{ + Repositories: subs, + } + } + return nil + } +} + +func setByteValue(data string) func(key string, value interface{}) error { + return func(key string, value interface{}) error { + if v, ok := value.(*[]byte); ok { + *v = []byte(data) + } + return nil + } +} + func newPlugin(userID string, gitHubURL string) *Plugin { p := NewPlugin() p.initializeAPI()