Skip to content

Commit 9de8428

Browse files
authored
fead: Add default container connection string provider (#1630)
1 parent 568b975 commit 9de8428

10 files changed

Lines changed: 279 additions & 5 deletions

Directory.Packages.props

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,22 @@
1414
<PackageVersion Include="SSH.NET" Version="2025.1.0"/>
1515
<!-- Cake build: -->
1616
<PackageVersion Include="Cake.Frosting.Git" Version="5.0.1"/>
17-
<PackageVersion Include="Cake.Frosting" Version="5.0.0"/>
17+
<PackageVersion Include="Cake.Frosting" Version="5.1.0"/>
1818
<PackageVersion Include="Cake.Sonar" Version="5.0.0"/>
1919
<!-- Unit and integration test dependencies: -->
2020
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1"/>
2121
<PackageVersion Include="Microsoft.Extensions.Diagnostics.Testing" Version="8.10.0"/>
2222
<PackageVersion Include="coverlet.collector" Version="6.0.4"/>
2323
<PackageVersion Include="Dapper" Version="2.1.66"/>
24+
<PackageVersion Include="Moq" Version="4.20.72"/>
2425
<PackageVersion Include="ReflectionMagic" Version="5.0.1"/>
25-
<PackageVersion Include="xunit.analyzers" Version="1.25.0"/>
26+
<PackageVersion Include="xunit.analyzers" Version="1.27.0"/>
2627
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5"/>
2728
<PackageVersion Include="xunit" Version="2.9.3"/>
28-
<PackageVersion Include="xunit.v3" Version="3.1.0"/>
29+
<PackageVersion Include="xunit.v3" Version="3.2.2"/>
2930
<!-- xUnit.net extensibility for Testcontainers.Xunit and Testcontainers.XunitV3 packages: -->
3031
<PackageVersion Include="xunit.extensibility.execution" Version="2.9.3"/>
31-
<PackageVersion Include="xunit.v3.extensibility.core" Version="3.1.0"/>
32+
<PackageVersion Include="xunit.v3.extensibility.core" Version="3.2.2"/>
3233
<!-- Third-party client dependencies to connect and interact with the containers: -->
3334
<PackageVersion Include="Apache.NMS.ActiveMQ" Version="2.1.1"/>
3435
<PackageVersion Include="ArangoDBNetStandard" Version="2.0.1"/>

src/Testcontainers/Configurations/Containers/ConnectionStringProvider.cs renamed to src/Testcontainers/Configurations/Containers/ConnectionStringProvider`2.cs

File renamed without changes.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
namespace DotNet.Testcontainers.Configurations
2+
{
3+
using DotNet.Testcontainers.Containers;
4+
using JetBrains.Annotations;
5+
6+
/// <summary>
7+
/// Provides a base implementation for container connection string providers.
8+
/// </summary>
9+
[PublicAPI]
10+
public abstract class ContainerConnectionStringProvider<TContainerEntity, TConfigurationEntity> : IConnectionStringProvider<TContainerEntity, TConfigurationEntity>
11+
where TContainerEntity : IContainer
12+
where TConfigurationEntity : IContainerConfiguration
13+
{
14+
/// <summary>
15+
/// Gets the configured container instance.
16+
/// </summary>
17+
protected TContainerEntity Container { get; private set; }
18+
19+
/// <summary>
20+
/// Gets the configured container configuration.
21+
/// </summary>
22+
protected TConfigurationEntity Configuration { get; private set; }
23+
24+
/// <inheritdoc />
25+
public virtual void Configure(TContainerEntity container, TConfigurationEntity configuration)
26+
{
27+
Container = container;
28+
Configuration = configuration;
29+
}
30+
31+
/// <inheritdoc />
32+
/// <exception cref="ConnectionStringNotAvailableException">Thrown when the requested connection string is not available.</exception>
33+
/// <exception cref="ConnectionStringModeNotSupportedException">Thrown when the requested connection mode is not supported.</exception>
34+
public virtual string GetConnectionString(ConnectionMode connectionMode = ConnectionMode.Host)
35+
{
36+
switch (connectionMode)
37+
{
38+
case ConnectionMode.Host:
39+
return GetRequiredConnectionString(GetHostConnectionString(), ConnectionMode.Host);
40+
case ConnectionMode.Container:
41+
return GetRequiredConnectionString(GetContainerConnectionString(), ConnectionMode.Container);
42+
default:
43+
throw new ConnectionStringModeNotSupportedException(connectionMode, GetType());
44+
}
45+
}
46+
47+
/// <inheritdoc />
48+
/// <exception cref="ConnectionStringNotAvailableException">Thrown when the requested connection string is not available.</exception>
49+
/// <exception cref="ConnectionStringModeNotSupportedException">Thrown when the requested connection mode is not supported.</exception>
50+
/// <exception cref="ConnectionStringNameNotSupportedException">Thrown when the requested connection string name is not supported.</exception>
51+
public virtual string GetConnectionString(string name, ConnectionMode connectionMode = ConnectionMode.Host)
52+
{
53+
return string.IsNullOrEmpty(name) ? GetConnectionString(connectionMode) : throw new ConnectionStringNameNotSupportedException(name, GetType());
54+
}
55+
56+
/// <summary>
57+
/// Gets the host connection string.
58+
/// </summary>
59+
/// <returns>The host connection string.</returns>
60+
protected abstract string GetHostConnectionString();
61+
62+
/// <summary>
63+
/// Gets the container connection string.
64+
/// </summary>
65+
/// <returns>The container connection string.</returns>
66+
protected virtual string GetContainerConnectionString()
67+
{
68+
throw new ConnectionStringModeNotSupportedException(ConnectionMode.Container, GetType());
69+
}
70+
71+
/// <summary>
72+
/// Ensures the connection string is present.
73+
/// </summary>
74+
/// <param name="connectionString">The connection string.</param>
75+
/// <param name="connectionMode">The connection mode.</param>
76+
/// <returns>The connection string.</returns>
77+
protected string GetRequiredConnectionString(string connectionString, ConnectionMode connectionMode)
78+
{
79+
return string.IsNullOrEmpty(connectionString) ? throw new ConnectionStringNotAvailableException(connectionMode, GetType()) : connectionString;
80+
}
81+
}
82+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace DotNet.Testcontainers.Containers
2+
{
3+
using System;
4+
using DotNet.Testcontainers.Configurations;
5+
using JetBrains.Annotations;
6+
7+
/// <summary>
8+
/// Represents an exception that is thrown when a connection mode is not supported.
9+
/// </summary>
10+
[PublicAPI]
11+
public sealed class ConnectionStringModeNotSupportedException : NotSupportedException
12+
{
13+
/// <summary>
14+
/// Initializes a new instance of the <see cref="ConnectionStringModeNotSupportedException" /> class.
15+
/// </summary>
16+
/// <param name="connectionMode">The connection mode.</param>
17+
/// <param name="providerType">The provider type.</param>
18+
public ConnectionStringModeNotSupportedException(ConnectionMode connectionMode, Type providerType)
19+
: base($"The connection mode '{connectionMode}' is not supported by connection string provider '{providerType.FullName}'.")
20+
{
21+
}
22+
}
23+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace DotNet.Testcontainers.Containers
2+
{
3+
using System;
4+
using JetBrains.Annotations;
5+
6+
/// <summary>
7+
/// Represents an exception that is thrown when a named connection string is not supported.
8+
/// </summary>
9+
[PublicAPI]
10+
public sealed class ConnectionStringNameNotSupportedException : NotSupportedException
11+
{
12+
/// <summary>
13+
/// Initializes a new instance of the <see cref="ConnectionStringNameNotSupportedException" /> class.
14+
/// </summary>
15+
/// <param name="name">The connection string name.</param>
16+
/// <param name="providerType">The provider type.</param>
17+
public ConnectionStringNameNotSupportedException(string name, Type providerType)
18+
: base($"The connection string name '{name}' is not supported by connection string provider '{providerType.FullName}'.")
19+
{
20+
}
21+
}
22+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace DotNet.Testcontainers.Containers
2+
{
3+
using System;
4+
using DotNet.Testcontainers.Configurations;
5+
using JetBrains.Annotations;
6+
7+
/// <summary>
8+
/// Represents an exception that is thrown when a connection string cannot be resolved.
9+
/// </summary>
10+
[PublicAPI]
11+
public sealed class ConnectionStringNotAvailableException : InvalidOperationException
12+
{
13+
/// <summary>
14+
/// Initializes a new instance of the <see cref="ConnectionStringNotAvailableException" /> class.
15+
/// </summary>
16+
/// <param name="connectionMode">The connection mode.</param>
17+
/// <param name="providerType">The provider type.</param>
18+
public ConnectionStringNotAvailableException(ConnectionMode connectionMode, Type providerType)
19+
: base($"The connection string provider '{providerType.FullName}' did not return a connection string for connection mode '{connectionMode}'.")
20+
{
21+
}
22+
}
23+
}

src/Testcontainers/Images/DockerfileArchive.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ private DockerfileArchive(
116116
/// excludes stages that do not correspond to base images. For example, it will not include
117117
/// the second line from the following Dockerfile configuration:
118118
/// <code>
119-
/// FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
119+
/// FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
120120
/// FROM build
121121
/// </code>
122122
/// </remarks>
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
namespace Testcontainers.Tests;
2+
3+
public sealed class ContainerConnectionStringProviderTests
4+
{
5+
[Fact]
6+
public void ConfigureStoresContainerAndConfiguration()
7+
{
8+
var container = new Mock<IContainer>();
9+
var configuration = new Mock<IContainerConfiguration>();
10+
var provider = new InspectableProvider();
11+
12+
provider.Configure(container.Object, configuration.Object);
13+
14+
Assert.Same(container.Object, provider.ConfiguredContainer);
15+
Assert.Same(configuration.Object, provider.ConfiguredConfiguration);
16+
}
17+
18+
[Fact]
19+
public void GetConnectionStringReturnsHostConnectionString()
20+
{
21+
var provider = new HostOnlyProvider("host-connection");
22+
23+
var connectionString = provider.GetConnectionString();
24+
25+
Assert.Equal("host-connection", connectionString);
26+
}
27+
28+
[Fact]
29+
public void GetConnectionStringWithEmptyNameReturnsHostConnectionString()
30+
{
31+
var provider = new HostOnlyProvider("host-connection");
32+
33+
var connectionString = provider.GetConnectionString(string.Empty);
34+
35+
Assert.Equal("host-connection", connectionString);
36+
}
37+
38+
[Fact]
39+
public void GetConnectionStringThrowsWhenHostConnectionStringIsMissing()
40+
{
41+
var provider = new HostOnlyProvider(string.Empty);
42+
43+
Assert.Throws<ConnectionStringNotAvailableException>(() => provider.GetConnectionString());
44+
}
45+
46+
[Fact]
47+
public void GetConnectionStringThrowsWhenContainerModeIsNotSupported()
48+
{
49+
var provider = new HostOnlyProvider("host-connection");
50+
51+
Assert.Throws<ConnectionStringModeNotSupportedException>(() => provider.GetConnectionString(ConnectionMode.Container));
52+
}
53+
54+
[Fact]
55+
public void GetConnectionStringReturnsContainerConnectionStringWhenSupported()
56+
{
57+
var provider = new HostAndContainerProvider("host-connection", "container-connection");
58+
59+
var connectionString = provider.GetConnectionString(ConnectionMode.Container);
60+
61+
Assert.Equal("container-connection", connectionString);
62+
}
63+
64+
[Fact]
65+
public void GetConnectionStringThrowsWhenNamedConnectionStringIsRequested()
66+
{
67+
var provider = new HostOnlyProvider("host-connection");
68+
69+
Assert.Throws<ConnectionStringNameNotSupportedException>(() => provider.GetConnectionString("primary"));
70+
}
71+
72+
private sealed class InspectableProvider : ContainerConnectionStringProvider<IContainer, IContainerConfiguration>
73+
{
74+
public IContainer ConfiguredContainer => Container;
75+
76+
public IContainerConfiguration ConfiguredConfiguration => Configuration;
77+
78+
protected override string GetHostConnectionString()
79+
{
80+
return "host";
81+
}
82+
}
83+
84+
private sealed class HostOnlyProvider : ContainerConnectionStringProvider<IContainer, IContainerConfiguration>
85+
{
86+
private readonly string _hostConnectionString;
87+
88+
public HostOnlyProvider(string hostConnectionString)
89+
{
90+
_hostConnectionString = hostConnectionString;
91+
}
92+
93+
protected override string GetHostConnectionString()
94+
{
95+
return _hostConnectionString;
96+
}
97+
}
98+
99+
private sealed class HostAndContainerProvider : ContainerConnectionStringProvider<IContainer, IContainerConfiguration>
100+
{
101+
private readonly string _hostConnectionString;
102+
103+
private readonly string _containerConnectionString;
104+
105+
public HostAndContainerProvider(string hostConnectionString, string containerConnectionString)
106+
{
107+
_hostConnectionString = hostConnectionString;
108+
_containerConnectionString = containerConnectionString;
109+
}
110+
111+
protected override string GetHostConnectionString()
112+
{
113+
return _hostConnectionString;
114+
}
115+
116+
protected override string GetContainerConnectionString()
117+
{
118+
return _containerConnectionString;
119+
}
120+
}
121+
}

tests/Testcontainers.Platform.Linux.Tests/Testcontainers.Platform.Linux.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
1010
<PackageReference Include="Microsoft.Extensions.Diagnostics.Testing"/>
1111
<PackageReference Include="coverlet.collector"/>
12+
<PackageReference Include="Moq"/>
1213
<PackageReference Include="ReflectionMagic"/>
1314
<PackageReference Include="xunit.runner.visualstudio"/>
1415
<PackageReference Include="xunit.v3"/>

tests/Testcontainers.Platform.Linux.Tests/Usings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
global using JetBrains.Annotations;
2323
global using Microsoft.Extensions.Logging.Abstractions;
2424
global using Microsoft.Extensions.Logging.Testing;
25+
global using Moq;
2526
global using ReflectionMagic;
2627
global using Xunit;
2728
global using Xunit.Sdk;

0 commit comments

Comments
 (0)