mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-18 21:40:41 -04:00
Merge pull request #116 from lanedirt/111-add-e2eunit-test-for-email-smtp-service
Add integration test for email smtp service
This commit is contained in:
29
.github/workflows/dotnet-e2e-tests.yml
vendored
Normal file
29
.github/workflows/dotnet-e2e-tests.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# This workflow will build a .NET project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
|
||||
|
||||
name: .NET E2E Tests (Playwright)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Install dependencies
|
||||
run: dotnet workload install wasm-tools
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
- name: Ensure browsers are installed
|
||||
run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net8.0/playwright.ps1 install --with-deps
|
||||
- name: Run E2E tests
|
||||
run: dotnet test src/Tests/AliasVault.E2ETests --no-build --verbosity normal
|
||||
@@ -1,7 +1,7 @@
|
||||
# This workflow will build a .NET project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
|
||||
|
||||
name: Playwright integration tests
|
||||
name: .NET Integration Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -23,7 +23,5 @@ jobs:
|
||||
run: dotnet workload install wasm-tools
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
- name: Ensure browsers are installed
|
||||
run: pwsh src/Tests/AliasVault.E2ETests/bin/Debug/net8.0/playwright.ps1 install --with-deps
|
||||
- name: Run your tests
|
||||
run: dotnet test src/Tests/AliasVault.E2ETests --no-build --verbosity normal
|
||||
- name: Run integration tests
|
||||
run: dotnet test src/Tests/AliasVault.IntegrationTests --no-build --verbosity normal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# This workflow will build a .NET project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
|
||||
|
||||
name: .NET build and run tests
|
||||
name: .NET Unit Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -10,10 +10,8 @@ on:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
@@ -26,5 +24,5 @@ jobs:
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
- name: Test
|
||||
- name: Run unittests
|
||||
run: dotnet test src/Tests/AliasVault.UnitTests --no-build --verbosity normal
|
||||
@@ -39,6 +39,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{8A
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.SmtpService", "src\Services\AliasVault.SmtpService\AliasVault.SmtpService.csproj", "{B095A174-E528-4D38-BEC1-D1D38B3B30C0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AliasVault.IntegrationTests", "src\Tests\AliasVault.IntegrationTests\AliasVault.IntegrationTests.csproj", "{1C7C8DE9-5F2A-43DB-A25E-33319E80A509}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -97,6 +99,10 @@ Global
|
||||
{B095A174-E528-4D38-BEC1-D1D38B3B30C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B095A174-E528-4D38-BEC1-D1D38B3B30C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B095A174-E528-4D38-BEC1-D1D38B3B30C0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1C7C8DE9-5F2A-43DB-A25E-33319E80A509}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1C7C8DE9-5F2A-43DB-A25E-33319E80A509}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1C7C8DE9-5F2A-43DB-A25E-33319E80A509}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1C7C8DE9-5F2A-43DB-A25E-33319E80A509}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -112,6 +118,7 @@ Global
|
||||
{607945F3-9896-4544-99EC-F3496CF4D36B} = {29DE523D-EEF2-41E9-AC12-F20D8D02BEBB}
|
||||
{A9C9A606-C87E-4298-AB32-09B1884D7487} = {01AB9389-2F89-4F8E-A688-BF4BF1FC42C8}
|
||||
{B095A174-E528-4D38-BEC1-D1D38B3B30C0} = {8A477241-B96C-4174-968D-D40CB77F1ECD}
|
||||
{1C7C8DE9-5F2A-43DB-A25E-33319E80A509} = {29DE523D-EEF2-41E9-AC12-F20D8D02BEBB}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {FEE82475-C009-4762-8113-A6563D9DC49E}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="AllowedDomainsFilter.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.SmtpService;
|
||||
|
||||
using SmtpServer;
|
||||
using SmtpServer.Mail;
|
||||
using SmtpServer.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// Filter to allow only emails from configured domains.
|
||||
/// </summary>
|
||||
public class AllowedDomainsFilter(Config config, ILogger<AllowedDomainsFilter> logger) : IMailboxFilter, IMailboxFilterFactory
|
||||
{
|
||||
private readonly TimeSpan _delay = TimeSpan.Zero;
|
||||
|
||||
public async Task<bool> CanAcceptFromAsync(ISessionContext context, IMailbox from, int size, CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Delay(_delay, cancellationToken);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<bool> CanDeliverToAsync(ISessionContext context, IMailbox to, IMailbox from, CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Delay(_delay, cancellationToken);
|
||||
|
||||
if (!config.AllowedToDomains.Contains(to.Host.ToLowerInvariant()))
|
||||
{
|
||||
// ToAddress host is not allowed, return error to sender.
|
||||
logger.LogWarning("Email to {ToAddress} is not allowed", to);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public IMailboxFilter CreateInstance(ISessionContext context)
|
||||
{
|
||||
return new AllowedDomainsFilter(context.ServiceProvider.GetRequiredService<Config>(), context.ServiceProvider.GetRequiredService<ILogger<AllowedDomainsFilter>>());
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.SmtpService.Handlers;
|
||||
|
||||
using System.Buffers;
|
||||
using System.Net.Mail;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -16,8 +18,6 @@ using SmtpServer;
|
||||
using SmtpServer.Protocol;
|
||||
using SmtpServer.Storage;
|
||||
|
||||
namespace AliasVault.SmtpService;
|
||||
|
||||
/// <summary>
|
||||
/// Custom exception for when the email parsing fails to find the "to" address in the email.
|
||||
/// </summary>
|
||||
@@ -58,7 +58,7 @@ public class DatabaseMessageStore(ILogger<DatabaseMessageStore> logger, Config c
|
||||
}
|
||||
|
||||
stream.Position = 0;
|
||||
var message = await MimeKit.MimeMessage.LoadAsync(stream, cancellationToken);
|
||||
var message = await MimeMessage.LoadAsync(stream, cancellationToken);
|
||||
// Retrieve all addresses from the SMTP transaction which should contain all recipients for this mail instance.
|
||||
var allAddresses = transaction.To
|
||||
.Distinct()
|
||||
@@ -76,19 +76,18 @@ public class DatabaseMessageStore(ILogger<DatabaseMessageStore> logger, Config c
|
||||
}
|
||||
if (!config.AllowedToDomains.Contains(toAddress.Host.ToLowerInvariant()))
|
||||
{
|
||||
// ToAddress domain is not allowed, return error to sender.
|
||||
// ToAddress domain is not allowed.
|
||||
if (toAddresses.Count > 1)
|
||||
{
|
||||
// If more recipients, silently skip this one.
|
||||
continue;
|
||||
}
|
||||
|
||||
// If only one recipient, return error.
|
||||
logger.LogWarning("Email to {ToAddress} is not allowed", toAddress.User + "@" + toAddress.Host);
|
||||
return SmtpResponse.NoValidRecipientsGiven;
|
||||
}
|
||||
|
||||
// Remove existing x-receiver and x-sender headers to avoid duplication.
|
||||
message.Headers.RemoveAll("x-receiver");
|
||||
message.Headers.RemoveAll("x-sender");
|
||||
|
||||
// Add new x-receiver and x-sender headers.
|
||||
message.Headers.Add("x-receiver", toAddress.User + "@" + toAddress.Host);
|
||||
message.Headers.Add("x-sender", transaction.From.User + "@" + transaction.From.Host);
|
||||
|
||||
var insertedId = await InsertEmailIntoDatabase(message);
|
||||
logger.LogInformation("Email saved into database with ID {insertedId}.", insertedId);
|
||||
}
|
||||
@@ -115,8 +114,8 @@ public class DatabaseMessageStore(ILogger<DatabaseMessageStore> logger, Config c
|
||||
/// <summary>
|
||||
/// Convert MimeMessage to Email database object.
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="message">MimeMessage object.</param>
|
||||
/// <returns>Email object.</returns>
|
||||
/// <exception cref="EmailParseMissingToException"></exception>
|
||||
private static Email ConvertMimeMessageToEmail(MimeMessage message)
|
||||
{
|
||||
@@ -6,12 +6,13 @@
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
using System.Data.Common;
|
||||
using AliasVault.SmtpService;
|
||||
using SmtpServer;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using AliasServerDb;
|
||||
using AliasVault.SmtpService;
|
||||
using AliasVault.SmtpService.Handlers;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using SmtpServer;
|
||||
using SmtpServer.Storage;
|
||||
|
||||
var builder = Host.CreateApplicationBuilder(args);
|
||||
@@ -43,11 +44,10 @@ builder.Services.AddSingleton<DbConnection>(container =>
|
||||
builder.Services.AddDbContextFactory<AliasServerDbContext>((container, options) =>
|
||||
{
|
||||
var connection = container.GetRequiredService<DbConnection>();
|
||||
options.UseSqlite(connection).UseLazyLoadingProxies();
|
||||
options.UseSqlite(connection);
|
||||
});
|
||||
|
||||
builder.Services.AddTransient<IMessageStore, DatabaseMessageStore>();
|
||||
builder.Services.AddTransient<IMailboxFilter, AllowedDomainsFilter>();
|
||||
|
||||
builder.Services.AddSingleton(
|
||||
provider =>
|
||||
@@ -120,6 +120,14 @@ builder.Services.AddSingleton(
|
||||
);
|
||||
|
||||
builder.Services.AddHostedService<Worker>();
|
||||
|
||||
var host = builder.Build();
|
||||
|
||||
using (var scope = host.Services.CreateScope())
|
||||
{
|
||||
var container = scope.ServiceProvider;
|
||||
var db = container.GetRequiredService<AliasServerDbContext>();
|
||||
|
||||
await db.Database.MigrateAsync();
|
||||
}
|
||||
|
||||
await host.RunAsync();
|
||||
|
||||
@@ -24,16 +24,16 @@ using Microsoft.Extensions.Hosting;
|
||||
public class WebApplicationApiFactoryFixture<TEntryPoint> : WebApplicationFactory<TEntryPoint>
|
||||
where TEntryPoint : class
|
||||
{
|
||||
/// <summary>
|
||||
/// The DbContext instance that is created for the test.
|
||||
/// </summary>
|
||||
private AliasServerDbContext? _dbContext;
|
||||
|
||||
/// <summary>
|
||||
/// The DbConnection instance that is created for the test.
|
||||
/// </summary>
|
||||
private DbConnection? _dbConnection;
|
||||
|
||||
/// <summary>
|
||||
/// The DbContext instance that is created for the test.
|
||||
/// </summary>
|
||||
private AliasServerDbContext? _dbContext;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL the web application host will listen on.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
|
||||
<PackageReference Include="MailKit" Version="4.7.1.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0"/>
|
||||
<PackageReference Include="NUnit" Version="3.14.0"/>
|
||||
<PackageReference Include="NUnit.Analyzers" Version="3.9.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="NUnit.Framework"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\..\stylecop.json">
|
||||
<Link>stylecop.json</Link>
|
||||
</AdditionalFiles>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Services\AliasVault.SmtpService\AliasVault.SmtpService.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,200 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="SmtpServerTests.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.IntegrationTests.SmtpServer;
|
||||
|
||||
using AliasServerDb;
|
||||
|
||||
using MailKit.Security;
|
||||
using MailKit.Net.Smtp;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MimeKit;
|
||||
|
||||
[TestFixture]
|
||||
public class SmtpServerTests
|
||||
{
|
||||
/// <summary>
|
||||
/// The test host instance.
|
||||
/// </summary>
|
||||
private IHost _testHost = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The test host builder instance.
|
||||
/// </summary>
|
||||
private TestHostBuilder _testHostBuilder = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Setup logic for every test.
|
||||
/// </summary>
|
||||
[SetUp]
|
||||
public async Task Setup()
|
||||
{
|
||||
_testHostBuilder = new TestHostBuilder();
|
||||
_testHost = _testHostBuilder.Build();
|
||||
|
||||
await _testHost.StartAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tear down logic for every test.
|
||||
/// </summary>
|
||||
[TearDown]
|
||||
public async Task TearDown()
|
||||
{
|
||||
if (_testHost != null)
|
||||
{
|
||||
await _testHost.StopAsync();
|
||||
_testHost.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests sending a single email in plain format to the SMTP server to check if it is processed correctly.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task SingleEmailPlain()
|
||||
{
|
||||
// Send an email to the SMTP server.
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(new MailboxAddress("Test Sender", "sender@example.com"));
|
||||
message.To.Add(new MailboxAddress("Test Recipient", "recipient@example.tld"));
|
||||
message.Subject = "Test Email";
|
||||
const string textBody = "This is a test email plain.";
|
||||
message.Body = new BodyBuilder { TextBody = textBody}.ToMessageBody();
|
||||
await SendMessageToSmtpServer(message);
|
||||
|
||||
// Check if the email is in the database.
|
||||
var processedEmail = await _testHostBuilder.GetDbContext().Emails.FirstAsync();
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(processedEmail, Is.Not.Null);
|
||||
Assert.That(processedEmail.From, Is.EqualTo("\"Test Sender\" <sender@example.com>"));
|
||||
Assert.That(processedEmail.FromLocal, Is.EqualTo("sender"));
|
||||
Assert.That(processedEmail.FromDomain, Is.EqualTo("example.com"));
|
||||
Assert.That(processedEmail.To, Is.EqualTo("\"Test Recipient\" <recipient@example.tld>"));
|
||||
Assert.That(processedEmail.MessagePreview, Is.EqualTo("This is a test email plain."));
|
||||
Assert.That(processedEmail.MessagePlain, Is.EqualTo("This is a test email plain."));
|
||||
Assert.That(processedEmail.MessageHtml, Is.Null);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests sending a single email in html format to the SMTP server to check if it is processed correctly.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task SingleEmailHtml()
|
||||
{
|
||||
// Arrange
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(new MailboxAddress("Test Sender", "sender@example.com"));
|
||||
message.To.Add(new MailboxAddress("Test Recipient", "recipient@example.tld"));
|
||||
message.Subject = "Test Email with HTML body.";
|
||||
const string htmlBody = "<html><body><h1>This is a test email html.</h1></body></html>";
|
||||
message.Body = new BodyBuilder { HtmlBody = htmlBody }.ToMessageBody();
|
||||
await SendMessageToSmtpServer(message);
|
||||
|
||||
// Check if the email is in the database.
|
||||
var processedEmail = await _testHostBuilder.GetDbContext().Emails.FirstAsync();
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(processedEmail, Is.Not.Null);
|
||||
Assert.That(processedEmail.To, Is.EqualTo("\"Test Recipient\" <recipient@example.tld>"));
|
||||
Assert.That(processedEmail.MessagePreview, Is.EqualTo("This is a test email html."));
|
||||
Assert.That(processedEmail.MessagePlain, Is.Null);
|
||||
Assert.That(processedEmail.MessageHtml, Is.EqualTo(htmlBody));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests sending a single email in multipart format to the SMTP server to check if it is processed correctly.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task SingleEmailMultipart()
|
||||
{
|
||||
// Arrange
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(new MailboxAddress("Test Sender", "sender@example.com"));
|
||||
message.To.Add(new MailboxAddress("Test Recipient", "recipient@example.tld"));
|
||||
message.Subject = "Test Email with multipart body.";
|
||||
const string textBody = "This is a test email multipart.";
|
||||
const string htmlBody = "<html><body><h1>This is a test email multipart.</h1></body></html>";
|
||||
message.Body = new BodyBuilder { TextBody = textBody, HtmlBody = htmlBody}.ToMessageBody();
|
||||
await SendMessageToSmtpServer(message);
|
||||
|
||||
// Check if the email is in the database.
|
||||
var processedEmail = await _testHostBuilder.GetDbContext().Emails.FirstAsync();
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(processedEmail, Is.Not.Null);
|
||||
Assert.That(processedEmail.To, Is.EqualTo("\"Test Recipient\" <recipient@example.tld>"));
|
||||
Assert.That(processedEmail.MessagePreview, Is.EqualTo("This is a test email multipart."));
|
||||
Assert.That(processedEmail.MessagePlain, Is.EqualTo("This is a test email multipart."));
|
||||
Assert.That(processedEmail.MessageHtml, Is.EqualTo(htmlBody));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests sending a single email in plain format to the SMTP server to check if it is processed correctly.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task MultipleRecipientsEmail()
|
||||
{
|
||||
// Send an email to the SMTP server.
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(new MailboxAddress("Test Sender", "sender@example.com"));
|
||||
message.To.Add(new MailboxAddress("Test Recipient", "recipient.to@example.tld"));
|
||||
message.Cc.Add(new MailboxAddress("Test Recipient 2", "recipient.cc@example.tld"));
|
||||
message.Cc.Add(new MailboxAddress("Test Recipient 3 unknown domain", "recipient@unknowndomain.tld"));
|
||||
|
||||
message.Subject = "Test Email";
|
||||
const string textBody = "This is a test email plain.";
|
||||
message.Body = new BodyBuilder { TextBody = textBody}.ToMessageBody();
|
||||
await SendMessageToSmtpServer(message);
|
||||
|
||||
// Check that two emails are in the database, one for each allowed recipient.
|
||||
Assert.That(await _testHostBuilder.GetDbContext().Emails.CountAsync(), Is.EqualTo(2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests sending a single email in plain format to the SMTP server to check if it is processed correctly.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SingleEmailUnknownRecipient()
|
||||
{
|
||||
// Send an email to the SMTP server.
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(new MailboxAddress("Test Sender", "sender@example.com"));
|
||||
message.To.Add(new MailboxAddress("Test Recipient", "recipient@unknowndomain.tld"));
|
||||
message.Subject = "Test Email";
|
||||
const string textBody = "This is a test email plain.";
|
||||
message.Body = new BodyBuilder { TextBody = textBody}.ToMessageBody();
|
||||
|
||||
// Expect error from SmtpClient when sending email to unknown domain.
|
||||
Assert.ThrowsAsync<SmtpCommandException>(async () => await SendMessageToSmtpServer(message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a message to the SMTP server.
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
private static async Task SendMessageToSmtpServer(MimeMessage message)
|
||||
{
|
||||
using var client = new SmtpClient();
|
||||
|
||||
await client.ConnectAsync("localhost", 2525, SecureSocketOptions.None);
|
||||
try
|
||||
{
|
||||
await client.SendAsync(message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await client.DisconnectAsync(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// -----------------------------------------------------------------------
|
||||
// <copyright file="TestHostBuilder.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
using AliasVault.SmtpService.Handlers;
|
||||
|
||||
namespace AliasVault.IntegrationTests.SmtpServer;
|
||||
|
||||
using System.Data.Common;
|
||||
using AliasServerDb;
|
||||
using SmtpService;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using global::SmtpServer;
|
||||
using global::SmtpServer.Storage;
|
||||
|
||||
public class TestHostBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// The DbConnection instance that is created for the test.
|
||||
/// </summary>
|
||||
private DbConnection? _dbConnection;
|
||||
|
||||
/// <summary>
|
||||
/// The DbContext instance that is created for the test.
|
||||
/// </summary>
|
||||
private AliasServerDbContext? _dbContext;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the DbContext instance for the test. This can be used to seed the database with test data.
|
||||
/// </summary>
|
||||
/// <returns>AliasServerDbContext instance.</returns>
|
||||
public AliasServerDbContext GetDbContext()
|
||||
{
|
||||
if (_dbContext == null)
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<AliasServerDbContext>()
|
||||
.UseSqlite(_dbConnection!)
|
||||
.Options;
|
||||
|
||||
_dbContext = new AliasServerDbContext(options);
|
||||
}
|
||||
|
||||
return _dbContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the SmtpService test host.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IHost Build()
|
||||
{
|
||||
// Create a persistent in-memory database for the duration of the test.
|
||||
_dbConnection = new SqliteConnection("DataSource=:memory:");
|
||||
_dbConnection.Open();
|
||||
|
||||
var builder = Host.CreateDefaultBuilder()
|
||||
.ConfigureServices((context, services) =>
|
||||
{
|
||||
services.AddSingleton(new Config
|
||||
{
|
||||
AllowedToDomains = new List<string> { "example.tld" },
|
||||
SmtpTlsEnabled = "false"
|
||||
});
|
||||
|
||||
services.AddSingleton(_dbConnection);
|
||||
|
||||
services.AddDbContextFactory<AliasServerDbContext>((sp, options) =>
|
||||
{
|
||||
var connection = sp.GetRequiredService<DbConnection>();
|
||||
options.UseSqlite(connection);
|
||||
});
|
||||
|
||||
services.AddTransient<IMessageStore, DatabaseMessageStore>();
|
||||
services.AddSingleton<global::SmtpServer.SmtpServer>(
|
||||
provider =>
|
||||
{
|
||||
var options = new SmtpServerOptionsBuilder()
|
||||
.ServerName("aliasvault");
|
||||
|
||||
// Note: port 25 doesn't work in GitHub actions so we use these instead for the integration tests:
|
||||
// - 2525 for the SMTP server
|
||||
// - 5870 for the submission server
|
||||
options.Endpoint(serverBuilder =>
|
||||
serverBuilder
|
||||
.Port(2525, false))
|
||||
.Endpoint(serverBuilder =>
|
||||
serverBuilder
|
||||
.Port(5870, false)
|
||||
);
|
||||
|
||||
return new SmtpServer(options.Build(), provider.GetRequiredService<IServiceProvider>());
|
||||
}
|
||||
);
|
||||
|
||||
services.AddHostedService<Worker>();
|
||||
|
||||
// Ensure the in-memory database is populated with tables
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
using (var scope = serviceProvider.CreateScope())
|
||||
{
|
||||
var dbContextFactory = scope.ServiceProvider.GetRequiredService<IDbContextFactory<AliasServerDbContext>>();
|
||||
var dbContext = dbContextFactory.CreateDbContext();
|
||||
dbContext.Database.Migrate();
|
||||
}
|
||||
});
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user