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.
176 lines
8.2 KiB
C#
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();
|
|
}
|
|
}
|