From b83e8d4eb171f86e2a225cd5810d738216506817 Mon Sep 17 00:00:00 2001 From: Mobin Barfi Date: Sat, 7 Sep 2024 16:29:25 +0330 Subject: [PATCH 1/3] feat: add ICsvReaderService interface --- src/Application/Interfaces/Services/ICsvReaderService.cs | 6 ++++++ src/Application/Services/DomainService/AccountService.cs | 6 ++++-- .../Services/DomainService/TransactionService.cs | 6 ++++-- src/Application/Services/SharedService/CsvReaderService.cs | 5 +++-- src/Web/Startup/ServiceExtensions.DI.cs | 2 ++ 5 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 src/Application/Interfaces/Services/ICsvReaderService.cs diff --git a/src/Application/Interfaces/Services/ICsvReaderService.cs b/src/Application/Interfaces/Services/ICsvReaderService.cs new file mode 100644 index 0000000..dc56c7b --- /dev/null +++ b/src/Application/Interfaces/Services/ICsvReaderService.cs @@ -0,0 +1,6 @@ +namespace Application.Interfaces.Services; + +public interface ICsvReaderService +{ + List ReadFromCsv(string filePath); +} \ No newline at end of file diff --git a/src/Application/Services/DomainService/AccountService.cs b/src/Application/Services/DomainService/AccountService.cs index 8e8cef1..f893788 100644 --- a/src/Application/Services/DomainService/AccountService.cs +++ b/src/Application/Services/DomainService/AccountService.cs @@ -11,17 +11,19 @@ namespace Application.Services.DomainService; public class AccountService : IAccountService { private readonly IAccountRepository _accountRepository; + private readonly ICsvReaderService _csvReaderService; - public AccountService(IAccountRepository accountRepository) + public AccountService(IAccountRepository accountRepository, ICsvReaderService csvReaderService) { _accountRepository = accountRepository; + _csvReaderService = csvReaderService; } public async Task AddAccountsFromCsvAsync(string filePath) { try { - var accountCsvModels = CsvReaderService.ReadFromCsv(filePath); + var accountCsvModels = _csvReaderService.ReadFromCsv(filePath); var accounts = accountCsvModels .Select(csvModel => csvModel.ToAccount()) diff --git a/src/Application/Services/DomainService/TransactionService.cs b/src/Application/Services/DomainService/TransactionService.cs index 3cd71eb..6729940 100644 --- a/src/Application/Services/DomainService/TransactionService.cs +++ b/src/Application/Services/DomainService/TransactionService.cs @@ -11,15 +11,17 @@ namespace Application.Services.DomainService; public class TransactionService : ITransactionService { private readonly ITransactionRepository _transactionRepository; + private readonly ICsvReaderService _csvReaderService; - public TransactionService(ITransactionRepository transactionRepository) + public TransactionService(ITransactionRepository transactionRepository, ICsvReaderService csvReaderService) { _transactionRepository = transactionRepository; + _csvReaderService = csvReaderService; } public async Task AddTransactionsFromCsvAsync(string filePath) { - var transactionCsvModels = CsvReaderService.ReadFromCsv(filePath); + var transactionCsvModels = _csvReaderService.ReadFromCsv(filePath); var transactions = transactionCsvModels .Select(csvModel => csvModel.ToTransaction()) diff --git a/src/Application/Services/SharedService/CsvReaderService.cs b/src/Application/Services/SharedService/CsvReaderService.cs index c12e326..2cf14b8 100644 --- a/src/Application/Services/SharedService/CsvReaderService.cs +++ b/src/Application/Services/SharedService/CsvReaderService.cs @@ -1,12 +1,13 @@ using System.Globalization; +using Application.Interfaces.Services; using CsvHelper; using CsvHelper.Configuration; namespace Application.Services.SharedService; -public static class CsvReaderService +public class CsvReaderService : ICsvReaderService { - public static List ReadFromCsv(string filePath) + public List ReadFromCsv(string filePath) { using var reader = new StreamReader(filePath); using var csv = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture) diff --git a/src/Web/Startup/ServiceExtensions.DI.cs b/src/Web/Startup/ServiceExtensions.DI.cs index 46f6623..d43179f 100644 --- a/src/Web/Startup/ServiceExtensions.DI.cs +++ b/src/Web/Startup/ServiceExtensions.DI.cs @@ -2,6 +2,7 @@ using Application.Interfaces.Repositories; using Application.Interfaces.Services; using Application.Services.DomainService; +using Application.Services.SharedService; using Infrastructure.Repositories; using Web.Services; @@ -20,5 +21,6 @@ public static void AddApplicationServices(this IServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } } \ No newline at end of file From e727b81ab6246be271c58a3c636d501e636b604e Mon Sep 17 00:00:00 2001 From: Mobin Barfi Date: Sat, 7 Sep 2024 16:30:33 +0330 Subject: [PATCH 2/3] test: test account service --- .../DomainService/AccountServiceTests.cs | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 test/Application.UnitTests/Services/DomainService/AccountServiceTests.cs diff --git a/test/Application.UnitTests/Services/DomainService/AccountServiceTests.cs b/test/Application.UnitTests/Services/DomainService/AccountServiceTests.cs new file mode 100644 index 0000000..d1e01fd --- /dev/null +++ b/test/Application.UnitTests/Services/DomainService/AccountServiceTests.cs @@ -0,0 +1,165 @@ +using Application.DTOs.Account; +using Application.Interfaces.Repositories; +using Application.Interfaces.Services; +using Application.Mappers; +using Application.Services.DomainService; +using Application.Services.SharedService; +using Domain.Entities; +using NSubstitute; + +namespace test.Application.UnitTests.Services.DomainService; + +public class AccountServiceTests +{ + private readonly IAccountRepository _accountRepository; + private readonly AccountService _accountService; + private readonly ICsvReaderService _csvReaderService; + + public AccountServiceTests() + { + _accountRepository = Substitute.For(); + _csvReaderService = Substitute.For(); + _accountService = new AccountService(_accountRepository, _csvReaderService); + } + + [Fact] + public async Task AddAccountsFromCsvAsync_WhenCsvIsValid_ReturnsOk() + { + // Arrange + var filePath = "dummy.csv"; + var accountCsvModels = new List + { + new AccountCsvModel + { + AccountID = 1, + CardID = 100, + IBAN = "IBAN1", + AccountType = "Savings", + BranchTelephone = "1234567890", + BranchAdress = "Main Street", + BranchName = "Main Branch", + OwnerName = "Mobin", + OwnerLastName = "Barfi", + OwnerID = 101 + }, + new AccountCsvModel + { + AccountID = 2, + CardID = 200, + IBAN = "IBAN2", + AccountType = "Checking", + BranchTelephone = "0987654321", + BranchAdress = "Some Street", + BranchName = "Some Branch", + OwnerName = "Mohammad", + OwnerLastName = "Mohammadi", + OwnerID = 102 + } + }; + var existingAccountIds = new List { 1 }; + + _csvReaderService.ReadFromCsv(filePath).Returns(accountCsvModels); + _accountRepository.GetAllIdsAsync().Returns(existingAccountIds); + + // Act + var result = await _accountService.AddAccountsFromCsvAsync(filePath); + + // Assert + Assert.True(result.Succeed); + await _accountRepository + .Received(1) + .CreateBulkAsync(Arg.Is>(a => + a.Count == 1 + && a.First().AccountId == 2 + && a.First().OwnerName == "Mohammad")); + } + + [Fact] + public async Task AddAccountsFromCsvAsync_WhenExceptionIsThrown_ReturnsFail() + { + // Arrange + var filePath = "dummy.csv"; + _csvReaderService + .When(x => x.ReadFromCsv(filePath)) + .Do(x => { throw new Exception("CSV read error"); }); + + // Act + var result = await _accountService.AddAccountsFromCsvAsync(filePath); + + // Assert + Assert.False(result.Succeed); + Assert.Contains("An unexpected error occurred: CSV read error", result.Message); + } + + [Fact] + public async Task GetAccountByIdAsync_WhenAccountExists_ReturnsAccount() + { + // Arrange + var accountId = 1; + var account = new Account { AccountId = accountId, OwnerName = "John Doe" }; + + _accountRepository.GetByIdAsync(accountId).Returns(account); + + // Act + var result = await _accountService.GetAccountByIdAsync(accountId); + + // Assert + Assert.NotNull(result); + Assert.Equal(accountId, result.Value!.AccountId); + Assert.Equal("John Doe", result.Value!.OwnerName); + } + + [Fact] + public async Task GetAccountByIdAsync_WhenAccountDoesNotExist_ReturnsNull() + { + // Arrange + var accountId = 1; + _accountRepository.GetByIdAsync(accountId).Returns((Account?)null); + + // Act + var result = await _accountService.GetAccountByIdAsync(accountId); + + // Assert + Assert.False(result.Succeed); + Assert.Null(result.Value); + Assert.Equal("Account not found", result.Message); + } + + [Fact] + public async Task GetAllAccountsAsync_WhenAccountsExist_ReturnsListOfAccounts() + { + // Arrange + var accounts = new List + { + new Account { AccountId = 1, OwnerName = "Mobin Barfi" }, + new Account { AccountId = 2, OwnerName = "Mohammad Mohammadi" } + }; + + _accountRepository.GetAllAccounts().Returns(accounts); + + // Act + var result = await _accountService.GetAllAccountsAsync(); + + // Assert + Assert.True(result.Succeed); + Assert.Equal(2, result.Value.Count); + Assert.Equal("Mobin Barfi", result.Value[0].OwnerName); + Assert.Equal("Mohammad Mohammadi", result.Value[1].OwnerName); + } + + [Fact] + public async Task GetAllAccountsAsync_WhenExceptionIsThrown_ReturnsFail() + { + // Arrange + _accountRepository + .When(x => x.GetAllAccounts()) + .Do(x => throw new Exception("Database error")); + + // Act + var result = await _accountService.GetAllAccountsAsync(); + + // Assert + Assert.False(result.Succeed); + Assert.Equal("An unexpected error occurred: Database error", result.Message); + } +} \ No newline at end of file From b21877d256bee92f83962c06afa7fe112329a40f Mon Sep 17 00:00:00 2001 From: Mobin Barfi Date: Sat, 7 Sep 2024 16:30:33 +0330 Subject: [PATCH 3/3] test: test account service --- .../DomainService/AccountServiceTests.cs | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 test/Application.UnitTests/Services/DomainService/AccountServiceTests.cs diff --git a/test/Application.UnitTests/Services/DomainService/AccountServiceTests.cs b/test/Application.UnitTests/Services/DomainService/AccountServiceTests.cs new file mode 100644 index 0000000..d1e01fd --- /dev/null +++ b/test/Application.UnitTests/Services/DomainService/AccountServiceTests.cs @@ -0,0 +1,165 @@ +using Application.DTOs.Account; +using Application.Interfaces.Repositories; +using Application.Interfaces.Services; +using Application.Mappers; +using Application.Services.DomainService; +using Application.Services.SharedService; +using Domain.Entities; +using NSubstitute; + +namespace test.Application.UnitTests.Services.DomainService; + +public class AccountServiceTests +{ + private readonly IAccountRepository _accountRepository; + private readonly AccountService _accountService; + private readonly ICsvReaderService _csvReaderService; + + public AccountServiceTests() + { + _accountRepository = Substitute.For(); + _csvReaderService = Substitute.For(); + _accountService = new AccountService(_accountRepository, _csvReaderService); + } + + [Fact] + public async Task AddAccountsFromCsvAsync_WhenCsvIsValid_ReturnsOk() + { + // Arrange + var filePath = "dummy.csv"; + var accountCsvModels = new List + { + new AccountCsvModel + { + AccountID = 1, + CardID = 100, + IBAN = "IBAN1", + AccountType = "Savings", + BranchTelephone = "1234567890", + BranchAdress = "Main Street", + BranchName = "Main Branch", + OwnerName = "Mobin", + OwnerLastName = "Barfi", + OwnerID = 101 + }, + new AccountCsvModel + { + AccountID = 2, + CardID = 200, + IBAN = "IBAN2", + AccountType = "Checking", + BranchTelephone = "0987654321", + BranchAdress = "Some Street", + BranchName = "Some Branch", + OwnerName = "Mohammad", + OwnerLastName = "Mohammadi", + OwnerID = 102 + } + }; + var existingAccountIds = new List { 1 }; + + _csvReaderService.ReadFromCsv(filePath).Returns(accountCsvModels); + _accountRepository.GetAllIdsAsync().Returns(existingAccountIds); + + // Act + var result = await _accountService.AddAccountsFromCsvAsync(filePath); + + // Assert + Assert.True(result.Succeed); + await _accountRepository + .Received(1) + .CreateBulkAsync(Arg.Is>(a => + a.Count == 1 + && a.First().AccountId == 2 + && a.First().OwnerName == "Mohammad")); + } + + [Fact] + public async Task AddAccountsFromCsvAsync_WhenExceptionIsThrown_ReturnsFail() + { + // Arrange + var filePath = "dummy.csv"; + _csvReaderService + .When(x => x.ReadFromCsv(filePath)) + .Do(x => { throw new Exception("CSV read error"); }); + + // Act + var result = await _accountService.AddAccountsFromCsvAsync(filePath); + + // Assert + Assert.False(result.Succeed); + Assert.Contains("An unexpected error occurred: CSV read error", result.Message); + } + + [Fact] + public async Task GetAccountByIdAsync_WhenAccountExists_ReturnsAccount() + { + // Arrange + var accountId = 1; + var account = new Account { AccountId = accountId, OwnerName = "John Doe" }; + + _accountRepository.GetByIdAsync(accountId).Returns(account); + + // Act + var result = await _accountService.GetAccountByIdAsync(accountId); + + // Assert + Assert.NotNull(result); + Assert.Equal(accountId, result.Value!.AccountId); + Assert.Equal("John Doe", result.Value!.OwnerName); + } + + [Fact] + public async Task GetAccountByIdAsync_WhenAccountDoesNotExist_ReturnsNull() + { + // Arrange + var accountId = 1; + _accountRepository.GetByIdAsync(accountId).Returns((Account?)null); + + // Act + var result = await _accountService.GetAccountByIdAsync(accountId); + + // Assert + Assert.False(result.Succeed); + Assert.Null(result.Value); + Assert.Equal("Account not found", result.Message); + } + + [Fact] + public async Task GetAllAccountsAsync_WhenAccountsExist_ReturnsListOfAccounts() + { + // Arrange + var accounts = new List + { + new Account { AccountId = 1, OwnerName = "Mobin Barfi" }, + new Account { AccountId = 2, OwnerName = "Mohammad Mohammadi" } + }; + + _accountRepository.GetAllAccounts().Returns(accounts); + + // Act + var result = await _accountService.GetAllAccountsAsync(); + + // Assert + Assert.True(result.Succeed); + Assert.Equal(2, result.Value.Count); + Assert.Equal("Mobin Barfi", result.Value[0].OwnerName); + Assert.Equal("Mohammad Mohammadi", result.Value[1].OwnerName); + } + + [Fact] + public async Task GetAllAccountsAsync_WhenExceptionIsThrown_ReturnsFail() + { + // Arrange + _accountRepository + .When(x => x.GetAllAccounts()) + .Do(x => throw new Exception("Database error")); + + // Act + var result = await _accountService.GetAllAccountsAsync(); + + // Assert + Assert.False(result.Succeed); + Assert.Equal("An unexpected error occurred: Database error", result.Message); + } +} \ No newline at end of file