diff --git a/src/Testcontainers.Db2/Db2Builder.cs b/src/Testcontainers.Db2/Db2Builder.cs index 3f1b9ccf1..4731c9005 100644 --- a/src/Testcontainers.Db2/Db2Builder.cs +++ b/src/Testcontainers.Db2/Db2Builder.cs @@ -14,12 +14,6 @@ public sealed class Db2Builder : ContainerBuilder<Db2Builder, Db2Container, Db2C public const string DefaultPassword = "db2inst1"; - private const string AcceptLicenseAgreementEnvVar = "LICENSE"; - - private const string AcceptLicenseAgreement = "accept"; - - private const string DeclineLicenseAgreement = "decline"; - /// <summary> /// Initializes a new instance of the <see cref="Db2Builder" /> class. /// </summary> @@ -42,6 +36,15 @@ private Db2Builder(Db2Configuration resourceConfiguration) /// <inheritdoc /> protected override Db2Configuration DockerResourceConfiguration { get; } + /// <inheritdoc /> + protected override string AcceptLicenseAgreementEnvVar { get; } = "LICENSE"; + + /// <inheritdoc /> + protected override string AcceptLicenseAgreement { get; } = "accept"; + + /// <inheritdoc /> + protected override string DeclineLicenseAgreement { get; } = "decline"; + /// <summary> /// Accepts the license agreement. /// </summary> @@ -50,7 +53,7 @@ private Db2Builder(Db2Configuration resourceConfiguration) /// </remarks> /// <param name="acceptLicenseAgreement">A boolean value indicating whether the Db2 license agreement is accepted.</param> /// <returns>A configured instance of <see cref="Db2Builder" />.</returns> - public Db2Builder WithAcceptLicenseAgreement(bool acceptLicenseAgreement) + public override Db2Builder WithAcceptLicenseAgreement(bool acceptLicenseAgreement) { var licenseAgreement = acceptLicenseAgreement ? AcceptLicenseAgreement : DeclineLicenseAgreement; return WithEnvironment(AcceptLicenseAgreementEnvVar, licenseAgreement); @@ -94,6 +97,7 @@ public Db2Builder WithPassword(string password) public override Db2Container Build() { Validate(); + ValidateLicenseAgreement(); return new Db2Container(DockerResourceConfiguration); } @@ -110,16 +114,8 @@ protected override Db2Builder Init() => base.Init() /// <inheritdoc /> protected override void Validate() { - const string message = "The image '{0}' requires you to accept a license agreement."; - base.Validate(); - Predicate<Db2Configuration> licenseAgreementNotAccepted = value => - !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(string.Format(message, DockerResourceConfiguration.Image.FullName), argument.Name)); - _ = Guard.Argument(DockerResourceConfiguration.Username, nameof(DockerResourceConfiguration.Username)) .NotNull() .NotEmpty(); diff --git a/src/Testcontainers.Neo4j/Neo4jBuilder.cs b/src/Testcontainers.Neo4j/Neo4jBuilder.cs index 0dc610619..399076936 100644 --- a/src/Testcontainers.Neo4j/Neo4jBuilder.cs +++ b/src/Testcontainers.Neo4j/Neo4jBuilder.cs @@ -10,12 +10,6 @@ public sealed class Neo4jBuilder : ContainerBuilder<Neo4jBuilder, Neo4jContainer public const ushort Neo4jBoltPort = 7687; - private const string AcceptLicenseAgreementEnvVar = "NEO4J_ACCEPT_LICENSE_AGREEMENT"; - - private const string AcceptLicenseAgreement = "yes"; - - private const string DeclineLicenseAgreement = "no"; - /// <summary> /// Initializes a new instance of the <see cref="Neo4jBuilder" /> class. /// </summary> @@ -38,6 +32,15 @@ private Neo4jBuilder(Neo4jConfiguration resourceConfiguration) /// <inheritdoc /> protected override Neo4jConfiguration DockerResourceConfiguration { get; } + /// <inheritdoc /> + protected override string AcceptLicenseAgreementEnvVar { get; } = "NEO4J_ACCEPT_LICENSE_AGREEMENT"; + + /// <inheritdoc /> + protected override string AcceptLicenseAgreement { get; } = "yes"; + + /// <inheritdoc /> + protected override string DeclineLicenseAgreement { get; } = "no"; + /// <summary> /// Sets the image to the Neo4j Enterprise Edition. /// </summary> diff --git a/src/Testcontainers.Pulsar/Usings.cs b/src/Testcontainers.Pulsar/Usings.cs index 59c490a37..cbed90f27 100644 --- a/src/Testcontainers.Pulsar/Usings.cs +++ b/src/Testcontainers.Pulsar/Usings.cs @@ -9,7 +9,6 @@ global using System.Threading; global using System.Threading.Tasks; 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/src/Testcontainers.ServiceBus/ServiceBusBuilder.cs b/src/Testcontainers.ServiceBus/ServiceBusBuilder.cs index 278a85cf7..7a15b11e2 100644 --- a/src/Testcontainers.ServiceBus/ServiceBusBuilder.cs +++ b/src/Testcontainers.ServiceBus/ServiceBusBuilder.cs @@ -12,12 +12,6 @@ public sealed class ServiceBusBuilder : ContainerBuilder<ServiceBusBuilder, Serv public const ushort ServiceBusPort = 5672; - private const string AcceptLicenseAgreementEnvVar = "ACCEPT_EULA"; - - private const string AcceptLicenseAgreement = "Y"; - - private const string DeclineLicenseAgreement = "N"; - /// <summary> /// Initializes a new instance of the <see cref="ServiceBusBuilder" /> class. /// </summary> @@ -40,6 +34,15 @@ private ServiceBusBuilder(ServiceBusConfiguration resourceConfiguration) /// <inheritdoc /> protected override ServiceBusConfiguration DockerResourceConfiguration { get; } + /// <inheritdoc /> + protected override string AcceptLicenseAgreementEnvVar { get; } = "ACCEPT_EULA"; + + /// <inheritdoc /> + protected override string AcceptLicenseAgreement { get; } = "Y"; + + /// <inheritdoc /> + protected override string DeclineLicenseAgreement { get; } = "N"; + /// <summary> /// Accepts the license agreement. /// </summary> @@ -48,7 +51,7 @@ private ServiceBusBuilder(ServiceBusConfiguration resourceConfiguration) /// </remarks> /// <param name="acceptLicenseAgreement">A boolean value indicating whether the Azure Service Bus Emulator license agreement is accepted.</param> /// <returns>A configured instance of <see cref="ServiceBusBuilder" />.</returns> - public ServiceBusBuilder WithAcceptLicenseAgreement(bool acceptLicenseAgreement) + public override ServiceBusBuilder WithAcceptLicenseAgreement(bool acceptLicenseAgreement) { var licenseAgreement = acceptLicenseAgreement ? AcceptLicenseAgreement : DeclineLicenseAgreement; return WithEnvironment(AcceptLicenseAgreementEnvVar, licenseAgreement); @@ -85,6 +88,7 @@ public ServiceBusBuilder WithMsSqlContainer( public override ServiceBusContainer Build() { Validate(); + ValidateLicenseAgreement(); if (DockerResourceConfiguration.DatabaseContainer != null) { @@ -105,20 +109,6 @@ public override ServiceBusContainer Build() return new ServiceBusContainer(serviceBusContainer.DockerResourceConfiguration); } - /// <inheritdoc /> - protected override void Validate() - { - const string message = "The image '{0}' requires you to accept a license agreement."; - - base.Validate(); - - Predicate<ServiceBusConfiguration> licenseAgreementNotAccepted = value => - !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(string.Format(message, DockerResourceConfiguration.Image.FullName), argument.Name)); - } - /// <inheritdoc /> protected override ServiceBusBuilder Init() { diff --git a/src/Testcontainers.ServiceBus/Usings.cs b/src/Testcontainers.ServiceBus/Usings.cs index ea7628ae0..15c8b2eda 100644 --- a/src/Testcontainers.ServiceBus/Usings.cs +++ b/src/Testcontainers.ServiceBus/Usings.cs @@ -4,7 +4,6 @@ global using System.Threading; global using System.Threading.Tasks; 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/src/Testcontainers/Builders/Base64Provider.cs b/src/Testcontainers/Builders/Base64Provider.cs index 29ed7b142..de0671e57 100644 --- a/src/Testcontainers/Builders/Base64Provider.cs +++ b/src/Testcontainers/Builders/Base64Provider.cs @@ -39,7 +39,7 @@ public Base64Provider(JsonElement jsonElement, ILogger logger) } /// <summary> - /// Gets a predicate that determines whether or not a <see cref="JsonProperty" /> contains a Docker registry key. + /// Gets a predicate that determines whether a <see cref="JsonProperty" /> contains a Docker registry key. /// </summary> public static Func<JsonProperty, string, bool> HasDockerRegistryKey { get; } = (property, hostname) => property.Name.Equals(hostname, StringComparison.OrdinalIgnoreCase) || property.Name.EndsWith("://" + hostname, StringComparison.OrdinalIgnoreCase); @@ -47,7 +47,7 @@ public Base64Provider(JsonElement jsonElement, ILogger logger) /// <inheritdoc /> public bool IsApplicable(string hostname) { - return !default(JsonElement).Equals(_rootElement) && !JsonValueKind.Null.Equals(_rootElement.ValueKind) && _rootElement.EnumerateObject().Any(property => HasDockerRegistryKey(property, hostname)); + return !JsonValueKind.Undefined.Equals(_rootElement.ValueKind) && !JsonValueKind.Null.Equals(_rootElement.ValueKind) && _rootElement.EnumerateObject().Any(property => HasDockerRegistryKey(property, hostname)); } /// <inheritdoc /> diff --git a/src/Testcontainers/Builders/ContainerBuilder`3.cs b/src/Testcontainers/Builders/ContainerBuilder`3.cs index 5bc8509eb..8bf63d77a 100644 --- a/src/Testcontainers/Builders/ContainerBuilder`3.cs +++ b/src/Testcontainers/Builders/ContainerBuilder`3.cs @@ -36,6 +36,28 @@ protected ContainerBuilder(TConfigurationEntity dockerResourceConfiguration) { } + /// <summary> + /// Gets the name of the environment variable that must be set to accept the image license agreement. + /// </summary> + protected virtual string AcceptLicenseAgreementEnvVar { get; } + + /// <summary> + /// Gets the expected value of <see cref="AcceptLicenseAgreementEnvVar" /> that indicates acceptance of the license agreement. + /// </summary> + protected virtual string AcceptLicenseAgreement { get; } + + /// <summary> + /// Gets the expected value of <see cref="AcceptLicenseAgreementEnvVar" /> that indicates rejection of the license agreement. + /// </summary> + protected virtual string DeclineLicenseAgreement { get; } + + /// <inheritdoc /> + public virtual TBuilderEntity WithAcceptLicenseAgreement(bool acceptLicenseAgreement) + { + const string licenseAgreementNotRequired = "The module does not require you to accept a license agreement."; + throw new InvalidOperationException(licenseAgreementNotRequired); + } + /// <inheritdoc /> public TBuilderEntity DependsOn(IContainer container) { @@ -374,7 +396,7 @@ public TBuilderEntity WithStartupCallback(Func<TContainerEntity, CancellationTok /// <inheritdoc /> protected override TBuilderEntity Init() { - return base.Init().WithImagePullPolicy(PullPolicy.Missing).WithPortForwarding().WithOutputConsumer(Consume.DoNotConsumeStdoutAndStderr()).WithWaitStrategy(Wait.ForUnixContainer()).WithStartupCallback((_, ct) => Task.CompletedTask); + return base.Init().WithImagePullPolicy(PullPolicy.Missing).WithPortForwarding().WithOutputConsumer(Consume.DoNotConsumeStdoutAndStderr()).WithWaitStrategy(Wait.ForUnixContainer()).WithStartupCallback((_, _) => Task.CompletedTask); } /// <inheritdoc /> @@ -390,6 +412,21 @@ protected override void Validate() .NotNull(); } + /// <summary> + /// Validates the license agreement. + /// </summary> + /// <exception cref="ArgumentException">Thrown when the license agreement is not accepted.</exception> + protected virtual void ValidateLicenseAgreement() + { + const string message = "The image '{0}' requires you to accept a license agreement."; + + Predicate<TConfigurationEntity> licenseAgreementNotAccepted = value => + !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(string.Format(message, DockerResourceConfiguration.Image.FullName), argument.Name)); + } + /// <summary> /// Clones the Docker resource builder configuration. /// </summary> diff --git a/src/Testcontainers/Builders/CredsHelperProvider.cs b/src/Testcontainers/Builders/CredsHelperProvider.cs index 36eb10f14..4e2ab9387 100644 --- a/src/Testcontainers/Builders/CredsHelperProvider.cs +++ b/src/Testcontainers/Builders/CredsHelperProvider.cs @@ -39,7 +39,7 @@ public CredsHelperProvider(JsonElement jsonElement, ILogger logger) /// <inheritdoc /> public bool IsApplicable(string hostname) { - return !default(JsonElement).Equals(_rootElement) && !JsonValueKind.Null.Equals(_rootElement.ValueKind) && _rootElement.EnumerateObject().Any(property => Base64Provider.HasDockerRegistryKey(property, hostname)); + return !JsonValueKind.Undefined.Equals(_rootElement.ValueKind) && !JsonValueKind.Null.Equals(_rootElement.ValueKind) && _rootElement.EnumerateObject().Any(property => Base64Provider.HasDockerRegistryKey(property, hostname)); } /// <inheritdoc /> diff --git a/src/Testcontainers/Builders/CredsStoreProvider.cs b/src/Testcontainers/Builders/CredsStoreProvider.cs index 75617d896..22fac5595 100644 --- a/src/Testcontainers/Builders/CredsStoreProvider.cs +++ b/src/Testcontainers/Builders/CredsStoreProvider.cs @@ -38,7 +38,7 @@ public CredsStoreProvider(JsonElement jsonElement, ILogger logger) /// <inheritdoc /> public bool IsApplicable(string hostname) { - return !default(JsonElement).Equals(_rootElement) && !string.IsNullOrEmpty(_rootElement.GetString()); + return !JsonValueKind.Undefined.Equals(_rootElement.ValueKind) && !string.IsNullOrEmpty(_rootElement.GetString()); } /// <inheritdoc /> diff --git a/src/Testcontainers/Builders/IContainerBuilder`2.cs b/src/Testcontainers/Builders/IContainerBuilder`2.cs index 26477db49..0493fcbe5 100644 --- a/src/Testcontainers/Builders/IContainerBuilder`2.cs +++ b/src/Testcontainers/Builders/IContainerBuilder`2.cs @@ -21,6 +21,18 @@ namespace DotNet.Testcontainers.Builders [PublicAPI] public interface IContainerBuilder<out TBuilderEntity, out TContainerEntity> : IAbstractBuilder<TBuilderEntity, TContainerEntity, CreateContainerParameters> { + /// <summary> + /// Accepts the license agreement. + /// </summary> + /// <remarks> + /// Modules that require a license agreement must override and implement this + /// method to enforce proper license acceptance behavior. + /// </remarks> + /// <param name="acceptLicenseAgreement">A boolean value indicating whether the license agreement is accepted.</param> + /// <returns>A configured instance of <typeparamref name="TBuilderEntity" />.</returns> + /// <exception cref="InvalidOperationException">Thrown when the module does not require a license agreement.</exception> + TBuilderEntity WithAcceptLicenseAgreement(bool acceptLicenseAgreement); + /// <summary> /// Sets the dependent container to resolve and start before starting this container configuration. /// </summary> diff --git a/tests/Testcontainers.Db2.Tests/DeclineLicenseAgreementTest.cs b/tests/Testcontainers.Db2.Tests/DeclineLicenseAgreementTest.cs new file mode 100644 index 000000000..faffd313a --- /dev/null +++ b/tests/Testcontainers.Db2.Tests/DeclineLicenseAgreementTest.cs @@ -0,0 +1,21 @@ +namespace Testcontainers.Db2; + +public sealed partial class DeclineLicenseAgreementTest +{ + [GeneratedRegex("The image '.+' requires you to accept a license agreement\\.")] + private static partial Regex LicenseAgreementNotAccepted(); + + [Fact] + public void WithoutAcceptingLicenseAgreementThrowsArgumentException() + { + var exception = Assert.Throws<ArgumentException>(() => new Db2Builder().Build()); + Assert.Matches(LicenseAgreementNotAccepted(), exception.Message); + } + + [Fact] + public void WithLicenseAgreementDeclinedThrowsArgumentException() + { + var exception = Assert.Throws<ArgumentException>(() => new Db2Builder().WithAcceptLicenseAgreement(false).Build()); + Assert.Matches(LicenseAgreementNotAccepted(), exception.Message); + } +} \ No newline at end of file diff --git a/tests/Testcontainers.Db2.Tests/Usings.cs b/tests/Testcontainers.Db2.Tests/Usings.cs index dbf73bb31..2a9a1fa23 100644 --- a/tests/Testcontainers.Db2.Tests/Usings.cs +++ b/tests/Testcontainers.Db2.Tests/Usings.cs @@ -1,4 +1,6 @@ +global using System; global using System.Data; +global using System.Text.RegularExpressions; global using System.Data.Common; global using System.Threading.Tasks; global using DotNet.Testcontainers.Commons; diff --git a/tests/Testcontainers.Kafka.Tests/Usings.cs b/tests/Testcontainers.Kafka.Tests/Usings.cs index 3e69bfb4f..6a6c42891 100644 --- a/tests/Testcontainers.Kafka.Tests/Usings.cs +++ b/tests/Testcontainers.Kafka.Tests/Usings.cs @@ -1,8 +1,5 @@ global using System; -global using System.Collections.Generic; -global using System.Diagnostics; global using System.Text; -global using System.Threading; global using System.Threading.Tasks; global using Confluent.Kafka; global using Confluent.Kafka.SyncOverAsync; diff --git a/tests/Testcontainers.Platform.Linux.Tests/AcceptLicenseAgreementTest.cs b/tests/Testcontainers.Platform.Linux.Tests/AcceptLicenseAgreementTest.cs new file mode 100644 index 000000000..d04bde64c --- /dev/null +++ b/tests/Testcontainers.Platform.Linux.Tests/AcceptLicenseAgreementTest.cs @@ -0,0 +1,11 @@ +namespace Testcontainers.Tests; + +public sealed class AcceptLicenseAgreementTest +{ + [Fact] + public void WithLicenseAgreementAcceptedThrowsArgumentException() + { + var exception = Assert.Throws<InvalidOperationException>(() => new ContainerBuilder().WithAcceptLicenseAgreement(true).Build()); + Assert.Equal("The module does not require you to accept a license agreement.", exception.Message); + } +} \ No newline at end of file diff --git a/tests/Testcontainers.ServiceBus.Tests/DeclineLicenseAgreementTest.cs b/tests/Testcontainers.ServiceBus.Tests/DeclineLicenseAgreementTest.cs new file mode 100644 index 000000000..f3f693d2f --- /dev/null +++ b/tests/Testcontainers.ServiceBus.Tests/DeclineLicenseAgreementTest.cs @@ -0,0 +1,21 @@ +namespace Testcontainers.ServiceBus; + +public sealed partial class DeclineLicenseAgreementTest +{ + [GeneratedRegex("The image '.+' requires you to accept a license agreement\\.")] + private static partial Regex LicenseAgreementNotAccepted(); + + [Fact] + public void WithoutAcceptingLicenseAgreementThrowsArgumentException() + { + var exception = Assert.Throws<ArgumentException>(() => new ServiceBusBuilder().Build()); + Assert.Matches(LicenseAgreementNotAccepted(), exception.Message); + } + + [Fact] + public void WithLicenseAgreementDeclinedThrowsArgumentException() + { + var exception = Assert.Throws<ArgumentException>(() => new ServiceBusBuilder().WithAcceptLicenseAgreement(false).Build()); + Assert.Matches(LicenseAgreementNotAccepted(), exception.Message); + } +} \ No newline at end of file diff --git a/tests/Testcontainers.ServiceBus.Tests/Usings.cs b/tests/Testcontainers.ServiceBus.Tests/Usings.cs index 1e0024b2c..8fb60a0cc 100644 --- a/tests/Testcontainers.ServiceBus.Tests/Usings.cs +++ b/tests/Testcontainers.ServiceBus.Tests/Usings.cs @@ -1,3 +1,5 @@ +global using System; +global using System.Text.RegularExpressions; global using System.Threading.Tasks; global using Azure.Messaging.ServiceBus; global using DotNet.Testcontainers.Builders;