From 9e123018e39314c9272942a7d03cef95400608bc Mon Sep 17 00:00:00 2001 From: Bill Bagdon Date: Thu, 13 Jul 2023 10:41:44 -0400 Subject: [PATCH] Support for Azure DevOps (#136) This PR adds initial support for Azure DevOps Build and Pull Request events Co-authored-by: William Bagdon --- azuredevops/azuredevops.go | 76 +++++++ azuredevops/azuredevops_test.go | 113 ++++++++++ azuredevops/payload.go | 193 ++++++++++++++++++ testdata/azuredevops/build.complete.json | 88 ++++++++ .../azuredevops/git.pullrequest.created.json | 85 ++++++++ .../azuredevops/git.pullrequest.merged.json | 86 ++++++++ .../azuredevops/git.pullrequest.updated.json | 92 +++++++++ 7 files changed, 733 insertions(+) create mode 100644 azuredevops/azuredevops.go create mode 100644 azuredevops/azuredevops_test.go create mode 100644 azuredevops/payload.go create mode 100644 testdata/azuredevops/build.complete.json create mode 100644 testdata/azuredevops/git.pullrequest.created.json create mode 100644 testdata/azuredevops/git.pullrequest.merged.json create mode 100644 testdata/azuredevops/git.pullrequest.updated.json diff --git a/azuredevops/azuredevops.go b/azuredevops/azuredevops.go new file mode 100644 index 0000000..79f665f --- /dev/null +++ b/azuredevops/azuredevops.go @@ -0,0 +1,76 @@ +package azuredevops + +// this package receives Azure DevOps Server webhooks +// https://docs.microsoft.com/en-us/azure/devops/service-hooks/services/webhooks?view=azure-devops-2020 + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" +) + +// parse errors +var ( + ErrInvalidHTTPMethod = errors.New("invalid HTTP Method") + ErrParsingPayload = errors.New("error parsing payload") +) + +// Event defines an Azure DevOps server hook event type +type Event string + +// Azure DevOps Server hook types +const ( + BuildCompleteEventType Event = "build.complete" + GitPullRequestCreatedEventType Event = "git.pullrequest.created" + GitPullRequestUpdatedEventType Event = "git.pullrequest.updated" + GitPullRequestMergedEventType Event = "git.pullrequest.merged" +) + +// Webhook instance contains all methods needed to process events +type Webhook struct { +} + +// New creates and returns a WebHook instance +func New() (*Webhook, error) { + hook := new(Webhook) + return hook, nil +} + +// Parse verifies and parses the events specified and returns the payload object or an error +func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error) { + defer func() { + _, _ = io.Copy(ioutil.Discard, r.Body) + _ = r.Body.Close() + }() + + if r.Method != http.MethodPost { + return nil, ErrInvalidHTTPMethod + } + + payload, err := ioutil.ReadAll(r.Body) + if err != nil || len(payload) == 0 { + return nil, ErrParsingPayload + } + + var pl BasicEvent + err = json.Unmarshal([]byte(payload), &pl) + if err != nil { + return nil, ErrParsingPayload + } + + switch pl.EventType { + case GitPullRequestCreatedEventType, GitPullRequestMergedEventType, GitPullRequestUpdatedEventType: + var fpl GitPullRequestEvent + err = json.Unmarshal([]byte(payload), &fpl) + return fpl, err + case BuildCompleteEventType: + var fpl BuildCompleteEvent + err = json.Unmarshal([]byte(payload), &fpl) + return fpl, err + default: + return nil, fmt.Errorf("unknown event %s", pl.EventType) + } +} diff --git a/azuredevops/azuredevops_test.go b/azuredevops/azuredevops_test.go new file mode 100644 index 0000000..5cfc099 --- /dev/null +++ b/azuredevops/azuredevops_test.go @@ -0,0 +1,113 @@ +package azuredevops + +import ( + "log" + "net/http" + "net/http/httptest" + "os" + "testing" + + "reflect" + + "github.com/stretchr/testify/require" +) + +// NOTES: +// - Run "go test" to run tests +// - Run "gocov test | gocov report" to report on test converage by file +// - Run "gocov test | gocov annotate -" to report on all code and functions, those ,marked with "MISS" were never called +// +// or +// +// -- may be a good idea to change to output path to somewherelike /tmp +// go test -coverprofile cover.out && go tool cover -html=cover.out -o cover.html +// + +const ( + virtualDir = "/webhooks" +) + +var hook *Webhook + +func TestMain(m *testing.M) { + + // setup + var err error + hook, err = New() + if err != nil { + log.Fatal(err) + } + os.Exit(m.Run()) + // teardown +} + +func newServer(handler http.HandlerFunc) *httptest.Server { + mux := http.NewServeMux() + mux.HandleFunc(virtualDir, handler) + return httptest.NewServer(mux) +} + +func TestWebhooks(t *testing.T) { + assert := require.New(t) + tests := []struct { + name string + event Event + typ interface{} + filename string + headers http.Header + }{ + { + name: "build.complete", + event: BuildCompleteEventType, + typ: BuildCompleteEvent{}, + filename: "../testdata/azuredevops/build.complete.json", + }, + { + name: "git.pullrequest.created", + event: GitPullRequestCreatedEventType, + typ: GitPullRequestEvent{}, + filename: "../testdata/azuredevops/git.pullrequest.created.json", + }, + { + name: "git.pullrequest.merged", + event: GitPullRequestMergedEventType, + typ: GitPullRequestEvent{}, + filename: "../testdata/azuredevops/git.pullrequest.merged.json", + }, + { + name: "git.pullrequest.updated", + event: GitPullRequestUpdatedEventType, + typ: GitPullRequestEvent{}, + filename: "../testdata/azuredevops/git.pullrequest.updated.json", + }, + } + + for _, tt := range tests { + tc := tt + client := &http.Client{} + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + payload, err := os.Open(tc.filename) + assert.NoError(err) + defer func() { + _ = payload.Close() + }() + + var parseError error + var results interface{} + server := newServer(func(w http.ResponseWriter, r *http.Request) { + results, parseError = hook.Parse(r, tc.event) + }) + defer server.Close() + req, err := http.NewRequest(http.MethodPost, server.URL+virtualDir, payload) + assert.NoError(err) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + assert.NoError(err) + assert.Equal(http.StatusOK, resp.StatusCode) + assert.NoError(parseError) + assert.Equal(reflect.TypeOf(tc.typ), reflect.TypeOf(results)) + }) + } +} diff --git a/azuredevops/payload.go b/azuredevops/payload.go new file mode 100644 index 0000000..cf8e820 --- /dev/null +++ b/azuredevops/payload.go @@ -0,0 +1,193 @@ +package azuredevops + +import ( + "fmt" + "strings" + "time" +) + +// https://docs.microsoft.com/en-us/azure/devops/service-hooks/events + +// azure devops does not send an event header, this BasicEvent is provided to get the EventType + +type BasicEvent struct { + ID string `json:"id"` + EventType Event `json:"eventType"` + PublisherID string `json:"publisherId"` + Scope string `json:"scope"` + CreatedDate Date `json:"createdDate"` +} + +// git.pullrequest.* +// git.pullrequest.created +// git.pullrequest.merged +// git.pullrequest.updated + +type GitPullRequestEvent struct { + ID string `json:"id"` + EventType Event `json:"eventType"` + PublisherID string `json:"publisherId"` + Scope string `json:"scope"` + Message Message `json:"message"` + DetailedMessage Message `json:"detailedMessage"` + Resource PullRequest `json:"resource"` + ResourceVersion string `json:"resourceVersion"` + ResourceContainers interface{} `json:"resourceContainers"` + CreatedDate Date `json:"createdDate"` +} + +// build.complete + +type BuildCompleteEvent struct { + ID string `json:"id"` + EventType Event `json:"eventType"` + PublisherID string `json:"publisherId"` + Scope string `json:"scope"` + Message Message `json:"message"` + DetailedMessage Message `json:"detailedMessage"` + Resource Build `json:"resource"` + ResourceVersion string `json:"resourceVersion"` + ResourceContainers interface{} `json:"resourceContainers"` + CreatedDate Date `json:"createdDate"` +} + +// ----------------------- + +type Message struct { + Text string `json:"text"` + HTML string `json:"html"` + Markdown string `json:"markdown"` +} + +type Commit struct { + CommitID string `json:"commitId"` + URL string `json:"url"` +} + +type PullRequest struct { + Repository Repository `json:"repository"` + PullRequestID int `json:"pullRequestId"` + Status string `json:"status"` + CreatedBy User `json:"createdBy"` + CreationDate Date `json:"creationDate"` + ClosedDate Date `json:"closedDate"` + Title string `json:"title"` + Description string `json:"description"` + SourceRefName string `json:"sourceRefName"` + TargetRefName string `json:"targetRefName"` + MergeStatus string `json:"mergeStatus"` + MergeID string `json:"mergeId"` + LastMergeSourceCommit Commit `json:"lastMergeSourceCommit"` + LastMergeTargetCommit Commit `json:"lastMergeTargetCommit"` + LastMergeCommit Commit `json:"lastMergeCommit"` + Reviewers []Reviewer `json:"reviewers"` + Commits []Commit `json:"commits"` + URL string `json:"url"` +} + +type Repository struct { + ID string `json:"id"` + Name string `json:"name"` + URL string `json:"url"` + Project Project `json:"project"` + DefaultBranch string `json:"defaultBranch"` + RemoteURL string `json:"remoteUrl"` +} + +type Project struct { + ID string `json:"id"` + Name string `json:"name"` + URL string `json:"url"` + State string `json:"state"` +} + +type User struct { + ID string `json:"id"` + DisplayName string `json:"displayName"` + UniqueName string `json:"uniqueName"` + URL string `json:"url"` + ImageURL string `json:"imageUrl"` +} + +type Reviewer struct { + ReviewerURL string `json:"reviewerUrl"` + Vote int `json:"vote"` + ID string `json:"id"` + DisplayName string `json:"displayName"` + UniqueName string `json:"uniqueName"` + URL string `json:"url"` + ImageURL string `json:"imageUrl"` + IsContainer bool `json:"isContainer"` +} + +type Build struct { + URI string `json:"uri"` + ID int `json:"id"` + BuildNumber string `json:"buildNumber"` + URL string `json:"url"` + StartTime Date `json:"startTime"` + FinishTime Date `json:"finishTime"` + Reason string `json:"reason"` + Status string `json:"status"` + DropLocation string `json:"dropLocation"` + Drop Drop `json:"drop"` + Log Log `json:"log"` + SourceGetVersion string `json:"sourceGetVersion"` + LastChangedBy User `json:"lastChangedBy"` + RetainIndefinitely bool `json:"retainIndefinitely"` + HasDiagnostics bool `json:"hasDiagnostics"` + Definition BuildDefinition `json:"definition"` + Queue Queue `json:"queue"` + Requests []Request `json:"requests"` +} + +type Drop struct { + Location string `json:"location"` + Type string `json:"type"` + URL string `json:"url"` + DownloadURL string `json:"downloadUrl"` +} + +type Log struct { + Type string `json:"type"` + URL string `json:"url"` + DownloadURL string `json:"downloadUrl"` +} + +type BuildDefinition struct { + BatchSize int `json:"batchSize"` + TriggerType string `json:"triggerType"` + DefinitionType string `json:"definitionType"` + ID int `json:"id"` + Name string `json:"name"` + URL string `json:"url"` +} + +type Queue struct { + QueueType string `json:"queueType"` + ID int `json:"id"` + Name string `json:"name"` + URL string `json:"url"` +} + +type Request struct { + ID int `json:"id"` + URL string `json:"url"` + RequestedFor User `json:"requestedFor"` +} + +type Date time.Time + +func (b *Date) UnmarshalJSON(p []byte) error { + t, err := time.Parse(time.RFC3339Nano, strings.Replace(string(p), "\"", "", -1)) + if err != nil { + return err + } + *b = Date(t) + return nil +} + +func (b Date) MarshalJSON() ([]byte, error) { + stamp := fmt.Sprintf("\"%s\"", time.Time(b).Format(time.RFC3339Nano)) + return []byte(stamp), nil +} diff --git a/testdata/azuredevops/build.complete.json b/testdata/azuredevops/build.complete.json new file mode 100644 index 0000000..6cc68c6 --- /dev/null +++ b/testdata/azuredevops/build.complete.json @@ -0,0 +1,88 @@ +{ + "id": "4a5d99d6-1c75-4e53-91b9-ee80057d4ce3", + "eventType": "build.complete", + "publisherId": "tfs", + "scope": "all", + "message": { + "text": "Build ConsumerAddressModule_20150407.2 succeeded", + "html": "Build ConsumerAddressModule_20150407.2 succeeded", + "markdown": "Build [ConsumerAddressModule_20150407.2](https://dev.azure.com/fabrikam-fiber-inc/web/build.aspx?pcguid=5023c10b-bef3-41c3-bf53-686c4e34ee9e&builduri=vstfs%3a%2f%2f%2fBuild%2fBuild%2f3) succeeded" + }, + "detailedMessage": { + "text": "Build ConsumerAddressModule_20150407.2 succeeded", + "html": "Build ConsumerAddressModule_20150407.2 succeeded", + "markdown": "Build [ConsumerAddressModule_20150407.2](https://dev.azure.com/fabrikam-fiber-inc/web/build.aspx?pcguid=5023c10b-bef3-41c3-bf53-686c4e34ee9e&builduri=vstfs%3a%2f%2f%2fBuild%2fBuild%2f3) succeeded" + }, + "resource": { + "uri": "vstfs:///Build/Build/2", + "id": 2, + "buildNumber": "ConsumerAddressModule_20150407.1", + "url": "https://dev.azure.com/fabrikam-fiber-inc/DefaultCollection/71777fbc-1cf2-4bd1-9540-128c1c71f766/_apis/build-release/Builds/2", + "startTime": "2015-04-07T18:04:06.83Z", + "finishTime": "2015-04-07T18:06:10.69Z", + "reason": "manual", + "status": "succeeded", + "dropLocation": "#/3/drop", + "drop": { + "location": "#/3/drop", + "type": "container", + "url": "https://dev.azure.com/fabrikam-fiber-inc/DefaultCollection/_apis/resources/Containers/3/drop", + "downloadUrl": "https://dev.azure.com/fabrikam-fiber-inc/DefaultCollection/_apis/resources/Containers/3/drop?api-version=1.0&$format=zip&downloadFileName=ConsumerAddressModule_20150407.1_drop" + }, + "log": { + "type": "container", + "url": "https://dev.azure.com/fabrikam-fiber-inc/DefaultCollection/_apis/resources/Containers/3/logs", + "downloadUrl": "https://dev.azure.com/fabrikam-fiber-inc/_apis/resources/Containers/3/logs?api-version=1.0&$format=zip&downloadFileName=ConsumerAddressModule_20150407.1_logs" + }, + "sourceGetVersion": "LG:refs/heads/master:600c52d2d5b655caa111abfd863e5a9bd304bb0e", + "lastChangedBy": { + "id": "d6245f20-2af8-44f4-9451-8107cb2767db", + "displayName": "Normal Paulk", + "uniqueName": "fabrikamfiber16@hotmail.com", + "url": "https://dev.azure.com/fabrikam-fiber-inc/_apis/Identities/d6245f20-2af8-44f4-9451-8107cb2767db", + "imageUrl": "https://dev.azure.com/fabrikam-fiber-inc/DefaultCollection/_api/_common/identityImage?id=d6245f20-2af8-44f4-9451-8107cb2767db" + }, + "retainIndefinitely": false, + "hasDiagnostics": true, + "definition": { + "batchSize": 1, + "triggerType": "none", + "definitionType": "xaml", + "id": 2, + "name": "ConsumerAddressModule", + "url": "https://dev.azure.com/fabrikam-fiber-inc/DefaultCollection/71777fbc-1cf2-4bd1-9540-128c1c71f766/_apis/build-release/Definitions/2" + }, + "queue": { + "queueType": "buildController", + "id": 4, + "name": "Hosted Build Controller", + "url": "https://dev.azure.com/fabrikam-fiber-inc/DefaultCollection/_apis/build-release/Queues/4" + }, + "requests": [ + { + "id": 1, + "url": "https://dev.azure.com/fabrikam-fiber-inc/DefaultCollection/71777fbc-1cf2-4bd1-9540-128c1c71f766/_apis/build-release/Requests/1", + "requestedFor": { + "id": "d6245f20-2af8-44f4-9451-8107cb2767db", + "displayName": "Normal Paulk", + "uniqueName": "fabrikamfiber16@hotmail.com", + "url": "https://dev.azure.com/fabrikam-fiber-inc/_apis/Identities/d6245f20-2af8-44f4-9451-8107cb2767db", + "imageUrl": "https://dev.azure.com/fabrikam-fiber-inc/DefaultCollection/_api/_common/identityImage?id=d6245f20-2af8-44f4-9451-8107cb2767db" + } + } + ] + }, + "resourceVersion": "1.0", + "resourceContainers": { + "collection": { + "id": "c12d0eb8-e382-443b-9f9c-c52cba5014c2" + }, + "account": { + "id": "f844ec47-a9db-4511-8281-8b63f4eaf94e" + }, + "project": { + "id": "be9b3917-87e6-42a4-a549-2bc06a7a878f" + } + }, + "createdDate": "2016-09-19T13:03:26.4285528Z" + } \ No newline at end of file diff --git a/testdata/azuredevops/git.pullrequest.created.json b/testdata/azuredevops/git.pullrequest.created.json new file mode 100644 index 0000000..5f6771a --- /dev/null +++ b/testdata/azuredevops/git.pullrequest.created.json @@ -0,0 +1,85 @@ +{ + "id": "2ab4e3d3-b7a6-425e-92b1-5a9982c1269e", + "eventType": "git.pullrequest.created", + "publisherId": "tfs", + "scope": "all", + "message": { + "text": "Jamal Hartnett created a new pull request", + "html": "Jamal Hartnett created a new pull request", + "markdown": "Jamal Hartnett created a new pull request" + }, + "detailedMessage": { + "text": "Jamal Hartnett created a new pull request\r\n\r\n- Merge status: Succeeded\r\n- Merge commit: eef717(https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/eef717f69257a6333f221566c1c987dc94cc0d72)\r\n", + "html": "Jamal Hartnett created a new pull request\r\n", + "markdown": "Jamal Hartnett created a new pull request\r\n\r\n+ Merge status: Succeeded\r\n+ Merge commit: [eef717](https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/eef717f69257a6333f221566c1c987dc94cc0d72)\r\n" + }, + "resource": { + "repository": { + "id": "4bc14d40-c903-45e2-872e-0462c7748079", + "name": "Fabrikam", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079", + "project": { + "id": "6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", + "name": "Fabrikam", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/projects/6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", + "state": "wellFormed" + }, + "defaultBranch": "refs/heads/master", + "remoteUrl": "https://dev.azure.com/fabrikam/DefaultCollection/_git/Fabrikam" + }, + "pullRequestId": 1, + "status": "active", + "createdBy": { + "id": "54d125f7-69f7-4191-904f-c5b96b6261c8", + "displayName": "Jamal Hartnett", + "uniqueName": "fabrikamfiber4@hotmail.com", + "url": "https://vssps.dev.azure.com/fabrikam/_apis/Identities/54d125f7-69f7-4191-904f-c5b96b6261c8", + "imageUrl": "https://dev.azure.com/fabrikam/DefaultCollection/_api/_common/identityImage?id=54d125f7-69f7-4191-904f-c5b96b6261c8" + }, + "creationDate": "2014-06-17T16:55:46.589889Z", + "title": "my first pull request", + "description": " - test2\r\n", + "sourceRefName": "refs/heads/mytopic", + "targetRefName": "refs/heads/master", + "mergeStatus": "succeeded", + "mergeId": "a10bb228-6ba6-4362-abd7-49ea21333dbd", + "lastMergeSourceCommit": { + "commitId": "53d54ac915144006c2c9e90d2c7d3880920db49c", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/53d54ac915144006c2c9e90d2c7d3880920db49c" + }, + "lastMergeTargetCommit": { + "commitId": "a511f535b1ea495ee0c903badb68fbc83772c882", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/a511f535b1ea495ee0c903badb68fbc83772c882" + }, + "lastMergeCommit": { + "commitId": "eef717f69257a6333f221566c1c987dc94cc0d72", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/eef717f69257a6333f221566c1c987dc94cc0d72" + }, + "reviewers": [ + { + "reviewerUrl": null, + "vote": 0, + "id": "2ea2d095-48f9-4cd6-9966-62f6f574096c", + "displayName": "[Mobile]\\Mobile Team", + "uniqueName": "vstfs:///Classification/TeamProject/f0811a3b-8c8a-4e43-a3bf-9a049b4835bd\\Mobile Team", + "url": "https://vssps.dev.azure.com/fabrikam/_apis/Identities/2ea2d095-48f9-4cd6-9966-62f6f574096c", + "imageUrl": "https://dev.azure.com/fabrikam/DefaultCollection/_api/_common/identityImage?id=2ea2d095-48f9-4cd6-9966-62f6f574096c", + "isContainer": true + } + ], + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/pullRequests/1" + }, + "resourceVersion": "1.0", + "resourceContainers": { + "collection": { + "id": "c12d0eb8-e382-443b-9f9c-c52cba5014c2" + }, + "account": { + "id": "f844ec47-a9db-4511-8281-8b63f4eaf94e" + }, + "project": { + "id": "be9b3917-87e6-42a4-a549-2bc06a7a878f" + } + }, + "createdDate": "2016-09-19T13:03:27.2879096Z" +} \ No newline at end of file diff --git a/testdata/azuredevops/git.pullrequest.merged.json b/testdata/azuredevops/git.pullrequest.merged.json new file mode 100644 index 0000000..0162d0c --- /dev/null +++ b/testdata/azuredevops/git.pullrequest.merged.json @@ -0,0 +1,86 @@ +{ + "id": "6872ee8c-b333-4eff-bfb9-0d5274943566", + "eventType": "git.pullrequest.merged", + "publisherId": "tfs", + "scope": "all", + "message": { + "text": "Jamal Hartnett has created a pull request merge commit", + "html": "Jamal Hartnett has created a pull request merge commit", + "markdown": "Jamal Hartnett has created a pull request merge commit" + }, + "detailedMessage": { + "text": "Jamal Hartnett has created a pull request merge commit\r\n\r\n- Merge status: Succeeded\r\n- Merge commit: eef717(https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/eef717f69257a6333f221566c1c987dc94cc0d72)\r\n", + "html": "Jamal Hartnett has created a pull request merge commit\r\n", + "markdown": "Jamal Hartnett has created a pull request merge commit\r\n\r\n+ Merge status: Succeeded\r\n+ Merge commit: [eef717](https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/eef717f69257a6333f221566c1c987dc94cc0d72)\r\n" + }, + "resource": { + "repository": { + "id": "4bc14d40-c903-45e2-872e-0462c7748079", + "name": "Fabrikam", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079", + "project": { + "id": "6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", + "name": "Fabrikam", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/projects/6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", + "state": "wellFormed" + }, + "defaultBranch": "refs/heads/master", + "remoteUrl": "https://dev.azure.com/fabrikam/DefaultCollection/_git/Fabrikam" + }, + "pullRequestId": 1, + "status": "completed", + "createdBy": { + "id": "54d125f7-69f7-4191-904f-c5b96b6261c8", + "displayName": "Jamal Hartnett", + "uniqueName": "fabrikamfiber4@hotmail.com", + "url": "https://vssps.dev.azure.com/fabrikam/_apis/Identities/54d125f7-69f7-4191-904f-c5b96b6261c8", + "imageUrl": "https://dev.azure.com/fabrikam/DefaultCollection/_api/_common/identityImage?id=54d125f7-69f7-4191-904f-c5b96b6261c8" + }, + "creationDate": "2014-06-17T16:55:46.589889Z", + "closedDate": "2014-06-30T18:59:12.3660573Z", + "title": "my first pull request", + "description": " - test2\r\n", + "sourceRefName": "refs/heads/mytopic", + "targetRefName": "refs/heads/master", + "mergeStatus": "succeeded", + "mergeId": "a10bb228-6ba6-4362-abd7-49ea21333dbd", + "lastMergeSourceCommit": { + "commitId": "53d54ac915144006c2c9e90d2c7d3880920db49c", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/53d54ac915144006c2c9e90d2c7d3880920db49c" + }, + "lastMergeTargetCommit": { + "commitId": "a511f535b1ea495ee0c903badb68fbc83772c882", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/a511f535b1ea495ee0c903badb68fbc83772c882" + }, + "lastMergeCommit": { + "commitId": "eef717f69257a6333f221566c1c987dc94cc0d72", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/eef717f69257a6333f221566c1c987dc94cc0d72" + }, + "reviewers": [ + { + "reviewerUrl": null, + "vote": 0, + "id": "2ea2d095-48f9-4cd6-9966-62f6f574096c", + "displayName": "[Mobile]\\Mobile Team", + "uniqueName": "vstfs:///Classification/TeamProject/f0811a3b-8c8a-4e43-a3bf-9a049b4835bd\\Mobile Team", + "url": "https://vssps.dev.azure.com/fabrikam/_apis/Identities/2ea2d095-48f9-4cd6-9966-62f6f574096c", + "imageUrl": "https://dev.azure.com/fabrikam/DefaultCollection/_api/_common/identityImage?id=2ea2d095-48f9-4cd6-9966-62f6f574096c", + "isContainer": true + } + ], + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/pullRequests/1" + }, + "resourceVersion": "1.0", + "resourceContainers": { + "collection": { + "id": "c12d0eb8-e382-443b-9f9c-c52cba5014c2" + }, + "account": { + "id": "f844ec47-a9db-4511-8281-8b63f4eaf94e" + }, + "project": { + "id": "be9b3917-87e6-42a4-a549-2bc06a7a878f" + } + }, + "createdDate": "2016-09-19T13:03:27.3156388Z" +} \ No newline at end of file diff --git a/testdata/azuredevops/git.pullrequest.updated.json b/testdata/azuredevops/git.pullrequest.updated.json new file mode 100644 index 0000000..25115a7 --- /dev/null +++ b/testdata/azuredevops/git.pullrequest.updated.json @@ -0,0 +1,92 @@ +{ + "id": "af07be1b-f3ad-44c8-a7f1-c4835f2df06b", + "eventType": "git.pullrequest.updated", + "publisherId": "tfs", + "scope": "all", + "message": { + "text": "Jamal Hartnett marked the pull request as completed", + "html": "Jamal Hartnett marked the pull request as completed", + "markdown": "Jamal Hartnett marked the pull request as completed" + }, + "detailedMessage": { + "text": "Jamal Hartnett marked the pull request as completed\r\n\r\n- Merge status: Succeeded\r\n- Merge commit: eef717(https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/eef717f69257a6333f221566c1c987dc94cc0d72)\r\n", + "html": "Jamal Hartnett marked the pull request as completed\r\n", + "markdown": "Jamal Hartnett marked the pull request as completed\r\n\r\n+ Merge status: Succeeded\r\n+ Merge commit: [eef717](https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/eef717f69257a6333f221566c1c987dc94cc0d72)\r\n" + }, + "resource": { + "repository": { + "id": "4bc14d40-c903-45e2-872e-0462c7748079", + "name": "Fabrikam", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079", + "project": { + "id": "6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", + "name": "Fabrikam", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/projects/6ce954b1-ce1f-45d1-b94d-e6bf2464ba2c", + "state": "wellFormed" + }, + "defaultBranch": "refs/heads/master", + "remoteUrl": "https://dev.azure.com/fabrikam/DefaultCollection/_git/Fabrikam" + }, + "pullRequestId": 1, + "status": "completed", + "createdBy": { + "id": "54d125f7-69f7-4191-904f-c5b96b6261c8", + "displayName": "Jamal Hartnett", + "uniqueName": "fabrikamfiber4@hotmail.com", + "url": "https://vssps.dev.azure.com/fabrikam/_apis/Identities/54d125f7-69f7-4191-904f-c5b96b6261c8", + "imageUrl": "https://dev.azure.com/fabrikam/DefaultCollection/_api/_common/identityImage?id=54d125f7-69f7-4191-904f-c5b96b6261c8" + }, + "creationDate": "2014-06-17T16:55:46.589889Z", + "closedDate": "2014-06-30T18:59:12.3660573Z", + "title": "my first pull request", + "description": " - test2\r\n", + "sourceRefName": "refs/heads/mytopic", + "targetRefName": "refs/heads/master", + "mergeStatus": "succeeded", + "mergeId": "a10bb228-6ba6-4362-abd7-49ea21333dbd", + "lastMergeSourceCommit": { + "commitId": "53d54ac915144006c2c9e90d2c7d3880920db49c", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/53d54ac915144006c2c9e90d2c7d3880920db49c" + }, + "lastMergeTargetCommit": { + "commitId": "a511f535b1ea495ee0c903badb68fbc83772c882", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/a511f535b1ea495ee0c903badb68fbc83772c882" + }, + "lastMergeCommit": { + "commitId": "eef717f69257a6333f221566c1c987dc94cc0d72", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/eef717f69257a6333f221566c1c987dc94cc0d72" + }, + "reviewers": [ + { + "reviewerUrl": null, + "vote": 0, + "id": "2ea2d095-48f9-4cd6-9966-62f6f574096c", + "displayName": "[Mobile]\\Mobile Team", + "uniqueName": "vstfs:///Classification/TeamProject/f0811a3b-8c8a-4e43-a3bf-9a049b4835bd\\Mobile Team", + "url": "https://vssps.dev.azure.com/fabrikam/_apis/Identities/2ea2d095-48f9-4cd6-9966-62f6f574096c", + "imageUrl": "https://dev.azure.com/fabrikam/DefaultCollection/_api/_common/identityImage?id=2ea2d095-48f9-4cd6-9966-62f6f574096c", + "isContainer": true + } + ], + "commits": [ + { + "commitId": "53d54ac915144006c2c9e90d2c7d3880920db49c", + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/commits/53d54ac915144006c2c9e90d2c7d3880920db49c" + } + ], + "url": "https://dev.azure.com/fabrikam/DefaultCollection/_apis/repos/git/repositories/4bc14d40-c903-45e2-872e-0462c7748079/pullRequests/1" + }, + "resourceVersion": "1.0", + "resourceContainers": { + "collection": { + "id": "c12d0eb8-e382-443b-9f9c-c52cba5014c2" + }, + "account": { + "id": "f844ec47-a9db-4511-8281-8b63f4eaf94e" + }, + "project": { + "id": "be9b3917-87e6-42a4-a549-2bc06a7a878f" + } + }, + "createdDate": "2016-09-19T13:03:27.2813828Z" +} \ No newline at end of file