forked from dapr/go-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'dapr:main' into feature/334-3
- Loading branch information
Showing
163 changed files
with
9,814 additions
and
10,763 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.go text eol=lf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
organization: dapr | ||
defaultSticker: clmjkxscc122740fl0mkmb7egi | ||
stickers: | ||
- | ||
id: clmjkxscc122740fl0mkmb7egi | ||
alias: ghc2023 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Description | ||
|
||
<!-- | ||
Please explain the changes you've made. | ||
--> | ||
|
||
## Issue reference | ||
|
||
<!-- | ||
We strive to have all PR being opened based on an issue, where the problem or feature have been discussed prior to implementation. | ||
--> | ||
|
||
Please reference the issue this PR will close: #_[issue number]_ | ||
|
||
## Checklist | ||
|
||
Please make sure you've completed the relevant tasks for this PR, out of the following list: | ||
|
||
* [ ] Code compiles correctly | ||
* [ ] Created/updated tests | ||
* [ ] Extended the documentation |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
name: dapr-bot | ||
|
||
on: | ||
issue_comment: | ||
types: [created] | ||
|
||
jobs: | ||
bot-run: | ||
name: bot-processor | ||
runs-on: ubuntu-latest | ||
permissions: | ||
issues: write | ||
contents: read | ||
env: | ||
GITHUB_TOKEN: ${{ github.token }} | ||
|
||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v4 | ||
|
||
- name: Setup Golang | ||
uses: actions/setup-go@v4 | ||
with: | ||
go-version-file: "./.github/workflows/dapr-bot/go.mod" | ||
cache-dependency-path: | | ||
./.github/workflows/dapr-bot/ | ||
- name: go-bot-mod | ||
working-directory: ./.github/workflows/dapr-bot/ | ||
run: go mod tidy | ||
|
||
- name: go-bot-run | ||
working-directory: ./.github/workflows/dapr-bot/ | ||
run: go run . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
GO_COMPAT_VERSION=1.22 | ||
|
||
.PHONY: cover | ||
cover: | ||
go test -coverprofile=cover.out ./ && go tool cover -html=cover.out | ||
|
||
.PHONY: tidy | ||
tidy: ## Updates the go modules | ||
go mod tidy -compat=$(GO_COMPAT_VERSION) | ||
|
||
.PHONY: test | ||
test: | ||
go test -count=1 \ | ||
-race \ | ||
-coverprofile=coverage.txt \ | ||
-covermode=atomic \ | ||
./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/google/go-github/v55/github" | ||
) | ||
|
||
var ( | ||
errCommentBodyEmpty = errors.New("comment body is empty") | ||
errIssueClosed = errors.New("issue is closed") | ||
errIssueAlreadyAssigned = errors.New("issue is already assigned") | ||
errUnauthorizedClient = errors.New("possibly unauthorized client issue") | ||
) | ||
|
||
type issueInterface interface { | ||
CreateComment(ctx context.Context, owner string, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) | ||
AddAssignees(ctx context.Context, owner string, repo string, number int, assignees []string) (*github.Issue, *github.Response, error) | ||
} | ||
|
||
type Bot struct { | ||
ctx context.Context | ||
issueClient issueInterface | ||
} | ||
|
||
func NewBot(ghClient *github.Client) *Bot { | ||
return &Bot{ | ||
ctx: context.Background(), | ||
issueClient: ghClient.Issues, | ||
} | ||
} | ||
|
||
func (b *Bot) HandleEvent(ctx context.Context, event Event) (res string, err error) { | ||
commentBody := event.IssueCommentEvent.Comment.GetBody() | ||
|
||
// split the comment after any potential new lines | ||
newline := strings.Split(strings.ReplaceAll(commentBody, "\r\n", "\n"), "\n")[0] | ||
|
||
command := strings.Split(newline, " ")[0] | ||
|
||
if command[0] != '/' { | ||
return "no command found", err | ||
} | ||
|
||
switch command { | ||
case "/assign": | ||
assignee, err := b.AssignIssueToCommenter(event) | ||
res = fmt.Sprintf("👍 Issue assigned to %s", assignee) | ||
if err == nil { | ||
err = b.CreateIssueComment(fmt.Sprintf("🚀 Issue assigned to you @%s", assignee), event) | ||
} else { | ||
err = b.CreateIssueComment("⚠️ Unable to assign issue", event) | ||
} | ||
if err != nil { | ||
return fmt.Sprintf("failed to comment on issue: %v", err), err | ||
} | ||
} | ||
return | ||
} | ||
|
||
func (b *Bot) CreateIssueComment(body string, event Event) error { | ||
if body == "" { | ||
return errCommentBodyEmpty | ||
} | ||
ctx := context.Background() | ||
comment := &github.IssueComment{ | ||
Body: github.String(body), | ||
} | ||
_, response, err := b.issueClient.CreateComment(ctx, event.GetIssueOrg(), event.GetIssueRepo(), event.GetIssueNumber(), comment) | ||
if err != nil || response.StatusCode == http.StatusNotFound { | ||
return fmt.Errorf("failed to create comment: %v%v", err, response.StatusCode) | ||
} | ||
return nil | ||
} | ||
|
||
func (b *Bot) AssignIssueToCommenter(event Event) (string, error) { | ||
if event.GetIssueState() == "closed" { | ||
return "", errIssueClosed | ||
} | ||
|
||
if len(event.GetIssueAssignees()) > 0 { | ||
return "", errIssueAlreadyAssigned | ||
} | ||
|
||
ctx := context.Background() | ||
_, response, err := b.issueClient.AddAssignees(ctx, event.GetIssueOrg(), event.GetIssueRepo(), event.GetIssueNumber(), []string{event.GetIssueUser()}) | ||
if response.StatusCode == http.StatusNotFound { | ||
return "", errUnauthorizedClient | ||
} | ||
return event.GetIssueUser(), err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/google/go-github/v55/github" | ||
"github.com/jinzhu/copier" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
var testBot = &Bot{ | ||
ctx: context.Background(), | ||
issueClient: &testClient{}, | ||
} | ||
|
||
type testClient struct { | ||
issue *github.Issue | ||
issueComment *github.IssueComment | ||
resp *github.Response | ||
} | ||
|
||
func (tc *testClient) CreateComment(ctx context.Context, org, repo string, number int, comment *github.IssueComment) (*github.IssueComment, *github.Response, error) { | ||
return tc.issueComment, tc.resp, nil | ||
} | ||
|
||
func (tc *testClient) AddAssignees(ctx context.Context, org, repo string, number int, assignees []string) (*github.Issue, *github.Response, error) { | ||
return tc.issue, tc.resp, nil | ||
} | ||
|
||
func TestNewBot(t *testing.T) { | ||
t.Run("create a bot test", func(t *testing.T) { | ||
bot := NewBot(github.NewClient(nil)) | ||
assert.NotNil(t, bot) | ||
}) | ||
} | ||
|
||
func TestHandleEvent(t *testing.T) { | ||
t.Run("handle valid event", func(t *testing.T) { | ||
tc := testClient{ | ||
resp: &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, | ||
} | ||
testBot.issueClient = &tc | ||
ctx := context.Background() | ||
var testEventCopy Event | ||
errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) | ||
if errC != nil { | ||
t.Error(errC) | ||
} | ||
testEventCopy.IssueCommentEvent.Comment.Body = github.String("/assign") | ||
res, err := testBot.HandleEvent(ctx, testEventCopy) | ||
require.NoError(t, err) | ||
assert.NotEmpty(t, res) | ||
}) | ||
|
||
t.Run("handle valid (longer body) event", func(t *testing.T) { | ||
tc := testClient{ | ||
resp: &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, | ||
} | ||
testBot.issueClient = &tc | ||
ctx := context.Background() | ||
var testEventCopy Event | ||
errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) | ||
if errC != nil { | ||
t.Error(errC) | ||
} | ||
testEventCopy.IssueCommentEvent.Comment.Body = github.String("/assign \r \ntest body") | ||
res, err := testBot.HandleEvent(ctx, testEventCopy) | ||
require.NoError(t, err) | ||
assert.NotEmpty(t, res) | ||
}) | ||
|
||
t.Run("handle unable to assign", func(t *testing.T) { | ||
tc := testClient{ | ||
resp: &github.Response{Response: &http.Response{StatusCode: http.StatusNotFound}}, | ||
} | ||
testBot.issueClient = &tc | ||
ctx := context.Background() | ||
var testEventCopy Event | ||
errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) | ||
if errC != nil { | ||
t.Error(errC) | ||
} | ||
testEventCopy.IssueCommentEvent.Comment.Body = github.String("/assign") | ||
res, err := testBot.HandleEvent(ctx, testEventCopy) | ||
require.Error(t, err) | ||
assert.NotEmpty(t, res) | ||
}) | ||
|
||
t.Run("handle no event", func(t *testing.T) { | ||
tc := testClient{} | ||
testBot.issueClient = &tc | ||
ctx := context.Background() | ||
var testEventCopy Event | ||
errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) | ||
if errC != nil { | ||
t.Error(errC) | ||
} | ||
testEventCopy.IssueCommentEvent.Comment.Body = github.String("assign") | ||
res, err := testBot.HandleEvent(ctx, testEventCopy) | ||
require.NoError(t, err) | ||
assert.Equal(t, "no command found", res) | ||
}) | ||
} | ||
|
||
func TestCreateIssueComment(t *testing.T) { | ||
t.Run("failure to create issue comment", func(t *testing.T) { | ||
tc := testClient{ | ||
resp: &github.Response{Response: &http.Response{StatusCode: http.StatusNotFound}}, | ||
} | ||
testBot.issueClient = &tc | ||
err := testBot.CreateIssueComment("test", testEvent) | ||
require.Error(t, err) | ||
}) | ||
|
||
t.Run("create issue comment", func(t *testing.T) { | ||
tc := testClient{ | ||
resp: &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, | ||
} | ||
testBot.issueClient = &tc | ||
err := testBot.CreateIssueComment("test", testEvent) | ||
require.NoError(t, err) | ||
}) | ||
|
||
t.Run("create issue comment with empty body", func(t *testing.T) { | ||
tc := testClient{ | ||
resp: &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, | ||
} | ||
testBot.issueClient = &tc | ||
err := testBot.CreateIssueComment("", testEvent) | ||
require.Error(t, err) | ||
}) | ||
} | ||
|
||
func TestAssignIssueToCommenter(t *testing.T) { | ||
t.Run("failure to assign issue to commenter", func(t *testing.T) { | ||
tc := testClient{ | ||
resp: &github.Response{Response: &http.Response{StatusCode: http.StatusNotFound}}, | ||
} | ||
testBot.issueClient = &tc | ||
assignee, err := testBot.AssignIssueToCommenter(testEvent) | ||
require.Error(t, err) | ||
assert.Empty(t, assignee) | ||
}) | ||
|
||
t.Run("successfully assign issue to commenter", func(t *testing.T) { | ||
tc := testClient{ | ||
resp: &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, | ||
} | ||
testBot.issueClient = &tc | ||
var testEventCopy Event | ||
errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) | ||
if errC != nil { | ||
t.Error(errC) | ||
} | ||
testEventCopy.IssueCommentEvent.Issue.Assignees = []*github.User{} | ||
assignee, err := testBot.AssignIssueToCommenter(testEventCopy) | ||
require.NoError(t, err) | ||
assert.Equal(t, "testCommentLogin", assignee) | ||
}) | ||
|
||
t.Run("attempt to assign a closed issue", func(t *testing.T) { | ||
tc := testClient{} | ||
testBot.issueClient = &tc | ||
var testEventCopy Event | ||
errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) | ||
if errC != nil { | ||
t.Error(errC) | ||
} | ||
testEventCopy.IssueCommentEvent.Issue.State = github.String("closed") | ||
assignee, err := testBot.AssignIssueToCommenter(testEventCopy) | ||
require.Error(t, err) | ||
assert.Empty(t, assignee) | ||
}) | ||
|
||
t.Run("issue already assigned to user", func(t *testing.T) { | ||
tc := testClient{} | ||
testBot.issueClient = &tc | ||
var testEventCopy Event | ||
errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) | ||
if errC != nil { | ||
t.Error(errC) | ||
} | ||
testEventCopy.IssueCommentEvent.Issue.Assignees = []*github.User{{Login: github.String("testCommentLogin")}} | ||
assignee, err := testBot.AssignIssueToCommenter(testEventCopy) | ||
require.Error(t, err) | ||
assert.Empty(t, assignee) | ||
}) | ||
|
||
t.Run("issue already assigned to another user", func(t *testing.T) { | ||
tc := testClient{ | ||
resp: &github.Response{Response: &http.Response{StatusCode: http.StatusOK}}, | ||
} | ||
testBot.issueClient = &tc | ||
var testEventCopy Event | ||
errC := copier.CopyWithOption(&testEventCopy, &testEvent, copier.Option{DeepCopy: true}) | ||
if errC != nil { | ||
t.Error(errC) | ||
} | ||
testEventCopy.IssueCommentEvent.Issue.Assignees = []*github.User{{Login: github.String("testCommentLogin2")}} | ||
assignee, err := testBot.AssignIssueToCommenter(testEventCopy) | ||
require.Error(t, err) | ||
assert.Empty(t, assignee) | ||
}) | ||
} |
Oops, something went wrong.