diff --git a/server/forge/bitbucketdatacenter/bitbucketdatacenter.go b/server/forge/bitbucketdatacenter/bitbucketdatacenter.go index ef843836e14..ab91c709967 100644 --- a/server/forge/bitbucketdatacenter/bitbucketdatacenter.go +++ b/server/forge/bitbucketdatacenter/bitbucketdatacenter.go @@ -477,40 +477,28 @@ func (c *client) Deactivate(ctx context.Context, u *model.User, r *model.Repo, l } func (c *client) Hook(ctx context.Context, r *http.Request) (*model.Repo, *model.Pipeline, error) { - ev, payload, err := bb.ParsePayloadWithoutSignature(r) + hook, err := parseHook(r, c.url) if err != nil { - return nil, nil, fmt.Errorf("unable to parse payload from webhook invocation: %w", err) + return nil, nil, fmt.Errorf("unable to parse hook: %w", err) } - var repo *model.Repo - var pipe *model.Pipeline - - switch e := ev.(type) { - case *bb.RepositoryPushEvent: - repo = convertRepo(&e.Repository, nil, "") - pipe = convertRepositoryPushEvent(e, c.url) - case *bb.PullRequestEvent: - repo = convertRepo(&e.PullRequest.Source.Repository, nil, "") - pipe = convertPullRequestEvent(e, c.url) - default: - return nil, nil, nil - } - - user, repo, err := c.getUserAndRepo(ctx, repo) + user, repo, err := c.getUserAndRepo(ctx, hook.Repo) if err != nil { return nil, nil, fmt.Errorf("failed to get user and repo: %w", err) } - err = bb.ValidateSignature(r, payload, []byte(repo.Hash)) + err = bb.ValidateSignature(r, hook.Payload, []byte(repo.Hash)) if err != nil { return nil, nil, fmt.Errorf("unable to validate signature on incoming webhook payload: %w", err) } - switch e := ev.(type) { + var pipe *model.Pipeline + + switch e := hook.Event.(type) { case *bb.RepositoryPushEvent: - pipe, err = c.updatePipelineFromCommit(ctx, user, repo, pipe) + pipe, err = c.updatePipelineFromCommit(ctx, user, repo, hook.Pipeline) case *bb.PullRequestEvent: - pipe, err = c.updatePipelineFromPullRequest(ctx, user, repo, pipe, e.PullRequest.ID) + pipe, err = c.updatePipelineFromPullRequest(ctx, user, repo, hook.Pipeline, e.PullRequest.ID) } if err != nil { diff --git a/server/forge/bitbucketdatacenter/fixtures/HookPullRequestMerged.json b/server/forge/bitbucketdatacenter/fixtures/HookPullRequestMerged.json new file mode 100644 index 00000000000..02bf665922a --- /dev/null +++ b/server/forge/bitbucketdatacenter/fixtures/HookPullRequestMerged.json @@ -0,0 +1,181 @@ +{ + "date": "2025-09-11T14:53:09+0300", + "actor": { + "emailAddress": "john.doe@example.com", + "displayName": "EXT Doe John", + "name": "john.doe@example.com", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/john.doe_example.com" }] }, + "id": 13581, + "type": "NORMAL", + "slug": "john.doe_example.com" + }, + "eventKey": "pr:merged", + "pullRequest": { + "author": { + "approved": false, + "role": "AUTHOR", + "user": { + "emailAddress": "john.doe@example.com", + "displayName": "EXT Doe John", + "name": "john.doe@example.com", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/john.doe_example.com" }] }, + "id": 13581, + "type": "NORMAL", + "slug": "john.doe_example.com" + }, + "status": "UNAPPROVED" + }, + "description": "Updates ArgoCD to version where the CVE is patched.", + "updatedDate": 1757591589232, + "title": "chore(CVE-2025-55190): bump argocd", + "version": 2, + "reviewers": [ + { + "approved": false, + "role": "REVIEWER", + "user": { + "emailAddress": "jane.smith@contractor.com", + "displayName": "EXT Smith Jane", + "name": "jane.smith@contractor.com", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/jane.smith_contractor.com" }] }, + "id": 9374, + "type": "NORMAL", + "slug": "jane.smith_contractor.com" + }, + "status": "UNAPPROVED" + }, + { + "approved": false, + "role": "REVIEWER", + "user": { + "emailAddress": "mike.johnson@vendor.com", + "displayName": "EXT Johnson Mike", + "name": "mike.johnson@vendor.com", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/mike.johnson_vendor.com" }] }, + "id": 15107, + "type": "NORMAL", + "slug": "mike.johnson_vendor.com" + }, + "status": "UNAPPROVED" + }, + { + "approved": true, + "role": "REVIEWER", + "user": { + "emailAddress": "alex.brown@freelance.com", + "displayName": "EXT Brown Alex", + "name": "alex.brown@freelance.com", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/alex.brown_freelance.com" }] }, + "id": 13360, + "type": "NORMAL", + "slug": "alex.brown_freelance.com" + }, + "lastReviewedCommit": "993203acecdb65ffe947424d0917768b0e5c3903", + "status": "APPROVED" + } + ], + "toRef": { + "latestCommit": "2bbf6d0c36db47566a934ab8f8e391e1ee54d392", + "id": "refs/heads/master", + "displayId": "master", + "type": "BRANCH", + "repository": { + "archived": false, + "public": false, + "hierarchyId": "da7793ace13b18fa55a5", + "name": "deployment-automation", + "forkable": true, + "project": { + "public": false, + "name": "devops-team", + "description": "DevOps Team", + "links": { "self": [{ "href": "https://bitbucket.example.com/projects/DEV" }] }, + "id": 565, + "type": "NORMAL", + "key": "DEV" + }, + "links": { + "clone": [ + { + "name": "http", + "href": "https://bitbucket.example.com/scm/dev/deployment-automation.git" + }, + { + "name": "ssh", + "href": "ssh://git@bitbucket.example.com:7999/dev/deployment-automation.git" + } + ], + "self": [{ "href": "https://bitbucket.example.com/projects/DEV/repos/deployment-automation/browse" }] + }, + "id": 1684, + "scmId": "git", + "state": "AVAILABLE", + "slug": "deployment-automation", + "statusMessage": "Available" + } + }, + "createdDate": 1757571094582, + "closedDate": 1757591589232, + "draft": false, + "closed": true, + "fromRef": { + "latestCommit": "993203acecdb65ffe947424d0917768b0e5c3903", + "id": "refs/heads/PROJ-4584", + "displayId": "PROJ-4584", + "type": "BRANCH", + "repository": { + "archived": false, + "public": false, + "hierarchyId": "da7793ace13b18fa55a5", + "name": "deployment-automation", + "forkable": true, + "project": { + "public": false, + "name": "devops-team", + "description": "DevOps Team", + "links": { "self": [{ "href": "https://bitbucket.example.com/projects/DEV" }] }, + "id": 565, + "type": "NORMAL", + "key": "DEV" + }, + "links": { + "clone": [ + { + "name": "http", + "href": "https://bitbucket.example.com/scm/dev/deployment-automation.git" + }, + { + "name": "ssh", + "href": "ssh://git@bitbucket.example.com:7999/dev/deployment-automation.git" + } + ], + "self": [{ "href": "https://bitbucket.example.com/projects/DEV/repos/deployment-automation/browse" }] + }, + "id": 1684, + "scmId": "git", + "state": "AVAILABLE", + "slug": "deployment-automation", + "statusMessage": "Available" + } + }, + "links": { + "self": [{ "href": "https://bitbucket.example.com/projects/DEV/repos/deployment-automation/pull-requests/111" }] + }, + "id": 111, + "state": "MERGED", + "locked": false, + "open": false, + "properties": { + "mergeCommit": { + "id": "c690da9e7f6a6d90defe03d57b8802df149c4aff", + "displayId": "c690da9e7f6" + } + }, + "participants": [] + } +} diff --git a/server/forge/bitbucketdatacenter/fixtures/HookPullRequestOpened.json b/server/forge/bitbucketdatacenter/fixtures/HookPullRequestOpened.json new file mode 100644 index 00000000000..59964af6921 --- /dev/null +++ b/server/forge/bitbucketdatacenter/fixtures/HookPullRequestOpened.json @@ -0,0 +1,175 @@ +{ + "date": "2025-09-19T09:07:23+0300", + "actor": { + "emailAddress": "mike.johnson@vendor.com", + "displayName": "EXT Johnson Mike", + "name": "mike.johnson@vendor.com", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/mike.johnson_vendor.com" }] }, + "id": 15107, + "type": "NORMAL", + "slug": "mike.johnson_vendor.com" + }, + "eventKey": "pr:opened", + "pullRequest": { + "author": { + "approved": false, + "role": "AUTHOR", + "user": { + "emailAddress": "mike.johnson@vendor.com", + "displayName": "EXT Johnson Mike", + "name": "mike.johnson@vendor.com", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/mike.johnson_vendor.com" }] }, + "id": 15107, + "type": "NORMAL", + "slug": "mike.johnson_vendor.com" + }, + "status": "UNAPPROVED" + }, + "description": "#### What?\n\nGather statistics about active repositories Woodpecker", + "updatedDate": 1758262043663, + "title": "feat: gather statistics about Woodpecker migration", + "version": 0, + "reviewers": [ + { + "approved": false, + "role": "REVIEWER", + "user": { + "emailAddress": "jane.smith@contractor.com", + "displayName": "EXT Smith Jane", + "name": "jane.smith@contractor.com", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/jane.smith_contractor.com" }] }, + "id": 9374, + "type": "NORMAL", + "slug": "jane.smith_contractor.com" + }, + "status": "UNAPPROVED" + }, + { + "approved": false, + "role": "REVIEWER", + "user": { + "emailAddress": "john.doe@example.com", + "displayName": "EXT Doe John", + "name": "john.doe@example.com", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/john.doe_example.com" }] }, + "id": 13581, + "type": "NORMAL", + "slug": "john.doe_example.com" + }, + "status": "UNAPPROVED" + }, + { + "approved": false, + "role": "REVIEWER", + "user": { + "emailAddress": "alex.brown@freelance.com", + "displayName": "EXT Brown Alex", + "name": "alex.brown@freelance.com", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/alex.brown_freelance.com" }] }, + "id": 13360, + "type": "NORMAL", + "slug": "alex.brown_freelance.com" + }, + "status": "UNAPPROVED" + } + ], + "toRef": { + "latestCommit": "3767a5d2d2223447d03838654baa271fc15d94df", + "id": "refs/heads/main", + "displayId": "main", + "type": "BRANCH", + "repository": { + "hierarchyId": "618693f8805af6d8f5c7", + "description": "Network Monitor", + "project": { + "public": false, + "name": "devops-team", + "description": "DevOps Team", + "links": { "self": [{ "href": "https://bitbucket.example.com/projects/DEV" }] }, + "id": 565, + "type": "NORMAL", + "key": "DEV" + }, + "statusMessage": "Available", + "archived": false, + "public": false, + "name": "network-monitor", + "forkable": true, + "links": { + "clone": [ + { + "name": "http", + "href": "https://bitbucket.example.com/scm/dev/network-monitor.git" + }, + { + "name": "ssh", + "href": "ssh://git@bitbucket.example.com:7999/dev/network-monitor.git" + } + ], + "self": [{ "href": "https://bitbucket.example.com/projects/DEV/repos/network-monitor/browse" }] + }, + "id": 1079, + "scmId": "git", + "state": "AVAILABLE", + "slug": "network-monitor" + } + }, + "createdDate": 1758262043663, + "draft": false, + "closed": false, + "fromRef": { + "latestCommit": "1c7589876bc8b5e83122b1656925d679915193d4", + "id": "refs/heads/PROJ-4596", + "displayId": "PROJ-4596", + "type": "BRANCH", + "repository": { + "hierarchyId": "618693f8805af6d8f5c7", + "description": "Network Monitor", + "project": { + "public": false, + "name": "devops-team", + "description": "DevOps Team", + "links": { "self": [{ "href": "https://bitbucket.example.com/projects/DEV" }] }, + "id": 565, + "type": "NORMAL", + "key": "DEV" + }, + "statusMessage": "Available", + "archived": false, + "public": false, + "name": "network-monitor", + "forkable": true, + "links": { + "clone": [ + { + "name": "http", + "href": "https://bitbucket.example.com/scm/dev/network-monitor.git" + }, + { + "name": "ssh", + "href": "ssh://git@bitbucket.example.com:7999/dev/network-monitor.git" + } + ], + "self": [{ "href": "https://bitbucket.example.com/projects/DEV/repos/network-monitor/browse" }] + }, + "id": 1079, + "scmId": "git", + "state": "AVAILABLE", + "slug": "network-monitor" + } + }, + "links": { + "self": [{ "href": "https://bitbucket.example.com/projects/DEV/repos/network-monitor/pull-requests/125" }] + }, + "id": 125, + "state": "OPEN", + "locked": false, + "open": true, + "participants": [] + } +} diff --git a/server/forge/bitbucketdatacenter/fixtures/HookPullRequestOpenedFromFork.json b/server/forge/bitbucketdatacenter/fixtures/HookPullRequestOpenedFromFork.json new file mode 100644 index 00000000000..37f23f900aa --- /dev/null +++ b/server/forge/bitbucketdatacenter/fixtures/HookPullRequestOpenedFromFork.json @@ -0,0 +1,170 @@ +{ + "date": "2025-09-22T13:36:11+0300", + "actor": { + "emailAddress": "john.doe@example.com", + "displayName": "EXT Doe John", + "name": "john.doe@example.com", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/john.doe_example.com" }] }, + "id": 13581, + "type": "NORMAL", + "slug": "john.doe_example.com" + }, + "eventKey": "pr:opened", + "pullRequest": { + "author": { + "approved": false, + "role": "AUTHOR", + "user": { + "emailAddress": "john.doe@example.com", + "displayName": "EXT Doe John", + "name": "john.doe@example.com", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/john.doe_example.com" }] }, + "id": 13581, + "type": "NORMAL", + "slug": "john.doe_example.com" + }, + "status": "UNAPPROVED" + }, + "updatedDate": 1758537371875, + "title": "testing PROJ-4600", + "version": 0, + "reviewers": [], + "toRef": { + "latestCommit": "8c49fecb1363fffdf00456cedaaff6a50613725a", + "id": "refs/heads/master", + "displayId": "master", + "type": "BRANCH", + "repository": { + "archived": false, + "public": false, + "hierarchyId": "da7793ace13b18fa55a5", + "name": "deployment-automation", + "forkable": true, + "project": { + "public": false, + "name": "devops-team", + "description": "DevOps Team", + "links": { "self": [{ "href": "https://bitbucket.example.com/projects/DEV" }] }, + "id": 565, + "type": "NORMAL", + "key": "DEV" + }, + "links": { + "clone": [ + { + "name": "http", + "href": "https://bitbucket.example.com/scm/dev/deployment-automation.git" + }, + { + "name": "ssh", + "href": "ssh://git@bitbucket.example.com:7999/dev/deployment-automation.git" + } + ], + "self": [{ "href": "https://bitbucket.example.com/projects/DEV/repos/deployment-automation/browse" }] + }, + "id": 1684, + "scmId": "git", + "state": "AVAILABLE", + "slug": "deployment-automation", + "statusMessage": "Available" + } + }, + "createdDate": 1758537371875, + "draft": false, + "closed": false, + "fromRef": { + "latestCommit": "716e510cecbe203618609cf103c54e040b949739", + "id": "refs/heads/master", + "displayId": "master", + "type": "BRANCH", + "repository": { + "hierarchyId": "da7793ace13b18fa55a5", + "origin": { + "archived": false, + "public": false, + "hierarchyId": "da7793ace13b18fa55a5", + "name": "deployment-automation", + "forkable": true, + "project": { + "public": false, + "name": "devops-team", + "description": "DevOps Team", + "links": { "self": [{ "href": "https://bitbucket.example.com/projects/DEV" }] }, + "id": 565, + "type": "NORMAL", + "key": "DEV" + }, + "links": { + "clone": [ + { + "name": "http", + "href": "https://bitbucket.example.com/scm/dev/deployment-automation.git" + }, + { + "name": "ssh", + "href": "ssh://git@bitbucket.example.com:7999/dev/deployment-automation.git" + } + ], + "self": [{ "href": "https://bitbucket.example.com/projects/DEV/repos/deployment-automation/browse" }] + }, + "id": 1684, + "scmId": "git", + "state": "AVAILABLE", + "slug": "deployment-automation", + "statusMessage": "Available" + }, + "project": { + "owner": { + "emailAddress": "john.doe@example.com", + "displayName": "EXT Doe John", + "name": "john.doe@example.com", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/john.doe_example.com" }] }, + "id": 13581, + "type": "NORMAL", + "slug": "john.doe_example.com" + }, + "name": "EXT Doe John", + "links": { "self": [{ "href": "https://bitbucket.example.com/users/john.doe_example.com" }] }, + "id": 1120, + "type": "PERSONAL", + "key": "~JOHN.DOE_EXAMPLE.COM" + }, + "statusMessage": "Available", + "archived": false, + "public": false, + "name": "deployment-automation", + "forkable": true, + "links": { + "clone": [ + { + "name": "ssh", + "href": "ssh://git@bitbucket.example.com:7999/~john.doe_example.com/deployment-automation.git" + }, + { + "name": "http", + "href": "https://bitbucket.example.com/scm/~john.doe_example.com/deployment-automation.git" + } + ], + "self": [ + { "href": "https://bitbucket.example.com/users/john.doe_example.com/repos/deployment-automation/browse" } + ] + }, + "id": 1856, + "scmId": "git", + "state": "AVAILABLE", + "slug": "deployment-automation" + } + }, + "links": { + "self": [{ "href": "https://bitbucket.example.com/projects/DEV/repos/deployment-automation/pull-requests/114" }] + }, + "id": 114, + "state": "OPEN", + "locked": false, + "open": true, + "participants": [] + } +} diff --git a/server/forge/bitbucketdatacenter/fixtures/HookPush.json b/server/forge/bitbucketdatacenter/fixtures/HookPush.json new file mode 100644 index 00000000000..2d6ab677bbd --- /dev/null +++ b/server/forge/bitbucketdatacenter/fixtures/HookPush.json @@ -0,0 +1,146 @@ +{ + "date": "2025-09-23T03:15:55+0300", + "actor": { + "emailAddress": "renovatebot@example.com", + "displayName": "Renovate Bot", + "name": "renovatebot", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/renovatebot" }] }, + "id": 14570, + "type": "NORMAL", + "slug": "renovatebot" + }, + "toCommit": { + "committer": { + "emailAddress": "renovatebot@example.com", + "displayName": "Renovate Bot", + "name": "renovatebot", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/renovatebot" }] }, + "id": 14570, + "type": "NORMAL", + "slug": "renovatebot" + }, + "committerTimestamp": 1758586555000, + "author": { + "emailAddress": "renovatebot@example.com", + "displayName": "Renovate Bot", + "name": "renovatebot", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/renovatebot" }] }, + "id": 14570, + "type": "NORMAL", + "slug": "renovatebot" + }, + "authorTimestamp": 1758586555000, + "id": "76797d54bca87db6d1e3e82ee40622c7908aa514", + "displayId": "76797d54bca", + "message": "chore(deps): update all", + "parents": [ + { + "committer": { + "emailAddress": "john.doe@example.com", + "name": "John Doe" + }, + "committerTimestamp": 1757592099000, + "author": { + "emailAddress": "john.doe@example.com", + "name": "John Doe" + }, + "authorTimestamp": 1757592099000, + "id": "8c49fecb1363fffdf00456cedaaff6a50613725a", + "displayId": "8c49fecb136", + "message": "chore: bump deployment automation version", + "parents": [ + { + "id": "c690da9e7f6a6d90defe03d57b8802df149c4aff", + "displayId": "c690da9e7f6" + } + ] + } + ] + }, + "eventKey": "repo:refs_changed", + "changes": [ + { + "ref": { + "id": "refs/heads/renovate-all", + "displayId": "renovate-all", + "type": "BRANCH" + }, + "fromHash": "e0e15221b987fd8296141c0faa6a79f7c86ca4ce", + "toHash": "76797d54bca87db6d1e3e82ee40622c7908aa514", + "refId": "refs/heads/renovate-all", + "type": "UPDATE" + } + ], + "commits": [ + { + "committer": { + "emailAddress": "renovatebot@example.com", + "displayName": "Renovate Bot", + "name": "renovatebot", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/renovatebot" }] }, + "id": 14570, + "type": "NORMAL", + "slug": "renovatebot" + }, + "committerTimestamp": 1758586555000, + "author": { + "emailAddress": "renovatebot@example.com", + "displayName": "Renovate Bot", + "name": "renovatebot", + "active": true, + "links": { "self": [{ "href": "https://bitbucket.example.com/users/renovatebot" }] }, + "id": 14570, + "type": "NORMAL", + "slug": "renovatebot" + }, + "authorTimestamp": 1758586555000, + "id": "76797d54bca87db6d1e3e82ee40622c7908aa514", + "displayId": "76797d54bca", + "message": "chore(deps): update all", + "parents": [ + { + "id": "8c49fecb1363fffdf00456cedaaff6a50613725a", + "displayId": "8c49fecb136" + } + ] + } + ], + "repository": { + "archived": false, + "public": false, + "hierarchyId": "da7793ace13b18fa55a5", + "name": "deployment-automation", + "forkable": true, + "project": { + "public": false, + "name": "devops-team", + "description": "DevOps Team", + "links": { "self": [{ "href": "https://bitbucket.example.com/projects/DEV" }] }, + "id": 565, + "type": "NORMAL", + "key": "DEV" + }, + "links": { + "clone": [ + { + "name": "http", + "href": "https://bitbucket.example.com/scm/dev/deployment-automation.git" + }, + { + "name": "ssh", + "href": "ssh://git@bitbucket.example.com:7999/dev/deployment-automation.git" + } + ], + "self": [{ "href": "https://bitbucket.example.com/projects/DEV/repos/deployment-automation/browse" }] + }, + "id": 1684, + "scmId": "git", + "state": "AVAILABLE", + "slug": "deployment-automation", + "statusMessage": "Available" + } +} diff --git a/server/forge/bitbucketdatacenter/fixtures/hooks.go b/server/forge/bitbucketdatacenter/fixtures/hooks.go new file mode 100644 index 00000000000..d311c6457b3 --- /dev/null +++ b/server/forge/bitbucketdatacenter/fixtures/hooks.go @@ -0,0 +1,15 @@ +package fixtures + +import _ "embed" + +//go:embed HookPullRequestOpenedFromFork.json +var HookPullFork string + +//go:embed HookPush.json +var HookPush string + +//go:embed HookPullRequestMerged.json +var HookPullMerged string + +//go:embed HookPullRequestOpened.json +var HookPull string diff --git a/server/forge/bitbucketdatacenter/parse.go b/server/forge/bitbucketdatacenter/parse.go new file mode 100644 index 00000000000..8b5fe825377 --- /dev/null +++ b/server/forge/bitbucketdatacenter/parse.go @@ -0,0 +1,42 @@ +package bitbucketdatacenter + +import ( + "fmt" + "net/http" + + bb "github.com/neticdk/go-bitbucket/bitbucket" + + "go.woodpecker-ci.org/woodpecker/v3/server/model" +) + +type HookResult struct { + Repo *model.Repo + Pipeline *model.Pipeline + Event any + Payload []byte +} + +func parseHook(r *http.Request, baseURL string) (*HookResult, error) { + ev, payload, err := bb.ParsePayloadWithoutSignature(r) + if err != nil { + return nil, fmt.Errorf("unable to parse payload from webhook invocation: %w", err) + } + + result := &HookResult{ + Event: ev, + Payload: payload, + } + + switch e := ev.(type) { + case *bb.RepositoryPushEvent: + result.Repo = convertRepo(&e.Repository, nil, "") + result.Pipeline = convertRepositoryPushEvent(e, baseURL) + case *bb.PullRequestEvent: + result.Repo = convertRepo(&e.PullRequest.Target.Repository, nil, "") + result.Pipeline = convertPullRequestEvent(e, baseURL) + default: + return nil, fmt.Errorf("unsupported webhook event type: %T", e) + } + + return result, nil +} diff --git a/server/forge/bitbucketdatacenter/parse_test.go b/server/forge/bitbucketdatacenter/parse_test.go new file mode 100644 index 00000000000..30549b2c19d --- /dev/null +++ b/server/forge/bitbucketdatacenter/parse_test.go @@ -0,0 +1,95 @@ +package bitbucketdatacenter + +import ( + "bytes" + "net/http" + "testing" + + bb "github.com/neticdk/go-bitbucket/bitbucket" + "github.com/stretchr/testify/assert" + + "go.woodpecker-ci.org/woodpecker/v3/server/forge/bitbucketdatacenter/fixtures" + "go.woodpecker-ci.org/woodpecker/v3/server/model" +) + +func Test_parseHook(t *testing.T) { + t.Run("pull-request opened", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPull) + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Event-Key", "pr:opened") + + result, err := parseHook(req, "https://bitbucket.example.com") + + assert.NoError(t, err) + assert.NotNil(t, result) + assert.IsType(t, &bb.PullRequestEvent{}, result.Event) + assert.NotNil(t, result.Repo) + assert.NotNil(t, result.Pipeline) + assert.NotNil(t, result.Payload) + assert.Equal(t, "DEV/network-monitor", result.Repo.FullName) + assert.Equal(t, "1c7589876bc8b5e83122b1656925d679915193d4", result.Pipeline.Commit) + assert.Equal(t, model.EventPull, result.Pipeline.Event) + }) + + t.Run("pull-request opened from fork", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPullFork) + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Event-Key", "pr:opened") + + result, err := parseHook(req, "https://bitbucket.example.com") + + assert.NoError(t, err) + assert.NotNil(t, result) + assert.IsType(t, &bb.PullRequestEvent{}, result.Event) + assert.NotNil(t, result.Repo) + assert.NotNil(t, result.Pipeline) + assert.NotNil(t, result.Payload) + assert.Equal(t, "DEV/deployment-automation", result.Repo.FullName) + assert.Equal(t, "716e510cecbe203618609cf103c54e040b949739", result.Pipeline.Commit) + assert.Equal(t, model.EventPull, result.Pipeline.Event) + }) + + t.Run("push hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPush) + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Event-Key", "repo:refs_changed") + + result, err := parseHook(req, "https://bitbucket.example.com") + + assert.NoError(t, err) + assert.NotNil(t, result) + assert.IsType(t, &bb.RepositoryPushEvent{}, result.Event) + assert.NotNil(t, result.Repo) + assert.NotNil(t, result.Pipeline) + assert.NotNil(t, result.Payload) + assert.Equal(t, "DEV/deployment-automation", result.Repo.FullName) + assert.Equal(t, "76797d54bca87db6d1e3e82ee40622c7908aa514", result.Pipeline.Commit) + assert.Equal(t, model.EventPush, result.Pipeline.Event) + }) + + t.Run("pull-request merged", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPullMerged) + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Event-Key", "pr:merged") + + result, err := parseHook(req, "https://bitbucket.example.com") + + assert.NoError(t, err) + assert.NotNil(t, result) + assert.IsType(t, &bb.PullRequestEvent{}, result.Event) + assert.NotNil(t, result.Repo) + assert.NotNil(t, result.Pipeline) + assert.NotNil(t, result.Payload) + assert.Equal(t, "DEV/deployment-automation", result.Repo.FullName) + assert.Equal(t, "993203acecdb65ffe947424d0917768b0e5c3903", result.Pipeline.Commit) + assert.Equal(t, model.EventPullClosed, result.Pipeline.Event) + }) +}