diff --git a/src/DotNet.Testcontainers.Tests/DotNet.Testcontainers.Tests.csproj b/src/DotNet.Testcontainers.Tests/DotNet.Testcontainers.Tests.csproj index cdc992286..69405ed40 100644 --- a/src/DotNet.Testcontainers.Tests/DotNet.Testcontainers.Tests.csproj +++ b/src/DotNet.Testcontainers.Tests/DotNet.Testcontainers.Tests.csproj @@ -7,6 +7,7 @@ + diff --git a/src/DotNet.Testcontainers.Tests/Unit/DatabaseContainerTest.cs b/src/DotNet.Testcontainers.Tests/Unit/DatabaseContainerTest.cs index 38db160ea..47ee92506 100644 --- a/src/DotNet.Testcontainers.Tests/Unit/DatabaseContainerTest.cs +++ b/src/DotNet.Testcontainers.Tests/Unit/DatabaseContainerTest.cs @@ -3,6 +3,8 @@ namespace DotNet.Testcontainers.Tests.Unit using System.Threading.Tasks; using DotNet.Testcontainers.Core.Builder; using DotNet.Testcontainers.Core.Containers.Database; + using DotNet.Testcontainers.Core.Models; + using Npgsql; using Xunit; public class DatabaseContainerTest @@ -10,18 +12,31 @@ public class DatabaseContainerTest [Fact] public async Task PostgreSqlContainer() { - var database = string.Empty; - - var username = string.Empty; - - var password = string.Empty; - var testcontainersBuilder = new TestcontainersBuilder() - .WithDatabase(database, username, password); + .WithDatabase(new DatabaseConfiguration + { + Database = "db", + Username = "postgres", + Password = "postgres", + }); using (var testcontainer = testcontainersBuilder.Build()) { await testcontainer.StartAsync(); + + /* + using (var connection = new NpgsqlConnection(testcontainer.ConnectionString)) + { + connection.Open(); + + using (var cmd = new NpgsqlCommand()) + { + cmd.Connection = connection; + cmd.CommandText = "SELECT 1"; + cmd.ExecuteReader(); + } + } + */ } } } diff --git a/src/DotNet.Testcontainers.Tests/Unit/TestcontainersTest.cs b/src/DotNet.Testcontainers.Tests/Unit/TestcontainersContainerTest.cs similarity index 99% rename from src/DotNet.Testcontainers.Tests/Unit/TestcontainersTest.cs rename to src/DotNet.Testcontainers.Tests/Unit/TestcontainersContainerTest.cs index 66cbb7f5a..a947a07fa 100644 --- a/src/DotNet.Testcontainers.Tests/Unit/TestcontainersTest.cs +++ b/src/DotNet.Testcontainers.Tests/Unit/TestcontainersContainerTest.cs @@ -10,7 +10,7 @@ namespace DotNet.Testcontainers.Tests.Unit using Xunit; using static LanguageExt.Prelude; - public static class TestcontainersTest + public static class TestcontainersContainerTest { private static readonly string TempDir = Environment.GetEnvironmentVariable("AGENT_TEMPDIRECTORY") ?? "."; // We cannot use `Path.GetTempPath()` on macOS, see: https://github.com/common-workflow-language/cwltool/issues/328 diff --git a/src/DotNet.Testcontainers.Tests/Unit/TestcontainersWaitStrategyTest.cs b/src/DotNet.Testcontainers.Tests/Unit/TestcontainersWaitStrategyTest.cs index bc395a5a5..1c8a4c33f 100644 --- a/src/DotNet.Testcontainers.Tests/Unit/TestcontainersWaitStrategyTest.cs +++ b/src/DotNet.Testcontainers.Tests/Unit/TestcontainersWaitStrategyTest.cs @@ -35,7 +35,7 @@ protected override async Task Until() public class Timeout : WaitStrategy { [Fact] - public async Task WhileAfter5ms() + public async Task WhileAfter1ms() { await Assert.ThrowsAsync(async () => { @@ -44,7 +44,7 @@ await Assert.ThrowsAsync(async () => } [Fact] - public async Task UntilAfter5ms() + public async Task UntilAfter1ms() { await Assert.ThrowsAsync(async () => { diff --git a/src/DotNet.Testcontainers/Core/Builder/TestcontainersBuilder.cs b/src/DotNet.Testcontainers/Core/Builder/TestcontainersBuilder.cs index bd38f891b..6c73ef465 100644 --- a/src/DotNet.Testcontainers/Core/Builder/TestcontainersBuilder.cs +++ b/src/DotNet.Testcontainers/Core/Builder/TestcontainersBuilder.cs @@ -103,7 +103,7 @@ public ITestcontainersBuilder WithExposedPort(string port) { return Build(this, new TestcontainersConfiguration { - Container = new ContainerConfiguration { Labels = new Dictionary { { port, port } } }, + Container = new ContainerConfiguration { ExposedPorts = new Dictionary { { port, port } } }, }); } diff --git a/src/DotNet.Testcontainers/Core/Builder/TestcontainersBuilderExtensions.cs b/src/DotNet.Testcontainers/Core/Builder/TestcontainersBuilderExtensions.cs deleted file mode 100644 index 52b6caebc..000000000 --- a/src/DotNet.Testcontainers/Core/Builder/TestcontainersBuilderExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace DotNet.Testcontainers.Core.Builder -{ - using DotNet.Testcontainers.Core.Containers.Database; - - public static class TestcontainersBuilderExtensions - { - public static ITestcontainersBuilder WithDatabase(this ITestcontainersBuilder builder, string database, string username, string password) - where T : PostgreSqlContainer - { - return builder - .WithImage("postgres") - .WithCommand("postgres") - .WithExposedPort(5432) - .WithEnvironment("POSTGRES_DB", database) - .WithEnvironment("POSTGRES_USER", username) - .WithEnvironment("POSTGRES_PASSWORD", password) - .ConfigureContainer((container) => - { - container.Database = database; - container.Username = username; - container.Password = password; - }); - } - } -} diff --git a/src/DotNet.Testcontainers/Core/Builder/TestcontainersBuilderPostgreSqlExtension.cs b/src/DotNet.Testcontainers/Core/Builder/TestcontainersBuilderPostgreSqlExtension.cs new file mode 100644 index 000000000..73ddfd676 --- /dev/null +++ b/src/DotNet.Testcontainers/Core/Builder/TestcontainersBuilderPostgreSqlExtension.cs @@ -0,0 +1,37 @@ +namespace DotNet.Testcontainers.Core.Builder +{ + using DotNet.Testcontainers.Core.Containers.Database; + using DotNet.Testcontainers.Core.Models; + + public static class TestcontainersBuilderPostgreSqlExtension + { + private const string DefaultImage = "postgres:11.2"; + + private const string DefaultPort = "5432"; + + private const string Database = "POSTGRES_DB"; + + private const string Username = "POSTGRES_USER"; + + private const string Password = "POSTGRES_PASSWORD"; + + public static ITestcontainersBuilder WithDatabase(this ITestcontainersBuilder builder, DatabaseConfiguration configuration) + where T : PostgreSqlContainer + { + return builder + .WithImage(DefaultImage) + .WithPortBinding(DefaultPort) + .WithEnvironment(Database, configuration.Database) + .WithEnvironment(Username, configuration.Username) + .WithEnvironment(Password, configuration.Password) + .ConfigureContainer((container) => + { + container.Hostname = configuration.Hostname; + container.Port = configuration.Port; + container.Database = configuration.Database; + container.Username = configuration.Username; + container.Password = configuration.Password; + }); + } + } +} diff --git a/src/DotNet.Testcontainers/Core/Containers/Database/DatabaseContainer.cs b/src/DotNet.Testcontainers/Core/Containers/Database/DatabaseContainer.cs index c54771e76..094c49b9f 100644 --- a/src/DotNet.Testcontainers/Core/Containers/Database/DatabaseContainer.cs +++ b/src/DotNet.Testcontainers/Core/Containers/Database/DatabaseContainer.cs @@ -5,10 +5,14 @@ namespace DotNet.Testcontainers.Core.Containers.Database public abstract class DatabaseContainer : TestcontainersContainer { - internal DatabaseContainer(TestcontainersConfiguration configuration) : base(configuration) + protected DatabaseContainer(TestcontainersConfiguration configuration) : base(configuration) { } + public virtual string Hostname { get; set; } + + public virtual string Port { get; set; } + public virtual string Database { get; set; } public virtual string Username { get; set; } diff --git a/src/DotNet.Testcontainers/Core/Containers/Database/PostgreSqlContainer.cs b/src/DotNet.Testcontainers/Core/Containers/Database/PostgreSqlContainer.cs index 152acbfb3..3315b284d 100644 --- a/src/DotNet.Testcontainers/Core/Containers/Database/PostgreSqlContainer.cs +++ b/src/DotNet.Testcontainers/Core/Containers/Database/PostgreSqlContainer.cs @@ -8,6 +8,6 @@ internal PostgreSqlContainer(TestcontainersConfiguration configuration) : base(c { } - public override string ConnectionString => $"Server=127.0.0.1;Port=5432;Database={this.Database};User Id={this.Username};Password={this.Password};"; + public override string ConnectionString => $"Server={this.Hostname};Port={this.Port};Database={this.Database};User Id={this.Username};Password={this.Password};"; } } diff --git a/src/DotNet.Testcontainers/Core/Containers/TestcontainersContainer.cs b/src/DotNet.Testcontainers/Core/Containers/TestcontainersContainer.cs index 5b66c1aff..22729d92a 100644 --- a/src/DotNet.Testcontainers/Core/Containers/TestcontainersContainer.cs +++ b/src/DotNet.Testcontainers/Core/Containers/TestcontainersContainer.cs @@ -19,7 +19,7 @@ public class TestcontainersContainer : IDockerContainer private Option container = None; - internal TestcontainersContainer(TestcontainersConfiguration configuration) + protected TestcontainersContainer(TestcontainersConfiguration configuration) { this.Configuration = configuration; } diff --git a/src/DotNet.Testcontainers/Core/Mapper/TestcontainersConfigurationConverter.cs b/src/DotNet.Testcontainers/Core/Mapper/TestcontainersConfigurationConverter.cs index a5cd94e89..8ed19fe70 100644 --- a/src/DotNet.Testcontainers/Core/Mapper/TestcontainersConfigurationConverter.cs +++ b/src/DotNet.Testcontainers/Core/Mapper/TestcontainersConfigurationConverter.cs @@ -33,7 +33,7 @@ private class ToList : CollectionConverter> { public override IList Convert(IReadOnlyCollection source) { - return source.ToList(); + return source?.ToList(); } } @@ -41,7 +41,7 @@ private class ToDictionary : DictionaryConverter> { public override IDictionary Convert(IReadOnlyDictionary source) { - return source.ToDictionary(item => item.Key, item => item.Value); + return source?.ToDictionary(item => item.Key, item => item.Value); } } @@ -49,7 +49,7 @@ private class ToMappedList : DictionaryConverter> { public override IList Convert(IReadOnlyDictionary source) { - return source.Select(item => $"{item.Key}={item.Value}").ToList(); + return source?.Select(item => $"{item.Key}={item.Value}").ToList(); } } @@ -61,7 +61,7 @@ public ToExposedPorts() : base("ExposedPorts") public override IDictionary Convert(IReadOnlyDictionary source) { - return source.ToDictionary(exposedPort => $"{exposedPort.Key}/tcp", exposedPort => default(EmptyStruct)); + return source?.ToDictionary(exposedPort => $"{exposedPort.Key}/tcp", exposedPort => default(EmptyStruct)); } } @@ -73,7 +73,7 @@ public ToPortBindings() : base("PortBindings") public override IDictionary> Convert(IReadOnlyDictionary source) { - return source.ToDictionary(binding => $"{binding.Value}/tcp", binding => new List { new PortBinding { HostPort = binding.Key } } as IList); + return source?.ToDictionary(binding => $"{binding.Value}/tcp", binding => new List { new PortBinding { HostPort = binding.Key } } as IList); } } @@ -85,7 +85,7 @@ public ToMounts() : base("Mounts") public override IList Convert(IReadOnlyDictionary source) { - return source.Select(mount => new Mount { Source = Path.GetFullPath(mount.Key), Target = mount.Value, Type = "bind" }).ToList(); + return source?.Select(mount => new Mount { Source = Path.GetFullPath(mount.Key), Target = mount.Value, Type = "bind" }).ToList(); } } } diff --git a/src/DotNet.Testcontainers/Core/Models/DatabaseConfiguration.cs b/src/DotNet.Testcontainers/Core/Models/DatabaseConfiguration.cs new file mode 100644 index 000000000..2b9b0f921 --- /dev/null +++ b/src/DotNet.Testcontainers/Core/Models/DatabaseConfiguration.cs @@ -0,0 +1,15 @@ +namespace DotNet.Testcontainers.Core.Models +{ + public class DatabaseConfiguration + { + public virtual string Hostname { get; set; } = "localhost"; + + public virtual string Port { get; set; } + + public virtual string Database { get; set; } + + public virtual string Username { get; set; } + + public virtual string Password { get; set; } + } +} diff --git a/src/DotNet.Testcontainers/Core/Models/TestcontainersConfiguration.cs b/src/DotNet.Testcontainers/Core/Models/TestcontainersConfiguration.cs index c5729ecf8..7013a8be9 100644 --- a/src/DotNet.Testcontainers/Core/Models/TestcontainersConfiguration.cs +++ b/src/DotNet.Testcontainers/Core/Models/TestcontainersConfiguration.cs @@ -5,7 +5,7 @@ namespace DotNet.Testcontainers.Core.Models using System.Linq; using DotNet.Testcontainers.Diagnostics; - internal class TestcontainersConfiguration + public class TestcontainersConfiguration { public ContainerConfiguration Container { get; set; } = new ContainerConfiguration(); @@ -39,7 +39,7 @@ internal TestcontainersConfiguration Merge(TestcontainersConfiguration old) this.Host.Mounts = Merge(this.Host.Mounts, old.Host.Mounts); - this.CleanUp = old.CleanUp; + this.CleanUp = this.CleanUp && old.CleanUp; this.OutputConsumer = Merge(this.OutputConsumer, old.OutputConsumer); @@ -57,22 +57,22 @@ private static T Merge(T myself, T old) private static IReadOnlyCollection Merge(IReadOnlyCollection myself, IReadOnlyCollection old) where T : class { - if (myself == null) + if (myself == null || old == null) { - return old ?? new ReadOnlyCollection(new List()); + return myself ?? old; } else { - return myself.Concat(old.Where(x => !myself.Contains(x))).ToList(); + return myself.Concat(old).ToList(); } } private static IReadOnlyDictionary Merge(IReadOnlyDictionary myself, IReadOnlyDictionary old) where T : class { - if (myself == null) + if (myself == null || old == null) { - return old ?? new ReadOnlyDictionary(new Dictionary()); + return myself ?? old; } else { @@ -88,22 +88,22 @@ public class ContainerConfiguration public string WorkingDirectory { get; set; } - public IReadOnlyCollection Entrypoint { get; set; } = new List(); + public IReadOnlyCollection Entrypoint { get; set; } - public IReadOnlyCollection Command { get; set; } = new List(); + public IReadOnlyCollection Command { get; set; } - public IReadOnlyDictionary Environments { get; set; } = new Dictionary(); + public IReadOnlyDictionary Environments { get; set; } - public IReadOnlyDictionary Labels { get; set; } = new Dictionary(); + public IReadOnlyDictionary Labels { get; set; } - public IReadOnlyDictionary ExposedPorts { get; set; } = new Dictionary(); + public IReadOnlyDictionary ExposedPorts { get; set; } } public class HostConfiguration { - public IReadOnlyDictionary PortBindings { get; set; } = new Dictionary(); + public IReadOnlyDictionary PortBindings { get; set; } - public IReadOnlyDictionary Mounts { get; set; } = new Dictionary(); + public IReadOnlyDictionary Mounts { get; set; } } } }