From 3efcd8feee50eb184662674bc8964f8693e9be54 Mon Sep 17 00:00:00 2001 From: John Reese Date: Fri, 10 Apr 2020 16:27:04 -0400 Subject: [PATCH] Add merge policy --- server/events/vcs/azuredevops_client.go | 8 ++- server/events/vcs/azuredevops_client_test.go | 66 +++++++++++-------- .../azuredevops-policyevaluations.json | 16 +++++ .../events/vcs/fixtures/azuredevops-pr.json | 21 ++++-- 4 files changed, 73 insertions(+), 38 deletions(-) create mode 100644 server/events/vcs/fixtures/azuredevops-policyevaluations.json diff --git a/server/events/vcs/azuredevops_client.go b/server/events/vcs/azuredevops_client.go index 220363f04..01bddab98 100644 --- a/server/events/vcs/azuredevops_client.go +++ b/server/events/vcs/azuredevops_client.go @@ -6,6 +6,7 @@ import ( "net/http" "net/url" "path/filepath" + "strconv" "strings" "time" @@ -176,13 +177,16 @@ func (g *AzureDevopsClient) PullIsMergeable(repo models.Repo, pull models.PullRe } projectID := *adPull.Repository.Project.ID - artifactID := g.Client.PolicyEvaluations.GetPullRequestArtifactID(projectID, string(pull.Num)) + artifactID := g.Client.PolicyEvaluations.GetPullRequestArtifactID(projectID, strconv.Itoa(pull.Num)) policyEvaluations, _, err := g.Client.PolicyEvaluations.List(g.ctx, owner, project, artifactID, &azuredevops.PolicyEvaluationsListOptions{}) if err != nil { return false, fmt.Errorf("list policy evaluations: %w", err) } for _, policyEvaluation := range policyEvaluations { + if *policyEvaluation.Configuration.IsEnabled == false || *policyEvaluation.Configuration.IsDeleted { + continue + } // Ignore the Atlantis status, even if its set as a blocker. // This status should not be considered when evaluating if the pull request can be applied. @@ -191,7 +195,7 @@ func (g *AzureDevopsClient) PullIsMergeable(repo models.Repo, pull models.PullRe continue } - if *policyEvaluation.Configuration.IsBlocking && *policyEvaluation.Status != "succeeded" { + if *policyEvaluation.Configuration.IsBlocking && *policyEvaluation.Status != "approved" { return false, nil } } diff --git a/server/events/vcs/azuredevops_client_test.go b/server/events/vcs/azuredevops_client_test.go index b6e4be107..6791cbefd 100644 --- a/server/events/vcs/azuredevops_client_test.go +++ b/server/events/vcs/azuredevops_client_test.go @@ -278,53 +278,59 @@ func TestAzureDevopsClient_GetModifiedFiles(t *testing.T) { func TestAzureDevopsClient_PullIsMergeable(t *testing.T) { cases := []struct { - state string + testName string + mergeStatus string + policyStatus string expMergeable bool }{ { + "merge conflicts", azuredevops.MergeConflicts.String(), + "approved", false, }, { - azuredevops.MergeRejectedByPolicy.String(), - false, - }, - { - azuredevops.MergeFailure.String(), - true, - }, - { - azuredevops.MergeNotSet.String(), - true, - }, - { - azuredevops.MergeQueued.String(), - true, - }, - { + "rejected policy status", azuredevops.MergeSucceeded.String(), + "rejected", + false, + }, + { + "rejected policy status on disabled policy", + azuredevops.MergeSucceeded.String(), + "rejected", + true, + }, + { + "merge succeeded", + azuredevops.MergeSucceeded.String(), + "approved", true, }, } - // Use a real Azure DevOps json response and edit the mergeable_state field. - jsBytes, err := ioutil.ReadFile("fixtures/azuredevops-pr.json") + jsonPullRequestBytes, err := ioutil.ReadFile("fixtures/azuredevops-pr.json") Ok(t, err) - json := string(jsBytes) + + jsonPolicyEvaluationBytes, err := ioutil.ReadFile("fixtures/azuredevops-policyevaluations.json") + Ok(t, err) + + pullRequestBody := string(jsonPullRequestBytes) + policyEvaluationsBody := string(jsonPolicyEvaluationBytes) for _, c := range cases { - t.Run(c.state, func(t *testing.T) { - response := strings.Replace(json, - `"mergeStatus": "NotSet"`, - fmt.Sprintf(`"mergeStatus": "%s"`, c.state), - 1, - ) + t.Run(c.testName, func(t *testing.T) { + pullRequestResponse := strings.Replace(pullRequestBody, `"mergeStatus": "notSet"`, fmt.Sprintf(`"mergeStatus": "%s"`, c.mergeStatus), 1) + policyEvaluationsResponse := strings.Replace(policyEvaluationsBody, `"status": "approved"`, fmt.Sprintf(`"status": "%s"`, c.policyStatus), 1) testServer := httptest.NewTLSServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.RequestURI { case "/owner/project/_apis/git/repositories/repo/pullrequests/1?api-version=5.1-preview.1&includeWorkItemRefs=true": - w.Write([]byte(response)) // nolint: errcheck + w.Write([]byte(pullRequestResponse)) // nolint: errcheck + return + case "/owner/project/_apis/policy/evaluations?api-version=5.1-preview&artifactId=vstfs%3A%2F%2F%2FCodeReview%2FCodeReviewId%2F33333333-3333-3333-333333333333%2F1": + w.Write([]byte(policyEvaluationsResponse)) // nolint: errcheck return default: t.Errorf("got unexpected request at %q", r.RequestURI) @@ -332,10 +338,13 @@ func TestAzureDevopsClient_PullIsMergeable(t *testing.T) { return } })) + testServerURL, err := url.Parse(testServer.URL) Ok(t, err) + client, err := vcs.NewAzureDevopsClient(testServerURL.Host, "token") Ok(t, err) + defer disableSSLVerification()() actMergeable, err := client.PullIsMergeable(models.Repo{ @@ -402,11 +411,10 @@ func TestAzureDevopsClient_PullIsApproved(t *testing.T) { }, } - // Use a real Azure DevOps json response and edit the mergeable_state field. jsBytes, err := ioutil.ReadFile("fixtures/azuredevops-pr.json") Ok(t, err) - json := string(jsBytes) + json := string(jsBytes) for _, c := range cases { t.Run(c.testName, func(t *testing.T) { response := strings.Replace(json, `"vote": 0,`, fmt.Sprintf(`"vote": %d,`, c.reviewerVote), 1) diff --git a/server/events/vcs/fixtures/azuredevops-policyevaluations.json b/server/events/vcs/fixtures/azuredevops-policyevaluations.json new file mode 100644 index 000000000..c44821ff7 --- /dev/null +++ b/server/events/vcs/fixtures/azuredevops-policyevaluations.json @@ -0,0 +1,16 @@ +{ + "value": [ + { + "configuration": { + "isDeleted": false, + "isEnabled": true, + "isBlocking": true, + "settings": { + "statusName": "pending" + } + }, + "status": "approved" + } + ], + "count": 1 +} \ No newline at end of file diff --git a/server/events/vcs/fixtures/azuredevops-pr.json b/server/events/vcs/fixtures/azuredevops-pr.json index 4de0089e6..ca5f44f44 100644 --- a/server/events/vcs/fixtures/azuredevops-pr.json +++ b/server/events/vcs/fixtures/azuredevops-pr.json @@ -1,18 +1,25 @@ { - "status": "completed", + "repository": { + "id": "22222222-2222-2222-222222222222", + "name": "MyRepository", + "project": { + "id": "33333333-3333-3333-333333333333", + "name": "MyProject", + "description": "The place for MyProject" + } + }, + "status": "active", "createdBy": { "displayName": "Atlantis Author", - "url": "https://spsprodeus23.vssps.visualstudio.com/11111111-2222-3333-444444444444/_apis/Identities/55555555-6666-7777-888888888888", - "id": "11111111-2222-3333-444444444444", + "id": "11111111-1111-1111-111111111111", "uniqueName": "atlantis.author@example.com" }, "mergeStatus": "notSet", + "isDraft": false, "autoCompleteSetBy": { - "id": "11111111-2222-3333-444444444444", + "id": "11111111-1111-1111-111111111111", "displayName": "Atlantis Author", - "uniqueName": "atlantis.author@example.com", - "url": "https://spsprodeus23.vssps.visualstudio.com/11111111-2222-3333-444444444444/_apis/Identities/55555555-6666-7777-888888888888", - "imageUrl": "https://dev.azure.com/owner/DefaultCollection/_api/_common/identityImage?id=8010495e-1002-438d-acbf-aaf245dac7c2" + "uniqueName": "atlantis.author@example.com" }, "pullRequestId": 22, "completionOptions": {