using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using NSubstitute.ExceptionExtensions; using ScadaLink.Commons.Entities.Notifications; using ScadaLink.Commons.Interfaces.Repositories; namespace ScadaLink.NotificationService.Tests; /// /// WP-11/12: Tests for notification delivery — SMTP delivery, error classification, S&F integration. /// public class NotificationDeliveryServiceTests { private readonly INotificationRepository _repository = Substitute.For(); private readonly ISmtpClientWrapper _smtpClient = Substitute.For(); private NotificationDeliveryService CreateService(StoreAndForward.StoreAndForwardService? sf = null) { return new NotificationDeliveryService( _repository, () => _smtpClient, NullLogger.Instance, tokenService: null, storeAndForward: sf); } private void SetupHappyPath() { var list = new NotificationList("ops-team") { Id = 1 }; var recipients = new List { new("Alice", "alice@example.com") { Id = 1, NotificationListId = 1 }, new("Bob", "bob@example.com") { Id = 2, NotificationListId = 1 } }; var smtpConfig = new SmtpConfiguration("smtp.example.com", "basic", "noreply@example.com") { Id = 1, Port = 587, Credentials = "user:pass", TlsMode = "starttls" }; _repository.GetListByNameAsync("ops-team").Returns(list); _repository.GetRecipientsByListIdAsync(1).Returns(recipients); _repository.GetAllSmtpConfigurationsAsync().Returns(new List { smtpConfig }); } [Fact] public async Task Send_ListNotFound_ReturnsError() { _repository.GetListByNameAsync("nonexistent").Returns((NotificationList?)null); var service = CreateService(); var result = await service.SendAsync("nonexistent", "Subject", "Body"); Assert.False(result.Success); Assert.Contains("not found", result.ErrorMessage); } [Fact] public async Task Send_NoRecipients_ReturnsError() { var list = new NotificationList("empty-list") { Id = 1 }; _repository.GetListByNameAsync("empty-list").Returns(list); _repository.GetRecipientsByListIdAsync(1).Returns(new List()); var service = CreateService(); var result = await service.SendAsync("empty-list", "Subject", "Body"); Assert.False(result.Success); Assert.Contains("no recipients", result.ErrorMessage); } [Fact] public async Task Send_NoSmtpConfig_ReturnsError() { var list = new NotificationList("test") { Id = 1 }; var recipients = new List { new("Alice", "alice@example.com") { Id = 1, NotificationListId = 1 } }; _repository.GetListByNameAsync("test").Returns(list); _repository.GetRecipientsByListIdAsync(1).Returns(recipients); _repository.GetAllSmtpConfigurationsAsync().Returns(new List()); var service = CreateService(); var result = await service.SendAsync("test", "Subject", "Body"); Assert.False(result.Success); Assert.Contains("No SMTP configuration", result.ErrorMessage); } [Fact] public async Task Send_Successful_ReturnsSuccess() { SetupHappyPath(); var service = CreateService(); var result = await service.SendAsync("ops-team", "Alert", "Something happened"); Assert.True(result.Success); Assert.Null(result.ErrorMessage); Assert.False(result.WasBuffered); } [Fact] public async Task Send_SmtpConnectsWithCorrectParams() { SetupHappyPath(); var service = CreateService(); await service.SendAsync("ops-team", "Alert", "Body"); await _smtpClient.Received().ConnectAsync("smtp.example.com", 587, true, Arg.Any()); await _smtpClient.Received().AuthenticateAsync("basic", "user:pass", Arg.Any()); await _smtpClient.Received().SendAsync( "noreply@example.com", Arg.Is>(bcc => bcc.Count() == 2), "Alert", "Body", Arg.Any()); } [Fact] public async Task Send_PermanentSmtpError_ReturnsErrorDirectly() { SetupHappyPath(); _smtpClient.SendAsync(Arg.Any(), Arg.Any>(), Arg.Any(), Arg.Any(), Arg.Any()) .Throws(new SmtpPermanentException("550 Mailbox not found")); var service = CreateService(); var result = await service.SendAsync("ops-team", "Alert", "Body"); Assert.False(result.Success); Assert.Contains("Permanent SMTP error", result.ErrorMessage); } [Fact] public async Task Send_TransientError_NoStoreAndForward_ReturnsError() { SetupHappyPath(); _smtpClient.SendAsync(Arg.Any(), Arg.Any>(), Arg.Any(), Arg.Any(), Arg.Any()) .Throws(new TimeoutException("Connection timed out")); var service = CreateService(sf: null); var result = await service.SendAsync("ops-team", "Alert", "Body"); Assert.False(result.Success); Assert.Contains("store-and-forward not available", result.ErrorMessage); } }