Skip to content

Commit

Permalink
feat: Add Papercut module (#1044)
Browse files Browse the repository at this point in the history
Co-authored-by: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com>
  • Loading branch information
TechLiam and HofmeisterAn authored Nov 15, 2023
1 parent 2229e96 commit 1f3daa7
Show file tree
Hide file tree
Showing 12 changed files with 288 additions and 1 deletion.
14 changes: 14 additions & 0 deletions Testcontainers.sln
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Neo4j", "src
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle", "src\Testcontainers.Oracle\Testcontainers.Oracle.csproj", "{596EAFC1-0496-495C-B382-D57415FA456A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Papercut", "src\Testcontainers.Papercut\Testcontainers.Papercut.csproj", "{464F1120-A0DA-462B-B9E8-45176D883625}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PostgreSql", "src\Testcontainers.PostgreSql\Testcontainers.PostgreSql.csproj", "{8AB91636-9055-4900-A72A-7CFFACDFDBF0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.PubSub", "src\Testcontainers.PubSub\Testcontainers.PubSub.csproj", "{E6642255-667D-476B-B584-089AA5E6C0B1}"
Expand Down Expand Up @@ -135,6 +137,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Neo4j.Tests"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Oracle.Tests", "tests\Testcontainers.Oracle.Tests\Testcontainers.Oracle.Tests.csproj", "{4AC1088B-9965-4497-AC8E-570F1AD5631F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Papercut.Tests", "tests\Testcontainers.Papercut.Tests\Testcontainers.Papercut.Tests.csproj", "{904C8476-FCEF-41F0-8948-9EFA7C08712E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Platform.Linux.Tests", "tests\Testcontainers.Platform.Linux.Tests\Testcontainers.Platform.Linux.Tests.csproj", "{DA1D7ADE-452C-4369-83CC-56289176EACD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Platform.Windows.Tests", "tests\Testcontainers.Platform.Windows.Tests\Testcontainers.Platform.Windows.Tests.csproj", "{3E55CBE8-AFE8-426D-9470-49D63CD1051C}"
Expand Down Expand Up @@ -268,6 +272,10 @@ Global
{596EAFC1-0496-495C-B382-D57415FA456A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{596EAFC1-0496-495C-B382-D57415FA456A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{596EAFC1-0496-495C-B382-D57415FA456A}.Release|Any CPU.Build.0 = Release|Any CPU
{464F1120-A0DA-462B-B9E8-45176D883625}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{464F1120-A0DA-462B-B9E8-45176D883625}.Debug|Any CPU.Build.0 = Debug|Any CPU
{464F1120-A0DA-462B-B9E8-45176D883625}.Release|Any CPU.ActiveCfg = Release|Any CPU
{464F1120-A0DA-462B-B9E8-45176D883625}.Release|Any CPU.Build.0 = Release|Any CPU
{8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8AB91636-9055-4900-A72A-7CFFACDFDBF0}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -412,6 +420,10 @@ Global
{4AC1088B-9965-4497-AC8E-570F1AD5631F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4AC1088B-9965-4497-AC8E-570F1AD5631F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4AC1088B-9965-4497-AC8E-570F1AD5631F}.Release|Any CPU.Build.0 = Release|Any CPU
{904C8476-FCEF-41F0-8948-9EFA7C08712E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{904C8476-FCEF-41F0-8948-9EFA7C08712E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{904C8476-FCEF-41F0-8948-9EFA7C08712E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{904C8476-FCEF-41F0-8948-9EFA7C08712E}.Release|Any CPU.Build.0 = Release|Any CPU
{DA1D7ADE-452C-4369-83CC-56289176EACD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DA1D7ADE-452C-4369-83CC-56289176EACD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DA1D7ADE-452C-4369-83CC-56289176EACD}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -487,6 +499,7 @@ Global
{BF37BEA1-0816-4326-B1E0-E82290F8FCE0} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{ADC2372B-6FE0-421D-8277-BB628E8EFC22} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{596EAFC1-0496-495C-B382-D57415FA456A} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{464F1120-A0DA-462B-B9E8-45176D883625} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{8AB91636-9055-4900-A72A-7CFFACDFDBF0} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{E6642255-667D-476B-B584-089AA5E6C0B1} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
{A6D480BC-FDE8-4B92-A2A6-FF16BEE486AE} = {673F23AE-7694-4BB9-ABD4-136D6C13634E}
Expand Down Expand Up @@ -523,6 +536,7 @@ Global
{87A3F137-6DC3-4CE5-91E6-01797D076086} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{D3F63405-C0FA-4F83-8B79-E30BFF5FF5BF} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{4AC1088B-9965-4497-AC8E-570F1AD5631F} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{904C8476-FCEF-41F0-8948-9EFA7C08712E} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{DA1D7ADE-452C-4369-83CC-56289176EACD} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{3E55CBE8-AFE8-426D-9470-49D63CD1051C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
{56D0DCA5-567F-4B3B-8B79-CB108F8EB8A6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF}
Expand Down
1 change: 0 additions & 1 deletion src/Testcontainers.Consul/Usings.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
global using System;
global using System.Net;
global using Docker.DotNet.Models;
global using DotNet.Testcontainers.Builders;
global using DotNet.Testcontainers.Configurations;
Expand Down
1 change: 1 addition & 0 deletions src/Testcontainers.Papercut/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
69 changes: 69 additions & 0 deletions src/Testcontainers.Papercut/PapercutBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
namespace Testcontainers.Papercut;

/// <inheritdoc cref="ContainerBuilder{TBuilderEntity, TContainerEntity, TConfigurationEntity}" />
[PublicAPI]
public sealed class PapercutBuilder : ContainerBuilder<PapercutBuilder, PapercutContainer, PapercutConfiguration>
{
public const string PapercutImage = "jijiechen/papercut:latest";

public const ushort HttpPort = 37408;

public const ushort SmtpPort = 25;

/// <summary>
/// Initializes a new instance of the <see cref="PapercutBuilder" /> class.
/// </summary>
public PapercutBuilder()
: this(new PapercutConfiguration())
{
DockerResourceConfiguration = Init().DockerResourceConfiguration;
}

/// <summary>
/// Initializes a new instance of the <see cref="PapercutBuilder" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
private PapercutBuilder(PapercutConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
DockerResourceConfiguration = resourceConfiguration;
}

/// <inheritdoc />
protected override PapercutConfiguration DockerResourceConfiguration { get; }

/// <inheritdoc />
public override PapercutContainer Build()
{
Validate();
return new PapercutContainer(DockerResourceConfiguration, TestcontainersSettings.Logger);
}

/// <inheritdoc />
protected override PapercutBuilder Init()
{
return base.Init()
.WithImage(PapercutImage)
.WithPortBinding(HttpPort, true)
.WithPortBinding(SmtpPort, true)
.WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(request => request.ForPort(HttpPort)));
}

/// <inheritdoc />
protected override PapercutBuilder Clone(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new PapercutConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override PapercutBuilder Clone(IContainerConfiguration resourceConfiguration)
{
return Merge(DockerResourceConfiguration, new PapercutConfiguration(resourceConfiguration));
}

/// <inheritdoc />
protected override PapercutBuilder Merge(PapercutConfiguration oldValue, PapercutConfiguration newValue)
{
return new PapercutBuilder(new PapercutConfiguration(oldValue, newValue));
}
}
53 changes: 53 additions & 0 deletions src/Testcontainers.Papercut/PapercutConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
namespace Testcontainers.Papercut;

/// <inheritdoc cref="ContainerConfiguration" />
[PublicAPI]
public sealed class PapercutConfiguration : ContainerConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="PapercutConfiguration" /> class.
/// </summary>
public PapercutConfiguration()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="PapercutConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public PapercutConfiguration(IResourceConfiguration<CreateContainerParameters> resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="PapercutConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public PapercutConfiguration(IContainerConfiguration resourceConfiguration)
: base(resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="PapercutConfiguration" /> class.
/// </summary>
/// <param name="resourceConfiguration">The Docker resource configuration.</param>
public PapercutConfiguration(PapercutConfiguration resourceConfiguration)
: this(new PapercutConfiguration(), resourceConfiguration)
{
// Passes the configuration upwards to the base implementations to create an updated immutable copy.
}

/// <summary>
/// Initializes a new instance of the <see cref="PapercutConfiguration" /> class.
/// </summary>
/// <param name="oldValue">The old Docker resource configuration.</param>
/// <param name="newValue">The new Docker resource configuration.</param>
public PapercutConfiguration(PapercutConfiguration oldValue, PapercutConfiguration newValue)
: base(oldValue, newValue)
{
}
}
30 changes: 30 additions & 0 deletions src/Testcontainers.Papercut/PapercutContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Testcontainers.Papercut;

/// <inheritdoc cref="DockerContainer" />
[PublicAPI]
public sealed class PapercutContainer : DockerContainer
{
/// <summary>
/// Initializes a new instance of the <see cref="PapercutContainer" /> class.
/// </summary>
/// <param name="configuration">The container configuration.</param>
/// <param name="logger">The logger.</param>
public PapercutContainer(PapercutConfiguration configuration, ILogger logger)
: base(configuration, logger)
{
}

/// <summary>
/// Gets the SMTP port.
/// </summary>
public ushort SmtpPort => GetMappedPublicPort(PapercutBuilder.SmtpPort);

/// <summary>
/// Gets the Papercut base address.
/// </summary>
/// <returns>The Papercut base address.</returns>
public string GetBaseAddress()
{
return new UriBuilder(Uri.UriSchemeHttp, Hostname, GetMappedPublicPort(PapercutBuilder.HttpPort)).ToString();
}
}
16 changes: 16 additions & 0 deletions src/Testcontainers.Papercut/Testcontainers.Papercut.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Copyright>Copyright (c) 2019 - 2023 Liam Wilson, Andre Hofmeister and other authors</Copyright>
<Authors>Liam Wilson, Andre Hofmeister and contributors</Authors>
<Description>A Testcontainers Papercut module for testing SMTP clients and sending emails.</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" PrivateAssets="All"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SolutionDir)src/Testcontainers/Testcontainers.csproj"/>
</ItemGroup>
</Project>
7 changes: 7 additions & 0 deletions src/Testcontainers.Papercut/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
global using System;
global using Docker.DotNet.Models;
global using DotNet.Testcontainers.Builders;
global using DotNet.Testcontainers.Configurations;
global using DotNet.Testcontainers.Containers;
global using JetBrains.Annotations;
global using Microsoft.Extensions.Logging;
1 change: 1 addition & 0 deletions tests/Testcontainers.Papercut.Tests/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
root = true
72 changes: 72 additions & 0 deletions tests/Testcontainers.Papercut.Tests/PapercutContainerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
namespace Testcontainers.Papercut;

public sealed class PapercutContainerTest : IAsyncLifetime
{
private readonly PapercutContainer _papercutContainer = new PapercutBuilder().Build();

public Task InitializeAsync()
{
return _papercutContainer.StartAsync();
}

public Task DisposeAsync()
{
return _papercutContainer.DisposeAsync().AsTask();
}

[Fact]
[Trait(nameof(DockerCli.DockerPlatform), nameof(DockerCli.DockerPlatform.Linux))]
public async Task ReceivesSentMessage()
{
// Given
const string subject = "Test";

Message[] messages;

using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(_papercutContainer.GetBaseAddress());

using var smtpClient = new SmtpClient(_papercutContainer.Hostname, _papercutContainer.SmtpPort);

// When
smtpClient.Send("from@example.com", "to@example.com", subject, "A test message");

do
{
var messagesJson = await httpClient.GetStringAsync("/api/messages")
.ConfigureAwait(false);

var jsonDocument = JsonDocument.Parse(messagesJson);
messages = jsonDocument.RootElement.GetProperty("messages").Deserialize<Message[]>();
}
while (messages.Length == 0);

// Then
Assert.NotEmpty(messages);
Assert.Equal(subject, messages[0].Subject);
}

private readonly struct Message
{
[JsonConstructor]
public Message(string id, string subject, string size, DateTime createdAt)
{
Id = id;
Subject = subject;
Size = size;
CreatedAt = createdAt;
}

[JsonPropertyName("id")]
public string Id { get; }

[JsonPropertyName("subject")]
public string Subject { get; }

[JsonPropertyName("size")]
public string Size { get; }

[JsonPropertyName("createdAt")]
public DateTime CreatedAt { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<IsPublishable>false</IsPublishable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2"/>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0"/>
<PackageReference Include="xunit" Version="2.5.0"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(SolutionDir)src/Testcontainers.Papercut/Testcontainers.Papercut.csproj"/>
<ProjectReference Include="$(SolutionDir)tests/Testcontainers.Commons/Testcontainers.Commons.csproj"/>
</ItemGroup>
</Project>
8 changes: 8 additions & 0 deletions tests/Testcontainers.Papercut.Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
global using System;
global using System.Net.Http;
global using System.Net.Mail;
global using System.Text.Json;
global using System.Text.Json.Serialization;
global using System.Threading.Tasks;
global using DotNet.Testcontainers.Commons;
global using Xunit;

0 comments on commit 1f3daa7

Please sign in to comment.