Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Oracle support #1321

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
<PackageVersion Include="NATS.Client" Version="1.0.8"/>
<PackageVersion Include="Neo4j.Driver" Version="5.5.0"/>
<PackageVersion Include="Npgsql" Version="6.0.11"/>
<PackageVersion Include="Oracle.ManagedDataAccess.Core" Version="3.21.160"/>
<PackageVersion Include="Oracle.ManagedDataAccess.Core" Version="23.6.1"/>
<PackageVersion Include="RabbitMQ.Client" Version="6.4.0"/>
<PackageVersion Include="RavenDB.Client" Version="5.4.100"/>
<PackageVersion Include="Selenium.WebDriver" Version="4.8.1"/>
Expand Down
54 changes: 40 additions & 14 deletions src/Testcontainers.Oracle/OracleBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public sealed class OracleBuilder : ContainerBuilder<OracleBuilder, OracleContai

public const ushort OraclePort = 1521;

[Obsolete("Not used anymore. Only valid for Oracle images > 11 and < 23")]
public const string DefaultDatabase = "XEPDB1";

public const string DefaultUsername = "oracle";
Expand Down Expand Up @@ -59,20 +60,55 @@ public OracleBuilder WithPassword(string password)
.WithEnvironment("APP_USER_PASSWORD", password);
}

/// <summary>
/// Sets the Oracle database.
/// </summary>
/// <remarks>
/// The database can only be set for Oracle 18 and onwards.
/// </remarks>
/// <param name="database">The Oracle database.</param>
/// <returns>A configured instance of <see cref="OracleBuilder" />.</returns>
public OracleBuilder WithDatabase(string database)
{
return Merge(DockerResourceConfiguration, new OracleConfiguration(database: database));
}

/// <inheritdoc />
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);
}

private string GetDefaultServiceName()
{
if (DockerResourceConfiguration.Image.MatchVersion(v => v.Major >= 23))
return "FREEPDB1";

if (DockerResourceConfiguration.Image.MatchVersion(v => v.Major > 11))
return "XEPDB1";

return "XE";
}

/// <inheritdoc />
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!"));
Expand All @@ -86,6 +122,9 @@ protected override void Validate()
_ = Guard.Argument(DockerResourceConfiguration.Password, nameof(DockerResourceConfiguration.Password))
.NotNull()
.NotEmpty();

if (DockerResourceConfiguration.Database != null && DockerResourceConfiguration.Image.MatchVersion(v => v.Major < 18))
throw new NotSupportedException($"Setting the database is not supported with {DockerResourceConfiguration.Image.FullName}. It is only supported on Oracle 18 and onwards.");
}

/// <inheritdoc />
Expand All @@ -105,17 +144,4 @@ protected override OracleBuilder Merge(OracleConfiguration oldValue, OracleConfi
{
return new OracleBuilder(new OracleConfiguration(oldValue, newValue));
}

/// <summary>
/// Sets the Oracle database.
/// </summary>
/// <remarks>
/// The Docker image does not allow to configure the database.
/// </remarks>
/// <param name="database">The Oracle database.</param>
/// <returns>A configured instance of <see cref="OracleBuilder" />.</returns>
private OracleBuilder WithDatabase(string database)
{
return Merge(DockerResourceConfiguration, new OracleConfiguration(database: database));
}
}
58 changes: 43 additions & 15 deletions tests/Testcontainers.Oracle.Tests/OracleContainerTest.cs
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -36,11 +24,51 @@ 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<OracleBuilder, OracleContainer>(messageSink)
{
public override DbProviderFactory DbProviderFactory => OracleClientFactory.Instance;

protected override OracleBuilder Configure(OracleBuilder builder)
{
if (edition == default && version == default)
{
return builder;
}

var image = $"gvenzl/oracle-{edition}:{version}-slim-faststart";
return database == null ? builder.WithImage(image) : builder.WithImage(image).WithDatabase(database);
}
}

[UsedImplicitly] public sealed class OracleDefault(OracleDefaultFixture fixture) : OracleContainerTest(fixture), IClassFixture<OracleDefaultFixture>;
[UsedImplicitly] public sealed class Oracle11(Oracle11Fixture fixture) : OracleContainerTest(fixture), IClassFixture<Oracle11Fixture>;
[UsedImplicitly] public sealed class Oracle18(Oracle18Fixture fixture) : OracleContainerTest(fixture), IClassFixture<Oracle18Fixture>;
[UsedImplicitly] public sealed class Oracle21(Oracle21Fixture fixture) : OracleContainerTest(fixture), IClassFixture<Oracle21Fixture>;
[UsedImplicitly] public sealed class Oracle23(Oracle23Fixture fixture) : OracleContainerTest(fixture), IClassFixture<Oracle23Fixture>;
[UsedImplicitly] public sealed class Oracle18Default(Oracle18FixtureDefault fixture) : OracleContainerTest(fixture), IClassFixture<Oracle18FixtureDefault>;
[UsedImplicitly] public sealed class Oracle21Default(Oracle21FixtureDefault fixture) : OracleContainerTest(fixture), IClassFixture<Oracle21FixtureDefault>;
[UsedImplicitly] public sealed class Oracle23Default(Oracle23FixtureDefault fixture) : OracleContainerTest(fixture), IClassFixture<Oracle23FixtureDefault>;
[UsedImplicitly] public sealed class Oracle18Scott(Oracle18FixtureScott fixture) : OracleContainerTest(fixture), IClassFixture<Oracle18FixtureScott>;
[UsedImplicitly] public sealed class Oracle21Scott(Oracle21FixtureScott fixture) : OracleContainerTest(fixture), IClassFixture<Oracle21FixtureScott>;
[UsedImplicitly] public sealed class Oracle23Scott(Oracle23FixtureScott fixture) : OracleContainerTest(fixture), IClassFixture<Oracle23FixtureScott>;

[UsedImplicitly] public class OracleDefaultFixture(IMessageSink messageSink) : OracleFixture(messageSink, default, default);
[UsedImplicitly] public class Oracle11Fixture(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 11);
[UsedImplicitly] public class Oracle18Fixture(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 18);
[UsedImplicitly] public class Oracle21Fixture(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 21);
[UsedImplicitly] public class Oracle23Fixture(IMessageSink messageSink) : OracleFixture(messageSink, "free", 23);
[UsedImplicitly] public class Oracle18FixtureDefault(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 18, "XEPDB1");
[UsedImplicitly] public class Oracle21FixtureDefault(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 21, "XEPDB1");
[UsedImplicitly] public class Oracle23FixtureDefault(IMessageSink messageSink) : OracleFixture(messageSink, "free", 23, "FREEPDB1");
[UsedImplicitly] public class Oracle18FixtureScott(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 18, "SCOTT");
[UsedImplicitly] public class Oracle21FixtureScott(IMessageSink messageSink) : OracleFixture(messageSink, "xe", 21, "SCOTT");
[UsedImplicitly] public class Oracle23FixtureScott(IMessageSink messageSink) : OracleFixture(messageSink, "free", 23, "SCOTT");
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../src/Testcontainers.Oracle/Testcontainers.Oracle.csproj"/>
<ProjectReference Include="../../src/Testcontainers.Xunit/Testcontainers.Xunit.csproj" />
<ProjectReference Include="../Testcontainers.Commons/Testcontainers.Commons.csproj"/>
</ItemGroup>
</Project>
5 changes: 4 additions & 1 deletion tests/Testcontainers.Oracle.Tests/Usings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
global using Testcontainers.Xunit;
global using Xunit;
global using Xunit.Abstractions;
Loading