From 4c24aaba814ef28df2dc02427f5e475de52b4701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Mon, 17 Feb 2025 08:37:20 +0100 Subject: [PATCH] feat(Oracle): Add support for WithDatabase(string) for Oracle 18 and onwards. (#1321) Co-authored-by: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> --- .github/workflows/cicd.yml | 4 ++ Directory.Packages.props | 2 +- Testcontainers.sln | 28 ++++++++ src/Testcontainers.Oracle/OracleBuilder.cs | 61 +++++++++++++--- .../OracleContainerTest.cs | 71 +++++++++++++++---- .../Testcontainers.Oracle.Tests.csproj | 2 + tests/Testcontainers.Oracle.Tests/Usings.cs | 5 +- .../.editorconfig | 1 + .../Testcontainers.Oracle11.Tests.csproj | 23 ++++++ .../.editorconfig | 1 + .../Testcontainers.Oracle18.Tests.csproj | 23 ++++++ .../.editorconfig | 1 + .../Testcontainers.Oracle21.Tests.csproj | 23 ++++++ .../.editorconfig | 1 + .../Testcontainers.Oracle23.Tests.csproj | 23 ++++++ 15 files changed, 241 insertions(+), 28 deletions(-) create mode 100644 tests/Testcontainers.Oracle11.Tests/.editorconfig create mode 100644 tests/Testcontainers.Oracle11.Tests/Testcontainers.Oracle11.Tests.csproj create mode 100644 tests/Testcontainers.Oracle18.Tests/.editorconfig create mode 100644 tests/Testcontainers.Oracle18.Tests/Testcontainers.Oracle18.Tests.csproj create mode 100644 tests/Testcontainers.Oracle21.Tests/.editorconfig create mode 100644 tests/Testcontainers.Oracle21.Tests/Testcontainers.Oracle21.Tests.csproj create mode 100644 tests/Testcontainers.Oracle23.Tests/.editorconfig create mode 100644 tests/Testcontainers.Oracle23.Tests/Testcontainers.Oracle23.Tests.csproj diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index e1f122658..7b75c21e3 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -72,6 +72,10 @@ jobs: { name: "Testcontainers.Nats", runs-on: "ubuntu-22.04" }, { name: "Testcontainers.Neo4j", runs-on: "ubuntu-22.04" }, { name: "Testcontainers.Oracle", runs-on: "ubuntu-22.04" }, + { name: "Testcontainers.Oracle11", runs-on: "ubuntu-22.04" }, + { name: "Testcontainers.Oracle18", runs-on: "ubuntu-22.04" }, + { name: "Testcontainers.Oracle21", runs-on: "ubuntu-22.04" }, + { name: "Testcontainers.Oracle23", runs-on: "ubuntu-22.04" }, { name: "Testcontainers.Papercut", runs-on: "ubuntu-22.04" }, { name: "Testcontainers.PostgreSql", runs-on: "ubuntu-22.04" }, { name: "Testcontainers.PubSub", runs-on: "ubuntu-22.04" }, diff --git a/Directory.Packages.props b/Directory.Packages.props index 0a4db1cff..7478929fd 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -68,7 +68,7 @@ - + diff --git a/Testcontainers.sln b/Testcontainers.sln index 96eb3545f..138c3c0e1 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -187,6 +187,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Neo4j.Tests" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle.Tests", "tests\Testcontainers.Oracle.Tests\Testcontainers.Oracle.Tests.csproj", "{4AC1088B-9965-4497-AC8E-570F1AD5631F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle11.Tests", "tests\Testcontainers.Oracle11.Tests\Testcontainers.Oracle11.Tests.csproj", "{0A0AC20D-226B-46F9-B267-0D00964A7601}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle18.Tests", "tests\Testcontainers.Oracle18.Tests\Testcontainers.Oracle18.Tests.csproj", "{E4C887A9-A44A-4641-BB9B-0664CC4C362F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle21.Tests", "tests\Testcontainers.Oracle21.Tests\Testcontainers.Oracle21.Tests.csproj", "{1F6415BD-646E-436A-9F57-9AE30A0AA694}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle23.Tests", "tests\Testcontainers.Oracle23.Tests\Testcontainers.Oracle23.Tests.csproj", "{FC417A93-4521-4FDB-943E-23886F3243C8}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Papercut.Tests", "tests\Testcontainers.Papercut.Tests\Testcontainers.Papercut.Tests.csproj", "{F03FA970-BE2B-4AE2-96FE-7E1F805CEA20}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Platform.Linux.Tests", "tests\Testcontainers.Platform.Linux.Tests\Testcontainers.Platform.Linux.Tests.csproj", "{DA1D7ADE-452C-4369-83CC-56289176EACD}" @@ -578,6 +586,22 @@ Global {4AC1088B-9965-4497-AC8E-570F1AD5631F}.Debug|Any CPU.Build.0 = Debug|Any CPU {4AC1088B-9965-4497-AC8E-570F1AD5631F}.Release|Any CPU.ActiveCfg = Release|Any CPU {4AC1088B-9965-4497-AC8E-570F1AD5631F}.Release|Any CPU.Build.0 = Release|Any CPU + {0A0AC20D-226B-46F9-B267-0D00964A7601}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A0AC20D-226B-46F9-B267-0D00964A7601}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A0AC20D-226B-46F9-B267-0D00964A7601}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A0AC20D-226B-46F9-B267-0D00964A7601}.Release|Any CPU.Build.0 = Release|Any CPU + {E4C887A9-A44A-4641-BB9B-0664CC4C362F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4C887A9-A44A-4641-BB9B-0664CC4C362F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4C887A9-A44A-4641-BB9B-0664CC4C362F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4C887A9-A44A-4641-BB9B-0664CC4C362F}.Release|Any CPU.Build.0 = Release|Any CPU + {1F6415BD-646E-436A-9F57-9AE30A0AA694}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F6415BD-646E-436A-9F57-9AE30A0AA694}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F6415BD-646E-436A-9F57-9AE30A0AA694}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F6415BD-646E-436A-9F57-9AE30A0AA694}.Release|Any CPU.Build.0 = Release|Any CPU + {FC417A93-4521-4FDB-943E-23886F3243C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC417A93-4521-4FDB-943E-23886F3243C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC417A93-4521-4FDB-943E-23886F3243C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC417A93-4521-4FDB-943E-23886F3243C8}.Release|Any CPU.Build.0 = Release|Any CPU {F03FA970-BE2B-4AE2-96FE-7E1F805CEA20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F03FA970-BE2B-4AE2-96FE-7E1F805CEA20}.Debug|Any CPU.Build.0 = Debug|Any CPU {F03FA970-BE2B-4AE2-96FE-7E1F805CEA20}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -735,6 +759,10 @@ Global {87A3F137-6DC3-4CE5-91E6-01797D076086} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {D3F63405-C0FA-4F83-8B79-E30BFF5FF5BF} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {4AC1088B-9965-4497-AC8E-570F1AD5631F} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} + {0A0AC20D-226B-46F9-B267-0D00964A7601} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} + {E4C887A9-A44A-4641-BB9B-0664CC4C362F} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} + {1F6415BD-646E-436A-9F57-9AE30A0AA694} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} + {FC417A93-4521-4FDB-943E-23886F3243C8} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {F03FA970-BE2B-4AE2-96FE-7E1F805CEA20} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {DA1D7ADE-452C-4369-83CC-56289176EACD} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {3E55CBE8-AFE8-426D-9470-49D63CD1051C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} diff --git a/src/Testcontainers.Oracle/OracleBuilder.cs b/src/Testcontainers.Oracle/OracleBuilder.cs index c5cd3339e..e6c5710b5 100644 --- a/src/Testcontainers.Oracle/OracleBuilder.cs +++ b/src/Testcontainers.Oracle/OracleBuilder.cs @@ -8,6 +8,7 @@ public sealed class OracleBuilder : ContainerBuilder protected override OracleConfiguration DockerResourceConfiguration { get; } + /// + /// Sets the Oracle database. + /// + /// + /// The database can only be set for Oracle 18 and onwards. + /// + /// The Oracle database. + /// A configured instance of . + public OracleBuilder WithDatabase(string database) + { + return Merge(DockerResourceConfiguration, new OracleConfiguration(database: database)); + } + /// /// Sets the Oracle username. /// @@ -63,6 +77,18 @@ public OracleBuilder WithPassword(string password) public override OracleContainer Build() { Validate(); + + var defaultServiceName = GetDefaultServiceName(); + if (DockerResourceConfiguration.Database == null) + { + return new OracleContainer(WithDatabase(defaultServiceName).DockerResourceConfiguration); + } + + if (DockerResourceConfiguration.Database != defaultServiceName) + { + return new OracleContainer(WithEnvironment("ORACLE_DATABASE", DockerResourceConfiguration.Database).DockerResourceConfiguration); + } + return new OracleContainer(DockerResourceConfiguration); } @@ -72,7 +98,6 @@ protected override OracleBuilder Init() return base.Init() .WithImage(OracleImage) .WithPortBinding(OraclePort, true) - .WithDatabase(DefaultDatabase) .WithUsername(DefaultUsername) .WithPassword(DefaultPassword) .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("DATABASE IS READY TO USE!")); @@ -83,6 +108,18 @@ protected override void Validate() { base.Validate(); + const string message = "The image '{0}' does not support configuring the database. It is only supported on Oracle 18 and onwards."; + + Predicate databaseConfigurationNotSupported = value => + value.Database != null && value.Image.MatchVersion(v => v.Major < 18); + + _ = Guard.Argument(DockerResourceConfiguration, nameof(DockerResourceConfiguration.Database)) + .ThrowIf(argument => databaseConfigurationNotSupported(argument.Value), _ => throw new NotSupportedException(string.Format(message, DockerResourceConfiguration.Image.FullName))); + + _ = Guard.Argument(DockerResourceConfiguration.Username, nameof(DockerResourceConfiguration.Username)) + .NotNull() + .NotEmpty(); + _ = Guard.Argument(DockerResourceConfiguration.Password, nameof(DockerResourceConfiguration.Password)) .NotNull() .NotEmpty(); @@ -106,16 +143,18 @@ protected override OracleBuilder Merge(OracleConfiguration oldValue, OracleConfi return new OracleBuilder(new OracleConfiguration(oldValue, newValue)); } - /// - /// Sets the Oracle database. - /// - /// - /// The Docker image does not allow to configure the database. - /// - /// The Oracle database. - /// A configured instance of . - private OracleBuilder WithDatabase(string database) + private string GetDefaultServiceName() { - return Merge(DockerResourceConfiguration, new OracleConfiguration(database: database)); + if (DockerResourceConfiguration.Image.MatchVersion(v => v.Major >= 23)) + { + return "FREEPDB1"; + } + + if (DockerResourceConfiguration.Image.MatchVersion(v => v.Major > 11)) + { + return "XEPDB1"; + } + + return "XE"; } } \ No newline at end of file diff --git a/tests/Testcontainers.Oracle.Tests/OracleContainerTest.cs b/tests/Testcontainers.Oracle.Tests/OracleContainerTest.cs index 5b6984dd0..e0bf81335 100644 --- a/tests/Testcontainers.Oracle.Tests/OracleContainerTest.cs +++ b/tests/Testcontainers.Oracle.Tests/OracleContainerTest.cs @@ -1,25 +1,13 @@ namespace Testcontainers.Oracle; -public sealed class OracleContainerTest : IAsyncLifetime +public abstract class OracleContainerTest(OracleContainerTest.OracleFixture oracleFixture) { - private readonly OracleContainer _oracleContainer = new OracleBuilder().Build(); - - public Task InitializeAsync() - { - return _oracleContainer.StartAsync(); - } - - public Task DisposeAsync() - { - return _oracleContainer.DisposeAsync().AsTask(); - } - [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] public void ConnectionStateReturnsOpen() { // Given - using DbConnection connection = new OracleConnection(_oracleContainer.GetConnectionString()); + using DbConnection connection = oracleFixture.CreateConnection(); // When connection.Open(); @@ -36,11 +24,64 @@ public async Task ExecScriptReturnsSuccessful() const string scriptContent = "SELECT 1 FROM DUAL;"; // When - var execResult = await _oracleContainer.ExecScriptAsync(scriptContent) + var execResult = await oracleFixture.Container.ExecScriptAsync(scriptContent) .ConfigureAwait(true); // Then Assert.True(0L.Equals(execResult.ExitCode), execResult.Stderr); Assert.Empty(execResult.Stderr); } + + public abstract class OracleFixture(IMessageSink messageSink, string edition, int? version, string database = null) : DbContainerFixture(messageSink) + { + public override DbProviderFactory DbProviderFactory => OracleClientFactory.Instance; + + protected override OracleBuilder Configure(OracleBuilder builder) + { + if (edition == null && version == null) + { + return builder; + } + + var image = $"gvenzl/oracle-{edition}:{version}-slim-faststart"; + return database == null ? builder.WithImage(image) : builder.WithImage(image).WithDatabase(database); + } + } + +#if ORACLE_DEFAULT + [UsedImplicitly] public sealed class OracleDefault(OracleDefaultFixture fixture) : OracleContainerTest(fixture), IClassFixture; + [UsedImplicitly] public sealed class OracleDefaultFixture(IMessageSink messageSink) : OracleFixture(messageSink, null, null); +#endif + +#if ORACLE_11 + [UsedImplicitly] public sealed class Oracle11(Oracle11Fixture fixture) : OracleContainerTest(fixture), IClassFixture; + [UsedImplicitly] public sealed class Oracle11Fixture(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 11); +#endif + +#if ORACLE_18 + [UsedImplicitly] public sealed class Oracle18(Oracle18Fixture fixture) : OracleContainerTest(fixture), IClassFixture; + [UsedImplicitly] public sealed class Oracle18Default(Oracle18FixtureDefault fixture) : OracleContainerTest(fixture), IClassFixture; + [UsedImplicitly] public sealed class Oracle18Scott(Oracle18FixtureScott fixture) : OracleContainerTest(fixture), IClassFixture; + [UsedImplicitly] public sealed class Oracle18Fixture(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 18); + [UsedImplicitly] public sealed class Oracle18FixtureDefault(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 18, "XEPDB1"); + [UsedImplicitly] public sealed class Oracle18FixtureScott(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 18, "SCOTT"); +#endif + +#if ORACLE_21 + [UsedImplicitly] public sealed class Oracle21(Oracle21Fixture fixture) : OracleContainerTest(fixture), IClassFixture; + [UsedImplicitly] public sealed class Oracle21Default(Oracle21FixtureDefault fixture) : OracleContainerTest(fixture), IClassFixture; + [UsedImplicitly] public sealed class Oracle21Scott(Oracle21FixtureScott fixture) : OracleContainerTest(fixture), IClassFixture; + [UsedImplicitly] public sealed class Oracle21Fixture(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 21); + [UsedImplicitly] public sealed class Oracle21FixtureDefault(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 21, "XEPDB1"); + [UsedImplicitly] public sealed class Oracle21FixtureScott(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 21, "SCOTT"); +#endif + +#if ORACLE_23 + [UsedImplicitly] public sealed class Oracle23(Oracle23Fixture fixture) : OracleContainerTest(fixture), IClassFixture; + [UsedImplicitly] public sealed class Oracle23Default(Oracle23FixtureDefault fixture) : OracleContainerTest(fixture), IClassFixture; + [UsedImplicitly] public sealed class Oracle23Scott(Oracle23FixtureScott fixture) : OracleContainerTest(fixture), IClassFixture; + [UsedImplicitly] public sealed class Oracle23Fixture(IMessageSink messageSink) : OracleFixture(messageSink, "free", 23); + [UsedImplicitly] public sealed class Oracle23FixtureDefault(IMessageSink messageSink) : OracleFixture(messageSink, "free", 23, "FREEPDB1"); + [UsedImplicitly] public sealed class Oracle23FixtureScott(IMessageSink messageSink) : OracleFixture(messageSink, "free", 23, "SCOTT"); +#endif } \ No newline at end of file diff --git a/tests/Testcontainers.Oracle.Tests/Testcontainers.Oracle.Tests.csproj b/tests/Testcontainers.Oracle.Tests/Testcontainers.Oracle.Tests.csproj index 4d619cd84..914e234f4 100644 --- a/tests/Testcontainers.Oracle.Tests/Testcontainers.Oracle.Tests.csproj +++ b/tests/Testcontainers.Oracle.Tests/Testcontainers.Oracle.Tests.csproj @@ -3,6 +3,7 @@ net9.0 false false + $(DefineConstants);ORACLE_DEFAULT @@ -13,6 +14,7 @@ + \ No newline at end of file diff --git a/tests/Testcontainers.Oracle.Tests/Usings.cs b/tests/Testcontainers.Oracle.Tests/Usings.cs index eb37bd7e3..e1e61a204 100644 --- a/tests/Testcontainers.Oracle.Tests/Usings.cs +++ b/tests/Testcontainers.Oracle.Tests/Usings.cs @@ -2,5 +2,8 @@ global using System.Data.Common; global using System.Threading.Tasks; global using DotNet.Testcontainers.Commons; +global using JetBrains.Annotations; global using Oracle.ManagedDataAccess.Client; -global using Xunit; \ No newline at end of file +global using Testcontainers.Xunit; +global using Xunit; +global using Xunit.Abstractions; \ No newline at end of file diff --git a/tests/Testcontainers.Oracle11.Tests/.editorconfig b/tests/Testcontainers.Oracle11.Tests/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/tests/Testcontainers.Oracle11.Tests/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/tests/Testcontainers.Oracle11.Tests/Testcontainers.Oracle11.Tests.csproj b/tests/Testcontainers.Oracle11.Tests/Testcontainers.Oracle11.Tests.csproj new file mode 100644 index 000000000..3b6fae47e --- /dev/null +++ b/tests/Testcontainers.Oracle11.Tests/Testcontainers.Oracle11.Tests.csproj @@ -0,0 +1,23 @@ + + + net9.0 + false + false + $(DefineConstants);ORACLE_11 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Testcontainers.Oracle18.Tests/.editorconfig b/tests/Testcontainers.Oracle18.Tests/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/tests/Testcontainers.Oracle18.Tests/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/tests/Testcontainers.Oracle18.Tests/Testcontainers.Oracle18.Tests.csproj b/tests/Testcontainers.Oracle18.Tests/Testcontainers.Oracle18.Tests.csproj new file mode 100644 index 000000000..cdd26379a --- /dev/null +++ b/tests/Testcontainers.Oracle18.Tests/Testcontainers.Oracle18.Tests.csproj @@ -0,0 +1,23 @@ + + + net9.0 + false + false + $(DefineConstants);ORACLE_18 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Testcontainers.Oracle21.Tests/.editorconfig b/tests/Testcontainers.Oracle21.Tests/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/tests/Testcontainers.Oracle21.Tests/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/tests/Testcontainers.Oracle21.Tests/Testcontainers.Oracle21.Tests.csproj b/tests/Testcontainers.Oracle21.Tests/Testcontainers.Oracle21.Tests.csproj new file mode 100644 index 000000000..a75f705bc --- /dev/null +++ b/tests/Testcontainers.Oracle21.Tests/Testcontainers.Oracle21.Tests.csproj @@ -0,0 +1,23 @@ + + + net9.0 + false + false + $(DefineConstants);ORACLE_21 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Testcontainers.Oracle23.Tests/.editorconfig b/tests/Testcontainers.Oracle23.Tests/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/tests/Testcontainers.Oracle23.Tests/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/tests/Testcontainers.Oracle23.Tests/Testcontainers.Oracle23.Tests.csproj b/tests/Testcontainers.Oracle23.Tests/Testcontainers.Oracle23.Tests.csproj new file mode 100644 index 000000000..60f0cfc65 --- /dev/null +++ b/tests/Testcontainers.Oracle23.Tests/Testcontainers.Oracle23.Tests.csproj @@ -0,0 +1,23 @@ + + + net9.0 + false + false + $(DefineConstants);ORACLE_23 + + + + + + + + + + + + + + + + + \ No newline at end of file