Files
DigitalData.MessagingService/tests/DigitalData.MessagingService.Tests/Integration/MessagingServiceClientTests.cs
TekH 888f01ea95 Add integration tests for MessagingServiceClient
Introduce `MessagingServiceClientTests` to test email sending
via RabbitMQ with dependency injection. Implement methods to
load configuration, manage RabbitMQ messages, and ensure
proper resource disposal. Include an in-memory email account
repository for testing purposes.
2026-11-04 15:09:51 +01:00

176 lines
8.2 KiB
C#

using System.Linq.Expressions;
using System.Text;
using System.Text.Json;
using DigitalData.MessagingService.Application.Common.Dto;
using DigitalData.MessagingService.Application.Common.Interfaces.Repositories;
using DigitalData.MessagingService.Client;
using DigitalData.MessagingService.Client.DependencyInjection;
using DigitalData.MessagingService.Domain.Entities;
using DigitalData.MessagingService.Publisher;
using DigitalData.MessagingService.RabbitMQ;
using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.MessagingService.Tests.Integration;
public sealed class MessagingServiceClientTests : IAsyncDisposable
{
private const string SenderAddress = "test-flow@digitaldata.works";
private const string RecipientAddress = "htek0100@gmail.com";
private readonly ServiceProvider _serviceProvider;
private readonly MessagingServiceClient _client;
private readonly RabbitMqConnectionFactory _factory;
public MessagingServiceClientTests()
{
var services = new ServiceCollection();
services.AddLogging();
services.AddMessagingServiceClient(LoadLuckyPennySoftLicenseKey(), RabbitMqTestConfig.Apply);
services.AddSingleton<IRepository<EmailAccount>>(
new InMemoryEmailAccountRepository(
[
new EmailAccount
{
Id = 1,
Username = SenderAddress,
Password = "ddemail108",
SmtpServer = "kundencenter.triplew.de",
SmtpPort = 465,
SmtpUseSsl = true,
UseOAuth2 = false
}
]));
_serviceProvider = services.BuildServiceProvider();
_client = _serviceProvider.GetRequiredService<MessagingServiceClient>();
_factory = _serviceProvider.GetRequiredService<RabbitMqConnectionFactory>();
}
[Fact]
public async Task SendAsync_WithDependencyInjectionAndRealBroker_PublishesExpectedMessage()
{
var subject = $"MessagingServiceClient Integration Test {Guid.NewGuid():N}";
var email = new Email
{
From = SenderAddress,
To = [RecipientAddress],
Subject = subject,
Body = "<p>Realistic test mail via MessagingServiceClient.</p>",
IsHtml = true
};
await _client.SendAsync(email);
await Task.Delay(500);
var evt = await FindMessageBySubjectAsync(subject);
Assert.NotNull(evt);
Assert.Equal(SenderAddress, evt.Mail.Sender.Username);
Assert.Contains(RecipientAddress, evt.Mail.Recipients);
Assert.Equal(subject, evt.Mail.Subject);
Assert.Equal(email.Body, evt.Mail.Body);
Assert.True(evt.Mail.IsHtml);
}
private async Task<SendingEmailEvent?> FindMessageBySubjectAsync(string subject, int maxMessages = 100)
{
var connection = await _factory.GetDefaultConnectionAsync();
await using var channel = await connection.CreateChannelAsync();
var inspectedTags = new List<ulong>();
for (var i = 0; i < maxMessages; i++)
{
var result = await channel.BasicGetAsync(RabbitMqTestConfig.QueueName, autoAck: false);
if (result is null)
break;
inspectedTags.Add(result.DeliveryTag);
var json = Encoding.UTF8.GetString(result.Body.ToArray());
var message = JsonSerializer.Deserialize<SendingEmailEvent>(json);
if (message?.Mail.Subject == subject)
{
foreach (var tag in inspectedTags)
await channel.BasicNackAsync(tag, multiple: false, requeue: true);
return message;
}
}
foreach (var tag in inspectedTags)
await channel.BasicNackAsync(tag, multiple: false, requeue: true);
return null;
}
private static string LoadLuckyPennySoftLicenseKey()
{
var appsettingsPath = FindApiAppSettingsPath();
using var doc = JsonDocument.Parse(File.ReadAllText(appsettingsPath));
if (doc.RootElement.TryGetProperty("LuckyPennySoftLicenseKey", out var key) &&
key.ValueKind == JsonValueKind.String &&
!string.IsNullOrWhiteSpace(key.GetString()))
return key.GetString()!;
throw new InvalidOperationException($"LuckyPennySoftLicenseKey not found in '{appsettingsPath}'.");
}
private static string FindApiAppSettingsPath()
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
while (current is not null)
{
var candidate = Path.Combine(current.FullName, "src", "presentation", "DigitalData.MessagingService.API", "appsettings.json");
if (File.Exists(candidate))
return candidate;
current = current.Parent;
}
throw new FileNotFoundException("Could not locate API appsettings.json from test output directory.");
}
public async ValueTask DisposeAsync()
{
await _factory.DisposeAsync();
await _serviceProvider.DisposeAsync();
}
private sealed class InMemoryEmailAccountRepository(IEnumerable<EmailAccount> accounts) : IRepository<EmailAccount>
{
private readonly List<EmailAccount> _accounts = accounts.ToList();
public Task<IEnumerable<EmailAccount>> FindAsync(Expression<Func<EmailAccount, bool>> predicate, int? skip = null, int? take = null, CancellationToken cancellationToken = default)
{
var query = _accounts.AsQueryable().Where(predicate);
if (skip.HasValue)
query = query.Skip(skip.Value);
if (take.HasValue)
query = query.Take(take.Value);
return Task.FromResult<IEnumerable<EmailAccount>>(query.ToList());
}
public Task<EmailAccount> CreateAsync<TDto>(TDto dto, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<IEnumerable<EmailAccount>> CreateRangeAsync<TDto>(IEnumerable<TDto> dtos, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<EmailAccount?> GetByIdAsync(int id, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<IEnumerable<EmailAccount>> GetAllAsync(CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<EmailAccount?> FindFirstAsync(Expression<Func<EmailAccount, bool>> predicate, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<EmailAccount?> FindSingleAsync(Expression<Func<EmailAccount, bool>> predicate, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<int> CountAsync(Expression<Func<EmailAccount, bool>>? predicate = null, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<bool> AnyAsync(Expression<Func<EmailAccount, bool>> predicate, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<(EmailAccount Entity, bool Created)> UpsertAsync<TDto>(Expression<Func<EmailAccount, bool>> predicate, TDto dto, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<(EmailAccount Entity, bool Created)> UpsertSingleAsync<TDto>(Expression<Func<EmailAccount, bool>> predicate, TDto dto, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task UpdateSingleAsync<TDto>(Expression<Func<EmailAccount, bool>> predicate, TDto dto, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<int> UpdateAsync<TDto>(Expression<Func<EmailAccount, bool>> predicate, TDto dto, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task DeleteSingleAsync(Expression<Func<EmailAccount, bool>> predicate, CancellationToken cancellationToken = default) => throw new NotSupportedException();
public Task<int> DeleteAsync(Expression<Func<EmailAccount, bool>> predicate, CancellationToken cancellationToken = default) => throw new NotSupportedException();
}
}