diff --git a/InvoiceReminder.CrossCutting.IoC/DependencyInjectionConfig.cs b/InvoiceReminder.CrossCutting.IoC/DependencyInjectionConfig.cs index 597515e..2d6582c 100644 --- a/InvoiceReminder.CrossCutting.IoC/DependencyInjectionConfig.cs +++ b/InvoiceReminder.CrossCutting.IoC/DependencyInjectionConfig.cs @@ -92,7 +92,7 @@ private static IServiceCollection AddExternalServices(this IServiceCollection se _ = services.Scan(scan => scan.FromAssembliesOf(typeof(IGmailServiceWrapper)) .AddClasses(classes => classes.InNamespaces(namespaces)) - .UsingRegistrationStrategy(RegistrationStrategy.Skip) + .UsingRegistrationStrategy(RegistrationStrategy.Append) .AsImplementedInterfaces() .WithScopedLifetime() ); diff --git a/InvoiceReminder.ExternalServices/BarcodeReader/AccountInvoiceBarcodeHandler.cs b/InvoiceReminder.ExternalServices/BarcodeReader/AccountInvoiceBarcodeHandler.cs index 0889d54..1679d1a 100644 --- a/InvoiceReminder.ExternalServices/BarcodeReader/AccountInvoiceBarcodeHandler.cs +++ b/InvoiceReminder.ExternalServices/BarcodeReader/AccountInvoiceBarcodeHandler.cs @@ -1,10 +1,13 @@ using InvoiceReminder.Domain.Entities; +using InvoiceReminder.Domain.Enums; using System.Text.RegularExpressions; namespace InvoiceReminder.ExternalServices.BarcodeReader; public class AccountInvoiceBarcodeHandler : IInvoiceBarcodeHandler { + public InvoiceType InvoiceType => InvoiceType.AccountInvoice; + public Invoice CreateInvoice(string content, string beneficiary) { ArgumentException.ThrowIfNullOrWhiteSpace(content); diff --git a/InvoiceReminder.ExternalServices/BarcodeReader/BankInvoiceBarcodeHandler.cs b/InvoiceReminder.ExternalServices/BarcodeReader/BankInvoiceBarcodeHandler.cs index 7a08993..c6c94b5 100644 --- a/InvoiceReminder.ExternalServices/BarcodeReader/BankInvoiceBarcodeHandler.cs +++ b/InvoiceReminder.ExternalServices/BarcodeReader/BankInvoiceBarcodeHandler.cs @@ -1,4 +1,5 @@ using InvoiceReminder.Domain.Entities; +using InvoiceReminder.Domain.Enums; using System.Globalization; using System.Text.RegularExpressions; @@ -8,6 +9,8 @@ public class BankInvoiceBarcodeHandler : IInvoiceBarcodeHandler { private readonly Dictionary knowBanks; + public InvoiceType InvoiceType => InvoiceType.BankInvoice; + public BankInvoiceBarcodeHandler() { knowBanks = new() diff --git a/InvoiceReminder.ExternalServices/BarcodeReader/BarcodeHandlerFactory.cs b/InvoiceReminder.ExternalServices/BarcodeReader/BarcodeHandlerFactory.cs new file mode 100644 index 0000000..267d66f --- /dev/null +++ b/InvoiceReminder.ExternalServices/BarcodeReader/BarcodeHandlerFactory.cs @@ -0,0 +1,32 @@ +using InvoiceReminder.Domain.Enums; + +namespace InvoiceReminder.ExternalServices.BarcodeReader; + +public class BarcodeHandlerFactory : IBarcodeHandlerFactory +{ + private readonly Dictionary _handlers; + + public BarcodeHandlerFactory(IEnumerable handlers) + { + var duplicatedTypes = handlers + .GroupBy(h => h.InvoiceType) + .Where(g => g.Count() > 1) + .Select(g => g.Key) + .ToArray(); + + if (duplicatedTypes.Length > 0) + { + throw new InvalidOperationException( + $"Duplicate barcode handlers registered for: {string.Join(", ", duplicatedTypes)}"); + } + + _handlers = handlers.ToDictionary(h => h.InvoiceType, h => h); + } + + public IInvoiceBarcodeHandler GetHandler(InvoiceType invoiceType) + { + return _handlers.TryGetValue(invoiceType, out var handler) + ? handler + : throw new NotSupportedException($"No barcode handler found for invoice type: {invoiceType}"); + } +} diff --git a/InvoiceReminder.ExternalServices/BarcodeReader/BarcodeReaderService.cs b/InvoiceReminder.ExternalServices/BarcodeReader/BarcodeReaderService.cs index 46307c1..ff18212 100644 --- a/InvoiceReminder.ExternalServices/BarcodeReader/BarcodeReaderService.cs +++ b/InvoiceReminder.ExternalServices/BarcodeReader/BarcodeReaderService.cs @@ -11,11 +11,12 @@ namespace InvoiceReminder.ExternalServices.BarcodeReader; public class BarcodeReaderService : IBarcodeReaderService { private readonly ILogger _logger; - private IInvoiceBarcodeHandler _barcodeHandler; + private readonly IBarcodeHandlerFactory _factory; - public BarcodeReaderService(ILogger logger) + public BarcodeReaderService(ILogger logger, IBarcodeHandlerFactory factory) { _logger = logger; + _factory = factory; } public Invoice ReadTextContentFromPdf(byte[] byteStream, string beneficiary, string password, InvoiceType invoiceType) @@ -45,6 +46,7 @@ public Invoice ReadTextContentFromPdf(byte[] byteStream, string beneficiary, str StringBuilder content = new(); var numberOfPages = pdfDoc.GetNumberOfPages(); + var barcodeHandler = _factory.GetHandler(invoiceType); for (var page = 1; page <= numberOfPages; page++) { @@ -57,18 +59,6 @@ public Invoice ReadTextContentFromPdf(byte[] byteStream, string beneficiary, str _ = content.Append(currentText).Replace(" \n", "\n").Replace(" \r\n", "\r\n"); } - SetBarcodeHandler(invoiceType); - - return _barcodeHandler.CreateInvoice(content.ToString(), beneficiary); - } - - private void SetBarcodeHandler(InvoiceType invoiceType) - { - _barcodeHandler = invoiceType switch - { - InvoiceType.BankInvoice => new BankInvoiceBarcodeHandler(), - InvoiceType.AccountInvoice => new AccountInvoiceBarcodeHandler(), - _ => default - }; + return barcodeHandler.CreateInvoice(content.ToString(), beneficiary); } } diff --git a/InvoiceReminder.ExternalServices/BarcodeReader/IBarcodeHandlerFactory.cs b/InvoiceReminder.ExternalServices/BarcodeReader/IBarcodeHandlerFactory.cs new file mode 100644 index 0000000..17dc209 --- /dev/null +++ b/InvoiceReminder.ExternalServices/BarcodeReader/IBarcodeHandlerFactory.cs @@ -0,0 +1,8 @@ +using InvoiceReminder.Domain.Enums; + +namespace InvoiceReminder.ExternalServices.BarcodeReader; + +public interface IBarcodeHandlerFactory +{ + IInvoiceBarcodeHandler GetHandler(InvoiceType invoiceType); +} diff --git a/InvoiceReminder.ExternalServices/BarcodeReader/IInvoiceBarcodeHandler.cs b/InvoiceReminder.ExternalServices/BarcodeReader/IInvoiceBarcodeHandler.cs index 38d08c3..aba12e8 100644 --- a/InvoiceReminder.ExternalServices/BarcodeReader/IInvoiceBarcodeHandler.cs +++ b/InvoiceReminder.ExternalServices/BarcodeReader/IInvoiceBarcodeHandler.cs @@ -1,8 +1,11 @@ using InvoiceReminder.Domain.Entities; +using InvoiceReminder.Domain.Enums; namespace InvoiceReminder.ExternalServices.BarcodeReader; public interface IInvoiceBarcodeHandler { + InvoiceType InvoiceType { get; } + Invoice CreateInvoice(string content, string beneficiary); } diff --git a/InvoiceReminder.UnitTests.ExternalServices/BarcodeReader/BarcodeHandlerFactoryTests.cs b/InvoiceReminder.UnitTests.ExternalServices/BarcodeReader/BarcodeHandlerFactoryTests.cs new file mode 100644 index 0000000..3487975 --- /dev/null +++ b/InvoiceReminder.UnitTests.ExternalServices/BarcodeReader/BarcodeHandlerFactoryTests.cs @@ -0,0 +1,114 @@ +using InvoiceReminder.Domain.Enums; +using InvoiceReminder.ExternalServices.BarcodeReader; +using NSubstitute; +using Shouldly; + +namespace InvoiceReminder.UnitTests.ExternalServices.BarcodeReader; + +[TestClass] +public sealed class BarcodeHandlerFactoryTests +{ + private readonly IInvoiceBarcodeHandler _accountInvoiceHandler; + private readonly IInvoiceBarcodeHandler _bankInvoiceHandler; + private BarcodeHandlerFactory _barcodeHandlerFactory; + + public BarcodeHandlerFactoryTests() + { + _accountInvoiceHandler = Substitute.For(); + _bankInvoiceHandler = Substitute.For(); + + _ = _accountInvoiceHandler.InvoiceType.Returns(InvoiceType.AccountInvoice); + _ = _bankInvoiceHandler.InvoiceType.Returns(InvoiceType.BankInvoice); + } + + [TestMethod] + public void Constructor_ShouldRegisterHandlers_WhenMultipleHandlersProvided() + { + // Arrange + var handlers = new List { _accountInvoiceHandler, _bankInvoiceHandler }; + + // Act + _barcodeHandlerFactory = new BarcodeHandlerFactory(handlers); + + // Assert + _ = _barcodeHandlerFactory.ShouldNotBeNull(); + } + + [TestMethod] + public void GetHandler_ShouldReturnAccountInvoiceHandler_WhenAccountInvoiceTypeRequested() + { + // Arrange + var handlers = new List { _accountInvoiceHandler, _bankInvoiceHandler }; + _barcodeHandlerFactory = new BarcodeHandlerFactory(handlers); + + // Act + var handler = _barcodeHandlerFactory.GetHandler(InvoiceType.AccountInvoice); + + // Assert + handler.ShouldBeSameAs(_accountInvoiceHandler); + } + + [TestMethod] + public void GetHandler_ShouldReturnBankInvoiceHandler_WhenBankInvoiceTypeRequested() + { + // Arrange + var handlers = new List { _accountInvoiceHandler, _bankInvoiceHandler }; + _barcodeHandlerFactory = new BarcodeHandlerFactory(handlers); + + // Act + var handler = _barcodeHandlerFactory.GetHandler(InvoiceType.BankInvoice); + + // Assert + handler.ShouldBeSameAs(_bankInvoiceHandler); + } + + [TestMethod] + public void GetHandler_ShouldThrowNotSupportedException_WhenInvoiceTypeNotFound() + { + // Arrange + var handlers = new List { _accountInvoiceHandler }; + _barcodeHandlerFactory = new BarcodeHandlerFactory(handlers); + + // Act && Assert + var exception = Should.Throw(() => + _barcodeHandlerFactory.GetHandler(InvoiceType.BankInvoice)); + + exception.Message.ShouldContain("No barcode handler found for invoice type:"); + } + + [TestMethod] + public void GetHandler_ShouldThrowNotSupportedException_WhenNoHandlersProvided() + { + // Arrange + var handlers = new List(); + _barcodeHandlerFactory = new BarcodeHandlerFactory(handlers); + + // Act && Assert + var exception = Should.Throw(() => + _barcodeHandlerFactory.GetHandler(InvoiceType.AccountInvoice)); + + exception.Message.ShouldContain("No barcode handler found for invoice type:"); + } + + [TestMethod] + public void Constructor_ShouldThrowInvalidOperationException_WhenDuplicateHandlersProvidedForSameType() + { + // Arrange + var duplicateAccountHandler = Substitute.For(); + _ = duplicateAccountHandler.InvoiceType.Returns(InvoiceType.AccountInvoice); + + var handlers = new List + { + _accountInvoiceHandler, + duplicateAccountHandler, + _bankInvoiceHandler + }; + + // Act && Assert + var exception = Should.Throw(() => + new BarcodeHandlerFactory(handlers)); + + exception.Message.ShouldContain("Duplicate barcode handlers registered for:"); + exception.Message.ShouldContain(InvoiceType.AccountInvoice.ToString()); + } +} diff --git a/InvoiceReminder.UnitTests.ExternalServices/BarcodeReader/BarcodeReaderServiceTests.cs b/InvoiceReminder.UnitTests.ExternalServices/BarcodeReader/BarcodeReaderServiceTests.cs index 77fbb6b..cf66491 100644 --- a/InvoiceReminder.UnitTests.ExternalServices/BarcodeReader/BarcodeReaderServiceTests.cs +++ b/InvoiceReminder.UnitTests.ExternalServices/BarcodeReader/BarcodeReaderServiceTests.cs @@ -16,6 +16,7 @@ public sealed class BarcodeReaderServiceTests { private readonly ILogger _logger; private readonly IInvoiceBarcodeHandler _barcodeHandler; + private readonly IBarcodeHandlerFactory _barcodeHandlerFactory; private readonly BarcodeReaderService _barcodeReaderService; private readonly Faker _invoiceFaker; private readonly Faker _faker; @@ -26,7 +27,8 @@ public BarcodeReaderServiceTests() { _logger = Substitute.For>(); _barcodeHandler = Substitute.For(); - _barcodeReaderService = new BarcodeReaderService(_logger); + _barcodeHandlerFactory = Substitute.For(); + _barcodeReaderService = new BarcodeReaderService(_logger, _barcodeHandlerFactory); _faker = new Faker(); _invoiceFaker = new Faker() @@ -83,6 +85,9 @@ public void ReadTextContentFromPdf_ShouldCreateValidAccountInvoice_WhenPdfContai var expectedInvoice = _invoiceFaker.Generate(); var password = string.Empty; + _ = _barcodeHandlerFactory.GetHandler(InvoiceType.AccountInvoice) + .Returns(_barcodeHandler); + _ = _barcodeHandler.CreateInvoice(Arg.Any(), Arg.Any()) .Returns(expectedInvoice); @@ -111,6 +116,9 @@ public void ReadTextContentFromPdf_ShouldCreateValidBankInvoice_WhenPdfContainsB var expectedInvoice = _invoiceFaker.Generate(); var password = string.Empty; + _ = _barcodeHandlerFactory.GetHandler(InvoiceType.BankInvoice) + .Returns(_barcodeHandler); + _ = _barcodeHandler.CreateInvoice(Arg.Any(), Arg.Any()) .Returns(expectedInvoice);