Skip to content

Commit

Permalink
feat: embed health check tool
Browse files Browse the repository at this point in the history
  • Loading branch information
mu88 committed Dec 19, 2024
1 parent 755c4e2 commit d807e57
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 24 deletions.
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="mu88.Shared" Version="1.0.0" />
<PackageVersion Include="mu88.Shared" Version="1.1.1" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
<PackageVersion Include="NSubstitute.Analyzers.CSharp" Version="1.0.17" />
<PackageVersion Include="NUnit" Version="4.3.0" />
Expand Down
76 changes: 53 additions & 23 deletions src/Tests/System/SystemTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,32 @@ public class SystemTests
public async Task AppRunningInDocker_ShouldBeHealthy()
{
// Arrange
await BuildDockerImageOfAppAsync();
var container = await StartAppInContainersAsync();
CancellationToken cancellationToken = CreateCancellationToken(TimeSpan.FromMinutes(1));
await BuildDockerImageOfAppAsync(cancellationToken);
var container = await StartAppInContainersAsync(cancellationToken);
var httpClient = new HttpClient { BaseAddress = GetAppBaseAddress(container) };

// Act
var healthCheckResponse = await httpClient.GetAsync("healthz");
var appResponse = await httpClient.GetAsync("/");
var healthCheckResponse = await httpClient.GetAsync("healthz", cancellationToken);
var appResponse = await httpClient.GetAsync("/", cancellationToken);
ExecResult healthCheckToolResult = await container.ExecAsync(["dotnet", "/app/mu88.HealthCheck.dll", "http://localhost:8080/thisIsYourLife/healthz"], cancellationToken);

// Assert
(string Stdout, string Stderr) logValues = await container.GetLogsAsync();
Console.WriteLine($"Stderr:{Environment.NewLine}{logValues.Stderr}");
Console.WriteLine($"Stdout:{Environment.NewLine}{logValues.Stdout}");
logValues.Stdout.Should().NotContain("warn:");
healthCheckResponse.Should().BeSuccessful();
(await healthCheckResponse.Content.ReadAsStringAsync()).Should().Be("Healthy");
appResponse.Should().BeSuccessful();
(await appResponse.Content.ReadAsStringAsync()).Should().Contain("<title>This is your life</title>");
await LogsShouldNotContainWarningsAsync(container, cancellationToken);
await HealthCheckShouldBeHealthyAsync(healthCheckResponse, cancellationToken);
await AppShouldRunAsync(appResponse, cancellationToken);
healthCheckToolResult.ExitCode.Should().Be(0);
}
private static CancellationToken CreateCancellationToken(TimeSpan timeout)
{
var timeoutCts = new CancellationTokenSource();
timeoutCts.CancelAfter(timeout);
CancellationToken cancellationToken = timeoutCts.Token;

return cancellationToken;
}

private static async Task BuildDockerImageOfAppAsync()
private static async Task BuildDockerImageOfAppAsync(CancellationToken cancellationToken)
{
var rootDirectory = Directory.GetParent(Environment.CurrentDirectory)?.Parent?.Parent?.Parent ?? throw new NullReferenceException();
var projectFile = Path.Join(rootDirectory.FullName, "WebApp", "WebApp.csproj");
Expand All @@ -42,7 +48,7 @@ private static async Task BuildDockerImageOfAppAsync()
StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"publish {projectFile} --os linux --arch amd64 /t:PublishContainer -p:ContainerImageTags=local-system-test",
Arguments = $"publish {projectFile} --os linux --arch amd64 /t:PublishContainer -p:ContainerFamily=noble-chiseled-extra -p:ContainerImageTags=local-system-test-chiseled",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
Expand All @@ -51,33 +57,57 @@ private static async Task BuildDockerImageOfAppAsync()
process.Start();
while (!process.StandardOutput.EndOfStream)
{
Console.WriteLine(await process.StandardOutput.ReadLineAsync());
Console.WriteLine(await process.StandardOutput.ReadLineAsync(cancellationToken));
}

await process.WaitForExitAsync();
await process.WaitForExitAsync(cancellationToken);
process.ExitCode.Should().Be(0);
}

private static async Task<IContainer> StartAppInContainersAsync()
private static async Task<IContainer> StartAppInContainersAsync(CancellationToken cancellationToken)
{
Console.WriteLine("Building and starting network");
var network = new NetworkBuilder().Build();
await network.CreateAsync();
INetwork? network = new NetworkBuilder().Build();
await network.CreateAsync(cancellationToken);
Console.WriteLine("Network started");

Console.WriteLine("Building and starting app container");
var container = BuildAppContainer(network);
await container.StartAsync();
IContainer container = BuildAppContainer(network);
await container.StartAsync(cancellationToken);
Console.WriteLine("App container started");

return container;
}

private static IContainer BuildAppContainer(INetwork network) =>
new ContainerBuilder()
.WithImage("mu88/thisisyourlife:local-system-test")
.WithImage("mu88/thisisyourlife:local-system-test-chiseled")
.WithNetwork(network)
.WithPortBinding(8080, true)
.WithWaitStrategy(Wait.ForUnixContainer().UntilPortIsAvailable(8080))
.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged(
"Content root path: /app",
strategy => strategy.WithTimeout(TimeSpan.FromSeconds(30)))) // as it's a chiseled container, waiting for the port does not work
.Build();

private static Uri GetAppBaseAddress(IContainer container) => new($"http://{container.Hostname}:{container.GetMappedPublicPort(8080)}/thisIsYourLife");

private static async Task AppShouldRunAsync(HttpResponseMessage appResponse, CancellationToken cancellationToken)
{
appResponse.Should().BeSuccessful();
(await appResponse.Content.ReadAsStringAsync(cancellationToken)).Should().Contain("<title>Raspi Fan Controller</title>");
}

private static async Task HealthCheckShouldBeHealthyAsync(HttpResponseMessage healthCheckResponse, CancellationToken cancellationToken)
{
healthCheckResponse.Should().BeSuccessful();
(await healthCheckResponse.Content.ReadAsStringAsync(cancellationToken)).Should().Be("Healthy");
}

private static async Task LogsShouldNotContainWarningsAsync(IContainer container, CancellationToken cancellationToken)
{
(string Stdout, string Stderr) logValues = await container.GetLogsAsync(ct: cancellationToken);
Console.WriteLine($"Stderr:{Environment.NewLine}{logValues.Stderr}");
Console.WriteLine($"Stdout:{Environment.NewLine}{logValues.Stdout}");
logValues.Stdout.Should().NotContain("warn:");
}
}

0 comments on commit d807e57

Please sign in to comment.