feat: consume transactional email notifications
Add contract validation, SMTP delivery results, terminal failure context, neutral development seeding, and local Docker setup. Ref: IT-1033
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
namespace HrynCo.NotificationService.Services.Tests.EmailProcessing;
|
||||
|
||||
using HrynCo.NotificationService.Contracts.Messages;
|
||||
using HrynCo.NotificationService.DAL.Abstract.Providers;
|
||||
using HrynCo.NotificationService.DAL.Abstract.Repositories;
|
||||
using HrynCo.NotificationService.DAL.Abstract.Templates;
|
||||
using HrynCo.NotificationService.Worker.Services.EmailProcessing;
|
||||
using Hrynco.RabbitMq;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
|
||||
public sealed class SendEmailServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task ProcessAsync_ValidItemTrackerMessage_SendsOnceAndUpdatesUsage()
|
||||
{
|
||||
Guid channelId = Guid.NewGuid();
|
||||
var settings = new SmtpChannelSettings
|
||||
{
|
||||
Host = "smtp.example.invalid",
|
||||
Port = 587,
|
||||
Username = "smtp-user",
|
||||
Password = "not-a-real-secret",
|
||||
UseSsl = true,
|
||||
FromEmail = "notifications@example.com",
|
||||
FromName = "StoreMate"
|
||||
};
|
||||
var channel = new EmailChannel
|
||||
{
|
||||
Id = channelId,
|
||||
ServiceName = "StoreMate-Prod",
|
||||
Priority = 1,
|
||||
EmailChannelType = EmailChannelType.Smtp,
|
||||
Settings = settings,
|
||||
IsActive = true
|
||||
};
|
||||
var template = new EmailTemplate
|
||||
{
|
||||
ServiceName = "StoreMate-Prod",
|
||||
Key = "EmailVerification",
|
||||
LanguageCode = "uk",
|
||||
Subject = "Verify {{AppName}}",
|
||||
HtmlBody = "<p>{{AppName}}</p>",
|
||||
TextBody = "{{AppName}}",
|
||||
Variables = [new EmailTemplateVariable { Name = "AppName", Required = true }]
|
||||
};
|
||||
|
||||
IEmailChannelRepository channels = Substitute.For<IEmailChannelRepository>();
|
||||
channels.GetByServiceAsync("StoreMate-Prod", Arg.Any<CancellationToken>())
|
||||
.Returns([channel]);
|
||||
IEmailChannelUsageRepository usage = Substitute.For<IEmailChannelUsageRepository>();
|
||||
IEmailTemplateService templates = Substitute.For<IEmailTemplateService>();
|
||||
templates.GetAsync("StoreMate-Prod", "EmailVerification", "uk", Arg.Any<CancellationToken>())
|
||||
.Returns(template);
|
||||
var renderer = new EmailTemplateRenderingService();
|
||||
var smtp = new RecordingSmtpEmailSender();
|
||||
INotificationResultPublisher resultPublisher = Substitute.For<INotificationResultPublisher>();
|
||||
var service = new SendEmailService(
|
||||
channels,
|
||||
usage,
|
||||
templates,
|
||||
renderer,
|
||||
smtp,
|
||||
resultPublisher,
|
||||
NullLogger<SendEmailService>.Instance);
|
||||
var message = new SendEmailMessage
|
||||
{
|
||||
CorrelationContext = new CorrelationContext
|
||||
{
|
||||
CorrelationId = Guid.NewGuid().ToString("D")
|
||||
},
|
||||
Data = new SendEmailMessageData
|
||||
{
|
||||
ServiceName = "StoreMate-Prod",
|
||||
TemplateKey = "EmailVerification",
|
||||
RecipientEmail = "owner@example.com",
|
||||
RecipientName = "Owner",
|
||||
LanguageCode = "uk",
|
||||
Variables = new Dictionary<string, string> { ["AppName"] = "StoreMate" }
|
||||
}
|
||||
};
|
||||
|
||||
await service.ProcessAsync(message, CancellationToken.None);
|
||||
|
||||
Assert.Equal(1, smtp.SendCount);
|
||||
Assert.Same(settings, smtp.Settings);
|
||||
Assert.Equal("Verify StoreMate", smtp.Email?.Subject);
|
||||
Assert.Equal("owner@example.com", smtp.RecipientEmail);
|
||||
Assert.Equal("Owner", smtp.RecipientName);
|
||||
await usage.Received(1).IncrementUsageAsync(
|
||||
channelId,
|
||||
Arg.Any<DateOnly>(),
|
||||
CancellationToken.None);
|
||||
await resultPublisher.Received(1).PublishAsync(
|
||||
message,
|
||||
null,
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
private sealed class RecordingSmtpEmailSender : ISmtpEmailSender
|
||||
{
|
||||
public int SendCount { get; private set; }
|
||||
public SmtpChannelSettings? Settings { get; private set; }
|
||||
public RenderedEmail? Email { get; private set; }
|
||||
public string? RecipientEmail { get; private set; }
|
||||
public string? RecipientName { get; private set; }
|
||||
|
||||
public Task SendAsync(
|
||||
SmtpChannelSettings settings,
|
||||
RenderedEmail email,
|
||||
string recipientEmail,
|
||||
string recipientName,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
SendCount++;
|
||||
Settings = settings;
|
||||
Email = email;
|
||||
RecipientEmail = recipientEmail;
|
||||
RecipientName = recipientName;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user