Add unit tests for HttpClient with WebProxy (#1010)

* Add test for SSL / Https

* Add unit tests for HttpClient with WebProxy

* cert.pem

* x

* skip

* example google

* revert

* host tests

* sonar
This commit is contained in:
Stef Heyenrath
2023-10-14 17:55:29 +02:00
committed by GitHub
parent 30372a9348
commit 62fa4666b5
8 changed files with 202 additions and 7 deletions

View File

@@ -1,4 +1,4 @@
using WireMock.Logging; using WireMock.Logging;
using WireMock.Server; using WireMock.Server;
using WireMock.Settings; using WireMock.Settings;
@@ -24,7 +24,6 @@ namespace WireMock.Net.Console.NETCoreApp3WithCertificate
// X509CertificateFilePath = "example.pfx", // X509CertificateFilePath = "example.pfx",
// X509CertificatePassword = "wiremock" // X509CertificatePassword = "wiremock"
} }
}); });
System.Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls)); System.Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls));

View File

@@ -12,8 +12,6 @@ internal class HostUrlOptions
public int? Port { get; set; } public int? Port { get; set; }
public int? HttpsPort { get; set; }
public HostingScheme HostingScheme { get; set; } public HostingScheme HostingScheme { get; set; }
public IReadOnlyList<HostUrlDetails> GetDetails() public IReadOnlyList<HostUrlDetails> GetDetails()

View File

@@ -141,6 +141,31 @@ public partial class WireMockServer : IWireMockServer
return client; return client;
} }
/// <summary>
/// Create a <see cref="HttpClient"/> which can be used to call this instance.
/// <param name="handlers">
/// <param name="innerHandler">The inner handler represents the destination of the HTTP message channel.</param>
/// An ordered list of System.Net.Http.DelegatingHandler instances to be invoked
/// as an System.Net.Http.HttpRequestMessage travels from the System.Net.Http.HttpClient
/// to the network and an System.Net.Http.HttpResponseMessage travels from the network
/// back to System.Net.Http.HttpClient. The handlers are invoked in a top-down fashion.
/// That is, the first entry is invoked first for an outbound request message but
/// last for an inbound response message.
/// </param>
/// </summary>
[PublicAPI]
public HttpClient CreateClient(HttpMessageHandler innerHandler, params DelegatingHandler[] handlers)
{
if (!IsStarted)
{
throw new InvalidOperationException("Unable to create HttpClient because the service is not started.");
}
var client = HttpClientFactory2.Create(innerHandler, handlers);
client.BaseAddress = new Uri(Url!);
return client;
}
/// <summary> /// <summary>
/// Create <see cref="HttpClient"/>s (one for each URL) which can be used to call this instance. /// Create <see cref="HttpClient"/>s (one for each URL) which can be used to call this instance.
/// <param name="innerHandler">The inner handler represents the destination of the HTTP message channel.</param> /// <param name="innerHandler">The inner handler represents the destination of the HTTP message channel.</param>

View File

