Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,7 @@ public async Task<HttpRequestResult> SendAsync(HttpRequestInfo request, Cancella
throw new ArgumentException("Request method must be provided.", nameof(request));
}

HttpRequestInfo currentRequest = request;
Uri currentUri = CreateAbsoluteUri(ResolveRequestUri(request));
HttpRequestInfo currentRequest = CreateCanonicalRequestInfo(request, out Uri currentUri);

using CancellationTokenSource? timeoutCts = request.Timeout is { } timeout && timeout > TimeSpan.Zero
? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)
Expand Down Expand Up @@ -306,6 +305,21 @@ private static Uri CreateAbsoluteUri(string requestUri)
return uri;
}

private static HttpRequestInfo CreateCanonicalRequestInfo(HttpRequestInfo request, out Uri uri)
{
uri = CreateAbsoluteUri(ResolveRequestUri(request));
return new HttpRequestInfo
{
Method = request.Method,
Url = uri.ToString(),
Headers = request.Headers,
BodyContentType = request.BodyContentType,
Body = request.Body,
Timeout = request.Timeout,
ConnectionName = request.ConnectionName,
};
}

private static bool TryCreateRedirectRequest(
HttpResponseMessage response,
HttpRequestInfo currentRequest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,49 @@ public async Task SendAsyncProviderClientAllowsScopedDefaultHeadersOnInitialRequ
Assert.Equal(1, providerCallCount);
}

[Fact]
public async Task SendAsyncInvokesProviderWithCanonicalRequestUriAsync()
{
// Arrange
CancellationToken cancellationToken = TestContext.Current.CancellationToken;
List<string> providerUrls = [];
List<string> requestUrls = [];
using HttpResponseMessage okResponse = new(HttpStatusCode.OK)
{
Content = new StringContent("ok", Encoding.UTF8, "text/plain"),
};
#pragma warning disable CA2025
TestHttpMessageHandler messageHandler = new((req, _) =>
{
requestUrls.Add(req.RequestUri!.ToString());
return Task.FromResult(okResponse);
});
#pragma warning restore CA2025
using HttpClient providerClient = new(messageHandler);
#pragma warning disable CA2025
await using DefaultHttpRequestHandler handler = new((info, _) =>
{
providerUrls.Add(info.Url);
return Task.FromResult<HttpClient?>(providerClient);
});
#pragma warning restore CA2025

HttpRequestInfo request = new()
{
Method = "GET",
Url = "https://api.example.test/public/../admin/private",
};

// Act
HttpRequestResult result = await handler.SendAsync(request, cancellationToken);

// Assert - the provider must authorize the same canonical URI that reaches
// HttpClient, not the raw dot-segment URL from the workflow definition.
Assert.Equal("ok", result.Body);
Assert.Equal(["https://api.example.test/admin/private"], requestUrls);
Assert.Equal(requestUrls, providerUrls);
}

[Fact]
public async Task SendAsyncSuppliedClientReturnsRedirectResponseAsync()
{
Expand Down
Loading