From 1797277422b0e5e0f784b61608673f5ec0a23778 Mon Sep 17 00:00:00 2001 From: Stefan Schranz Date: Sun, 22 Sep 2024 16:21:39 +0200 Subject: [PATCH 1/7] GH-1261 [Enhancement]: Support Neo4j Enterprise - Added small QoL method to support Neo4j enterprise - Added validation to throw when enterprise is requested without the correct license agreement environment variable - Added tests for enterprise & configuration changes - Changed existing test to use IAsyncDisposable instead of sync-over-async disposal --- src/Testcontainers.Neo4j/Neo4jBuilder.cs | 35 +++++++++++ .../Neo4jBuilderConfigurationTest.cs | 14 +++++ .../Neo4jContainerTest.cs | 61 ++++++++++++++++--- 3 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 tests/Testcontainers.Neo4j.Tests/Neo4jBuilderConfigurationTest.cs diff --git a/src/Testcontainers.Neo4j/Neo4jBuilder.cs b/src/Testcontainers.Neo4j/Neo4jBuilder.cs index 54770e961..4ecc5d39c 100644 --- a/src/Testcontainers.Neo4j/Neo4jBuilder.cs +++ b/src/Testcontainers.Neo4j/Neo4jBuilder.cs @@ -1,3 +1,6 @@ +using System.Linq; +using DotNet.Testcontainers; + namespace Testcontainers.Neo4j; /// @@ -6,10 +9,16 @@ public sealed class Neo4jBuilder : ContainerBuilder /// Initializes a new instance of the class. /// @@ -39,6 +48,26 @@ public override Neo4jContainer Build() return new Neo4jContainer(DockerResourceConfiguration); } + /// + protected override void Validate() + { + base.Validate(); + + if (IsEnterpriseImage()) + { + const string message = $"The 'enterprise' image requires setting environment variable {AcceptLicenseAgreementEnvVar} to= '{AcceptLicenseAgreementEnvVarValue}'"; + _ = Guard.Argument(DockerResourceConfiguration, "Enterprise License agreement") + .ThrowIf( + argument => !argument.Value.Environments.TryGetValue(AcceptLicenseAgreementEnvVar, out var licenseAgreementValue) || licenseAgreementValue != AcceptLicenseAgreementEnvVarValue, + argument => new ArgumentException(message, argument.Name)); + } + } + + private bool IsEnterpriseImage() + { + return DockerResourceConfiguration.Image.Tag?.Contains("enterprise") ?? false; + } + /// protected override Neo4jBuilder Init() { @@ -68,4 +97,10 @@ protected override Neo4jBuilder Merge(Neo4jConfiguration oldValue, Neo4jConfigur { return new Neo4jBuilder(new Neo4jConfiguration(oldValue, newValue)); } + + public Neo4jBuilder WithEnterpriseEdition() + { + return WithImage(Neo4jEnterpriseImage) + .WithEnvironment(AcceptLicenseAgreementEnvVar, AcceptLicenseAgreementEnvVarValue); + } } \ No newline at end of file diff --git a/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderConfigurationTest.cs b/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderConfigurationTest.cs new file mode 100644 index 000000000..bcd5c9094 --- /dev/null +++ b/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderConfigurationTest.cs @@ -0,0 +1,14 @@ +using System; + +namespace Testcontainers.Neo4j; + +public sealed class Neo4jBuilderConfigurationTest +{ + [Fact] + public void CreatingEnterpriseContainerWithoutLicenseAgreementShouldThrow() + { + Assert.Throws(() => new Neo4jBuilder() + .WithImage(Neo4jBuilder.Neo4jEnterpriseImage) + .Build()); + } +} \ No newline at end of file diff --git a/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs b/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs index 460b38934..1b5f55b23 100644 --- a/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs +++ b/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs @@ -1,9 +1,19 @@ +using System; +using JetBrains.Annotations; + namespace Testcontainers.Neo4j; -public sealed class Neo4jContainerTest : IAsyncLifetime +public abstract class Neo4jContainerTest : IAsyncLifetime { + private const string Neo4jDatabase = "neo4j"; + // # --8<-- [start:UseNeo4jContainer] - private readonly Neo4jContainer _neo4jContainer = new Neo4jBuilder().Build(); + private readonly Neo4jContainer _neo4jContainer; + + private Neo4jContainerTest(Neo4jContainer neo4jContainer) + { + _neo4jContainer = neo4jContainer; + } public Task InitializeAsync() { @@ -17,18 +27,53 @@ public Task DisposeAsync() [Fact] [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public void SessionReturnsDatabase() + public async Task SessionReturnsDatabase() { // Given - const string database = "neo4j"; - - using var driver = GraphDatabase.Driver(_neo4jContainer.GetConnectionString()); + await using var driver = GraphDatabase.Driver(_neo4jContainer.GetConnectionString()); // When - using var session = driver.AsyncSession(sessionConfigBuilder => sessionConfigBuilder.WithDatabase(database)); + await using var session = driver.AsyncSession(sessionConfigBuilder => sessionConfigBuilder.WithDatabase(Neo4jDatabase)); // Then - Assert.Equal(database, session.SessionConfig.Database); + Assert.Equal(Neo4jDatabase, session.SessionConfig.Database); } // # --8<-- [end:UseNeo4jContainer] + + [UsedImplicitly] + public sealed class Neo4jDefaultConfiguration : Neo4jContainerTest + { + public Neo4jDefaultConfiguration() + : base(new Neo4jBuilder().Build()) + { + } + } + + [UsedImplicitly] + public sealed class Neo4jEnterpriseConfiguration : Neo4jContainerTest + { + public Neo4jEnterpriseConfiguration() + : base(new Neo4jBuilder() + .WithEnterpriseEdition() + .Build()) + { + } + + [Fact] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + public async Task DatabaseShouldReturnEnterpriseEdition() + { + // Given + await using var driver = GraphDatabase.Driver(_neo4jContainer.GetConnectionString()); + + // When + await using var session = driver.AsyncSession(sessionConfigBuilder => sessionConfigBuilder.WithDatabase(Neo4jDatabase)); + var result = await session.RunAsync("CALL dbms.components() YIELD edition RETURN edition"); + var record = await result.SingleAsync(); + var edition = record["edition"].As(); + + // Then + Assert.Equal("enterprise", edition); + } + } } \ No newline at end of file From e0028335791cf95ebf9336cd59ef5e54e410dd67 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:58:32 +0200 Subject: [PATCH 2/7] chore: Remove BOM, organize usings --- src/Testcontainers.Neo4j/Neo4jBuilder.cs | 3 --- src/Testcontainers.Neo4j/Usings.cs | 1 + .../Neo4jBuilderConfigurationTest.cs | 4 +--- .../Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs | 11 ++++------- tests/Testcontainers.Neo4j.Tests/Usings.cs | 2 ++ 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Testcontainers.Neo4j/Neo4jBuilder.cs b/src/Testcontainers.Neo4j/Neo4jBuilder.cs index 4ecc5d39c..d6c004599 100644 --- a/src/Testcontainers.Neo4j/Neo4jBuilder.cs +++ b/src/Testcontainers.Neo4j/Neo4jBuilder.cs @@ -1,6 +1,3 @@ -using System.Linq; -using DotNet.Testcontainers; - namespace Testcontainers.Neo4j; /// diff --git a/src/Testcontainers.Neo4j/Usings.cs b/src/Testcontainers.Neo4j/Usings.cs index 79fd3af9b..8e5c20fd5 100644 --- a/src/Testcontainers.Neo4j/Usings.cs +++ b/src/Testcontainers.Neo4j/Usings.cs @@ -1,5 +1,6 @@ global using System; global using Docker.DotNet.Models; +global using DotNet.Testcontainers; global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Configurations; global using DotNet.Testcontainers.Containers; diff --git a/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderConfigurationTest.cs b/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderConfigurationTest.cs index bcd5c9094..11f6b21e9 100644 --- a/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderConfigurationTest.cs +++ b/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderConfigurationTest.cs @@ -1,6 +1,4 @@ -using System; - -namespace Testcontainers.Neo4j; +namespace Testcontainers.Neo4j; public sealed class Neo4jBuilderConfigurationTest { diff --git a/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs b/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs index 1b5f55b23..9d5e75933 100644 --- a/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs +++ b/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs @@ -1,12 +1,9 @@ -using System; -using JetBrains.Annotations; - namespace Testcontainers.Neo4j; public abstract class Neo4jContainerTest : IAsyncLifetime { private const string Neo4jDatabase = "neo4j"; - + // # --8<-- [start:UseNeo4jContainer] private readonly Neo4jContainer _neo4jContainer; @@ -39,7 +36,7 @@ public async Task SessionReturnsDatabase() Assert.Equal(Neo4jDatabase, session.SessionConfig.Database); } // # --8<-- [end:UseNeo4jContainer] - + [UsedImplicitly] public sealed class Neo4jDefaultConfiguration : Neo4jContainerTest { @@ -48,7 +45,7 @@ public Neo4jDefaultConfiguration() { } } - + [UsedImplicitly] public sealed class Neo4jEnterpriseConfiguration : Neo4jContainerTest { @@ -71,7 +68,7 @@ public async Task DatabaseShouldReturnEnterpriseEdition() var result = await session.RunAsync("CALL dbms.components() YIELD edition RETURN edition"); var record = await result.SingleAsync(); var edition = record["edition"].As(); - + // Then Assert.Equal("enterprise", edition); } diff --git a/tests/Testcontainers.Neo4j.Tests/Usings.cs b/tests/Testcontainers.Neo4j.Tests/Usings.cs index a6d11cfdb..b7288ff63 100644 --- a/tests/Testcontainers.Neo4j.Tests/Usings.cs +++ b/tests/Testcontainers.Neo4j.Tests/Usings.cs @@ -1,4 +1,6 @@ +global using System; global using System.Threading.Tasks; global using DotNet.Testcontainers.Commons; +global using JetBrains.Annotations; global using Neo4j.Driver; global using Xunit; \ No newline at end of file From 122450cdc1e3cd19bf01996d751e3f36db28fe1e Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:33:12 +0200 Subject: [PATCH 3/7] feat: Expect devs accepting the license agreement --- src/Testcontainers.Neo4j/Neo4jBuilder.cs | 42 ++++++++++--------- .../Neo4jContainerTest.cs | 4 +- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/Testcontainers.Neo4j/Neo4jBuilder.cs b/src/Testcontainers.Neo4j/Neo4jBuilder.cs index d6c004599..530e49d3a 100644 --- a/src/Testcontainers.Neo4j/Neo4jBuilder.cs +++ b/src/Testcontainers.Neo4j/Neo4jBuilder.cs @@ -14,7 +14,9 @@ public sealed class Neo4jBuilder : ContainerBuilder /// Initializes a new instance of the class. @@ -38,6 +40,20 @@ private Neo4jBuilder(Neo4jConfiguration resourceConfiguration) /// protected override Neo4jConfiguration DockerResourceConfiguration { get; } + /// + /// Sets the image to the Neo4j Enterprise Edition. + /// + /// + /// When is set to true, the Neo4j Enterprise Edition license is accepted. + /// + /// A boolean value indicating whether the Neo4j Enterprise Edition license agreement is accepted. + /// A configured instance of . + public Neo4jBuilder WithEnterpriseEdition(bool acceptLicenseAgreement) + { + return WithImage(Neo4jEnterpriseImage) + .WithEnvironment(AcceptLicenseAgreementEnvVar, acceptLicenseAgreement ? AcceptLicenseAgreement : DeclineLicenseAgreement); + } + /// public override Neo4jContainer Build() { @@ -48,21 +64,15 @@ public override Neo4jContainer Build() /// protected override void Validate() { + var message = "The image '" + DockerResourceConfiguration.Image.FullName + "' requires you to accept a license agreement."; + base.Validate(); - if (IsEnterpriseImage()) - { - const string message = $"The 'enterprise' image requires setting environment variable {AcceptLicenseAgreementEnvVar} to= '{AcceptLicenseAgreementEnvVarValue}'"; - _ = Guard.Argument(DockerResourceConfiguration, "Enterprise License agreement") - .ThrowIf( - argument => !argument.Value.Environments.TryGetValue(AcceptLicenseAgreementEnvVar, out var licenseAgreementValue) || licenseAgreementValue != AcceptLicenseAgreementEnvVarValue, - argument => new ArgumentException(message, argument.Name)); - } - } + Predicate licenseAgreementNotAccepted = value => value.Image.Tag != null && value.Image.Tag.Contains("enterprise") + && (!value.Environments.TryGetValue(AcceptLicenseAgreementEnvVar, out var licenseAgreementValue) || !AcceptLicenseAgreement.Equals(licenseAgreementValue, StringComparison.Ordinal)); - private bool IsEnterpriseImage() - { - return DockerResourceConfiguration.Image.Tag?.Contains("enterprise") ?? false; + _ = Guard.Argument(DockerResourceConfiguration, nameof(DockerResourceConfiguration.Image)) + .ThrowIf(argument => licenseAgreementNotAccepted(argument.Value), argument => throw new ArgumentException(message, argument.Name)); } /// @@ -94,10 +104,4 @@ protected override Neo4jBuilder Merge(Neo4jConfiguration oldValue, Neo4jConfigur { return new Neo4jBuilder(new Neo4jConfiguration(oldValue, newValue)); } - - public Neo4jBuilder WithEnterpriseEdition() - { - return WithImage(Neo4jEnterpriseImage) - .WithEnvironment(AcceptLicenseAgreementEnvVar, AcceptLicenseAgreementEnvVarValue); - } } \ No newline at end of file diff --git a/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs b/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs index 9d5e75933..13f02c0f9 100644 --- a/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs +++ b/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs @@ -50,9 +50,7 @@ public Neo4jDefaultConfiguration() public sealed class Neo4jEnterpriseConfiguration : Neo4jContainerTest { public Neo4jEnterpriseConfiguration() - : base(new Neo4jBuilder() - .WithEnterpriseEdition() - .Build()) + : base(new Neo4jBuilder().WithEnterpriseEdition(true).Build()) { } From 286284f15a4e091da6ba4c40e5e36ff0a2865bd5 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:05:11 +0200 Subject: [PATCH 4/7] docs: Add enterprise edition to docs --- docs/modules/neo4j.md | 9 +++- src/Testcontainers.Neo4j/Neo4jBuilder.cs | 3 +- .../Neo4jBuilderConfigurationTest.cs | 12 ----- .../Neo4jBuilderTest.cs | 20 ++++++++ .../Neo4jContainerTest.cs | 46 +++++++++---------- 5 files changed, 52 insertions(+), 38 deletions(-) delete mode 100644 tests/Testcontainers.Neo4j.Tests/Neo4jBuilderConfigurationTest.cs create mode 100644 tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs diff --git a/docs/modules/neo4j.md b/docs/modules/neo4j.md index 6f6f0d12b..abcb9b6a1 100644 --- a/docs/modules/neo4j.md +++ b/docs/modules/neo4j.md @@ -8,7 +8,14 @@ Add the following dependency to your project file: dotnet add package Testcontainers.Neo4j ``` -You can start an Neo4j container instance from any .NET application. This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method. +You can start an Neo4j container instance from any .NET application. Here, we create different container instances and pass them to the base test class. This allows us to test different configurations. + +=== "Create Container Instance" + ```csharp + --8<-- "tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs:CreateNeo4jContainer" + ``` + +This example uses xUnit.net's `IAsyncLifetime` interface to manage the lifecycle of the container. The container is started in the `InitializeAsync` method before the test method runs, ensuring that the environment is ready for testing. After the test completes, the container is removed in the `DisposeAsync` method. === "Usage Example" ```csharp diff --git a/src/Testcontainers.Neo4j/Neo4jBuilder.cs b/src/Testcontainers.Neo4j/Neo4jBuilder.cs index 530e49d3a..77ac6219f 100644 --- a/src/Testcontainers.Neo4j/Neo4jBuilder.cs +++ b/src/Testcontainers.Neo4j/Neo4jBuilder.cs @@ -50,8 +50,7 @@ private Neo4jBuilder(Neo4jConfiguration resourceConfiguration) /// A configured instance of . public Neo4jBuilder WithEnterpriseEdition(bool acceptLicenseAgreement) { - return WithImage(Neo4jEnterpriseImage) - .WithEnvironment(AcceptLicenseAgreementEnvVar, acceptLicenseAgreement ? AcceptLicenseAgreement : DeclineLicenseAgreement); + return WithImage(Neo4jEnterpriseImage).WithEnvironment(AcceptLicenseAgreementEnvVar, acceptLicenseAgreement ? AcceptLicenseAgreement : DeclineLicenseAgreement); } /// diff --git a/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderConfigurationTest.cs b/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderConfigurationTest.cs deleted file mode 100644 index 11f6b21e9..000000000 --- a/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderConfigurationTest.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Testcontainers.Neo4j; - -public sealed class Neo4jBuilderConfigurationTest -{ - [Fact] - public void CreatingEnterpriseContainerWithoutLicenseAgreementShouldThrow() - { - Assert.Throws(() => new Neo4jBuilder() - .WithImage(Neo4jBuilder.Neo4jEnterpriseImage) - .Build()); - } -} \ No newline at end of file diff --git a/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs b/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs new file mode 100644 index 000000000..4799f59a9 --- /dev/null +++ b/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs @@ -0,0 +1,20 @@ +namespace Testcontainers.Neo4j; + +public sealed class Neo4jBuilderTest +{ + [Theory] + [ClassData(typeof(Neo4jBuilderConfigurations))] + public void ThrowsArgumentExceptionWhenEnterpriseEditionLicenseAgreementIsNotAccepted(Neo4jBuilder neo4jBuilder) + { + Assert.Throws(neo4jBuilder.Build); + } + + private sealed class Neo4jBuilderConfigurations : TheoryData + { + public Neo4jBuilderConfigurations() + { + Add(new Neo4jBuilder().WithImage(Neo4jBuilder.Neo4jEnterpriseImage)); + Add(new Neo4jBuilder().WithEnterpriseEdition(false)); + } + } +} \ No newline at end of file diff --git a/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs b/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs index 13f02c0f9..0cbe70728 100644 --- a/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs +++ b/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs @@ -2,9 +2,6 @@ namespace Testcontainers.Neo4j; public abstract class Neo4jContainerTest : IAsyncLifetime { - private const string Neo4jDatabase = "neo4j"; - - // # --8<-- [start:UseNeo4jContainer] private readonly Neo4jContainer _neo4jContainer; private Neo4jContainerTest(Neo4jContainer neo4jContainer) @@ -12,6 +9,9 @@ private Neo4jContainerTest(Neo4jContainer neo4jContainer) _neo4jContainer = neo4jContainer; } + public abstract string Edition { get; } + + // # --8<-- [start:UseNeo4jContainer] public Task InitializeAsync() { return _neo4jContainer.StartAsync(); @@ -27,16 +27,28 @@ public Task DisposeAsync() public async Task SessionReturnsDatabase() { // Given + const string neo4jDatabase = "neo4j"; + await using var driver = GraphDatabase.Driver(_neo4jContainer.GetConnectionString()); // When - await using var session = driver.AsyncSession(sessionConfigBuilder => sessionConfigBuilder.WithDatabase(Neo4jDatabase)); + await using var session = driver.AsyncSession(sessionConfigBuilder => sessionConfigBuilder.WithDatabase(neo4jDatabase)); + + var result = await session.RunAsync("CALL dbms.components() YIELD edition RETURN edition") + .ConfigureAwait(true); + + var record = await result.SingleAsync() + .ConfigureAwait(true); + + var edition = record["edition"].As(); // Then - Assert.Equal(Neo4jDatabase, session.SessionConfig.Database); + Assert.Equal(neo4jDatabase, session.SessionConfig.Database); + Assert.Equal(Edition, edition); } // # --8<-- [end:UseNeo4jContainer] + // # --8<-- [start:CreateNeo4jContainer] [UsedImplicitly] public sealed class Neo4jDefaultConfiguration : Neo4jContainerTest { @@ -44,31 +56,19 @@ public Neo4jDefaultConfiguration() : base(new Neo4jBuilder().Build()) { } + + public override string Edition => "community"; } [UsedImplicitly] - public sealed class Neo4jEnterpriseConfiguration : Neo4jContainerTest + public sealed class Neo4jEnterpriseEditionConfiguration : Neo4jContainerTest { - public Neo4jEnterpriseConfiguration() + public Neo4jEnterpriseEditionConfiguration() : base(new Neo4jBuilder().WithEnterpriseEdition(true).Build()) { } - [Fact] - [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] - public async Task DatabaseShouldReturnEnterpriseEdition() - { - // Given - await using var driver = GraphDatabase.Driver(_neo4jContainer.GetConnectionString()); - - // When - await using var session = driver.AsyncSession(sessionConfigBuilder => sessionConfigBuilder.WithDatabase(Neo4jDatabase)); - var result = await session.RunAsync("CALL dbms.components() YIELD edition RETURN edition"); - var record = await result.SingleAsync(); - var edition = record["edition"].As(); - - // Then - Assert.Equal("enterprise", edition); - } + public override string Edition => "enterprise"; } + // # --8<-- [end:CreateNeo4jContainer] } \ No newline at end of file From d415f6c836d6e8f0926c39762a8f349eab246eb1 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Thu, 3 Oct 2024 12:58:53 +0200 Subject: [PATCH 5/7] chore: Remove BOM --- src/Testcontainers.Neo4j/Neo4jBuilder.cs | 4 ++-- tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs | 2 +- tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Testcontainers.Neo4j/Neo4jBuilder.cs b/src/Testcontainers.Neo4j/Neo4jBuilder.cs index 77ac6219f..1c9f76b59 100644 --- a/src/Testcontainers.Neo4j/Neo4jBuilder.cs +++ b/src/Testcontainers.Neo4j/Neo4jBuilder.cs @@ -63,7 +63,7 @@ public override Neo4jContainer Build() /// protected override void Validate() { - var message = "The image '" + DockerResourceConfiguration.Image.FullName + "' requires you to accept a license agreement."; + const string message = "The image '{0}' requires you to accept a license agreement."; base.Validate(); @@ -71,7 +71,7 @@ protected override void Validate() && (!value.Environments.TryGetValue(AcceptLicenseAgreementEnvVar, out var licenseAgreementValue) || !AcceptLicenseAgreement.Equals(licenseAgreementValue, StringComparison.Ordinal)); _ = Guard.Argument(DockerResourceConfiguration, nameof(DockerResourceConfiguration.Image)) - .ThrowIf(argument => licenseAgreementNotAccepted(argument.Value), argument => throw new ArgumentException(message, argument.Name)); + .ThrowIf(argument => licenseAgreementNotAccepted(argument.Value), argument => throw new ArgumentException(string.Format(message, DockerResourceConfiguration.Image.FullName), argument.Name)); } /// diff --git a/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs b/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs index 4799f59a9..d81976086 100644 --- a/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs +++ b/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs @@ -1,4 +1,4 @@ -namespace Testcontainers.Neo4j; +namespace Testcontainers.Neo4j; public sealed class Neo4jBuilderTest { diff --git a/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs b/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs index 0cbe70728..b81a48eac 100644 --- a/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs +++ b/tests/Testcontainers.Neo4j.Tests/Neo4jContainerTest.cs @@ -29,10 +29,10 @@ public async Task SessionReturnsDatabase() // Given const string neo4jDatabase = "neo4j"; - await using var driver = GraphDatabase.Driver(_neo4jContainer.GetConnectionString()); + using var driver = GraphDatabase.Driver(_neo4jContainer.GetConnectionString()); // When - await using var session = driver.AsyncSession(sessionConfigBuilder => sessionConfigBuilder.WithDatabase(neo4jDatabase)); + using var session = driver.AsyncSession(sessionConfigBuilder => sessionConfigBuilder.WithDatabase(neo4jDatabase)); var result = await session.RunAsync("CALL dbms.components() YIELD edition RETURN edition") .ConfigureAwait(true); From afb792b63d5f38bd405cc245398024d0a8f349eb Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Thu, 3 Oct 2024 18:18:01 +0200 Subject: [PATCH 6/7] feat: Append enterprise suffix instead of overriding the image --- src/Testcontainers.Neo4j/Neo4jBuilder.cs | 46 ++++++++++++++++++- src/Testcontainers.Neo4j/Usings.cs | 3 ++ .../Neo4jBuilderTest.cs | 30 ++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/Testcontainers.Neo4j/Neo4jBuilder.cs b/src/Testcontainers.Neo4j/Neo4jBuilder.cs index 1c9f76b59..56dce612d 100644 --- a/src/Testcontainers.Neo4j/Neo4jBuilder.cs +++ b/src/Testcontainers.Neo4j/Neo4jBuilder.cs @@ -45,12 +45,56 @@ private Neo4jBuilder(Neo4jConfiguration resourceConfiguration) /// /// /// When is set to true, the Neo4j Enterprise Edition license is accepted. + /// If the Community Edition is explicitly used, we do not update the image tag. /// /// A boolean value indicating whether the Neo4j Enterprise Edition license agreement is accepted. /// A configured instance of . public Neo4jBuilder WithEnterpriseEdition(bool acceptLicenseAgreement) { - return WithImage(Neo4jEnterpriseImage).WithEnvironment(AcceptLicenseAgreementEnvVar, acceptLicenseAgreement ? AcceptLicenseAgreement : DeclineLicenseAgreement); + const string communitySuffix = "community"; + + const string enterpriseSuffix = "enterprise"; + + var operatingSystems = new[] { "bullseye", "ubi9" }; + + var image = DockerResourceConfiguration.Image; + + string tag; + + // If the specified image does not contain a tag (but a digest), we cannot determine the + // actual version and append the enterprise suffix. We expect the developer to set the + // Enterprise Edition. + if (image.Tag == null) + { + tag = null; + } + else if (image.MatchLatestOrNightly()) + { + tag = enterpriseSuffix; + } + else if (image.MatchVersion(v => v.Contains(communitySuffix))) + { + tag = image.Tag; + } + else if (image.MatchVersion(v => v.Contains(enterpriseSuffix))) + { + tag = image.Tag; + } + else if (image.MatchVersion(v => operatingSystems.Any(v.Contains))) + { + MatchEvaluator evaluator = match => $"{enterpriseSuffix}-{match.Value}"; + tag = Regex.Replace(image.Tag, string.Join("|", operatingSystems), evaluator); + } + else + { + tag = $"{image.Tag}-{enterpriseSuffix}"; + } + + var enterpriseImage = new DockerImage(image.Repository, image.Registry, tag, tag == null ? image.Digest : null); + + var licenseAgreement = acceptLicenseAgreement ? AcceptLicenseAgreement : DeclineLicenseAgreement; + + return WithImage(enterpriseImage).WithEnvironment(AcceptLicenseAgreementEnvVar, licenseAgreement); } /// diff --git a/src/Testcontainers.Neo4j/Usings.cs b/src/Testcontainers.Neo4j/Usings.cs index 8e5c20fd5..adef2193a 100644 --- a/src/Testcontainers.Neo4j/Usings.cs +++ b/src/Testcontainers.Neo4j/Usings.cs @@ -1,7 +1,10 @@ global using System; +global using System.Linq; +global using System.Text.RegularExpressions; global using Docker.DotNet.Models; global using DotNet.Testcontainers; global using DotNet.Testcontainers.Builders; global using DotNet.Testcontainers.Configurations; global using DotNet.Testcontainers.Containers; +global using DotNet.Testcontainers.Images; global using JetBrains.Annotations; \ No newline at end of file diff --git a/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs b/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs index d81976086..cb9612ee7 100644 --- a/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs +++ b/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs @@ -3,6 +3,36 @@ namespace Testcontainers.Neo4j; public sealed class Neo4jBuilderTest { [Theory] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] + [InlineData("neo4j:5.23.0", "5.23.0-enterprise")] + [InlineData("neo4j:5.23", "5.23-enterprise")] + [InlineData("neo4j:5", "5-enterprise")] + [InlineData("neo4j:5.23.0-community", "5.23.0-community")] + [InlineData("neo4j:5.23-community", "5.23-community")] + [InlineData("neo4j:5-community", "5-community")] + [InlineData("neo4j:community", "community")] + [InlineData("neo4j:5.23.0-bullseye", "5.23.0-enterprise-bullseye")] + [InlineData("neo4j:5.23-bullseye", "5.23-enterprise-bullseye")] + [InlineData("neo4j:5-bullseye", "5-enterprise-bullseye")] + [InlineData("neo4j:bullseye", "enterprise-bullseye")] + [InlineData("neo4j:5.23.0-enterprise-bullseye", "5.23.0-enterprise-bullseye")] + [InlineData("neo4j:5.23-enterprise-bullseye", "5.23-enterprise-bullseye")] + [InlineData("neo4j:5-enterprise-bullseye", "5-enterprise-bullseye")] + [InlineData("neo4j:enterprise-bullseye", "enterprise-bullseye")] + [InlineData("neo4j:5.23.0-enterprise", "5.23.0-enterprise")] + [InlineData("neo4j:5.23-enterprise", "5.23-enterprise")] + [InlineData("neo4j:5-enterprise", "5-enterprise")] + [InlineData("neo4j:enterprise", "enterprise")] + [InlineData("neo4j", "enterprise")] + [InlineData("neo4j@sha256:20eb19e3d60f9f07c12c89eac8d8722e393be7e45c6d7e56004a2c493b8e2032", null)] + public void AppendsEnterpriseTagWhenEnterpriseEditionLicenseAgreementIsAccepted(string image, string expected) + { + var neo4jContainer = new Neo4jBuilder().WithImage(image).WithEnterpriseEdition(true).Build(); + Assert.Equal(expected, neo4jContainer.Image.Tag); + } + + [Theory] + [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))] [ClassData(typeof(Neo4jBuilderConfigurations))] public void ThrowsArgumentExceptionWhenEnterpriseEditionLicenseAgreementIsNotAccepted(Neo4jBuilder neo4jBuilder) { From c80e83007db83fb51b53668fe5983225c24aa175 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Thu, 3 Oct 2024 18:52:38 +0200 Subject: [PATCH 7/7] chore: Remove Neo4jEnterpriseImage const --- src/Testcontainers.Neo4j/Neo4jBuilder.cs | 4 +--- tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Testcontainers.Neo4j/Neo4jBuilder.cs b/src/Testcontainers.Neo4j/Neo4jBuilder.cs index 56dce612d..14013d278 100644 --- a/src/Testcontainers.Neo4j/Neo4jBuilder.cs +++ b/src/Testcontainers.Neo4j/Neo4jBuilder.cs @@ -6,8 +6,6 @@ public sealed class Neo4jBuilder : ContainerBuilder /// /// When is set to true, the Neo4j Enterprise Edition license is accepted. - /// If the Community Edition is explicitly used, we do not update the image tag. + /// If the Community Edition is explicitly used, we do not update the image. /// /// A boolean value indicating whether the Neo4j Enterprise Edition license agreement is accepted. /// A configured instance of . diff --git a/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs b/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs index cb9612ee7..fc8b15b1e 100644 --- a/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs +++ b/tests/Testcontainers.Neo4j.Tests/Neo4jBuilderTest.cs @@ -25,7 +25,7 @@ public sealed class Neo4jBuilderTest [InlineData("neo4j:enterprise", "enterprise")] [InlineData("neo4j", "enterprise")] [InlineData("neo4j@sha256:20eb19e3d60f9f07c12c89eac8d8722e393be7e45c6d7e56004a2c493b8e2032", null)] - public void AppendsEnterpriseTagWhenEnterpriseEditionLicenseAgreementIsAccepted(string image, string expected) + public void AppendsEnterpriseSuffixWhenEnterpriseEditionLicenseAgreementIsAccepted(string image, string expected) { var neo4jContainer = new Neo4jBuilder().WithImage(image).WithEnterpriseEdition(true).Build(); Assert.Equal(expected, neo4jContainer.Image.Tag); @@ -43,7 +43,7 @@ private sealed class Neo4jBuilderConfigurations : TheoryData { public Neo4jBuilderConfigurations() { - Add(new Neo4jBuilder().WithImage(Neo4jBuilder.Neo4jEnterpriseImage)); + Add(new Neo4jBuilder().WithImage(Neo4jBuilder.Neo4jImage + "-enterprise")); Add(new Neo4jBuilder().WithEnterpriseEdition(false)); } }