diff --git a/Directory.Packages.props b/Directory.Packages.props
index 9fb067dcf..2e5189342 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -71,6 +71,7 @@
+
diff --git a/Testcontainers.dic b/Testcontainers.dic
index df1f16915..a8eab215e 100644
--- a/Testcontainers.dic
+++ b/Testcontainers.dic
@@ -17,6 +17,7 @@ lipsum
ltsc
memopt
mongosh
+mosquitto
mycounter
mydatabase
myregistry
diff --git a/Testcontainers.sln b/Testcontainers.sln
index 6a10627b8..98dabc193 100644
--- a/Testcontainers.sln
+++ b/Testcontainers.sln
@@ -88,6 +88,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Minio", "src
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.MongoDb", "src\Testcontainers.MongoDb\Testcontainers.MongoDb.csproj", "{2613F146-6C66-4059-9D37-D48BA6B61515}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Mosquitto", "src\Testcontainers.Mosquitto\Testcontainers.Mosquitto.csproj", "{3A64B210-645C-4229-B089-5BB2AAFCF535}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.MsSql", "src\Testcontainers.MsSql\Testcontainers.MsSql.csproj", "{121FB123-40D9-44D4-9AB7-AD57ED34F466}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.MySql", "src\Testcontainers.MySql\Testcontainers.MySql.csproj", "{9FDCFAEA-AE42-4C69-89EF-F1FF75E88CCC}"
@@ -210,6 +212,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Minio.Tests"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.MongoDb.Tests", "tests\Testcontainers.MongoDb.Tests\Testcontainers.MongoDb.Tests.csproj", "{82A7E7B8-3187-4CAE-845B-0BF43409B38A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Mosquitto.Tests", "tests\Testcontainers.Mosquitto.Tests\Testcontainers.Mosquitto.Tests.csproj", "{6314B57A-EE0C-4C3B-A9A9-64D68A47312A}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.MsSql.Tests", "tests\Testcontainers.MsSql.Tests\Testcontainers.MsSql.Tests.csproj", "{25DBED78-99F4-433F-BBF5-1B4E9DEAE437}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.MySql.Tests", "tests\Testcontainers.MySql.Tests\Testcontainers.MySql.Tests.csproj", "{E42DA1CE-698F-4E45-8D1F-5D5895893840}"
@@ -418,6 +422,10 @@ Global
{2613F146-6C66-4059-9D37-D48BA6B61515}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2613F146-6C66-4059-9D37-D48BA6B61515}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2613F146-6C66-4059-9D37-D48BA6B61515}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3A64B210-645C-4229-B089-5BB2AAFCF535}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3A64B210-645C-4229-B089-5BB2AAFCF535}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3A64B210-645C-4229-B089-5BB2AAFCF535}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3A64B210-645C-4229-B089-5BB2AAFCF535}.Release|Any CPU.Build.0 = Release|Any CPU
{121FB123-40D9-44D4-9AB7-AD57ED34F466}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{121FB123-40D9-44D4-9AB7-AD57ED34F466}.Debug|Any CPU.Build.0 = Debug|Any CPU
{121FB123-40D9-44D4-9AB7-AD57ED34F466}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -662,6 +670,10 @@ Global
{82A7E7B8-3187-4CAE-845B-0BF43409B38A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{82A7E7B8-3187-4CAE-845B-0BF43409B38A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{82A7E7B8-3187-4CAE-845B-0BF43409B38A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6314B57A-EE0C-4C3B-A9A9-64D68A47312A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6314B57A-EE0C-4C3B-A9A9-64D68A47312A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6314B57A-EE0C-4C3B-A9A9-64D68A47312A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6314B57A-EE0C-4C3B-A9A9-64D68A47312A}.Release|Any CPU.Build.0 = Release|Any CPU
{25DBED78-99F4-433F-BBF5-1B4E9DEAE437}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{25DBED78-99F4-433F-BBF5-1B4E9DEAE437}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25DBED78-99F4-433F-BBF5-1B4E9DEAE437}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -834,6 +846,7 @@ Global
{B024E315-831F-429D-92AA-44B839AC10F4} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{1266E1E6-5CEF-4161-8B45-83282455746E} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{2613F146-6C66-4059-9D37-D48BA6B61515} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
+ {3A64B210-645C-4229-B089-5BB2AAFCF535} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{121FB123-40D9-44D4-9AB7-AD57ED34F466} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{9FDCFAEA-AE42-4C69-89EF-F1FF75E88CCC} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{BF37BEA1-0816-4326-B1E0-E82290F8FCE0} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
@@ -895,6 +908,7 @@ Global
{5247DF94-32F3-4ED6-AE71-6AB4F4078E6D} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{5DB1F35F-B714-4B62-84BE-16A33084D3E1} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{82A7E7B8-3187-4CAE-845B-0BF43409B38A} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
+ {6314B57A-EE0C-4C3B-A9A9-64D68A47312A} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{25DBED78-99F4-433F-BBF5-1B4E9DEAE437} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{E42DA1CE-698F-4E45-8D1F-5D5895893840} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{87A3F137-6DC3-4CE5-91E6-01797D076086} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
diff --git a/Testcontainers.sln.DotSettings b/Testcontainers.sln.DotSettings
index ff93162f2..71a67e934 100644
--- a/Testcontainers.sln.DotSettings
+++ b/Testcontainers.sln.DotSettings
@@ -26,6 +26,7 @@
True
True
True
+ True
True
True
True
diff --git a/docs/modules/index.md b/docs/modules/index.md
index 95dac0163..d3e7dbffe 100644
--- a/docs/modules/index.md
+++ b/docs/modules/index.md
@@ -55,6 +55,7 @@ await moduleNameContainer.StartAsync();
| Milvus | `milvusdb/milvus:v2.3.10` | [NuGet](https://www.nuget.org/packages/Testcontainers.Milvus) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Milvus) |
| MinIO | `minio/minio:RELEASE.2023-01-31T02-24-19Z` | [NuGet](https://www.nuget.org/packages/Testcontainers.Minio) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Minio) |
| MongoDB | `mongo:6.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.MongoDb) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.MongoDb) |
+| Mosquitto | `eclipse-mosquitto:2.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.Mosquitto) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Mosquitto) |
| MySQL | `mysql:8.0` | [NuGet](https://www.nuget.org/packages/Testcontainers.MySql) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.MySql) |
| NATS | `nats:2.9` | [NuGet](https://www.nuget.org/packages/Testcontainers.Nats) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Nats) |
| Neo4j | `neo4j:5.4` | [NuGet](https://www.nuget.org/packages/Testcontainers.Neo4j) | [Source](https://github.com/testcontainers/testcontainers-dotnet/tree/develop/src/Testcontainers.Neo4j) |
diff --git a/src/Testcontainers.Mosquitto/.editorconfig b/src/Testcontainers.Mosquitto/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/src/Testcontainers.Mosquitto/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/src/Testcontainers.Mosquitto/MosquittoBuilder.cs b/src/Testcontainers.Mosquitto/MosquittoBuilder.cs
new file mode 100644
index 000000000..ff0bb3b38
--- /dev/null
+++ b/src/Testcontainers.Mosquitto/MosquittoBuilder.cs
@@ -0,0 +1,139 @@
+namespace Testcontainers.Mosquitto;
+
+///
+[PublicAPI]
+public sealed class MosquittoBuilder : ContainerBuilder
+{
+ public const string MosquittoImage = "eclipse-mosquitto:2.0";
+
+ public const ushort MqttPort = 1883;
+
+ public const ushort MqttTlsPort = 8883;
+
+ public const ushort MqttWsPort = 8080;
+
+ public const ushort MqttWssPort = 8081;
+
+ public const string CertificateFilePath = "/etc/mosquitto/certs/server.crt";
+
+ public const string CertificateKeyFilePath = "/etc/mosquitto/certs/server.key";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MosquittoBuilder()
+ : this(new MosquittoConfiguration())
+ {
+ DockerResourceConfiguration = Init().DockerResourceConfiguration;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public MosquittoBuilder(MosquittoConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ DockerResourceConfiguration = resourceConfiguration;
+ }
+
+ ///
+ protected override MosquittoConfiguration DockerResourceConfiguration { get; }
+
+ ///
+ /// Sets the public certificate and private key to enable TLS.
+ ///
+ /// The public certificate in PEM format.
+ /// The private key associated with the certificate in PEM format.
+ /// A configured instance of .
+ public MosquittoBuilder WithCertificate(string certificate, string certificateKey)
+ {
+ return Merge(DockerResourceConfiguration, new MosquittoConfiguration(certificate: certificate, certificateKey: certificateKey))
+ .WithPortBinding(MqttTlsPort, true)
+ .WithPortBinding(MqttWssPort, true)
+ .WithResourceMapping(Encoding.Default.GetBytes(certificate), CertificateFilePath)
+ .WithResourceMapping(Encoding.Default.GetBytes(certificateKey), CertificateKeyFilePath);
+ }
+
+ ///
+ public override MosquittoContainer Build()
+ {
+ Validate();
+
+ // Maybe we should move this into the startup callback.
+ var mosquittoConfig = new StringWriter();
+ mosquittoConfig.NewLine = "\n";
+
+ mosquittoConfig.WriteLine("per_listener_settings true");
+ mosquittoConfig.WriteLine("log_dest stdout");
+ mosquittoConfig.WriteLine("log_type information");
+
+ mosquittoConfig.WriteLine();
+ mosquittoConfig.WriteLine("persistence false");
+ mosquittoConfig.WriteLine("persistence_location /mosquitto/data/");
+
+ mosquittoConfig.WriteLine();
+ mosquittoConfig.WriteLine("# MQTT, unencrypted, unauthenticated");
+ mosquittoConfig.WriteLine($"listener {MqttPort} 0.0.0.0");
+ mosquittoConfig.WriteLine("protocol mqtt");
+ mosquittoConfig.WriteLine("allow_anonymous true");
+
+ mosquittoConfig.WriteLine();
+ mosquittoConfig.WriteLine("# MQTT over WebSockets, unencrypted, unauthenticated");
+ mosquittoConfig.WriteLine($"listener {MqttWsPort} 0.0.0.0");
+ mosquittoConfig.WriteLine("protocol websockets");
+ mosquittoConfig.WriteLine("allow_anonymous true");
+
+ if (DockerResourceConfiguration.TlsEnabled)
+ {
+ mosquittoConfig.WriteLine();
+ mosquittoConfig.WriteLine("# MQTT, encrypted, unauthenticated");
+ mosquittoConfig.WriteLine($"listener {MqttTlsPort} 0.0.0.0");
+ mosquittoConfig.WriteLine("protocol mqtt");
+ mosquittoConfig.WriteLine("allow_anonymous true");
+ mosquittoConfig.WriteLine("tls_version tlsv1.2");
+ mosquittoConfig.WriteLine($"certfile {CertificateFilePath}");
+ mosquittoConfig.WriteLine($"keyfile {CertificateKeyFilePath}");
+
+ mosquittoConfig.WriteLine();
+ mosquittoConfig.WriteLine("# MQTT over WebSockets, encrypted, unauthenticated");
+ mosquittoConfig.WriteLine($"listener {MqttWssPort} 0.0.0.0");
+ mosquittoConfig.WriteLine("protocol websockets");
+ mosquittoConfig.WriteLine("allow_anonymous true");
+ mosquittoConfig.WriteLine("tls_version tlsv1.2");
+ mosquittoConfig.WriteLine($"certfile {CertificateFilePath}");
+ mosquittoConfig.WriteLine($"keyfile {CertificateKeyFilePath}");
+ }
+
+ var mosquittoBuilder = WithResourceMapping(Encoding.Default.GetBytes(mosquittoConfig.ToString()), "/mosquitto/config/mosquitto.conf");
+ return new MosquittoContainer(mosquittoBuilder.DockerResourceConfiguration);
+ }
+
+ ///
+ protected override MosquittoBuilder Init()
+ {
+ return base.Init()
+ .WithImage(MosquittoImage)
+ .WithPortBinding(MqttPort, true)
+ .WithPortBinding(MqttWsPort, true)
+ .WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged("mosquitto.*running"));
+ }
+
+ ///
+ protected override MosquittoBuilder Clone(IResourceConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new MosquittoConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override MosquittoBuilder Clone(IContainerConfiguration resourceConfiguration)
+ {
+ return Merge(DockerResourceConfiguration, new MosquittoConfiguration(resourceConfiguration));
+ }
+
+ ///
+ protected override MosquittoBuilder Merge(MosquittoConfiguration oldValue, MosquittoConfiguration newValue)
+ {
+ return new MosquittoBuilder(new MosquittoConfiguration(oldValue, newValue));
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.Mosquitto/MosquittoConfiguration.cs b/src/Testcontainers.Mosquitto/MosquittoConfiguration.cs
new file mode 100644
index 000000000..c2128bb3f
--- /dev/null
+++ b/src/Testcontainers.Mosquitto/MosquittoConfiguration.cs
@@ -0,0 +1,76 @@
+namespace Testcontainers.Mosquitto;
+
+///
+[PublicAPI]
+public sealed class MosquittoConfiguration : ContainerConfiguration
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The public certificate in PEM format.
+ /// The private key associated with the certificate in PEM format.
+ public MosquittoConfiguration(
+ string certificate = null,
+ string certificateKey = null)
+ {
+ Certificate = certificate;
+ CertificateKey = certificateKey;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public MosquittoConfiguration(IResourceConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public MosquittoConfiguration(IContainerConfiguration resourceConfiguration)
+ : base(resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The Docker resource configuration.
+ public MosquittoConfiguration(MosquittoConfiguration resourceConfiguration)
+ : this(new MosquittoConfiguration(), resourceConfiguration)
+ {
+ // Passes the configuration upwards to the base implementations to create an updated immutable copy.
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The old Docker resource configuration.
+ /// The new Docker resource configuration.
+ public MosquittoConfiguration(MosquittoConfiguration oldValue, MosquittoConfiguration newValue)
+ : base(oldValue, newValue)
+ {
+ Certificate = BuildConfiguration.Combine(oldValue.Certificate, newValue.Certificate);
+ CertificateKey = BuildConfiguration.Combine(oldValue.CertificateKey, newValue.CertificateKey);
+ }
+
+ ///
+ /// Gets a value indicating whether TLS is enabled or not.
+ ///
+ public bool TlsEnabled => Certificate != null && CertificateKey != null;
+
+ ///
+ /// Gets the public certificate in PEM format.
+ ///
+ public string Certificate { get; }
+
+ ///
+ /// Gets the private key associated with the certificate in PEM format.
+ ///
+ public string CertificateKey { get; }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.Mosquitto/MosquittoContainer.cs b/src/Testcontainers.Mosquitto/MosquittoContainer.cs
new file mode 100644
index 000000000..2a40596df
--- /dev/null
+++ b/src/Testcontainers.Mosquitto/MosquittoContainer.cs
@@ -0,0 +1,80 @@
+namespace Testcontainers.Mosquitto;
+
+///
+[PublicAPI]
+public sealed class MosquittoContainer : DockerContainer
+{
+ private readonly MosquittoConfiguration _configuration;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The container configuration.
+ public MosquittoContainer(MosquittoConfiguration configuration)
+ : base(configuration)
+ {
+ _configuration = configuration;
+ }
+
+ ///
+ /// Gets the MQTT port.
+ ///
+ public ushort MqttPort => GetMappedPublicPort(MosquittoBuilder.MqttPort);
+
+ ///
+ /// Gets the secure MQTT port.
+ ///
+ public ushort MqttTlsPort => GetMappedPublicPort(MosquittoBuilder.MqttTlsPort);
+
+ ///
+ /// Gets the MQTT connection string.
+ ///
+ /// An MQTT connection string in the format: mqtt://hostname:port.
+ public string GetConnectionString()
+ {
+ return new UriBuilder("mqtt", Hostname, MqttPort).ToString();
+ }
+
+ ///
+ /// Gets the secure MQTT connection string.
+ ///
+ /// A secure MQTT connection string in the format: mqtts://hostname:port.
+ /// TLS/SSL support is not enabled in the container configuration.
+ public string GetSecureConnectionString()
+ {
+ ThrowIfTlsNotEnabled();
+ return new UriBuilder("mqtts", Hostname, MqttTlsPort).ToString();
+ }
+
+ ///
+ /// Gets the MQTT over WebSocket connection string.
+ ///
+ /// A WebSocket connection string in the format: ws://hostname:port.
+ public string GetWsConnectionString()
+ {
+ return new UriBuilder("ws", Hostname, GetMappedPublicPort(MosquittoBuilder.MqttWsPort)).ToString();
+ }
+
+ ///
+ /// Gets the secure MQTT over WebSocket connection string.
+ ///
+ /// A secure WebSocket connection string in the format: wss://hostname:port.
+ /// TLS/SSL support is not enabled in the container configuration.
+ public string GetWssConnectionString()
+ {
+ ThrowIfTlsNotEnabled();
+ return new UriBuilder("wss", Hostname, GetMappedPublicPort(MosquittoBuilder.MqttWssPort)).ToString();
+ }
+
+ ///
+ /// Throws when TLS/SSL support is not enabled in the container configuration.
+ ///
+ /// TLS/SSL support is not enabled in the container configuration.
+ private void ThrowIfTlsNotEnabled()
+ {
+ if (!_configuration.TlsEnabled)
+ {
+ throw new InvalidOperationException("TLS/SSL support is not enabled in the container configuration.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Testcontainers.Mosquitto/Testcontainers.Mosquitto.csproj b/src/Testcontainers.Mosquitto/Testcontainers.Mosquitto.csproj
new file mode 100644
index 000000000..058c1e82b
--- /dev/null
+++ b/src/Testcontainers.Mosquitto/Testcontainers.Mosquitto.csproj
@@ -0,0 +1,12 @@
+
+
+ net8.0;net9.0;netstandard2.0;netstandard2.1
+ latest
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Testcontainers.Mosquitto/Usings.cs b/src/Testcontainers.Mosquitto/Usings.cs
new file mode 100644
index 000000000..5cd85391a
--- /dev/null
+++ b/src/Testcontainers.Mosquitto/Usings.cs
@@ -0,0 +1,8 @@
+global using System;
+global using System.IO;
+global using System.Text;
+global using Docker.DotNet.Models;
+global using DotNet.Testcontainers.Builders;
+global using DotNet.Testcontainers.Configurations;
+global using DotNet.Testcontainers.Containers;
+global using JetBrains.Annotations;
\ No newline at end of file
diff --git a/src/Testcontainers.Qdrant/QdrantConfiguration.cs b/src/Testcontainers.Qdrant/QdrantConfiguration.cs
index 99a4288d0..c465cfa2f 100644
--- a/src/Testcontainers.Qdrant/QdrantConfiguration.cs
+++ b/src/Testcontainers.Qdrant/QdrantConfiguration.cs
@@ -66,7 +66,7 @@ public QdrantConfiguration(QdrantConfiguration oldValue, QdrantConfiguration new
///
/// Gets a value indicating whether TLS is enabled or not.
///
- public bool TlsEnabled => Certificate != null;
+ public bool TlsEnabled => Certificate != null && CertificateKey != null;
///
/// Gets the API key that secures the instance.
diff --git a/tests/Testcontainers.Mosquitto.Tests/.editorconfig b/tests/Testcontainers.Mosquitto.Tests/.editorconfig
new file mode 100644
index 000000000..6f066619d
--- /dev/null
+++ b/tests/Testcontainers.Mosquitto.Tests/.editorconfig
@@ -0,0 +1 @@
+root = true
\ No newline at end of file
diff --git a/tests/Testcontainers.Mosquitto.Tests/.runs-on b/tests/Testcontainers.Mosquitto.Tests/.runs-on
new file mode 100644
index 000000000..d0395e498
--- /dev/null
+++ b/tests/Testcontainers.Mosquitto.Tests/.runs-on
@@ -0,0 +1 @@
+ubuntu-24.04
\ No newline at end of file
diff --git a/tests/Testcontainers.Mosquitto.Tests/MosquittoContainerTest.cs b/tests/Testcontainers.Mosquitto.Tests/MosquittoContainerTest.cs
new file mode 100644
index 000000000..a62b24700
--- /dev/null
+++ b/tests/Testcontainers.Mosquitto.Tests/MosquittoContainerTest.cs
@@ -0,0 +1,135 @@
+namespace Testcontainers.Mosquitto;
+
+public abstract class MosquittoContainerTest : ContainerTest
+{
+ private static readonly string Certificate = File.ReadAllText(Certificates.Instance.GetFilePath("server", "server.crt"));
+
+ private static readonly string CertificateKey = File.ReadAllText(Certificates.Instance.GetFilePath("server", "server.key"));
+
+ private readonly MqttClientFactory _clientFactory = new MqttClientFactory();
+
+ private MosquittoContainerTest(ITestOutputHelper testOutputHelper)
+ : base(testOutputHelper)
+ {
+ }
+
+ protected abstract MqttClientOptions GetClientOptions();
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task EstablishesConnection()
+ {
+ // Given
+ using var client = _clientFactory.CreateMqttClient();
+
+ // When
+ var result = await client.ConnectAsync(GetClientOptions(), TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ // Then
+ Assert.Equal(MqttClientConnectResultCode.Success, result.ResultCode);
+ }
+
+ [Fact]
+ [Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
+ public async Task SubTopicReturnsPubMessage()
+ {
+ // Given
+ const string helloMosquitto = "Hello, Mosquitto!";
+
+ const string topicId = "hello-topic";
+
+ var messageReceived = new TaskCompletionSource();
+
+ using var client = _clientFactory.CreateMqttClient();
+
+ // When
+ _ = await client.ConnectAsync(GetClientOptions(), TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ client.ApplicationMessageReceivedAsync += e =>
+ {
+ messageReceived.SetResult(e.ApplicationMessage.ConvertPayloadToString());
+ return Task.CompletedTask;
+ };
+
+ _ = await client.SubscribeAsync(topicId, cancellationToken: TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ _ = await client.PublishStringAsync(topicId, helloMosquitto, cancellationToken: TestContext.Current.CancellationToken)
+ .ConfigureAwait(true);
+
+ var completedTask = await Task.WhenAny(messageReceived.Task, Task.Delay(TimeSpan.FromSeconds(1), TestContext.Current.CancellationToken))
+ .ConfigureAwait(true);
+
+ // Then
+ Assert.Equal(messageReceived.Task, completedTask);
+
+ var message = await messageReceived.Task
+ .ConfigureAwait(true);
+
+ Assert.Equal(helloMosquitto, message);
+ }
+
+ [UsedImplicitly]
+ public sealed class TcpUnencryptedUnauthenticatedConfiguration(ITestOutputHelper testOutputHelper)
+ : MosquittoContainerTest(testOutputHelper)
+ {
+ protected override MqttClientOptions GetClientOptions()
+ {
+ return new MqttClientOptionsBuilder()
+ .WithTcpServer(Container.Hostname, Container.MqttPort)
+ .Build();
+ }
+ }
+
+ [UsedImplicitly]
+ public sealed class TcpEncryptedUnauthenticatedConfiguration(ITestOutputHelper testOutputHelper)
+ : MosquittoContainerTest(testOutputHelper)
+ {
+ protected override MosquittoBuilder Configure(MosquittoBuilder builder)
+ {
+ return builder.WithCertificate(Certificate, CertificateKey);
+ }
+
+ protected override MqttClientOptions GetClientOptions()
+ {
+ return new MqttClientOptionsBuilder()
+ .WithTcpServer(Container.Hostname, Container.MqttTlsPort)
+ .WithTlsOptions(options => options.WithCertificateValidationHandler(e =>
+ "CN=Test CA".Equals(e.Certificate.Issuer, StringComparison.Ordinal)))
+ .Build();
+ }
+ }
+
+ [UsedImplicitly]
+ public sealed class WebSocketUnencryptedUnauthenticatedConfiguration(ITestOutputHelper testOutputHelper)
+ : MosquittoContainerTest(testOutputHelper)
+ {
+ protected override MqttClientOptions GetClientOptions()
+ {
+ return new MqttClientOptionsBuilder()
+ .WithWebSocketServer(options => options.WithUri(Container.GetWsConnectionString()))
+ .Build();
+ }
+ }
+
+ [UsedImplicitly]
+ public sealed class WebSocketEncryptedUnauthenticatedConfiguration(ITestOutputHelper testOutputHelper)
+ : MosquittoContainerTest(testOutputHelper)
+ {
+ protected override MosquittoBuilder Configure(MosquittoBuilder builder)
+ {
+ return builder.WithCertificate(Certificate, CertificateKey);
+ }
+
+ protected override MqttClientOptions GetClientOptions()
+ {
+ return new MqttClientOptionsBuilder()
+ .WithWebSocketServer(options => options.WithUri(Container.GetWssConnectionString()))
+ .WithTlsOptions(options => options.WithCertificateValidationHandler(e =>
+ "CN=Test CA".Equals(e.Certificate.Issuer, StringComparison.Ordinal)))
+ .Build();
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Testcontainers.Mosquitto.Tests/Testcontainers.Mosquitto.Tests.csproj b/tests/Testcontainers.Mosquitto.Tests/Testcontainers.Mosquitto.Tests.csproj
new file mode 100644
index 000000000..dd152d79e
--- /dev/null
+++ b/tests/Testcontainers.Mosquitto.Tests/Testcontainers.Mosquitto.Tests.csproj
@@ -0,0 +1,20 @@
+
+
+ net9.0
+ false
+ false
+ Exe
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/Testcontainers.Mosquitto.Tests/Usings.cs b/tests/Testcontainers.Mosquitto.Tests/Usings.cs
new file mode 100644
index 000000000..f129b6023
--- /dev/null
+++ b/tests/Testcontainers.Mosquitto.Tests/Usings.cs
@@ -0,0 +1,8 @@
+global using System;
+global using System.IO;
+global using System.Threading.Tasks;
+global using DotNet.Testcontainers.Commons;
+global using JetBrains.Annotations;
+global using MQTTnet;
+global using Testcontainers.Xunit;
+global using Xunit;
\ No newline at end of file