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.
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user