Skip to content

Commit

Permalink
refactor: Allow WithUsername and WithPassword null or string.Empty
Browse files Browse the repository at this point in the history
  • Loading branch information
HofmeisterAn committed Aug 30, 2023
1 parent a12ef09 commit f470e4b
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 50 deletions.
61 changes: 36 additions & 25 deletions src/Testcontainers.MongoDb/MongoDbBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,12 @@ public sealed class MongoDbBuilder : ContainerBuilder<MongoDbBuilder, MongoDbCon

public const string DefaultPassword = "mongo";

private readonly bool _useDefaultCredentials;

/// <summary>
/// Initializes a new instance of the <see cref="MongoDbBuilder" /> class.
/// </summary>
/// <param name="useDefaultCredentials">When set to <c>false</c>, MongoDb will run without authentication unless you explicitly specify a username and password.</param>
public MongoDbBuilder(bool useDefaultCredentials = true)
public MongoDbBuilder()
: this(new MongoDbConfiguration())
{
_useDefaultCredentials = useDefaultCredentials;
DockerResourceConfiguration = Init().DockerResourceConfiguration;
}

Expand All @@ -45,8 +41,10 @@ private MongoDbBuilder(MongoDbConfiguration resourceConfiguration)
/// <returns>A configured instance of <see cref="MongoDbBuilder" />.</returns>
public MongoDbBuilder WithUsername(string username)
{
return Merge(DockerResourceConfiguration, new MongoDbConfiguration(username: username))
.WithEnvironment("MONGO_INITDB_ROOT_USERNAME", username);
var initDbRootUsername = username ?? string.Empty;

return Merge(DockerResourceConfiguration, new MongoDbConfiguration(username: initDbRootUsername))
.WithEnvironment("MONGO_INITDB_ROOT_USERNAME", initDbRootUsername);
}

/// <summary>
Expand All @@ -56,47 +54,49 @@ public MongoDbBuilder WithUsername(string username)
/// <returns>A configured instance of <see cref="MongoDbBuilder" />.</returns>
public MongoDbBuilder WithPassword(string password)
{
return Merge(DockerResourceConfiguration, new MongoDbConfiguration(password: password))
.WithEnvironment("MONGO_INITDB_ROOT_PASSWORD", password);
var initDbRootPassword = password ?? string.Empty;

return Merge(DockerResourceConfiguration, new MongoDbConfiguration(password: initDbRootPassword))
.WithEnvironment("MONGO_INITDB_ROOT_PASSWORD", initDbRootPassword);
}

/// <inheritdoc />
public override MongoDbContainer Build()
{
Validate();
return new MongoDbContainer(DockerResourceConfiguration, TestcontainersSettings.Logger);

// The wait strategy relies on the configuration of MongoDb. If credentials are
// provided, the log message "Waiting for connections" appears twice.
// If the user does not provide a custom waiting strategy, append the default MongoDb waiting strategy.
var mongoDbBuilder = DockerResourceConfiguration.WaitStrategies.Count() > 1 ? this : WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil(DockerResourceConfiguration)));
return new MongoDbContainer(mongoDbBuilder.DockerResourceConfiguration, TestcontainersSettings.Logger);
}

/// <inheritdoc />
protected override MongoDbBuilder Init()
{
string username = null;
string password = null;

if (_useDefaultCredentials)
{
username = DefaultUsername;
password = DefaultPassword;
}

return base.Init()
.WithImage(MongoDbImage)
.WithPortBinding(MongoDbPort, true)
.WithUsername(username)
.WithPassword(password)
.WithWaitStrategy(Wait.ForUnixContainer().AddCustomWaitStrategy(new WaitUntil()));
.WithUsername(DefaultUsername)
.WithPassword(DefaultPassword);
}

/// <inheritdoc />
protected override void Validate()
{
const string message = "Missing username or password. Both must be specified for a user to be created.";

base.Validate();

_ = Guard.Argument(DockerResourceConfiguration.Username, nameof(DockerResourceConfiguration.Username))
.NotEmpty();
.NotNull();

_ = Guard.Argument(DockerResourceConfiguration.Password, nameof(DockerResourceConfiguration.Password))
.NotEmpty();
.NotNull();

_ = Guard.Argument(DockerResourceConfiguration, "Credentials")
.ThrowIf(argument => 1.Equals(new[] { argument.Value.Username, argument.Value.Password }.Count(string.IsNullOrEmpty)), argument => new ArgumentException(message, argument.Name));
}