@@ -215,10 +215,13 @@ public class WireMockAssertionsTests : IDisposable
{ {
// Arrange // Arrange
using var server = WireMockServer.Start(); using var server = WireMockServer.Start();
using var client = server.CreateClient(); using var client1 = server.CreateClient();
var handler = new HttpClientHandler();
using var client2 = server.CreateClient(handler);
// Act 1 // Act 1
await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/") await client1.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/")
{ {
Headers = Headers =
{ {
@@ -227,7 +230,7 @@ public class WireMockAssertionsTests : IDisposable
}); });
// Act 2 // Act 2
await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/") await client2.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/")
{ {
Headers = Headers =
{ {

View File

@@ -0,0 +1,60 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using FluentAssertions;
using WireMock.Owin;
using WireMock.Types;
using Xunit;
namespace WireMock.Net.Tests.Owin;
[ExcludeFromCodeCoverage]
public class HostUrlOptionsTests
{
[Fact]
public void GetDetails_WithNoUrlsAndHttpScheme_ShouldReturnCorrectDetails()
{
// Arrange
var options = new HostUrlOptions
{
HostingScheme = HostingScheme.Http,
Port = 8080
};
// Act
var details = options.GetDetails();
// Assert
details.Should().HaveCount(1);
var detail = details.Single();
detail.Should().Match<HostUrlDetails>(d =>
d.Scheme == "http" &&
d.Host == "localhost" &&
d.Port == 8080 &&
d.IsHttps == false
);
}
[Fact]
public void GetDetails_WithNoUrlsAndHttpsScheme_ShouldReturnCorrectDetails()
{
// Arrange
var options = new HostUrlOptions
{
HostingScheme = HostingScheme.Https,
Port = 8081
};
// Act
var details = options.GetDetails();
// Assert
details.Should().HaveCount(1);
var detail = details.Single();
detail.Should().Match<HostUrlDetails>(d =>
d.Scheme == "https" &&
d.Host == "localhost" &&
d.Port == 8081 &&
d.IsHttps == true
);
}
}

View File

@@ -110,6 +110,9 @@
<None Update="OpenApiParser\*.yml"> <None Update="OpenApiParser\*.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Update="cert.pem">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="responsebody.json"> <None Update="responsebody.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
@@ -100,6 +101,99 @@ public partial class WireMockServerTests
server.Stop(); server.Stop();
} }
#if NET461_OR_GREATER || NET6_0_OR_GREATER
[Fact]
public async Task WireMockServer_Should_Support_Https()
{
// Arrange
const string body = "example";
var path = $"/foo_{Guid.NewGuid()}";
var settings = new WireMockServerSettings
{
UseSSL = true
};
var server = WireMockServer.Start(settings);
server
.Given(Request.Create()
.WithPath(path)
.UsingGet()
)
.RespondWith(Response.Create()
.WithBody(body)
);
// Configure the HttpClient to trust self-signed certificates
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
};
using var client = new HttpClient(handler);
// Act
var result = await client.GetStringAsync($"{server.Url}{path}").ConfigureAwait(false);
// Assert
result.Should().Be(body);
server.Stop();
}
#endif
#if NET6_0_OR_GREATER
[Fact]
public async Task WireMockServer_When_HttpClientWithWebProxyCallsHttp_Should_Work_Correct()
{
// Arrange
const string body = "example";
var settings = new WireMockServerSettings
{
HostingScheme = HostingScheme.Http
};
var server = WireMockServer.Start(settings);
// The response to an HTTP CONNECT method, which is used to establish a tunnel with a proxy, should typically be a 200 OK status code if the connection is successful.
// This indicates that a tunnel has been established successfully between the client and the server via the proxy.
server
.Given(Request.Create()
.UsingConnect()
)
.RespondWith(Response.Create()
.WithBody("Connection established")
);
server
.Given(Request.Create()
.UsingGet()
)
.RespondWith(Response.Create()
.WithBody(body)
);
var httpUrl = server.Urls.First();
// Act
string result;
var currentProxy = HttpClient.DefaultProxy;
try
{
HttpClient.DefaultProxy = new WebProxy(httpUrl, false);
result = await new HttpClient().GetStringAsync(httpUrl).ConfigureAwait(false);
}
finally
{
// Revert
HttpClient.DefaultProxy = currentProxy;
}
// Assert
result.Should().Be(body);
server.Stop();
}
#endif
[Fact] [Fact]
public async Task WireMockServer_Should_respond_a_redirect_without_body() public async Task WireMockServer_Should_respond_a_redirect_without_body()
{ {

View File

@@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB9TCCAZugAwIBAgIUYH7UM/DAXzosxsT+ea2jdYvhqqMwCgYIKoZIzj0EAwIw
UDELMAkGA1UEBhMCTkwxEzARBgNVBAgMClNvbWUtU3RhdGUxFTATBgNVBAoMDFdp
cmVNb2NrLk5ldDEVMBMGA1UEAwwMV2lyZU1vY2suTmV0MB4XDTIyMDgxMTE2MjE0
NFoXDTMyMDYxOTE2MjE0NFowUDELMAkGA1UEBhMCTkwxEzARBgNVBAgMClNvbWUt
U3RhdGUxFTATBgNVBAoMDFdpcmVNb2NrLk5ldDEVMBMGA1UEAwwMV2lyZU1vY2su
TmV0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE39VoI268uDuIeKmRzr9e9jgM
SGeuJTvTG7+cSXmeDymrVgIGXQgmqKA8TDXpJNrRhWMd/fpsnWu1JwJUjBmspaNT
MFEwHQYDVR0OBBYEFILL8V+fAtMnccWKGAdkx2Dh/v/TMB8GA1UdIwQYMBaAFILL
8V+fAtMnccWKGAdkx2Dh/v/TMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwID
SAAwRQIgKDLAG8OWK6GF5HV4kmWz3kp2V3yVsNK2V9Lw3dSE+YsCIQCK1EEBvuqc
0ncZV4ETVnOY23PWFOMk1VwN2aoTi5n++Q==
-----END CERTIFICATE-----