Add tests

This commit is contained in:
Zero King
2017-07-26 12:41:29 +00:00
parent d77c33ee75
commit 02b152ca80
10 changed files with 321 additions and 64 deletions

View File

@@ -2,12 +2,12 @@ package db
import (
"database/sql"
"log"
"errors"
"os"
"strings"
"errors"
// PostgreSQL driver
_ "github.com/lib/pq"
"os"
)
type Maintainer struct {
@@ -22,30 +22,39 @@ type PortMaintainer struct {
OpenMaintainer bool
}
var tracDB *sql.DB
var wwwDB *sql.DB
var prDB *sql.DB
// Create connections to DBs
func init() {
var err error
tracDB, err = sql.Open("postgres", os.Getenv("TRAC_DB"))
if err != nil {
log.Fatal(err)
}
wwwDB, err = sql.Open("postgres", os.Getenv("WWW_DB"))
if err != nil {
log.Fatal(err)
}
prDB, err = sql.Open("postgres", os.Getenv("PR_DB"))
if err != nil {
log.Fatal(err)
}
type DBHelper interface {
GetGitHubHandle(email string) (string, error)
GetPortMaintainer(port string) (*PortMaintainer, error)
}
func GetGitHubHandle(email string) (string, error) {
func NewDBHelper() (DBHelper, error) {
// TODO: move os.Getenv to main
tracDB, err := sql.Open("postgres", os.Getenv("TRAC_DB"))
if err != nil {
return nil, err
}
wwwDB, err := sql.Open("postgres", os.Getenv("WWW_DB"))
if err != nil {
return nil, err
}
prDB, err := sql.Open("postgres", os.Getenv("PR_DB"))
if err != nil {
return nil, err
}
return &sqlDBHelper{
tracDB: tracDB,
wwwDB: wwwDB,
prDB: prDB,
}, nil
}
type sqlDBHelper struct {
tracDB, wwwDB, prDB *sql.DB
}
func (sqlDB *sqlDBHelper) GetGitHubHandle(email string) (string, error) {
sid := ""
err := tracDB.QueryRow("SELECT sid "+
err := sqlDB.tracDB.QueryRow("SELECT sid "+
"FROM trac_macports.session_attribute "+
"WHERE value = $1 "+
"AND name = 'email' "+
@@ -59,8 +68,8 @@ func GetGitHubHandle(email string) (string, error) {
}
// GetPortMaintainer returns the maintainers of a port
func GetPortMaintainer(port string) (*PortMaintainer, error) {
rows, err := wwwDB.Query("SELECT maintainer, is_primary "+
func (sqlDB *sqlDBHelper) GetPortMaintainer(port string) (*PortMaintainer, error) {
rows, err := sqlDB.wwwDB.Query("SELECT maintainer, is_primary "+
"FROM public.maintainers "+
"WHERE portfile = $1", port)
if err != nil {
@@ -87,9 +96,9 @@ func GetPortMaintainer(port string) (*PortMaintainer, error) {
continue
}
if isPrimary {
maintainer.Primary = parseMaintainer(maintainerCursor)
maintainer.Primary = sqlDB.parseMaintainer(maintainerCursor)
} else {
maintainer.Others = append(maintainer.Others, parseMaintainer(maintainerCursor))
maintainer.Others = append(maintainer.Others, sqlDB.parseMaintainer(maintainerCursor))
}
}
@@ -100,7 +109,17 @@ func GetPortMaintainer(port string) (*PortMaintainer, error) {
return maintainer, nil
}
func parseMaintainer(maintainerFullString string) *Maintainer {
func (sqlDB *sqlDBHelper) parseMaintainer(maintainerFullString string) *Maintainer {
maintainer := parseMaintainerString(maintainerFullString)
if maintainer.GithubHandle == "" && maintainer.Email != "" {
if handle, err := sqlDB.GetGitHubHandle(maintainer.Email); err == nil {
maintainer.GithubHandle = handle
}
}
return maintainer
}
func parseMaintainerString(maintainerFullString string) *Maintainer {
maintainerStrings := strings.Split(maintainerFullString, " ")
maintainer := new(Maintainer)
for _, maintainerString := range maintainerStrings {
@@ -113,10 +132,5 @@ func parseMaintainer(maintainerFullString string) *Maintainer {
maintainer.Email = maintainerString + "@macports.org"
}
}
if maintainer.GithubHandle == "" && maintainer.Email != "" {
if handle, err := GetGitHubHandle(maintainer.Email); err == nil {
maintainer.GithubHandle = handle
}
}
return maintainer
}

17
pr/db/dbutil_test.go Normal file
View File

@@ -0,0 +1,17 @@
package db
import "testing"
func TestParseMaintainerString(t *testing.T) {
l2dy := parseMaintainerString("l2dy @l2dy")
if l2dy.Email != "l2dy@macports.org" {
t.Error("Expected @macports.org email, got", l2dy.Email)
}
if l2dy.GithubHandle != "l2dy" {
t.Error("Expected GitHub login, got", l2dy.GithubHandle)
}
jverne := parseMaintainerString("@jverne example.org:julesverne")
if jverne.Email != "julesverne@example.org" {
t.Error("Expected deobfuscated email, got", jverne.Email)
}
}

34
pr/githubapi/client.go Normal file
View File

@@ -0,0 +1,34 @@
package githubapi
import (
"context"
"github.com/google/go-github/github"
"golang.org/x/oauth2"
)
type Client interface {
ListChangedPortsAndFiles(owner, repo string, number int) (ports []string, commitFiles []*github.CommitFile, err error)
CreateComment(owner, repo string, number int, body *string) error
ReplaceLabels(owner, repo string, number int, labels []string) error
ListLabels(owner, repo string, number int) ([]string, error)
ListOrgMembers(org string) ([]*github.User, error)
}
type githubClient struct {
*github.Client
ctx context.Context
}
func NewClient(botSecret string) Client {
ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: botSecret},
)
tc := oauth2.NewClient(ctx, ts)
return &githubClient{
Client: github.NewClient(tc),
ctx: ctx,
}
}

8
pr/githubapi/org.go Normal file
View File

@@ -0,0 +1,8 @@
package githubapi
import "github.com/google/go-github/github"
func (client *githubClient) ListOrgMembers(org string) ([]*github.User, error) {
users, _, err := client.Organizations.ListMembers(client.ctx, org, nil)
return users, err
}

View File

@@ -5,28 +5,9 @@ import (
"regexp"
"github.com/google/go-github/github"
"golang.org/x/oauth2"
)
type Client struct {
*github.Client
ctx context.Context
}
func NewClient(botSecret string) *Client {
ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: botSecret},
)
tc := oauth2.NewClient(ctx, ts)
return &Client{
Client: github.NewClient(tc),
ctx: ctx,
}
}
func (client *Client) ListChangedPortsAndFiles(owner, repo string, number int) (ports []string, commitFiles []*github.CommitFile, err error) {
func (client *githubClient) ListChangedPortsAndFiles(owner, repo string, number int) (ports []string, commitFiles []*github.CommitFile, err error) {
files, _, err := client.PullRequests.ListFiles(
context.Background(),
owner,
@@ -47,7 +28,7 @@ func (client *Client) ListChangedPortsAndFiles(owner, repo string, number int) (
return
}
func (client *Client) CreateComment(owner, repo string, number int, body *string) error {
func (client *githubClient) CreateComment(owner, repo string, number int, body *string) error {
_, _, err := client.Issues.CreateComment(
client.ctx,
owner,
@@ -58,7 +39,7 @@ func (client *Client) CreateComment(owner, repo string, number int, body *string
return err
}
func (client *Client) ReplaceLabels(owner, repo string, number int, labels []string) error {
func (client *githubClient) ReplaceLabels(owner, repo string, number int, labels []string) error {
_, _, err := client.Issues.ReplaceLabelsForIssue(
client.ctx,
owner,
@@ -69,7 +50,7 @@ func (client *Client) ReplaceLabels(owner, repo string, number int, labels []str
return err
}
func (client *Client) ListLabels(owner, repo string, number int) ([]string, error) {
func (client *githubClient) ListLabels(owner, repo string, number int) ([]string, error) {
labels, _, err := client.Issues.ListLabelsByIssue(
client.ctx,
owner,

View File

@@ -5,6 +5,7 @@ import (
"log"
"os"
"github.com/macports/mpbot-github/pr/db"
"github.com/macports/mpbot-github/pr/webhook"
)
@@ -22,10 +23,14 @@ func main() {
}
prodFlag := false
if os.Getenv("BOT_ENV") == "production" {
prodFlag = true
}
webhook.NewReceiver(*webhookAddr, hookSecret, botSecret, prodFlag).Start()
dbHelper, err := db.NewDBHelper()
if err != nil {
log.Fatal(err)
}
webhook.NewReceiver(*webhookAddr, hookSecret, botSecret, prodFlag, dbHelper).Start()
}

View File

@@ -8,7 +8,6 @@ import (
"strings"
"github.com/google/go-github/github"
"github.com/macports/mpbot-github/pr/db"
)
var cveRegexp = regexp.MustCompile(`CVE-\d{4}-\d+`)
@@ -49,7 +48,7 @@ func (receiver *Receiver) handlePullRequest(body []byte) {
// If PR sender is maintainer of one of the ports changed
isOneMaintainer := false
for i, port := range ports {
portMaintainer, err := db.GetPortMaintainer(port)
portMaintainer, err := receiver.dbHelper.GetPortMaintainer(port)
if err != nil {
// TODO: handle submission of duplicate ports
if err.Error() == "port not found" && *files[i].Status == "added" {
@@ -168,7 +167,9 @@ func (receiver *Receiver) handlePullRequest(body []byte) {
// fallthrough
//case "synchronize":
}
log.Println("PR #" + strconv.Itoa(number) + " processed")
if !receiver.testing {
log.Println("PR #" + strconv.Itoa(number) + " processed")
}
}
// TODO: use map to dedup

View File

@@ -0,0 +1,3 @@
package webhook
func (receiver *Receiver) handlePullRequestReview(body []byte) {}

View File

@@ -0,0 +1,188 @@
package webhook
import (
"encoding/json"
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/google/go-github/github"
"github.com/macports/mpbot-github/pr/db"
)
var errNotFound = errors.New("404")
func TestCVERegexp(t *testing.T) {
assert.Equal(t, "CVE-2017-0001", cveRegexp.FindString("Fixes CVE-2017-0001."))
assert.Equal(t, "", cveRegexp.FindString("CVE-pending."))
}
type PullRequestEventTest struct {
number int
sender string
title string
body string
comment string
labels []string
}
func TestHandlePullRequest(t *testing.T) {
stubClient := stubGitHubClient{}
receiver := &Receiver{
githubClient: &stubClient,
dbHelper: &stubDBHelper{},
testing: true,
}
var event github.PullRequestEvent
json.Unmarshal([]byte(`{
"action": "opened",
"number": 1,
"pull_request": {
"number": 1,
"state": "open",
"title": "",
"user": {
"login": "l2dy"
},
"body": ""
},
"repository": {
"name": "macports-ports",
"owner": {
"login": "macports"
}
},
"sender": {
"login": "l2dy"
}
}`), &event)
prTests := []*PullRequestEventTest{
{number: 1, sender: "l2dy", title: "z: update to 1.1", labels: []string{"maintainer: none", "type: update"}},
{number: 1, sender: "l2dy", title: "z: update to 1.1", body: "[x] enhancement", labels: []string{"maintainer: none", "type: update", "type: enhancement"}},
{number: 1, sender: "l2dy", title: "z: update to 1.1", body: "Fixes CVE-0000-0.", labels: []string{"maintainer: none", "type: update", "type: security fix"}},
{number: 2, sender: "l2dy", title: "upx-devel: new port", labels: []string{"type: submission"}},
{number: 3, sender: "l2dy", title: "upx: update to 1.1", labels: []string{"maintainer", "maintainer: open", "type: update"}},
{number: 3, sender: "jverne", title: "upx: update to 1.1", comment: "Notifying maintainers:\n@_l2dy for port upx.\n\nBy a harmless bot.", labels: []string{"maintainer: open", "type: update"}},
{number: 3, sender: "jverne", title: "upx: update to 1.1", body: "<!-- [skip notification] -->", labels: []string{"maintainer: open", "type: update"}},
}
for _, prt := range prTests {
stubClient.newComment = ""
stubClient.newLabels = nil
event.Number = &prt.number
event.Sender.Login = &prt.sender
event.PullRequest.Title = &prt.title
event.PullRequest.Body = &prt.body
eventBody, err := json.Marshal(event)
if err != nil {
t.Error(err)
}
receiver.handlePullRequest(eventBody)
assert.Equal(t, prt.comment, stubClient.newComment)
assert.Subset(t, stubClient.newLabels, prt.labels)
assert.Subset(t, prt.labels, stubClient.newLabels)
}
}
type stubGitHubClient struct {
newComment string
newLabels []string
}
func (stub *stubGitHubClient) ListChangedPortsAndFiles(owner, repo string, number int) (ports []string, commitFiles []*github.CommitFile, err error) {
if owner != "macports" || repo != "macports-ports" {
return nil, nil, errNotFound
}
switch number {
case 1:
return []string{"z"},
[]*github.CommitFile{
{
Filename: ptrOfStr("sysutils/z/Portfile"),
Status: ptrOfStr("modified"),
Changes: ptrOfInt(6),
},
}, nil
case 2:
return []string{"upx-devel"},
[]*github.CommitFile{
{
Filename: ptrOfStr("archivers/upx-devel/Portfile"),
Status: ptrOfStr("added"),
Changes: ptrOfInt(43),
},
}, nil
case 3:
return []string{"upx"},
[]*github.CommitFile{
{
Filename: ptrOfStr("archivers/upx/Portfile"),
Status: ptrOfStr("modified"),
Changes: ptrOfInt(6),
},
}, nil
default:
return nil, nil, errNotFound
}
}
func (stub *stubGitHubClient) CreateComment(owner, repo string, number int, body *string) error {
stub.newComment = *body
return nil
}
func (stub *stubGitHubClient) ReplaceLabels(owner, repo string, number int, labels []string) error {
stub.newLabels = labels
return nil
}
func (stub *stubGitHubClient) ListLabels(owner, repo string, number int) ([]string, error) {
if owner != "macports" || repo != "macports-ports" {
return nil, errNotFound
}
return nil, nil
}
func (stub *stubGitHubClient) ListOrgMembers(org string) ([]*github.User, error) {
return []*github.User{
{Login: ptrOfStr("l2dy")},
}, nil
}
type stubDBHelper struct{}
func (stub *stubDBHelper) GetGitHubHandle(email string) (string, error) {
if email == "l2dy@macports.org" {
return "l2dy", nil
}
return "", errNotFound
}
func (stub *stubDBHelper) GetPortMaintainer(port string) (*db.PortMaintainer, error) {
if port == "upx" {
return &db.PortMaintainer{
Primary: &db.Maintainer{
GithubHandle: "l2dy",
Email: "l2dy@macports.org",
},
NoMaintainer: false,
OpenMaintainer: true,
}, nil
}
if port == "z" {
return &db.PortMaintainer{
Primary: nil,
NoMaintainer: true,
OpenMaintainer: false,
}, nil
}
return nil, errors.New("port not found")
}
func ptrOfStr(s string) *string {
return &s
}
func ptrOfInt(s int) *int {
return &s
}

View File

@@ -8,6 +8,7 @@ import (
"net/http"
"strings"
"github.com/macports/mpbot-github/pr/db"
"github.com/macports/mpbot-github/pr/githubapi"
)
@@ -15,15 +16,18 @@ type Receiver struct {
listenAddr string
hookSecret []byte
production bool
githubClient *githubapi.Client
testing bool
githubClient githubapi.Client
dbHelper db.DBHelper
}
func NewReceiver(listenAddr string, hookSecret []byte, botSecret string, production bool) *Receiver {
func NewReceiver(listenAddr string, hookSecret []byte, botSecret string, production bool, dbHelper db.DBHelper) *Receiver {
return &Receiver{
listenAddr: listenAddr,
hookSecret: hookSecret,
production: production,
githubClient: githubapi.NewClient(botSecret),
dbHelper: dbHelper,
}
}
@@ -60,6 +64,8 @@ func (receiver *Receiver) Start() {
return
case "pull_request":
go receiver.handlePullRequest(body)
case "pull_request_review":
go receiver.handlePullRequestReview(body)
}
w.WriteHeader(http.StatusNoContent)