Skip to content

Commit

Permalink
Merge branch 'dapr:main' into feature/334-3
Browse files Browse the repository at this point in the history
  • Loading branch information
wXwcoder authored Feb 19, 2024
2 parents 3774c94 + eaaaf9c commit 2a87d02
Show file tree
Hide file tree
Showing 163 changed files with 9,814 additions and 10,763 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.go text eol=lf
6 changes: 6 additions & 0 deletions .github/holopin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
organization: dapr
defaultSticker: clmjkxscc122740fl0mkmb7egi
stickers:
-
id: clmjkxscc122740fl0mkmb7egi
alias: ghc2023
21 changes: 21 additions & 0 deletions .github/pull_request_template.md
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
34 changes: 34 additions & 0 deletions .github/workflows/dapr-bot.yml
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 .
17 changes: 17 additions & 0 deletions .github/workflows/dapr-bot/Makefile
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 \
./...
95 changes: 95 additions & 0 deletions .github/workflows/dapr-bot/bot.go
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
}
207 changes: 207 additions & 0 deletions .github/workflows/dapr-bot/bot_test.go
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)
})
}
Loading

0 comments on commit 2a87d02

Please sign in to comment.