diff --git a/src/WireMock.Net.Testcontainers/WireMockContainer.cs b/src/WireMock.Net.Testcontainers/WireMockContainer.cs index f34cc1b3..411e6a43 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainer.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainer.cs @@ -24,28 +24,21 @@ namespace WireMock.Net.Testcontainers; /// /// A container for running WireMock in a docker environment. /// -public sealed class WireMockContainer : DockerContainer +/// +/// Initializes a new instance of the class. +/// +/// The container configuration. +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? _publicUris; - /// - /// Initializes a new instance of the class. - /// - /// The container configuration. - public WireMockContainer(WireMockConfiguration configuration) : base(configuration) - { - _configuration = Guard.NotNull(configuration); - - Started += async (sender, eventArgs) => await WireMockContainerStartedAsync(sender, eventArgs); - } - /// /// Gets the public Url. /// @@ -157,14 +150,28 @@ public sealed class WireMockContainer : DockerContainer 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"); } } + /// + /// Performs additional actions after the container is ready. + /// + public Task CallAdditionalActionsAfterReadyAsync() + { + Logger.LogInformation("WireMock.Net -> Calling additional actions."); + + _adminApi = CreateWireMockAdminClient(); + + RegisterEnhancedFileSystemWatcher(); + + return AddProtoDefinitionsAsync(); + } + /// protected override ValueTask DisposeAsyncCore() { @@ -197,15 +204,6 @@ public sealed class WireMockContainer : DockerContainer } } - private async Task WireMockContainerStartedAsync(object sender, EventArgs e) - { - _adminApi = CreateWireMockAdminClient(); - - RegisterEnhancedFileSystemWatcher(); - - await CallAdditionalActionsAfterStartedAsync(); - } - private void RegisterEnhancedFileSystemWatcher() { if (!_configuration.WatchStaticMappings || string.IsNullOrEmpty(_configuration.StaticMappingsPath)) @@ -223,22 +221,22 @@ public sealed class WireMockContainer : DockerContainer _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); } } } @@ -255,17 +253,17 @@ public sealed class WireMockContainer : DockerContainer 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); } diff --git a/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs b/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs index 37241598..5820f7c5 100644 --- a/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs +++ b/src/WireMock.Net.Testcontainers/WireMockContainerBuilder.cs @@ -253,8 +253,9 @@ public sealed class WireMockContainerBuilder : ContainerBuilder waitStrategy.WithTimeout(TimeSpan.FromSeconds(30))) .UntilHttpRequestIsSucceeded(httpWaitStrategy => httpWaitStrategy .ForPort(WireMockContainer.ContainerPort) .WithMethod(HttpMethod.Get) @@ -267,6 +268,7 @@ public sealed class WireMockContainerBuilder : ContainerBuilder waitStrategy.WithTimeout(TimeSpan.FromSeconds(30))) - ); + .WithCommand($"--WireMockLogger {DefaultLogger}"); } /// diff --git a/src/WireMock.Net.Testcontainers/WireMockWaitStrategy.cs b/src/WireMock.Net.Testcontainers/WireMockWaitStrategy.cs new file mode 100644 index 00000000..d33364d8 --- /dev/null +++ b/src/WireMock.Net.Testcontainers/WireMockWaitStrategy.cs @@ -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 UntilAsync(IContainer container) + { + if (container is not WireMockContainer wireMockContainer) + { + throw new InvalidOperationException("The passed container is not a WireMockContainer."); + + } + + await wireMockContainer.CallAdditionalActionsAfterReadyAsync(); + return true; + } +} diff --git a/test/WireMock.Net.Tests/Testcontainers/TestcontainersTests.cs b/test/WireMock.Net.Tests/Testcontainers/TestcontainersTests.cs index 8bde055b..c1cefe1d 100644 --- a/test/WireMock.Net.Tests/Testcontainers/TestcontainersTests.cs +++ b/test/WireMock.Net.Tests/Testcontainers/TestcontainersTests.cs @@ -7,6 +7,8 @@ using System.Threading.Tasks; 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; @@ -17,6 +19,12 @@ 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() { @@ -24,6 +32,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper) var adminUsername = $"username_{Guid.NewGuid()}"; var adminPassword = $"password_{Guid.NewGuid()}"; var wireMockContainer = new WireMockContainerBuilder() + .WithLogger(_logger) .WithAdminUserNameAndPassword(adminUsername, adminPassword) .WithAutoRemove(true) .WithCleanUp(true) @@ -43,6 +52,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper) .Build(); var wireMockContainer = new WireMockContainerBuilder() + .WithLogger(_logger) .WithNetwork(dummyNetwork) .WithWatchStaticMappings(true) .Build(); @@ -58,6 +68,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper) 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; @@ -83,6 +94,7 @@ public class TestcontainersTests(ITestOutputHelper testOutputHelper) 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; diff --git a/test/WireMock.Net.Tests/Testcontainers/TestcontainersTestsGrpc.cs b/test/WireMock.Net.Tests/Testcontainers/TestcontainersTestsGrpc.cs index e28f71f7..13163ced 100644 --- a/test/WireMock.Net.Tests/Testcontainers/TestcontainersTestsGrpc.cs +++ b/test/WireMock.Net.Tests/Testcontainers/TestcontainersTestsGrpc.cs @@ -11,6 +11,8 @@ using FluentAssertions; 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; @@ -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() { @@ -32,6 +40,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) // Act var wireMockContainer = new WireMockContainerBuilder() + .WithLogger(_logger) .WithAdminUserNameAndPassword(adminUsername, adminPassword) .WithCommand("--UseHttp2") .WithCommand("--Urls", $"http://*:80 grpc://*:{port}") @@ -88,6 +97,7 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) // Act var wireMockContainer = new WireMockContainerBuilder() + .WithLogger(_logger) .WithAdminUserNameAndPassword(adminUsername, adminPassword) .AddUrl($"http://*:{ports[0]}") .AddUrl($"grpc://*:{ports[1]}") @@ -222,10 +232,11 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) } } - private static async Task Given_WireMockContainerIsStartedForHttpAndGrpcAsync() + private async Task Given_WireMockContainerIsStartedForHttpAndGrpcAsync() { var port = PortUtils.FindFreeTcpPort(); var wireMockContainer = new WireMockContainerBuilder() + .WithLogger(_logger) .AddUrl($"grpc://*:{port}") .Build(); @@ -234,10 +245,11 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) return wireMockContainer; } - private static async Task Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync() + private async Task Given_WireMockContainerWithProtoDefinitionAtServerLevelIsStartedForHttpAndGrpcAsync() { var port = PortUtils.FindFreeTcpPort(); var wireMockContainer = new WireMockContainerBuilder() + .WithLogger(_logger) .AddUrl($"grpc://*:{port}") .AddProtoDefinition("my-greeter", ReadFile("greet.proto")) .Build(); @@ -247,10 +259,11 @@ public class TestcontainersTestsGrpc(ITestOutputHelper testOutputHelper) return wireMockContainer; } - private static async Task Given_WireMockContainerWithProtoDefinitionAtServerLevelWithWatchStaticMappingsIsStartedForHttpAndGrpcAsync() + private async Task 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")) diff --git a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj index afeecb45..39c9445c 100644 --- a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj +++ b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj @@ -121,6 +121,7 @@ +