diff --git a/src/Testcontainers.MongoDb/MongoDbBuilder.cs b/src/Testcontainers.MongoDb/MongoDbBuilder.cs index bbc017b83..736f13c6a 100644 --- a/src/Testcontainers.MongoDb/MongoDbBuilder.cs +++ b/src/Testcontainers.MongoDb/MongoDbBuilder.cs @@ -204,7 +204,11 @@ private static async Task InitiateReplicaSetAsync(MongoDbContainer container, Mo // with custom configurations as needed. var options = new WaitStrategy(); - var scriptContent = $"var r=rs.initiate({{_id:\"{configuration.ReplicaSetName}\",members:[{{_id:0,host:\"127.0.0.1:27017\"}}]}});quit(r.ok===1?0:1);"; + // The startup callback runs on every start, so a container that is restarted or reused + // already holds an initiated replica set. Initiating it again throws instead of returning + // a result, which would otherwise be retried until the wait strategy times out: + // https://github.com/testcontainers/testcontainers-dotnet/issues/1722. + var scriptContent = $"try{{var r=rs.initiate({{_id:\"{configuration.ReplicaSetName}\",members:[{{_id:0,host:\"127.0.0.1:27017\"}}]}});quit(r.ok===1?0:1);}}catch(e){{quit(e.codeName===\"AlreadyInitialized\"?0:1);}}"; var initiate = async () => { diff --git a/tests/Testcontainers.MongoDb.Tests/MongoDbReplicaSetReuseTest.cs b/tests/Testcontainers.MongoDb.Tests/MongoDbReplicaSetReuseTest.cs new file mode 100644 index 000000000..beca79df2 --- /dev/null +++ b/tests/Testcontainers.MongoDb.Tests/MongoDbReplicaSetReuseTest.cs @@ -0,0 +1,72 @@ +namespace Testcontainers.MongoDb; + +/// +/// Reusing a container runs the startup callback again against a replica set that is already +/// initiated (https://github.com/testcontainers/testcontainers-dotnet/issues/1722). +/// +public sealed class MongoDbReplicaSetReuseTest : IAsyncLifetime +{ + private readonly string _labelKey = Guid.NewGuid().ToString("D"); + + private readonly string _labelValue = Guid.NewGuid().ToString("D"); + + private readonly IList _containers = new List(); + + public ValueTask InitializeAsync() + { + return ValueTask.CompletedTask; + } + + public async ValueTask DisposeAsync() + { + foreach (var container in _containers.Distinct()) + { + await container.DisposeAsync() + .ConfigureAwait(false); + } + + GC.SuppressFinalize(this); + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public async Task ReusedContainerStartsAgain() + { + // Given + // The default wait strategy timeout is one hour, so bound the wait to keep a regression + // from hanging the test run instead of failing it. + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3)); + + var container = CreateContainer(); + _containers.Add(container); + + await container.StartAsync(cts.Token) + .ConfigureAwait(true); + + // When + var reusedContainer = CreateContainer(); + _containers.Add(reusedContainer); + + await reusedContainer.StartAsync(cts.Token) + .ConfigureAwait(true); + + // Then + Assert.Equal(container.Id, reusedContainer.Id); + + const string scriptContent = "rs.status().ok;"; + + var execResult = await reusedContainer.ExecScriptAsync(scriptContent, cts.Token) + .ConfigureAwait(true); + + Assert.True(0L.Equals(execResult.ExitCode), execResult.Stderr); + } + + private MongoDbContainer CreateContainer() + { + return new MongoDbBuilder(TestSession.GetImageFromDockerfile()) + .WithReplicaSet() + .WithLabel(_labelKey, _labelValue) + .WithReuse(true) + .Build(); + } +} diff --git a/tests/Testcontainers.MongoDb.Tests/Usings.cs b/tests/Testcontainers.MongoDb.Tests/Usings.cs index b68e8ba14..2482599f0 100644 --- a/tests/Testcontainers.MongoDb.Tests/Usings.cs +++ b/tests/Testcontainers.MongoDb.Tests/Usings.cs @@ -1,4 +1,7 @@ global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Threading; global using System.Threading.Tasks; global using DotNet.Testcontainers.Commons; global using DotNet.Testcontainers.Configurations;