mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-30 10:11:23 +02:00
increased coverage for bitbucket package
This commit is contained in:
parent
082570fb5b
commit
7c5257b61e
@ -1,9 +1,7 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
@ -16,7 +14,11 @@ import (
|
||||
"golang.org/x/oauth2/bitbucket"
|
||||
)
|
||||
|
||||
// Bitbucket Server endpoint.
|
||||
const Endpoint = "https://api.bitbucket.org"
|
||||
|
||||
type config struct {
|
||||
URL string
|
||||
Client string
|
||||
Secret string
|
||||
}
|
||||
@ -25,6 +27,7 @@ type config struct {
|
||||
// repository hosting service at https://bitbucket.org
|
||||
func New(client, secret string) remote.Remote {
|
||||
return &config{
|
||||
URL: Endpoint,
|
||||
Client: client,
|
||||
Secret: secret,
|
||||
}
|
||||
@ -33,6 +36,7 @@ func New(client, secret string) remote.Remote {
|
||||
// helper function to return the bitbucket oauth2 client
|
||||
func (c *config) newClient(u *model.User) *internal.Client {
|
||||
return internal.NewClientToken(
|
||||
c.URL,
|
||||
c.Client,
|
||||
c.Secret,
|
||||
&oauth2.Token{
|
||||
@ -61,7 +65,7 @@ func (c *config) Login(res http.ResponseWriter, req *http.Request) (*model.User,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := internal.NewClient(config.Client(oauth2.NoContext, token))
|
||||
client := internal.NewClient(c.URL, config.Client(oauth2.NoContext, token))
|
||||
curr, err := client.FindCurrent()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -72,6 +76,7 @@ func (c *config) Login(res http.ResponseWriter, req *http.Request) (*model.User,
|
||||
|
||||
func (c *config) Auth(token, secret string) (string, error) {
|
||||
client := internal.NewClientToken(
|
||||
c.URL,
|
||||
c.Client,
|
||||
c.Secret,
|
||||
&oauth2.Token{
|
||||
@ -196,8 +201,8 @@ func (c *config) File(u *model.User, r *model.Repo, b *model.Build, f string) ([
|
||||
|
||||
func (c *config) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||
status := internal.BuildStatus{
|
||||
State: getStatus(b.Status),
|
||||
Desc: getDesc(b.Status),
|
||||
State: convertStatus(b.Status),
|
||||
Desc: convertDesc(b.Status),
|
||||
Key: "Drone",
|
||||
Url: link,
|
||||
}
|
||||
@ -219,10 +224,7 @@ func (c *config) Activate(u *model.User, r *model.Repo, k *model.Key, link strin
|
||||
}
|
||||
|
||||
// deletes any previously created hooks
|
||||
if err := c.Deactivate(u, r, link); err != nil {
|
||||
// we can live with failure here. Things happen and manually scrubbing
|
||||
// hooks is certinaly not the end of the world.
|
||||
}
|
||||
c.Deactivate(u, r, link)
|
||||
|
||||
return c.newClient(u).CreateHook(r.Owner, r.Name, &internal.Hook{
|
||||
Active: true,
|
||||
@ -242,114 +244,22 @@ func (c *config) Deactivate(u *model.User, r *model.Repo, link string) error {
|
||||
|
||||
hooks, err := client.ListHooks(r.Owner, r.Name, &internal.ListOpts{})
|
||||
if err != nil {
|
||||
return nil // we can live with undeleted hooks
|
||||
return err
|
||||
}
|
||||
|
||||
for _, hook := range hooks.Values {
|
||||
hookurl, err := url.Parse(hook.Url)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if hookurl.Host == linkurl.Host {
|
||||
client.DeleteHook(r.Owner, r.Name, hook.Uuid)
|
||||
break // we can live with undeleted hooks
|
||||
if err == nil && hookurl.Host == linkurl.Host {
|
||||
return client.DeleteHook(r.Owner, r.Name, hook.Uuid)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hook parses the incoming Bitbucket hook and returns the Repository and
|
||||
// Build details. If the hook is unsupported nil values are returned and the
|
||||
// hook should be skipped.
|
||||
func (c *config) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
|
||||
switch r.Header.Get("X-Event-Key") {
|
||||
case "repo:push":
|
||||
return c.pushHook(r)
|
||||
case "pullrequest:created", "pullrequest:updated":
|
||||
return c.pullHook(r)
|
||||
}
|
||||
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func (c *config) pushHook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
payload := []byte(r.FormValue("payload"))
|
||||
if len(payload) == 0 {
|
||||
defer r.Body.Close()
|
||||
payload, _ = ioutil.ReadAll(r.Body)
|
||||
}
|
||||
|
||||
hook := internal.PushHook{}
|
||||
err := json.Unmarshal(payload, &hook)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// the hook can container one or many changes. Since I don't
|
||||
// fully understand this yet, we will just pick the first
|
||||
// change that has branch information.
|
||||
for _, change := range hook.Push.Changes {
|
||||
|
||||
// must have sha information
|
||||
if change.New.Target.Hash == "" {
|
||||
continue
|
||||
}
|
||||
// we only support tag and branch pushes for now
|
||||
buildEventType := model.EventPush
|
||||
buildRef := fmt.Sprintf("refs/heads/%s", change.New.Name)
|
||||
if change.New.Type == "tag" || change.New.Type == "annotated_tag" || change.New.Type == "bookmark" {
|
||||
buildEventType = model.EventTag
|
||||
buildRef = fmt.Sprintf("refs/tags/%s", change.New.Name)
|
||||
} else if change.New.Type != "branch" && change.New.Type != "named_branch" {
|
||||
continue
|
||||
}
|
||||
|
||||
// return the updated repository information and the
|
||||
// build information.
|
||||
// TODO(bradrydzewski) uses unit tested conversion function
|
||||
return convertRepo(&hook.Repo), &model.Build{
|
||||
Event: buildEventType,
|
||||
Commit: change.New.Target.Hash,
|
||||
Ref: buildRef,
|
||||
Link: change.New.Target.Links.Html.Href,
|
||||
Branch: change.New.Name,
|
||||
Message: change.New.Target.Message,
|
||||
Avatar: hook.Actor.Links.Avatar.Href,
|
||||
Author: hook.Actor.Login,
|
||||
Timestamp: change.New.Target.Date.UTC().Unix(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func (c *config) pullHook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
payload := []byte(r.FormValue("payload"))
|
||||
if len(payload) == 0 {
|
||||
defer r.Body.Close()
|
||||
payload, _ = ioutil.ReadAll(r.Body)
|
||||
}
|
||||
|
||||
hook := internal.PullRequestHook{}
|
||||
err := json.Unmarshal(payload, &hook)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if hook.PullRequest.State != "OPEN" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// TODO(bradrydzewski) uses unit tested conversion function
|
||||
return convertRepo(&hook.Repo), &model.Build{
|
||||
Event: model.EventPull,
|
||||
Commit: hook.PullRequest.Dest.Commit.Hash,
|
||||
Ref: fmt.Sprintf("refs/heads/%s", hook.PullRequest.Dest.Branch.Name),
|
||||
Refspec: fmt.Sprintf("https://bitbucket.org/%s.git", hook.PullRequest.Source.Repo.FullName),
|
||||
Remote: cloneLink(&hook.PullRequest.Dest.Repo),
|
||||
Link: hook.PullRequest.Links.Html.Href,
|
||||
Branch: hook.PullRequest.Dest.Branch.Name,
|
||||
Message: hook.PullRequest.Desc,
|
||||
Avatar: hook.Actor.Links.Avatar.Href,
|
||||
Author: hook.Actor.Login,
|
||||
Timestamp: hook.PullRequest.Updated.UTC().Unix(),
|
||||
}, nil
|
||||
return parseHook(r)
|
||||
}
|
||||
|
253
remote/bitbucket/bitbucket_test.go
Normal file
253
remote/bitbucket/bitbucket_test.go
Normal file
@ -0,0 +1,253 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/remote/bitbucket/fixtures"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Test_bitbucket(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
s := httptest.NewServer(fixtures.Handler())
|
||||
c := &config{URL: s.URL}
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Bitbucket client", func() {
|
||||
|
||||
g.After(func() {
|
||||
s.Close()
|
||||
})
|
||||
|
||||
g.It("Should return client with default endpoint", func() {
|
||||
remote := New("4vyW6b49Z", "a5012f6c6")
|
||||
g.Assert(remote.(*config).URL).Equal("https://api.bitbucket.org")
|
||||
g.Assert(remote.(*config).Client).Equal("4vyW6b49Z")
|
||||
g.Assert(remote.(*config).Secret).Equal("a5012f6c6")
|
||||
})
|
||||
g.It("Should return the netrc file", func() {
|
||||
remote := New("", "")
|
||||
netrc, _ := remote.Netrc(fakeUser, nil)
|
||||
g.Assert(netrc.Machine).Equal("bitbucket.org")
|
||||
g.Assert(netrc.Login).Equal("x-token-auth")
|
||||
g.Assert(netrc.Password).Equal(fakeUser.Token)
|
||||
})
|
||||
|
||||
g.Describe("Given an access token", func() {
|
||||
g.It("Should return the authenticated user", func() {
|
||||
login, err := c.Auth(
|
||||
fakeUser.Token,
|
||||
fakeUser.Secret,
|
||||
)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(login).Equal(fakeUser.Login)
|
||||
})
|
||||
g.It("Should return error when request fails", func() {
|
||||
_, err := c.Auth(
|
||||
fakeUserNotFound.Token,
|
||||
fakeUserNotFound.Secret,
|
||||
)
|
||||
g.Assert(err != nil).IsTrue()
|
||||
})
|
||||
})
|
||||
|
||||
g.Describe("When requesting a repository", func() {
|
||||
g.It("Should return the details", func() {
|
||||
repo, err := c.Repo(
|
||||
fakeUser,
|
||||
fakeRepo.Owner,
|
||||
fakeRepo.Name,
|
||||
)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(repo.FullName).Equal(fakeRepo.FullName)
|
||||
})
|
||||
g.It("Should handle not found errors", func() {
|
||||
_, err := c.Repo(
|
||||
fakeUser,
|
||||
fakeRepoNotFound.Owner,
|
||||
fakeRepoNotFound.Name,
|
||||
)
|
||||
g.Assert(err != nil).IsTrue()
|
||||
})
|
||||
})
|
||||
|
||||
g.Describe("When requesting repository permissions", func() {
|
||||
g.It("Should handle not found errors", func() {
|
||||
_, err := c.Perm(
|
||||
fakeUser,
|
||||
fakeRepoNotFound.Owner,
|
||||
fakeRepoNotFound.Name,
|
||||
)
|
||||
g.Assert(err != nil).IsTrue()
|
||||
})
|
||||
g.It("Should authorize read access", func() {
|
||||
perm, err := c.Perm(
|
||||
fakeUser,
|
||||
fakeRepoNoHooks.Owner,
|
||||
fakeRepoNoHooks.Name,
|
||||
)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(perm.Pull).IsTrue()
|
||||
g.Assert(perm.Push).IsFalse()
|
||||
g.Assert(perm.Admin).IsFalse()
|
||||
})
|
||||
g.It("Should authorize admin access", func() {
|
||||
perm, err := c.Perm(
|
||||
fakeUser,
|
||||
fakeRepo.Owner,
|
||||
fakeRepo.Name,
|
||||
)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(perm.Pull).IsTrue()
|
||||
g.Assert(perm.Push).IsTrue()
|
||||
g.Assert(perm.Admin).IsTrue()
|
||||
})
|
||||
})
|
||||
|
||||
g.Describe("When requesting user repositories", func() {
|
||||
g.It("Should return the details", func() {
|
||||
repos, err := c.Repos(fakeUser)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(repos[0].FullName).Equal(fakeRepo.FullName)
|
||||
})
|
||||
g.It("Should handle organization not found errors", func() {
|
||||
_, err := c.Repos(fakeUserNoTeams)
|
||||
g.Assert(err != nil).IsTrue()
|
||||
})
|
||||
g.It("Should handle not found errors", func() {
|
||||
_, err := c.Repos(fakeUserNoRepos)
|
||||
g.Assert(err != nil).IsTrue()
|
||||
})
|
||||
})
|
||||
|
||||
g.Describe("When requesting user teams", func() {
|
||||
g.It("Should return the details", func() {
|
||||
teams, err := c.Teams(fakeUser)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(teams[0].Login).Equal("superfriends")
|
||||
g.Assert(teams[0].Avatar).Equal("http://i.imgur.com/ZygP55A.jpg")
|
||||
})
|
||||
g.It("Should handle not found error", func() {
|
||||
_, err := c.Teams(fakeUserNoTeams)
|
||||
g.Assert(err != nil).IsTrue()
|
||||
})
|
||||
})
|
||||
|
||||
g.Describe("When downloading a file", func() {
|
||||
g.It("Should return the bytes", func() {
|
||||
raw, err := c.File(fakeUser, fakeRepo, fakeBuild, "file")
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(len(raw) != 0).IsTrue()
|
||||
})
|
||||
g.It("Should handle not found error", func() {
|
||||
_, err := c.File(fakeUser, fakeRepo, fakeBuild, "file_not_found")
|
||||
g.Assert(err != nil).IsTrue()
|
||||
})
|
||||
})
|
||||
|
||||
g.Describe("When activating a repository", func() {
|
||||
g.It("Should error when malformed hook", func() {
|
||||
err := c.Activate(fakeUser, fakeRepo, nil, "%gh&%ij")
|
||||
g.Assert(err != nil).IsTrue()
|
||||
})
|
||||
g.It("Should create the hook", func() {
|
||||
err := c.Activate(fakeUser, fakeRepo, nil, "http://127.0.0.1")
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
g.It("Should remove previous hooks")
|
||||
})
|
||||
|
||||
g.Describe("When deactivating a repository", func() {
|
||||
g.It("Should error when malformed hook", func() {
|
||||
err := c.Deactivate(fakeUser, fakeRepo, "%gh&%ij")
|
||||
g.Assert(err != nil).IsTrue()
|
||||
})
|
||||
g.It("Should error when listing hooks fails", func() {
|
||||
err := c.Deactivate(fakeUser, fakeRepoNoHooks, "http://127.0.0.1")
|
||||
g.Assert(err != nil).IsTrue()
|
||||
})
|
||||
g.It("Should successfully remove hooks", func() {
|
||||
err := c.Deactivate(fakeUser, fakeRepo, "http://127.0.0.1")
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
g.It("Should successfully deactivate when hook already removed", func() {
|
||||
err := c.Deactivate(fakeUser, fakeRepoEmptyHook, "http://127.0.0.1")
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
})
|
||||
|
||||
g.It("Should update the status", func() {
|
||||
err := c.Status(fakeUser, fakeRepo, fakeBuild, "http://127.0.0.1")
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should parse the hook", func() {
|
||||
buf := bytes.NewBufferString(fixtures.HookPush)
|
||||
req, _ := http.NewRequest("POST", "/hook", buf)
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set(hookEvent, hookPush)
|
||||
|
||||
r, _, err := c.Hook(req)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(r.FullName).Equal("user_name/repo_name")
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
fakeUser = &model.User{
|
||||
Login: "superman",
|
||||
Token: "cfcd2084",
|
||||
}
|
||||
|
||||
fakeUserNotFound = &model.User{
|
||||
Login: "superman",
|
||||
Token: "user_not_found",
|
||||
}
|
||||
|
||||
fakeUserNoTeams = &model.User{
|
||||
Login: "superman",
|
||||
Token: "teams_not_found",
|
||||
}
|
||||
|
||||
fakeUserNoRepos = &model.User{
|
||||
Login: "superman",
|
||||
Token: "repos_not_found",
|
||||
}
|
||||
|
||||
fakeRepo = &model.Repo{
|
||||
Owner: "test_name",
|
||||
Name: "repo_name",
|
||||
FullName: "test_name/repo_name",
|
||||
}
|
||||
|
||||
fakeRepoNotFound = &model.Repo{
|
||||
Owner: "test_name",
|
||||
Name: "repo_not_found",
|
||||
FullName: "test_name/repo_not_found",
|
||||
}
|
||||
|
||||
fakeRepoNoHooks = &model.Repo{
|
||||
Owner: "test_name",
|
||||
Name: "hooks_not_found",
|
||||
FullName: "test_name/hooks_not_found",
|
||||
}
|
||||
|
||||
fakeRepoEmptyHook = &model.Repo{
|
||||
Owner: "test_name",
|
||||
Name: "hook_empty",
|
||||
FullName: "test_name/hook_empty",
|
||||
}
|
||||
|
||||
fakeBuild = &model.Build{
|
||||
Commit: "9ecad50",
|
||||
}
|
||||
)
|
@ -1,40 +0,0 @@
|
||||
package bitbucket
|
||||
|
||||
import "github.com/drone/drone/model"
|
||||
|
||||
const (
|
||||
statusPending = "INPROGRESS"
|
||||
statusSuccess = "SUCCESSFUL"
|
||||
statusFailure = "FAILED"
|
||||
)
|
||||
|
||||
const (
|
||||
descPending = "this build is pending"
|
||||
descSuccess = "the build was successful"
|
||||
descFailure = "the build failed"
|
||||
descError = "oops, something went wrong"
|
||||
)
|
||||
|
||||
func getStatus(status string) string {
|
||||
switch status {
|
||||
case model.StatusPending, model.StatusRunning:
|
||||
return statusPending
|
||||
case model.StatusSuccess:
|
||||
return statusSuccess
|
||||
default:
|
||||
return statusFailure
|
||||
}
|
||||
}
|
||||
|
||||
func getDesc(status string) string {
|
||||
switch status {
|
||||
case model.StatusPending, model.StatusRunning:
|
||||
return descPending
|
||||
case model.StatusSuccess:
|
||||
return descSuccess
|
||||
case model.StatusFailure:
|
||||
return descFailure
|
||||
default:
|
||||
return descError
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func Test_status(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Bitbucket status", func() {
|
||||
g.It("should return passing", func() {
|
||||
g.Assert(getStatus(model.StatusSuccess)).Equal(statusSuccess)
|
||||
})
|
||||
g.It("should return pending", func() {
|
||||
g.Assert(getStatus(model.StatusPending)).Equal(statusPending)
|
||||
g.Assert(getStatus(model.StatusRunning)).Equal(statusPending)
|
||||
})
|
||||
g.It("should return failing", func() {
|
||||
g.Assert(getStatus(model.StatusFailure)).Equal(statusFailure)
|
||||
g.Assert(getStatus(model.StatusKilled)).Equal(statusFailure)
|
||||
g.Assert(getStatus(model.StatusError)).Equal(statusFailure)
|
||||
})
|
||||
|
||||
g.It("should return passing desc", func() {
|
||||
g.Assert(getDesc(model.StatusSuccess)).Equal(descSuccess)
|
||||
})
|
||||
g.It("should return pending desc", func() {
|
||||
g.Assert(getDesc(model.StatusPending)).Equal(descPending)
|
||||
g.Assert(getDesc(model.StatusRunning)).Equal(descPending)
|
||||
})
|
||||
g.It("should return failing desc", func() {
|
||||
g.Assert(getDesc(model.StatusFailure)).Equal(descFailure)
|
||||
})
|
||||
g.It("should return error desc", func() {
|
||||
g.Assert(getDesc(model.StatusKilled)).Equal(descError)
|
||||
g.Assert(getDesc(model.StatusError)).Equal(descError)
|
||||
})
|
||||
})
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@ -10,6 +11,47 @@ import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
statusPending = "INPROGRESS"
|
||||
statusSuccess = "SUCCESSFUL"
|
||||
statusFailure = "FAILED"
|
||||
)
|
||||
|
||||
const (
|
||||
descPending = "this build is pending"
|
||||
descSuccess = "the build was successful"
|
||||
descFailure = "the build failed"
|
||||
descError = "oops, something went wrong"
|
||||
)
|
||||
|
||||
// convertStatus is a helper function used to convert a Drone status to a
|
||||
// Bitbucket commit status.
|
||||
func convertStatus(status string) string {
|
||||
switch status {
|
||||
case model.StatusPending, model.StatusRunning:
|
||||
return statusPending
|
||||
case model.StatusSuccess:
|
||||
return statusSuccess
|
||||
default:
|
||||
return statusFailure
|
||||
}
|
||||
}
|
||||
|
||||
// convertDesc is a helper function used to convert a Drone status to a
|
||||
// Bitbucket status description.
|
||||
func convertDesc(status string) string {
|
||||
switch status {
|
||||
case model.StatusPending, model.StatusRunning:
|
||||
return descPending
|
||||
case model.StatusSuccess:
|
||||
return descSuccess
|
||||
case model.StatusFailure:
|
||||
return descFailure
|
||||
default:
|
||||
return descError
|
||||
}
|
||||
}
|
||||
|
||||
// convertRepo is a helper function used to convert a Bitbucket repository
|
||||
// structure to the common Drone repository structure.
|
||||
func convertRepo(from *internal.Repo) *model.Repo {
|
||||
@ -102,3 +144,43 @@ func convertTeam(from *internal.Account) *model.Team {
|
||||
Avatar: from.Links.Avatar.Href,
|
||||
}
|
||||
}
|
||||
|
||||
// convertPullHook is a helper function used to convert a Bitbucket pull request
|
||||
// hook to the Drone build struct holding commit information.
|
||||
func convertPullHook(from *internal.PullRequestHook) *model.Build {
|
||||
return &model.Build{
|
||||
Event: model.EventPull,
|
||||
Commit: from.PullRequest.Dest.Commit.Hash,
|
||||
Ref: fmt.Sprintf("refs/heads/%s", from.PullRequest.Dest.Branch.Name),
|
||||
Remote: cloneLink(&from.PullRequest.Dest.Repo),
|
||||
Link: from.PullRequest.Links.Html.Href,
|
||||
Branch: from.PullRequest.Dest.Branch.Name,
|
||||
Message: from.PullRequest.Desc,
|
||||
Avatar: from.Actor.Links.Avatar.Href,
|
||||
Author: from.Actor.Login,
|
||||
Timestamp: from.PullRequest.Updated.UTC().Unix(),
|
||||
}
|
||||
}
|
||||
|
||||
// convertPushHook is a helper function used to convert a Bitbucket push
|
||||
// hook to the Drone build struct holding commit information.
|
||||
func convertPushHook(hook *internal.PushHook, change *internal.Change) *model.Build {
|
||||
build := &model.Build{
|
||||
Commit: change.New.Target.Hash,
|
||||
Link: change.New.Target.Links.Html.Href,
|
||||
Branch: change.New.Name,
|
||||
Message: change.New.Target.Message,
|
||||
Avatar: hook.Actor.Links.Avatar.Href,
|
||||
Author: hook.Actor.Login,
|
||||
Timestamp: change.New.Target.Date.UTC().Unix(),
|
||||
}
|
||||
switch change.New.Type {
|
||||
case "tag", "annotated_tag", "bookmark":
|
||||
build.Event = model.EventTag
|
||||
build.Ref = fmt.Sprintf("refs/tags/%s", change.New.Name)
|
||||
default:
|
||||
build.Event = model.EventPush
|
||||
build.Ref = fmt.Sprintf("refs/heads/%s", change.New.Name)
|
||||
}
|
||||
return build
|
||||
}
|
195
remote/bitbucket/convert_test.go
Normal file
195
remote/bitbucket/convert_test.go
Normal file
@ -0,0 +1,195 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/remote/bitbucket/internal"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func Test_helper(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Bitbucket converter", func() {
|
||||
|
||||
g.It("should convert passing status", func() {
|
||||
g.Assert(convertStatus(model.StatusSuccess)).Equal(statusSuccess)
|
||||
})
|
||||
|
||||
g.It("should convert pending status", func() {
|
||||
g.Assert(convertStatus(model.StatusPending)).Equal(statusPending)
|
||||
g.Assert(convertStatus(model.StatusRunning)).Equal(statusPending)
|
||||
})
|
||||
|
||||
g.It("should convert failing status", func() {
|
||||
g.Assert(convertStatus(model.StatusFailure)).Equal(statusFailure)
|
||||
g.Assert(convertStatus(model.StatusKilled)).Equal(statusFailure)
|
||||
g.Assert(convertStatus(model.StatusError)).Equal(statusFailure)
|
||||
})
|
||||
|
||||
g.It("should convert passing desc", func() {
|
||||
g.Assert(convertDesc(model.StatusSuccess)).Equal(descSuccess)
|
||||
})
|
||||
|
||||
g.It("should convert pending desc", func() {
|
||||
g.Assert(convertDesc(model.StatusPending)).Equal(descPending)
|
||||
g.Assert(convertDesc(model.StatusRunning)).Equal(descPending)
|
||||
})
|
||||
|
||||
g.It("should convert failing desc", func() {
|
||||
g.Assert(convertDesc(model.StatusFailure)).Equal(descFailure)
|
||||
})
|
||||
|
||||
g.It("should convert error desc", func() {
|
||||
g.Assert(convertDesc(model.StatusKilled)).Equal(descError)
|
||||
g.Assert(convertDesc(model.StatusError)).Equal(descError)
|
||||
})
|
||||
|
||||
g.It("should convert repository lite", func() {
|
||||
from := &internal.Repo{}
|
||||
from.FullName = "octocat/hello-world"
|
||||
from.Owner.Links.Avatar.Href = "http://..."
|
||||
|
||||
to := convertRepoLite(from)
|
||||
g.Assert(to.Avatar).Equal(from.Owner.Links.Avatar.Href)
|
||||
g.Assert(to.FullName).Equal(from.FullName)
|
||||
g.Assert(to.Owner).Equal("octocat")
|
||||
g.Assert(to.Name).Equal("hello-world")
|
||||
})
|
||||
|
||||
g.It("should convert repository", func() {
|
||||
from := &internal.Repo{
|
||||
FullName: "octocat/hello-world",
|
||||
IsPrivate: true,
|
||||
Scm: "hg",
|
||||
}
|
||||
from.Owner.Links.Avatar.Href = "http://..."
|
||||
from.Links.Html.Href = "https://bitbucket.org/foo/bar"
|
||||
|
||||
to := convertRepo(from)
|
||||
g.Assert(to.Avatar).Equal(from.Owner.Links.Avatar.Href)
|
||||
g.Assert(to.FullName).Equal(from.FullName)
|
||||
g.Assert(to.Owner).Equal("octocat")
|
||||
g.Assert(to.Name).Equal("hello-world")
|
||||
g.Assert(to.Branch).Equal("default")
|
||||
g.Assert(to.Kind).Equal(from.Scm)
|
||||
g.Assert(to.IsPrivate).Equal(from.IsPrivate)
|
||||
g.Assert(to.Clone).Equal(from.Links.Html.Href)
|
||||
g.Assert(to.Link).Equal(from.Links.Html.Href)
|
||||
})
|
||||
|
||||
g.It("should convert team", func() {
|
||||
from := &internal.Account{Login: "octocat"}
|
||||
from.Links.Avatar.Href = "http://..."
|
||||
to := convertTeam(from)
|
||||
g.Assert(to.Avatar).Equal(from.Links.Avatar.Href)
|
||||
g.Assert(to.Login).Equal(from.Login)
|
||||
})
|
||||
|
||||
g.It("should convert team list", func() {
|
||||
from := &internal.Account{Login: "octocat"}
|
||||
from.Links.Avatar.Href = "http://..."
|
||||
to := convertTeamList([]*internal.Account{from})
|
||||
g.Assert(to[0].Avatar).Equal(from.Links.Avatar.Href)
|
||||
g.Assert(to[0].Login).Equal(from.Login)
|
||||
})
|
||||
|
||||
g.It("should convert user", func() {
|
||||
token := &oauth2.Token{
|
||||
AccessToken: "foo",
|
||||
RefreshToken: "bar",
|
||||
Expiry: time.Now(),
|
||||
}
|
||||
user := &internal.Account{Login: "octocat"}
|
||||
user.Links.Avatar.Href = "http://..."
|
||||
|
||||
result := convertUser(user, token)
|
||||
g.Assert(result.Avatar).Equal(user.Links.Avatar.Href)
|
||||
g.Assert(result.Login).Equal(user.Login)
|
||||
g.Assert(result.Token).Equal(token.AccessToken)
|
||||
g.Assert(result.Token).Equal(token.AccessToken)
|
||||
g.Assert(result.Secret).Equal(token.RefreshToken)
|
||||
g.Assert(result.Expiry).Equal(token.Expiry.UTC().Unix())
|
||||
})
|
||||
|
||||
g.It("should use clone url", func() {
|
||||
repo := &internal.Repo{}
|
||||
repo.Links.Clone = append(repo.Links.Clone, internal.Link{
|
||||
Name: "https",
|
||||
Href: "https://bitbucket.org/foo/bar.git",
|
||||
})
|
||||
link := cloneLink(repo)
|
||||
g.Assert(link).Equal(repo.Links.Clone[0].Href)
|
||||
})
|
||||
|
||||
g.It("should build clone url", func() {
|
||||
repo := &internal.Repo{}
|
||||
repo.Links.Html.Href = "https://foo:bar@bitbucket.org/foo/bar.git"
|
||||
link := cloneLink(repo)
|
||||
g.Assert(link).Equal("https://bitbucket.org/foo/bar.git")
|
||||
})
|
||||
|
||||
g.It("should convert pull hook to build", func() {
|
||||
hook := &internal.PullRequestHook{}
|
||||
hook.Actor.Login = "octocat"
|
||||
hook.Actor.Links.Avatar.Href = "https://..."
|
||||
hook.PullRequest.Dest.Commit.Hash = "73f9c44d"
|
||||
hook.PullRequest.Dest.Branch.Name = "master"
|
||||
hook.PullRequest.Dest.Repo.Links.Html.Href = "https://bitbucket.org/foo/bar"
|
||||
hook.PullRequest.Links.Html.Href = "https://bitbucket.org/foo/bar/pulls/5"
|
||||
hook.PullRequest.Desc = "updated README"
|
||||
hook.PullRequest.Updated = time.Now()
|
||||
|
||||
build := convertPullHook(hook)
|
||||
g.Assert(build.Event).Equal(model.EventPull)
|
||||
g.Assert(build.Author).Equal(hook.Actor.Login)
|
||||
g.Assert(build.Avatar).Equal(hook.Actor.Links.Avatar.Href)
|
||||
g.Assert(build.Commit).Equal(hook.PullRequest.Dest.Commit.Hash)
|
||||
g.Assert(build.Branch).Equal(hook.PullRequest.Dest.Branch.Name)
|
||||
g.Assert(build.Link).Equal(hook.PullRequest.Links.Html.Href)
|
||||
g.Assert(build.Ref).Equal("refs/heads/master")
|
||||
g.Assert(build.Message).Equal(hook.PullRequest.Desc)
|
||||
g.Assert(build.Timestamp).Equal(hook.PullRequest.Updated.Unix())
|
||||
})
|
||||
|
||||
g.It("should convert push hook to build", func() {
|
||||
change := internal.Change{}
|
||||
change.New.Target.Hash = "73f9c44d"
|
||||
change.New.Name = "master"
|
||||
change.New.Target.Links.Html.Href = "https://bitbucket.org/foo/bar/commits/73f9c44d"
|
||||
change.New.Target.Message = "updated README"
|
||||
change.New.Target.Date = time.Now()
|
||||
|
||||
hook := internal.PushHook{}
|
||||
hook.Actor.Login = "octocat"
|
||||
hook.Actor.Links.Avatar.Href = "https://..."
|
||||
|
||||
build := convertPushHook(&hook, &change)
|
||||
g.Assert(build.Event).Equal(model.EventPush)
|
||||
g.Assert(build.Author).Equal(hook.Actor.Login)
|
||||
g.Assert(build.Avatar).Equal(hook.Actor.Links.Avatar.Href)
|
||||
g.Assert(build.Commit).Equal(change.New.Target.Hash)
|
||||
g.Assert(build.Branch).Equal(change.New.Name)
|
||||
g.Assert(build.Link).Equal(change.New.Target.Links.Html.Href)
|
||||
g.Assert(build.Ref).Equal("refs/heads/master")
|
||||
g.Assert(build.Message).Equal(change.New.Target.Message)
|
||||
g.Assert(build.Timestamp).Equal(change.New.Target.Date.Unix())
|
||||
})
|
||||
|
||||
g.It("should convert tag hook to build", func() {
|
||||
change := internal.Change{}
|
||||
change.New.Name = "v1.0.0"
|
||||
change.New.Type = "tag"
|
||||
|
||||
hook := internal.PushHook{}
|
||||
|
||||
build := convertPushHook(&hook, &change)
|
||||
g.Assert(build.Event).Equal(model.EventTag)
|
||||
g.Assert(build.Ref).Equal("refs/tags/v1.0.0")
|
||||
})
|
||||
})
|
||||
}
|
181
remote/bitbucket/fixtures/handler.go
Normal file
181
remote/bitbucket/fixtures/handler.go
Normal file
@ -0,0 +1,181 @@
|
||||
package fixtures
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Handler returns an http.Handler that is capable of handling a variety of mock
|
||||
// Bitbucket requests and returning mock responses.
|
||||
func Handler() http.Handler {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
e := gin.New()
|
||||
e.GET("/2.0/repositories/:owner/:name", getRepo)
|
||||
e.GET("/2.0/repositories/:owner/:name/hooks", getRepoHooks)
|
||||
e.GET("/1.0/repositories/:owner/:name/src/:commit/:file", getRepoFile)
|
||||
e.DELETE("/2.0/repositories/:owner/:name/hooks/:hook", deleteRepoHook)
|
||||
e.POST("/2.0/repositories/:owner/:name/hooks", createRepoHook)
|
||||
e.POST("/2.0/repositories/:owner/:name/commit/:commit/statuses/build", createRepoStatus)
|
||||
e.GET("/2.0/repositories/:owner", getUserRepos)
|
||||
e.GET("/2.0/teams/", getUserTeams)
|
||||
e.GET("/2.0/user/", getUser)
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func getRepo(c *gin.Context) {
|
||||
switch c.Param("name") {
|
||||
case "not_found", "repo_unknown", "repo_not_found":
|
||||
c.String(404, "")
|
||||
default:
|
||||
c.String(200, repoPayload)
|
||||
}
|
||||
}
|
||||
|
||||
func getRepoHooks(c *gin.Context) {
|
||||
switch c.Param("name") {
|
||||
case "hooks_not_found", "repo_no_hooks":
|
||||
c.String(404, "")
|
||||
case "hook_empty":
|
||||
c.String(200, "{}")
|
||||
default:
|
||||
c.String(200, repoHookPayload)
|
||||
}
|
||||
}
|
||||
|
||||
func getRepoFile(c *gin.Context) {
|
||||
switch c.Param("file") {
|
||||
case "file_not_found":
|
||||
c.String(404, "")
|
||||
default:
|
||||
c.String(200, repoFilePayload)
|
||||
}
|
||||
}
|
||||
|
||||
func createRepoStatus(c *gin.Context) {
|
||||
switch c.Param("name") {
|
||||
case "repo_not_found":
|
||||
c.String(404, "")
|
||||
default:
|
||||
c.String(200, "")
|
||||
}
|
||||
}
|
||||
|
||||
func createRepoHook(c *gin.Context) {
|
||||
c.String(200, "")
|
||||
}
|
||||
|
||||
func deleteRepoHook(c *gin.Context) {
|
||||
switch c.Param("name") {
|
||||
case "hook_not_found":
|
||||
c.String(404, "")
|
||||
default:
|
||||
c.String(200, "")
|
||||
}
|
||||
}
|
||||
|
||||
func getUser(c *gin.Context) {
|
||||
switch c.Request.Header.Get("Authorization") {
|
||||
case "Bearer user_not_found", "Bearer a87ff679":
|
||||
c.String(404, "")
|
||||
default:
|
||||
c.String(200, userPayload)
|
||||
}
|
||||
}
|
||||
|
||||
func getUserTeams(c *gin.Context) {
|
||||
switch c.Request.Header.Get("Authorization") {
|
||||
case "Bearer teams_not_found", "Bearer c81e728d":
|
||||
c.String(404, "")
|
||||
default:
|
||||
c.String(200, userTeamPayload)
|
||||
}
|
||||
}
|
||||
|
||||
func getUserRepos(c *gin.Context) {
|
||||
switch c.Request.Header.Get("Authorization") {
|
||||
case "Bearer repos_not_found", "Bearer 70efdf2e":
|
||||
c.String(404, "")
|
||||
default:
|
||||
c.String(200, userRepoPayload)
|
||||
}
|
||||
}
|
||||
|
||||
const repoPayload = `
|
||||
{
|
||||
"full_name": "test_name/repo_name",
|
||||
"scm": "git",
|
||||
"is_private": true
|
||||
}
|
||||
`
|
||||
|
||||
const repoHookPayload = `
|
||||
{
|
||||
"pagelen": 10,
|
||||
"values": [
|
||||
{
|
||||
"uuid": "{afe61e14-2c5f-49e8-8b68-ad1fb55fc052}",
|
||||
"url": "http://127.0.0.1"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"size": 1
|
||||
}
|
||||
`
|
||||
|
||||
const repoFilePayload = `
|
||||
{
|
||||
"data": "{ platform: linux/amd64 }"
|
||||
}
|
||||
`
|
||||
|
||||
const userPayload = `
|
||||
{
|
||||
"username": "superman",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "http:\/\/i.imgur.com\/ZygP55A.jpg"
|
||||
}
|
||||
},
|
||||
"type": "user"
|
||||
}
|
||||
`
|
||||
|
||||
const userRepoPayload = `
|
||||
{
|
||||
"page": 1,
|
||||
"pagelen": 10,
|
||||
"size": 1,
|
||||
"values": [
|
||||
{
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "http:\/\/i.imgur.com\/ZygP55A.jpg"
|
||||
}
|
||||
},
|
||||
"full_name": "test_name/repo_name",
|
||||
"scm": "git",
|
||||
"is_private": true
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
const userTeamPayload = `
|
||||
{
|
||||
"pagelen": 100,
|
||||
"values": [
|
||||
{
|
||||
"username": "superfriends",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "http:\/\/i.imgur.com\/ZygP55A.jpg"
|
||||
}
|
||||
},
|
||||
"type": "team"
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
164
remote/bitbucket/fixtures/hooks.go
Normal file
164
remote/bitbucket/fixtures/hooks.go
Normal file
@ -0,0 +1,164 @@
|
||||
package fixtures
|
||||
|
||||
const HookPush = `
|
||||
{
|
||||
"actor": {
|
||||
"username": "emmap1",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https:\/\/bitbucket-api-assetroot.s3.amazonaws.com\/c\/photos\/2015\/Feb\/26\/3613917261-0-emmap1-avatar_avatar.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"links": {
|
||||
"html": {
|
||||
"href": "https:\/\/api.bitbucket.org\/team_name\/repo_name"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https:\/\/api-staging-assetroot.s3.amazonaws.com\/c\/photos\/2014\/Aug\/01\/bitbucket-logo-2629490769-3_avatar.png"
|
||||
}
|
||||
},
|
||||
"full_name": "user_name\/repo_name",
|
||||
"scm": "git",
|
||||
"is_private": true
|
||||
},
|
||||
"push": {
|
||||
"changes": [
|
||||
{
|
||||
"new": {
|
||||
"type": "branch",
|
||||
"name": "name-of-branch",
|
||||
"target": {
|
||||
"type": "commit",
|
||||
"hash": "709d658dc5b6d6afcd46049c2f332ee3f515a67d",
|
||||
"author": {
|
||||
"username": "emmap1",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https:\/\/bitbucket-api-assetroot.s3.amazonaws.com\/c\/photos\/2015\/Feb\/26\/3613917261-0-emmap1-avatar_avatar.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"message": "new commit message\n",
|
||||
"date": "2015-06-09T03:34:49+00:00"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const HookPushEmptyHash = `
|
||||
{
|
||||
"push": {
|
||||
"changes": [
|
||||
{
|
||||
"new": {
|
||||
"type": "branch",
|
||||
"target": { "hash": "" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const HookPull = `
|
||||
{
|
||||
"actor": {
|
||||
"username": "emmap1",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https:\/\/bitbucket-api-assetroot.s3.amazonaws.com\/c\/photos\/2015\/Feb\/26\/3613917261-0-emmap1-avatar_avatar.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pullrequest": {
|
||||
"id": 1,
|
||||
"title": "Title of pull request",
|
||||
"description": "Description of pull request",
|
||||
"state": "OPEN",
|
||||
"author": {
|
||||
"username": "emmap1",
|
||||
"links": {
|
||||
"avatar": {
|
||||
"href": "https:\/\/bitbucket-api-assetroot.s3.amazonaws.com\/c\/photos\/2015\/Feb\/26\/3613917261-0-emmap1-avatar_avatar.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"source": {
|
||||
"branch": {
|
||||
"name": "branch2"
|
||||
},
|
||||
"commit": {
|
||||
"hash": "d3022fc0ca3d"
|
||||
},
|
||||
"repository": {
|
||||
"links": {
|
||||
"html": {
|
||||
"href": "https:\/\/api.bitbucket.org\/team_name\/repo_name"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https:\/\/api-staging-assetroot.s3.amazonaws.com\/c\/photos\/2014\/Aug\/01\/bitbucket-logo-2629490769-3_avatar.png"
|
||||
}
|
||||
},
|
||||
"full_name": "user_name\/repo_name",
|
||||
"scm": "git",
|
||||
"is_private": true
|
||||
}
|
||||
},
|
||||
"destination": {
|
||||
"branch": {
|
||||
"name": "master"
|
||||
},
|
||||
"commit": {
|
||||
"hash": "ce5965ddd289"
|
||||
},
|
||||
"repository": {
|
||||
"links": {
|
||||
"html": {
|
||||
"href": "https:\/\/api.bitbucket.org\/team_name\/repo_name"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https:\/\/api-staging-assetroot.s3.amazonaws.com\/c\/photos\/2014\/Aug\/01\/bitbucket-logo-2629490769-3_avatar.png"
|
||||
}
|
||||
},
|
||||
"full_name": "user_name\/repo_name",
|
||||
"scm": "git",
|
||||
"is_private": true
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"self": {
|
||||
"href": "https:\/\/api.bitbucket.org\/api\/2.0\/pullrequests\/pullrequest_id"
|
||||
},
|
||||
"html": {
|
||||
"href": "https:\/\/api.bitbucket.org\/pullrequest_id"
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"links": {
|
||||
"html": {
|
||||
"href": "https:\/\/api.bitbucket.org\/team_name\/repo_name"
|
||||
},
|
||||
"avatar": {
|
||||
"href": "https:\/\/api-staging-assetroot.s3.amazonaws.com\/c\/photos\/2014\/Aug\/01\/bitbucket-logo-2629490769-3_avatar.png"
|
||||
}
|
||||
},
|
||||
"full_name": "user_name\/repo_name",
|
||||
"scm": "git",
|
||||
"is_private": true
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const HookMerged = `
|
||||
{
|
||||
"pullrequest": {
|
||||
"state": "MERGED"
|
||||
}
|
||||
}
|
||||
`
|
@ -1,102 +0,0 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/remote/bitbucket/internal"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func Test_helper(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Bitbucket", func() {
|
||||
|
||||
g.It("should convert repository lite", func() {
|
||||
from := &internal.Repo{}
|
||||
from.FullName = "octocat/hello-world"
|
||||
from.Owner.Links.Avatar.Href = "http://..."
|
||||
|
||||
to := convertRepoLite(from)
|
||||
g.Assert(to.Avatar).Equal(from.Owner.Links.Avatar.Href)
|
||||
g.Assert(to.FullName).Equal(from.FullName)
|
||||
g.Assert(to.Owner).Equal("octocat")
|
||||
g.Assert(to.Name).Equal("hello-world")
|
||||
})
|
||||
|
||||
g.It("should convert repository", func() {
|
||||
from := &internal.Repo{
|
||||
FullName: "octocat/hello-world",
|
||||
IsPrivate: true,
|
||||
Scm: "hg",
|
||||
}
|
||||
from.Owner.Links.Avatar.Href = "http://..."
|
||||
from.Links.Html.Href = "https://bitbucket.org/foo/bar"
|
||||
|
||||
to := convertRepo(from)
|
||||
g.Assert(to.Avatar).Equal(from.Owner.Links.Avatar.Href)
|
||||
g.Assert(to.FullName).Equal(from.FullName)
|
||||
g.Assert(to.Owner).Equal("octocat")
|
||||
g.Assert(to.Name).Equal("hello-world")
|
||||
g.Assert(to.Branch).Equal("default")
|
||||
g.Assert(to.Kind).Equal(from.Scm)
|
||||
g.Assert(to.IsPrivate).Equal(from.IsPrivate)
|
||||
g.Assert(to.Clone).Equal(from.Links.Html.Href)
|
||||
g.Assert(to.Link).Equal(from.Links.Html.Href)
|
||||
})
|
||||
|
||||
g.It("should convert team", func() {
|
||||
from := &internal.Account{Login: "octocat"}
|
||||
from.Links.Avatar.Href = "http://..."
|
||||
to := convertTeam(from)
|
||||
g.Assert(to.Avatar).Equal(from.Links.Avatar.Href)
|
||||
g.Assert(to.Login).Equal(from.Login)
|
||||
})
|
||||
|
||||
g.It("should convert team list", func() {
|
||||
from := &internal.Account{Login: "octocat"}
|
||||
from.Links.Avatar.Href = "http://..."
|
||||
to := convertTeamList([]*internal.Account{from})
|
||||
g.Assert(to[0].Avatar).Equal(from.Links.Avatar.Href)
|
||||
g.Assert(to[0].Login).Equal(from.Login)
|
||||
})
|
||||
|
||||
g.It("should convert user", func() {
|
||||
token := &oauth2.Token{
|
||||
AccessToken: "foo",
|
||||
RefreshToken: "bar",
|
||||
Expiry: time.Now(),
|
||||
}
|
||||
user := &internal.Account{Login: "octocat"}
|
||||
user.Links.Avatar.Href = "http://..."
|
||||
|
||||
result := convertUser(user, token)
|
||||
g.Assert(result.Avatar).Equal(user.Links.Avatar.Href)
|
||||
g.Assert(result.Login).Equal(user.Login)
|
||||
g.Assert(result.Token).Equal(token.AccessToken)
|
||||
g.Assert(result.Token).Equal(token.AccessToken)
|
||||
g.Assert(result.Secret).Equal(token.RefreshToken)
|
||||
g.Assert(result.Expiry).Equal(token.Expiry.UTC().Unix())
|
||||
})
|
||||
|
||||
g.It("should use clone url", func() {
|
||||
repo := &internal.Repo{}
|
||||
repo.Links.Clone = append(repo.Links.Clone, internal.Link{
|
||||
Name: "https",
|
||||
Href: "https://bitbucket.org/foo/bar.git",
|
||||
})
|
||||
link := cloneLink(repo)
|
||||
g.Assert(link).Equal(repo.Links.Clone[0].Href)
|
||||
})
|
||||
|
||||
g.It("should build clone url", func() {
|
||||
repo := &internal.Repo{}
|
||||
repo.Links.Html.Href = "https://foo:bar@bitbucket.org/foo/bar.git"
|
||||
link := cloneLink(repo)
|
||||
g.Assert(link).Equal("https://bitbucket.org/foo/bar.git")
|
||||
})
|
||||
})
|
||||
}
|
@ -20,8 +20,6 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
base = "https://api.bitbucket.org"
|
||||
|
||||
pathUser = "%s/2.0/user/"
|
||||
pathEmails = "%s/2.0/user/emails"
|
||||
pathTeams = "%s/2.0/teams/?%s"
|
||||
@ -35,52 +33,53 @@ const (
|
||||
|
||||
type Client struct {
|
||||
*http.Client
|
||||
base string
|
||||
}
|
||||
|
||||
func NewClient(client *http.Client) *Client {
|
||||
return &Client{client}
|
||||
func NewClient(url string, client *http.Client) *Client {
|
||||
return &Client{client, url}
|
||||
}
|
||||
|
||||
func NewClientToken(client, secret string, token *oauth2.Token) *Client {
|
||||
func NewClientToken(url, client, secret string, token *oauth2.Token) *Client {
|
||||
config := &oauth2.Config{
|
||||
ClientID: client,
|
||||
ClientSecret: secret,
|
||||
Endpoint: bitbucket.Endpoint,
|
||||
}
|
||||
return NewClient(config.Client(oauth2.NoContext, token))
|
||||
return NewClient(url, config.Client(oauth2.NoContext, token))
|
||||
}
|
||||
|
||||
func (c *Client) FindCurrent() (*Account, error) {
|
||||
out := new(Account)
|
||||
uri := fmt.Sprintf(pathUser, base)
|
||||
uri := fmt.Sprintf(pathUser, c.base)
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) ListEmail() (*EmailResp, error) {
|
||||
out := new(EmailResp)
|
||||
uri := fmt.Sprintf(pathEmails, base)
|
||||
uri := fmt.Sprintf(pathEmails, c.base)
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) ListTeams(opts *ListTeamOpts) (*AccountResp, error) {
|
||||
out := new(AccountResp)
|
||||
uri := fmt.Sprintf(pathTeams, base, opts.Encode())
|
||||
uri := fmt.Sprintf(pathTeams, c.base, opts.Encode())
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) FindRepo(owner, name string) (*Repo, error) {
|
||||
out := new(Repo)
|
||||
uri := fmt.Sprintf(pathRepo, base, owner, name)
|
||||
uri := fmt.Sprintf(pathRepo, c.base, owner, name)
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) ListRepos(account string, opts *ListOpts) (*RepoResp, error) {
|
||||
out := new(RepoResp)
|
||||
uri := fmt.Sprintf(pathRepos, base, account, opts.Encode())
|
||||
uri := fmt.Sprintf(pathRepos, c.base, account, opts.Encode())
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
@ -105,37 +104,37 @@ func (c *Client) ListReposAll(account string) ([]*Repo, error) {
|
||||
|
||||
func (c *Client) FindHook(owner, name, id string) (*Hook, error) {
|
||||
out := new(Hook)
|
||||
uri := fmt.Sprintf(pathHook, base, owner, name, id)
|
||||
uri := fmt.Sprintf(pathHook, c.base, owner, name, id)
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) ListHooks(owner, name string, opts *ListOpts) (*HookResp, error) {
|
||||
out := new(HookResp)
|
||||
uri := fmt.Sprintf(pathHooks, base, owner, name, opts.Encode())
|
||||
uri := fmt.Sprintf(pathHooks, c.base, owner, name, opts.Encode())
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) CreateHook(owner, name string, hook *Hook) error {
|
||||
uri := fmt.Sprintf(pathHooks, base, owner, name, "")
|
||||
uri := fmt.Sprintf(pathHooks, c.base, owner, name, "")
|
||||
return c.do(uri, post, hook, nil)
|
||||
}
|
||||
|
||||
func (c *Client) DeleteHook(owner, name, id string) error {
|
||||
uri := fmt.Sprintf(pathHook, base, owner, name, id)
|
||||
uri := fmt.Sprintf(pathHook, c.base, owner, name, id)
|
||||
return c.do(uri, del, nil, nil)
|
||||
}
|
||||
|
||||
func (c *Client) FindSource(owner, name, revision, path string) (*Source, error) {
|
||||
out := new(Source)
|
||||
uri := fmt.Sprintf(pathSource, base, owner, name, revision, path)
|
||||
uri := fmt.Sprintf(pathSource, c.base, owner, name, revision, path)
|
||||
err := c.do(uri, get, nil, out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (c *Client) CreateStatus(owner, name, revision string, status *BuildStatus) error {
|
||||
uri := fmt.Sprintf(pathStatus, base, owner, name, revision)
|
||||
uri := fmt.Sprintf(pathStatus, c.base, owner, name, revision)
|
||||
return c.do(uri, post, status, nil)
|
||||
}
|
||||
|
||||
|
@ -100,27 +100,29 @@ type Source struct {
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
type Change struct {
|
||||
New struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Target struct {
|
||||
Type string `json:"type"`
|
||||
Hash string `json:"hash"`
|
||||
Message string `json:"message"`
|
||||
Date time.Time `json:"date"`
|
||||
Links Links `json:"links"`
|
||||
Author struct {
|
||||
Raw string `json:"raw"`
|
||||
User Account `json:"user"`
|
||||
} `json:"author"`
|
||||
} `json:"target"`
|
||||
} `json:"new"`
|
||||
}
|
||||
|
||||
type PushHook struct {
|
||||
Actor Account `json:"actor"`
|
||||
Repo Repo `json:"repository"`
|
||||
Push struct {
|
||||
Changes []struct {
|
||||
New struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Target struct {
|
||||
Type string `json:"type"`
|
||||
Hash string `json:"hash"`
|
||||
Message string `json:"message"`
|
||||
Date time.Time `json:"date"`
|
||||
Links Links `json:"links"`
|
||||
Author struct {
|
||||
Raw string `json:"raw"`
|
||||
User Account `json:"user"`
|
||||
} `json:"author"`
|
||||
} `json:"target"`
|
||||
} `json:"new"`
|
||||
} `json:"changes"`
|
||||
Changes []Change `json:"changes"`
|
||||
} `json:"push"`
|
||||
}
|
||||
|
||||
|
71
remote/bitbucket/parse.go
Normal file
71
remote/bitbucket/parse.go
Normal file
@ -0,0 +1,71 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/remote/bitbucket/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
hookEvent = "X-Event-Key"
|
||||
hookPush = "repo:push"
|
||||
hookPullCreated = "pullrequest:created"
|
||||
hookPullUpdated = "pullrequest:updated"
|
||||
|
||||
changeBranch = "branch"
|
||||
changeNamedBranch = "named_branch"
|
||||
|
||||
stateMerged = "MERGED"
|
||||
stateDeclined = "DECLINED"
|
||||
stateOpen = "OPEN"
|
||||
)
|
||||
|
||||
// parseHook parses a Bitbucket hook from an http.Request request and returns
|
||||
// Repo and Build detail. If a hook type is unsupported nil values are returned.
|
||||
func parseHook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||
payload, _ := ioutil.ReadAll(r.Body)
|
||||
|
||||
switch r.Header.Get(hookEvent) {
|
||||
case hookPush:
|
||||
return parsePushHook(payload)
|
||||
case hookPullCreated, hookPullUpdated:
|
||||
return parsePullHook(payload)
|
||||
}
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// parsePushHook parses a push hook and returns the Repo and Build details.
|
||||
// If the commit type is unsupported nil values are returned.
|
||||
func parsePushHook(payload []byte) (*model.Repo, *model.Build, error) {
|
||||
hook := internal.PushHook{}
|
||||
|
||||
err := json.Unmarshal(payload, &hook)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, change := range hook.Push.Changes {
|
||||
if change.New.Target.Hash == "" {
|
||||
continue
|
||||
}
|
||||
return convertRepo(&hook.Repo), convertPushHook(&hook, &change), nil
|
||||
}
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// parsePullHook parses a pull request hook and returns the Repo and Build
|
||||
// details. If the pull request is closed nil values are returned.
|
||||
func parsePullHook(payload []byte) (*model.Repo, *model.Build, error) {
|
||||
hook := internal.PullRequestHook{}
|
||||
|
||||
if err := json.Unmarshal(payload, &hook); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if hook.PullRequest.State != stateOpen {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return convertRepo(&hook.Repo), convertPullHook(&hook), nil
|
||||
}
|
104
remote/bitbucket/parse_test.go
Normal file
104
remote/bitbucket/parse_test.go
Normal file
@ -0,0 +1,104 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/drone/drone/remote/bitbucket/fixtures"
|
||||
|
||||
"github.com/franela/goblin"
|
||||
)
|
||||
|
||||
func Test_parser(t *testing.T) {
|
||||
|
||||
g := goblin.Goblin(t)
|
||||
g.Describe("Bitbucket hook parser", func() {
|
||||
|
||||
g.It("Should ignore unsupported hook", func() {
|
||||
buf := bytes.NewBufferString(fixtures.HookPush)
|
||||
req, _ := http.NewRequest("POST", "/hook", buf)
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set(hookEvent, "issue:created")
|
||||
|
||||
r, b, err := parseHook(req)
|
||||
g.Assert(r == nil).IsTrue()
|
||||
g.Assert(b == nil).IsTrue()
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
|
||||
g.Describe("Given a pull request hook", func() {
|
||||
|
||||
g.It("Should return err when malformed", func() {
|
||||
buf := bytes.NewBufferString("[]")
|
||||
req, _ := http.NewRequest("POST", "/hook", buf)
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set(hookEvent, hookPullCreated)
|
||||
|
||||
_, _, err := parseHook(req)
|
||||
g.Assert(err != nil).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should return nil if not open", func() {
|
||||
buf := bytes.NewBufferString(fixtures.HookMerged)
|
||||
req, _ := http.NewRequest("POST", "/hook", buf)
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set(hookEvent, hookPullCreated)
|
||||
|
||||
r, b, err := parseHook(req)
|
||||
g.Assert(r == nil).IsTrue()
|
||||
g.Assert(b == nil).IsTrue()
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should return pull request details", func() {
|
||||
buf := bytes.NewBufferString(fixtures.HookPull)
|
||||
req, _ := http.NewRequest("POST", "/hook", buf)
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set(hookEvent, hookPullCreated)
|
||||
|
||||
r, b, err := parseHook(req)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(r.FullName).Equal("user_name/repo_name")
|
||||
g.Assert(b.Commit).Equal("ce5965ddd289")
|
||||
})
|
||||
})
|
||||
|
||||
g.Describe("Given a push hook", func() {
|
||||
|
||||
g.It("Should return err when malformed", func() {
|
||||
buf := bytes.NewBufferString("[]")
|
||||
req, _ := http.NewRequest("POST", "/hook", buf)
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set(hookEvent, hookPush)
|
||||
|
||||
_, _, err := parseHook(req)
|
||||
g.Assert(err != nil).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should return nil if missing commit sha", func() {
|
||||
buf := bytes.NewBufferString(fixtures.HookPushEmptyHash)
|
||||
req, _ := http.NewRequest("POST", "/hook", buf)
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set(hookEvent, hookPush)
|
||||
|
||||
r, b, err := parseHook(req)
|
||||
g.Assert(r == nil).IsTrue()
|
||||
g.Assert(b == nil).IsTrue()
|
||||
g.Assert(err == nil).IsTrue()
|
||||
})
|
||||
|
||||
g.It("Should return push details", func() {
|
||||
buf := bytes.NewBufferString(fixtures.HookPush)
|
||||
req, _ := http.NewRequest("POST", "/hook", buf)
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set(hookEvent, hookPush)
|
||||
|
||||
r, b, err := parseHook(req)
|
||||
g.Assert(err == nil).IsTrue()
|
||||
g.Assert(r.FullName).Equal("user_name/repo_name")
|
||||
g.Assert(b.Commit).Equal("709d658dc5b6d6afcd46049c2f332ee3f515a67d")
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user