From aa2ffafa6acef420eaecd7f4e873d135dc3a08f6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:45:05 +0000 Subject: [PATCH 1/2] Initial plan From 8e3700fe0031c2da2436574be75af2b5025931ae Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 13 Sep 2026 11:47:11 +0000 Subject: [PATCH 2/2] Map unknown Azure DevOps PR status to hosting exception Co-authored-by: matt-edmondson <19528727+matt-edmondson@users.noreply.github.com> --- .../Hosting/AzureDevOpsProviderTests.cs | 21 +++++++++++++++ GitIntegration/Hosting/AzureDevOpsProvider.cs | 26 ++++++++++++------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/GitIntegration.Test/Hosting/AzureDevOpsProviderTests.cs b/GitIntegration.Test/Hosting/AzureDevOpsProviderTests.cs index e1b7274..7621682 100644 --- a/GitIntegration.Test/Hosting/AzureDevOpsProviderTests.cs +++ b/GitIntegration.Test/Hosting/AzureDevOpsProviderTests.cs @@ -733,6 +733,27 @@ public async Task MapsActiveCompletedAndAbandonedAsync() } } + [TestMethod] + public async Task TranslatesAnUnrecognisedPullRequestStatusToGitHostingRequestExceptionAsync() + { + using FakeHttpMessageHandler handler = new FakeHttpMessageHandler() + .Respond(HttpStatusCode.OK, SinglePullRequestListResponse("notSet"), ("Content-Type", "application/json")); + AzureDevOpsProvider provider = new() + { + Owner = "contoso".As(), + Project = "ExampleProject".As(), + Handler = handler, + }; + + GitHostingRequestException exception = await Assert.ThrowsExactlyAsync( + async () => await provider.GetPullRequestsAsync("example-repo".As(), TestContext.CancellationTokenSource.Token).ConfigureAwait(false)) + .ConfigureAwait(false); + + Assert.AreEqual(HttpStatusCode.OK, exception.StatusCode); + StringAssert.Contains(exception.Message, "unrecognised pull request status"); + StringAssert.Contains(exception.ResponseBody, "\"status\": \"notSet\""); + } + [TestMethod] public async Task FetchesEveryPageOfPullRequestsAsync() { diff --git a/GitIntegration/Hosting/AzureDevOpsProvider.cs b/GitIntegration/Hosting/AzureDevOpsProvider.cs index 945cb13..0f61b82 100644 --- a/GitIntegration/Hosting/AzureDevOpsProvider.cs +++ b/GitIntegration/Hosting/AzureDevOpsProvider.cs @@ -201,7 +201,7 @@ internal override async Task> GetPullRequestsCoreA body, AzureDevOpsJsonContext.Default.AzureDevOpsPullRequestListResponse, response.StatusCode); IReadOnlyList page = parsed?.Value ?? []; - pullRequests.AddRange(page.Select(ToGitPullRequest)); + pullRequests.AddRange(page.Select(pullRequest => ToGitPullRequest(pullRequest, response.StatusCode, body))); // Advanced by what actually arrived rather than by the page size asked for, so a // service returning more than $top would skip past the entries it already sent @@ -222,7 +222,7 @@ internal override async Task> GetPullRequestsCoreA /// are bare branch names — this library's own /// normalisation, matching what a caller gets back from every read path — so they are qualified /// with refs/heads/ here before being sent, the reverse of the stripping - /// does on the way back in. The response is + /// does on the way back in. The response is /// the created pull request; Microsoft's own worked example reports 201 despite the /// endpoint's response table saying 200, so this method checks /// rather than a specific status code. @@ -271,7 +271,7 @@ internal override async Task CreatePullRequestCoreAsync(string r return parsed is null ? throw new GitHostingRequestException( "Azure DevOps reported success but returned no pull request body.", Name, response.StatusCode, body) - : ToGitPullRequest(parsed); + : ToGitPullRequest(parsed, response.StatusCode, body); } /// @@ -475,8 +475,10 @@ private static AuthenticationHeaderValue BasicAuthenticationHeader(string userna /// composed from a constructed URL — see 's remarks. /// /// The pull request Azure DevOps returned. + /// The status code Azure DevOps reported for the response carrying . + /// The full response body carrying . /// The equivalent . - private static GitPullRequest ToGitPullRequest(AzureDevOpsPullRequest pullRequest) => new() + private GitPullRequest ToGitPullRequest(AzureDevOpsPullRequest pullRequest, HttpStatusCode statusCode, string responseBody) => new() { Number = pullRequest.PullRequestId.ToString(CultureInfo.InvariantCulture).As(), Title = (pullRequest.Title ?? string.Empty).As(), @@ -484,7 +486,7 @@ private static AuthenticationHeaderValue BasicAuthenticationHeader(string userna SourceBranch = StripRefsHeadsPrefix(pullRequest.SourceRefName ?? string.Empty).As(), TargetBranch = StripRefsHeadsPrefix(pullRequest.TargetRefName ?? string.Empty).As(), Author = pullRequest.CreatedBy?.UniqueName is string uniqueName ? uniqueName.As() : null, - State = ToGitPullRequestState(pullRequest.Status), + State = ToGitPullRequestState(pullRequest.Status, statusCode, responseBody), IsDraft = pullRequest.IsDraft, WebURI = pullRequest.Links?.Web?.Href is string href ? href.As() : null, CreatedAt = pullRequest.CreationDate, @@ -511,17 +513,23 @@ private static string StripRefsHeadsPrefix(string refName) /// "Contradictions and gaps" entry 4): active → , /// completed → , abandoned → /// . notSet and all are query-side-only - /// values a host never reports as a pull request's own status, so they fall through to the - /// unsupported case along with anything else unrecognised. + /// values a host never reports as a pull request's own status, so they fall through to a + /// along with anything else unrecognised. /// /// The status Azure DevOps reported. + /// The status code Azure DevOps reported for the response carrying . + /// The full response body carrying . /// The equivalent . - private static GitPullRequestState ToGitPullRequestState(string? status) => status switch + private GitPullRequestState ToGitPullRequestState(string? status, HttpStatusCode statusCode, string responseBody) => status switch { "active" => GitPullRequestState.Open, "completed" => GitPullRequestState.Merged, "abandoned" => GitPullRequestState.Closed, - _ => throw new NotSupportedException($"Azure DevOps reported an unrecognised pull request status '{status}'."), + _ => throw new GitHostingRequestException( + $"Azure DevOps reported an unrecognised pull request status '{status}'.", + Name, + statusCode, + responseBody), }; ///