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
60 changes: 29 additions & 31 deletions src/WireMock.Net.Testcontainers/WireMockContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,21 @@ namespace WireMock.Net.Testcontainers;
/// <summary>
/// A container for running WireMock in a docker environment.
/// </summary>
public sealed class WireMockContainer : DockerContainer
/// <remarks>
/// Initializes a new instance of the <see cref="WireMockContainer" /> class.
/// </remarks>
/// <param name="configuration">The container configuration.</param>
public sealed class WireMockContainer(WireMockConfiguration configuration) : DockerContainer(configuration)
{
private const int EnhancedFileSystemWatcherTimeoutMs = 2000;
internal const int ContainerPort = 80;

private readonly WireMockConfiguration _configuration;
private readonly WireMockConfiguration _configuration = Guard.NotNull(configuration);

private IWireMockAdminApi? _adminApi;
private EnhancedFileSystemWatcher? _enhancedFileSystemWatcher;
private IDictionary<int, Uri>? _publicUris;

/// <summary>
/// Initializes a new instance of the <see cref="WireMockContainer" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
public WireMockContainer(WireMockConfiguration configuration) : base(configuration)
{
_configuration = Guard.NotNull(configuration);

Started += async (sender, eventArgs) => await WireMockContainerStartedAsync(sender, eventArgs);
}

/// <summary>
/// Gets the public Url.
/// </summary>
Expand Down Expand Up @@ -157,14 +150,28 @@ public async Task ReloadStaticMappingsAsync(CancellationToken cancellationToken
try
{
var result = await _adminApi.ReloadStaticMappingsAsync(cancellationToken);
Logger.LogInformation("ReloadStaticMappings result: {Result}", result);
Logger.LogInformation("WireMock.Net -> ReloadStaticMappings result: {Result}", result);
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Error calling /__admin/mappings/reloadStaticMappings");
Logger.LogWarning(ex, "WireMock.Net -> Error calling /__admin/mappings/reloadStaticMappings");
}
}

/// <summary>
/// Performs additional actions after the container is ready.
/// </summary>
public Task CallAdditionalActionsAfterReadyAsync()
{
Logger.LogInformation("WireMock.Net -> Calling additional actions.");

_adminApi = CreateWireMockAdminClient();

RegisterEnhancedFileSystemWatcher();

return AddProtoDefinitionsAsync();
}

/// <inheritdoc />
protected override ValueTask DisposeAsyncCore()
{
Expand Down Expand Up @@ -197,15 +204,6 @@ private void ValidateIfRunning()
}
}

private async Task WireMockContainerStartedAsync(object sender, EventArgs e)
{
_adminApi = CreateWireMockAdminClient();

RegisterEnhancedFileSystemWatcher();

await CallAdditionalActionsAfterStartedAsync();
}

private void RegisterEnhancedFileSystemWatcher()
{
if (!_configuration.WatchStaticMappings || string.IsNullOrEmpty(_configuration.StaticMappingsPath))
Expand All @@ -223,22 +221,22 @@ private void RegisterEnhancedFileSystemWatcher()
_enhancedFileSystemWatcher.EnableRaisingEvents = true;
}