/// <inheritdoc />
Expand All @@ -122,13 +122,24 @@ private sealed class WaitUntil : IWaitUntil
{
private static readonly string[] LineEndings = { "\r\n", "\n" };

private readonly int _count;

/// <summary>
/// Initializes a new instance of the <see cref="WaitUntil" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
public WaitUntil(MongoDbConfiguration configuration)
{
_count = string.IsNullOrEmpty(configuration.Username) && string.IsNullOrEmpty(configuration.Password) ? 1 : 2;
}

/// <inheritdoc />
public async Task<bool> UntilAsync(IContainer container)
{
var (stdout, stderr) = await container.GetLogsAsync(timestampsEnabled: false)
.ConfigureAwait(false);

return 2.Equals(Array.Empty<string>()
return _count.Equals(Array.Empty<string>()
.Concat(stdout.Split(LineEndings, StringSplitOptions.RemoveEmptyEntries))
.Concat(stderr.Split(LineEndings, StringSplitOptions.RemoveEmptyEntries))
.Count(line => line.Contains("Waiting for connections")));
Expand Down
9 changes: 2 additions & 7 deletions src/Testcontainers.MongoDb/MongoDbContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,8 @@ public string GetConnectionString()
{
// The MongoDb documentation recommends to use percent-encoding for username and password: https://www.mongodb.com/docs/manual/reference/connection-string/.
var endpoint = new UriBuilder("mongodb://", Hostname, GetMappedPublicPort(MongoDbBuilder.MongoDbPort));

if (_configuration.Username != null && _configuration.Password != null)
{
endpoint.UserName = Uri.EscapeDataString(_configuration.Username);
endpoint.Password = Uri.EscapeDataString(_configuration.Password);
}

endpoint.UserName = Uri.EscapeDataString(_configuration.Username);
endpoint.Password = Uri.EscapeDataString(_configuration.Password);
return endpoint.ToString();
}

Expand Down
24 changes: 6 additions & 18 deletions src/Testcontainers.MongoDb/MongoDbShellCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ namespace Testcontainers.MongoDb;
/// </summary>
internal sealed class MongoDbShellCommand : List<string>
{
private const string AuthFormat = "{0} --username '{1}' --password '{2}' --quiet --eval '{3}'";
private const string NoAuthFormat = "{0} --quiet --eval '{1}'";
private const string Format = "{0} --username '{1}' --password '{2}' --quiet --eval '{3}'";

private const string Sanitize = "'\"'\"'";

Expand All @@ -25,22 +24,11 @@ internal sealed class MongoDbShellCommand : List<string>
public MongoDbShellCommand(string js, string username, string password)
{
var sanitizedJs = js.Replace("'", Sanitize);

if (username != null && password != null)
{
var sanitizedUsername = username.Replace("'", Sanitize);
var sanitizedPassword = password.Replace("'", Sanitize);
_mongoDbShellCommand.AppendFormat(AuthFormat, "mongosh", sanitizedUsername, sanitizedPassword, sanitizedJs);
_mongoDbShellCommand.Append(" || ");
_mongoDbShellCommand.AppendFormat(AuthFormat, "mongo", sanitizedUsername, sanitizedPassword, sanitizedJs);
}
else
{
_mongoDbShellCommand.AppendFormat(NoAuthFormat, "mongosh", sanitizedJs);
_mongoDbShellCommand.Append(" || ");
_mongoDbShellCommand.AppendFormat(NoAuthFormat, "mongo", sanitizedJs);
}

var sanitizedUsername = username.Replace("'", Sanitize);
var sanitizedPassword = password.Replace("'", Sanitize);
_mongoDbShellCommand.AppendFormat(Format, "mongosh", sanitizedUsername, sanitizedPassword, sanitizedJs);
_mongoDbShellCommand.Append(" || ");
_mongoDbShellCommand.AppendFormat(Format, "mongo", sanitizedUsername, sanitizedPassword, sanitizedJs);
Add("/bin/sh");
Add("-c");
Add(_mongoDbShellCommand.ToString());
Expand Down
9 changes: 9 additions & 0 deletions tests/Testcontainers.MongoDb.Tests/MongoDbContainerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ public MongoDbDefaultConfiguration()
}
}

[UsedImplicitly]
public sealed class MongoDbNoAuthConfiguration : MongoDbContainerTest
{
public MongoDbNoAuthConfiguration()
: base(new MongoDbBuilder().WithUsername(string.Empty).WithPassword(string.Empty).Build())
{
}
}

[UsedImplicitly]
public sealed class MongoDbV5Configuration : MongoDbContainerTest
{
Expand Down

0 comments on commit f470e4b

Please sign in to comment.