Skip to content

Commit

Permalink
Added support for merging pull requests and checking a pull requests …
Browse files Browse the repository at this point in the history
…status via the API. (#38)

* Added merge function to the pull request api.

* Added Fetching of 'pull-request merge status' and 'pull request merging' to the api. Modified HttpCommunication worker exception handling so it added the response from the server to any exceptions that occur during parsing.

* Created pull-request status and merging test cases however the current test setup is not appropriate for pull-request merging so the merge test checks two different cases a merge and a failed merge. Added specific exception for failed merges.

* Renamed Test case to match existing style.
  • Loading branch information
glennc1 authored and jlouros committed Nov 23, 2017
1 parent fab26c5 commit 53646f9
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 12 deletions.
4 changes: 2 additions & 2 deletions Atlassian.Stash.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atlassian.Stash.Net45", "src\Atlassian.Stash.Net45\Atlassian.Stash.Net45.csproj", "{7D240671-3DA9-47A8-9B6C-620721A2F92A}"
EndProject
Expand Down Expand Up @@ -36,9 +36,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".docs", ".docs", "{6D9B80C2
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Atlassian.Stash\Atlassian.Stash.projitems*{7d240671-3da9-47a8-9b6c-620721a2f92a}*SharedItemsImports = 4
src\Atlassian.Stash\Atlassian.Stash.projitems*{1eacbefb-9384-41ca-9e5a-bd6e446af528}*SharedItemsImports = 4
src\Atlassian.Stash\Atlassian.Stash.projitems*{5f0023ae-5f95-4244-b56b-46259a5fb649}*SharedItemsImports = 13
src\Atlassian.Stash\Atlassian.Stash.projitems*{7d240671-3da9-47a8-9b6c-620721a2f92a}*SharedItemsImports = 4
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down
39 changes: 38 additions & 1 deletion src/Atlassian.Stash/Api/PullRequests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using Atlassian.Stash.Entities;
using System;
using Atlassian.Stash.Entities;
using Atlassian.Stash.Helpers;
using Atlassian.Stash.Workers;
using System.Threading.Tasks;
using Atlassian.Stash.Api.Entities;
using Newtonsoft.Json;
using Atlassian.Stash.Api.Exceptions;

namespace Atlassian.Stash.Api
{
Expand All @@ -21,6 +25,9 @@ public enum Direction
}

private const string PULL_REQUEST = "rest/api/1.0/projects/{0}/repos/{1}/pull-requests";
private const string PULL_REQUEST_MERGEABLE = "rest/api/1.0/projects/{0}/repos/{1}/pull-requests/{2}/merge";
private const string PULL_REQUEST_MERGE = "rest/api/1.0/projects/{0}/repos/{1}/pull-requests/{2}/merge?version={3}";

private HttpCommunicationWorker _httpWorker;

internal PullRequests(HttpCommunicationWorker httpWorker)
Expand All @@ -37,6 +44,36 @@ public async Task<PullRequest> Create(string projectKey, string repositorySlug,
return pr;
}

public async Task<PullRequestStatus> Status(PullRequest pullRequest, string projectKey)
{
string requestUrl = UrlBuilder.FormatRestApiUrl(PULL_REQUEST_MERGEABLE, null, projectKey,
pullRequest.FromRef.Repository.Slug, pullRequest.Id);

PullRequestStatus pr = await _httpWorker.GetAsync<PullRequestStatus>(requestUrl).ConfigureAwait(false);

return pr;
}

public async Task<PullRequest> Merge(PullRequest pullRequest, string projectKey)
{
string requestUrl = UrlBuilder.FormatRestApiUrl(PULL_REQUEST_MERGE, null, projectKey,
pullRequest.FromRef.Repository.Slug, pullRequest.Id, pullRequest.Version);
try
{
PullRequest pr = await _httpWorker.PostAsync<PullRequest>(requestUrl,null).ConfigureAwait(false);
return pr;
}
catch (Exception ex)
{
if (ex.Data["json"] != null)
{
MergeErrorResponse error = JsonConvert.DeserializeObject<MergeErrorResponse>((string)ex.Data["json"]);
throw new StashMergeException("Merge failure",ex, error);
}
throw;
}
}

public async Task<ResponseWrapper<PullRequest>> Get(string projectKey, string repositorySlug, RequestOptions options = null, Direction direction = Direction.INCOMING,
PullRequestState state = PullRequestState.OPEN, bool withAttributes = true, bool withProperties = true)
{
Expand Down
3 changes: 3 additions & 0 deletions src/Atlassian.Stash/Atlassian.Stash.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Entities\Changes.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\Clone.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\Commit.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\MergeErrorResponse.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\File.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\Fork.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\Group.cs" />
Expand All @@ -39,11 +40,13 @@
<Compile Include="$(MSBuildThisFileDirectory)Entities\Project.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\PullRequest.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\PullRequestSettings.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\PullRequestStatus.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\Ref.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\Repository.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\Self.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\Tag.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Entities\User.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Exceptions\StashMergeException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\DateTimeHelpers.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\RequestOptions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Helpers\RequestOptionsForCommits.cs" />
Expand Down
27 changes: 27 additions & 0 deletions src/Atlassian.Stash/Entities/MergeErrorResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;

namespace Atlassian.Stash.Api.Entities
{
public class MergeErrorResponse
{
[JsonProperty("errors")]
public IEnumerable<MergeError> Errors { get; set; }
}

public class MergeError
{
[JsonProperty("context")]
public string Context { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("exceptionName")]
public string ExceptionName { get; set; }
[JsonProperty("conflicted")]
public bool Conflicted { get; set; }
[JsonProperty("vetoes")]
public IEnumerable<Vetoe> Vetoes { get; set; }
}
}
33 changes: 33 additions & 0 deletions src/Atlassian.Stash/Entities/PullRequestStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Text;
using Atlassian.Stash.Entities;
using Newtonsoft.Json;

namespace Atlassian.Stash.Api.Entities
{
public class PullRequestStatus
{
[JsonProperty("canMerge")]
public bool CanMerge { get; set; }

[JsonProperty("conflicted")]
public bool RequiredAllApprovers { get; set; }

[JsonProperty("outcome")]
public string Outcome { get; set; }

[JsonProperty("vetoes")]
public IEnumerable<Vetoe> Vetoes { get; set; }
}

public class Vetoe
{
[JsonProperty("summaryMessage")]
public string SummaryMessage { get; set; }

[JsonProperty("detailedMessage")]
public string DetailedMessage { get; set; }
}
}
22 changes: 22 additions & 0 deletions src/Atlassian.Stash/Exceptions/StashMergeException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Text;
using Atlassian.Stash.Api.Entities;

namespace Atlassian.Stash.Api.Exceptions
{
public class StashMergeException : Exception
{
public MergeErrorResponse ErrorResponse { get; set; }

public StashMergeException(string message, Exception exc, MergeErrorResponse response) : base(message,exc)
{
ErrorResponse = response;
}

public StashMergeException(string message, MergeErrorResponse response) : base(message)
{
ErrorResponse = response;
}
}
}
22 changes: 15 additions & 7 deletions src/Atlassian.Stash/Workers/HttpCommunicationWorker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Atlassian.Stash.Api.Entities;
using Atlassian.Stash.Api.Exceptions;

namespace Atlassian.Stash.Workers
{
Expand Down Expand Up @@ -64,10 +66,15 @@ public async Task<T> GetAsync<T>(string requestUrl)
using (HttpResponseMessage httpResponse = await httpClient.GetAsync(requestUrl).ConfigureAwait(false))
{
string json = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);

T response = JsonConvert.DeserializeObject<T>(json);

return response;
try
{
return JsonConvert.DeserializeObject<T>(json);
}
catch (Exception ex)
{
ex.Data["json"] = json;
throw;
}
}
}

Expand All @@ -93,18 +100,19 @@ public async Task<T> PostAsync<T>(string requestUrl, T data, bool ignoreNullFiel
using (HttpClient httpClient = CreateHttpClient())
using (HttpResponseMessage httpResponse = await httpClient.PostAsync(requestUrl, contentToPost))
{
string json = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
if (httpResponse.StatusCode != HttpStatusCode.Created && httpResponse.StatusCode != HttpStatusCode.OK && httpResponse.StatusCode != HttpStatusCode.NoContent)
{
throw new Exception(string.Format("POST operation unsuccessful. Got HTTP status code '{0}'", httpResponse.StatusCode));
var exc = new Exception(string.Format("POST operation unsuccessful. Got HTTP status code '{0}'", httpResponse.StatusCode));
exc.Data["json"] = json;
throw exc;
}

if (httpResponse.StatusCode == HttpStatusCode.NoContent)
{
return default(T);
}

string json = await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(false);

T response = JsonConvert.DeserializeObject<T>(json);

return response;
Expand Down
4 changes: 2 additions & 2 deletions test/Atlassian.Stash.IntegrationTests/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
<add key="existing-file" value="test.txt" />
<add key="existing-file-in-subfolder" value="folder/test.txt" />
<add key="existing-file-in-subfolder-with-spaces" value="my folder/my test.txt" />
<add key="existing-commit" value="ac172307a4de26474b7cae5ffafdd9cf3e05ccce" />
<add key="existing-older-commit" value="218e30006e07b6d788e9c139db109f78da41bc92" />
<add key="existing-commit" value="ef69fc055ea7f5d854dba1a3389e39b025c6a559" />
<add key="existing-older-commit" value="ef69fc055ea7f5d854dba1a3389e39b025c6a559" />
<add key="existing-number-of-changes" value="1" />
<add key="existing-branch-reference" value="refs/heads/master" />
<add key="existing-group" value="TestGroup" />
Expand Down
54 changes: 54 additions & 0 deletions test/Atlassian.Stash.IntegrationTests/StashClientTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Atlassian.Stash.Api.Entities;
using Atlassian.Stash.Api.Exceptions;

namespace Atlassian.Stash.IntegrationTests
{
Expand Down Expand Up @@ -679,5 +682,56 @@ public async Task Can_Get_Then_Create_Then_Grant_Access_To_Project_And_Delete_Us
Assert.IsInstanceOfType(deletedUser, typeof(User));
Assert.AreEqual("tmpTestUser", deletedUser.Name);
}

[TestMethod]
public async Task Can_GetPullRequestStatus()
{
var pullrequests = await stashClient.PullRequests.Get(EXISTING_PROJECT, EXISTING_REPOSITORY);
var request = pullrequests.Values.First();
var status = await stashClient.PullRequests.Status(request, EXISTING_PROJECT);

Assert.IsNotNull(status);
}

// TO DO: Setup a proper way of creating a testing envrionment for merging pull requests so as to
// avoid the weird error handling in this test class due to not knowing if the tester created a mergable pull request.
// TO DO: Split this test case up into three seperate test cases
[TestMethod]
public async Task Merge_PullRequest()
{
var pullrequests = await stashClient.PullRequests.Get(EXISTING_PROJECT, EXISTING_REPOSITORY);
var request = pullrequests.Values.First();
var status = await stashClient.PullRequests.Status(request, EXISTING_PROJECT);

Assert.IsNotNull(status);
PullRequest requestMerged = null;
MergeErrorResponse stashError = null;
try
{
requestMerged = await stashClient.PullRequests.Merge(request, EXISTING_PROJECT);
}
catch (StashMergeException exc)
{
stashError = exc.ErrorResponse;
}

if (stashError == null)
{
Console.WriteLine("Pull Request Merged");
Assert.IsNotNull(requestMerged);
Assert.AreEqual(true, status.CanMerge);
Assert.AreEqual(status.CanMerge, requestMerged.State == PullRequestState.MERGED);
}
else
{
Console.WriteLine("Pull Request failed to merge");
Assert.AreEqual(false, status.CanMerge);
foreach (var error in stashError.Errors)
{
Assert.AreEqual(true,error.Conflicted);
}
}

}
}
}

0 comments on commit 53646f9

Please sign in to comment.