private async Task CallAdditionalActionsAfterStartedAsync()
private async Task AddProtoDefinitionsAsync()
{
foreach (var kvp in _configuration.ProtoDefinitions)
{
Logger.LogInformation("Adding ProtoDefinition {Id}", kvp.Key);
Logger.LogInformation("WireMock.Net -> Adding ProtoDefinition '{Id}'", kvp.Key);

foreach (var protoDefinition in kvp.Value)
{
try
{
var result = await _adminApi!.AddProtoDefinitionAsync(kvp.Key, protoDefinition);
Logger.LogInformation("AddProtoDefinition '{Id}' result: {Result}", kvp.Key, result);
Logger.LogInformation("WireMock.Net -> AddProtoDefinition '{Id}' result: {Result}", kvp.Key, result);
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Error adding ProtoDefinition '{Id}'.", kvp.Key);
Logger.LogWarning(ex, "WireMock.Net -> Error adding ProtoDefinition '{Id}'.", kvp.Key);
}
}
}
Expand All @@ -255,17 +253,17 @@ private async void FileCreatedChangedOrDeleted(object sender, FileSystemEventArg
try
{
await ReloadStaticMappingsAsync(args.FullPath);
Logger.LogInformation("ReloadStaticMappings triggered from file change: '{FullPath}'.", args.FullPath);
Logger.LogInformation("WireMock.Net -> ReloadStaticMappings triggered from file change: '{FullPath}'.", args.FullPath);
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Error reloading static mappings from '{FullPath}'.", args.FullPath);
Logger.LogWarning(ex, "WireMock.Net -> Error reloading static mappings from '{FullPath}'.", args.FullPath);
}
}

private async Task ReloadStaticMappingsAsync(string path, CancellationToken cancellationToken = default)
{
Logger.LogInformation("MappingFile created, changed or deleted: '{Path}'. Triggering ReloadStaticMappings.", path);
Logger.LogInformation("WireMock.Net -> MappingFile created, changed or deleted: '{Path}'. Triggering ReloadStaticMappings.", path);
await ReloadStaticMappingsAsync(cancellationToken);
}

Expand Down
10 changes: 4 additions & 6 deletions src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,9 @@ public override WireMockContainer Build()
builder.Validate();

var waitForContainerOS = _imageOS == OSPlatform.Windows ? Wait.ForWindowsContainer() : Wait.ForUnixContainer();
builder
builder = builder
.WithWaitStrategy(waitForContainerOS
.UntilMessageIsLogged("WireMock.Net server running", waitStrategy => waitStrategy.WithTimeout(TimeSpan.FromSeconds(30)))
.UntilHttpRequestIsSucceeded(httpWaitStrategy => httpWaitStrategy
.ForPort(WireMockContainer.ContainerPort)
.WithMethod(HttpMethod.Get)
Expand All @@ -267,6 +268,7 @@ public override WireMockContainer Build()
return content?.Contains("Healthy") == true;
})
)
.AddCustomWaitStrategy(new WireMockWaitStrategy())
);

return new WireMockContainer(builder.DockerResourceConfiguration);
Expand All @@ -277,13 +279,9 @@ protected override WireMockContainerBuilder Init()
{
var builder = base.Init();

var waitForContainerOS = _imageOS == OSPlatform.Windows ? Wait.ForWindowsContainer() : Wait.ForUnixContainer();
return builder
.WithPortBinding(WireMockContainer.ContainerPort, true)
.WithCommand($"--WireMockLogger {DefaultLogger}")
.WithWaitStrategy(waitForContainerOS
.UntilMessageIsLogged("WireMock.Net server running", waitStrategy => waitStrategy.WithTimeout(TimeSpan.FromSeconds(30)))
);
.WithCommand($"--WireMockLogger {DefaultLogger}");
}

/// <inheritdoc />
Expand Down
23 changes: 23 additions & 0 deletions src/WireMock.Net.Testcontainers/WireMockWaitStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright © WireMock.Net

using System;
using System.Threading.Tasks;
using DotNet.Testcontainers.Configurations;
using DotNet.Testcontainers.Containers;

namespace WireMock.Net.Testcontainers;

internal class WireMockWaitStrategy : IWaitUntil
{
public async Task<bool> UntilAsync(IContainer container)
{
if (container is not WireMockContainer wireMockContainer)
{
throw new InvalidOperationException("The passed container is not a WireMockContainer.");

}

await wireMockContainer.CallAdditionalActionsAfterReadyAsync();
return true;
}
}
12 changes: 12 additions & 0 deletions test/WireMock.Net.Tests/Testcontainers/TestcontainersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using DotNet.Testcontainers.Builders;
using FluentAssertions;
using FluentAssertions.Execution;
using Meziantou.Extensions.Logging.Xunit;
using Microsoft.Extensions.Logging;
using WireMock.Net.Testcontainers;
using WireMock.Net.Testcontainers.Utils;
using WireMock.Net.Tests.Facts;
Expand All @@ -17,13 +19,20 @@ namespace WireMock.Net.Tests.Testcontainers;

public class TestcontainersTests(ITestOutputHelper testOutputHelper)
{
private readonly ILogger _logger = new XUnitLogger(testOutputHelper, new LoggerExternalScopeProvider(), nameof(TestcontainersTests), new XUnitLoggerOptions
{
IncludeCategory = true,
TimestampFormat = "yyy-MM-dd HH:mm:ss.fff"
});

[Fact]
public async Task WireMockContainer_Build_And_StartAsync_and_StopAsync()
{
// Act
var adminUsername = $"username_{Guid.NewGuid()}";
var adminPassword = $"password_{Guid.NewGuid()}";
var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword)
.WithAutoRemove(true)
.WithCleanUp(true)
Expand All @@ -43,6 +52,7 @@ public async Task WireMockContainer_Build_WithNetwork_And_StartAsync_and_StopAsy
.Build();

var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithNetwork(dummyNetwork)
.WithWatchStaticMappings(true)
.Build();
Expand All @@ -58,6 +68,7 @@ public async Task WireMockContainer_Build_WithImage_And_StartAsync_and_StopAsync
var adminUsername = $"username_{Guid.NewGuid()}";
var adminPassword = $"password_{Guid.NewGuid()}";
var wireMockContainerBuilder = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword);

