Skip to content

Commit

Permalink
Added end to end middleware test with LE client mock (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
dv00d00 authored and ffMathy committed Dec 30, 2019
1 parent 7bc1761 commit 0eddf7a
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="5.9.0" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
Expand Down
150 changes: 150 additions & 0 deletions src/FluffySpoon.AspNet.LetsEncrypt.Tests/MiddlewareTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using System;
using System.Linq;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Certes;
using Certes.Acme;
using FluffySpoon.AspNet.LetsEncrypt.Certes;
using FluffySpoon.AspNet.LetsEncrypt.Persistence;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NSubstitute;

namespace FluffySpoon.AspNet.LetsEncrypt.Tests
{
[TestClass]
public class LetsEncryptChallengeApprovalMiddlewareMiddlewareTests
{
private static readonly string AcmeToken = Guid.NewGuid().ToString();
private static readonly string AcmeResponse = $"{Guid.NewGuid()}-{Guid.NewGuid()}";

private FakeLetsEncryptClient _fakeClient;
private IWebHostBuilder _webHostBuilder;

[TestInitialize]
public void Setup()
{
_fakeClient = new FakeLetsEncryptClient();
var letsEncryptClientFactory = Substitute.For<ILetsEncryptClientFactory>();
letsEncryptClientFactory.GetClient().Returns(Task.FromResult((ILetsEncryptClient)_fakeClient));

_webHostBuilder = WebHost.CreateDefaultBuilder()
.ConfigureServices(services =>
{
services.AddFluffySpoonLetsEncryptRenewalService(new LetsEncryptOptions()
{
Email = "some-email@github.com",
UseStaging = true,
Domains = new[] {"test.com"},
TimeUntilExpiryBeforeRenewal = TimeSpan.FromDays(30),
CertificateSigningRequest = new CsrInfo
{
CountryName = "CountryNameStuff",
Locality = "LocalityStuff",
Organization = "OrganizationStuff",
OrganizationUnit = "OrganizationUnitStuff",
State = "StateStuff"
}
});
services.AddFluffySpoonLetsEncryptMemoryCertficatesPersistence();
services.AddFluffySpoonLetsEncryptMemoryChallengePersistence();
// mock communication with LetsEncrypt
services.Remove(services.Single(x => x.ServiceType == typeof(ILetsEncryptClientFactory)));
services.AddSingleton<ILetsEncryptClientFactory>(letsEncryptClientFactory);
})
.Configure(app =>
{
app.UseDeveloperExceptionPage();
app.UseFluffySpoonLetsEncryptChallengeApprovalMiddleware();
app.Run(async context =>
{
context.Response.StatusCode = 404;
await context.Response.WriteAsync("Not found");
});
})
.UseKestrel(kestrelOptions =>
{
kestrelOptions.ConfigureHttpsDefaults(httpsOptions =>
{
httpsOptions.ServerCertificateSelector =
(c, s) => LetsEncryptRenewalService.Certificate;
});
})
.ConfigureLogging(l => l.AddConsole(x => x.IncludeScopes = true));
}

[TestMethod]
public async Task FullFlow()
{
using var server = new TestServer(_webHostBuilder);
var client = server.CreateClient();

var initialziationTimeout = await Task.WhenAny(Task.Delay(10000, _fakeClient.OrderPlacedCts.Token));
Assert.IsTrue(initialziationTimeout.IsCanceled, "Fake LE client initialization timed out");

var response = await client.GetAsync($"/.well-known/acme-challenge/{AcmeToken}");

Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Assert.AreEqual(AcmeResponse, await response.Content.ReadAsStringAsync());

var finalizationTimeout = await Task.WhenAny(Task.Delay(10000, _fakeClient.OrderFinalizedCts.Token));
Assert.IsTrue(finalizationTimeout.IsCanceled, "Fake LE client finalization timed out");

var appCert = LetsEncryptRenewalService.Certificate.RawData;
var fakeCert = FakeLetsEncryptClient.FakeCert.RawData;

Assert.IsTrue(appCert.SequenceEqual(fakeCert), "Certificates do not match");
}

private class FakeLetsEncryptClient : ILetsEncryptClient
{
public static readonly X509Certificate2 FakeCert = SelfSignedCertificate.Make(DateTime.Now, DateTime.Now.AddDays(90));

public CancellationTokenSource OrderPlacedCts { get; }
public CancellationTokenSource OrderFinalizedCts { get; }

public FakeLetsEncryptClient()
{
OrderPlacedCts = new CancellationTokenSource();
OrderFinalizedCts = new CancellationTokenSource();
}

public async Task<PlacedOrder> PlaceOrder(string[] domains)
{
var challengeDtos = new []{new ChallengeDto
{
Token = AcmeToken,
Response = AcmeResponse
}};

OrderPlacedCts.CancelAfter(250);

return new PlacedOrder(
challengeDtos,
Substitute.For<IOrderContext>(),
Array.Empty<IChallengeContext>());
}

public async Task<PfxCertificate> FinalizeOrder(PlacedOrder placedOrder)
{
await Task.Delay(500);

OrderFinalizedCts.CancelAfter(250);

return new PfxCertificate(FakeCert.RawData);
}
}
}
}

0 comments on commit 0eddf7a

Please sign in to comment.