var imageOS = await TestcontainersUtils.GetImageOSAsync.Value;
Expand All @@ -83,6 +94,7 @@ public async Task WireMockContainer_Build_WithImageAsText_And_StartAsync_and_Sto
var adminUsername = $"username_{Guid.NewGuid()}";
var adminPassword = $"password_{Guid.NewGuid()}";
var wireMockContainerBuilder = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword);

var imageOS = await TestcontainersUtils.GetImageOSAsync.Value;
Expand Down
19 changes: 16 additions & 3 deletions test/WireMock.Net.Tests/Testcontainers/TestcontainersTestsGrpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
using FluentAssertions.Execution;
using Greet;
using Grpc.Net.Client;
using Meziantou.Extensions.Logging.Xunit;
using Microsoft.Extensions.Logging;
using WireMock.Constants;
using WireMock.Net.Testcontainers;
using WireMock.Util;
Expand All @@ -22,6 +24,12 @@ namespace WireMock.Net.Tests.Testcontainers;
[Collection("Grpc")]
public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper)
{
private readonly ILogger _logger = new XUnitLogger(testOutputHelper, new LoggerExternalScopeProvider(), nameof(TestcontainersTestsGrpc), new XUnitLoggerOptions
{
IncludeCategory = true,
TimestampFormat = "yyy-MM-dd HH:mm:ss.fff"
});

[Fact]
public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls1()
{
Expand All @@ -32,6 +40,7 @@ public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls1()

// Act
var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword)
.WithCommand("--UseHttp2")
.WithCommand("--Urls", $"http://*:80 grpc://*:{port}")
Expand Down Expand Up @@ -88,6 +97,7 @@ public async Task WireMockContainer_Build_Grpc_TestPortsAndUrls2()

// Act
var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.WithAdminUserNameAndPassword(adminUsername, adminPassword)
.AddUrl($"http://*:{ports[0]}")
.AddUrl($"grpc://*:{ports[1]}")
Expand Down Expand Up @@ -222,10 +232,11 @@ at DotNet.Testcontainers.Containers.DockerContainer.StopAsync(CancellationToken
}
}

private static async Task<WireMockContainer> Given_WireMockContainerIsStartedForHttpAndGrpcAsync()
private async Task<WireMockContainer> Given_WireMockContainerIsStartedForHttpAndGrpcAsync()
{
var port = PortUtils.FindFreeTcpPort();
var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.AddUrl($"grpc://*:{port}")
.Build();

Expand All @@ -234,10 +245,11 @@ private static async Task<WireMockContainer> Given_WireMockContainerIsStartedFor
return wireMockContainer;
}

private static async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync()
private async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync()
{
var port = PortUtils.FindFreeTcpPort();
var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.AddUrl($"grpc://*:{port}")
.AddProtoDefinition("my-greeter", ReadFile("greet.proto"))
.Build();
Expand All @@ -247,10 +259,11 @@ private static async Task<WireMockContainer> Given_WireMockContainerWithProtoDef
return wireMockContainer;
}

private static async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelWithWatchStaticMappingsIsStartedForHttpAndGrpcAsync()
private async Task<WireMockContainer> Given_WireMockContainerWithProtoDefinitionAtServerLevelWithWatchStaticMappingsIsStartedForHttpAndGrpcAsync()
{
var port = PortUtils.FindFreeTcpPort();
var wireMockContainer = new WireMockContainerBuilder()
.WithLogger(_logger)
.AddUrl($"grpc://*:{port}")
.AddProtoDefinition("my-greeter", ReadFile("greet.proto"))
.WithMappings(Path.Combine(Directory.GetCurrentDirectory(), "__admin", "mappings"))
Expand Down
1 change: 1 addition & 0 deletions test/WireMock.Net.Tests/WireMock.Net.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0' or '$(TargetFramework)' == 'net7.0' or '$(TargetFramework)' == 'net8.0'">
<ProjectReference Include="..\..\src\WireMock.Net.Testcontainers\WireMock.Net.Testcontainers.csproj" />
<PackageReference Include="Meziantou.Extensions.Logging.Xunit" Version="1.0.21" />
</ItemGroup>

<ItemGroup>
Expand Down