From 7774a97ae605c6d824256ea7f4413d679c4ef060 Mon Sep 17 00:00:00 2001 From: "Jefferson L. da Silva" Date: Tue, 30 Dec 2025 16:46:24 -0300 Subject: [PATCH 1/5] Refactors and enhances tests Refactors the test suite by reorganizing and renaming test projects to improve clarity and maintainability. Also, integrates Testcontainers for more robust integration tests, ensuring reliable database interactions and adds Docker layer caching in CI to speed up workflow execution. --- .github/workflows/workflow-ci.yml | 10 +- Directory.Packages.props | 9 +- .../BearerSecuritySchemeTransformer.cs | 2 +- .../InvoiceReminder.ArchitectureTests.csproj | 4 - .../Interfaces/IUserRepository.cs | 1 - .../20250930210104_Initial_Create.cs | 413 ++++++------ .../EntitiesConfig/EmailAuthTokenConfig.cs | 2 +- .../EntitiesConfig/InvoiceConfig.cs | 2 +- .../EntitiesConfig/JobScheduleConfig.cs | 2 +- .../ScanEmailDefinitionConfig.cs | 2 +- .../Persistence/EntitiesConfig/UserConfig.cs | 2 +- InvoiceReminder.Data/Repository/UnitOfWork.cs | 30 +- .../Data/Repository/BaseRepositoryTests.cs | 349 ---------- .../ScanEmailDefinitionRepositoryTests.cs | 147 ----- .../Data/Repository/UnitOfWorkTests.cs | 147 ----- .../Data/Repository/UserRepositoryTests.cs | 109 ---- .../Data/ContainerSetup/DatabaseFixture.cs | 55 ++ .../BaseRepositoryIntegrationTests.cs | 612 +++++++++++++++++ ...mailAuthTokenRepositoryIntegrationTests.cs | 284 ++++++++ .../InvoiceRepositoryIntegrationTests.cs | 297 +++++++++ .../JobScheduleRepositoryIntegrationTests.cs | 309 +++++++++ ...ailDefinitionRepositoryIntegrationTests.cs | 613 ++++++++++++++++++ .../Repository/UnitOfWorkIntegrationTests.cs | 368 +++++++++++ .../UserRepositoryIntegrationTests.cs | 344 ++++++++++ .../InvoiceReminder.IntegrationTests.csproj | 40 ++ .../MSTestSettings.cs | 0 .../BearerSecuritySchemeTransformerTests.cs | 2 +- .../JwtBearerOptionsSetupTests.cs | 2 +- .../JwtOptionsSetupTests.cs | 2 +- .../AuthenticationSetup/LoginRequestTests.cs | 4 +- .../MockAuthenticationHandler.cs | 2 +- .../Endpoints/GoogleOAuthEndpointsTests.cs | 4 +- .../Endpoints/InvoiceEndpointsTests.cs | 4 +- .../Endpoints/JobScheduleEndPointsTests.cs | 4 +- .../Endpoints/LoginEndpointTests.cs | 4 +- .../ScanEmailDefinitionEndpointsTests.cs | 4 +- .../Endpoints/SendMessageEndpointsTests.cs | 4 +- .../Endpoints/UserEndpointsTests.cs | 4 +- .../Factories/CustomWebApplicationFactory.cs | 2 +- .../InvoiceReminder.UnitTests.API.csproj | 6 +- .../MSTestSettings.cs | 0 .../AppServices/BaseAppServiceTests.cs | 2 +- .../EmailAuthTokenAppServiceTests.cs | 2 +- .../AppServices/InvoiceAppServiceTests.cs | 2 +- .../AppServices/JobScheduleAppServiceTests.cs | 2 +- .../ScanEmailDefinitionAppServiceTests.cs | 2 +- .../AppServices/UserAppServiceTests.cs | 2 +- ...voiceReminder.UnitTests.Application.csproj | 6 +- .../MSTestSettings.cs | 0 .../EmailAuthTokenViewModelTests.cs | 4 +- .../ViewModels/InvoiceViewModelTests.cs | 4 +- .../ViewModels/JobScheduleViewmodelTests.cs | 4 +- .../ScanEmailDefinitionViewModelTests.cs | 4 +- .../ViewModels/UserViewModelTests.cs | 4 +- .../Entities/EmailAuthTokenTests.cs | 4 +- .../Entities/InvoiceTests.cs | 4 +- .../Entities/JobScheduleTests.cs | 4 +- .../Entities/ScanEmailDefinitionTests.cs | 4 +- .../Entities/UserTests.cs | 4 +- .../Extensions/EntityExtensionsTests.cs | 2 +- .../Extensions/UserExtensionsTests.cs | 2 +- .../Extensions/UserParameterTests.cs | 4 +- .../InvoiceReminder.UnitTests.Domain.csproj | 6 +- .../MSTestSettings.cs | 0 .../ConfigurationServiceTests.cs | 2 +- .../Configuration/TokenCryptoServiceTests.cs | 2 +- .../BarcodeReaderServiceTests.cs | 12 +- ...Reminder.UnitTests.ExternalServices.csproj | 8 +- .../MSTestSettings.cs | 0 .../SendMessage/SendMessageServiceTests.cs | 93 +-- .../Authentication/JwtObjectTests.cs | 4 +- .../Authentication/JwtOptionsTests.cs | 4 +- .../Authentication/JwtProviderTests.cs | 2 +- .../StringHashExtensionTests.cs | 2 +- .../Data/EntitiesConfig/InvoiceConfigTests.cs | 2 +- .../EntitiesConfig/JobScheduleConfigTests.cs | 2 +- .../ScanEmailDefinitionConfigTests.cs | 2 +- .../Data/EntitiesConfig/UserConfigTests.cs | 2 +- .../EmailAuthTokenRepositoryTests.cs | 45 ++ .../Data/Repository/InvoiceRepositoryTests.cs | 29 +- .../Repository/JobScheduleRepositoryTests.cs | 29 +- .../ScanEmailDefinitionRepositoryTests.cs | 45 ++ .../Data/Repository/UserRepositoryTests.cs | 45 ++ ...ceReminder.UnitTests.Infrastructure.csproj | 7 +- .../MSTestSettings.cs | 0 .../HostedService/QuartzHostedServiceTests.cs | 41 +- ...oiceReminder.UnitTests.JobScheduler.csproj | 8 +- .../JobSettings/CronJobTests.cs | 27 +- .../MSTestSettings.cs | 1 + .../Exception/PropertyTestException.cs | 2 +- .../Helpers/Guard.cs | 2 +- .../Helpers/TypeFactory.cs | 2 +- ...nvoiceReminder.UnitTests.SUT.Assets.csproj | 0 .../PropertyTester.cs | 6 +- InvoiceReminder.sln | 74 ++- global.json | 3 +- 96 files changed, 3537 insertions(+), 1239 deletions(-) delete mode 100644 InvoiceReminder.Infrastructure.UnitTests/Data/Repository/BaseRepositoryTests.cs delete mode 100644 InvoiceReminder.Infrastructure.UnitTests/Data/Repository/ScanEmailDefinitionRepositoryTests.cs delete mode 100644 InvoiceReminder.Infrastructure.UnitTests/Data/Repository/UnitOfWorkTests.cs delete mode 100644 InvoiceReminder.Infrastructure.UnitTests/Data/Repository/UserRepositoryTests.cs create mode 100644 InvoiceReminder.IntegrationTests/Data/ContainerSetup/DatabaseFixture.cs create mode 100644 InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs create mode 100644 InvoiceReminder.IntegrationTests/Data/Repository/EmailAuthTokenRepositoryIntegrationTests.cs create mode 100644 InvoiceReminder.IntegrationTests/Data/Repository/InvoiceRepositoryIntegrationTests.cs create mode 100644 InvoiceReminder.IntegrationTests/Data/Repository/JobScheduleRepositoryIntegrationTests.cs create mode 100644 InvoiceReminder.IntegrationTests/Data/Repository/ScanEmailDefinitionRepositoryIntegrationTests.cs create mode 100644 InvoiceReminder.IntegrationTests/Data/Repository/UnitOfWorkIntegrationTests.cs create mode 100644 InvoiceReminder.IntegrationTests/Data/Repository/UserRepositoryIntegrationTests.cs create mode 100644 InvoiceReminder.IntegrationTests/InvoiceReminder.IntegrationTests.csproj rename {InvoiceReminder.API.UnitTests => InvoiceReminder.IntegrationTests}/MSTestSettings.cs (100%) rename {InvoiceReminder.API.UnitTests => InvoiceReminder.UnitTests.API}/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs (98%) rename {InvoiceReminder.API.UnitTests => InvoiceReminder.UnitTests.API}/AuthenticationSetup/JwtBearerOptionsSetupTests.cs (98%) rename {InvoiceReminder.API.UnitTests => InvoiceReminder.UnitTests.API}/AuthenticationSetup/JwtOptionsSetupTests.cs (98%) rename {InvoiceReminder.API.UnitTests => InvoiceReminder.UnitTests.API}/AuthenticationSetup/LoginRequestTests.cs (72%) rename {InvoiceReminder.API.UnitTests => InvoiceReminder.UnitTests.API}/AuthenticationSetup/MockAuthenticationHandler.cs (92%) rename {InvoiceReminder.API.UnitTests => InvoiceReminder.UnitTests.API}/Endpoints/GoogleOAuthEndpointsTests.cs (99%) rename {InvoiceReminder.API.UnitTests => InvoiceReminder.UnitTests.API}/Endpoints/InvoiceEndpointsTests.cs (99%) rename {InvoiceReminder.API.UnitTests => InvoiceReminder.UnitTests.API}/Endpoints/JobScheduleEndPointsTests.cs (99%) rename {InvoiceReminder.API.UnitTests => InvoiceReminder.UnitTests.API}/Endpoints/LoginEndpointTests.cs (98%) rename {InvoiceReminder.API.UnitTests => InvoiceReminder.UnitTests.API}/Endpoints/ScanEmailDefinitionEndpointsTests.cs (99%) rename {InvoiceReminder.API.UnitTests => InvoiceReminder.UnitTests.API}/Endpoints/SendMessageEndpointsTests.cs (99%) rename {InvoiceReminder.API.UnitTests => InvoiceReminder.UnitTests.API}/Endpoints/UserEndpointsTests.cs (99%) rename {InvoiceReminder.API.UnitTests => InvoiceReminder.UnitTests.API}/Factories/CustomWebApplicationFactory.cs (97%) rename InvoiceReminder.API.UnitTests/InvoiceReminder.API.UnitTests.csproj => InvoiceReminder.UnitTests.API/InvoiceReminder.UnitTests.API.csproj (75%) rename {InvoiceReminder.Application.UnitTests => InvoiceReminder.UnitTests.API}/MSTestSettings.cs (100%) rename {InvoiceReminder.Application.UnitTests => InvoiceReminder.UnitTests.Application}/AppServices/BaseAppServiceTests.cs (99%) rename {InvoiceReminder.Application.UnitTests => InvoiceReminder.UnitTests.Application}/AppServices/EmailAuthTokenAppServiceTests.cs (98%) rename {InvoiceReminder.Application.UnitTests => InvoiceReminder.UnitTests.Application}/AppServices/InvoiceAppServiceTests.cs (98%) rename {InvoiceReminder.Application.UnitTests => InvoiceReminder.UnitTests.Application}/AppServices/JobScheduleAppServiceTests.cs (99%) rename {InvoiceReminder.Application.UnitTests => InvoiceReminder.UnitTests.Application}/AppServices/ScanEmailDefinitionAppServiceTests.cs (99%) rename {InvoiceReminder.Application.UnitTests => InvoiceReminder.UnitTests.Application}/AppServices/UserAppServiceTests.cs (98%) rename InvoiceReminder.Application.UnitTests/InvoiceReminder.Application.UnitTests.csproj => InvoiceReminder.UnitTests.Application/InvoiceReminder.UnitTests.Application.csproj (73%) rename {InvoiceReminder.DomainEntities.UnitTests => InvoiceReminder.UnitTests.Application}/MSTestSettings.cs (100%) rename {InvoiceReminder.Application.UnitTests => InvoiceReminder.UnitTests.Application}/ViewModels/EmailAuthTokenViewModelTests.cs (88%) rename {InvoiceReminder.Application.UnitTests => InvoiceReminder.UnitTests.Application}/ViewModels/InvoiceViewModelTests.cs (73%) rename {InvoiceReminder.Application.UnitTests => InvoiceReminder.UnitTests.Application}/ViewModels/JobScheduleViewmodelTests.cs (74%) rename {InvoiceReminder.Application.UnitTests => InvoiceReminder.UnitTests.Application}/ViewModels/ScanEmailDefinitionViewModelTests.cs (75%) rename {InvoiceReminder.Application.UnitTests => InvoiceReminder.UnitTests.Application}/ViewModels/UserViewModelTests.cs (81%) rename {InvoiceReminder.DomainEntities.UnitTests => InvoiceReminder.UnitTests.Domain}/Entities/EmailAuthTokenTests.cs (73%) rename {InvoiceReminder.DomainEntities.UnitTests => InvoiceReminder.UnitTests.Domain}/Entities/InvoiceTests.cs (72%) rename {InvoiceReminder.DomainEntities.UnitTests => InvoiceReminder.UnitTests.Domain}/Entities/JobScheduleTests.cs (72%) rename {InvoiceReminder.DomainEntities.UnitTests => InvoiceReminder.UnitTests.Domain}/Entities/ScanEmailDefinitionTests.cs (74%) rename {InvoiceReminder.DomainEntities.UnitTests => InvoiceReminder.UnitTests.Domain}/Entities/UserTests.cs (82%) rename {InvoiceReminder.DomainEntities.UnitTests => InvoiceReminder.UnitTests.Domain}/Extensions/EntityExtensionsTests.cs (99%) rename {InvoiceReminder.DomainEntities.UnitTests => InvoiceReminder.UnitTests.Domain}/Extensions/UserExtensionsTests.cs (99%) rename {InvoiceReminder.DomainEntities.UnitTests => InvoiceReminder.UnitTests.Domain}/Extensions/UserParameterTests.cs (73%) rename InvoiceReminder.DomainEntities.UnitTests/InvoiceReminder.DomainEntities.UnitTests.csproj => InvoiceReminder.UnitTests.Domain/InvoiceReminder.UnitTests.Domain.csproj (70%) rename {InvoiceReminder.ExternalServices.UnitTests => InvoiceReminder.UnitTests.Domain}/MSTestSettings.cs (100%) rename {InvoiceReminder.DomainEntities.UnitTests => InvoiceReminder.UnitTests.Domain}/Services/Configuration/ConfigurationServiceTests.cs (98%) rename {InvoiceReminder.DomainEntities.UnitTests => InvoiceReminder.UnitTests.Domain}/Services/Configuration/TokenCryptoServiceTests.cs (99%) rename {InvoiceReminder.ExternalServices.UnitTests => InvoiceReminder.UnitTests.ExternalServices}/BarcodeReader/BarcodeReaderServiceTests.cs (93%) rename InvoiceReminder.ExternalServices.UnitTests/InvoiceReminder.ExternalServices.UnitTests.csproj => InvoiceReminder.UnitTests.ExternalServices/InvoiceReminder.UnitTests.ExternalServices.csproj (79%) rename {InvoiceReminder.Infrastructure.UnitTests => InvoiceReminder.UnitTests.ExternalServices}/MSTestSettings.cs (100%) rename {InvoiceReminder.ExternalServices.UnitTests => InvoiceReminder.UnitTests.ExternalServices}/SendMessage/SendMessageServiceTests.cs (89%) rename {InvoiceReminder.Infrastructure.UnitTests => InvoiceReminder.UnitTests.Infrastructure}/Authentication/JwtObjectTests.cs (71%) rename {InvoiceReminder.Infrastructure.UnitTests => InvoiceReminder.UnitTests.Infrastructure}/Authentication/JwtOptionsTests.cs (71%) rename {InvoiceReminder.Infrastructure.UnitTests => InvoiceReminder.UnitTests.Infrastructure}/Authentication/JwtProviderTests.cs (97%) rename {InvoiceReminder.Infrastructure.UnitTests => InvoiceReminder.UnitTests.Infrastructure}/Authentication/StringHashExtensionTests.cs (95%) rename {InvoiceReminder.Infrastructure.UnitTests => InvoiceReminder.UnitTests.Infrastructure}/Data/EntitiesConfig/InvoiceConfigTests.cs (85%) rename {InvoiceReminder.Infrastructure.UnitTests => InvoiceReminder.UnitTests.Infrastructure}/Data/EntitiesConfig/JobScheduleConfigTests.cs (85%) rename {InvoiceReminder.Infrastructure.UnitTests => InvoiceReminder.UnitTests.Infrastructure}/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs (86%) rename {InvoiceReminder.Infrastructure.UnitTests => InvoiceReminder.UnitTests.Infrastructure}/Data/EntitiesConfig/UserConfigTests.cs (84%) create mode 100644 InvoiceReminder.UnitTests.Infrastructure/Data/Repository/EmailAuthTokenRepositoryTests.cs rename {InvoiceReminder.Infrastructure.UnitTests => InvoiceReminder.UnitTests.Infrastructure}/Data/Repository/InvoiceRepositoryTests.cs (58%) rename {InvoiceReminder.Infrastructure.UnitTests => InvoiceReminder.UnitTests.Infrastructure}/Data/Repository/JobScheduleRepositoryTests.cs (58%) create mode 100644 InvoiceReminder.UnitTests.Infrastructure/Data/Repository/ScanEmailDefinitionRepositoryTests.cs create mode 100644 InvoiceReminder.UnitTests.Infrastructure/Data/Repository/UserRepositoryTests.cs rename InvoiceReminder.Infrastructure.UnitTests/InvoiceReminder.Infrastructure.UnitTests.csproj => InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj (73%) rename {InvoiceReminder.JobScheduler.UnitTests => InvoiceReminder.UnitTests.Infrastructure}/MSTestSettings.cs (100%) rename {InvoiceReminder.JobScheduler.UnitTests => InvoiceReminder.UnitTests.JobScheduler}/HostedService/QuartzHostedServiceTests.cs (94%) rename InvoiceReminder.JobScheduler.UnitTests/InvoiceReminder.JobScheduler.UnitTests.csproj => InvoiceReminder.UnitTests.JobScheduler/InvoiceReminder.UnitTests.JobScheduler.csproj (79%) rename {InvoiceReminder.JobScheduler.UnitTests => InvoiceReminder.UnitTests.JobScheduler}/JobSettings/CronJobTests.cs (84%) create mode 100644 InvoiceReminder.UnitTests.JobScheduler/MSTestSettings.cs rename {InvoiceReminder.UnitTests.Assets => InvoiceReminder.UnitTests.SUT.Assets}/Exception/PropertyTestException.cs (87%) rename {InvoiceReminder.UnitTests.Assets => InvoiceReminder.UnitTests.SUT.Assets}/Helpers/Guard.cs (97%) rename {InvoiceReminder.UnitTests.Assets => InvoiceReminder.UnitTests.SUT.Assets}/Helpers/TypeFactory.cs (99%) rename InvoiceReminder.UnitTests.Assets/InvoiceReminder.UnitTests.Assets.csproj => InvoiceReminder.UnitTests.SUT.Assets/InvoiceReminder.UnitTests.SUT.Assets.csproj (100%) rename {InvoiceReminder.UnitTests.Assets => InvoiceReminder.UnitTests.SUT.Assets}/PropertyTester.cs (98%) diff --git a/.github/workflows/workflow-ci.yml b/.github/workflows/workflow-ci.yml index 3fbf196..e10b6d9 100644 --- a/.github/workflows/workflow-ci.yml +++ b/.github/workflows/workflow-ci.yml @@ -24,6 +24,14 @@ jobs: with: dotnet-version: 10.0.x + - name: Cache Docker layers + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-docker-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-docker- + - name: Restore Dependencies run: dotnet restore @@ -34,7 +42,7 @@ jobs: shell: bash run: | echo "🔍 Localizando Projetos de Teste..." - TEST_PROJECTS=$(find . -name "*.csproj" | grep -E "\.UnitTests|\.Tests|\.ArchitectureTests" | grep -v "Assets") + TEST_PROJECTS=$(find . -name "*.csproj" | grep -E "\.ArchitectureTests|\.IntegrationTests|\.UnitTests" | grep -v "Assets") if [ -z "$TEST_PROJECTS" ]; then echo "⚠️ Nenhum projeto de teste encontrado." diff --git a/Directory.Packages.props b/Directory.Packages.props index 024b8fd..288ffc3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,7 +11,6 @@ - @@ -26,7 +25,7 @@ - + @@ -49,7 +48,7 @@ - + @@ -58,8 +57,10 @@ + + - + diff --git a/InvoiceReminder.API/AuthenticationSetup/BearerSecuritySchemeTransformer.cs b/InvoiceReminder.API/AuthenticationSetup/BearerSecuritySchemeTransformer.cs index e3d1a10..2bcf61a 100644 --- a/InvoiceReminder.API/AuthenticationSetup/BearerSecuritySchemeTransformer.cs +++ b/InvoiceReminder.API/AuthenticationSetup/BearerSecuritySchemeTransformer.cs @@ -3,7 +3,7 @@ using Microsoft.OpenApi; using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("InvoiceReminder.API.UnitTests")] +[assembly: InternalsVisibleTo("InvoiceReminder.UnitTests.API")] namespace InvoiceReminder.API.AuthenticationSetup; diff --git a/InvoiceReminder.ArchitectureTests/InvoiceReminder.ArchitectureTests.csproj b/InvoiceReminder.ArchitectureTests/InvoiceReminder.ArchitectureTests.csproj index e00d40d..70853db 100644 --- a/InvoiceReminder.ArchitectureTests/InvoiceReminder.ArchitectureTests.csproj +++ b/InvoiceReminder.ArchitectureTests/InvoiceReminder.ArchitectureTests.csproj @@ -1,10 +1,6 @@  - true Exe false diff --git a/InvoiceReminder.Data/Interfaces/IUserRepository.cs b/InvoiceReminder.Data/Interfaces/IUserRepository.cs index 1347d21..c381d5c 100644 --- a/InvoiceReminder.Data/Interfaces/IUserRepository.cs +++ b/InvoiceReminder.Data/Interfaces/IUserRepository.cs @@ -5,5 +5,4 @@ namespace InvoiceReminder.Data.Interfaces; public interface IUserRepository : IBaseRepository { Task GetByEmailAsync(string value, CancellationToken cancellationToken = default); - new Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); } diff --git a/InvoiceReminder.Data/Migrations/20250930210104_Initial_Create.cs b/InvoiceReminder.Data/Migrations/20250930210104_Initial_Create.cs index e6809e9..1c5720d 100644 --- a/InvoiceReminder.Data/Migrations/20250930210104_Initial_Create.cs +++ b/InvoiceReminder.Data/Migrations/20250930210104_Initial_Create.cs @@ -3,217 +3,218 @@ #nullable disable -namespace InvoiceReminder.Data.Migrations +namespace InvoiceReminder.Data.Migrations; + +[ExcludeFromCodeCoverage] +/// +public partial class Initial_Create : Migration { - [ExcludeFromCodeCoverage] /// - public partial class Initial_Create : Migration + protected override void Up(MigrationBuilder migrationBuilder) { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - _ = migrationBuilder.EnsureSchema( - name: "invoice_reminder"); - - _ = migrationBuilder.CreateTable( - name: "user", - schema: "invoice_reminder", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - telegram_chat_id = table.Column(type: "bigint", nullable: false, defaultValue: 0L), - name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - email = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - password = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => _ = table.PrimaryKey("PK_user", x => x.id)); - - _ = migrationBuilder.InsertData( - table: "user", - schema: "invoice_reminder", - columns: ["id", "telegram_chat_id", "name", "email", "password", "created_at", "updated_at"], - values: new object[,] - { - { - "0d77a03d-ac35-480c-b409-a08133409c7c", - 0, - "John Doe", - "john.doe@notmail.com", - "8D969EEF6ECAD3C29A3A629280E686CF0C3F5D5A86AFF3CA12020C923ADC6C92", - new DateTime(2025, 05, 06, 0, 0, 0, DateTimeKind.Utc), - new DateTime(2025, 05, 06, 0, 0, 0, DateTimeKind.Utc) - }, - { - "59918776-f6c6-4def-93b1-95d7a7717942", - 0, - "Jane Doe", - "jane.doe@notmail.com", - "8D969EEF6ECAD3C29A3A629280E686CF0C3F5D5A86AFF3CA12020C923ADC6C92", - new DateTime(2025, 05, 06, 0, 0, 0, DateTimeKind.Utc), - new DateTime(2025, 05, 06, 0, 0, 0, DateTimeKind.Utc) - } - }); - - _ = migrationBuilder.CreateTable( - name: "email_auth_token", - schema: "invoice_reminder", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - user_id = table.Column(type: "uuid", nullable: false), - access_token = table.Column(type: "character varying(512)", maxLength: 512, nullable: false), - refresh_token = table.Column(type: "character varying(512)", maxLength: 512, nullable: false), - nonce_value = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), - token_provider = table.Column(type: "character varying(25)", maxLength: 25, nullable: false), - access_token_expiry = table.Column(type: "timestamp with time zone", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - _ = table.PrimaryKey("PK_email_auth_token", x => x.id); - _ = table.ForeignKey( - name: "FK_email_auth_token_user_user_id", - column: x => x.user_id, - principalSchema: "invoice_reminder", - principalTable: "user", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - _ = migrationBuilder.CreateTable( - name: "invoice", - schema: "invoice_reminder", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - user_id = table.Column(type: "uuid", nullable: false), - bank = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - beneficiary = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - amount = table.Column(type: "numeric", nullable: false), - barcode = table.Column(type: "text", nullable: false), - due_date = table.Column(type: "timestamp with time zone", nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - _ = table.PrimaryKey("PK_invoice", x => x.id); - _ = table.ForeignKey( - name: "FK_invoice_user_user_id", - column: x => x.user_id, - principalSchema: "invoice_reminder", - principalTable: "user", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - _ = migrationBuilder.CreateTable( - name: "job_schedule", - schema: "invoice_reminder", - columns: table => new + _ = migrationBuilder.Sql("CREATE SCHEMA IF NOT EXISTS invoice_reminder;"); + + _ = migrationBuilder.EnsureSchema( + name: "invoice_reminder"); + + _ = migrationBuilder.CreateTable( + name: "user", + schema: "invoice_reminder", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + telegram_chat_id = table.Column(type: "bigint", nullable: false, defaultValue: 0L), + name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + email = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + password = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => _ = table.PrimaryKey("PK_user", x => x.id)); + + _ = migrationBuilder.InsertData( + table: "user", + schema: "invoice_reminder", + columns: ["id", "telegram_chat_id", "name", "email", "password", "created_at", "updated_at"], + values: new object[,] + { { - id = table.Column(type: "uuid", nullable: false), - user_id = table.Column(type: "uuid", nullable: false), - cron_expression = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false) + "0d77a03d-ac35-480c-b409-a08133409c7c", + 0, + "John Doe", + "john.doe@notmail.com", + "8D969EEF6ECAD3C29A3A629280E686CF0C3F5D5A86AFF3CA12020C923ADC6C92", + new DateTime(2025, 05, 06, 0, 0, 0, DateTimeKind.Utc), + new DateTime(2025, 05, 06, 0, 0, 0, DateTimeKind.Utc) }, - constraints: table => { - _ = table.PrimaryKey("PK_job_schedule", x => x.id); - _ = table.ForeignKey( - name: "FK_job_schedule_user_user_id", - column: x => x.user_id, - principalSchema: "invoice_reminder", - principalTable: "user", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - _ = migrationBuilder.CreateTable( - name: "scan_email_definition", - schema: "invoice_reminder", - columns: table => new - { - id = table.Column(type: "uuid", nullable: false), - user_id = table.Column(type: "uuid", nullable: false), - invoice_type = table.Column(type: "integer", nullable: false), - beneficiary = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - description = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - sender_email_address = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - attachment_filename = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), - created_at = table.Column(type: "timestamp with time zone", nullable: false), - updated_at = table.Column(type: "timestamp with time zone", nullable: false) - }, - constraints: table => - { - _ = table.PrimaryKey("PK_scan_email_definition", x => x.id); - _ = table.ForeignKey( - name: "FK_scan_email_definition_user_user_id", - column: x => x.user_id, - principalSchema: "invoice_reminder", - principalTable: "user", - principalColumn: "id", - onDelete: ReferentialAction.Cascade); - }); - - _ = migrationBuilder.CreateIndex( - name: "IX_email_auth_token_user_id", - schema: "invoice_reminder", - table: "email_auth_token", - column: "user_id"); - - _ = migrationBuilder.CreateIndex( - name: "IX_invoice_user_id", - schema: "invoice_reminder", - table: "invoice", - column: "user_id"); - - _ = migrationBuilder.CreateIndex( - name: "IX_job_schedule_user_id", - schema: "invoice_reminder", - table: "job_schedule", - column: "user_id"); - - _ = migrationBuilder.CreateIndex( - name: "IX_scan_email_definition_user_id", - schema: "invoice_reminder", - table: "scan_email_definition", - column: "user_id"); - - _ = migrationBuilder.CreateIndex( - name: "idx_user_email", - schema: "invoice_reminder", - table: "user", - column: "email", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - _ = migrationBuilder.DropTable( - name: "email_auth_token", - schema: "invoice_reminder"); - - _ = migrationBuilder.DropTable( - name: "invoice", - schema: "invoice_reminder"); - - _ = migrationBuilder.DropTable( - name: "job_schedule", - schema: "invoice_reminder"); - - _ = migrationBuilder.DropTable( - name: "scan_email_definition", - schema: "invoice_reminder"); - - _ = migrationBuilder.DropTable( - name: "user", - schema: "invoice_reminder"); - } + "59918776-f6c6-4def-93b1-95d7a7717942", + 0, + "Jane Doe", + "jane.doe@notmail.com", + "8D969EEF6ECAD3C29A3A629280E686CF0C3F5D5A86AFF3CA12020C923ADC6C92", + new DateTime(2025, 05, 06, 0, 0, 0, DateTimeKind.Utc), + new DateTime(2025, 05, 06, 0, 0, 0, DateTimeKind.Utc) + } + }); + + _ = migrationBuilder.CreateTable( + name: "email_auth_token", + schema: "invoice_reminder", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + user_id = table.Column(type: "uuid", nullable: false), + access_token = table.Column(type: "character varying(512)", maxLength: 512, nullable: false), + refresh_token = table.Column(type: "character varying(512)", maxLength: 512, nullable: false), + nonce_value = table.Column(type: "character varying(64)", maxLength: 64, nullable: false), + token_provider = table.Column(type: "character varying(25)", maxLength: 25, nullable: false), + access_token_expiry = table.Column(type: "timestamp with time zone", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + _ = table.PrimaryKey("PK_email_auth_token", x => x.id); + _ = table.ForeignKey( + name: "FK_email_auth_token_user_user_id", + column: x => x.user_id, + principalSchema: "invoice_reminder", + principalTable: "user", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + _ = migrationBuilder.CreateTable( + name: "invoice", + schema: "invoice_reminder", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + user_id = table.Column(type: "uuid", nullable: false), + bank = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + beneficiary = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + amount = table.Column(type: "numeric", nullable: false), + barcode = table.Column(type: "text", nullable: false), + due_date = table.Column(type: "timestamp with time zone", nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + _ = table.PrimaryKey("PK_invoice", x => x.id); + _ = table.ForeignKey( + name: "FK_invoice_user_user_id", + column: x => x.user_id, + principalSchema: "invoice_reminder", + principalTable: "user", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + _ = migrationBuilder.CreateTable( + name: "job_schedule", + schema: "invoice_reminder", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + user_id = table.Column(type: "uuid", nullable: false), + cron_expression = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + _ = table.PrimaryKey("PK_job_schedule", x => x.id); + _ = table.ForeignKey( + name: "FK_job_schedule_user_user_id", + column: x => x.user_id, + principalSchema: "invoice_reminder", + principalTable: "user", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + _ = migrationBuilder.CreateTable( + name: "scan_email_definition", + schema: "invoice_reminder", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + user_id = table.Column(type: "uuid", nullable: false), + invoice_type = table.Column(type: "integer", nullable: false), + beneficiary = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + description = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + sender_email_address = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + attachment_filename = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + created_at = table.Column(type: "timestamp with time zone", nullable: false), + updated_at = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + _ = table.PrimaryKey("PK_scan_email_definition", x => x.id); + _ = table.ForeignKey( + name: "FK_scan_email_definition_user_user_id", + column: x => x.user_id, + principalSchema: "invoice_reminder", + principalTable: "user", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + _ = migrationBuilder.CreateIndex( + name: "IX_email_auth_token_user_id", + schema: "invoice_reminder", + table: "email_auth_token", + column: "user_id"); + + _ = migrationBuilder.CreateIndex( + name: "IX_invoice_user_id", + schema: "invoice_reminder", + table: "invoice", + column: "user_id"); + + _ = migrationBuilder.CreateIndex( + name: "IX_job_schedule_user_id", + schema: "invoice_reminder", + table: "job_schedule", + column: "user_id"); + + _ = migrationBuilder.CreateIndex( + name: "IX_scan_email_definition_user_id", + schema: "invoice_reminder", + table: "scan_email_definition", + column: "user_id"); + + _ = migrationBuilder.CreateIndex( + name: "idx_user_email", + schema: "invoice_reminder", + table: "user", + column: "email", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + _ = migrationBuilder.DropTable( + name: "email_auth_token", + schema: "invoice_reminder"); + + _ = migrationBuilder.DropTable( + name: "invoice", + schema: "invoice_reminder"); + + _ = migrationBuilder.DropTable( + name: "job_schedule", + schema: "invoice_reminder"); + + _ = migrationBuilder.DropTable( + name: "scan_email_definition", + schema: "invoice_reminder"); + + _ = migrationBuilder.DropTable( + name: "user", + schema: "invoice_reminder"); } } diff --git a/InvoiceReminder.Data/Persistence/EntitiesConfig/EmailAuthTokenConfig.cs b/InvoiceReminder.Data/Persistence/EntitiesConfig/EmailAuthTokenConfig.cs index 306b6a8..a46456d 100644 --- a/InvoiceReminder.Data/Persistence/EntitiesConfig/EmailAuthTokenConfig.cs +++ b/InvoiceReminder.Data/Persistence/EntitiesConfig/EmailAuthTokenConfig.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("InvoiceReminder.Infrastructure.UnitTests")] +[assembly: InternalsVisibleTo("InvoiceReminder.UnitTests.Infrastructure")] namespace InvoiceReminder.Data.Persistence.EntitiesConfig; diff --git a/InvoiceReminder.Data/Persistence/EntitiesConfig/InvoiceConfig.cs b/InvoiceReminder.Data/Persistence/EntitiesConfig/InvoiceConfig.cs index 67c85e7..e958023 100644 --- a/InvoiceReminder.Data/Persistence/EntitiesConfig/InvoiceConfig.cs +++ b/InvoiceReminder.Data/Persistence/EntitiesConfig/InvoiceConfig.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("InvoiceReminder.Infrastructure.UnitTests")] +[assembly: InternalsVisibleTo("InvoiceReminder.UnitTests.Infrastructure")] namespace InvoiceReminder.Data.Persistence.EntitiesConfig; diff --git a/InvoiceReminder.Data/Persistence/EntitiesConfig/JobScheduleConfig.cs b/InvoiceReminder.Data/Persistence/EntitiesConfig/JobScheduleConfig.cs index 290c756..daf8ae5 100644 --- a/InvoiceReminder.Data/Persistence/EntitiesConfig/JobScheduleConfig.cs +++ b/InvoiceReminder.Data/Persistence/EntitiesConfig/JobScheduleConfig.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("InvoiceReminder.Infrastructure.UnitTests")] +[assembly: InternalsVisibleTo("InvoiceReminder.UnitTests.Infrastructure")] namespace InvoiceReminder.Data.Persistence.EntitiesConfig; diff --git a/InvoiceReminder.Data/Persistence/EntitiesConfig/ScanEmailDefinitionConfig.cs b/InvoiceReminder.Data/Persistence/EntitiesConfig/ScanEmailDefinitionConfig.cs index 4944e3d..90588b8 100644 --- a/InvoiceReminder.Data/Persistence/EntitiesConfig/ScanEmailDefinitionConfig.cs +++ b/InvoiceReminder.Data/Persistence/EntitiesConfig/ScanEmailDefinitionConfig.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("InvoiceReminder.Infrastructure.UnitTests")] +[assembly: InternalsVisibleTo("InvoiceReminder.UnitTests.Infrastructure")] namespace InvoiceReminder.Data.Persistence.EntitiesConfig; diff --git a/InvoiceReminder.Data/Persistence/EntitiesConfig/UserConfig.cs b/InvoiceReminder.Data/Persistence/EntitiesConfig/UserConfig.cs index 8db1858..d870d57 100644 --- a/InvoiceReminder.Data/Persistence/EntitiesConfig/UserConfig.cs +++ b/InvoiceReminder.Data/Persistence/EntitiesConfig/UserConfig.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("InvoiceReminder.Infrastructure.UnitTests")] +[assembly: InternalsVisibleTo("InvoiceReminder.UnitTests.Infrastructure")] namespace InvoiceReminder.Data.Persistence.EntitiesConfig; diff --git a/InvoiceReminder.Data/Repository/UnitOfWork.cs b/InvoiceReminder.Data/Repository/UnitOfWork.cs index 3091e9f..3cb4fde 100644 --- a/InvoiceReminder.Data/Repository/UnitOfWork.cs +++ b/InvoiceReminder.Data/Repository/UnitOfWork.cs @@ -2,6 +2,7 @@ using InvoiceReminder.Data.Interfaces; using InvoiceReminder.Data.Persistence; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.Logging; using System.Data; using System.Data.Common; @@ -14,6 +15,7 @@ public class UnitOfWork : IUnitOfWork private readonly CoreDbContext _context; private readonly DbConnection _connection; private readonly ILogger _logger; + private const string LogExceptionMessage = "{ContextualInfo} - Exception: {Message}"; public UnitOfWork(CoreDbContext context, ILogger logger) { @@ -24,29 +26,47 @@ public UnitOfWork(CoreDbContext context, ILogger logger) public async Task SaveChangesAsync(CancellationToken cancellationToken = default) { - await OpenConnection(cancellationToken); - - using var transaction = await _connection.BeginTransactionAsync(cancellationToken); + IDbContextTransaction transaction = default; try { + await OpenConnection(cancellationToken); + + transaction = await _context.Database.BeginTransactionAsync(cancellationToken); + _ = await _context.SaveChangesAsync(cancellationToken); await transaction.CommitAsync(cancellationToken); } + catch (OperationCanceledException ex) when (cancellationToken.IsCancellationRequested) + { + var method = $"{nameof(UnitOfWork)}.{nameof(SaveChangesAsync)}"; + var contextualInfo = $"Method {method} execution was interrupted by a CancellationToken Request..."; + + if (_logger.IsEnabled(LogLevel.Warning)) + { + _logger.LogWarning(ex, LogExceptionMessage, contextualInfo, ex.Message); + } + + throw new OperationCanceledException(contextualInfo, ex, cancellationToken); + } catch (Exception ex) { + var method = $"{nameof(UnitOfWork)}.{nameof(SaveChangesAsync)}"; + var contextualInfo = $"Exception raised. Rolling back changes >> {method}(...)"; + if (_logger.IsEnabled(LogLevel.Error)) { - _logger.LogError(ex, "{Message}", ex.Message); + _logger.LogError(ex, LogExceptionMessage, contextualInfo, ex.Message); } await transaction.RollbackAsync(cancellationToken); - throw new DataLayerException($"Exception raised while saving changes: {ex.Message}", ex); + throw new DataLayerException(contextualInfo, ex); } finally { + transaction?.Dispose(); await CloseConnection(); } } diff --git a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/BaseRepositoryTests.cs b/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/BaseRepositoryTests.cs deleted file mode 100644 index a9ca8c5..0000000 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/BaseRepositoryTests.cs +++ /dev/null @@ -1,349 +0,0 @@ -using Bogus; -using InvoiceReminder.Data.Repository; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using NSubstitute; -using Shouldly; -using System.Linq.Expressions; - -namespace InvoiceReminder.Infrastructure.UnitTests.Data.Repository; - -[TestClass] -public sealed class BaseRepositoryTests -{ - private readonly SqliteConnection _connection; - private readonly DbContextOptions _contextOptions; - private Faker _testEntityFaker; - - public TestContext TestContext { get; set; } - - public BaseRepositoryTests() - { - _connection = new SqliteConnection("Filename=:memory:"); - _connection.Open(); - - _contextOptions = new DbContextOptionsBuilder() - .UseSqlite(_connection) - .Options; - } - - [TestInitialize] - public async Task Setup() - { - InitializeFaker(); - using var context = new TestDbContext(_contextOptions); - _ = await context.Database.EnsureCreatedAsync(TestContext.CancellationToken); - } - - [TestCleanup] - public void TearDown() - { - _connection.Dispose(); - } - - private void InitializeFaker() - { - _testEntityFaker = new Faker() - .RuleFor(e => e.Id, _ => Guid.NewGuid()) - .RuleFor(e => e.Name, f => f.Lorem.Word()); - } - - private TestDbContext CreateContext() - { - return new(_contextOptions); - } - - [TestMethod] - public async Task AddAsync_Should_AddEntityToDatabase() - { - // Arrange - var entity = _testEntityFaker.Generate(); - using var context = CreateContext(); - var repository = new BaseRepository(context); - - // Act - var addedEntity = await repository.AddAsync(entity, TestContext.CancellationToken); - _ = await context.SaveChangesAsync(TestContext.CancellationToken); - - // Assert - addedEntity.ShouldBeSameAs(entity); - context.TestEntities.ShouldContain(entity); - } - - [TestMethod] - public async Task BulkInsertAsync_Should_AddMultipleEntitiesToDatabaseWithTimestamps() - { - // Arrange - var entities = _testEntityFaker.Generate(2); - - using var context = CreateContext(); - var repository = new BaseRepository(context); - - // Act - var count = await repository.BulkInsertAsync(entities, TestContext.CancellationToken); - _ = await context.SaveChangesAsync(TestContext.CancellationToken); - var total = await context.TestEntities.CountAsync(TestContext.CancellationToken); - - // Assert - count.ShouldBe(entities.Count); - - context.ShouldSatisfyAllConditions(() => - { - total.ShouldBe(entities.Count); - context.TestEntities.ShouldAllBe(e => e.CreatedAt.HasValue && e.UpdatedAt.HasValue); - }); - } - - [TestMethod] - public async Task Remove_Should_RemoveExistingEntityFromDatabase() - { - // Arrange - var entity = _testEntityFaker.Generate(); - using var context = CreateContext(); - var repository = new BaseRepository(context); - _ = await repository.AddAsync(entity, TestContext.CancellationToken); - _ = await context.SaveChangesAsync(TestContext.CancellationToken); - - // Act - repository.Remove(entity); - _ = await context.SaveChangesAsync(TestContext.CancellationToken); - - // Assert - context.TestEntities.ShouldNotContain(entity); - } - - [TestMethod] - public async Task Remove_Should_AttachAndRemoveDetachedEntity() - { - // Arrange - var entity = _testEntityFaker.Generate(); - using var context = CreateContext(); - var repository = new BaseRepository(context); - - _ = await repository.AddAsync(entity, TestContext.CancellationToken); - _ = await context.SaveChangesAsync(TestContext.CancellationToken); - - _ = context.Attach(entity); - context.Entry(entity).State = EntityState.Detached; - - // Act - repository.Remove(entity); - _ = await context.SaveChangesAsync(TestContext.CancellationToken); - - // Assert - context.TestEntities.ShouldNotContain(entity); - } - - [TestMethod] - public async Task GetByIdAsync_Should_ReturnEntityById() - { - // Arrange - var entity = _testEntityFaker.Generate(); - using var context = CreateContext(); - var repository = new BaseRepository(context); - - _ = await repository.AddAsync(entity, TestContext.CancellationToken); - _ = await context.SaveChangesAsync(TestContext.CancellationToken); - - // Act - var retrievedEntity = await repository.GetByIdAsync(entity.Id, TestContext.CancellationToken); - - // Assert - retrievedEntity.ShouldSatisfyAllConditions(() => - { - _ = retrievedEntity.ShouldNotBeNull(); - retrievedEntity.Id.ShouldBe(entity.Id); - retrievedEntity.Name.ShouldBe(entity.Name); - }); - } - - [TestMethod] - public async Task GetByIdAsync_Should_ReturnNullWhenEntityNotFound() - { - // Arrange - var nonExistingId = Guid.NewGuid(); - using var context = CreateContext(); - var repository = new BaseRepository(context); - - // Act - var retrievedEntity = await repository.GetByIdAsync(nonExistingId, TestContext.CancellationToken); - - // Assert - retrievedEntity.ShouldBeNull(); - } - - [TestMethod] - public async Task GetAll_Should_ReturnAllEntities() - { - // Arrange - var entities = _testEntityFaker.Generate(2); - - using var context = CreateContext(); - var repository = new BaseRepository(context); - - _ = await repository.BulkInsertAsync(entities, TestContext.CancellationToken); - _ = await context.SaveChangesAsync(TestContext.CancellationToken); - - // Act - var allEntities = repository.GetAll().ToList(); - - // Assert - allEntities.ShouldSatisfyAllConditions(() => - { - _ = allEntities.ShouldNotBeNull(); - allEntities.Count.ShouldBe(entities.Count); - allEntities.ShouldBeEquivalentTo(entities); - }); - } - - [TestMethod] - public async Task Update_Should_UpdateExistingEntity() - { - // Arrange - var entity = _testEntityFaker.Generate(); - using var context = CreateContext(); - var repository = new BaseRepository(context); - - _ = await repository.AddAsync(entity, TestContext.CancellationToken); - _ = await context.SaveChangesAsync(TestContext.CancellationToken); - - entity.Name = _testEntityFaker.Generate().Name; - - // Act - var updatedEntity = repository.Update(entity); - _ = await context.SaveChangesAsync(TestContext.CancellationToken); - var retrievedEntity = await repository.GetByIdAsync(entity.Id, TestContext.CancellationToken); - - // Assert - updatedEntity.ShouldBeSameAs(entity); - - retrievedEntity.ShouldSatisfyAllConditions(() => - { - _ = retrievedEntity.ShouldNotBeNull(); - retrievedEntity.Name.ShouldBe(entity.Name); - }); - } - - [TestMethod] - public async Task Update_Should_AttachAndUpdateDetachedEntity() - { - // Arrange - var entity = _testEntityFaker.Generate(); - using var context = CreateContext(); - var repository = new BaseRepository(context); - - _ = await repository.AddAsync(entity, TestContext.CancellationToken); - _ = await context.SaveChangesAsync(TestContext.CancellationToken); - - _ = context.Attach(entity); - context.Entry(entity).State = EntityState.Detached; - - entity.Name = _testEntityFaker.Generate().Name; - - // Act - var updatedEntity = repository.Update(entity); - _ = await context.SaveChangesAsync(TestContext.CancellationToken); - var retrievedEntity = await repository.GetByIdAsync(entity.Id, TestContext.CancellationToken); - - // Assert - updatedEntity.ShouldBeSameAs(entity); - - retrievedEntity.ShouldSatisfyAllConditions(() => - { - _ = retrievedEntity.ShouldNotBeNull(); - retrievedEntity.Name.ShouldBe(entity.Name); - }); - } - - [TestMethod] - public async Task Where_Should_ReturnEntitiesMatchingPredicate() - { - // Arrange - var entity1 = _testEntityFaker.Generate(); - entity1.Name = "Test1"; - - var entity2 = _testEntityFaker.Generate(); - entity2.Name = "AnotherTest"; - - var entity3 = _testEntityFaker.Generate(); - entity3.Name = "Test2"; - - var entities = new List { entity1, entity2, entity3 }; - - using var context = CreateContext(); - context.TestEntities.AddRange(entities); - _ = await context.SaveChangesAsync(TestContext.CancellationToken); - var repository = new BaseRepository(context); - - // Act - Expression> predicate = e => e.Name.StartsWith("Test"); - var filteredEntities = repository.Where(predicate).ToList(); - - // Assert - filteredEntities.ShouldSatisfyAllConditions(() => - { - filteredEntities.Count.ShouldBe(2); - filteredEntities.ShouldContain(entities[0]); - filteredEntities.ShouldNotContain(entities[1]); - filteredEntities.ShouldContain(entities[2]); - }); - } - - [TestMethod] - public void Dispose_Should_DisposeDbContext() - { - // Arrange - var mockConnection = Substitute.For(); - var options = new DbContextOptionsBuilder() - .UseSqlite(mockConnection) - .Options; - var mockDbContext = Substitute.For(options); - var repository = new BaseRepository(mockDbContext); - - // Act - repository.Dispose(); - - // Assert - mockDbContext.Received(1).Dispose(); - } - - [TestMethod] - public void Dispose_CalledMultipleTimes_Should_DisposeDbContextOnlyOnce() - { - // Arrange - var mockConnection = Substitute.For(); - var options = new DbContextOptionsBuilder() - .UseSqlite(mockConnection) - .Options; - var mockDbContext = Substitute.For(options); - var repository = new BaseRepository(mockDbContext); - - // Act - repository.Dispose(); - repository.Dispose(); - - // Assert - mockDbContext.Received(1).Dispose(); - } -} - -public class TestDbContext : DbContext -{ - public DbSet TestEntities { get; set; } - - public TestDbContext(DbContextOptions options) : base(options) { } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - _ = modelBuilder.Entity().HasKey(e => e.Id); - base.OnModelCreating(modelBuilder); - } -} - -public class TestEntity -{ - public Guid Id { get; set; } - public string Name { get; set; } - public DateTime? CreatedAt { get; set; } - public DateTime? UpdatedAt { get; set; } -} diff --git a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/ScanEmailDefinitionRepositoryTests.cs b/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/ScanEmailDefinitionRepositoryTests.cs deleted file mode 100644 index 214f7b7..0000000 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/ScanEmailDefinitionRepositoryTests.cs +++ /dev/null @@ -1,147 +0,0 @@ -using Bogus; -using InvoiceReminder.Data.Interfaces; -using InvoiceReminder.Data.Persistence; -using InvoiceReminder.Data.Repository; -using InvoiceReminder.Domain.Entities; -using InvoiceReminder.Domain.Enums; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using NSubstitute; -using Shouldly; - -namespace InvoiceReminder.Infrastructure.UnitTests.Data.Repository; - -[TestClass] -public sealed class ScanEmailDefinitionRepositoryTests -{ - private readonly CoreDbContext _dbContext; - private readonly ILogger _logger; - private readonly IScanEmailDefinitionRepository _repository; - - public TestContext TestContext { get; set; } - - public ScanEmailDefinitionRepositoryTests() - { - var options = new DbContextOptionsBuilder() - .UseNpgsql(default) - .Options; - - _dbContext = Substitute.ForPartsOf(options); - _logger = Substitute.For>(); - _repository = Substitute.For(); - } - - private static Faker CreateFaker(Action> configure = null) - { - var faker = new Faker() - .RuleFor(s => s.Id, _ => Guid.NewGuid()) - .RuleFor(s => s.UserId, _ => Guid.NewGuid()) - .RuleFor(s => s.InvoiceType, f => f.PickRandom()) - .RuleFor(s => s.Beneficiary, f => f.Company.CompanyName()) - .RuleFor(s => s.Description, f => f.Lorem.Sentence()) - .RuleFor(s => s.SenderEmailAddress, f => f.Internet.Email()) - .RuleFor(s => s.AttachmentFileName, f => f.System.FileName()); - - configure?.Invoke(faker); - - return faker; - } - - [TestMethod] - public void ScanEmailDefinitionRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() - { - // Arrange && Act - var repository = new ScanEmailDefinitionRepository(_dbContext, _logger); - - // Assert - repository.ShouldSatisfyAllConditions(() => - { - _ = repository.ShouldBeAssignableTo(); - _ = repository.ShouldBeAssignableTo>(); - _ = repository.ShouldBeAssignableTo>(); - - _ = repository.ShouldNotBeNull(); - _ = repository.ShouldBeOfType(); - }); - } - - [TestMethod] - public async Task GetBySenderBeneficiaryAsync_ShouldReturnScanEmailDefinition_WhenScanEmailDefinitionExists() - { - // Arrange - var userId = Guid.NewGuid(); - var beneficiary = new Faker().Company.CompanyName(); - var scanEmailDefinition = CreateFaker() - .RuleFor(s => s.UserId, _ => userId) - .RuleFor(s => s.Beneficiary, _ => beneficiary) - .Generate(); - - _ = _repository.GetBySenderBeneficiaryAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(Task.FromResult(scanEmailDefinition)); - - // Act - var result = await _repository.GetBySenderBeneficiaryAsync(beneficiary, userId, TestContext.CancellationToken); - - // Assert - result.ShouldSatisfyAllConditions(() => - { - _ = result.ShouldNotBeNull(); - _ = result.ShouldBeOfType(); - result.UserId.ShouldBe(userId); - result.Beneficiary.ShouldBe(beneficiary); - }); - } - - [TestMethod] - public async Task GetBySenderEmailAsync_ShouldReturnScanEmailDefinition_WhenScanEmailDefinitionExists() - { - // Arrange - var userId = Guid.NewGuid(); - var senderEmail = new Faker().Internet.Email(); - var scanEmailDefinition = CreateFaker() - .RuleFor(s => s.UserId, _ => userId) - .RuleFor(s => s.SenderEmailAddress, _ => senderEmail) - .Generate(); - - _ = _repository.GetBySenderEmailAddressAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(Task.FromResult(scanEmailDefinition)); - - // Act - var result = await _repository.GetBySenderEmailAddressAsync(senderEmail, userId, TestContext.CancellationToken); - - // Assert - result.ShouldSatisfyAllConditions(() => - { - _ = result.ShouldNotBeNull(); - _ = result.ShouldBeOfType(); - result.UserId.ShouldBe(userId); - result.SenderEmailAddress.ShouldBe(senderEmail); - }); - } - - [TestMethod] - public async Task GetByUserIdAsync_ShouldReturnScanEmailDefinition_WhenScanEmailDefinitionExists() - { - // Arrange - var userId = Guid.NewGuid(); - var collection = CreateFaker() - .RuleFor(s => s.UserId, _ => userId) - .Generate(2); - - _ = _repository.GetByUserIdAsync(Arg.Any(), Arg.Any()) - .Returns(Task.FromResult>(collection)); - - // Act - var result = await _repository.GetByUserIdAsync(userId, TestContext.CancellationToken); - - // Assert - result.ShouldSatisfyAllConditions(() => - { - _ = result.ShouldNotBeNull(); - _ = result.ShouldBeOfType>(); - result.ShouldNotBeEmpty(); - result.ShouldContain(x => x.UserId == userId); - result.Count().ShouldBe(2); - }); - } -} diff --git a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/UnitOfWorkTests.cs b/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/UnitOfWorkTests.cs deleted file mode 100644 index a74c583..0000000 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/UnitOfWorkTests.cs +++ /dev/null @@ -1,147 +0,0 @@ -using InvoiceReminder.Data.Exceptions; -using InvoiceReminder.Data.Persistence; -using InvoiceReminder.Data.Repository; -using InvoiceReminder.Domain.Entities; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using NSubstitute; -using Shouldly; -using System.Data; - -namespace InvoiceReminder.Infrastructure.UnitTests.Data.Repository -{ - [TestClass] - public sealed class UnitOfWorkTests - { - private readonly SqliteConnection _connection; - private readonly DbContextOptions _contextOptions; - private readonly ILogger _logger; - - public TestContext TestContext { get; set; } - - public UnitOfWorkTests() - { - _connection = new SqliteConnection("Filename=:memory:"); - - _contextOptions = new DbContextOptionsBuilder() - .UseSqlite(_connection) - .Options; - - _logger = Substitute.For>(); - } - - [TestInitialize] - public void Setup() - { - _connection.Open(); - } - - [TestCleanup] - public void TearDown() - { - _connection.Dispose(); - } - - [TestMethod] - public async Task SaveChangesAsync_Should_OpenConnection_BeginTransaction_SaveChanges_CommitTransaction_CloseConnection() - { - // Arrange - using var context = CreateContext(); - _ = await context.Database.EnsureCreatedAsync(TestContext.CancellationToken); - var unitOfWork = CreateUnitOfWork(context); - - // Act - await unitOfWork.SaveChangesAsync(TestContext.CancellationToken); - - // Assert - context.Database.GetDbConnection().State.ShouldBe(ConnectionState.Closed); - } - - [TestMethod] - public async Task SaveChangesAsync_Should_RollbackTransaction_LogError_AndThrowDataLayerException_OnException() - { - // Arrange - using var context = CreateContext(); - _ = await context.Database.EnsureCreatedAsync(TestContext.CancellationToken); - var unitOfWork = CreateUnitOfWork(context); - - _ = context.Users.Add(new User { Id = Guid.NewGuid() }); - _ = _logger.IsEnabled(Arg.Any()).Returns(true); - - // Act - var dataLayerException = await Should.ThrowAsync( - async () => await unitOfWork.SaveChangesAsync(TestContext.CancellationToken) - ); - - // Assert - context.Database.GetDbConnection().State.ShouldBe(ConnectionState.Closed); - - _ = dataLayerException.ShouldNotBeNull(); - _ = dataLayerException.InnerException.ShouldBeOfType(); - dataLayerException.Message.ShouldContain("Exception raised while saving changes"); - - var eventId = Arg.Any(); - var state = Arg.Any(); - var exception = Arg.Any(); - var formatter = Arg.Any>(); - - _logger.Received(1).Log(LogLevel.Error, eventId, state, exception, formatter); - } - - [TestMethod] - public async Task SaveChangesAsync_Should_HandleConnectionAlreadyOpen() - { - // Arrange - using var context = CreateContext(); - _ = await context.Database.EnsureCreatedAsync(TestContext.CancellationToken); - await context.Database.OpenConnectionAsync(TestContext.CancellationToken); - var unitOfWork = CreateUnitOfWork(context); - - // Act - await unitOfWork.SaveChangesAsync(TestContext.CancellationToken); - - // Assert - context.Database.GetDbConnection().State.ShouldBe(ConnectionState.Closed); - } - - [TestMethod] - public void Dispose_Should_DisposeDbContext() - { - // Arrange - using var context = CreateContext(); - var unitOfWork = CreateUnitOfWork(context); - - // Act - unitOfWork.Dispose(); - - // Assert - _ = Should.Throw(() => context.Users.FirstOrDefault()); - } - - [TestMethod] - public void Dispose_CalledMultipleTimes_Should_NotThrowException() - { - // Arrange - using var context = CreateContext(); - var unitOfWork = CreateUnitOfWork(context); - - // Act - unitOfWork.Dispose(); - unitOfWork.Dispose(); - - // Assert - Should.NotThrow(() => { }); - } - - private CoreDbContext CreateContext() - { - return new(_contextOptions); - } - - private UnitOfWork CreateUnitOfWork(CoreDbContext context) - { - return new UnitOfWork(context, _logger); - } - } -} diff --git a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/UserRepositoryTests.cs b/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/UserRepositoryTests.cs deleted file mode 100644 index 46ec4da..0000000 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/UserRepositoryTests.cs +++ /dev/null @@ -1,109 +0,0 @@ -using Bogus; -using InvoiceReminder.Data.Interfaces; -using InvoiceReminder.Data.Persistence; -using InvoiceReminder.Data.Repository; -using InvoiceReminder.Domain.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using NSubstitute; -using Shouldly; - -namespace InvoiceReminder.Infrastructure.UnitTests.Data.Repository; - -[TestClass] -public sealed class UserRepositoryTests -{ - private readonly CoreDbContext _dbContext; - private readonly ILogger _logger; - private readonly IUserRepository _repository; - - public TestContext TestContext { get; set; } - - public UserRepositoryTests() - { - var options = new DbContextOptionsBuilder() - .UseNpgsql(default) - .Options; - - _dbContext = Substitute.ForPartsOf(options); - _logger = Substitute.For>(); - _repository = Substitute.For(); - } - - private static Faker CreateFaker() - { - return new Faker() - .RuleFor(u => u.Id, _ => Guid.NewGuid()) - .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) - .RuleFor(u => u.Name, f => f.Person.FullName) - .RuleFor(u => u.Email, f => f.Internet.Email()) - .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); - } - - [TestMethod] - public void UserRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() - { - // Arrange && Act - var repository = new UserRepository(_dbContext, _logger); - - // Assert - repository.ShouldSatisfyAllConditions(() => - { - _ = repository.ShouldBeAssignableTo(); - _ = repository.ShouldBeAssignableTo>(); - _ = repository.ShouldBeAssignableTo>(); - - _ = repository.ShouldNotBeNull(); - _ = repository.ShouldBeOfType(); - }); - } - - [TestMethod] - public async Task GetByEmailAsync_ShouldReturnUser_WhenUserExists() - { - // Arrange - var email = new Faker().Internet.Email(); - var user = CreateFaker() - .RuleFor(u => u.Email, _ => email) - .Generate(); - - _ = _repository.GetByEmailAsync(Arg.Any(), Arg.Any()) - .Returns(Task.FromResult(user)); - - // Act - var result = await _repository.GetByEmailAsync(email, TestContext.CancellationToken); - - // Assert - result.ShouldSatisfyAllConditions(() => - { - _ = result.ShouldNotBeNull(); - _ = result.ShouldBeOfType(); - result.Email.ShouldBe(email); - result.Id.ShouldNotBe(Guid.Empty); - }); - } - - [TestMethod] - public async Task GetByIdAsync_ShouldReturnUser_WhenUserExists() - { - // Arrange - var userId = Guid.NewGuid(); - var user = CreateFaker() - .RuleFor(u => u.Id, _ => userId) - .Generate(); - - _ = _repository.GetByIdAsync(Arg.Any(), Arg.Any()) - .Returns(Task.FromResult(user)); - - // Act - var result = await _repository.GetByIdAsync(userId, TestContext.CancellationToken); - - // Assert - result.ShouldSatisfyAllConditions(() => - { - _ = result.ShouldNotBeNull(); - _ = result.ShouldBeOfType(); - result.Id.ShouldBe(userId); - }); - } -} diff --git a/InvoiceReminder.IntegrationTests/Data/ContainerSetup/DatabaseFixture.cs b/InvoiceReminder.IntegrationTests/Data/ContainerSetup/DatabaseFixture.cs new file mode 100644 index 0000000..c277e46 --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/ContainerSetup/DatabaseFixture.cs @@ -0,0 +1,55 @@ +using InvoiceReminder.Data.Persistence; +using Microsoft.EntityFrameworkCore; +using Npgsql; +using Testcontainers.PostgreSql; + +namespace InvoiceReminder.IntegrationTests.Data.ContainerSetup; + +[TestClass] +public static class DatabaseFixture +{ + private static PostgreSqlContainer _dbContainer; + + public static string ConnectionString => _dbContainer.GetConnectionString(); + + [AssemblyInitialize] + public static async Task AssemblyInit(TestContext context) + { + _dbContainer = new PostgreSqlBuilder() + .WithDatabase("postgres") + .WithUsername("postgres") + .WithPassword("Fake!Password#123") + .WithImage("postgres:15-alpine") + .Build(); + + await _dbContainer.StartAsync(context.CancellationToken); + await CreateSchema(context); + await RunMigrations(context); + } + + [AssemblyCleanup] + public static async Task AssemblyCleanup() + { + await _dbContainer.DisposeAsync(); + } + + private static async Task CreateSchema(TestContext context) + { + await using var conn = new NpgsqlConnection(ConnectionString); + await conn.OpenAsync(context.CancellationToken); + + await using var cmd = new NpgsqlCommand("CREATE SCHEMA IF NOT EXISTS invoice_reminder;", conn); + _ = await cmd.ExecuteNonQueryAsync(context.CancellationToken); + } + + private static async Task RunMigrations(TestContext context) + { + var options = new DbContextOptionsBuilder() + .UseNpgsql(ConnectionString) + .Options; + + using var dbContext = new CoreDbContext(options); + + await dbContext.Database.MigrateAsync(context.CancellationToken); + } +} diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs new file mode 100644 index 0000000..bb9711b --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs @@ -0,0 +1,612 @@ +using Bogus; +using InvoiceReminder.Data.Persistence; +using InvoiceReminder.Data.Repository; +using InvoiceReminder.Domain.Entities; +using InvoiceReminder.IntegrationTests.Data.ContainerSetup; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Shouldly; + +namespace InvoiceReminder.IntegrationTests.Data.Repository; + +[TestClass] +public sealed class BaseRepositoryIntegrationTests +{ + private CoreDbContext _dbContext; + private BaseRepository _userRepository; + private BaseRepository _invoiceRepository; + private UnitOfWork _unitOfWork; + + public TestContext TestContext { get; set; } + + [TestInitialize] + public void TestInitialize() + { + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + _dbContext = new CoreDbContext(options); + _userRepository = new BaseRepository(_dbContext); + _invoiceRepository = new BaseRepository(_dbContext); + _unitOfWork = new UnitOfWork(_dbContext, Substitute.For>()); + } + + [TestCleanup] + public void TestCleanup() + { + _unitOfWork?.Dispose(); + _userRepository?.Dispose(); + _invoiceRepository?.Dispose(); + _dbContext?.Dispose(); + } + + #region Helper Methods + + private static Faker UserFaker() + { + return new Faker() + .RuleFor(u => u.Id, _ => Guid.NewGuid()) + .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) + .RuleFor(u => u.Name, f => f.Person.FullName) + .RuleFor(u => u.Email, f => f.Internet.Email()) + .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); + } + + #endregion + + #region AddAsync Tests + + [TestMethod] + public async Task AddAsync_Should_Add_Entity_To_DbSet() + { + // Arrange + var user = UserFaker().Generate(); + + // Act + _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); + + // Assert + var entry = _dbContext.Entry(user); + entry.State.ShouldBe(EntityState.Added); + } + + [TestMethod] + public async Task AddAsync_Should_Persist_Added_Entity_After_SaveChanges() + { + // Arrange + var user = UserFaker().Generate(); + + // Act + _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Assert + using var dbContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var result = await dbContext.Set().FirstOrDefaultAsync(u => u.Id == user.Id, TestContext.CancellationToken); + + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.Email.ShouldBe(user.Email); + }); + } + + [TestMethod] + public async Task AddAsync_Should_Return_Added_Entity() + { + // Arrange + var user = UserFaker().Generate(); + + // Act + var result = await _userRepository.AddAsync(user, TestContext.CancellationToken); + + // Assert + result.ShouldBeSameAs(user); + } + + #endregion + + #region BulkInsertAsync Tests + + [TestMethod] + public async Task BulkInsertAsync_Should_Insert_Multiple_Entities() + { + // Arrange + var users = UserFaker().Generate(5); + + // Act + var result = await _userRepository.BulkInsertAsync(users, TestContext.CancellationToken); + + // Assert + result.ShouldBe(5); + } + + [TestMethod] + public async Task BulkInsertAsync_Should_Set_CreatedAt_And_UpdatedAt() + { + // Arrange + var users = UserFaker().Generate(3); + + // Act + _ = await _userRepository.BulkInsertAsync(users, TestContext.CancellationToken); + + // Assert + users.ShouldAllBe(u => u.CreatedAt != default && u.UpdatedAt != default); + } + + [TestMethod] + public async Task BulkInsertAsync_Should_Persist_Entities_To_Database() + { + // Arrange + var users = UserFaker().Generate(3); + + // Act + _ = await _userRepository.BulkInsertAsync(users, TestContext.CancellationToken); + + // Assert + using var dbContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var userIds = users.Select(u => u.Id).ToList(); + var count = await dbContext.Set() + .CountAsync(u => userIds.Contains(u.Id), TestContext.CancellationToken); + + count.ShouldBe(3); + } + + [TestMethod] + public async Task BulkInsertAsync_Should_Handle_Empty_Collection() + { + // Arrange + var emptyUsers = new List(); + + // Act + var result = await _userRepository.BulkInsertAsync(emptyUsers, TestContext.CancellationToken); + + // Assert + result.ShouldBe(0); + } + + [TestMethod] + public async Task BulkInsertAsync_Should_Handle_Cancellation() + { + // Arrange + var users = UserFaker().Generate(5); + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _userRepository.BulkInsertAsync(users, cts.Token) + ); + } + + #endregion + + #region GetByIdAsync Tests + + [TestMethod] + public async Task GetByIdAsync_Should_Return_Entity_By_Id() + { + // Arrange + var user = UserFaker().Generate(); + _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = await _userRepository.GetByIdAsync(user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.Id.ShouldBe(user.Id); + result.Email.ShouldBe(user.Email); + }); + } + + [TestMethod] + public async Task GetByIdAsync_Should_Return_Null_For_NonExistent_Id() + { + // Arrange + var nonExistentId = Guid.NewGuid(); + + // Act + var result = await _userRepository.GetByIdAsync(nonExistentId, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetByIdAsync_Should_Handle_Cancellation() + { + // Arrange + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _userRepository.GetByIdAsync(Guid.NewGuid(), cts.Token) + ); + } + + #endregion + + #region GetAll Tests + + [TestMethod] + public async Task GetAll_Should_Return_All_Entities() + { + // Arrange + var users = UserFaker().Generate(3); + + _ = await _userRepository.BulkInsertAsync(users, TestContext.CancellationToken); + + // Dispose current context to ensure we read from fresh one + _userRepository.Dispose(); + await _dbContext.DisposeAsync(); + + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + using var dbContext = new CoreDbContext(options); + using var repository = new BaseRepository(dbContext); + + // Act + var result = repository.GetAll().ToList(); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType>(); + result.Count.ShouldBeGreaterThanOrEqualTo(3); + }); + } + + [TestMethod] + public async Task GetAll_Should_Return_Entities_As_NoTracking() + { + // Arrange + var user = UserFaker().Generate(); + _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + _userRepository.Dispose(); + await _dbContext.DisposeAsync(); + + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + using var dbContext = new CoreDbContext(options); + using var repository = new BaseRepository(dbContext); + + // Act + var result = repository.GetAll().FirstOrDefault(u => u.Id == user.Id); + + // Assert + _ = result.ShouldNotBeNull(); + var entry = dbContext.Entry(result); + entry.State.ShouldBe(EntityState.Detached); + } + + [TestMethod] + public async Task GetAll_Should_Return_Empty_Collection_When_No_Entities() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + using var dbContext = new CoreDbContext(options); + using var repository = new BaseRepository(dbContext); + + // Act - Try to get all invoices (likely none exist at test start) + var result = repository.GetAll().ToList(); + + // Assert + _ = result.ShouldNotBeNull(); + } + + #endregion + + #region Remove Tests + + [TestMethod] + public async Task Remove_Should_Mark_Entity_As_Deleted() + { + // Arrange + var user = UserFaker().Generate(); + _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + _userRepository.Remove(user); + + // Assert + var entry = _dbContext.Entry(user); + entry.State.ShouldBe(EntityState.Deleted); + } + + [TestMethod] + public async Task Remove_Should_Delete_Entity_From_Database() + { + // Arrange + var user = UserFaker().Generate(); + _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + _userRepository.Remove(user); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + using var dbContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var result = await dbContext.Set().FirstOrDefaultAsync(u => u.Id == user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task Remove_Should_Attach_Detached_Entity_Before_Deleting() + { + // Arrange + var user = UserFaker().Generate(); + _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Detach the entity + _dbContext.Entry(user).State = EntityState.Detached; + + // Act + _userRepository.Remove(user); + + // Assert + var entry = _dbContext.Entry(user); + entry.State.ShouldBe(EntityState.Deleted); + } + + #endregion + + #region Update Tests + + [TestMethod] + public async Task Update_Should_Mark_Entity_As_Modified() + { + // Arrange + var user = UserFaker().Generate(); + _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + user.Name = "Updated Name"; + + // Act + var result = _userRepository.Update(user); + + // Assert + result.ShouldBeSameAs(user); + var entry = _dbContext.Entry(user); + entry.State.ShouldBe(EntityState.Modified); + } + + [TestMethod] + public async Task Update_Should_Persist_Changes_To_Database() + { + // Arrange + var user = UserFaker().Generate(); + _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + user.Name = "Updated Name"; + + // Act + _ = _userRepository.Update(user); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + using var dbContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var result = await dbContext.Set().FirstOrDefaultAsync(u => u.Id == user.Id, TestContext.CancellationToken); + + // Assert + _ = result.ShouldNotBeNull(); + result.Name.ShouldBe("Updated Name"); + } + + [TestMethod] + public async Task Update_Should_Attach_Detached_Entity_Before_Updating() + { + // Arrange + var user = UserFaker().Generate(); + _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Detach the entity + _dbContext.Entry(user).State = EntityState.Detached; + user.Name = "Updated Name"; + + // Act + _ = _userRepository.Update(user); + + // Assert + var entry = _dbContext.Entry(user); + entry.State.ShouldBe(EntityState.Modified); + } + + [TestMethod] + public async Task Update_Should_Return_Updated_Entity() + { + // Arrange + var user = UserFaker().Generate(); + _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + user.Name = "Updated Name"; + + // Act + var result = _userRepository.Update(user); + + // Assert + result.ShouldBeSameAs(user); + } + + #endregion + + #region Where Tests + + [TestMethod] + public async Task Where_Should_Return_Filtered_Entities() + { + // Arrange + var user1 = UserFaker() + .RuleFor(u => u.Email, _ => "test1@example.com") + .Generate(); + var user2 = UserFaker() + .RuleFor(u => u.Email, _ => "test2@example.com") + .Generate(); + var user3 = UserFaker() + .RuleFor(u => u.Email, _ => "test3@example.com") + .Generate(); + + _ = await _userRepository.AddAsync(user1, TestContext.CancellationToken); + _ = await _userRepository.AddAsync(user2, TestContext.CancellationToken); + _ = await _userRepository.AddAsync(user3, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = _userRepository.Where(u => u.Email == "test2@example.com").ToList(); + + // Assert + result.Count.ShouldBe(1); + result[0].Email.ShouldBe("test2@example.com"); + } + + [TestMethod] + public async Task Where_Should_Return_Multiple_Matching_Entities() + { + // Arrange + var user1 = UserFaker() + .RuleFor(u => u.Name, _ => "Jack Doe") + .Generate(); + var user2 = UserFaker() + .RuleFor(u => u.Name, _ => "Jane Smith") + .Generate(); + var user3 = UserFaker() + .RuleFor(u => u.Name, _ => "Jack Smith") + .Generate(); + + _ = await _userRepository.AddAsync(user1, TestContext.CancellationToken); + _ = await _userRepository.AddAsync(user2, TestContext.CancellationToken); + _ = await _userRepository.AddAsync(user3, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = _userRepository.Where(u => u.Name.Contains("Jack")); + + // Assert + result.Count().ShouldBe(2); + result.ShouldAllBe(u => u.Name.Contains("Jack")); + } + + [TestMethod] + public async Task Where_Should_Return_Empty_Collection_When_No_Match() + { + // Arrange + var user = UserFaker().Generate(); + _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = _userRepository.Where(u => u.Email == "nonexistent@example.com").ToList(); + + // Assert + result.ShouldBeEmpty(); + } + + [TestMethod] + public async Task Where_Should_Support_Complex_Predicates() + { + // Arrange + var user1 = UserFaker() + .RuleFor(u => u.Name, _ => "Alice") + .RuleFor(u => u.TelegramChatId, _ => 1000) + .Generate(); + var user2 = UserFaker() + .RuleFor(u => u.Name, _ => "Bob") + .RuleFor(u => u.TelegramChatId, _ => 2000) + .Generate(); + + _ = await _userRepository.AddAsync(user1, TestContext.CancellationToken); + _ = await _userRepository.AddAsync(user2, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = _userRepository.Where(u => u.Name == "Alice" && u.TelegramChatId > 500).ToList(); + + // Assert + result.Count.ShouldBe(1); + result[0].Name.ShouldBe("Alice"); + } + + #endregion + + #region Dispose Tests + + [TestMethod] + public void Dispose_Should_Release_DbContext_Resources() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + var dbContext = new CoreDbContext(options); + var repository = new BaseRepository(dbContext); + + // Act + repository.Dispose(); + + // Assert + _ = Should.Throw(() => + { + _ = dbContext.Set(); + }); + } + + [TestMethod] + public void Dispose_Should_Be_Safe_To_Call_Multiple_Times() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + var dbContext = new CoreDbContext(options); + var repository = new BaseRepository(dbContext); + + // Act & Assert + Should.NotThrow(() => + { + repository.Dispose(); + repository.Dispose(); + repository.Dispose(); + }); + } + + #endregion +} diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/EmailAuthTokenRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/EmailAuthTokenRepositoryIntegrationTests.cs new file mode 100644 index 0000000..8f2bc1e --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Repository/EmailAuthTokenRepositoryIntegrationTests.cs @@ -0,0 +1,284 @@ +using Bogus; +using InvoiceReminder.Data.Exceptions; +using InvoiceReminder.Data.Persistence; +using InvoiceReminder.Data.Repository; +using InvoiceReminder.Domain.Entities; +using InvoiceReminder.IntegrationTests.Data.ContainerSetup; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Shouldly; + +namespace InvoiceReminder.IntegrationTests.Data.Repository; + +[TestClass] +public sealed class EmailAuthTokenRepositoryIntegrationTests +{ + private readonly CoreDbContext _dbContext; + private readonly ILogger _repositoryLogger; + private readonly ILogger _unitOfWorkLogger; + private readonly EmailAuthTokenRepository _repository; + private readonly UnitOfWork _unitOfWork; + + public TestContext TestContext { get; set; } + + public EmailAuthTokenRepositoryIntegrationTests() + { + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + _dbContext = new CoreDbContext(options); + _repositoryLogger = Substitute.For>(); + _unitOfWorkLogger = Substitute.For>(); + _repository = new EmailAuthTokenRepository(_dbContext, _repositoryLogger); + _unitOfWork = new UnitOfWork(_dbContext, _unitOfWorkLogger); + } + + #region Helper Methods + + private static Faker UserFaker() + { + return new Faker() + .RuleFor(u => u.Id, _ => Guid.NewGuid()) + .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) + .RuleFor(u => u.Name, f => f.Person.FullName) + .RuleFor(u => u.Email, f => f.Internet.Email()) + .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); + } + + private static Faker EmailAuthTokenFaker() + { + return new Faker() + .RuleFor(e => e.Id, faker => faker.Random.Guid()) + .RuleFor(e => e.UserId, faker => faker.Random.Guid()) + .RuleFor(e => e.AccessToken, faker => faker.Random.AlphaNumeric(128)) + .RuleFor(e => e.RefreshToken, faker => faker.Random.AlphaNumeric(128)) + .RuleFor(e => e.TokenProvider, faker => faker.PickRandom("Google", "Microsoft", "GitHub")) + .RuleFor(e => e.NonceValue, faker => faker.Random.Hash()) + .RuleFor(e => e.AccessTokenExpiry, faker => faker.Date.Future().ToUniversalTime()) + .RuleFor(e => e.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) + .RuleFor(e => e.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + } + + private async Task CreateAndSaveEmailAuthTokenAsync(EmailAuthToken emailAuthToken = null) + { + var user = await CreateAndSaveUserAsync(); + emailAuthToken ??= EmailAuthTokenFaker() + .RuleFor(e => e.UserId, _ => user.Id) + .Generate(); + + _ = await _repository.AddAsync(emailAuthToken, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return emailAuthToken; + } + + private async Task CreateAndSaveUserAsync(User user = null) + { + user ??= UserFaker().Generate(); + var logger = Substitute.For>(); ; + var userRepository = new UserRepository(_dbContext, logger); + + _ = await userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return user; + } + + #endregion + + #region GetByIdAsync Tests + + [TestMethod] + public async Task GetByIdAsync_Should_Return_EmailAuthToken_By_Id() + { + // Arrange + var emailAuthToken = await CreateAndSaveEmailAuthTokenAsync(); + + // Act + var result = await _repository.GetByIdAsync(emailAuthToken.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.Id.ShouldBe(emailAuthToken.Id); + }); + } + + [TestMethod] + public async Task GetByIdAsync_Should_Return_Null_For_NonExistent_EmailAuthToken() + { + // Arrange + var nonExistentId = Guid.NewGuid(); + + // Act + var result = await _repository.GetByIdAsync(nonExistentId, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetByIdAsync_Should_Throw_Exception_On_Database_Error() + { + // Arrange + var disposedContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var logger = Substitute.For>(); + + var repository = new EmailAuthTokenRepository(disposedContext, logger); + + // Act & Assert + await disposedContext.DisposeAsync(); + + _ = await Should.ThrowAsync( + async () => await repository.GetByIdAsync(Guid.NewGuid(), TestContext.CancellationToken) + ); + } + + #endregion + + #region GetByUserIdAsync Tests + + [TestMethod] + public async Task GetByUserIdAsync_Should_Return_EmailAuthToken_By_UserId_And_TokenProvider() + { + // Arrange + var emailAuthToken = await CreateAndSaveEmailAuthTokenAsync(); + + // Act + var result = await _repository.GetByUserIdAsync(emailAuthToken.UserId, emailAuthToken.TokenProvider, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.UserId.ShouldBe(emailAuthToken.UserId); + result.TokenProvider.ShouldBe(emailAuthToken.TokenProvider); + }); + } + + [TestMethod] + public async Task GetByUserIdAsync_Should_Return_Null_For_NonExistent_UserId() + { + // Arrange + var nonExistentUserId = Guid.NewGuid(); + var tokenProvider = "Google"; + + // Act + var result = await _repository.GetByUserIdAsync(nonExistentUserId, tokenProvider, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetByUserIdAsync_Should_Return_Null_For_NonExistent_TokenProvider() + { + // Arrange + var emailAuthToken = await CreateAndSaveEmailAuthTokenAsync(); + + // Act + var result = await _repository.GetByUserIdAsync(emailAuthToken.UserId, "NonExistentProvider", TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetByUserIdAsync_Should_Retrieve_Correct_Token_When_Multiple_Exist() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + var googleToken = EmailAuthTokenFaker() + .RuleFor(e => e.UserId, _ => user.Id) + .RuleFor(e => e.TokenProvider, _ => "Google") + .Generate(); + var microsoftToken = EmailAuthTokenFaker() + .RuleFor(e => e.UserId, _ => user.Id) + .RuleFor(e => e.TokenProvider, _ => "Microsoft") + .Generate(); + + _ = await _repository.AddAsync(googleToken, TestContext.CancellationToken); + _ = await _repository.AddAsync(microsoftToken, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = await _repository.GetByUserIdAsync(user.Id, "Microsoft", TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.Id.ShouldBe(microsoftToken.Id); + result.UserId.ShouldBe(user.Id); + result.TokenProvider.ShouldBe("Microsoft"); + }); + } + + [TestMethod] + public async Task GetByUserIdAsync_Should_Throw_Exception_On_Database_Error() + { + // Arrange + var disposedContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var logger = Substitute.For>(); + + var repository = new EmailAuthTokenRepository(disposedContext, logger); + + _ = logger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + await disposedContext.DisposeAsync(); + _ = await Should.ThrowAsync( + async () => await repository.GetByUserIdAsync(Guid.NewGuid(), "Google", TestContext.CancellationToken) + ); + + logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion + + #region CancellationToken Tests + + [TestMethod] + public async Task GetByUserIdAsync_Should_Handle_Cancellation_Request() + { + // Arrange + var emailAuthToken = await CreateAndSaveEmailAuthTokenAsync(); + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetByUserIdAsync(emailAuthToken.UserId, emailAuthToken.TokenProvider, cts.Token) + ); + + _repositoryLogger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion +} diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/InvoiceRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/InvoiceRepositoryIntegrationTests.cs new file mode 100644 index 0000000..071b9b6 --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Repository/InvoiceRepositoryIntegrationTests.cs @@ -0,0 +1,297 @@ +using Bogus; +using InvoiceReminder.Data.Exceptions; +using InvoiceReminder.Data.Persistence; +using InvoiceReminder.Data.Repository; +using InvoiceReminder.Domain.Entities; +using InvoiceReminder.IntegrationTests.Data.ContainerSetup; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Shouldly; + +namespace InvoiceReminder.IntegrationTests.Data.Repository; + +[TestClass] +public sealed class InvoiceRepositoryIntegrationTests +{ + private readonly CoreDbContext _dbContext; + private readonly ILogger _repositoryLogger; + private readonly ILogger _unitOfWorkLogger; + private readonly InvoiceRepository _repository; + private readonly UnitOfWork _unitOfWork; + + public TestContext TestContext { get; set; } + + public InvoiceRepositoryIntegrationTests() + { + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + _dbContext = new CoreDbContext(options); + _repositoryLogger = Substitute.For>(); + _unitOfWorkLogger = Substitute.For>(); + _repository = new InvoiceRepository(_dbContext, _repositoryLogger); + _unitOfWork = new UnitOfWork(_dbContext, _unitOfWorkLogger); + } + + #region Helper Methods + + private static Faker UserFaker() + { + return new Faker() + .RuleFor(u => u.Id, _ => Guid.NewGuid()) + .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) + .RuleFor(u => u.Name, f => f.Person.FullName) + .RuleFor(u => u.Email, f => f.Internet.Email()) + .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); + } + + private static Faker InvoiceFaker() + { + return new Faker() + .RuleFor(i => i.Id, faker => faker.Random.Guid()) + .RuleFor(i => i.UserId, faker => faker.Random.Guid()) + .RuleFor(i => i.Bank, faker => faker.PickRandom( + "Banco do Brasil", + "Bradesco", + "Itaú", + "Caixa Econômica Federal", + "Santander", + "Safra", + "Citibank", + "BTG Pactual")) + .RuleFor(i => i.Beneficiary, faker => faker.Person.FullName) + .RuleFor(i => i.Amount, faker => faker.Finance.Amount(10, 10000)) + .RuleFor(i => i.Barcode, faker => faker.Random.AlphaNumeric(44)) + .RuleFor(i => i.DueDate, faker => faker.Date.Future().ToUniversalTime()) + .RuleFor(i => i.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) + .RuleFor(i => i.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + } + + private async Task CreateAndSaveUserAsync(User user = null) + { + user ??= UserFaker().Generate(); + var logger = Substitute.For>(); + var userRepository = new UserRepository(_dbContext, logger); + + _ = await userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return user; + } + + private async Task CreateAndSaveInvoiceAsync(Invoice invoice = null) + { + var user = await CreateAndSaveUserAsync(); + invoice ??= InvoiceFaker() + .RuleFor(i => i.UserId, _ => user.Id) + .Generate(); + + _ = await _repository.AddAsync(invoice, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return invoice; + } + + #endregion + + #region GetByIdAsync Tests + + [TestMethod] + public async Task GetByIdAsync_Should_Return_Invoice_By_Id() + { + // Arrange + var invoice = await CreateAndSaveInvoiceAsync(); + + // Act + var result = await _repository.GetByIdAsync(invoice.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldBeAssignableTo(); + _ = result.ShouldBeOfType(); + _ = result.ShouldNotBeNull(); + result.Id.ShouldBe(invoice.Id); + }); + } + + [TestMethod] + public async Task GetByIdAsync_Should_Return_Null_For_NonExistent_Invoice() + { + // Arrange + var nonExistentId = Guid.NewGuid(); + + // Act + var result = await _repository.GetByIdAsync(nonExistentId, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetByIdAsync_Should_Throw_Exception_On_Database_Error() + { + // Arrange + var disposedContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var logger = Substitute.For>(); + + var repository = new InvoiceRepository(disposedContext, logger); + + // Act & Assert + await disposedContext.DisposeAsync(); + + _ = await Should.ThrowAsync( + async () => await repository.GetByIdAsync(Guid.NewGuid(), TestContext.CancellationToken) + ); + } + + #endregion + + #region GetByBarcodeAsync Tests + + [TestMethod] + public async Task GetByBarcodeAsync_Should_Return_Invoice_By_Barcode() + { + // Arrange + var invoice = await CreateAndSaveInvoiceAsync(); + + // Act + var result = await _repository.GetByBarcodeAsync(invoice.Barcode, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldBeAssignableTo(); + _ = result.ShouldBeOfType(); + _ = result.ShouldNotBeNull(); + result.Barcode.ShouldBe(invoice.Barcode); + }); + } + + [TestMethod] + public async Task GetByBarcodeAsync_Should_Return_Null_For_NonExistent_Barcode() + { + // Arrange + var nonExistentBarcode = "00000000000000000000000000000000000000000000"; + + // Act + var result = await _repository.GetByBarcodeAsync(nonExistentBarcode, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetByBarcodeAsync_Should_Find_Invoice_With_Whitespace_In_Barcode() + { + // Arrange + var invoice = await CreateAndSaveInvoiceAsync(); + + // Act + var result = await _repository.GetByBarcodeAsync($" {invoice.Barcode} ", TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldBeAssignableTo(); + _ = result.ShouldBeOfType(); + _ = result.ShouldNotBeNull(); + result.Barcode.ShouldBe(invoice.Barcode); + }); + } + + [TestMethod] + public async Task GetByBarcodeAsync_Should_Retrieve_Correct_Invoice_When_Multiple_Exist() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + var invoice1 = InvoiceFaker() + .RuleFor(i => i.UserId, _ => user.Id) + .Generate(); + var invoice2 = InvoiceFaker() + .RuleFor(i => i.UserId, _ => user.Id) + .Generate(); + + _ = await _repository.AddAsync(invoice1, TestContext.CancellationToken); + _ = await _repository.AddAsync(invoice2, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = await _repository.GetByBarcodeAsync(invoice2.Barcode, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.Barcode.ShouldBe(invoice2.Barcode); + result.Id.ShouldBe(invoice2.Id); + }); + } + + [TestMethod] + public async Task GetByBarcodeAsync_Should_Throw_Exception_On_Database_Error() + { + // Arrange + var disposedContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var logger = Substitute.For>(); + + var repository = new InvoiceRepository(disposedContext, logger); + + _ = logger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + await disposedContext.DisposeAsync(); + + _ = await Should.ThrowAsync( + async () => await repository.GetByBarcodeAsync("00000000000000000000000000000000000000000000", + TestContext.CancellationToken) + ); + + logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion + + #region CancellationToken Tests + + [TestMethod] + public async Task GetByBarcodeAsync_Should_Handle_Cancellation_Request() + { + // Arrange + var invoice = await CreateAndSaveInvoiceAsync(); + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetByBarcodeAsync(invoice.Barcode, cts.Token) + ); + + _repositoryLogger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion +} diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/JobScheduleRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/JobScheduleRepositoryIntegrationTests.cs new file mode 100644 index 0000000..868cd64 --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Repository/JobScheduleRepositoryIntegrationTests.cs @@ -0,0 +1,309 @@ +using Bogus; +using InvoiceReminder.Data.Exceptions; +using InvoiceReminder.Data.Persistence; +using InvoiceReminder.Data.Repository; +using InvoiceReminder.Domain.Entities; +using InvoiceReminder.IntegrationTests.Data.ContainerSetup; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Shouldly; + +namespace InvoiceReminder.IntegrationTests.Data.Repository; + +[TestClass] +public sealed class JobScheduleRepositoryIntegrationTests +{ + private readonly CoreDbContext _dbContext; + private readonly ILogger _repositoryLogger; + private readonly ILogger _unitOfWorkLogger; + private readonly JobScheduleRepository _repository; + private readonly UnitOfWork _unitOfWork; + + public TestContext TestContext { get; set; } + + public JobScheduleRepositoryIntegrationTests() + { + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + _dbContext = new CoreDbContext(options); + _repositoryLogger = Substitute.For>(); + _unitOfWorkLogger = Substitute.For>(); + _repository = new JobScheduleRepository(_dbContext, _repositoryLogger); + _unitOfWork = new UnitOfWork(_dbContext, _unitOfWorkLogger); + } + + #region Helper Methods + + private static Faker UserFaker() + { + return new Faker() + .RuleFor(u => u.Id, _ => Guid.NewGuid()) + .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) + .RuleFor(u => u.Name, f => f.Person.FullName) + .RuleFor(u => u.Email, f => f.Internet.Email()) + .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); + } + + private static Faker JobScheduleFaker() + { + return new Faker() + .RuleFor(j => j.Id, faker => faker.Random.Guid()) + .RuleFor(j => j.UserId, faker => faker.Random.Guid()) + .RuleFor(j => j.CronExpression, faker => faker.PickRandom( + "0 0 * * *", + "0 */6 * * *", + "0 */12 * * *", + "0 9 * * MON", + "0 9 * * MON-FRI", + "0 0 1 * *")) + .RuleFor(j => j.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) + .RuleFor(j => j.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + } + + private async Task CreateAndSaveUserAsync(User user = null) + { + user ??= UserFaker().Generate(); + var logger = Substitute.For>(); + var userRepository = new UserRepository(_dbContext, logger); + + _ = await userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return user; + } + + private async Task CreateAndSaveJobScheduleAsync(JobSchedule jobSchedule = null) + { + var user = await CreateAndSaveUserAsync(); + jobSchedule ??= JobScheduleFaker() + .RuleFor(j => j.UserId, _ => user.Id) + .Generate(); + + _ = await _repository.AddAsync(jobSchedule, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return jobSchedule; + } + + #endregion + + #region GetByIdAsync Tests + + [TestMethod] + public async Task GetByIdAsync_Should_Return_JobSchedule_By_Id() + { + // Arrange + var jobSchedule = await CreateAndSaveJobScheduleAsync(); + + // Act + var result = await _repository.GetByIdAsync(jobSchedule.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.Id.ShouldBe(jobSchedule.Id); + }); + } + + [TestMethod] + public async Task GetByIdAsync_Should_Return_Null_For_NonExistent_JobSchedule() + { + // Arrange + var nonExistentId = Guid.NewGuid(); + + // Act + var result = await _repository.GetByIdAsync(nonExistentId, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetByIdAsync_Should_Throw_Exception_On_Database_Error() + { + // Arrange + var disposedContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var logger = Substitute.For>(); + + var repository = new JobScheduleRepository(disposedContext, logger); + + // Act & Assert + await disposedContext.DisposeAsync(); + + _ = await Should.ThrowAsync( + async () => await repository.GetByIdAsync(Guid.NewGuid(), TestContext.CancellationToken) + ); + } + + #endregion + + #region GetByUserIdAsync Tests + + [TestMethod] + public async Task GetByUserIdAsync_Should_Return_Empty_Collection_For_NonExistent_UserId() + { + // Arrange + var nonExistentUserId = Guid.NewGuid(); + + // Act + var result = await _repository.GetByUserIdAsync(nonExistentUserId, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType>(); + result.ShouldBeEmpty(); + }); + } + + [TestMethod] + public async Task GetByUserIdAsync_Should_Return_JobSchedules_By_UserId() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + var jobSchedule = JobScheduleFaker() + .RuleFor(j => j.UserId, _ => user.Id) + .Generate(); + + _ = await _repository.AddAsync(jobSchedule, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = await _repository.GetByUserIdAsync(user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType>(); + result.ShouldNotBeEmpty(); + result.First().UserId.ShouldBe(user.Id); + }); + } + + [TestMethod] + public async Task GetByUserIdAsync_Should_Return_Multiple_JobSchedules_For_Same_User() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + var jobSchedules = JobScheduleFaker() + .RuleFor(j => j.UserId, _ => user.Id) + .Generate(3); + + _ = await _repository.BulkInsertAsync(jobSchedules, TestContext.CancellationToken); + + // Act + var result = await _repository.GetByUserIdAsync(user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType>(); + result.ShouldNotBeEmpty(); + result.Count().ShouldBe(3); + result.ShouldAllBe(x => x.UserId == user.Id); + }); + } + + [TestMethod] + public async Task GetByUserIdAsync_Should_Return_Only_JobSchedules_For_Specified_User() + { + // Arrange + var user1 = await CreateAndSaveUserAsync(); + var user2 = await CreateAndSaveUserAsync(); + + var jobSchedule1 = JobScheduleFaker() + .RuleFor(j => j.UserId, _ => user1.Id) + .Generate(); + var jobSchedule2 = JobScheduleFaker() + .RuleFor(j => j.UserId, _ => user2.Id) + .Generate(); + + _ = await _repository.AddAsync(jobSchedule1, TestContext.CancellationToken); + _ = await _repository.AddAsync(jobSchedule2, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = await _repository.GetByUserIdAsync(user1.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType>(); + result.Count().ShouldBe(1); + result.First().UserId.ShouldBe(user1.Id); + result.First().Id.ShouldBe(jobSchedule1.Id); + }); + } + + [TestMethod] + public async Task GetByUserIdAsync_Should_Throw_Exception_On_Database_Error() + { + // Arrange + var disposedContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var logger = Substitute.For>(); + + var repository = new JobScheduleRepository(disposedContext, logger); + + _ = logger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + await disposedContext.DisposeAsync(); + + _ = await Should.ThrowAsync( + async () => await repository.GetByUserIdAsync(Guid.NewGuid(), TestContext.CancellationToken) + ); + + logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion + + #region CancellationToken Tests + + [TestMethod] + public async Task GetByUserIdAsync_Should_Handle_Cancellation_Request() + { + // Arrange + var jobSchedule = await CreateAndSaveJobScheduleAsync(); + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetByUserIdAsync(jobSchedule.UserId, cts.Token) + ); + + _repositoryLogger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion +} diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/ScanEmailDefinitionRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/ScanEmailDefinitionRepositoryIntegrationTests.cs new file mode 100644 index 0000000..36c3541 --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Repository/ScanEmailDefinitionRepositoryIntegrationTests.cs @@ -0,0 +1,613 @@ +using Bogus; +using InvoiceReminder.Data.Exceptions; +using InvoiceReminder.Data.Persistence; +using InvoiceReminder.Data.Repository; +using InvoiceReminder.Domain.Entities; +using InvoiceReminder.Domain.Enums; +using InvoiceReminder.IntegrationTests.Data.ContainerSetup; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Shouldly; + +namespace InvoiceReminder.IntegrationTests.Data.Repository; + +[TestClass] +public sealed class ScanEmailDefinitionRepositoryIntegrationTests +{ + private readonly CoreDbContext _dbContext; + private readonly ILogger _repositoryLogger; + private readonly ILogger _unitOfWorkLogger; + private readonly ScanEmailDefinitionRepository _repository; + private readonly UnitOfWork _unitOfWork; + + public TestContext TestContext { get; set; } + + public ScanEmailDefinitionRepositoryIntegrationTests() + { + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + _dbContext = new CoreDbContext(options); + _repositoryLogger = Substitute.For>(); ; + _unitOfWorkLogger = Substitute.For>(); + _repository = new ScanEmailDefinitionRepository(_dbContext, _repositoryLogger); + _unitOfWork = new UnitOfWork(_dbContext, _unitOfWorkLogger); + } + + #region Helper Methods + + private static Faker UserFaker() + { + return new Faker() + .RuleFor(u => u.Id, _ => Guid.NewGuid()) + .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) + .RuleFor(u => u.Name, f => f.Person.FullName) + .RuleFor(u => u.Email, f => f.Internet.Email()) + .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); + } + + private static Faker ScanEmailDefinitionFaker() + { + return new Faker() + .RuleFor(s => s.Id, faker => faker.Random.Guid()) + .RuleFor(s => s.UserId, faker => faker.Random.Guid()) + .RuleFor(s => s.InvoiceType, faker => faker.PickRandom(InvoiceType.AccountInvoice, InvoiceType.BankInvoice)) + .RuleFor(s => s.Beneficiary, faker => faker.Person.FullName) + .RuleFor(s => s.Description, faker => faker.Lorem.Sentence()) + .RuleFor(s => s.SenderEmailAddress, faker => faker.Internet.Email()) + .RuleFor(s => s.AttachmentFileName, faker => faker.System.FileName("pdf")) + .RuleFor(s => s.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) + .RuleFor(s => s.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + } + + private async Task CreateAndSaveUserAsync(User user = null) + { + user ??= UserFaker().Generate(); + var logger = Substitute.For>(); + var userRepository = new UserRepository(_dbContext, logger); + + _ = await userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return user; + } + + private async Task CreateAndSaveScanEmailDefinitionAsync(ScanEmailDefinition scanEmailDefinition = null) + { + var user = await CreateAndSaveUserAsync(); + scanEmailDefinition ??= ScanEmailDefinitionFaker() + .RuleFor(s => s.UserId, _ => user.Id) + .Generate(); + + _ = await _repository.AddAsync(scanEmailDefinition, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return scanEmailDefinition; + } + + #endregion + + #region GetByIdAsync Tests + + [TestMethod] + public async Task GetByIdAsync_Should_Return_ScanEmailDefinition_By_Id() + { + // Arrange + var scanEmailDefinition = await CreateAndSaveScanEmailDefinitionAsync(); + + // Act + var result = await _repository.GetByIdAsync(scanEmailDefinition.Id, TestContext.CancellationToken); + + // Assert + _ = result.ShouldNotBeNull(); + result.Id.ShouldBe(scanEmailDefinition.Id); + } + + [TestMethod] + public async Task GetByIdAsync_Should_Return_Null_For_NonExistent_ScanEmailDefinition() + { + // Arrange + var nonExistentId = Guid.NewGuid(); + + // Act + var result = await _repository.GetByIdAsync(nonExistentId, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetByIdAsync_Should_Throw_Exception_On_Database_Error() + { + // Arrange + var disposedContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var logger = Substitute.For>(); + + var repository = new ScanEmailDefinitionRepository(disposedContext, logger); + + // Act & Assert + await disposedContext.DisposeAsync(); + + _ = await Should.ThrowAsync( + async () => await repository.GetByIdAsync(Guid.NewGuid(), TestContext.CancellationToken) + ); + } + + #endregion + + #region GetBySenderEmailAddressAsync Tests + + [TestMethod] + public async Task GetBySenderEmailAddressAsync_Should_Return_ScanEmailDefinition_By_Email_And_UserId() + { + // Arrange + var scanEmailDefinition = await CreateAndSaveScanEmailDefinitionAsync(); + + // Act + var result = await _repository.GetBySenderEmailAddressAsync(scanEmailDefinition.SenderEmailAddress, + scanEmailDefinition.UserId, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.SenderEmailAddress.ShouldBe(scanEmailDefinition.SenderEmailAddress); + result.UserId.ShouldBe(scanEmailDefinition.UserId); + }); + } + + [TestMethod] + public async Task GetBySenderEmailAddressAsync_Should_Return_Null_For_NonExistent_Email() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + var nonExistentEmail = "nonexistent@example.com"; + + // Act + var result = await _repository.GetBySenderEmailAddressAsync(nonExistentEmail, user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetBySenderEmailAddressAsync_Should_Return_Null_For_NonExistent_UserId() + { + // Arrange + var nonExistentUserId = Guid.NewGuid(); + var email = "test@example.com"; + + // Act + var result = await _repository.GetBySenderEmailAddressAsync(email, nonExistentUserId, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetBySenderEmailAddressAsync_Should_Find_Definition_With_Whitespace_In_Email() + { + // Arrange + var scanEmailDefinition = await CreateAndSaveScanEmailDefinitionAsync(); + + // Act + var result = await _repository.GetBySenderEmailAddressAsync($" {scanEmailDefinition.SenderEmailAddress} ", + scanEmailDefinition.UserId, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.SenderEmailAddress.ShouldBe(scanEmailDefinition.SenderEmailAddress); + }); + } + + [TestMethod] + public async Task GetBySenderEmailAddressAsync_Should_Retrieve_Correct_Definition_When_Multiple_Exist() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + var definition1 = ScanEmailDefinitionFaker() + .RuleFor(s => s.UserId, _ => user.Id) + .Generate(); + var definition2 = ScanEmailDefinitionFaker() + .RuleFor(s => s.UserId, _ => user.Id) + .Generate(); + + _ = await _repository.AddAsync(definition1, TestContext.CancellationToken); + _ = await _repository.AddAsync(definition2, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = await _repository.GetBySenderEmailAddressAsync(definition2.SenderEmailAddress, + user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.SenderEmailAddress.ShouldBe(definition2.SenderEmailAddress); + result.Id.ShouldBe(definition2.Id); + }); + } + + [TestMethod] + public async Task GetBySenderEmailAddressAsync_Should_Throw_Exception_On_Database_Error() + { + // Arrange + var disposedContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var logger = Substitute.For>(); + + var repository = new ScanEmailDefinitionRepository(disposedContext, logger); + + _ = logger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + await disposedContext.DisposeAsync(); + + _ = await Should.ThrowAsync( + async () => await repository.GetBySenderEmailAddressAsync("test@example.com", Guid.NewGuid(), TestContext.CancellationToken) + ); + + logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion + + #region GetBySenderBeneficiaryAsync Tests + + [TestMethod] + public async Task GetBySenderBeneficiaryAsync_Should_Return_ScanEmailDefinition_By_Beneficiary_And_UserId() + { + // Arrange + var scanEmailDefinition = await CreateAndSaveScanEmailDefinitionAsync(); + + // Act + var result = await _repository.GetBySenderBeneficiaryAsync(scanEmailDefinition.Beneficiary, scanEmailDefinition.UserId, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.Beneficiary.ShouldBe(scanEmailDefinition.Beneficiary); + result.UserId.ShouldBe(scanEmailDefinition.UserId); + }); + + } + + [TestMethod] + public async Task GetBySenderBeneficiaryAsync_Should_Return_Null_For_NonExistent_Beneficiary() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + var nonExistentBeneficiary = "NonExistent Company"; + + // Act + var result = await _repository.GetBySenderBeneficiaryAsync(nonExistentBeneficiary, user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetBySenderBeneficiaryAsync_Should_Return_Null_For_NonExistent_UserId() + { + // Arrange + var nonExistentUserId = Guid.NewGuid(); + var beneficiary = "Company Name"; + + // Act + var result = await _repository.GetBySenderBeneficiaryAsync(beneficiary, nonExistentUserId, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetBySenderBeneficiaryAsync_Should_Find_Definition_With_Whitespace_In_Beneficiary() + { + // Arrange + var scanEmailDefinition = await CreateAndSaveScanEmailDefinitionAsync(); + + // Act + var result = await _repository.GetBySenderBeneficiaryAsync($" {scanEmailDefinition.Beneficiary} ", scanEmailDefinition.UserId, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.Beneficiary.ShouldBe(scanEmailDefinition.Beneficiary); + }); + } + + [TestMethod] + public async Task GetBySenderBeneficiaryAsync_Should_Retrieve_Correct_Definition_When_Multiple_Exist() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + var definition1 = ScanEmailDefinitionFaker() + .RuleFor(s => s.UserId, _ => user.Id) + .Generate(); + var definition2 = ScanEmailDefinitionFaker() + .RuleFor(s => s.UserId, _ => user.Id) + .Generate(); + + _ = await _repository.AddAsync(definition1, TestContext.CancellationToken); + _ = await _repository.AddAsync(definition2, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = await _repository.GetBySenderBeneficiaryAsync(definition2.Beneficiary, user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.Beneficiary.ShouldBe(definition2.Beneficiary); + result.Id.ShouldBe(definition2.Id); + }); + } + + [TestMethod] + public async Task GetBySenderBeneficiaryAsync_Should_Throw_Exception_On_Database_Error() + { + // Arrange + var disposedContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var logger = Substitute.For>(); + + var repository = new ScanEmailDefinitionRepository(disposedContext, logger); + + _ = logger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + await disposedContext.DisposeAsync(); + + _ = await Should.ThrowAsync( + async () => await repository.GetBySenderBeneficiaryAsync("Company Name", Guid.NewGuid(), TestContext.CancellationToken) + ); + + logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion + + #region GetByUserIdAsync Tests + + [TestMethod] + public async Task GetByUserIdAsync_Should_Return_Empty_Collection_For_NonExistent_UserId() + { + // Arrange + var nonExistentUserId = Guid.NewGuid(); + + // Act + var result = await _repository.GetByUserIdAsync(nonExistentUserId, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType>(); + result.ShouldBeEmpty(); + }); + } + + [TestMethod] + public async Task GetByUserIdAsync_Should_Return_ScanEmailDefinitions_By_UserId() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + var scanEmailDefinition = ScanEmailDefinitionFaker() + .RuleFor(s => s.UserId, _ => user.Id) + .Generate(); + + _ = await _repository.AddAsync(scanEmailDefinition, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = await _repository.GetByUserIdAsync(user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType>(); + result.ShouldNotBeEmpty(); + result.First().UserId.ShouldBe(user.Id); + }); + } + + [TestMethod] + public async Task GetByUserIdAsync_Should_Return_Multiple_ScanEmailDefinitions_For_Same_User() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + var scanEmailDefinitions = ScanEmailDefinitionFaker() + .RuleFor(s => s.UserId, _ => user.Id) + .Generate(3); + + _ = await _repository.BulkInsertAsync(scanEmailDefinitions, TestContext.CancellationToken); + + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = await _repository.GetByUserIdAsync(user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType>(); + result.Count().ShouldBe(3); + result.ShouldAllBe(s => s.UserId == user.Id); + }); + } + + [TestMethod] + public async Task GetByUserIdAsync_Should_Return_Only_ScanEmailDefinitions_For_Specified_User() + { + // Arrange + var user1 = await CreateAndSaveUserAsync(); + var user2 = await CreateAndSaveUserAsync(); + + var definition1 = ScanEmailDefinitionFaker() + .RuleFor(s => s.UserId, _ => user1.Id) + .Generate(); + var definition2 = ScanEmailDefinitionFaker() + .RuleFor(s => s.UserId, _ => user2.Id) + .Generate(); + + _ = await _repository.AddAsync(definition1, TestContext.CancellationToken); + _ = await _repository.AddAsync(definition2, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Act + var result = await _repository.GetByUserIdAsync(user1.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType>(); + result.ShouldNotBeEmpty(); + result.First().UserId.ShouldBe(user1.Id); + result.First().Id.ShouldBe(definition1.Id); + }); + } + + [TestMethod] + public async Task GetByUserIdAsync_Should_Throw_Exception_On_Database_Error() + { + // Arrange + var disposedContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var logger = Substitute.For>(); + + var repository = new ScanEmailDefinitionRepository(disposedContext, logger); + + _ = logger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + await disposedContext.DisposeAsync(); + + _ = await Should.ThrowAsync( + async () => await repository.GetByUserIdAsync(Guid.NewGuid(), TestContext.CancellationToken) + ); + + logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion + + #region CancellationToken Tests + + [TestMethod] + public async Task GetBySenderEmailAddressAsync_Should_Handle_Cancellation_Request() + { + // Arrange + var scanEmailDefinition = await CreateAndSaveScanEmailDefinitionAsync(); + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetBySenderEmailAddressAsync(scanEmailDefinition.SenderEmailAddress, scanEmailDefinition.UserId, cts.Token) + ); + + _repositoryLogger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + [TestMethod] + public async Task GetBySenderBeneficiaryAsync_Should_Handle_Cancellation_Request() + { + // Arrange + var scanEmailDefinition = await CreateAndSaveScanEmailDefinitionAsync(); + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetBySenderBeneficiaryAsync(scanEmailDefinition.Beneficiary, scanEmailDefinition.UserId, cts.Token) + ); + + _repositoryLogger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + [TestMethod] + public async Task GetByUserIdAsync_Should_Handle_Cancellation_Request() + { + // Arrange + var scanEmailDefinition = await CreateAndSaveScanEmailDefinitionAsync(); + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetByUserIdAsync(scanEmailDefinition.UserId, cts.Token) + ); + + _repositoryLogger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion +} diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/UnitOfWorkIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/UnitOfWorkIntegrationTests.cs new file mode 100644 index 0000000..93ecd91 --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Repository/UnitOfWorkIntegrationTests.cs @@ -0,0 +1,368 @@ +using Bogus; +using InvoiceReminder.Data.Exceptions; +using InvoiceReminder.Data.Persistence; +using InvoiceReminder.Data.Repository; +using InvoiceReminder.Domain.Entities; +using InvoiceReminder.IntegrationTests.Data.ContainerSetup; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Shouldly; +using System.Data; + +namespace InvoiceReminder.IntegrationTests.Data.Repository; + +[TestClass] +public sealed class UnitOfWorkIntegrationTests +{ + private CoreDbContext _dbContext; + private ILogger _logger; + private UnitOfWork _unitOfWork; + + public TestContext TestContext { get; set; } + + [TestInitialize] + public void TestInitialize() + { + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + _dbContext = new CoreDbContext(options); + _logger = Substitute.For>(); + _unitOfWork = new UnitOfWork(_dbContext, _logger); + } + + [TestCleanup] + public void TestCleanup() + { + _unitOfWork?.Dispose(); + _dbContext?.Dispose(); + } + + #region Helper Methods + + private static Faker UserFaker() + { + return new Faker() + .RuleFor(u => u.Id, _ => Guid.NewGuid()) + .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) + .RuleFor(u => u.Name, f => f.Person.FullName) + .RuleFor(u => u.Email, f => f.Internet.Email()) + .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); + } + + #endregion + + #region SaveChangesAsync Tests + + [TestMethod] + public async Task SaveChangesAsync_Should_Persist_Changes_To_Database() + { + // Arrange + var user = UserFaker().Generate(); + _ = _dbContext.Set().Add(user); + + // Act + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Assert + using var dbContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var result = await dbContext.Set().FirstOrDefaultAsync(u => u.Id == user.Id, TestContext.CancellationToken); + + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.Id.ShouldBe(user.Id); + result.Email.ShouldBe(user.Email); + }); + } + + [TestMethod] + public async Task SaveChangesAsync_Should_Persist_Multiple_Changes() + { + // Arrange + var user1 = UserFaker().Generate(); + var user2 = UserFaker().Generate(); + var user3 = UserFaker().Generate(); + + _ = _dbContext.Set().Add(user1); + _ = _dbContext.Set().Add(user2); + _ = _dbContext.Set().Add(user3); + + // Act + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + using var dbContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var count = await dbContext.Set() + .CountAsync(u => u.Id == user1.Id || u.Id == user2.Id || u.Id == user3.Id, TestContext.CancellationToken); + + // Assert + count.ShouldBe(3); + } + + [TestMethod] + public async Task SaveChangesAsync_Should_Update_Existing_Entity() + { + // Arrange + var user = UserFaker().Generate(); + _ = _dbContext.Set().Add(user); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + var originalEmail = user.Email; + user.Email = "updated@example.com"; + _ = _dbContext.Set().Update(user); + + // Act + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + using var dbContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var result = await dbContext.Set().FirstOrDefaultAsync(u => u.Id == user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.Email.ShouldNotBe(originalEmail); + result.Email.ShouldBe("updated@example.com"); + }); + } + + [TestMethod] + public async Task SaveChangesAsync_Should_Delete_Entity() + { + // Arrange + var user = UserFaker().Generate(); + _ = _dbContext.Set().Add(user); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + _ = _dbContext.Set().Remove(user); + + // Act + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + using var dbContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var result = await dbContext.Set().FirstOrDefaultAsync(u => u.Id == user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task SaveChangesAsync_Should_Commit_Transaction_On_Success() + { + // Arrange + var user = UserFaker().Generate(); + _ = _dbContext.Set().Add(user); + + // Act + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Assert - Verify data persisted by checking with a fresh context + using var dbContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var result = await dbContext.Set().FirstOrDefaultAsync(u => u.Id == user.Id, TestContext.CancellationToken); + + _ = result.ShouldNotBeNull(); + result.Email.ShouldBe(user.Email); + } + + [TestMethod] + public async Task SaveChangesAsync_Should_Rollback_On_Invalid_Data() + { + // Arrange + var user = UserFaker().Generate(); + user.Email = null; + + _ = _logger.IsEnabled(Arg.Any()).Returns(true); + + _ = _dbContext.Set().Add(user); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken) + ); + + _logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Is(o => o.ToString().Contains("Rolling back changes")), + Arg.Any(), + Arg.Any>() + ); + } + + [TestMethod] + public async Task SaveChangesAsync_Should_Rollback_Transaction_On_Constraint_Violation() + { + // Arrange + var user1 = UserFaker().Generate(); + var user2 = UserFaker().Generate(); + user2.Email = user1.Email; + + _ = _logger.IsEnabled(Arg.Any()).Returns(true); + + _ = _dbContext.Set().Add(user1); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Dispose current unit of work and create a new one + _unitOfWork.Dispose(); + await _dbContext.DisposeAsync(); + + using var newDbContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + using var newUnitOfWork = new UnitOfWork(newDbContext, _logger); + + _ = newDbContext.Set().Add(user2); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await newUnitOfWork.SaveChangesAsync(TestContext.CancellationToken) + ); + + _logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Is(o => o.ToString().Contains("Rolling back changes")), + Arg.Any(), + Arg.Any>() + ); + } + + [TestMethod] + public async Task SaveChangesAsync_Should_Open_Connection_If_Closed() + { + // Arrange + var user = UserFaker().Generate(); + _ = _dbContext.Set().Add(user); + + var connection = _dbContext.Database.GetDbConnection(); + await connection.CloseAsync(); + + // Act + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + using var dbContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var result = await dbContext.Set().FirstOrDefaultAsync(u => u.Id == user.Id, TestContext.CancellationToken); + + // Assert + connection.State.ShouldBe(ConnectionState.Closed); + _ = result.ShouldNotBeNull(); + } + + [TestMethod] + public async Task SaveChangesAsync_Should_Close_Connection_After_Save() + { + // Arrange + var user = UserFaker().Generate(); + _ = _dbContext.Set().Add(user); + + // Act + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + // Assert + var connection = _dbContext.Database.GetDbConnection(); + + connection.State.ShouldBe(ConnectionState.Closed); + } + + #endregion + + #region CancellationToken Tests + + [TestMethod] + public async Task SaveChangesAsync_Should_Handle_Cancellation_Request() + { + // Arrange + var user = UserFaker().Generate(); + _ = _dbContext.Set().Add(user); + + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _logger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _unitOfWork.SaveChangesAsync(cts.Token) + ); + + _logger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion + + #region Dispose Tests + + [TestMethod] + public void Dispose_Should_Release_Context_Resources() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + var dbContext = new CoreDbContext(options); + var logger = Substitute.For>(); ; + var unitOfWork = new UnitOfWork(dbContext, logger); + + // Act + unitOfWork.Dispose(); + + // Assert + _ = Should.Throw(() => + { + _ = dbContext.Set(); + }); + } + + [TestMethod] + public void Dispose_Should_Be_Safe_To_Call_Multiple_Times() + { + // Arrange + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + var dbContext = new CoreDbContext(options); + var logger = Substitute.For>(); + var unitOfWork = new UnitOfWork(dbContext, logger); + + // Act & Assert - Should not throw + Should.NotThrow(() => + { + unitOfWork.Dispose(); + unitOfWork.Dispose(); + unitOfWork.Dispose(); + }); + } + + #endregion +} diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/UserRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/UserRepositoryIntegrationTests.cs new file mode 100644 index 0000000..aaeba1a --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Repository/UserRepositoryIntegrationTests.cs @@ -0,0 +1,344 @@ +using Bogus; +using InvoiceReminder.Data.Exceptions; +using InvoiceReminder.Data.Persistence; +using InvoiceReminder.Data.Repository; +using InvoiceReminder.Domain.Entities; +using InvoiceReminder.IntegrationTests.Data.ContainerSetup; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Shouldly; + +namespace InvoiceReminder.IntegrationTests.Data.Repository; + +[TestClass] +public sealed class UserRepositoryIntegrationTests +{ + private readonly CoreDbContext _dbContext; + private readonly ILogger _repositoryLogger; + private readonly ILogger _unitOfWorkLogger; + private readonly UserRepository _repository; + private readonly UnitOfWork _unitOfWork; + + public TestContext TestContext { get; set; } + + public UserRepositoryIntegrationTests() + { + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + _dbContext = new CoreDbContext(options); + _repositoryLogger = Substitute.For>(); + _unitOfWorkLogger = Substitute.For>(); + _repository = new UserRepository(_dbContext, _repositoryLogger); + _unitOfWork = new UnitOfWork(_dbContext, _unitOfWorkLogger); + } + + #region Helper Methods + + private static Faker EmailAuthTokenFaker() + { + return new Faker() + .RuleFor(e => e.Id, faker => faker.Random.Guid()) + .RuleFor(e => e.UserId, faker => faker.Random.Guid()) + .RuleFor(e => e.AccessToken, faker => faker.Random.AlphaNumeric(128)) + .RuleFor(e => e.RefreshToken, faker => faker.Random.AlphaNumeric(128)) + .RuleFor(e => e.TokenProvider, faker => faker.PickRandom("Google", "Microsoft", "GitHub")) + .RuleFor(e => e.NonceValue, faker => faker.Random.Hash()) + .RuleFor(e => e.AccessTokenExpiry, faker => faker.Date.Future().ToUniversalTime()) + .RuleFor(e => e.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) + .RuleFor(e => e.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + } + + private static Faker InvoiceFaker() + { + return new Faker() + .RuleFor(i => i.Id, faker => faker.Random.Guid()) + .RuleFor(i => i.UserId, faker => faker.Random.Guid()) + .RuleFor(i => i.Bank, faker => faker.PickRandom( + "Banco do Brasil", + "Bradesco", + "Itaú", + "Caixa Econômica Federal", + "Santander", + "Safra", + "Citibank", + "BTG Pactual")) + .RuleFor(i => i.Beneficiary, faker => faker.Person.FullName) + .RuleFor(i => i.Amount, faker => faker.Finance.Amount(10, 10000)) + .RuleFor(i => i.Barcode, faker => faker.Random.AlphaNumeric(44)) + .RuleFor(i => i.DueDate, faker => faker.Date.Future().ToUniversalTime()); + } + + private static Faker UserFaker() + { + return new Faker() + .RuleFor(u => u.Id, _ => Guid.NewGuid()) + .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) + .RuleFor(u => u.Name, f => f.Person.FullName) + .RuleFor(u => u.Email, f => f.Internet.Email()) + .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); + } + + private async Task CreateAndSaveUserAsync(User user = null) + { + user ??= UserFaker().Generate(); + _ = await _repository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return user; + } + + #endregion + + #region GetByIdAsync Tests + + [TestMethod] + public async Task GetByIdAsync_Should_Return_User_By_Id() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + + // Act + var result = await _repository.GetByIdAsync(user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.Id.ShouldBe(user.Id); + }); + } + + [TestMethod] + public async Task GetByIdAsync_Should_Return_Null_For_NonExistent_User() + { + // Arrange + var nonExistentId = Guid.NewGuid(); + + // Act + var result = await _repository.GetByIdAsync(nonExistentId, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetByIdAsync_Should_Load_Related_Entities() + { + // Arrange + var user = UserFaker().Generate(); + var invoice = InvoiceFaker() + .RuleFor(u => u.UserId, _ => user.Id) + .Generate(); + + user.Invoices.Add(invoice); + + _ = await CreateAndSaveUserAsync(user); + + // Act + var result = await _repository.GetByIdAsync(user.Id, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + _ = result.Invoices.ShouldNotBeNull(); + result.Invoices.Count.ShouldBeGreaterThan(0); + }); + } + + [TestMethod] + public async Task GetByIdAsync_Should_Throw_Exception_On_Database_Error() + { + // Arrange + var disposedContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var logger = Substitute.For>(); + + var repository = new UserRepository(disposedContext, logger); + + _ = logger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + await disposedContext.DisposeAsync(); + + _ = await Should.ThrowAsync( + async () => await repository.GetByIdAsync(Guid.NewGuid(), TestContext.CancellationToken) + ); + + logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion + + #region GetByEmailAsync Tests + + [TestMethod] + public async Task GetByEmailAsync_Should_Return_User_By_Email() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + + // Act + var result = await _repository.GetByEmailAsync(user.Email, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + result.Id.ShouldBe(user.Id); + }); + } + + [TestMethod] + public async Task GetByEmailAsync_Should_Return_Null_For_NonExistent_Email() + { + // Arrange + var nonExistentEmail = "nonexistent@example.com"; + + // Act + var result = await _repository.GetByEmailAsync(nonExistentEmail, TestContext.CancellationToken); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetByEmailAsync_Should_Find_User_With_Multiple_Related_Entities() + { + // Arrange + var user = UserFaker().Generate(); + var emailToken = EmailAuthTokenFaker() + .RuleFor(e => e.UserId, _ => user.Id) + .Generate(); + + user.EmailAuthTokens.Add(emailToken); + + _ = await CreateAndSaveUserAsync(user); + + // Act + var result = await _repository.GetByEmailAsync(user.Email, TestContext.CancellationToken); + + // Assert + result.ShouldSatisfyAllConditions(() => + { + _ = result.ShouldNotBeNull(); + _ = result.ShouldBeOfType(); + _ = result.EmailAuthTokens.ShouldNotBeNull(); + result.EmailAuthTokens.Count.ShouldBeGreaterThan(0); + }); + } + + [TestMethod] + public async Task GetByEmailAsync_Should_Be_Case_Sensitive() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + + // Act + var result = await _repository.GetByEmailAsync( + user.Email.ToUpper(), + TestContext.CancellationToken + ); + + // Assert + result.ShouldBeNull(); + } + + [TestMethod] + public async Task GetByEmailAsync_Should_Throw_Exception_On_Database_Error() + { + // Arrange + var disposedContext = new CoreDbContext(new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options); + + var logger = Substitute.For>(); + + var repository = new UserRepository(disposedContext, logger); + + _ = logger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + await disposedContext.DisposeAsync(); + + _ = await Should.ThrowAsync( + async () => await repository.GetByEmailAsync("test@example.com", TestContext.CancellationToken) + ); + + logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion + + #region CancellationToken Tests + + [TestMethod] + public async Task GetByIdAsync_Should_Handle_Cancellation_Request() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetByIdAsync(user.Id, cts.Token) + ); + + _repositoryLogger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + [TestMethod] + public async Task GetByEmailAsync_Should_Handle_Cancellation_Request() + { + // Arrange + var user = await CreateAndSaveUserAsync(); + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetByEmailAsync(user.Email, cts.Token) + ); + + _repositoryLogger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #endregion +} diff --git a/InvoiceReminder.IntegrationTests/InvoiceReminder.IntegrationTests.csproj b/InvoiceReminder.IntegrationTests/InvoiceReminder.IntegrationTests.csproj new file mode 100644 index 0000000..4e07c9e --- /dev/null +++ b/InvoiceReminder.IntegrationTests/InvoiceReminder.IntegrationTests.csproj @@ -0,0 +1,40 @@ + + + + CA1873 + + + + true + Exe + false + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/InvoiceReminder.API.UnitTests/MSTestSettings.cs b/InvoiceReminder.IntegrationTests/MSTestSettings.cs similarity index 100% rename from InvoiceReminder.API.UnitTests/MSTestSettings.cs rename to InvoiceReminder.IntegrationTests/MSTestSettings.cs diff --git a/InvoiceReminder.API.UnitTests/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs b/InvoiceReminder.UnitTests.API/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs similarity index 98% rename from InvoiceReminder.API.UnitTests/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs rename to InvoiceReminder.UnitTests.API/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs index 03d6310..1022bfc 100644 --- a/InvoiceReminder.API.UnitTests/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs +++ b/InvoiceReminder.UnitTests.API/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs @@ -5,7 +5,7 @@ using NSubstitute; using Shouldly; -namespace InvoiceReminder.API.UnitTests.AuthenticationSetup; +namespace InvoiceReminder.UnitTests.API.AuthenticationSetup; [TestClass] public sealed class BearerSecuritySchemeTransformerTests diff --git a/InvoiceReminder.API.UnitTests/AuthenticationSetup/JwtBearerOptionsSetupTests.cs b/InvoiceReminder.UnitTests.API/AuthenticationSetup/JwtBearerOptionsSetupTests.cs similarity index 98% rename from InvoiceReminder.API.UnitTests/AuthenticationSetup/JwtBearerOptionsSetupTests.cs rename to InvoiceReminder.UnitTests.API/AuthenticationSetup/JwtBearerOptionsSetupTests.cs index 7353a93..81f24b3 100644 --- a/InvoiceReminder.API.UnitTests/AuthenticationSetup/JwtBearerOptionsSetupTests.cs +++ b/InvoiceReminder.UnitTests.API/AuthenticationSetup/JwtBearerOptionsSetupTests.cs @@ -6,7 +6,7 @@ using Shouldly; using System.Text; -namespace InvoiceReminder.API.UnitTests.AuthenticationSetup; +namespace InvoiceReminder.UnitTests.API.AuthenticationSetup; [TestClass] public sealed class JwtBearerOptionsSetupTests diff --git a/InvoiceReminder.API.UnitTests/AuthenticationSetup/JwtOptionsSetupTests.cs b/InvoiceReminder.UnitTests.API/AuthenticationSetup/JwtOptionsSetupTests.cs similarity index 98% rename from InvoiceReminder.API.UnitTests/AuthenticationSetup/JwtOptionsSetupTests.cs rename to InvoiceReminder.UnitTests.API/AuthenticationSetup/JwtOptionsSetupTests.cs index 527553f..a749f51 100644 --- a/InvoiceReminder.API.UnitTests/AuthenticationSetup/JwtOptionsSetupTests.cs +++ b/InvoiceReminder.UnitTests.API/AuthenticationSetup/JwtOptionsSetupTests.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Configuration; using Shouldly; -namespace InvoiceReminder.API.UnitTests.AuthenticationSetup; +namespace InvoiceReminder.UnitTests.API.AuthenticationSetup; [TestClass] public sealed class JwtOptionsSetupTests diff --git a/InvoiceReminder.API.UnitTests/AuthenticationSetup/LoginRequestTests.cs b/InvoiceReminder.UnitTests.API/AuthenticationSetup/LoginRequestTests.cs similarity index 72% rename from InvoiceReminder.API.UnitTests/AuthenticationSetup/LoginRequestTests.cs rename to InvoiceReminder.UnitTests.API/AuthenticationSetup/LoginRequestTests.cs index 8e50955..84a64e8 100644 --- a/InvoiceReminder.API.UnitTests/AuthenticationSetup/LoginRequestTests.cs +++ b/InvoiceReminder.UnitTests.API/AuthenticationSetup/LoginRequestTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.API.AuthenticationSetup; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; -namespace InvoiceReminder.API.UnitTests.AuthenticationSetup; +namespace InvoiceReminder.UnitTests.API.AuthenticationSetup; [TestClass] public sealed class LoginRequestTests diff --git a/InvoiceReminder.API.UnitTests/AuthenticationSetup/MockAuthenticationHandler.cs b/InvoiceReminder.UnitTests.API/AuthenticationSetup/MockAuthenticationHandler.cs similarity index 92% rename from InvoiceReminder.API.UnitTests/AuthenticationSetup/MockAuthenticationHandler.cs rename to InvoiceReminder.UnitTests.API/AuthenticationSetup/MockAuthenticationHandler.cs index d10da6e..b0bc1f6 100644 --- a/InvoiceReminder.API.UnitTests/AuthenticationSetup/MockAuthenticationHandler.cs +++ b/InvoiceReminder.UnitTests.API/AuthenticationSetup/MockAuthenticationHandler.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Http; using System.Diagnostics.CodeAnalysis; -namespace InvoiceReminder.API.UnitTests.AuthenticationSetup; +namespace InvoiceReminder.UnitTests.API.AuthenticationSetup; [ExcludeFromCodeCoverage] internal sealed class MockAuthenticationHandler : IAuthenticationHandler diff --git a/InvoiceReminder.API.UnitTests/Endpoints/GoogleOAuthEndpointsTests.cs b/InvoiceReminder.UnitTests.API/Endpoints/GoogleOAuthEndpointsTests.cs similarity index 99% rename from InvoiceReminder.API.UnitTests/Endpoints/GoogleOAuthEndpointsTests.cs rename to InvoiceReminder.UnitTests.API/Endpoints/GoogleOAuthEndpointsTests.cs index b581244..01ae8e4 100644 --- a/InvoiceReminder.API.UnitTests/Endpoints/GoogleOAuthEndpointsTests.cs +++ b/InvoiceReminder.UnitTests.API/Endpoints/GoogleOAuthEndpointsTests.cs @@ -1,9 +1,9 @@ using Google.Apis.Auth.OAuth2; using Google.Apis.Auth.OAuth2.Flows; using Google.Apis.Gmail.v1; -using InvoiceReminder.API.UnitTests.Factories; using InvoiceReminder.Domain.Abstractions; using InvoiceReminder.ExternalServices.Gmail; +using InvoiceReminder.UnitTests.API.Factories; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; @@ -15,7 +15,7 @@ using System.Net.Http.Json; using System.Security.Claims; -namespace InvoiceReminder.API.UnitTests.Endpoints; +namespace InvoiceReminder.UnitTests.API.Endpoints; [TestClass] public sealed class GoogleOAuthEndpointsTests diff --git a/InvoiceReminder.API.UnitTests/Endpoints/InvoiceEndpointsTests.cs b/InvoiceReminder.UnitTests.API/Endpoints/InvoiceEndpointsTests.cs similarity index 99% rename from InvoiceReminder.API.UnitTests/Endpoints/InvoiceEndpointsTests.cs rename to InvoiceReminder.UnitTests.API/Endpoints/InvoiceEndpointsTests.cs index d258d3c..23dcbd5 100644 --- a/InvoiceReminder.API.UnitTests/Endpoints/InvoiceEndpointsTests.cs +++ b/InvoiceReminder.UnitTests.API/Endpoints/InvoiceEndpointsTests.cs @@ -1,8 +1,8 @@ using Bogus; -using InvoiceReminder.API.UnitTests.Factories; using InvoiceReminder.Application.Interfaces; using InvoiceReminder.Application.ViewModels; using InvoiceReminder.Domain.Abstractions; +using InvoiceReminder.UnitTests.API.Factories; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -15,7 +15,7 @@ using System.Net.Http.Json; using System.Security.Claims; -namespace InvoiceReminder.API.UnitTests.Endpoints; +namespace InvoiceReminder.UnitTests.API.Endpoints; [TestClass] public sealed class InvoiceEndpointsTests diff --git a/InvoiceReminder.API.UnitTests/Endpoints/JobScheduleEndPointsTests.cs b/InvoiceReminder.UnitTests.API/Endpoints/JobScheduleEndPointsTests.cs similarity index 99% rename from InvoiceReminder.API.UnitTests/Endpoints/JobScheduleEndPointsTests.cs rename to InvoiceReminder.UnitTests.API/Endpoints/JobScheduleEndPointsTests.cs index ee2d111..3529a2b 100644 --- a/InvoiceReminder.API.UnitTests/Endpoints/JobScheduleEndPointsTests.cs +++ b/InvoiceReminder.UnitTests.API/Endpoints/JobScheduleEndPointsTests.cs @@ -1,8 +1,8 @@ using Bogus; -using InvoiceReminder.API.UnitTests.Factories; using InvoiceReminder.Application.Interfaces; using InvoiceReminder.Application.ViewModels; using InvoiceReminder.Domain.Abstractions; +using InvoiceReminder.UnitTests.API.Factories; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -15,7 +15,7 @@ using System.Net.Http.Json; using System.Security.Claims; -namespace InvoiceReminder.API.UnitTests.Endpoints; +namespace InvoiceReminder.UnitTests.API.Endpoints; [TestClass] public sealed class JobScheduleEndPointsTests diff --git a/InvoiceReminder.API.UnitTests/Endpoints/LoginEndpointTests.cs b/InvoiceReminder.UnitTests.API/Endpoints/LoginEndpointTests.cs similarity index 98% rename from InvoiceReminder.API.UnitTests/Endpoints/LoginEndpointTests.cs rename to InvoiceReminder.UnitTests.API/Endpoints/LoginEndpointTests.cs index 6619fc8..2720c13 100644 --- a/InvoiceReminder.API.UnitTests/Endpoints/LoginEndpointTests.cs +++ b/InvoiceReminder.UnitTests.API/Endpoints/LoginEndpointTests.cs @@ -1,12 +1,12 @@ using Bogus; using InvoiceReminder.API.AuthenticationSetup; -using InvoiceReminder.API.UnitTests.Factories; using InvoiceReminder.Application.Interfaces; using InvoiceReminder.Application.ViewModels; using InvoiceReminder.Authentication.Extensions; using InvoiceReminder.Authentication.Interfaces; using InvoiceReminder.Authentication.Jwt; using InvoiceReminder.Domain.Abstractions; +using InvoiceReminder.UnitTests.API.Factories; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; @@ -16,7 +16,7 @@ using System.Net; using System.Net.Http.Json; -namespace InvoiceReminder.API.UnitTests.Endpoints; +namespace InvoiceReminder.UnitTests.API.Endpoints; [TestClass] public sealed class LoginEndpointTests diff --git a/InvoiceReminder.API.UnitTests/Endpoints/ScanEmailDefinitionEndpointsTests.cs b/InvoiceReminder.UnitTests.API/Endpoints/ScanEmailDefinitionEndpointsTests.cs similarity index 99% rename from InvoiceReminder.API.UnitTests/Endpoints/ScanEmailDefinitionEndpointsTests.cs rename to InvoiceReminder.UnitTests.API/Endpoints/ScanEmailDefinitionEndpointsTests.cs index 6ed3c07..231e151 100644 --- a/InvoiceReminder.API.UnitTests/Endpoints/ScanEmailDefinitionEndpointsTests.cs +++ b/InvoiceReminder.UnitTests.API/Endpoints/ScanEmailDefinitionEndpointsTests.cs @@ -1,9 +1,9 @@ using Bogus; -using InvoiceReminder.API.UnitTests.Factories; using InvoiceReminder.Application.Interfaces; using InvoiceReminder.Application.ViewModels; using InvoiceReminder.Domain.Abstractions; using InvoiceReminder.Domain.Enums; +using InvoiceReminder.UnitTests.API.Factories; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; @@ -15,7 +15,7 @@ using System.Net.Http.Json; using System.Security.Claims; -namespace InvoiceReminder.API.UnitTests.Endpoints; +namespace InvoiceReminder.UnitTests.API.Endpoints; [TestClass] public sealed class ScanEmailDefinitionEndpointsTests diff --git a/InvoiceReminder.API.UnitTests/Endpoints/SendMessageEndpointsTests.cs b/InvoiceReminder.UnitTests.API/Endpoints/SendMessageEndpointsTests.cs similarity index 99% rename from InvoiceReminder.API.UnitTests/Endpoints/SendMessageEndpointsTests.cs rename to InvoiceReminder.UnitTests.API/Endpoints/SendMessageEndpointsTests.cs index 4046ca3..524b8cd 100644 --- a/InvoiceReminder.API.UnitTests/Endpoints/SendMessageEndpointsTests.cs +++ b/InvoiceReminder.UnitTests.API/Endpoints/SendMessageEndpointsTests.cs @@ -1,5 +1,5 @@ -using InvoiceReminder.API.UnitTests.Factories; using InvoiceReminder.ExternalServices.SendMessage; +using InvoiceReminder.UnitTests.API.Factories; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; @@ -11,7 +11,7 @@ using System.Net.Http.Json; using System.Security.Claims; -namespace InvoiceReminder.API.UnitTests.Endpoints; +namespace InvoiceReminder.UnitTests.API.Endpoints; [TestClass] public sealed class SendMessageEndpointsTests diff --git a/InvoiceReminder.API.UnitTests/Endpoints/UserEndpointsTests.cs b/InvoiceReminder.UnitTests.API/Endpoints/UserEndpointsTests.cs similarity index 99% rename from InvoiceReminder.API.UnitTests/Endpoints/UserEndpointsTests.cs rename to InvoiceReminder.UnitTests.API/Endpoints/UserEndpointsTests.cs index dfa2513..25e1c37 100644 --- a/InvoiceReminder.API.UnitTests/Endpoints/UserEndpointsTests.cs +++ b/InvoiceReminder.UnitTests.API/Endpoints/UserEndpointsTests.cs @@ -1,8 +1,8 @@ using Bogus; -using InvoiceReminder.API.UnitTests.Factories; using InvoiceReminder.Application.Interfaces; using InvoiceReminder.Application.ViewModels; using InvoiceReminder.Domain.Abstractions; +using InvoiceReminder.UnitTests.API.Factories; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -15,7 +15,7 @@ using System.Net.Http.Json; using System.Security.Claims; -namespace InvoiceReminder.API.UnitTests.Endpoints; +namespace InvoiceReminder.UnitTests.API.Endpoints; [TestClass] public sealed class UserEndpointsTests diff --git a/InvoiceReminder.API.UnitTests/Factories/CustomWebApplicationFactory.cs b/InvoiceReminder.UnitTests.API/Factories/CustomWebApplicationFactory.cs similarity index 97% rename from InvoiceReminder.API.UnitTests/Factories/CustomWebApplicationFactory.cs rename to InvoiceReminder.UnitTests.API/Factories/CustomWebApplicationFactory.cs index d5201e8..6c1dc51 100644 --- a/InvoiceReminder.API.UnitTests/Factories/CustomWebApplicationFactory.cs +++ b/InvoiceReminder.UnitTests.API/Factories/CustomWebApplicationFactory.cs @@ -10,7 +10,7 @@ using Microsoft.Extensions.Hosting; using NSubstitute; -namespace InvoiceReminder.API.UnitTests.Factories; +namespace InvoiceReminder.UnitTests.API.Factories; public class CustomWebApplicationFactory : WebApplicationFactory where TStartup : class { diff --git a/InvoiceReminder.API.UnitTests/InvoiceReminder.API.UnitTests.csproj b/InvoiceReminder.UnitTests.API/InvoiceReminder.UnitTests.API.csproj similarity index 75% rename from InvoiceReminder.API.UnitTests/InvoiceReminder.API.UnitTests.csproj rename to InvoiceReminder.UnitTests.API/InvoiceReminder.UnitTests.API.csproj index 46c3e67..d18b759 100644 --- a/InvoiceReminder.API.UnitTests/InvoiceReminder.API.UnitTests.csproj +++ b/InvoiceReminder.UnitTests.API/InvoiceReminder.UnitTests.API.csproj @@ -1,10 +1,6 @@  - true Exe false @@ -12,7 +8,7 @@ - + diff --git a/InvoiceReminder.Application.UnitTests/MSTestSettings.cs b/InvoiceReminder.UnitTests.API/MSTestSettings.cs similarity index 100% rename from InvoiceReminder.Application.UnitTests/MSTestSettings.cs rename to InvoiceReminder.UnitTests.API/MSTestSettings.cs diff --git a/InvoiceReminder.Application.UnitTests/AppServices/BaseAppServiceTests.cs b/InvoiceReminder.UnitTests.Application/AppServices/BaseAppServiceTests.cs similarity index 99% rename from InvoiceReminder.Application.UnitTests/AppServices/BaseAppServiceTests.cs rename to InvoiceReminder.UnitTests.Application/AppServices/BaseAppServiceTests.cs index 12ea633..ab95bcd 100644 --- a/InvoiceReminder.Application.UnitTests/AppServices/BaseAppServiceTests.cs +++ b/InvoiceReminder.UnitTests.Application/AppServices/BaseAppServiceTests.cs @@ -5,7 +5,7 @@ using NSubstitute; using Shouldly; -namespace InvoiceReminder.Application.UnitTests.AppServices; +namespace InvoiceReminder.UnitTests.Application.AppServices; [TestClass] public sealed class BaseAppServiceTests diff --git a/InvoiceReminder.Application.UnitTests/AppServices/EmailAuthTokenAppServiceTests.cs b/InvoiceReminder.UnitTests.Application/AppServices/EmailAuthTokenAppServiceTests.cs similarity index 98% rename from InvoiceReminder.Application.UnitTests/AppServices/EmailAuthTokenAppServiceTests.cs rename to InvoiceReminder.UnitTests.Application/AppServices/EmailAuthTokenAppServiceTests.cs index bec2386..984c7ef 100644 --- a/InvoiceReminder.Application.UnitTests/AppServices/EmailAuthTokenAppServiceTests.cs +++ b/InvoiceReminder.UnitTests.Application/AppServices/EmailAuthTokenAppServiceTests.cs @@ -6,7 +6,7 @@ using NSubstitute; using Shouldly; -namespace InvoiceReminder.Application.UnitTests.AppServices; +namespace InvoiceReminder.UnitTests.Application.AppServices; [TestClass] public sealed class EmailAuthTokenAppServiceTests diff --git a/InvoiceReminder.Application.UnitTests/AppServices/InvoiceAppServiceTests.cs b/InvoiceReminder.UnitTests.Application/AppServices/InvoiceAppServiceTests.cs similarity index 98% rename from InvoiceReminder.Application.UnitTests/AppServices/InvoiceAppServiceTests.cs rename to InvoiceReminder.UnitTests.Application/AppServices/InvoiceAppServiceTests.cs index de971ac..84c5f9a 100644 --- a/InvoiceReminder.Application.UnitTests/AppServices/InvoiceAppServiceTests.cs +++ b/InvoiceReminder.UnitTests.Application/AppServices/InvoiceAppServiceTests.cs @@ -7,7 +7,7 @@ using NSubstitute; using Shouldly; -namespace InvoiceReminder.Application.UnitTests.AppServices; +namespace InvoiceReminder.UnitTests.Application.AppServices; [TestClass] public sealed class InvoiceAppServiceTests diff --git a/InvoiceReminder.Application.UnitTests/AppServices/JobScheduleAppServiceTests.cs b/InvoiceReminder.UnitTests.Application/AppServices/JobScheduleAppServiceTests.cs similarity index 99% rename from InvoiceReminder.Application.UnitTests/AppServices/JobScheduleAppServiceTests.cs rename to InvoiceReminder.UnitTests.Application/AppServices/JobScheduleAppServiceTests.cs index 4c5a3cf..4fb657b 100644 --- a/InvoiceReminder.Application.UnitTests/AppServices/JobScheduleAppServiceTests.cs +++ b/InvoiceReminder.UnitTests.Application/AppServices/JobScheduleAppServiceTests.cs @@ -9,7 +9,7 @@ using Quartz.Spi; using Shouldly; -namespace InvoiceReminder.Application.UnitTests.AppServices; +namespace InvoiceReminder.UnitTests.Application.AppServices; [TestClass] public sealed class JobScheduleAppServiceTests diff --git a/InvoiceReminder.Application.UnitTests/AppServices/ScanEmailDefinitionAppServiceTests.cs b/InvoiceReminder.UnitTests.Application/AppServices/ScanEmailDefinitionAppServiceTests.cs similarity index 99% rename from InvoiceReminder.Application.UnitTests/AppServices/ScanEmailDefinitionAppServiceTests.cs rename to InvoiceReminder.UnitTests.Application/AppServices/ScanEmailDefinitionAppServiceTests.cs index b391a19..5efcbc3 100644 --- a/InvoiceReminder.Application.UnitTests/AppServices/ScanEmailDefinitionAppServiceTests.cs +++ b/InvoiceReminder.UnitTests.Application/AppServices/ScanEmailDefinitionAppServiceTests.cs @@ -9,7 +9,7 @@ using NSubstitute; using Shouldly; -namespace InvoiceReminder.Application.UnitTests.AppServices; +namespace InvoiceReminder.UnitTests.Application.AppServices; [TestClass] public sealed class ScanEmailDefinitionAppServiceTests diff --git a/InvoiceReminder.Application.UnitTests/AppServices/UserAppServiceTests.cs b/InvoiceReminder.UnitTests.Application/AppServices/UserAppServiceTests.cs similarity index 98% rename from InvoiceReminder.Application.UnitTests/AppServices/UserAppServiceTests.cs rename to InvoiceReminder.UnitTests.Application/AppServices/UserAppServiceTests.cs index e2e420c..09af49e 100644 --- a/InvoiceReminder.Application.UnitTests/AppServices/UserAppServiceTests.cs +++ b/InvoiceReminder.UnitTests.Application/AppServices/UserAppServiceTests.cs @@ -8,7 +8,7 @@ using NSubstitute; using Shouldly; -namespace InvoiceReminder.Application.UnitTests.AppServices; +namespace InvoiceReminder.UnitTests.Application.AppServices; [TestClass] public sealed class UserAppServiceTests diff --git a/InvoiceReminder.Application.UnitTests/InvoiceReminder.Application.UnitTests.csproj b/InvoiceReminder.UnitTests.Application/InvoiceReminder.UnitTests.Application.csproj similarity index 73% rename from InvoiceReminder.Application.UnitTests/InvoiceReminder.Application.UnitTests.csproj rename to InvoiceReminder.UnitTests.Application/InvoiceReminder.UnitTests.Application.csproj index a65a181..0a9af9a 100644 --- a/InvoiceReminder.Application.UnitTests/InvoiceReminder.Application.UnitTests.csproj +++ b/InvoiceReminder.UnitTests.Application/InvoiceReminder.UnitTests.Application.csproj @@ -1,10 +1,6 @@  - true Exe false @@ -12,7 +8,7 @@ - + diff --git a/InvoiceReminder.DomainEntities.UnitTests/MSTestSettings.cs b/InvoiceReminder.UnitTests.Application/MSTestSettings.cs similarity index 100% rename from InvoiceReminder.DomainEntities.UnitTests/MSTestSettings.cs rename to InvoiceReminder.UnitTests.Application/MSTestSettings.cs diff --git a/InvoiceReminder.Application.UnitTests/ViewModels/EmailAuthTokenViewModelTests.cs b/InvoiceReminder.UnitTests.Application/ViewModels/EmailAuthTokenViewModelTests.cs similarity index 88% rename from InvoiceReminder.Application.UnitTests/ViewModels/EmailAuthTokenViewModelTests.cs rename to InvoiceReminder.UnitTests.Application/ViewModels/EmailAuthTokenViewModelTests.cs index cc749e1..b604a02 100644 --- a/InvoiceReminder.Application.UnitTests/ViewModels/EmailAuthTokenViewModelTests.cs +++ b/InvoiceReminder.UnitTests.Application/ViewModels/EmailAuthTokenViewModelTests.cs @@ -1,8 +1,8 @@ using InvoiceReminder.Application.ViewModels; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; using Shouldly; -namespace InvoiceReminder.Application.UnitTests.ViewModels; +namespace InvoiceReminder.UnitTests.Application.ViewModels; [TestClass] public sealed class EmailAuthTokenViewModelTests diff --git a/InvoiceReminder.Application.UnitTests/ViewModels/InvoiceViewModelTests.cs b/InvoiceReminder.UnitTests.Application/ViewModels/InvoiceViewModelTests.cs similarity index 73% rename from InvoiceReminder.Application.UnitTests/ViewModels/InvoiceViewModelTests.cs rename to InvoiceReminder.UnitTests.Application/ViewModels/InvoiceViewModelTests.cs index 02c993e..8fb32b1 100644 --- a/InvoiceReminder.Application.UnitTests/ViewModels/InvoiceViewModelTests.cs +++ b/InvoiceReminder.UnitTests.Application/ViewModels/InvoiceViewModelTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Application.ViewModels; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; -namespace InvoiceReminder.Application.UnitTests.ViewModels; +namespace InvoiceReminder.UnitTests.Application.ViewModels; [TestClass] public sealed class InvoiceViewModelTests diff --git a/InvoiceReminder.Application.UnitTests/ViewModels/JobScheduleViewmodelTests.cs b/InvoiceReminder.UnitTests.Application/ViewModels/JobScheduleViewmodelTests.cs similarity index 74% rename from InvoiceReminder.Application.UnitTests/ViewModels/JobScheduleViewmodelTests.cs rename to InvoiceReminder.UnitTests.Application/ViewModels/JobScheduleViewmodelTests.cs index 35ceb02..fa4806c 100644 --- a/InvoiceReminder.Application.UnitTests/ViewModels/JobScheduleViewmodelTests.cs +++ b/InvoiceReminder.UnitTests.Application/ViewModels/JobScheduleViewmodelTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Application.ViewModels; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; -namespace InvoiceReminder.Application.UnitTests.ViewModels; +namespace InvoiceReminder.UnitTests.Application.ViewModels; [TestClass] public sealed class JobScheduleViewmodelTests diff --git a/InvoiceReminder.Application.UnitTests/ViewModels/ScanEmailDefinitionViewModelTests.cs b/InvoiceReminder.UnitTests.Application/ViewModels/ScanEmailDefinitionViewModelTests.cs similarity index 75% rename from InvoiceReminder.Application.UnitTests/ViewModels/ScanEmailDefinitionViewModelTests.cs rename to InvoiceReminder.UnitTests.Application/ViewModels/ScanEmailDefinitionViewModelTests.cs index c332c93..1fccdf5 100644 --- a/InvoiceReminder.Application.UnitTests/ViewModels/ScanEmailDefinitionViewModelTests.cs +++ b/InvoiceReminder.UnitTests.Application/ViewModels/ScanEmailDefinitionViewModelTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Application.ViewModels; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; -namespace InvoiceReminder.Application.UnitTests.ViewModels; +namespace InvoiceReminder.UnitTests.Application.ViewModels; [TestClass] public sealed class ScanEmailDefinitionViewModelTests diff --git a/InvoiceReminder.Application.UnitTests/ViewModels/UserViewModelTests.cs b/InvoiceReminder.UnitTests.Application/ViewModels/UserViewModelTests.cs similarity index 81% rename from InvoiceReminder.Application.UnitTests/ViewModels/UserViewModelTests.cs rename to InvoiceReminder.UnitTests.Application/ViewModels/UserViewModelTests.cs index ec312bc..3085a43 100644 --- a/InvoiceReminder.Application.UnitTests/ViewModels/UserViewModelTests.cs +++ b/InvoiceReminder.UnitTests.Application/ViewModels/UserViewModelTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Application.ViewModels; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; -namespace InvoiceReminder.Application.UnitTests.ViewModels; +namespace InvoiceReminder.UnitTests.Application.ViewModels; [TestClass] public sealed class UserViewModelTests diff --git a/InvoiceReminder.DomainEntities.UnitTests/Entities/EmailAuthTokenTests.cs b/InvoiceReminder.UnitTests.Domain/Entities/EmailAuthTokenTests.cs similarity index 73% rename from InvoiceReminder.DomainEntities.UnitTests/Entities/EmailAuthTokenTests.cs rename to InvoiceReminder.UnitTests.Domain/Entities/EmailAuthTokenTests.cs index 8b4ec1b..de04bb6 100644 --- a/InvoiceReminder.DomainEntities.UnitTests/Entities/EmailAuthTokenTests.cs +++ b/InvoiceReminder.UnitTests.Domain/Entities/EmailAuthTokenTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Domain.Entities; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; -namespace InvoiceReminder.DomainEntities.UnitTests.Entities; +namespace InvoiceReminder.UnitTests.Domain.Entities; [TestClass] public sealed class EmailAuthTokenTests diff --git a/InvoiceReminder.DomainEntities.UnitTests/Entities/InvoiceTests.cs b/InvoiceReminder.UnitTests.Domain/Entities/InvoiceTests.cs similarity index 72% rename from InvoiceReminder.DomainEntities.UnitTests/Entities/InvoiceTests.cs rename to InvoiceReminder.UnitTests.Domain/Entities/InvoiceTests.cs index a518df2..15d99be 100644 --- a/InvoiceReminder.DomainEntities.UnitTests/Entities/InvoiceTests.cs +++ b/InvoiceReminder.UnitTests.Domain/Entities/InvoiceTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Domain.Entities; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; -namespace InvoiceReminder.DomainEntities.UnitTests.Entities; +namespace InvoiceReminder.UnitTests.Domain.Entities; [TestClass] public sealed class InvoiceTests diff --git a/InvoiceReminder.DomainEntities.UnitTests/Entities/JobScheduleTests.cs b/InvoiceReminder.UnitTests.Domain/Entities/JobScheduleTests.cs similarity index 72% rename from InvoiceReminder.DomainEntities.UnitTests/Entities/JobScheduleTests.cs rename to InvoiceReminder.UnitTests.Domain/Entities/JobScheduleTests.cs index c1d3193..e7ef170 100644 --- a/InvoiceReminder.DomainEntities.UnitTests/Entities/JobScheduleTests.cs +++ b/InvoiceReminder.UnitTests.Domain/Entities/JobScheduleTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Domain.Entities; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; -namespace InvoiceReminder.DomainEntities.UnitTests.Entities; +namespace InvoiceReminder.UnitTests.Domain.Entities; [TestClass] public sealed class JobScheduleTests diff --git a/InvoiceReminder.DomainEntities.UnitTests/Entities/ScanEmailDefinitionTests.cs b/InvoiceReminder.UnitTests.Domain/Entities/ScanEmailDefinitionTests.cs similarity index 74% rename from InvoiceReminder.DomainEntities.UnitTests/Entities/ScanEmailDefinitionTests.cs rename to InvoiceReminder.UnitTests.Domain/Entities/ScanEmailDefinitionTests.cs index 8ee85fa..3349a31 100644 --- a/InvoiceReminder.DomainEntities.UnitTests/Entities/ScanEmailDefinitionTests.cs +++ b/InvoiceReminder.UnitTests.Domain/Entities/ScanEmailDefinitionTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Domain.Entities; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; -namespace InvoiceReminder.DomainEntities.UnitTests.Entities; +namespace InvoiceReminder.UnitTests.Domain.Entities; [TestClass] public sealed class ScanEmailDefinitionTests diff --git a/InvoiceReminder.DomainEntities.UnitTests/Entities/UserTests.cs b/InvoiceReminder.UnitTests.Domain/Entities/UserTests.cs similarity index 82% rename from InvoiceReminder.DomainEntities.UnitTests/Entities/UserTests.cs rename to InvoiceReminder.UnitTests.Domain/Entities/UserTests.cs index dacd1b9..2890bb8 100644 --- a/InvoiceReminder.DomainEntities.UnitTests/Entities/UserTests.cs +++ b/InvoiceReminder.UnitTests.Domain/Entities/UserTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Domain.Entities; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; -namespace InvoiceReminder.DomainEntities.UnitTests.Entities; +namespace InvoiceReminder.UnitTests.Domain.Entities; [TestClass] public sealed class UserTests diff --git a/InvoiceReminder.DomainEntities.UnitTests/Extensions/EntityExtensionsTests.cs b/InvoiceReminder.UnitTests.Domain/Extensions/EntityExtensionsTests.cs similarity index 99% rename from InvoiceReminder.DomainEntities.UnitTests/Extensions/EntityExtensionsTests.cs rename to InvoiceReminder.UnitTests.Domain/Extensions/EntityExtensionsTests.cs index 740e9e9..d62872c 100644 --- a/InvoiceReminder.DomainEntities.UnitTests/Extensions/EntityExtensionsTests.cs +++ b/InvoiceReminder.UnitTests.Domain/Extensions/EntityExtensionsTests.cs @@ -2,7 +2,7 @@ using InvoiceReminder.Domain.Extensions; using Shouldly; -namespace InvoiceReminder.DomainEntities.UnitTests.Extensions; +namespace InvoiceReminder.UnitTests.Domain.Extensions; [TestClass] public sealed class EntityExtensionsTests diff --git a/InvoiceReminder.DomainEntities.UnitTests/Extensions/UserExtensionsTests.cs b/InvoiceReminder.UnitTests.Domain/Extensions/UserExtensionsTests.cs similarity index 99% rename from InvoiceReminder.DomainEntities.UnitTests/Extensions/UserExtensionsTests.cs rename to InvoiceReminder.UnitTests.Domain/Extensions/UserExtensionsTests.cs index fd37453..230bcaa 100644 --- a/InvoiceReminder.DomainEntities.UnitTests/Extensions/UserExtensionsTests.cs +++ b/InvoiceReminder.UnitTests.Domain/Extensions/UserExtensionsTests.cs @@ -4,7 +4,7 @@ using InvoiceReminder.Domain.Extensions; using Shouldly; -namespace InvoiceReminder.DomainEntities.UnitTests.Extensions; +namespace InvoiceReminder.UnitTests.Domain.Extensions; [TestClass] public sealed class UserExtensionsTests diff --git a/InvoiceReminder.DomainEntities.UnitTests/Extensions/UserParameterTests.cs b/InvoiceReminder.UnitTests.Domain/Extensions/UserParameterTests.cs similarity index 73% rename from InvoiceReminder.DomainEntities.UnitTests/Extensions/UserParameterTests.cs rename to InvoiceReminder.UnitTests.Domain/Extensions/UserParameterTests.cs index f5645a2..6860bec 100644 --- a/InvoiceReminder.DomainEntities.UnitTests/Extensions/UserParameterTests.cs +++ b/InvoiceReminder.UnitTests.Domain/Extensions/UserParameterTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Domain.Extensions; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; -namespace InvoiceReminder.DomainEntities.UnitTests.Extensions; +namespace InvoiceReminder.UnitTests.Domain.Extensions; [TestClass] public sealed class UserParametersTests diff --git a/InvoiceReminder.DomainEntities.UnitTests/InvoiceReminder.DomainEntities.UnitTests.csproj b/InvoiceReminder.UnitTests.Domain/InvoiceReminder.UnitTests.Domain.csproj similarity index 70% rename from InvoiceReminder.DomainEntities.UnitTests/InvoiceReminder.DomainEntities.UnitTests.csproj rename to InvoiceReminder.UnitTests.Domain/InvoiceReminder.UnitTests.Domain.csproj index ccd4db7..f3effe2 100644 --- a/InvoiceReminder.DomainEntities.UnitTests/InvoiceReminder.DomainEntities.UnitTests.csproj +++ b/InvoiceReminder.UnitTests.Domain/InvoiceReminder.UnitTests.Domain.csproj @@ -1,10 +1,6 @@  - true Exe false @@ -12,7 +8,7 @@ - + diff --git a/InvoiceReminder.ExternalServices.UnitTests/MSTestSettings.cs b/InvoiceReminder.UnitTests.Domain/MSTestSettings.cs similarity index 100% rename from InvoiceReminder.ExternalServices.UnitTests/MSTestSettings.cs rename to InvoiceReminder.UnitTests.Domain/MSTestSettings.cs diff --git a/InvoiceReminder.DomainEntities.UnitTests/Services/Configuration/ConfigurationServiceTests.cs b/InvoiceReminder.UnitTests.Domain/Services/Configuration/ConfigurationServiceTests.cs similarity index 98% rename from InvoiceReminder.DomainEntities.UnitTests/Services/Configuration/ConfigurationServiceTests.cs rename to InvoiceReminder.UnitTests.Domain/Services/Configuration/ConfigurationServiceTests.cs index 68af618..c4aa7b2 100644 --- a/InvoiceReminder.DomainEntities.UnitTests/Services/Configuration/ConfigurationServiceTests.cs +++ b/InvoiceReminder.UnitTests.Domain/Services/Configuration/ConfigurationServiceTests.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.Configuration; using Shouldly; -namespace InvoiceReminder.DomainEntities.UnitTests.Services.Configuration; +namespace InvoiceReminder.UnitTests.Domain.Services.Configuration; [TestClass] public class ConfigurationServiceTests diff --git a/InvoiceReminder.DomainEntities.UnitTests/Services/Configuration/TokenCryptoServiceTests.cs b/InvoiceReminder.UnitTests.Domain/Services/Configuration/TokenCryptoServiceTests.cs similarity index 99% rename from InvoiceReminder.DomainEntities.UnitTests/Services/Configuration/TokenCryptoServiceTests.cs rename to InvoiceReminder.UnitTests.Domain/Services/Configuration/TokenCryptoServiceTests.cs index 8e700d3..1dc96b3 100644 --- a/InvoiceReminder.DomainEntities.UnitTests/Services/Configuration/TokenCryptoServiceTests.cs +++ b/InvoiceReminder.UnitTests.Domain/Services/Configuration/TokenCryptoServiceTests.cs @@ -3,7 +3,7 @@ using Shouldly; using System.Security.Cryptography; -namespace InvoiceReminder.DomainEntities.UnitTests.Services.Configuration; +namespace InvoiceReminder.UnitTests.Domain.Services.Configuration; [TestClass] public sealed class TokenCryptoServiceTests diff --git a/InvoiceReminder.ExternalServices.UnitTests/BarcodeReader/BarcodeReaderServiceTests.cs b/InvoiceReminder.UnitTests.ExternalServices/BarcodeReader/BarcodeReaderServiceTests.cs similarity index 93% rename from InvoiceReminder.ExternalServices.UnitTests/BarcodeReader/BarcodeReaderServiceTests.cs rename to InvoiceReminder.UnitTests.ExternalServices/BarcodeReader/BarcodeReaderServiceTests.cs index b0f22c3..e482fb6 100644 --- a/InvoiceReminder.ExternalServices.UnitTests/BarcodeReader/BarcodeReaderServiceTests.cs +++ b/InvoiceReminder.UnitTests.ExternalServices/BarcodeReader/BarcodeReaderServiceTests.cs @@ -9,7 +9,7 @@ using NSubstitute; using Shouldly; -namespace InvoiceReminder.ExternalServices.UnitTests.BarcodeReader; +namespace InvoiceReminder.UnitTests.ExternalServices.BarcodeReader; [TestClass] public sealed class BarcodeReaderServiceTests @@ -59,9 +59,19 @@ public void ReadTextContentFromPdf_ShouldThrowException_WhenPdfHasInvalidLength( var invalidPdfData = Array.Empty(); var beneficiary = _faker.Company.CompanyName(); + _ = _logger.IsEnabled(Arg.Any()).Returns(true); + // Act && Assert _ = Should.Throw(() => _barcodeReaderService.ReadTextContentFromPdf(invalidPdfData, beneficiary, InvoiceType.BankInvoice)); + + _logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); } [TestMethod] diff --git a/InvoiceReminder.ExternalServices.UnitTests/InvoiceReminder.ExternalServices.UnitTests.csproj b/InvoiceReminder.UnitTests.ExternalServices/InvoiceReminder.UnitTests.ExternalServices.csproj similarity index 79% rename from InvoiceReminder.ExternalServices.UnitTests/InvoiceReminder.ExternalServices.UnitTests.csproj rename to InvoiceReminder.UnitTests.ExternalServices/InvoiceReminder.UnitTests.ExternalServices.csproj index 561e297..0e6d6fb 100644 --- a/InvoiceReminder.ExternalServices.UnitTests/InvoiceReminder.ExternalServices.UnitTests.csproj +++ b/InvoiceReminder.UnitTests.ExternalServices/InvoiceReminder.UnitTests.ExternalServices.csproj @@ -1,10 +1,10 @@  + + CA1873 + + - true Exe false diff --git a/InvoiceReminder.Infrastructure.UnitTests/MSTestSettings.cs b/InvoiceReminder.UnitTests.ExternalServices/MSTestSettings.cs similarity index 100% rename from InvoiceReminder.Infrastructure.UnitTests/MSTestSettings.cs rename to InvoiceReminder.UnitTests.ExternalServices/MSTestSettings.cs diff --git a/InvoiceReminder.ExternalServices.UnitTests/SendMessage/SendMessageServiceTests.cs b/InvoiceReminder.UnitTests.ExternalServices/SendMessage/SendMessageServiceTests.cs similarity index 89% rename from InvoiceReminder.ExternalServices.UnitTests/SendMessage/SendMessageServiceTests.cs rename to InvoiceReminder.UnitTests.ExternalServices/SendMessage/SendMessageServiceTests.cs index 3e5ab35..07f8e80 100644 --- a/InvoiceReminder.ExternalServices.UnitTests/SendMessage/SendMessageServiceTests.cs +++ b/InvoiceReminder.UnitTests.ExternalServices/SendMessage/SendMessageServiceTests.cs @@ -11,7 +11,7 @@ using NSubstitute.ExceptionExtensions; using Shouldly; -namespace InvoiceReminder.ExternalServices.UnitTests.SendMessage; +namespace InvoiceReminder.UnitTests.ExternalServices.SendMessage; [TestClass] public sealed class SendMessageServiceTests @@ -283,12 +283,13 @@ public async Task SendMessage_ShouldNot_SendMessages_When_UserIsNull() _ = _barcodeReader.DidNotReceive().ReadTextContentFromPdf(Arg.Any(), Arg.Any(), Arg.Any()); _ = _telegramMessageService.DidNotReceive().SendMessageAsync(Arg.Any(), Arg.Any(), Arg.Any()); - var eventId = Arg.Any(); - var state = Arg.Is(o => o.ToString().Contains("User not found!")); - var loggedException = Arg.Is(e => e == null); - var formatter = Arg.Any>(); - - _logger.Received(1).Log(LogLevel.Warning, eventId, state, loggedException, formatter); + _logger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Is(o => o.ToString().Contains("User not found!")), + Arg.Any(), + Arg.Any>() + ); } [TestMethod] @@ -312,12 +313,13 @@ public async Task SendMessage_ShouldNot_SendMessages_When_UserHasNoAuthenticatio // Assert result.ShouldBe($"No Authentication Token found for userId: {user.Id}"); - var eventId = Arg.Any(); - var state = Arg.Is(o => o.ToString().Contains("No Authentication Token found")); - var loggedException = Arg.Is(e => e == null); - var formatter = Arg.Any>(); - - _logger.Received(1).Log(LogLevel.Warning, eventId, state, loggedException, formatter); + _logger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Is(o => o.ToString().Contains("No Authentication Token found")), + Arg.Any(), + Arg.Any>() + ); } #endregion @@ -341,12 +343,13 @@ public async Task SendMessage_Should_Log_Error_On_Exception() await _sendMessageService.SendMessage(userId, TestContext.CancellationToken) ); - var eventId = Arg.Any(); - var state = Arg.Is(o => o.ToString().Contains("User not found")); - var loggedException = Arg.Any(); - var formatter = Arg.Any>(); - - _logger.Received(1).Log(LogLevel.Error, eventId, state, loggedException, formatter); + _logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Is(o => o.ToString().Contains("User not found")), + Arg.Any(), + Arg.Any>() + ); } [TestMethod] @@ -372,12 +375,13 @@ public async Task SendMessage_GmailService_ThrowsException_Should_Log_And_Rethro await _sendMessageService.SendMessage(user.Id, TestContext.CancellationToken) ); - var eventId = Arg.Any(); - var state = Arg.Any(); - var loggedException = Arg.Any(); - var formatter = Arg.Any>(); - - _logger.Received(1).Log(LogLevel.Error, eventId, state, loggedException, formatter); + _logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); } [TestMethod] @@ -410,12 +414,13 @@ public async Task SendMessage_BarcodeReader_ThrowsException_Should_Log_And_Rethr await _sendMessageService.SendMessage(user.Id, TestContext.CancellationToken) ); - var eventId = Arg.Any(); - var state = Arg.Any(); - var loggedException = Arg.Any(); - var formatter = Arg.Any>(); - - _logger.Received(1).Log(LogLevel.Error, eventId, state, loggedException, formatter); + _logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); } [TestMethod] @@ -437,12 +442,13 @@ public async Task SendMessage_OperationCanceledException_Should_Log_Warning_And_ await _sendMessageService.SendMessage(userId, cts.Token) ); - var eventId = Arg.Any(); - var state = Arg.Any(); - var loggedException = Arg.Any(); - var formatter = Arg.Any>(); - - _logger.Received(1).Log(LogLevel.Warning, eventId, state, loggedException, formatter); + _logger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); } [TestMethod] @@ -482,12 +488,13 @@ await _sendMessageService.SendMessage(user.Id, TestContext.CancellationToken) _ = _invoiceRepository.DidNotReceive() .BulkInsertAsync(Arg.Any>(), Arg.Any()); - var eventId = Arg.Any(); - var state = Arg.Any(); - var loggedException = Arg.Any(); - var formatter = Arg.Any>(); - - _logger.Received(1).Log(LogLevel.Error, eventId, state, loggedException, formatter); + _logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); } #endregion diff --git a/InvoiceReminder.Infrastructure.UnitTests/Authentication/JwtObjectTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Authentication/JwtObjectTests.cs similarity index 71% rename from InvoiceReminder.Infrastructure.UnitTests/Authentication/JwtObjectTests.cs rename to InvoiceReminder.UnitTests.Infrastructure/Authentication/JwtObjectTests.cs index 4dc2145..7a6f4b3 100644 --- a/InvoiceReminder.Infrastructure.UnitTests/Authentication/JwtObjectTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Authentication/JwtObjectTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Authentication.Jwt; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; -namespace InvoiceReminder.Infrastructure.UnitTests.Authentication; +namespace InvoiceReminder.UnitTests.Infrastructure.Authentication; [TestClass] public sealed class JwtObjectTests diff --git a/InvoiceReminder.Infrastructure.UnitTests/Authentication/JwtOptionsTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Authentication/JwtOptionsTests.cs similarity index 71% rename from InvoiceReminder.Infrastructure.UnitTests/Authentication/JwtOptionsTests.cs rename to InvoiceReminder.UnitTests.Infrastructure/Authentication/JwtOptionsTests.cs index 45ec57b..ebc96ff 100644 --- a/InvoiceReminder.Infrastructure.UnitTests/Authentication/JwtOptionsTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Authentication/JwtOptionsTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Authentication.Jwt; -using InvoiceReminder.UnitTests.Assets; +using InvoiceReminder.UnitTests.SUT.Assets; -namespace InvoiceReminder.Infrastructure.UnitTests.Authentication; +namespace InvoiceReminder.UnitTests.Infrastructure.Authentication; [TestClass] public sealed class JwtOptionsTests diff --git a/InvoiceReminder.Infrastructure.UnitTests/Authentication/JwtProviderTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Authentication/JwtProviderTests.cs similarity index 97% rename from InvoiceReminder.Infrastructure.UnitTests/Authentication/JwtProviderTests.cs rename to InvoiceReminder.UnitTests.Infrastructure/Authentication/JwtProviderTests.cs index 2287bcb..0b72b07 100644 --- a/InvoiceReminder.Infrastructure.UnitTests/Authentication/JwtProviderTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Authentication/JwtProviderTests.cs @@ -5,7 +5,7 @@ using NSubstitute; using Shouldly; -namespace InvoiceReminder.Infrastructure.UnitTests.Authentication; +namespace InvoiceReminder.UnitTests.Infrastructure.Authentication; [TestClass] public sealed class JwtProviderTests diff --git a/InvoiceReminder.Infrastructure.UnitTests/Authentication/StringHashExtensionTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Authentication/StringHashExtensionTests.cs similarity index 95% rename from InvoiceReminder.Infrastructure.UnitTests/Authentication/StringHashExtensionTests.cs rename to InvoiceReminder.UnitTests.Infrastructure/Authentication/StringHashExtensionTests.cs index e32862c..02a79ab 100644 --- a/InvoiceReminder.Infrastructure.UnitTests/Authentication/StringHashExtensionTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Authentication/StringHashExtensionTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Authentication.Extensions; using Shouldly; -namespace InvoiceReminder.Infrastructure.UnitTests.Authentication; +namespace InvoiceReminder.UnitTests.Infrastructure.Authentication; [TestClass] public sealed class StringHashExtensionTests diff --git a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/InvoiceConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs similarity index 85% rename from InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/InvoiceConfigTests.cs rename to InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs index 7842369..ca1d64a 100644 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/InvoiceConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Data.Persistence.EntitiesConfig; using Shouldly; -namespace InvoiceReminder.Infrastructure.UnitTests.Data.EntitiesConfig; +namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; [TestClass] public sealed class InvoiceConfigTests diff --git a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/JobScheduleConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs similarity index 85% rename from InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/JobScheduleConfigTests.cs rename to InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs index 000cd87..a3e6d6f 100644 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/JobScheduleConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Data.Persistence.EntitiesConfig; using Shouldly; -namespace InvoiceReminder.Infrastructure.UnitTests.Data.EntitiesConfig; +namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; [TestClass] public sealed class JobScheduleConfigTests diff --git a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs similarity index 86% rename from InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs rename to InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs index 16b63a6..9464bb8 100644 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Data.Persistence.EntitiesConfig; using Shouldly; -namespace InvoiceReminder.Infrastructure.UnitTests.Data.EntitiesConfig; +namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; [TestClass] public sealed class ScanEmailDefinitionConfigTests diff --git a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/UserConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs similarity index 84% rename from InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/UserConfigTests.cs rename to InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs index 43b0a44..cc381ac 100644 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/UserConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs @@ -1,7 +1,7 @@ using InvoiceReminder.Data.Persistence.EntitiesConfig; using Shouldly; -namespace InvoiceReminder.Infrastructure.UnitTests.Data.EntitiesConfig; +namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; [TestClass] public sealed class UserConfigTests diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/EmailAuthTokenRepositoryTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/EmailAuthTokenRepositoryTests.cs new file mode 100644 index 0000000..bc44197 --- /dev/null +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/EmailAuthTokenRepositoryTests.cs @@ -0,0 +1,45 @@ +using InvoiceReminder.Data.Interfaces; +using InvoiceReminder.Data.Persistence; +using InvoiceReminder.Data.Repository; +using InvoiceReminder.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Shouldly; + +namespace InvoiceReminder.UnitTests.Infrastructure.Data.Repository; + +[TestClass] +public sealed class EmailAuthTokenRepositoryTests +{ + private readonly CoreDbContext _dbContext; + private readonly ILogger _logger; + + public EmailAuthTokenRepositoryTests() + { + var options = new DbContextOptionsBuilder() + .UseNpgsql(default) + .Options; + + _dbContext = Substitute.ForPartsOf(options); + _logger = Substitute.For>(); + } + + [TestMethod] + public void EmailAuthTokenRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() + { + // Arrange && Act + var repository = new EmailAuthTokenRepository(_dbContext, _logger); + + // Assert + repository.ShouldSatisfyAllConditions(() => + { + _ = repository.ShouldBeAssignableTo(); + _ = repository.ShouldBeAssignableTo>(); + _ = repository.ShouldBeAssignableTo>(); + + _ = repository.ShouldNotBeNull(); + _ = repository.ShouldBeOfType(); + }); + } +} diff --git a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/InvoiceRepositoryTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/InvoiceRepositoryTests.cs similarity index 58% rename from InvoiceReminder.Infrastructure.UnitTests/Data/Repository/InvoiceRepositoryTests.cs rename to InvoiceReminder.UnitTests.Infrastructure/Data/Repository/InvoiceRepositoryTests.cs index 83ea47d..60818c8 100644 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/InvoiceRepositoryTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/InvoiceRepositoryTests.cs @@ -7,16 +7,13 @@ using NSubstitute; using Shouldly; -namespace InvoiceReminder.Infrastructure.UnitTests.Data.Repository; +namespace InvoiceReminder.UnitTests.Infrastructure.Data.Repository; [TestClass] public sealed class InvoiceRepositoryTests { private readonly CoreDbContext _dbContext; private readonly ILogger _logger; - private readonly IInvoiceRepository _repository; - - public TestContext TestContext { get; set; } public InvoiceRepositoryTests() { @@ -26,7 +23,6 @@ public InvoiceRepositoryTests() _dbContext = Substitute.ForPartsOf(options); _logger = Substitute.For>(); - _repository = Substitute.For(); } [TestMethod] @@ -46,27 +42,4 @@ public void InvoiceRepository_ShouldBeAssignableToItsInterface_And_GenericInterf _ = repository.ShouldBeOfType(); }); } - - [TestMethod] - public async Task GetByBarcodeAsync_ShouldReturnInvoice_WhenInvoiceExists() - { - // Arrange - var barcode = "12345678901234567890123456789012345678901234"; - var invoice = new Invoice { Barcode = barcode }; - - _ = _repository.GetByBarcodeAsync(Arg.Any(), Arg.Any()) - .Returns(Task.FromResult(invoice)); - - // Act - var result = await _repository.GetByBarcodeAsync(barcode, TestContext.CancellationToken); - - // Assert - result.ShouldSatisfyAllConditions(() => - { - _ = result.ShouldBeAssignableTo(); - _ = result.ShouldBeOfType(); - _ = result.ShouldNotBeNull(); - result.Barcode.ShouldBe(barcode); - }); - } } diff --git a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/JobScheduleRepositoryTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/JobScheduleRepositoryTests.cs similarity index 58% rename from InvoiceReminder.Infrastructure.UnitTests/Data/Repository/JobScheduleRepositoryTests.cs rename to InvoiceReminder.UnitTests.Infrastructure/Data/Repository/JobScheduleRepositoryTests.cs index d0d478f..bcd8906 100644 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/JobScheduleRepositoryTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/JobScheduleRepositoryTests.cs @@ -7,16 +7,13 @@ using NSubstitute; using Shouldly; -namespace InvoiceReminder.Infrastructure.UnitTests.Data.Repository; +namespace InvoiceReminder.UnitTests.Infrastructure.Data.Repository; [TestClass] public sealed class JobScheduleRepositoryTests { private readonly CoreDbContext _dbContext; private readonly ILogger _logger; - private readonly IJobScheduleRepository _repository; - - public TestContext TestContext { get; set; } public JobScheduleRepositoryTests() { @@ -26,7 +23,6 @@ public JobScheduleRepositoryTests() _dbContext = Substitute.ForPartsOf(options); _logger = Substitute.For>(); - _repository = Substitute.For(); } [TestMethod] @@ -46,27 +42,4 @@ public void JobScheduleRepository_ShouldBeAssignableToItsInterface_And_GenericIn _ = repository.ShouldBeOfType(); }); } - - [TestMethod] - public async Task GetByUserIdAsync_ShouldReturnJobSchedule_WhenJobScheduleExists() - { - // Arrange - var userId = Guid.NewGuid(); - var jobSchedule = new List { new() { UserId = userId } }; - - _ = _repository.GetByUserIdAsync(Arg.Any(), Arg.Any()) - .Returns(Task.FromResult>(jobSchedule)); - - // Act - var result = await _repository.GetByUserIdAsync(userId, TestContext.CancellationToken); - - // Assert - result.ShouldSatisfyAllConditions(() => - { - _ = result.ShouldNotBeNull(); - _ = result.ShouldBeOfType>(); - result.ShouldNotBeEmpty(); - result.ShouldAllBe(x => x.UserId == userId); - }); - } } diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/ScanEmailDefinitionRepositoryTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/ScanEmailDefinitionRepositoryTests.cs new file mode 100644 index 0000000..ca7718a --- /dev/null +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/ScanEmailDefinitionRepositoryTests.cs @@ -0,0 +1,45 @@ +using InvoiceReminder.Data.Interfaces; +using InvoiceReminder.Data.Persistence; +using InvoiceReminder.Data.Repository; +using InvoiceReminder.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Shouldly; + +namespace InvoiceReminder.UnitTests.Infrastructure.Data.Repository; + +[TestClass] +public sealed class ScanEmailDefinitionRepositoryTests +{ + private readonly CoreDbContext _dbContext; + private readonly ILogger _logger; + + public ScanEmailDefinitionRepositoryTests() + { + var options = new DbContextOptionsBuilder() + .UseNpgsql(default) + .Options; + + _dbContext = Substitute.ForPartsOf(options); + _logger = Substitute.For>(); + } + + [TestMethod] + public void ScanEmailDefinitionRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() + { + // Arrange && Act + var repository = new ScanEmailDefinitionRepository(_dbContext, _logger); + + // Assert + repository.ShouldSatisfyAllConditions(() => + { + _ = repository.ShouldBeAssignableTo(); + _ = repository.ShouldBeAssignableTo>(); + _ = repository.ShouldBeAssignableTo>(); + + _ = repository.ShouldNotBeNull(); + _ = repository.ShouldBeOfType(); + }); + } +} diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/UserRepositoryTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/UserRepositoryTests.cs new file mode 100644 index 0000000..dfe201d --- /dev/null +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/UserRepositoryTests.cs @@ -0,0 +1,45 @@ +using InvoiceReminder.Data.Interfaces; +using InvoiceReminder.Data.Persistence; +using InvoiceReminder.Data.Repository; +using InvoiceReminder.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Shouldly; + +namespace InvoiceReminder.UnitTests.Infrastructure.Data.Repository; + +[TestClass] +public sealed class UserRepositoryTests +{ + private readonly CoreDbContext _dbContext; + private readonly ILogger _logger; + + public UserRepositoryTests() + { + var options = new DbContextOptionsBuilder() + .UseNpgsql(default) + .Options; + + _dbContext = Substitute.ForPartsOf(options); + _logger = Substitute.For>(); + } + + [TestMethod] + public void UserRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() + { + // Arrange && Act + var repository = new UserRepository(_dbContext, _logger); + + // Assert + repository.ShouldSatisfyAllConditions(() => + { + _ = repository.ShouldBeAssignableTo(); + _ = repository.ShouldBeAssignableTo>(); + _ = repository.ShouldBeAssignableTo>(); + + _ = repository.ShouldNotBeNull(); + _ = repository.ShouldBeOfType(); + }); + } +} diff --git a/InvoiceReminder.Infrastructure.UnitTests/InvoiceReminder.Infrastructure.UnitTests.csproj b/InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj similarity index 73% rename from InvoiceReminder.Infrastructure.UnitTests/InvoiceReminder.Infrastructure.UnitTests.csproj rename to InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj index 1f094f2..e76981b 100644 --- a/InvoiceReminder.Infrastructure.UnitTests/InvoiceReminder.Infrastructure.UnitTests.csproj +++ b/InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj @@ -1,10 +1,6 @@  - true Exe false @@ -13,14 +9,13 @@ - + - all diff --git a/InvoiceReminder.JobScheduler.UnitTests/MSTestSettings.cs b/InvoiceReminder.UnitTests.Infrastructure/MSTestSettings.cs similarity index 100% rename from InvoiceReminder.JobScheduler.UnitTests/MSTestSettings.cs rename to InvoiceReminder.UnitTests.Infrastructure/MSTestSettings.cs diff --git a/InvoiceReminder.JobScheduler.UnitTests/HostedService/QuartzHostedServiceTests.cs b/InvoiceReminder.UnitTests.JobScheduler/HostedService/QuartzHostedServiceTests.cs similarity index 94% rename from InvoiceReminder.JobScheduler.UnitTests/HostedService/QuartzHostedServiceTests.cs rename to InvoiceReminder.UnitTests.JobScheduler/HostedService/QuartzHostedServiceTests.cs index 85f4757..990ddf1 100644 --- a/InvoiceReminder.JobScheduler.UnitTests/HostedService/QuartzHostedServiceTests.cs +++ b/InvoiceReminder.UnitTests.JobScheduler/HostedService/QuartzHostedServiceTests.cs @@ -7,7 +7,7 @@ using Quartz.Spi; using Shouldly; -namespace InvoiceReminder.JobScheduler.UnitTests.HostedService; +namespace InvoiceReminder.UnitTests.JobScheduler.HostedService; [TestClass] public sealed class QuartzHostedServiceTests @@ -88,12 +88,13 @@ public async Task StartAsync_InvalidCronExpression_ShouldLogError() await _service.StartAsync(TestContext.CancellationToken); // Assert - var eventId = Arg.Any(); - var state = Arg.Is(o => o.ToString().Contains("CronJob inválido:")); - var loggedException = Arg.Is(e => e == null); - var formatter = Arg.Any>(); - - _logger.Received(1).Log(LogLevel.Error, eventId, state, loggedException, formatter); + _logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Is(o => o.ToString().Contains("CronJob inválido:")), + Arg.Any(), + Arg.Any>() + ); _ = _scheduler.DidNotReceive().ScheduleJob( Arg.Any(), @@ -159,12 +160,13 @@ public async Task StartAsync_WithMultipleInvalidCronExpressions_ShouldLogErrorsF await _service.StartAsync(TestContext.CancellationToken); // Assert - var eventId = Arg.Any(); - var state = Arg.Any(); - var loggedException = Arg.Any(); - var formatter = Arg.Any>(); - - _logger.Received(2).Log(LogLevel.Error, eventId, state, loggedException, formatter); + _logger.Received(2).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); _ = _scheduler.DidNotReceive().ScheduleJob( Arg.Any(), @@ -190,12 +192,13 @@ public async Task StartAsync_WithMixedValidAndInvalidSchedules_ShouldScheduleOnl await _service.StartAsync(TestContext.CancellationToken); // Assert - var eventId = Arg.Any(); - var state = Arg.Any(); - var loggedException = Arg.Any(); - var formatter = Arg.Any>(); - - _logger.Received(1).Log(LogLevel.Error, eventId, state, loggedException, formatter); + _logger.Received(1).Log( + LogLevel.Error, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); _ = _scheduler.Received(1).ScheduleJob( Arg.Is(j => j.Key.Name == $"{validSchedule.Id}.job"), diff --git a/InvoiceReminder.JobScheduler.UnitTests/InvoiceReminder.JobScheduler.UnitTests.csproj b/InvoiceReminder.UnitTests.JobScheduler/InvoiceReminder.UnitTests.JobScheduler.csproj similarity index 79% rename from InvoiceReminder.JobScheduler.UnitTests/InvoiceReminder.JobScheduler.UnitTests.csproj rename to InvoiceReminder.UnitTests.JobScheduler/InvoiceReminder.UnitTests.JobScheduler.csproj index a4e37a0..ecd0d7c 100644 --- a/InvoiceReminder.JobScheduler.UnitTests/InvoiceReminder.JobScheduler.UnitTests.csproj +++ b/InvoiceReminder.UnitTests.JobScheduler/InvoiceReminder.UnitTests.JobScheduler.csproj @@ -1,10 +1,10 @@  + + CA1873 + + - true Exe false diff --git a/InvoiceReminder.JobScheduler.UnitTests/JobSettings/CronJobTests.cs b/InvoiceReminder.UnitTests.JobScheduler/JobSettings/CronJobTests.cs similarity index 84% rename from InvoiceReminder.JobScheduler.UnitTests/JobSettings/CronJobTests.cs rename to InvoiceReminder.UnitTests.JobScheduler/JobSettings/CronJobTests.cs index 35e930f..1ed97a8 100644 --- a/InvoiceReminder.JobScheduler.UnitTests/JobSettings/CronJobTests.cs +++ b/InvoiceReminder.UnitTests.JobScheduler/JobSettings/CronJobTests.cs @@ -6,7 +6,7 @@ using Quartz; using Shouldly; -namespace InvoiceReminder.JobScheduler.UnitTests.JobSettings; +namespace InvoiceReminder.UnitTests.JobScheduler.JobSettings; [TestClass] public sealed class CronJobTests @@ -59,11 +59,13 @@ public async Task Execute_ShouldCreateScopeResolveServiceAndSendMessage() _ = _sendMessageService.Received(1).SendMessage(Arg.Any(), Arg.Any()); - var eventId = Arg.Any(); - var state = Arg.Any(); - var formatter = Arg.Any>(); - - _logger.ReceivedWithAnyArgs(1).Log(LogLevel.Information, eventId, state, null, formatter); + _logger.ReceivedWithAnyArgs(1).Log( + LogLevel.Information, + Arg.Any(), + Arg.Any(), + null, + Arg.Any>() + ); } [TestMethod] @@ -105,11 +107,12 @@ public async Task Execute_SendMessageFails_ShouldNotThrowExceptionAndStillLog() _ = _serviceScopeFactory.Received(1).CreateScope(); _ = _sendMessageService.Received(1).SendMessage(Arg.Any(), Arg.Any()); - var eventId = Arg.Any(); - var state = Arg.Is(o => o.ToString().Contains("Test Job Description triggered...")); - var loggedException = Arg.Is(e => e == null); - var formatter = Arg.Any>(); - - _logger.Received(1).Log(LogLevel.Information, eventId, state, loggedException, formatter); + _logger.Received(1).Log( + LogLevel.Information, + Arg.Any(), + Arg.Is(o => o.ToString().Contains("Test Job Description triggered...")), + Arg.Any(), + Arg.Any>() + ); } } diff --git a/InvoiceReminder.UnitTests.JobScheduler/MSTestSettings.cs b/InvoiceReminder.UnitTests.JobScheduler/MSTestSettings.cs new file mode 100644 index 0000000..aaf278c --- /dev/null +++ b/InvoiceReminder.UnitTests.JobScheduler/MSTestSettings.cs @@ -0,0 +1 @@ +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/InvoiceReminder.UnitTests.Assets/Exception/PropertyTestException.cs b/InvoiceReminder.UnitTests.SUT.Assets/Exception/PropertyTestException.cs similarity index 87% rename from InvoiceReminder.UnitTests.Assets/Exception/PropertyTestException.cs rename to InvoiceReminder.UnitTests.SUT.Assets/Exception/PropertyTestException.cs index ec0a16a..df82c33 100644 --- a/InvoiceReminder.UnitTests.Assets/Exception/PropertyTestException.cs +++ b/InvoiceReminder.UnitTests.SUT.Assets/Exception/PropertyTestException.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace InvoiceReminder.UnitTests.Assets.Exception; +namespace InvoiceReminder.UnitTests.SUT.Assets.Exception; [ExcludeFromCodeCoverage] public class PropertyTestException : SystemException diff --git a/InvoiceReminder.UnitTests.Assets/Helpers/Guard.cs b/InvoiceReminder.UnitTests.SUT.Assets/Helpers/Guard.cs similarity index 97% rename from InvoiceReminder.UnitTests.Assets/Helpers/Guard.cs rename to InvoiceReminder.UnitTests.SUT.Assets/Helpers/Guard.cs index 854a09a..c036646 100644 --- a/InvoiceReminder.UnitTests.Assets/Helpers/Guard.cs +++ b/InvoiceReminder.UnitTests.SUT.Assets/Helpers/Guard.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace InvoiceReminder.UnitTests.Assets.Helpers; +namespace InvoiceReminder.UnitTests.SUT.Assets.Helpers; [ExcludeFromCodeCoverage] public static class Guard diff --git a/InvoiceReminder.UnitTests.Assets/Helpers/TypeFactory.cs b/InvoiceReminder.UnitTests.SUT.Assets/Helpers/TypeFactory.cs similarity index 99% rename from InvoiceReminder.UnitTests.Assets/Helpers/TypeFactory.cs rename to InvoiceReminder.UnitTests.SUT.Assets/Helpers/TypeFactory.cs index 21a77a5..1c336d6 100644 --- a/InvoiceReminder.UnitTests.Assets/Helpers/TypeFactory.cs +++ b/InvoiceReminder.UnitTests.SUT.Assets/Helpers/TypeFactory.cs @@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; -namespace InvoiceReminder.UnitTests.Assets.Helpers; +namespace InvoiceReminder.UnitTests.SUT.Assets.Helpers; public interface ITypeFactory { diff --git a/InvoiceReminder.UnitTests.Assets/InvoiceReminder.UnitTests.Assets.csproj b/InvoiceReminder.UnitTests.SUT.Assets/InvoiceReminder.UnitTests.SUT.Assets.csproj similarity index 100% rename from InvoiceReminder.UnitTests.Assets/InvoiceReminder.UnitTests.Assets.csproj rename to InvoiceReminder.UnitTests.SUT.Assets/InvoiceReminder.UnitTests.SUT.Assets.csproj diff --git a/InvoiceReminder.UnitTests.Assets/PropertyTester.cs b/InvoiceReminder.UnitTests.SUT.Assets/PropertyTester.cs similarity index 98% rename from InvoiceReminder.UnitTests.Assets/PropertyTester.cs rename to InvoiceReminder.UnitTests.SUT.Assets/PropertyTester.cs index 6e0dfda..35ddc1b 100644 --- a/InvoiceReminder.UnitTests.Assets/PropertyTester.cs +++ b/InvoiceReminder.UnitTests.SUT.Assets/PropertyTester.cs @@ -1,11 +1,11 @@ -using InvoiceReminder.UnitTests.Assets.Exception; -using InvoiceReminder.UnitTests.Assets.Helpers; +using InvoiceReminder.UnitTests.SUT.Assets.Exception; +using InvoiceReminder.UnitTests.SUT.Assets.Helpers; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; -namespace InvoiceReminder.UnitTests.Assets; +namespace InvoiceReminder.UnitTests.SUT.Assets; [ExcludeFromCodeCoverage] /// diff --git a/InvoiceReminder.sln b/InvoiceReminder.sln index a69510d..e51f105 100644 --- a/InvoiceReminder.sln +++ b/InvoiceReminder.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 VisualStudioVersion = 18.0.11222.15 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.API", "InvoiceReminder.API\InvoiceReminder.API.csproj", "{CA4D17D7-211B-4FFA-ADDF-FD8C1890708B}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1 - Presentation", "1 - Presentation", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2 - Services", "2 - Services", "{49106062-DF04-4407-B267-DBBE4A99B280}" @@ -25,37 +23,47 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.2 - Data", "4.2 - Data", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.3 - Crosscutting", "4.3 - Crosscutting", "{9E180232-F1E2-4A65-9C8B-17188EBE60AC}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5 - Unit Tests", "5 - Unit Tests", "{67471F47-0B78-4F8A-95CD-145F78F644C3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.CrossCutting.IoC", "InvoiceReminder.CrossCutting.IoC\InvoiceReminder.CrossCutting.IoC.csproj", "{90BE8610-76AB-44DC-B39A-57B6C684BF33}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5 - Tests", "5 - Tests", "{67471F47-0B78-4F8A-95CD-145F78F644C3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.Data", "InvoiceReminder.Data\InvoiceReminder.Data.csproj", "{D087924E-D489-4018-95FE-A89B0D0A42DA}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5.1 - Architecture Tests", "5.1 - Architecture Tests", "{75B98BB5-3EE7-4F79-BC4D-AD9261687449}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.Authentication", "InvoiceReminder.Authentication\InvoiceReminder.Authentication.csproj", "{D4D9D4C9-2DFF-4F0D-83C5-DEA36D455E3A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5.2 - Integration Tests", "5.2 - Integration Tests", "{CA571B85-3CB9-4B15-8363-20E769BDE34D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.Domain", "InvoiceReminder.Domain\InvoiceReminder.Domain.csproj", "{0A7AEFDB-5181-45E6-AFCF-368FD879EC12}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5.3 - Unit Tests", "5.3 - Unit Tests", "{787440A7-A78A-4DD9-9999-2694378FE4FE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.JobScheduler", "InvoiceReminder.JobScheduler\InvoiceReminder.JobScheduler.csproj", "{394EC1DE-9FDA-44F9-A5F5-7BF259150910}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.API", "InvoiceReminder.API\InvoiceReminder.API.csproj", "{CA4D17D7-211B-4FFA-ADDF-FD8C1890708B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.Application", "InvoiceReminder.Application\InvoiceReminder.Application.csproj", "{995B652F-6095-4056-A1AB-F45AAF0135D2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.JobScheduler", "InvoiceReminder.JobScheduler\InvoiceReminder.JobScheduler.csproj", "{394EC1DE-9FDA-44F9-A5F5-7BF259150910}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.ExternalServices", "InvoiceReminder.ExternalServices\InvoiceReminder.ExternalServices.csproj", "{F2EB382B-25E4-43D8-8159-53DB5085DED6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.DomainEntities.UnitTests", "InvoiceReminder.DomainEntities.UnitTests\InvoiceReminder.DomainEntities.UnitTests.csproj", "{F50B9864-67DB-48DF-9B95-D78C126E0A2C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.Domain", "InvoiceReminder.Domain\InvoiceReminder.Domain.csproj", "{0A7AEFDB-5181-45E6-AFCF-368FD879EC12}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.Authentication", "InvoiceReminder.Authentication\InvoiceReminder.Authentication.csproj", "{D4D9D4C9-2DFF-4F0D-83C5-DEA36D455E3A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.Data", "InvoiceReminder.Data\InvoiceReminder.Data.csproj", "{D087924E-D489-4018-95FE-A89B0D0A42DA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.Infrastructure.UnitTests", "InvoiceReminder.Infrastructure.UnitTests\InvoiceReminder.Infrastructure.UnitTests.csproj", "{BB724E60-FEC2-44C4-B6AF-1692EF1958B4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.CrossCutting.IoC", "InvoiceReminder.CrossCutting.IoC\InvoiceReminder.CrossCutting.IoC.csproj", "{90BE8610-76AB-44DC-B39A-57B6C684BF33}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.API.UnitTests", "InvoiceReminder.API.UnitTests\InvoiceReminder.API.UnitTests.csproj", "{A1770BD0-A964-4580-8F90-A6EAF20444B9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.ArchitectureTests", "InvoiceReminder.ArchitectureTests\InvoiceReminder.ArchitectureTests.csproj", "{F8FE30DD-52E6-4981-96A7-D4D487522A26}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.Application.UnitTests", "InvoiceReminder.Application.UnitTests\InvoiceReminder.Application.UnitTests.csproj", "{CCE01871-5AEF-4D60-ADAF-AE63F1AB4136}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.IntegrationTests", "InvoiceReminder.IntegrationTests\InvoiceReminder.IntegrationTests.csproj", "{F2E0270A-1FCB-4E71-B4EE-5E3059B7B8B7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.JobScheduler.UnitTests", "InvoiceReminder.JobScheduler.UnitTests\InvoiceReminder.JobScheduler.UnitTests.csproj", "{1CC0FC52-71DE-4FEB-8A8F-3B8F879D33C1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.UnitTests.Domain", "InvoiceReminder.UnitTests.Domain\InvoiceReminder.UnitTests.Domain.csproj", "{F50B9864-67DB-48DF-9B95-D78C126E0A2C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.ExternalServices.UnitTests", "InvoiceReminder.ExternalServices.UnitTests\InvoiceReminder.ExternalServices.UnitTests.csproj", "{45590E22-E032-476B-848F-D43669E82DA2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.UnitTests.Infrastructure", "InvoiceReminder.UnitTests.Infrastructure\InvoiceReminder.UnitTests.Infrastructure.csproj", "{BB724E60-FEC2-44C4-B6AF-1692EF1958B4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.UnitTests.Assets", "InvoiceReminder.UnitTests.Assets\InvoiceReminder.UnitTests.Assets.csproj", "{0D3F963F-37A2-40E6-865D-0FD732FC5376}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.UnitTests.API", "InvoiceReminder.UnitTests.API\InvoiceReminder.UnitTests.API.csproj", "{A1770BD0-A964-4580-8F90-A6EAF20444B9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.ArchitectureTests", "InvoiceReminder.ArchitectureTests\InvoiceReminder.ArchitectureTests.csproj", "{F8FE30DD-52E6-4981-96A7-D4D487522A26}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.UnitTests.Application", "InvoiceReminder.UnitTests.Application\InvoiceReminder.UnitTests.Application.csproj", "{CCE01871-5AEF-4D60-ADAF-AE63F1AB4136}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.UnitTests.JobScheduler", "InvoiceReminder.UnitTests.JobScheduler\InvoiceReminder.UnitTests.JobScheduler.csproj", "{1CC0FC52-71DE-4FEB-8A8F-3B8F879D33C1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.UnitTests.ExternalServices", "InvoiceReminder.UnitTests.ExternalServices\InvoiceReminder.UnitTests.ExternalServices.csproj", "{45590E22-E032-476B-848F-D43669E82DA2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InvoiceReminder.UnitTests.SUT.Assets", "InvoiceReminder.UnitTests.SUT.Assets\InvoiceReminder.UnitTests.SUT.Assets.csproj", "{0D3F963F-37A2-40E6-865D-0FD732FC5376}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Itens de Solução", "Itens de Solução", "{380A7511-A354-6D7A-0CC0-8FA1F1BA7B6C}" ProjectSection(SolutionItems) = preProject @@ -106,6 +114,14 @@ Global {F2EB382B-25E4-43D8-8159-53DB5085DED6}.Debug|Any CPU.Build.0 = Debug|Any CPU {F2EB382B-25E4-43D8-8159-53DB5085DED6}.Release|Any CPU.ActiveCfg = Release|Any CPU {F2EB382B-25E4-43D8-8159-53DB5085DED6}.Release|Any CPU.Build.0 = Release|Any CPU + {F8FE30DD-52E6-4981-96A7-D4D487522A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8FE30DD-52E6-4981-96A7-D4D487522A26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8FE30DD-52E6-4981-96A7-D4D487522A26}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8FE30DD-52E6-4981-96A7-D4D487522A26}.Release|Any CPU.Build.0 = Release|Any CPU + {F2E0270A-1FCB-4E71-B4EE-5E3059B7B8B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2E0270A-1FCB-4E71-B4EE-5E3059B7B8B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2E0270A-1FCB-4E71-B4EE-5E3059B7B8B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2E0270A-1FCB-4E71-B4EE-5E3059B7B8B7}.Release|Any CPU.Build.0 = Release|Any CPU {F50B9864-67DB-48DF-9B95-D78C126E0A2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F50B9864-67DB-48DF-9B95-D78C126E0A2C}.Debug|Any CPU.Build.0 = Debug|Any CPU {F50B9864-67DB-48DF-9B95-D78C126E0A2C}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -134,10 +150,6 @@ Global {0D3F963F-37A2-40E6-865D-0FD732FC5376}.Debug|Any CPU.Build.0 = Debug|Any CPU {0D3F963F-37A2-40E6-865D-0FD732FC5376}.Release|Any CPU.ActiveCfg = Release|Any CPU {0D3F963F-37A2-40E6-865D-0FD732FC5376}.Release|Any CPU.Build.0 = Release|Any CPU - {F8FE30DD-52E6-4981-96A7-D4D487522A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F8FE30DD-52E6-4981-96A7-D4D487522A26}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F8FE30DD-52E6-4981-96A7-D4D487522A26}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F8FE30DD-52E6-4981-96A7-D4D487522A26}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -157,14 +169,18 @@ Global {394EC1DE-9FDA-44F9-A5F5-7BF259150910} = {665C60E7-33FC-4B5B-B5EE-E31CDF83B1B1} {995B652F-6095-4056-A1AB-F45AAF0135D2} = {6A1AF399-E528-43C3-B5FD-CA7DADA09377} {F2EB382B-25E4-43D8-8159-53DB5085DED6} = {BF3C7CE8-60C1-4A70-9BF1-22050115A1C4} - {F50B9864-67DB-48DF-9B95-D78C126E0A2C} = {67471F47-0B78-4F8A-95CD-145F78F644C3} - {BB724E60-FEC2-44C4-B6AF-1692EF1958B4} = {67471F47-0B78-4F8A-95CD-145F78F644C3} - {A1770BD0-A964-4580-8F90-A6EAF20444B9} = {67471F47-0B78-4F8A-95CD-145F78F644C3} - {CCE01871-5AEF-4D60-ADAF-AE63F1AB4136} = {67471F47-0B78-4F8A-95CD-145F78F644C3} - {1CC0FC52-71DE-4FEB-8A8F-3B8F879D33C1} = {67471F47-0B78-4F8A-95CD-145F78F644C3} - {45590E22-E032-476B-848F-D43669E82DA2} = {67471F47-0B78-4F8A-95CD-145F78F644C3} - {0D3F963F-37A2-40E6-865D-0FD732FC5376} = {67471F47-0B78-4F8A-95CD-145F78F644C3} - {F8FE30DD-52E6-4981-96A7-D4D487522A26} = {67471F47-0B78-4F8A-95CD-145F78F644C3} + {F8FE30DD-52E6-4981-96A7-D4D487522A26} = {75B98BB5-3EE7-4F79-BC4D-AD9261687449} + {F2E0270A-1FCB-4E71-B4EE-5E3059B7B8B7} = {CA571B85-3CB9-4B15-8363-20E769BDE34D} + {F50B9864-67DB-48DF-9B95-D78C126E0A2C} = {787440A7-A78A-4DD9-9999-2694378FE4FE} + {BB724E60-FEC2-44C4-B6AF-1692EF1958B4} = {787440A7-A78A-4DD9-9999-2694378FE4FE} + {A1770BD0-A964-4580-8F90-A6EAF20444B9} = {787440A7-A78A-4DD9-9999-2694378FE4FE} + {CCE01871-5AEF-4D60-ADAF-AE63F1AB4136} = {787440A7-A78A-4DD9-9999-2694378FE4FE} + {1CC0FC52-71DE-4FEB-8A8F-3B8F879D33C1} = {787440A7-A78A-4DD9-9999-2694378FE4FE} + {45590E22-E032-476B-848F-D43669E82DA2} = {787440A7-A78A-4DD9-9999-2694378FE4FE} + {0D3F963F-37A2-40E6-865D-0FD732FC5376} = {787440A7-A78A-4DD9-9999-2694378FE4FE} + {75B98BB5-3EE7-4F79-BC4D-AD9261687449} = {67471F47-0B78-4F8A-95CD-145F78F644C3} + {CA571B85-3CB9-4B15-8363-20E769BDE34D} = {67471F47-0B78-4F8A-95CD-145F78F644C3} + {787440A7-A78A-4DD9-9999-2694378FE4FE} = {67471F47-0B78-4F8A-95CD-145F78F644C3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6165C451-34A2-4F1A-88D1-21F3EB4B587B} diff --git a/global.json b/global.json index 12d1731..36b0c0a 100644 --- a/global.json +++ b/global.json @@ -2,8 +2,7 @@ "sdk": { "version": "10.0.100" }, - "test": { "runner": "Microsoft.Testing.Platform" } -} +} \ No newline at end of file From 301c4b5c4a30f7e7729c8f792889d840a2106022 Mon Sep 17 00:00:00 2001 From: "Jefferson L. da Silva" Date: Tue, 30 Dec 2025 23:45:36 -0300 Subject: [PATCH 2/5] Refactors and enhances data layer and CI Refactors the data layer by specifying UUID types for IDs in entity configurations, ensuring data integrity and consistency. Improves test project discovery by sorting the results for consistent execution across environments. Enhances the robustness of database initialization in integration tests by adding exception handling. Rolls back transactions safely by checking for null before attempting to rollback. Removes docker layer caching to streamline the CI process. --- .github/workflows/workflow-ci.yml | 10 +- .../20250930210104_Initial_Create.cs | 4 +- .../EntitiesConfig/JobScheduleConfig.cs | 2 + .../ScanEmailDefinitionConfig.cs | 1 + InvoiceReminder.Data/Repository/UnitOfWork.cs | 2 +- .../Data/ContainerSetup/DatabaseFixture.cs | 16 ++- .../BaseRepositoryIntegrationTests.cs | 97 +++++++------- ...mailAuthTokenRepositoryIntegrationTests.cs | 102 ++++++++------- .../InvoiceRepositoryIntegrationTests.cs | 110 ++++++++-------- .../JobScheduleRepositoryIntegrationTests.cs | 108 +++++++-------- ...ailDefinitionRepositoryIntegrationTests.cs | 123 +++++++++--------- .../Repository/UnitOfWorkIntegrationTests.cs | 2 +- .../UserRepositoryIntegrationTests.cs | 97 ++++++-------- .../Data/Utils/TestData.cs | 84 ++++++++++++ .../EmailAuthTokenConfigTests.cs | 105 +++++++++++++++ .../Data/EntitiesConfig/InvoiceConfigTests.cs | 90 +++++++++++++ .../EntitiesConfig/JobScheduleConfigTests.cs | 63 +++++++++ .../ScanEmailDefinitionConfigTests.cs | 87 +++++++++++++ .../Data/EntitiesConfig/UserConfigTests.cs | 75 +++++++++++ .../EmailAuthTokenRepositoryTests.cs | 45 ------- .../Data/Repository/InvoiceRepositoryTests.cs | 45 ------- .../Repository/JobScheduleRepositoryTests.cs | 45 ------- .../ScanEmailDefinitionRepositoryTests.cs | 45 ------- .../Data/Repository/UserRepositoryTests.cs | 45 ------- ...ceReminder.UnitTests.Infrastructure.csproj | 2 - .../HostedService/QuartzHostedServiceTests.cs | 4 +- .../JobSettings/CronJobTests.cs | 14 +- 27 files changed, 834 insertions(+), 589 deletions(-) create mode 100644 InvoiceReminder.IntegrationTests/Data/Utils/TestData.cs create mode 100644 InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs delete mode 100644 InvoiceReminder.UnitTests.Infrastructure/Data/Repository/EmailAuthTokenRepositoryTests.cs delete mode 100644 InvoiceReminder.UnitTests.Infrastructure/Data/Repository/InvoiceRepositoryTests.cs delete mode 100644 InvoiceReminder.UnitTests.Infrastructure/Data/Repository/JobScheduleRepositoryTests.cs delete mode 100644 InvoiceReminder.UnitTests.Infrastructure/Data/Repository/ScanEmailDefinitionRepositoryTests.cs delete mode 100644 InvoiceReminder.UnitTests.Infrastructure/Data/Repository/UserRepositoryTests.cs diff --git a/.github/workflows/workflow-ci.yml b/.github/workflows/workflow-ci.yml index e10b6d9..36a5e86 100644 --- a/.github/workflows/workflow-ci.yml +++ b/.github/workflows/workflow-ci.yml @@ -24,14 +24,6 @@ jobs: with: dotnet-version: 10.0.x - - name: Cache Docker layers - uses: actions/cache@v4 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-docker-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-docker- - - name: Restore Dependencies run: dotnet restore @@ -42,7 +34,7 @@ jobs: shell: bash run: | echo "🔍 Localizando Projetos de Teste..." - TEST_PROJECTS=$(find . -name "*.csproj" | grep -E "\.ArchitectureTests|\.IntegrationTests|\.UnitTests" | grep -v "Assets") + TEST_PROJECTS=$(find . -name "*.csproj" | grep -E "\.ArchitectureTests|\.IntegrationTests|\.UnitTests" | grep -v "Assets" | sort) if [ -z "$TEST_PROJECTS" ]; then echo "⚠️ Nenhum projeto de teste encontrado." diff --git a/InvoiceReminder.Data/Migrations/20250930210104_Initial_Create.cs b/InvoiceReminder.Data/Migrations/20250930210104_Initial_Create.cs index 1c5720d..4ffba79 100644 --- a/InvoiceReminder.Data/Migrations/20250930210104_Initial_Create.cs +++ b/InvoiceReminder.Data/Migrations/20250930210104_Initial_Create.cs @@ -5,15 +5,13 @@ namespace InvoiceReminder.Data.Migrations; -[ExcludeFromCodeCoverage] /// +[ExcludeFromCodeCoverage] public partial class Initial_Create : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) { - _ = migrationBuilder.Sql("CREATE SCHEMA IF NOT EXISTS invoice_reminder;"); - _ = migrationBuilder.EnsureSchema( name: "invoice_reminder"); diff --git a/InvoiceReminder.Data/Persistence/EntitiesConfig/JobScheduleConfig.cs b/InvoiceReminder.Data/Persistence/EntitiesConfig/JobScheduleConfig.cs index daf8ae5..682ed70 100644 --- a/InvoiceReminder.Data/Persistence/EntitiesConfig/JobScheduleConfig.cs +++ b/InvoiceReminder.Data/Persistence/EntitiesConfig/JobScheduleConfig.cs @@ -17,11 +17,13 @@ public void Configure(EntityTypeBuilder builder) _ = builder.Property(x => x.Id) .HasColumnName("id") + .HasColumnType("uuid") .ValueGeneratedOnAdd() .IsRequired(); _ = builder.Property(x => x.UserId) .HasColumnName("user_id") + .HasColumnType("uuid") .IsRequired(); _ = builder.Property(x => x.CronExpression) diff --git a/InvoiceReminder.Data/Persistence/EntitiesConfig/ScanEmailDefinitionConfig.cs b/InvoiceReminder.Data/Persistence/EntitiesConfig/ScanEmailDefinitionConfig.cs index 90588b8..e57c94b 100644 --- a/InvoiceReminder.Data/Persistence/EntitiesConfig/ScanEmailDefinitionConfig.cs +++ b/InvoiceReminder.Data/Persistence/EntitiesConfig/ScanEmailDefinitionConfig.cs @@ -23,6 +23,7 @@ public void Configure(EntityTypeBuilder builder) _ = builder.Property(x => x.UserId) .HasColumnName("user_id") + .HasColumnType("uuid") .IsRequired(); _ = builder.Property(x => x.InvoiceType) diff --git a/InvoiceReminder.Data/Repository/UnitOfWork.cs b/InvoiceReminder.Data/Repository/UnitOfWork.cs index 3cb4fde..56f1c50 100644 --- a/InvoiceReminder.Data/Repository/UnitOfWork.cs +++ b/InvoiceReminder.Data/Repository/UnitOfWork.cs @@ -60,7 +60,7 @@ public async Task SaveChangesAsync(CancellationToken cancellationToken = default _logger.LogError(ex, LogExceptionMessage, contextualInfo, ex.Message); } - await transaction.RollbackAsync(cancellationToken); + await transaction?.RollbackAsync(CancellationToken.None); throw new DataLayerException(contextualInfo, ex); } diff --git a/InvoiceReminder.IntegrationTests/Data/ContainerSetup/DatabaseFixture.cs b/InvoiceReminder.IntegrationTests/Data/ContainerSetup/DatabaseFixture.cs index c277e46..db076be 100644 --- a/InvoiceReminder.IntegrationTests/Data/ContainerSetup/DatabaseFixture.cs +++ b/InvoiceReminder.IntegrationTests/Data/ContainerSetup/DatabaseFixture.cs @@ -15,16 +15,24 @@ public static class DatabaseFixture [AssemblyInitialize] public static async Task AssemblyInit(TestContext context) { - _dbContainer = new PostgreSqlBuilder() + try + { + _dbContainer = new PostgreSqlBuilder() .WithDatabase("postgres") .WithUsername("postgres") .WithPassword("Fake!Password#123") .WithImage("postgres:15-alpine") .Build(); - await _dbContainer.StartAsync(context.CancellationToken); - await CreateSchema(context); - await RunMigrations(context); + await _dbContainer.StartAsync(context.CancellationToken); + await CreateSchema(context); + await RunMigrations(context); + } + catch (Exception ex) + { + context.WriteLine($"Failed to initialize test database: {ex.Message}"); + throw; + } } [AssemblyCleanup] diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs index bb9711b..5a63916 100644 --- a/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs +++ b/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs @@ -1,8 +1,8 @@ -using Bogus; using InvoiceReminder.Data.Persistence; using InvoiceReminder.Data.Repository; using InvoiceReminder.Domain.Entities; using InvoiceReminder.IntegrationTests.Data.ContainerSetup; +using InvoiceReminder.IntegrationTests.Data.Utils; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using NSubstitute; @@ -42,27 +42,13 @@ public void TestCleanup() _dbContext?.Dispose(); } - #region Helper Methods - - private static Faker UserFaker() - { - return new Faker() - .RuleFor(u => u.Id, _ => Guid.NewGuid()) - .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) - .RuleFor(u => u.Name, f => f.Person.FullName) - .RuleFor(u => u.Email, f => f.Internet.Email()) - .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); - } - - #endregion - #region AddAsync Tests [TestMethod] public async Task AddAsync_Should_Add_Entity_To_DbSet() { // Arrange - var user = UserFaker().Generate(); + var user = TestData.UserFaker().Generate(); // Act _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); @@ -76,7 +62,7 @@ public async Task AddAsync_Should_Add_Entity_To_DbSet() public async Task AddAsync_Should_Persist_Added_Entity_After_SaveChanges() { // Arrange - var user = UserFaker().Generate(); + var user = TestData.UserFaker().Generate(); // Act _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); @@ -101,7 +87,7 @@ public async Task AddAsync_Should_Persist_Added_Entity_After_SaveChanges() public async Task AddAsync_Should_Return_Added_Entity() { // Arrange - var user = UserFaker().Generate(); + var user = TestData.UserFaker().Generate(); // Act var result = await _userRepository.AddAsync(user, TestContext.CancellationToken); @@ -118,7 +104,7 @@ public async Task AddAsync_Should_Return_Added_Entity() public async Task BulkInsertAsync_Should_Insert_Multiple_Entities() { // Arrange - var users = UserFaker().Generate(5); + var users = TestData.UserFaker().Generate(5); // Act var result = await _userRepository.BulkInsertAsync(users, TestContext.CancellationToken); @@ -131,7 +117,7 @@ public async Task BulkInsertAsync_Should_Insert_Multiple_Entities() public async Task BulkInsertAsync_Should_Set_CreatedAt_And_UpdatedAt() { // Arrange - var users = UserFaker().Generate(3); + var users = TestData.UserFaker().Generate(3); // Act _ = await _userRepository.BulkInsertAsync(users, TestContext.CancellationToken); @@ -144,7 +130,7 @@ public async Task BulkInsertAsync_Should_Set_CreatedAt_And_UpdatedAt() public async Task BulkInsertAsync_Should_Persist_Entities_To_Database() { // Arrange - var users = UserFaker().Generate(3); + var users = TestData.UserFaker().Generate(3); // Act _ = await _userRepository.BulkInsertAsync(users, TestContext.CancellationToken); @@ -178,7 +164,7 @@ public async Task BulkInsertAsync_Should_Handle_Empty_Collection() public async Task BulkInsertAsync_Should_Handle_Cancellation() { // Arrange - var users = UserFaker().Generate(5); + var users = TestData.UserFaker().Generate(5); using var cts = new CancellationTokenSource(); await cts.CancelAsync(); @@ -196,7 +182,7 @@ public async Task BulkInsertAsync_Should_Handle_Cancellation() public async Task GetByIdAsync_Should_Return_Entity_By_Id() { // Arrange - var user = UserFaker().Generate(); + var user = TestData.UserFaker().Generate(); _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); @@ -247,7 +233,7 @@ public async Task GetByIdAsync_Should_Handle_Cancellation() public async Task GetAll_Should_Return_All_Entities() { // Arrange - var users = UserFaker().Generate(3); + var users = TestData.UserFaker().Generate(3); _ = await _userRepository.BulkInsertAsync(users, TestContext.CancellationToken); @@ -255,12 +241,7 @@ public async Task GetAll_Should_Return_All_Entities() _userRepository.Dispose(); await _dbContext.DisposeAsync(); - var options = new DbContextOptionsBuilder() - .UseNpgsql(DatabaseFixture.ConnectionString) - .Options; - - using var dbContext = new CoreDbContext(options); - using var repository = new BaseRepository(dbContext); + using var repository = CreateFreshRepository(); // Act var result = repository.GetAll().ToList(); @@ -278,7 +259,7 @@ public async Task GetAll_Should_Return_All_Entities() public async Task GetAll_Should_Return_Entities_As_NoTracking() { // Arrange - var user = UserFaker().Generate(); + var user = TestData.UserFaker().Generate(); _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); @@ -305,12 +286,7 @@ public async Task GetAll_Should_Return_Entities_As_NoTracking() public async Task GetAll_Should_Return_Empty_Collection_When_No_Entities() { // Arrange - var options = new DbContextOptionsBuilder() - .UseNpgsql(DatabaseFixture.ConnectionString) - .Options; - - using var dbContext = new CoreDbContext(options); - using var repository = new BaseRepository(dbContext); + using var repository = CreateFreshRepository(); // Act - Try to get all invoices (likely none exist at test start) var result = repository.GetAll().ToList(); @@ -327,7 +303,7 @@ public async Task GetAll_Should_Return_Empty_Collection_When_No_Entities() public async Task Remove_Should_Mark_Entity_As_Deleted() { // Arrange - var user = UserFaker().Generate(); + var user = TestData.UserFaker().Generate(); _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); @@ -343,7 +319,7 @@ public async Task Remove_Should_Mark_Entity_As_Deleted() public async Task Remove_Should_Delete_Entity_From_Database() { // Arrange - var user = UserFaker().Generate(); + var user = TestData.UserFaker().Generate(); _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); @@ -365,7 +341,7 @@ public async Task Remove_Should_Delete_Entity_From_Database() public async Task Remove_Should_Attach_Detached_Entity_Before_Deleting() { // Arrange - var user = UserFaker().Generate(); + var user = TestData.UserFaker().Generate(); _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); @@ -388,7 +364,7 @@ public async Task Remove_Should_Attach_Detached_Entity_Before_Deleting() public async Task Update_Should_Mark_Entity_As_Modified() { // Arrange - var user = UserFaker().Generate(); + var user = TestData.UserFaker().Generate(); _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); @@ -407,7 +383,7 @@ public async Task Update_Should_Mark_Entity_As_Modified() public async Task Update_Should_Persist_Changes_To_Database() { // Arrange - var user = UserFaker().Generate(); + var user = TestData.UserFaker().Generate(); _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); @@ -432,7 +408,7 @@ public async Task Update_Should_Persist_Changes_To_Database() public async Task Update_Should_Attach_Detached_Entity_Before_Updating() { // Arrange - var user = UserFaker().Generate(); + var user = TestData.UserFaker().Generate(); _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); @@ -452,7 +428,7 @@ public async Task Update_Should_Attach_Detached_Entity_Before_Updating() public async Task Update_Should_Return_Updated_Entity() { // Arrange - var user = UserFaker().Generate(); + var user = TestData.UserFaker().Generate(); _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); @@ -473,13 +449,13 @@ public async Task Update_Should_Return_Updated_Entity() public async Task Where_Should_Return_Filtered_Entities() { // Arrange - var user1 = UserFaker() + var user1 = TestData.UserFaker() .RuleFor(u => u.Email, _ => "test1@example.com") .Generate(); - var user2 = UserFaker() + var user2 = TestData.UserFaker() .RuleFor(u => u.Email, _ => "test2@example.com") .Generate(); - var user3 = UserFaker() + var user3 = TestData.UserFaker() .RuleFor(u => u.Email, _ => "test3@example.com") .Generate(); @@ -500,13 +476,13 @@ public async Task Where_Should_Return_Filtered_Entities() public async Task Where_Should_Return_Multiple_Matching_Entities() { // Arrange - var user1 = UserFaker() + var user1 = TestData.UserFaker() .RuleFor(u => u.Name, _ => "Jack Doe") .Generate(); - var user2 = UserFaker() + var user2 = TestData.UserFaker() .RuleFor(u => u.Name, _ => "Jane Smith") .Generate(); - var user3 = UserFaker() + var user3 = TestData.UserFaker() .RuleFor(u => u.Name, _ => "Jack Smith") .Generate(); @@ -527,7 +503,7 @@ public async Task Where_Should_Return_Multiple_Matching_Entities() public async Task Where_Should_Return_Empty_Collection_When_No_Match() { // Arrange - var user = UserFaker().Generate(); + var user = TestData.UserFaker().Generate(); _ = await _userRepository.AddAsync(user, TestContext.CancellationToken); await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); @@ -542,11 +518,11 @@ public async Task Where_Should_Return_Empty_Collection_When_No_Match() public async Task Where_Should_Support_Complex_Predicates() { // Arrange - var user1 = UserFaker() + var user1 = TestData.UserFaker() .RuleFor(u => u.Name, _ => "Alice") .RuleFor(u => u.TelegramChatId, _ => 1000) .Generate(); - var user2 = UserFaker() + var user2 = TestData.UserFaker() .RuleFor(u => u.Name, _ => "Bob") .RuleFor(u => u.TelegramChatId, _ => 2000) .Generate(); @@ -609,4 +585,19 @@ public void Dispose_Should_Be_Safe_To_Call_Multiple_Times() } #endregion + + #region Helper Methods + + private BaseRepository CreateFreshRepository() where T : class + { + var options = new DbContextOptionsBuilder() + .UseNpgsql(DatabaseFixture.ConnectionString) + .Options; + + var dbContext = new CoreDbContext(options); + + return new BaseRepository(dbContext); + } + + #endregion } diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/EmailAuthTokenRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/EmailAuthTokenRepositoryIntegrationTests.cs index 8f2bc1e..e7167b3 100644 --- a/InvoiceReminder.IntegrationTests/Data/Repository/EmailAuthTokenRepositoryIntegrationTests.cs +++ b/InvoiceReminder.IntegrationTests/Data/Repository/EmailAuthTokenRepositoryIntegrationTests.cs @@ -1,9 +1,10 @@ -using Bogus; using InvoiceReminder.Data.Exceptions; +using InvoiceReminder.Data.Interfaces; using InvoiceReminder.Data.Persistence; using InvoiceReminder.Data.Repository; using InvoiceReminder.Domain.Entities; using InvoiceReminder.IntegrationTests.Data.ContainerSetup; +using InvoiceReminder.IntegrationTests.Data.Utils; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using NSubstitute; @@ -35,59 +36,32 @@ public EmailAuthTokenRepositoryIntegrationTests() _unitOfWork = new UnitOfWork(_dbContext, _unitOfWorkLogger); } - #region Helper Methods - - private static Faker UserFaker() - { - return new Faker() - .RuleFor(u => u.Id, _ => Guid.NewGuid()) - .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) - .RuleFor(u => u.Name, f => f.Person.FullName) - .RuleFor(u => u.Email, f => f.Internet.Email()) - .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); - } - - private static Faker EmailAuthTokenFaker() - { - return new Faker() - .RuleFor(e => e.Id, faker => faker.Random.Guid()) - .RuleFor(e => e.UserId, faker => faker.Random.Guid()) - .RuleFor(e => e.AccessToken, faker => faker.Random.AlphaNumeric(128)) - .RuleFor(e => e.RefreshToken, faker => faker.Random.AlphaNumeric(128)) - .RuleFor(e => e.TokenProvider, faker => faker.PickRandom("Google", "Microsoft", "GitHub")) - .RuleFor(e => e.NonceValue, faker => faker.Random.Hash()) - .RuleFor(e => e.AccessTokenExpiry, faker => faker.Date.Future().ToUniversalTime()) - .RuleFor(e => e.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) - .RuleFor(e => e.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); - } - - private async Task CreateAndSaveEmailAuthTokenAsync(EmailAuthToken emailAuthToken = null) + [TestCleanup] + public void TestCleanup() { - var user = await CreateAndSaveUserAsync(); - emailAuthToken ??= EmailAuthTokenFaker() - .RuleFor(e => e.UserId, _ => user.Id) - .Generate(); - - _ = await _repository.AddAsync(emailAuthToken, TestContext.CancellationToken); - await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); - - return emailAuthToken; + _unitOfWork?.Dispose(); + _repository?.Dispose(); + _dbContext?.Dispose(); } - private async Task CreateAndSaveUserAsync(User user = null) + [TestMethod] + public void EmailAuthTokenRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() { - user ??= UserFaker().Generate(); - var logger = Substitute.For>(); ; - var userRepository = new UserRepository(_dbContext, logger); + // Arrange && Act + var repository = new EmailAuthTokenRepository(_dbContext, _repositoryLogger); - _ = await userRepository.AddAsync(user, TestContext.CancellationToken); - await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + // Assert + repository.ShouldSatisfyAllConditions(() => + { + _ = repository.ShouldBeAssignableTo(); + _ = repository.ShouldBeAssignableTo>(); + _ = repository.ShouldBeAssignableTo>(); - return user; + _ = repository.ShouldNotBeNull(); + _ = repository.ShouldBeOfType(); + }); } - #endregion - #region GetByIdAsync Tests [TestMethod] @@ -196,11 +170,11 @@ public async Task GetByUserIdAsync_Should_Retrieve_Correct_Token_When_Multiple_E { // Arrange var user = await CreateAndSaveUserAsync(); - var googleToken = EmailAuthTokenFaker() + var googleToken = TestData.EmailAuthTokenFaker() .RuleFor(e => e.UserId, _ => user.Id) .RuleFor(e => e.TokenProvider, _ => "Google") .Generate(); - var microsoftToken = EmailAuthTokenFaker() + var microsoftToken = TestData.EmailAuthTokenFaker() .RuleFor(e => e.UserId, _ => user.Id) .RuleFor(e => e.TokenProvider, _ => "Microsoft") .Generate(); @@ -260,7 +234,6 @@ public async Task GetByUserIdAsync_Should_Throw_Exception_On_Database_Error() public async Task GetByUserIdAsync_Should_Handle_Cancellation_Request() { // Arrange - var emailAuthToken = await CreateAndSaveEmailAuthTokenAsync(); using var cts = new CancellationTokenSource(); await cts.CancelAsync(); @@ -268,7 +241,7 @@ public async Task GetByUserIdAsync_Should_Handle_Cancellation_Request() // Act & Assert _ = await Should.ThrowAsync( - async () => await _repository.GetByUserIdAsync(emailAuthToken.UserId, emailAuthToken.TokenProvider, cts.Token) + async () => await _repository.GetByUserIdAsync(Guid.NewGuid(), "Google", cts.Token) ); _repositoryLogger.Received(1).Log( @@ -281,4 +254,33 @@ public async Task GetByUserIdAsync_Should_Handle_Cancellation_Request() } #endregion + + #region Helper Methods + + private async Task CreateAndSaveEmailAuthTokenAsync(EmailAuthToken emailAuthToken = null) + { + var user = await CreateAndSaveUserAsync(); + emailAuthToken ??= TestData.EmailAuthTokenFaker() + .RuleFor(e => e.UserId, _ => user.Id) + .Generate(); + + _ = await _repository.AddAsync(emailAuthToken, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return emailAuthToken; + } + + private async Task CreateAndSaveUserAsync(User user = null) + { + user ??= TestData.UserFaker().Generate(); + var logger = Substitute.For>(); + var userRepository = new UserRepository(_dbContext, logger); + + _ = await userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return user; + } + + #endregion } diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/InvoiceRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/InvoiceRepositoryIntegrationTests.cs index 071b9b6..27006df 100644 --- a/InvoiceReminder.IntegrationTests/Data/Repository/InvoiceRepositoryIntegrationTests.cs +++ b/InvoiceReminder.IntegrationTests/Data/Repository/InvoiceRepositoryIntegrationTests.cs @@ -1,9 +1,10 @@ -using Bogus; using InvoiceReminder.Data.Exceptions; +using InvoiceReminder.Data.Interfaces; using InvoiceReminder.Data.Persistence; using InvoiceReminder.Data.Repository; using InvoiceReminder.Domain.Entities; using InvoiceReminder.IntegrationTests.Data.ContainerSetup; +using InvoiceReminder.IntegrationTests.Data.Utils; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using NSubstitute; @@ -35,67 +36,32 @@ public InvoiceRepositoryIntegrationTests() _unitOfWork = new UnitOfWork(_dbContext, _unitOfWorkLogger); } - #region Helper Methods - - private static Faker UserFaker() - { - return new Faker() - .RuleFor(u => u.Id, _ => Guid.NewGuid()) - .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) - .RuleFor(u => u.Name, f => f.Person.FullName) - .RuleFor(u => u.Email, f => f.Internet.Email()) - .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); - } - - private static Faker InvoiceFaker() + [TestCleanup] + public void TestCleanup() { - return new Faker() - .RuleFor(i => i.Id, faker => faker.Random.Guid()) - .RuleFor(i => i.UserId, faker => faker.Random.Guid()) - .RuleFor(i => i.Bank, faker => faker.PickRandom( - "Banco do Brasil", - "Bradesco", - "Itaú", - "Caixa Econômica Federal", - "Santander", - "Safra", - "Citibank", - "BTG Pactual")) - .RuleFor(i => i.Beneficiary, faker => faker.Person.FullName) - .RuleFor(i => i.Amount, faker => faker.Finance.Amount(10, 10000)) - .RuleFor(i => i.Barcode, faker => faker.Random.AlphaNumeric(44)) - .RuleFor(i => i.DueDate, faker => faker.Date.Future().ToUniversalTime()) - .RuleFor(i => i.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) - .RuleFor(i => i.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + _unitOfWork?.Dispose(); + _repository?.Dispose(); + _dbContext?.Dispose(); } - private async Task CreateAndSaveUserAsync(User user = null) - { - user ??= UserFaker().Generate(); - var logger = Substitute.For>(); - var userRepository = new UserRepository(_dbContext, logger); - - _ = await userRepository.AddAsync(user, TestContext.CancellationToken); - await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); - - return user; - } - - private async Task CreateAndSaveInvoiceAsync(Invoice invoice = null) + [TestMethod] + public void InvoiceRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() { - var user = await CreateAndSaveUserAsync(); - invoice ??= InvoiceFaker() - .RuleFor(i => i.UserId, _ => user.Id) - .Generate(); + // Arrange && Act + var repository = new InvoiceRepository(_dbContext, _repositoryLogger); - _ = await _repository.AddAsync(invoice, TestContext.CancellationToken); - await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + // Assert + repository.ShouldSatisfyAllConditions(() => + { + _ = repository.ShouldBeAssignableTo(); + _ = repository.ShouldBeAssignableTo>(); + _ = repository.ShouldBeAssignableTo>(); - return invoice; + _ = repository.ShouldNotBeNull(); + _ = repository.ShouldBeOfType(); + }); } - #endregion - #region GetByIdAsync Tests [TestMethod] @@ -210,10 +176,10 @@ public async Task GetByBarcodeAsync_Should_Retrieve_Correct_Invoice_When_Multipl { // Arrange var user = await CreateAndSaveUserAsync(); - var invoice1 = InvoiceFaker() + var invoice1 = TestData.InvoiceFaker() .RuleFor(i => i.UserId, _ => user.Id) .Generate(); - var invoice2 = InvoiceFaker() + var invoice2 = TestData.InvoiceFaker() .RuleFor(i => i.UserId, _ => user.Id) .Generate(); @@ -273,7 +239,6 @@ public async Task GetByBarcodeAsync_Should_Throw_Exception_On_Database_Error() public async Task GetByBarcodeAsync_Should_Handle_Cancellation_Request() { // Arrange - var invoice = await CreateAndSaveInvoiceAsync(); using var cts = new CancellationTokenSource(); await cts.CancelAsync(); @@ -281,7 +246,7 @@ public async Task GetByBarcodeAsync_Should_Handle_Cancellation_Request() // Act & Assert _ = await Should.ThrowAsync( - async () => await _repository.GetByBarcodeAsync(invoice.Barcode, cts.Token) + async () => await _repository.GetByBarcodeAsync("##########", cts.Token) ); _repositoryLogger.Received(1).Log( @@ -294,4 +259,33 @@ public async Task GetByBarcodeAsync_Should_Handle_Cancellation_Request() } #endregion + + #region Helper Methods + + private async Task CreateAndSaveUserAsync(User user = null) + { + user ??= TestData.UserFaker().Generate(); + var logger = Substitute.For>(); + var userRepository = new UserRepository(_dbContext, logger); + + _ = await userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return user; + } + + private async Task CreateAndSaveInvoiceAsync(Invoice invoice = null) + { + var user = await CreateAndSaveUserAsync(); + invoice ??= TestData.InvoiceFaker() + .RuleFor(i => i.UserId, _ => user.Id) + .Generate(); + + _ = await _repository.AddAsync(invoice, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return invoice; + } + + #endregion } diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/JobScheduleRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/JobScheduleRepositoryIntegrationTests.cs index 868cd64..8ed0ce3 100644 --- a/InvoiceReminder.IntegrationTests/Data/Repository/JobScheduleRepositoryIntegrationTests.cs +++ b/InvoiceReminder.IntegrationTests/Data/Repository/JobScheduleRepositoryIntegrationTests.cs @@ -1,9 +1,10 @@ -using Bogus; using InvoiceReminder.Data.Exceptions; +using InvoiceReminder.Data.Interfaces; using InvoiceReminder.Data.Persistence; using InvoiceReminder.Data.Repository; using InvoiceReminder.Domain.Entities; using InvoiceReminder.IntegrationTests.Data.ContainerSetup; +using InvoiceReminder.IntegrationTests.Data.Utils; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using NSubstitute; @@ -35,61 +36,32 @@ public JobScheduleRepositoryIntegrationTests() _unitOfWork = new UnitOfWork(_dbContext, _unitOfWorkLogger); } - #region Helper Methods - - private static Faker UserFaker() - { - return new Faker() - .RuleFor(u => u.Id, _ => Guid.NewGuid()) - .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) - .RuleFor(u => u.Name, f => f.Person.FullName) - .RuleFor(u => u.Email, f => f.Internet.Email()) - .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); - } - - private static Faker JobScheduleFaker() + [TestCleanup] + public void TestCleanup() { - return new Faker() - .RuleFor(j => j.Id, faker => faker.Random.Guid()) - .RuleFor(j => j.UserId, faker => faker.Random.Guid()) - .RuleFor(j => j.CronExpression, faker => faker.PickRandom( - "0 0 * * *", - "0 */6 * * *", - "0 */12 * * *", - "0 9 * * MON", - "0 9 * * MON-FRI", - "0 0 1 * *")) - .RuleFor(j => j.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) - .RuleFor(j => j.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + _unitOfWork?.Dispose(); + _repository?.Dispose(); + _dbContext?.Dispose(); } - private async Task CreateAndSaveUserAsync(User user = null) - { - user ??= UserFaker().Generate(); - var logger = Substitute.For>(); - var userRepository = new UserRepository(_dbContext, logger); - - _ = await userRepository.AddAsync(user, TestContext.CancellationToken); - await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); - - return user; - } - - private async Task CreateAndSaveJobScheduleAsync(JobSchedule jobSchedule = null) + [TestMethod] + public void JobScheduleRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() { - var user = await CreateAndSaveUserAsync(); - jobSchedule ??= JobScheduleFaker() - .RuleFor(j => j.UserId, _ => user.Id) - .Generate(); + // Arrange && Act + var repository = new JobScheduleRepository(_dbContext, _repositoryLogger); - _ = await _repository.AddAsync(jobSchedule, TestContext.CancellationToken); - await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + // Assert + repository.ShouldSatisfyAllConditions(() => + { + _ = repository.ShouldBeAssignableTo(); + _ = repository.ShouldBeAssignableTo>(); + _ = repository.ShouldBeAssignableTo>(); - return jobSchedule; + _ = repository.ShouldNotBeNull(); + _ = repository.ShouldBeOfType(); + }); } - #endregion - #region GetByIdAsync Tests [TestMethod] @@ -170,7 +142,7 @@ public async Task GetByUserIdAsync_Should_Return_JobSchedules_By_UserId() { // Arrange var user = await CreateAndSaveUserAsync(); - var jobSchedule = JobScheduleFaker() + var jobSchedule = TestData.JobScheduleFaker() .RuleFor(j => j.UserId, _ => user.Id) .Generate(); @@ -195,7 +167,7 @@ public async Task GetByUserIdAsync_Should_Return_Multiple_JobSchedules_For_Same_ { // Arrange var user = await CreateAndSaveUserAsync(); - var jobSchedules = JobScheduleFaker() + var jobSchedules = TestData.JobScheduleFaker() .RuleFor(j => j.UserId, _ => user.Id) .Generate(3); @@ -222,10 +194,10 @@ public async Task GetByUserIdAsync_Should_Return_Only_JobSchedules_For_Specified var user1 = await CreateAndSaveUserAsync(); var user2 = await CreateAndSaveUserAsync(); - var jobSchedule1 = JobScheduleFaker() + var jobSchedule1 = TestData.JobScheduleFaker() .RuleFor(j => j.UserId, _ => user1.Id) .Generate(); - var jobSchedule2 = JobScheduleFaker() + var jobSchedule2 = TestData.JobScheduleFaker() .RuleFor(j => j.UserId, _ => user2.Id) .Generate(); @@ -285,7 +257,6 @@ public async Task GetByUserIdAsync_Should_Throw_Exception_On_Database_Error() public async Task GetByUserIdAsync_Should_Handle_Cancellation_Request() { // Arrange - var jobSchedule = await CreateAndSaveJobScheduleAsync(); using var cts = new CancellationTokenSource(); await cts.CancelAsync(); @@ -293,7 +264,7 @@ public async Task GetByUserIdAsync_Should_Handle_Cancellation_Request() // Act & Assert _ = await Should.ThrowAsync( - async () => await _repository.GetByUserIdAsync(jobSchedule.UserId, cts.Token) + async () => await _repository.GetByUserIdAsync(Guid.NewGuid(), cts.Token) ); _repositoryLogger.Received(1).Log( @@ -306,4 +277,33 @@ public async Task GetByUserIdAsync_Should_Handle_Cancellation_Request() } #endregion + + #region Helper Methods + + private async Task CreateAndSaveUserAsync(User user = null) + { + user ??= TestData.UserFaker().Generate(); + var logger = Substitute.For>(); + var userRepository = new UserRepository(_dbContext, logger); + + _ = await userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return user; + } + + private async Task CreateAndSaveJobScheduleAsync(JobSchedule jobSchedule = null) + { + var user = await CreateAndSaveUserAsync(); + jobSchedule ??= TestData.JobScheduleFaker() + .RuleFor(j => j.UserId, _ => user.Id) + .Generate(); + + _ = await _repository.AddAsync(jobSchedule, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return jobSchedule; + } + + #endregion } diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/ScanEmailDefinitionRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/ScanEmailDefinitionRepositoryIntegrationTests.cs index 36c3541..28e861b 100644 --- a/InvoiceReminder.IntegrationTests/Data/Repository/ScanEmailDefinitionRepositoryIntegrationTests.cs +++ b/InvoiceReminder.IntegrationTests/Data/Repository/ScanEmailDefinitionRepositoryIntegrationTests.cs @@ -1,10 +1,10 @@ -using Bogus; using InvoiceReminder.Data.Exceptions; +using InvoiceReminder.Data.Interfaces; using InvoiceReminder.Data.Persistence; using InvoiceReminder.Data.Repository; using InvoiceReminder.Domain.Entities; -using InvoiceReminder.Domain.Enums; using InvoiceReminder.IntegrationTests.Data.ContainerSetup; +using InvoiceReminder.IntegrationTests.Data.Utils; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using NSubstitute; @@ -30,65 +30,38 @@ public ScanEmailDefinitionRepositoryIntegrationTests() .Options; _dbContext = new CoreDbContext(options); - _repositoryLogger = Substitute.For>(); ; + _repositoryLogger = Substitute.For>(); _unitOfWorkLogger = Substitute.For>(); _repository = new ScanEmailDefinitionRepository(_dbContext, _repositoryLogger); _unitOfWork = new UnitOfWork(_dbContext, _unitOfWorkLogger); } - #region Helper Methods - - private static Faker UserFaker() - { - return new Faker() - .RuleFor(u => u.Id, _ => Guid.NewGuid()) - .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) - .RuleFor(u => u.Name, f => f.Person.FullName) - .RuleFor(u => u.Email, f => f.Internet.Email()) - .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); - } - - private static Faker ScanEmailDefinitionFaker() + [TestCleanup] + public void TestCleanup() { - return new Faker() - .RuleFor(s => s.Id, faker => faker.Random.Guid()) - .RuleFor(s => s.UserId, faker => faker.Random.Guid()) - .RuleFor(s => s.InvoiceType, faker => faker.PickRandom(InvoiceType.AccountInvoice, InvoiceType.BankInvoice)) - .RuleFor(s => s.Beneficiary, faker => faker.Person.FullName) - .RuleFor(s => s.Description, faker => faker.Lorem.Sentence()) - .RuleFor(s => s.SenderEmailAddress, faker => faker.Internet.Email()) - .RuleFor(s => s.AttachmentFileName, faker => faker.System.FileName("pdf")) - .RuleFor(s => s.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) - .RuleFor(s => s.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); - } - - private async Task CreateAndSaveUserAsync(User user = null) - { - user ??= UserFaker().Generate(); - var logger = Substitute.For>(); - var userRepository = new UserRepository(_dbContext, logger); - - _ = await userRepository.AddAsync(user, TestContext.CancellationToken); - await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); - - return user; + _unitOfWork?.Dispose(); + _repository?.Dispose(); + _dbContext?.Dispose(); } - private async Task CreateAndSaveScanEmailDefinitionAsync(ScanEmailDefinition scanEmailDefinition = null) + [TestMethod] + public void ScanEmailDefinitionRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() { - var user = await CreateAndSaveUserAsync(); - scanEmailDefinition ??= ScanEmailDefinitionFaker() - .RuleFor(s => s.UserId, _ => user.Id) - .Generate(); + // Arrange && Act + var repository = new ScanEmailDefinitionRepository(_dbContext, _repositoryLogger); - _ = await _repository.AddAsync(scanEmailDefinition, TestContext.CancellationToken); - await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + // Assert + repository.ShouldSatisfyAllConditions(() => + { + _ = repository.ShouldBeAssignableTo(); + _ = repository.ShouldBeAssignableTo>(); + _ = repository.ShouldBeAssignableTo>(); - return scanEmailDefinition; + _ = repository.ShouldNotBeNull(); + _ = repository.ShouldBeOfType(); + }); } - #endregion - #region GetByIdAsync Tests [TestMethod] @@ -214,10 +187,10 @@ public async Task GetBySenderEmailAddressAsync_Should_Retrieve_Correct_Definitio { // Arrange var user = await CreateAndSaveUserAsync(); - var definition1 = ScanEmailDefinitionFaker() + var definition1 = TestData.ScanEmailDefinitionFaker() .RuleFor(s => s.UserId, _ => user.Id) .Generate(); - var definition2 = ScanEmailDefinitionFaker() + var definition2 = TestData.ScanEmailDefinitionFaker() .RuleFor(s => s.UserId, _ => user.Id) .Generate(); @@ -344,10 +317,10 @@ public async Task GetBySenderBeneficiaryAsync_Should_Retrieve_Correct_Definition { // Arrange var user = await CreateAndSaveUserAsync(); - var definition1 = ScanEmailDefinitionFaker() + var definition1 = TestData.ScanEmailDefinitionFaker() .RuleFor(s => s.UserId, _ => user.Id) .Generate(); - var definition2 = ScanEmailDefinitionFaker() + var definition2 = TestData.ScanEmailDefinitionFaker() .RuleFor(s => s.UserId, _ => user.Id) .Generate(); @@ -425,7 +398,7 @@ public async Task GetByUserIdAsync_Should_Return_ScanEmailDefinitions_By_UserId( { // Arrange var user = await CreateAndSaveUserAsync(); - var scanEmailDefinition = ScanEmailDefinitionFaker() + var scanEmailDefinition = TestData.ScanEmailDefinitionFaker() .RuleFor(s => s.UserId, _ => user.Id) .Generate(); @@ -450,7 +423,7 @@ public async Task GetByUserIdAsync_Should_Return_Multiple_ScanEmailDefinitions_F { // Arrange var user = await CreateAndSaveUserAsync(); - var scanEmailDefinitions = ScanEmailDefinitionFaker() + var scanEmailDefinitions = TestData.ScanEmailDefinitionFaker() .RuleFor(s => s.UserId, _ => user.Id) .Generate(3); @@ -478,10 +451,10 @@ public async Task GetByUserIdAsync_Should_Return_Only_ScanEmailDefinitions_For_S var user1 = await CreateAndSaveUserAsync(); var user2 = await CreateAndSaveUserAsync(); - var definition1 = ScanEmailDefinitionFaker() + var definition1 = TestData.ScanEmailDefinitionFaker() .RuleFor(s => s.UserId, _ => user1.Id) .Generate(); - var definition2 = ScanEmailDefinitionFaker() + var definition2 = TestData.ScanEmailDefinitionFaker() .RuleFor(s => s.UserId, _ => user2.Id) .Generate(); @@ -541,7 +514,6 @@ public async Task GetByUserIdAsync_Should_Throw_Exception_On_Database_Error() public async Task GetBySenderEmailAddressAsync_Should_Handle_Cancellation_Request() { // Arrange - var scanEmailDefinition = await CreateAndSaveScanEmailDefinitionAsync(); using var cts = new CancellationTokenSource(); await cts.CancelAsync(); @@ -549,7 +521,7 @@ public async Task GetBySenderEmailAddressAsync_Should_Handle_Cancellation_Reques // Act & Assert _ = await Should.ThrowAsync( - async () => await _repository.GetBySenderEmailAddressAsync(scanEmailDefinition.SenderEmailAddress, scanEmailDefinition.UserId, cts.Token) + async () => await _repository.GetBySenderEmailAddressAsync("any@mail.com", Guid.NewGuid(), cts.Token) ); _repositoryLogger.Received(1).Log( @@ -565,7 +537,6 @@ public async Task GetBySenderEmailAddressAsync_Should_Handle_Cancellation_Reques public async Task GetBySenderBeneficiaryAsync_Should_Handle_Cancellation_Request() { // Arrange - var scanEmailDefinition = await CreateAndSaveScanEmailDefinitionAsync(); using var cts = new CancellationTokenSource(); await cts.CancelAsync(); @@ -573,7 +544,7 @@ public async Task GetBySenderBeneficiaryAsync_Should_Handle_Cancellation_Request // Act & Assert _ = await Should.ThrowAsync( - async () => await _repository.GetBySenderBeneficiaryAsync(scanEmailDefinition.Beneficiary, scanEmailDefinition.UserId, cts.Token) + async () => await _repository.GetBySenderBeneficiaryAsync("Beneficiary", Guid.NewGuid(), cts.Token) ); _repositoryLogger.Received(1).Log( @@ -589,7 +560,6 @@ public async Task GetBySenderBeneficiaryAsync_Should_Handle_Cancellation_Request public async Task GetByUserIdAsync_Should_Handle_Cancellation_Request() { // Arrange - var scanEmailDefinition = await CreateAndSaveScanEmailDefinitionAsync(); using var cts = new CancellationTokenSource(); await cts.CancelAsync(); @@ -597,7 +567,7 @@ public async Task GetByUserIdAsync_Should_Handle_Cancellation_Request() // Act & Assert _ = await Should.ThrowAsync( - async () => await _repository.GetByUserIdAsync(scanEmailDefinition.UserId, cts.Token) + async () => await _repository.GetByUserIdAsync(Guid.NewGuid(), cts.Token) ); _repositoryLogger.Received(1).Log( @@ -610,4 +580,33 @@ public async Task GetByUserIdAsync_Should_Handle_Cancellation_Request() } #endregion + + #region Helper Methods + + private async Task CreateAndSaveUserAsync(User user = null) + { + user ??= TestData.UserFaker().Generate(); + var logger = Substitute.For>(); + var userRepository = new UserRepository(_dbContext, logger); + + _ = await userRepository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return user; + } + + private async Task CreateAndSaveScanEmailDefinitionAsync(ScanEmailDefinition scanEmailDefinition = null) + { + var user = await CreateAndSaveUserAsync(); + scanEmailDefinition ??= TestData.ScanEmailDefinitionFaker() + .RuleFor(s => s.UserId, _ => user.Id) + .Generate(); + + _ = await _repository.AddAsync(scanEmailDefinition, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return scanEmailDefinition; + } + + #endregion } diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/UnitOfWorkIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/UnitOfWorkIntegrationTests.cs index 93ecd91..191de0b 100644 --- a/InvoiceReminder.IntegrationTests/Data/Repository/UnitOfWorkIntegrationTests.cs +++ b/InvoiceReminder.IntegrationTests/Data/Repository/UnitOfWorkIntegrationTests.cs @@ -330,7 +330,7 @@ public void Dispose_Should_Release_Context_Resources() .Options; var dbContext = new CoreDbContext(options); - var logger = Substitute.For>(); ; + var logger = Substitute.For>(); var unitOfWork = new UnitOfWork(dbContext, logger); // Act diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/UserRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/UserRepositoryIntegrationTests.cs index aaeba1a..7068d06 100644 --- a/InvoiceReminder.IntegrationTests/Data/Repository/UserRepositoryIntegrationTests.cs +++ b/InvoiceReminder.IntegrationTests/Data/Repository/UserRepositoryIntegrationTests.cs @@ -1,9 +1,10 @@ -using Bogus; using InvoiceReminder.Data.Exceptions; +using InvoiceReminder.Data.Interfaces; using InvoiceReminder.Data.Persistence; using InvoiceReminder.Data.Repository; using InvoiceReminder.Domain.Entities; using InvoiceReminder.IntegrationTests.Data.ContainerSetup; +using InvoiceReminder.IntegrationTests.Data.Utils; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using NSubstitute; @@ -35,63 +36,32 @@ public UserRepositoryIntegrationTests() _unitOfWork = new UnitOfWork(_dbContext, _unitOfWorkLogger); } - #region Helper Methods - - private static Faker EmailAuthTokenFaker() - { - return new Faker() - .RuleFor(e => e.Id, faker => faker.Random.Guid()) - .RuleFor(e => e.UserId, faker => faker.Random.Guid()) - .RuleFor(e => e.AccessToken, faker => faker.Random.AlphaNumeric(128)) - .RuleFor(e => e.RefreshToken, faker => faker.Random.AlphaNumeric(128)) - .RuleFor(e => e.TokenProvider, faker => faker.PickRandom("Google", "Microsoft", "GitHub")) - .RuleFor(e => e.NonceValue, faker => faker.Random.Hash()) - .RuleFor(e => e.AccessTokenExpiry, faker => faker.Date.Future().ToUniversalTime()) - .RuleFor(e => e.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) - .RuleFor(e => e.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); - } - - private static Faker InvoiceFaker() + [TestCleanup] + public void TestCleanup() { - return new Faker() - .RuleFor(i => i.Id, faker => faker.Random.Guid()) - .RuleFor(i => i.UserId, faker => faker.Random.Guid()) - .RuleFor(i => i.Bank, faker => faker.PickRandom( - "Banco do Brasil", - "Bradesco", - "Itaú", - "Caixa Econômica Federal", - "Santander", - "Safra", - "Citibank", - "BTG Pactual")) - .RuleFor(i => i.Beneficiary, faker => faker.Person.FullName) - .RuleFor(i => i.Amount, faker => faker.Finance.Amount(10, 10000)) - .RuleFor(i => i.Barcode, faker => faker.Random.AlphaNumeric(44)) - .RuleFor(i => i.DueDate, faker => faker.Date.Future().ToUniversalTime()); + _unitOfWork?.Dispose(); + _repository?.Dispose(); + _dbContext?.Dispose(); } - private static Faker UserFaker() + [TestMethod] + public void UserRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() { - return new Faker() - .RuleFor(u => u.Id, _ => Guid.NewGuid()) - .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) - .RuleFor(u => u.Name, f => f.Person.FullName) - .RuleFor(u => u.Email, f => f.Internet.Email()) - .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); - } + // Arrange && Act + var repository = new UserRepository(_dbContext, _repositoryLogger); - private async Task CreateAndSaveUserAsync(User user = null) - { - user ??= UserFaker().Generate(); - _ = await _repository.AddAsync(user, TestContext.CancellationToken); - await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + // Assert + repository.ShouldSatisfyAllConditions(() => + { + _ = repository.ShouldBeAssignableTo(); + _ = repository.ShouldBeAssignableTo>(); + _ = repository.ShouldBeAssignableTo>(); - return user; + _ = repository.ShouldNotBeNull(); + _ = repository.ShouldBeOfType(); + }); } - #endregion - #region GetByIdAsync Tests [TestMethod] @@ -129,8 +99,8 @@ public async Task GetByIdAsync_Should_Return_Null_For_NonExistent_User() public async Task GetByIdAsync_Should_Load_Related_Entities() { // Arrange - var user = UserFaker().Generate(); - var invoice = InvoiceFaker() + var user = TestData.UserFaker().Generate(); + var invoice = TestData.InvoiceFaker() .RuleFor(u => u.UserId, _ => user.Id) .Generate(); @@ -220,8 +190,8 @@ public async Task GetByEmailAsync_Should_Return_Null_For_NonExistent_Email() public async Task GetByEmailAsync_Should_Find_User_With_Multiple_Related_Entities() { // Arrange - var user = UserFaker().Generate(); - var emailToken = EmailAuthTokenFaker() + var user = TestData.UserFaker().Generate(); + var emailToken = TestData.EmailAuthTokenFaker() .RuleFor(e => e.UserId, _ => user.Id) .Generate(); @@ -296,7 +266,6 @@ public async Task GetByEmailAsync_Should_Throw_Exception_On_Database_Error() public async Task GetByIdAsync_Should_Handle_Cancellation_Request() { // Arrange - var user = await CreateAndSaveUserAsync(); using var cts = new CancellationTokenSource(); await cts.CancelAsync(); @@ -304,7 +273,7 @@ public async Task GetByIdAsync_Should_Handle_Cancellation_Request() // Act & Assert _ = await Should.ThrowAsync( - async () => await _repository.GetByIdAsync(user.Id, cts.Token) + async () => await _repository.GetByIdAsync(Guid.NewGuid(), cts.Token) ); _repositoryLogger.Received(1).Log( @@ -320,7 +289,6 @@ public async Task GetByIdAsync_Should_Handle_Cancellation_Request() public async Task GetByEmailAsync_Should_Handle_Cancellation_Request() { // Arrange - var user = await CreateAndSaveUserAsync(); using var cts = new CancellationTokenSource(); await cts.CancelAsync(); @@ -328,7 +296,7 @@ public async Task GetByEmailAsync_Should_Handle_Cancellation_Request() // Act & Assert _ = await Should.ThrowAsync( - async () => await _repository.GetByEmailAsync(user.Email, cts.Token) + async () => await _repository.GetByEmailAsync("any@mail.com", cts.Token) ); _repositoryLogger.Received(1).Log( @@ -341,4 +309,17 @@ public async Task GetByEmailAsync_Should_Handle_Cancellation_Request() } #endregion + + #region Helper Methods + + private async Task CreateAndSaveUserAsync(User user = null) + { + user ??= TestData.UserFaker().Generate(); + _ = await _repository.AddAsync(user, TestContext.CancellationToken); + await _unitOfWork.SaveChangesAsync(TestContext.CancellationToken); + + return user; + } + + #endregion } diff --git a/InvoiceReminder.IntegrationTests/Data/Utils/TestData.cs b/InvoiceReminder.IntegrationTests/Data/Utils/TestData.cs new file mode 100644 index 0000000..5111566 --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Utils/TestData.cs @@ -0,0 +1,84 @@ +using Bogus; +using InvoiceReminder.Domain.Entities; +using InvoiceReminder.Domain.Enums; + +namespace InvoiceReminder.IntegrationTests.Data.Utils; + +public static class TestData +{ + public static Faker UserFaker() + { + return new Faker() + .RuleFor(u => u.Id, _ => Guid.NewGuid()) + .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) + .RuleFor(u => u.Name, f => f.Person.FullName) + .RuleFor(u => u.Email, f => f.Internet.Email()) + .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); + } + + public static Faker EmailAuthTokenFaker() + { + return new Faker() + .RuleFor(e => e.Id, faker => faker.Random.Guid()) + .RuleFor(e => e.UserId, faker => faker.Random.Guid()) + .RuleFor(e => e.AccessToken, faker => faker.Random.AlphaNumeric(128)) + .RuleFor(e => e.RefreshToken, faker => faker.Random.AlphaNumeric(128)) + .RuleFor(e => e.TokenProvider, faker => faker.PickRandom("Google", "Microsoft", "GitHub")) + .RuleFor(e => e.NonceValue, faker => faker.Random.Hash()) + .RuleFor(e => e.AccessTokenExpiry, faker => faker.Date.Future().ToUniversalTime()) + .RuleFor(e => e.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) + .RuleFor(e => e.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + } + + public static Faker InvoiceFaker() + { + return new Faker() + .RuleFor(i => i.Id, faker => faker.Random.Guid()) + .RuleFor(i => i.UserId, faker => faker.Random.Guid()) + .RuleFor(i => i.Bank, faker => faker.PickRandom( + "Banco do Brasil", + "Bradesco", + "Itaú", + "Caixa Econômica Federal", + "Santander", + "Safra", + "Citibank", + "BTG Pactual")) + .RuleFor(i => i.Beneficiary, faker => faker.Person.FullName) + .RuleFor(i => i.Amount, faker => faker.Finance.Amount(10, 10000)) + .RuleFor(i => i.Barcode, faker => faker.Random.AlphaNumeric(44)) + .RuleFor(i => i.DueDate, faker => faker.Date.Future().ToUniversalTime()) + .RuleFor(i => i.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) + .RuleFor(i => i.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + } + + public static Faker JobScheduleFaker() + { + return new Faker() + .RuleFor(j => j.Id, faker => faker.Random.Guid()) + .RuleFor(j => j.UserId, faker => faker.Random.Guid()) + .RuleFor(j => j.CronExpression, faker => faker.PickRandom( + "0 0 * * *", + "0 */6 * * *", + "0 */12 * * *", + "0 9 * * MON", + "0 9 * * MON-FRI", + "0 0 1 * *")) + .RuleFor(j => j.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) + .RuleFor(j => j.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + } + + public static Faker ScanEmailDefinitionFaker() + { + return new Faker() + .RuleFor(s => s.Id, faker => faker.Random.Guid()) + .RuleFor(s => s.UserId, faker => faker.Random.Guid()) + .RuleFor(s => s.InvoiceType, faker => faker.PickRandom(InvoiceType.AccountInvoice, InvoiceType.BankInvoice)) + .RuleFor(s => s.Beneficiary, faker => faker.Person.FullName) + .RuleFor(s => s.Description, faker => faker.Lorem.Sentence()) + .RuleFor(s => s.SenderEmailAddress, faker => faker.Internet.Email()) + .RuleFor(s => s.AttachmentFileName, faker => faker.System.FileName("pdf")) + .RuleFor(s => s.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) + .RuleFor(s => s.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + } +} diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs new file mode 100644 index 0000000..88ddc79 --- /dev/null +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs @@ -0,0 +1,105 @@ +using InvoiceReminder.Data.Persistence.EntitiesConfig; +using InvoiceReminder.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using Shouldly; + +namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; + +[TestClass] +public sealed class EmailAuthTokenConfigTests +{ + [TestMethod] + public void EmailAuthTokenConfig_ShouldNotThrowErrorWhenInstantiated() + { + // Arrange && Act + Action action = () => _ = new EmailAuthTokenConfig(); + + // Assert + action.ShouldNotThrow(); + } + + [TestMethod] + public void EmailAuthTokenConfig_ShouldConfigureEntityCorrectly() + { + // Arrange + var builder = new ModelBuilder(new ConventionSet()); + var config = new EmailAuthTokenConfig(); + + // Act + config.Configure(builder.Entity()); + + // Assert + var entityType = builder.Model.FindEntityType(typeof(EmailAuthToken)); + _ = entityType.ShouldNotBeNull(); + + // Verifica tabela + entityType.GetTableName().ShouldBe("email_auth_token"); + + // Verifica chave primária + var primaryKey = entityType.FindPrimaryKey(); + _ = primaryKey.ShouldNotBeNull(); + primaryKey.Properties.Count.ShouldBe(1); + primaryKey.Properties[0].Name.ShouldBe(nameof(EmailAuthToken.Id)); + + // Verifica propriedade Id + var idProperty = entityType.FindProperty(nameof(EmailAuthToken.Id)); + _ = idProperty.ShouldNotBeNull(); + idProperty.GetColumnName().ShouldBe("id"); + idProperty.GetColumnType().ShouldBe("uuid"); + (!idProperty.IsNullable).ShouldBeTrue(); + idProperty.ValueGenerated.ShouldBe(ValueGenerated.OnAdd); + + // Verifica propriedade UserId + var userIdProperty = entityType.FindProperty(nameof(EmailAuthToken.UserId)); + _ = userIdProperty.ShouldNotBeNull(); + userIdProperty.GetColumnName().ShouldBe("user_id"); + userIdProperty.GetColumnType().ShouldBe("uuid"); + (!userIdProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade AccessToken + var accessTokenProperty = entityType.FindProperty(nameof(EmailAuthToken.AccessToken)); + _ = accessTokenProperty.ShouldNotBeNull(); + accessTokenProperty.GetColumnName().ShouldBe("access_token"); + (!accessTokenProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade RefreshToken + var refreshTokenProperty = entityType.FindProperty(nameof(EmailAuthToken.RefreshToken)); + _ = refreshTokenProperty.ShouldNotBeNull(); + refreshTokenProperty.GetColumnName().ShouldBe("refresh_token"); + (!refreshTokenProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade NonceValue + var nonceValueProperty = entityType.FindProperty(nameof(EmailAuthToken.NonceValue)); + _ = nonceValueProperty.ShouldNotBeNull(); + nonceValueProperty.GetColumnName().ShouldBe("nonce_value"); + (!nonceValueProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade TokenProvider + var tokenProviderProperty = entityType.FindProperty(nameof(EmailAuthToken.TokenProvider)); + _ = tokenProviderProperty.ShouldNotBeNull(); + tokenProviderProperty.GetColumnName().ShouldBe("token_provider"); + (!tokenProviderProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade AccessTokenExpiry + var accessTokenExpiryProperty = entityType.FindProperty(nameof(EmailAuthToken.AccessTokenExpiry)); + _ = accessTokenExpiryProperty.ShouldNotBeNull(); + accessTokenExpiryProperty.GetColumnName().ShouldBe("access_token_expiry"); + (!accessTokenExpiryProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade CreatedAt (herdada de EntityDefaults) + var createdAtProperty = entityType.FindProperty(nameof(EmailAuthToken.CreatedAt)); + _ = createdAtProperty.ShouldNotBeNull(); + createdAtProperty.GetColumnName().ShouldBe("created_at"); + createdAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); + (!createdAtProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade UpdatedAt (herdada de EntityDefaults) + var updatedAtProperty = entityType.FindProperty(nameof(EmailAuthToken.UpdatedAt)); + _ = updatedAtProperty.ShouldNotBeNull(); + updatedAtProperty.GetColumnName().ShouldBe("updated_at"); + updatedAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); + (!updatedAtProperty.IsNullable).ShouldBeTrue(); + } +} diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs index ca1d64a..3c8a0ff 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs @@ -1,4 +1,8 @@ using InvoiceReminder.Data.Persistence.EntitiesConfig; +using InvoiceReminder.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Shouldly; namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; @@ -15,4 +19,90 @@ public void InvoiceConfig_ShouldNotThrowErrorWhenInstantiated() // Assert action.ShouldNotThrow(); } + + [TestMethod] + public void InvoiceConfig_ShouldConfigureEntityCorrectly() + { + // Arrange + var builder = new ModelBuilder(new ConventionSet()); + var config = new InvoiceConfig(); + + // Act + config.Configure(builder.Entity()); + + // Assert + var entityType = builder.Model.FindEntityType(typeof(Invoice)); + _ = entityType.ShouldNotBeNull(); + + // Verifica tabela + entityType.GetTableName().ShouldBe("invoice"); + + // Verifica chave primária + var primaryKey = entityType.FindPrimaryKey(); + _ = primaryKey.ShouldNotBeNull(); + primaryKey.Properties.Count.ShouldBe(1); + primaryKey.Properties[0].Name.ShouldBe(nameof(Invoice.Id)); + + // Verifica propriedade Id + var idProperty = entityType.FindProperty(nameof(Invoice.Id)); + _ = idProperty.ShouldNotBeNull(); + idProperty.GetColumnName().ShouldBe("id"); + idProperty.GetColumnType().ShouldBe("uuid"); + (!idProperty.IsNullable).ShouldBeTrue(); + idProperty.ValueGenerated.ShouldBe(ValueGenerated.OnAdd); + + // Verifica propriedade UserId + var userIdProperty = entityType.FindProperty(nameof(Invoice.UserId)); + _ = userIdProperty.ShouldNotBeNull(); + userIdProperty.GetColumnName().ShouldBe("user_id"); + userIdProperty.GetColumnType().ShouldBe("uuid"); + (!userIdProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade Bank + var bankProperty = entityType.FindProperty(nameof(Invoice.Bank)); + _ = bankProperty.ShouldNotBeNull(); + bankProperty.GetColumnName().ShouldBe("bank"); + (!bankProperty.IsNullable).ShouldBeTrue(); + bankProperty.GetMaxLength().ShouldBe(255); + + // Verifica propriedade Beneficiary + var beneficiaryProperty = entityType.FindProperty(nameof(Invoice.Beneficiary)); + _ = beneficiaryProperty.ShouldNotBeNull(); + beneficiaryProperty.GetColumnName().ShouldBe("beneficiary"); + (!beneficiaryProperty.IsNullable).ShouldBeTrue(); + beneficiaryProperty.GetMaxLength().ShouldBe(255); + + // Verifica propriedade Barcode + var barcodeProperty = entityType.FindProperty(nameof(Invoice.Barcode)); + _ = barcodeProperty.ShouldNotBeNull(); + barcodeProperty.GetColumnName().ShouldBe("barcode"); + (!barcodeProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade Amount + var amountProperty = entityType.FindProperty(nameof(Invoice.Amount)); + _ = amountProperty.ShouldNotBeNull(); + amountProperty.GetColumnName().ShouldBe("amount"); + (!amountProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade DueDate + var dueDateProperty = entityType.FindProperty(nameof(Invoice.DueDate)); + _ = dueDateProperty.ShouldNotBeNull(); + dueDateProperty.GetColumnName().ShouldBe("due_date"); + dueDateProperty.GetColumnType().ShouldBe("timestamp with time zone"); + (!dueDateProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade CreatedAt (herdada de EntityDefaults) + var createdAtProperty = entityType.FindProperty(nameof(Invoice.CreatedAt)); + _ = createdAtProperty.ShouldNotBeNull(); + createdAtProperty.GetColumnName().ShouldBe("created_at"); + createdAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); + (!createdAtProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade UpdatedAt (herdada de EntityDefaults) + var updatedAtProperty = entityType.FindProperty(nameof(Invoice.UpdatedAt)); + _ = updatedAtProperty.ShouldNotBeNull(); + updatedAtProperty.GetColumnName().ShouldBe("updated_at"); + updatedAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); + (!updatedAtProperty.IsNullable).ShouldBeTrue(); + } } diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs index a3e6d6f..d182691 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs @@ -1,4 +1,8 @@ using InvoiceReminder.Data.Persistence.EntitiesConfig; +using InvoiceReminder.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Shouldly; namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; @@ -15,4 +19,63 @@ public void JobScheduleConfig_ShouldNotThrowErrorWhenInstantiated() // Assert action.ShouldNotThrow(); } + + [TestMethod] + public void JobScheduleConfig_ShouldConfigureEntityCorrectly() + { + // Arrange + var builder = new ModelBuilder(new ConventionSet()); + var config = new JobScheduleConfig(); + + // Act + config.Configure(builder.Entity()); + + // Assert + var entityType = builder.Model.FindEntityType(typeof(JobSchedule)); + _ = entityType.ShouldNotBeNull(); + + // Verifica tabela + entityType.GetTableName().ShouldBe("job_schedule"); + + // Verifica chave primária + var primaryKey = entityType.FindPrimaryKey(); + _ = primaryKey.ShouldNotBeNull(); + primaryKey.Properties.Count.ShouldBe(1); + primaryKey.Properties[0].Name.ShouldBe(nameof(JobSchedule.Id)); + + // Verifica propriedade Id + var idProperty = entityType.FindProperty(nameof(JobSchedule.Id)); + _ = idProperty.ShouldNotBeNull(); + idProperty.GetColumnName().ShouldBe("id"); + idProperty.GetColumnType().ShouldBe("uuid"); + (!idProperty.IsNullable).ShouldBeTrue(); + idProperty.ValueGenerated.ShouldBe(ValueGenerated.OnAdd); + + // Verifica propriedade UserId + var userIdProperty = entityType.FindProperty(nameof(JobSchedule.UserId)); + _ = userIdProperty.ShouldNotBeNull(); + userIdProperty.GetColumnName().ShouldBe("user_id"); + userIdProperty.GetColumnType().ShouldBe("uuid"); + (!userIdProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade CronExpression + var cronExpressionProperty = entityType.FindProperty(nameof(JobSchedule.CronExpression)); + _ = cronExpressionProperty.ShouldNotBeNull(); + cronExpressionProperty.GetColumnName().ShouldBe("cron_expression"); + (!cronExpressionProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade CreatedAt (herdada de EntityDefaults) + var createdAtProperty = entityType.FindProperty(nameof(JobSchedule.CreatedAt)); + _ = createdAtProperty.ShouldNotBeNull(); + createdAtProperty.GetColumnName().ShouldBe("created_at"); + createdAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); + (!createdAtProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade UpdatedAt (herdada de EntityDefaults) + var updatedAtProperty = entityType.FindProperty(nameof(JobSchedule.UpdatedAt)); + _ = updatedAtProperty.ShouldNotBeNull(); + updatedAtProperty.GetColumnName().ShouldBe("updated_at"); + updatedAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); + (!updatedAtProperty.IsNullable).ShouldBeTrue(); + } } diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs index 9464bb8..acd09e0 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs @@ -1,4 +1,8 @@ using InvoiceReminder.Data.Persistence.EntitiesConfig; +using InvoiceReminder.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Shouldly; namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; @@ -15,4 +19,87 @@ public void ScanEmailDefinitionConfig_ShouldNotThrowErrorWhenInstantiated() // Assert action.ShouldNotThrow(); } + + [TestMethod] + public void ScanEmailDefinitionConfig_ShouldConfigureEntityCorrectly() + { + // Arrange + var builder = new ModelBuilder(new ConventionSet()); + var config = new ScanEmailDefinitionConfig(); + + // Act + config.Configure(builder.Entity()); + + // Assert + var entityType = builder.Model.FindEntityType(typeof(ScanEmailDefinition)); + _ = entityType.ShouldNotBeNull(); + + // Verifica tabela + entityType.GetTableName().ShouldBe("scan_email_definition"); + + // Verifica chave primária + var primaryKey = entityType.FindPrimaryKey(); + _ = primaryKey.ShouldNotBeNull(); + primaryKey.Properties.Count.ShouldBe(1); + primaryKey.Properties[0].Name.ShouldBe(nameof(ScanEmailDefinition.Id)); + + // Verifica propriedade Id + var idProperty = entityType.FindProperty(nameof(ScanEmailDefinition.Id)); + _ = idProperty.ShouldNotBeNull(); + idProperty.GetColumnName().ShouldBe("id"); + idProperty.GetColumnType().ShouldBe("uuid"); + (!idProperty.IsNullable).ShouldBeTrue(); + idProperty.ValueGenerated.ShouldBe(ValueGenerated.OnAdd); + + // Verifica propriedade UserId + var userIdProperty = entityType.FindProperty(nameof(ScanEmailDefinition.UserId)); + _ = userIdProperty.ShouldNotBeNull(); + userIdProperty.GetColumnName().ShouldBe("user_id"); + userIdProperty.GetColumnType().ShouldBe("uuid"); + (!userIdProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade InvoiceType + var invoiceTypeProperty = entityType.FindProperty(nameof(ScanEmailDefinition.InvoiceType)); + _ = invoiceTypeProperty.ShouldNotBeNull(); + invoiceTypeProperty.GetColumnName().ShouldBe("invoice_type"); + (!invoiceTypeProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade Beneficiary + var beneficiaryProperty = entityType.FindProperty(nameof(ScanEmailDefinition.Beneficiary)); + _ = beneficiaryProperty.ShouldNotBeNull(); + beneficiaryProperty.GetColumnName().ShouldBe("beneficiary"); + (!beneficiaryProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade Description + var descriptionProperty = entityType.FindProperty(nameof(ScanEmailDefinition.Description)); + _ = descriptionProperty.ShouldNotBeNull(); + descriptionProperty.GetColumnName().ShouldBe("description"); + (!descriptionProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade SenderEmailAddress + var senderEmailAddressProperty = entityType.FindProperty(nameof(ScanEmailDefinition.SenderEmailAddress)); + _ = senderEmailAddressProperty.ShouldNotBeNull(); + senderEmailAddressProperty.GetColumnName().ShouldBe("sender_email_address"); + (!senderEmailAddressProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade AttachmentFileName + var cronExpressionProperty = entityType.FindProperty(nameof(ScanEmailDefinition.AttachmentFileName)); + _ = cronExpressionProperty.ShouldNotBeNull(); + cronExpressionProperty.GetColumnName().ShouldBe("attachment_filename"); + (!cronExpressionProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade CreatedAt (herdada de EntityDefaults) + var createdAtProperty = entityType.FindProperty(nameof(ScanEmailDefinition.CreatedAt)); + _ = createdAtProperty.ShouldNotBeNull(); + createdAtProperty.GetColumnName().ShouldBe("created_at"); + createdAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); + (!createdAtProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade UpdatedAt (herdada de EntityDefaults) + var updatedAtProperty = entityType.FindProperty(nameof(ScanEmailDefinition.UpdatedAt)); + _ = updatedAtProperty.ShouldNotBeNull(); + updatedAtProperty.GetColumnName().ShouldBe("updated_at"); + updatedAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); + (!updatedAtProperty.IsNullable).ShouldBeTrue(); + } } diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs index cc381ac..6405677 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs @@ -1,4 +1,8 @@ using InvoiceReminder.Data.Persistence.EntitiesConfig; +using InvoiceReminder.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Shouldly; namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; @@ -15,4 +19,75 @@ public void UserConfig_ShouldNotThrowErrorWhenInstantiated() // Assert action.ShouldNotThrow(); } + + [TestMethod] + public void UserConfig_ShouldConfigureEntityCorrectly() + { + // Arrange + var builder = new ModelBuilder(new ConventionSet()); + var config = new UserConfig(); + + // Act + config.Configure(builder.Entity()); + + // Assert + var entityType = builder.Model.FindEntityType(typeof(User)); + _ = entityType.ShouldNotBeNull(); + + // Verifica tabela + entityType.GetTableName().ShouldBe("user"); + + // Verifica chave primária + var primaryKey = entityType.FindPrimaryKey(); + _ = primaryKey.ShouldNotBeNull(); + primaryKey.Properties.Count.ShouldBe(1); + primaryKey.Properties[0].Name.ShouldBe(nameof(User.Id)); + + // Verifica propriedade Id + var idProperty = entityType.FindProperty(nameof(User.Id)); + _ = idProperty.ShouldNotBeNull(); + idProperty.GetColumnName().ShouldBe("id"); + idProperty.GetColumnType().ShouldBe("uuid"); + (!idProperty.IsNullable).ShouldBeTrue(); + idProperty.ValueGenerated.ShouldBe(ValueGenerated.OnAdd); + + // Verifica propriedade TelegramChatId + var telegramChatIdProperty = entityType.FindProperty(nameof(User.TelegramChatId)); + _ = telegramChatIdProperty.ShouldNotBeNull(); + telegramChatIdProperty.GetColumnName().ShouldBe("telegram_chat_id"); + telegramChatIdProperty.GetColumnType().ShouldBe("bigint"); + (!telegramChatIdProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade Name + var nameProperty = entityType.FindProperty(nameof(User.Name)); + _ = nameProperty.ShouldNotBeNull(); + nameProperty.GetColumnName().ShouldBe("name"); + (!nameProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade Email + var emailProperty = entityType.FindProperty(nameof(User.Email)); + _ = emailProperty.ShouldNotBeNull(); + emailProperty.GetColumnName().ShouldBe("email"); + (!emailProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade Password + var passwordProperty = entityType.FindProperty(nameof(User.Password)); + _ = passwordProperty.ShouldNotBeNull(); + passwordProperty.GetColumnName().ShouldBe("password"); + (!passwordProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade CreatedAt (herdada de EntityDefaults) + var createdAtProperty = entityType.FindProperty(nameof(User.CreatedAt)); + _ = createdAtProperty.ShouldNotBeNull(); + createdAtProperty.GetColumnName().ShouldBe("created_at"); + createdAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); + (!createdAtProperty.IsNullable).ShouldBeTrue(); + + // Verifica propriedade UpdatedAt (herdada de EntityDefaults) + var updatedAtProperty = entityType.FindProperty(nameof(User.UpdatedAt)); + _ = updatedAtProperty.ShouldNotBeNull(); + updatedAtProperty.GetColumnName().ShouldBe("updated_at"); + updatedAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); + (!updatedAtProperty.IsNullable).ShouldBeTrue(); + } } diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/EmailAuthTokenRepositoryTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/EmailAuthTokenRepositoryTests.cs deleted file mode 100644 index bc44197..0000000 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/EmailAuthTokenRepositoryTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using InvoiceReminder.Data.Interfaces; -using InvoiceReminder.Data.Persistence; -using InvoiceReminder.Data.Repository; -using InvoiceReminder.Domain.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using NSubstitute; -using Shouldly; - -namespace InvoiceReminder.UnitTests.Infrastructure.Data.Repository; - -[TestClass] -public sealed class EmailAuthTokenRepositoryTests -{ - private readonly CoreDbContext _dbContext; - private readonly ILogger _logger; - - public EmailAuthTokenRepositoryTests() - { - var options = new DbContextOptionsBuilder() - .UseNpgsql(default) - .Options; - - _dbContext = Substitute.ForPartsOf(options); - _logger = Substitute.For>(); - } - - [TestMethod] - public void EmailAuthTokenRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() - { - // Arrange && Act - var repository = new EmailAuthTokenRepository(_dbContext, _logger); - - // Assert - repository.ShouldSatisfyAllConditions(() => - { - _ = repository.ShouldBeAssignableTo(); - _ = repository.ShouldBeAssignableTo>(); - _ = repository.ShouldBeAssignableTo>(); - - _ = repository.ShouldNotBeNull(); - _ = repository.ShouldBeOfType(); - }); - } -} diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/InvoiceRepositoryTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/InvoiceRepositoryTests.cs deleted file mode 100644 index 60818c8..0000000 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/InvoiceRepositoryTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using InvoiceReminder.Data.Interfaces; -using InvoiceReminder.Data.Persistence; -using InvoiceReminder.Data.Repository; -using InvoiceReminder.Domain.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using NSubstitute; -using Shouldly; - -namespace InvoiceReminder.UnitTests.Infrastructure.Data.Repository; - -[TestClass] -public sealed class InvoiceRepositoryTests -{ - private readonly CoreDbContext _dbContext; - private readonly ILogger _logger; - - public InvoiceRepositoryTests() - { - var options = new DbContextOptionsBuilder() - .UseNpgsql(default) - .Options; - - _dbContext = Substitute.ForPartsOf(options); - _logger = Substitute.For>(); - } - - [TestMethod] - public void InvoiceRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() - { - // Arrange && Act - var repository = new InvoiceRepository(_dbContext, _logger); - - // Assert - repository.ShouldSatisfyAllConditions(() => - { - _ = repository.ShouldBeAssignableTo(); - _ = repository.ShouldBeAssignableTo>(); - _ = repository.ShouldBeAssignableTo>(); - - _ = repository.ShouldNotBeNull(); - _ = repository.ShouldBeOfType(); - }); - } -} diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/JobScheduleRepositoryTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/JobScheduleRepositoryTests.cs deleted file mode 100644 index bcd8906..0000000 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/JobScheduleRepositoryTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using InvoiceReminder.Data.Interfaces; -using InvoiceReminder.Data.Persistence; -using InvoiceReminder.Data.Repository; -using InvoiceReminder.Domain.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using NSubstitute; -using Shouldly; - -namespace InvoiceReminder.UnitTests.Infrastructure.Data.Repository; - -[TestClass] -public sealed class JobScheduleRepositoryTests -{ - private readonly CoreDbContext _dbContext; - private readonly ILogger _logger; - - public JobScheduleRepositoryTests() - { - var options = new DbContextOptionsBuilder() - .UseNpgsql(default) - .Options; - - _dbContext = Substitute.ForPartsOf(options); - _logger = Substitute.For>(); - } - - [TestMethod] - public void JobScheduleRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() - { - // Arrange && Act - var repository = new JobScheduleRepository(_dbContext, _logger); - - // Assert - repository.ShouldSatisfyAllConditions(() => - { - _ = repository.ShouldBeAssignableTo(); - _ = repository.ShouldBeAssignableTo>(); - _ = repository.ShouldBeAssignableTo>(); - - _ = repository.ShouldNotBeNull(); - _ = repository.ShouldBeOfType(); - }); - } -} diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/ScanEmailDefinitionRepositoryTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/ScanEmailDefinitionRepositoryTests.cs deleted file mode 100644 index ca7718a..0000000 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/ScanEmailDefinitionRepositoryTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using InvoiceReminder.Data.Interfaces; -using InvoiceReminder.Data.Persistence; -using InvoiceReminder.Data.Repository; -using InvoiceReminder.Domain.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using NSubstitute; -using Shouldly; - -namespace InvoiceReminder.UnitTests.Infrastructure.Data.Repository; - -[TestClass] -public sealed class ScanEmailDefinitionRepositoryTests -{ - private readonly CoreDbContext _dbContext; - private readonly ILogger _logger; - - public ScanEmailDefinitionRepositoryTests() - { - var options = new DbContextOptionsBuilder() - .UseNpgsql(default) - .Options; - - _dbContext = Substitute.ForPartsOf(options); - _logger = Substitute.For>(); - } - - [TestMethod] - public void ScanEmailDefinitionRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() - { - // Arrange && Act - var repository = new ScanEmailDefinitionRepository(_dbContext, _logger); - - // Assert - repository.ShouldSatisfyAllConditions(() => - { - _ = repository.ShouldBeAssignableTo(); - _ = repository.ShouldBeAssignableTo>(); - _ = repository.ShouldBeAssignableTo>(); - - _ = repository.ShouldNotBeNull(); - _ = repository.ShouldBeOfType(); - }); - } -} diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/UserRepositoryTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/UserRepositoryTests.cs deleted file mode 100644 index dfe201d..0000000 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/Repository/UserRepositoryTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using InvoiceReminder.Data.Interfaces; -using InvoiceReminder.Data.Persistence; -using InvoiceReminder.Data.Repository; -using InvoiceReminder.Domain.Entities; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using NSubstitute; -using Shouldly; - -namespace InvoiceReminder.UnitTests.Infrastructure.Data.Repository; - -[TestClass] -public sealed class UserRepositoryTests -{ - private readonly CoreDbContext _dbContext; - private readonly ILogger _logger; - - public UserRepositoryTests() - { - var options = new DbContextOptionsBuilder() - .UseNpgsql(default) - .Options; - - _dbContext = Substitute.ForPartsOf(options); - _logger = Substitute.For>(); - } - - [TestMethod] - public void UserRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() - { - // Arrange && Act - var repository = new UserRepository(_dbContext, _logger); - - // Assert - repository.ShouldSatisfyAllConditions(() => - { - _ = repository.ShouldBeAssignableTo(); - _ = repository.ShouldBeAssignableTo>(); - _ = repository.ShouldBeAssignableTo>(); - - _ = repository.ShouldNotBeNull(); - _ = repository.ShouldBeOfType(); - }); - } -} diff --git a/InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj b/InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj index e76981b..f025a73 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj +++ b/InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj @@ -14,8 +14,6 @@ - - all diff --git a/InvoiceReminder.UnitTests.JobScheduler/HostedService/QuartzHostedServiceTests.cs b/InvoiceReminder.UnitTests.JobScheduler/HostedService/QuartzHostedServiceTests.cs index 990ddf1..24902de 100644 --- a/InvoiceReminder.UnitTests.JobScheduler/HostedService/QuartzHostedServiceTests.cs +++ b/InvoiceReminder.UnitTests.JobScheduler/HostedService/QuartzHostedServiceTests.cs @@ -163,7 +163,7 @@ public async Task StartAsync_WithMultipleInvalidCronExpressions_ShouldLogErrorsF _logger.Received(2).Log( LogLevel.Error, Arg.Any(), - Arg.Any(), + Arg.Is(o => o.ToString().Contains("CronJob inválido:")), Arg.Any(), Arg.Any>() ); @@ -195,7 +195,7 @@ public async Task StartAsync_WithMixedValidAndInvalidSchedules_ShouldScheduleOnl _logger.Received(1).Log( LogLevel.Error, Arg.Any(), - Arg.Any(), + Arg.Is(o => o.ToString().Contains("CronJob inválido:")), Arg.Any(), Arg.Any>() ); diff --git a/InvoiceReminder.UnitTests.JobScheduler/JobSettings/CronJobTests.cs b/InvoiceReminder.UnitTests.JobScheduler/JobSettings/CronJobTests.cs index 1ed97a8..ccb5f0e 100644 --- a/InvoiceReminder.UnitTests.JobScheduler/JobSettings/CronJobTests.cs +++ b/InvoiceReminder.UnitTests.JobScheduler/JobSettings/CronJobTests.cs @@ -59,13 +59,13 @@ public async Task Execute_ShouldCreateScopeResolveServiceAndSendMessage() _ = _sendMessageService.Received(1).SendMessage(Arg.Any(), Arg.Any()); - _logger.ReceivedWithAnyArgs(1).Log( - LogLevel.Information, - Arg.Any(), - Arg.Any(), - null, - Arg.Any>() - ); + _logger.Received(1).Log( + LogLevel.Information, + Arg.Any(), + Arg.Any(), + null, + Arg.Any>() + ); } [TestMethod] From b950766dbc34fe45eb3a5946fb266838827aeab3 Mon Sep 17 00:00:00 2001 From: "Jefferson L. da Silva" Date: Wed, 31 Dec 2025 02:31:01 -0300 Subject: [PATCH 3/5] Adds bulk remove and update operations Implements bulk remove and update functionalities in the base repository for improved data manipulation efficiency. Also adds timestamping of updated entities during bulk updates to maintain audit trails. Removes redundant tests and adds data seeding to integration tests for better test coverage. --- .../Interfaces/IBaseRepository.cs | 2 ++ .../Repository/BaseRepository.cs | 15 +++++++++++++ .../BaseRepositoryIntegrationTests.cs | 15 +++++-------- .../Data/Utils/TestData.cs | 4 +++- .../EmailAuthTokenConfigTests.cs | 14 ++++-------- .../Data/EntitiesConfig/InvoiceConfigTests.cs | 10 --------- .../EntitiesConfig/JobScheduleConfigTests.cs | 11 +--------- .../ScanEmailDefinitionConfigTests.cs | 22 +++++++------------ .../Data/EntitiesConfig/UserConfigTests.cs | 20 ++++++++--------- .../JobSettings/CronJobTests.cs | 2 +- 10 files changed, 50 insertions(+), 65 deletions(-) diff --git a/InvoiceReminder.Data/Interfaces/IBaseRepository.cs b/InvoiceReminder.Data/Interfaces/IBaseRepository.cs index 94e4f56..350bdbc 100644 --- a/InvoiceReminder.Data/Interfaces/IBaseRepository.cs +++ b/InvoiceReminder.Data/Interfaces/IBaseRepository.cs @@ -6,6 +6,8 @@ public interface IBaseRepository : IDisposable where TEntity : class { Task AddAsync(TEntity entity, CancellationToken cancellationToken = default); Task BulkInsertAsync(ICollection entities, CancellationToken cancellationToken = default); + Task BulkRemoveAsync(ICollection entities, CancellationToken cancellationToken = default); + Task BulkUpdateAsync(ICollection entities, CancellationToken cancellationToken = default); void Remove(TEntity entity); Task GetByIdAsync(Guid id, CancellationToken cancellationToken = default); IEnumerable GetAll(); diff --git a/InvoiceReminder.Data/Repository/BaseRepository.cs b/InvoiceReminder.Data/Repository/BaseRepository.cs index 236d3da..e839a8d 100644 --- a/InvoiceReminder.Data/Repository/BaseRepository.cs +++ b/InvoiceReminder.Data/Repository/BaseRepository.cs @@ -39,6 +39,21 @@ public virtual async Task BulkInsertAsync(ICollection entities, Ca return entities.Count; } + public virtual async Task BulkRemoveAsync(ICollection entities, CancellationToken cancellationToken = default) + { + await _dbContext.BulkDeleteAsync(entities, cancellationToken: cancellationToken); + } + + public virtual async Task BulkUpdateAsync(ICollection entities, CancellationToken cancellationToken = default) + { + foreach (var entity in entities) + { + entity.GetType().GetProperty("UpdatedAt")?.SetValue(entity, DateTime.UtcNow); + } + + await _dbContext.BulkUpdateAsync(entities, cancellationToken: cancellationToken); + } + public virtual void Remove(TEntity entity) { if (_dbContext.Entry(entity).State == EntityState.Detached) diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs index 5a63916..18fa9d9 100644 --- a/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs +++ b/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs @@ -136,13 +136,8 @@ public async Task BulkInsertAsync_Should_Persist_Entities_To_Database() _ = await _userRepository.BulkInsertAsync(users, TestContext.CancellationToken); // Assert - using var dbContext = new CoreDbContext(new DbContextOptionsBuilder() - .UseNpgsql(DatabaseFixture.ConnectionString) - .Options); - var userIds = users.Select(u => u.Id).ToList(); - var count = await dbContext.Set() - .CountAsync(u => userIds.Contains(u.Id), TestContext.CancellationToken); + var count = _userRepository.Where(u => userIds.Contains(u.Id)).Count(); count.ShouldBe(3); } @@ -286,13 +281,15 @@ public async Task GetAll_Should_Return_Entities_As_NoTracking() public async Task GetAll_Should_Return_Empty_Collection_When_No_Entities() { // Arrange - using var repository = CreateFreshRepository(); + var invoices = _invoiceRepository.GetAll().ToList(); + await _invoiceRepository.BulkRemoveAsync(invoices, TestContext.CancellationToken); - // Act - Try to get all invoices (likely none exist at test start) - var result = repository.GetAll().ToList(); + // Act + var result = _invoiceRepository.GetAll(); // Assert _ = result.ShouldNotBeNull(); + result.ShouldBeEmpty(); } #endregion diff --git a/InvoiceReminder.IntegrationTests/Data/Utils/TestData.cs b/InvoiceReminder.IntegrationTests/Data/Utils/TestData.cs index 5111566..7d6e739 100644 --- a/InvoiceReminder.IntegrationTests/Data/Utils/TestData.cs +++ b/InvoiceReminder.IntegrationTests/Data/Utils/TestData.cs @@ -13,7 +13,9 @@ public static Faker UserFaker() .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) .RuleFor(u => u.Name, f => f.Person.FullName) .RuleFor(u => u.Email, f => f.Internet.Email()) - .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)); + .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)) + .RuleFor(u => u.CreatedAt, f => f.Date.Past().ToUniversalTime()) + .RuleFor(u => u.UpdatedAt, f => f.Date.Recent().ToUniversalTime()); } public static Faker EmailAuthTokenFaker() diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs index 88ddc79..d263d25 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs @@ -10,16 +10,6 @@ namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; [TestClass] public sealed class EmailAuthTokenConfigTests { - [TestMethod] - public void EmailAuthTokenConfig_ShouldNotThrowErrorWhenInstantiated() - { - // Arrange && Act - Action action = () => _ = new EmailAuthTokenConfig(); - - // Assert - action.ShouldNotThrow(); - } - [TestMethod] public void EmailAuthTokenConfig_ShouldConfigureEntityCorrectly() { @@ -62,18 +52,21 @@ public void EmailAuthTokenConfig_ShouldConfigureEntityCorrectly() var accessTokenProperty = entityType.FindProperty(nameof(EmailAuthToken.AccessToken)); _ = accessTokenProperty.ShouldNotBeNull(); accessTokenProperty.GetColumnName().ShouldBe("access_token"); + accessTokenProperty.GetMaxLength().ShouldBe(512); (!accessTokenProperty.IsNullable).ShouldBeTrue(); // Verifica propriedade RefreshToken var refreshTokenProperty = entityType.FindProperty(nameof(EmailAuthToken.RefreshToken)); _ = refreshTokenProperty.ShouldNotBeNull(); refreshTokenProperty.GetColumnName().ShouldBe("refresh_token"); + refreshTokenProperty.GetMaxLength().ShouldBe(512); (!refreshTokenProperty.IsNullable).ShouldBeTrue(); // Verifica propriedade NonceValue var nonceValueProperty = entityType.FindProperty(nameof(EmailAuthToken.NonceValue)); _ = nonceValueProperty.ShouldNotBeNull(); nonceValueProperty.GetColumnName().ShouldBe("nonce_value"); + nonceValueProperty.GetMaxLength().ShouldBe(64); (!nonceValueProperty.IsNullable).ShouldBeTrue(); // Verifica propriedade TokenProvider @@ -86,6 +79,7 @@ public void EmailAuthTokenConfig_ShouldConfigureEntityCorrectly() var accessTokenExpiryProperty = entityType.FindProperty(nameof(EmailAuthToken.AccessTokenExpiry)); _ = accessTokenExpiryProperty.ShouldNotBeNull(); accessTokenExpiryProperty.GetColumnName().ShouldBe("access_token_expiry"); + accessTokenExpiryProperty.GetColumnType().ShouldBe("timestamp with time zone"); (!accessTokenExpiryProperty.IsNullable).ShouldBeTrue(); // Verifica propriedade CreatedAt (herdada de EntityDefaults) diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs index 3c8a0ff..5dfb7ac 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs @@ -10,16 +10,6 @@ namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; [TestClass] public sealed class InvoiceConfigTests { - [TestMethod] - public void InvoiceConfig_ShouldNotThrowErrorWhenInstantiated() - { - // Arrange && Act - Action action = () => _ = new InvoiceConfig(); - - // Assert - action.ShouldNotThrow(); - } - [TestMethod] public void InvoiceConfig_ShouldConfigureEntityCorrectly() { diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs index d182691..8ef91cd 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs @@ -10,16 +10,6 @@ namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; [TestClass] public sealed class JobScheduleConfigTests { - [TestMethod] - public void JobScheduleConfig_ShouldNotThrowErrorWhenInstantiated() - { - // Arrange && Act - Action action = () => _ = new JobScheduleConfig(); - - // Assert - action.ShouldNotThrow(); - } - [TestMethod] public void JobScheduleConfig_ShouldConfigureEntityCorrectly() { @@ -62,6 +52,7 @@ public void JobScheduleConfig_ShouldConfigureEntityCorrectly() var cronExpressionProperty = entityType.FindProperty(nameof(JobSchedule.CronExpression)); _ = cronExpressionProperty.ShouldNotBeNull(); cronExpressionProperty.GetColumnName().ShouldBe("cron_expression"); + cronExpressionProperty.GetMaxLength().ShouldBe(255); (!cronExpressionProperty.IsNullable).ShouldBeTrue(); // Verifica propriedade CreatedAt (herdada de EntityDefaults) diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs index acd09e0..549455f 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs @@ -10,16 +10,6 @@ namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; [TestClass] public sealed class ScanEmailDefinitionConfigTests { - [TestMethod] - public void ScanEmailDefinitionConfig_ShouldNotThrowErrorWhenInstantiated() - { - // Arrange && Act - Action action = () => _ = new ScanEmailDefinitionConfig(); - - // Assert - action.ShouldNotThrow(); - } - [TestMethod] public void ScanEmailDefinitionConfig_ShouldConfigureEntityCorrectly() { @@ -68,25 +58,29 @@ public void ScanEmailDefinitionConfig_ShouldConfigureEntityCorrectly() var beneficiaryProperty = entityType.FindProperty(nameof(ScanEmailDefinition.Beneficiary)); _ = beneficiaryProperty.ShouldNotBeNull(); beneficiaryProperty.GetColumnName().ShouldBe("beneficiary"); + beneficiaryProperty.GetMaxLength().ShouldBe(255); (!beneficiaryProperty.IsNullable).ShouldBeTrue(); // Verifica propriedade Description var descriptionProperty = entityType.FindProperty(nameof(ScanEmailDefinition.Description)); _ = descriptionProperty.ShouldNotBeNull(); descriptionProperty.GetColumnName().ShouldBe("description"); + descriptionProperty.GetMaxLength().ShouldBe(255); (!descriptionProperty.IsNullable).ShouldBeTrue(); // Verifica propriedade SenderEmailAddress var senderEmailAddressProperty = entityType.FindProperty(nameof(ScanEmailDefinition.SenderEmailAddress)); _ = senderEmailAddressProperty.ShouldNotBeNull(); senderEmailAddressProperty.GetColumnName().ShouldBe("sender_email_address"); + senderEmailAddressProperty.GetMaxLength().ShouldBe(255); (!senderEmailAddressProperty.IsNullable).ShouldBeTrue(); // Verifica propriedade AttachmentFileName - var cronExpressionProperty = entityType.FindProperty(nameof(ScanEmailDefinition.AttachmentFileName)); - _ = cronExpressionProperty.ShouldNotBeNull(); - cronExpressionProperty.GetColumnName().ShouldBe("attachment_filename"); - (!cronExpressionProperty.IsNullable).ShouldBeTrue(); + var attachmentFileNameProperty = entityType.FindProperty(nameof(ScanEmailDefinition.AttachmentFileName)); + _ = attachmentFileNameProperty.ShouldNotBeNull(); + attachmentFileNameProperty.GetColumnName().ShouldBe("attachment_filename"); + attachmentFileNameProperty.GetMaxLength().ShouldBe(255); + (!attachmentFileNameProperty.IsNullable).ShouldBeTrue(); // Verifica propriedade CreatedAt (herdada de EntityDefaults) var createdAtProperty = entityType.FindProperty(nameof(ScanEmailDefinition.CreatedAt)); diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs index 6405677..897e7ae 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs @@ -10,16 +10,6 @@ namespace InvoiceReminder.UnitTests.Infrastructure.Data.EntitiesConfig; [TestClass] public sealed class UserConfigTests { - [TestMethod] - public void UserConfig_ShouldNotThrowErrorWhenInstantiated() - { - // Arrange && Act - Action action = () => _ = new UserConfig(); - - // Assert - action.ShouldNotThrow(); - } - [TestMethod] public void UserConfig_ShouldConfigureEntityCorrectly() { @@ -43,6 +33,13 @@ public void UserConfig_ShouldConfigureEntityCorrectly() primaryKey.Properties.Count.ShouldBe(1); primaryKey.Properties[0].Name.ShouldBe(nameof(User.Id)); + // Verifica índice único no Email + var emailIndex = entityType.GetIndexes() + .FirstOrDefault(i => i.Properties.Count == 1 && i.Properties[0].Name == nameof(User.Email)); + _ = emailIndex.ShouldNotBeNull(); + emailIndex.IsUnique.ShouldBeTrue(); + emailIndex.GetDatabaseName().ShouldBe("idx_user_email"); + // Verifica propriedade Id var idProperty = entityType.FindProperty(nameof(User.Id)); _ = idProperty.ShouldNotBeNull(); @@ -62,18 +59,21 @@ public void UserConfig_ShouldConfigureEntityCorrectly() var nameProperty = entityType.FindProperty(nameof(User.Name)); _ = nameProperty.ShouldNotBeNull(); nameProperty.GetColumnName().ShouldBe("name"); + nameProperty.GetMaxLength().ShouldBe(255); (!nameProperty.IsNullable).ShouldBeTrue(); // Verifica propriedade Email var emailProperty = entityType.FindProperty(nameof(User.Email)); _ = emailProperty.ShouldNotBeNull(); emailProperty.GetColumnName().ShouldBe("email"); + emailProperty.GetMaxLength().ShouldBe(255); (!emailProperty.IsNullable).ShouldBeTrue(); // Verifica propriedade Password var passwordProperty = entityType.FindProperty(nameof(User.Password)); _ = passwordProperty.ShouldNotBeNull(); passwordProperty.GetColumnName().ShouldBe("password"); + passwordProperty.GetMaxLength().ShouldBe(255); (!passwordProperty.IsNullable).ShouldBeTrue(); // Verifica propriedade CreatedAt (herdada de EntityDefaults) diff --git a/InvoiceReminder.UnitTests.JobScheduler/JobSettings/CronJobTests.cs b/InvoiceReminder.UnitTests.JobScheduler/JobSettings/CronJobTests.cs index ccb5f0e..1095705 100644 --- a/InvoiceReminder.UnitTests.JobScheduler/JobSettings/CronJobTests.cs +++ b/InvoiceReminder.UnitTests.JobScheduler/JobSettings/CronJobTests.cs @@ -111,7 +111,7 @@ public async Task Execute_SendMessageFails_ShouldNotThrowExceptionAndStillLog() LogLevel.Information, Arg.Any(), Arg.Is(o => o.ToString().Contains("Test Job Description triggered...")), - Arg.Any(), + null, Arg.Any>() ); } From 8aea040b6d8888f288088849a3ace0a14f5eaa02 Mon Sep 17 00:00:00 2001 From: "Jefferson L. da Silva" Date: Wed, 31 Dec 2025 03:02:57 -0300 Subject: [PATCH 4/5] Refactors tests for improved reliability Enhances test reliability by ensuring a clean state before each test execution. Updates test data generation to provide more realistic values, especially for dates, improving the accuracy of tests related to temporal data. Improves assertion clarity and correctness in entity configuration tests. --- .../BaseRepositoryIntegrationTests.cs | 2 ++ .../Data/Utils/TestData.cs | 20 +++++++++---------- .../BearerSecuritySchemeTransformerTests.cs | 2 +- .../EmailAuthTokenConfigTests.cs | 17 ++++++++-------- .../Data/EntitiesConfig/InvoiceConfigTests.cs | 18 ++++++++--------- .../EntitiesConfig/JobScheduleConfigTests.cs | 10 +++++----- .../ScanEmailDefinitionConfigTests.cs | 18 ++++++++--------- .../Data/EntitiesConfig/UserConfigTests.cs | 14 ++++++------- 8 files changed, 52 insertions(+), 49 deletions(-) diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs index 18fa9d9..845f6aa 100644 --- a/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs +++ b/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs @@ -473,6 +473,8 @@ public async Task Where_Should_Return_Filtered_Entities() public async Task Where_Should_Return_Multiple_Matching_Entities() { // Arrange + await _userRepository.BulkRemoveAsync([.. _userRepository.GetAll()], TestContext.CancellationToken); + var user1 = TestData.UserFaker() .RuleFor(u => u.Name, _ => "Jack Doe") .Generate(); diff --git a/InvoiceReminder.IntegrationTests/Data/Utils/TestData.cs b/InvoiceReminder.IntegrationTests/Data/Utils/TestData.cs index 7d6e739..67a0bc2 100644 --- a/InvoiceReminder.IntegrationTests/Data/Utils/TestData.cs +++ b/InvoiceReminder.IntegrationTests/Data/Utils/TestData.cs @@ -10,12 +10,12 @@ public static Faker UserFaker() { return new Faker() .RuleFor(u => u.Id, _ => Guid.NewGuid()) - .RuleFor(u => u.TelegramChatId, f => f.Random.Long(100000000, long.MaxValue)) - .RuleFor(u => u.Name, f => f.Person.FullName) - .RuleFor(u => u.Email, f => f.Internet.Email()) - .RuleFor(u => u.Password, f => f.Internet.Password(length: 16, memorable: false)) - .RuleFor(u => u.CreatedAt, f => f.Date.Past().ToUniversalTime()) - .RuleFor(u => u.UpdatedAt, f => f.Date.Recent().ToUniversalTime()); + .RuleFor(u => u.TelegramChatId, faker => faker.Random.Long(100000000, long.MaxValue)) + .RuleFor(u => u.Name, faker => faker.Person.FullName) + .RuleFor(u => u.Email, faker => faker.Internet.Email()) + .RuleFor(u => u.Password, faker => faker.Internet.Password(length: 16, memorable: false)) + .RuleFor(u => u.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) + .RuleFor(e => e.UpdatedAt, (faker, u) => faker.Date.Between(u.CreatedAt, DateTime.UtcNow).ToUniversalTime()); } public static Faker EmailAuthTokenFaker() @@ -29,7 +29,7 @@ public static Faker EmailAuthTokenFaker() .RuleFor(e => e.NonceValue, faker => faker.Random.Hash()) .RuleFor(e => e.AccessTokenExpiry, faker => faker.Date.Future().ToUniversalTime()) .RuleFor(e => e.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) - .RuleFor(e => e.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + .RuleFor(e => e.UpdatedAt, (faker, e) => faker.Date.Between(e.CreatedAt, DateTime.UtcNow).ToUniversalTime()); } public static Faker InvoiceFaker() @@ -51,7 +51,7 @@ public static Faker InvoiceFaker() .RuleFor(i => i.Barcode, faker => faker.Random.AlphaNumeric(44)) .RuleFor(i => i.DueDate, faker => faker.Date.Future().ToUniversalTime()) .RuleFor(i => i.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) - .RuleFor(i => i.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + .RuleFor(i => i.UpdatedAt, (faker, i) => faker.Date.Between(i.CreatedAt, DateTime.UtcNow).ToUniversalTime()); } public static Faker JobScheduleFaker() @@ -67,7 +67,7 @@ public static Faker JobScheduleFaker() "0 9 * * MON-FRI", "0 0 1 * *")) .RuleFor(j => j.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) - .RuleFor(j => j.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + .RuleFor(j => j.UpdatedAt, (faker, j) => faker.Date.Between(j.CreatedAt, DateTime.UtcNow).ToUniversalTime()); } public static Faker ScanEmailDefinitionFaker() @@ -81,6 +81,6 @@ public static Faker ScanEmailDefinitionFaker() .RuleFor(s => s.SenderEmailAddress, faker => faker.Internet.Email()) .RuleFor(s => s.AttachmentFileName, faker => faker.System.FileName("pdf")) .RuleFor(s => s.CreatedAt, faker => faker.Date.Past().ToUniversalTime()) - .RuleFor(s => s.UpdatedAt, faker => faker.Date.Recent().ToUniversalTime()); + .RuleFor(s => s.UpdatedAt, (faker, s) => faker.Date.Between(s.CreatedAt, DateTime.UtcNow).ToUniversalTime()); } } diff --git a/InvoiceReminder.UnitTests.API/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs b/InvoiceReminder.UnitTests.API/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs index 1022bfc..b2d43e5 100644 --- a/InvoiceReminder.UnitTests.API/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs +++ b/InvoiceReminder.UnitTests.API/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs @@ -48,8 +48,8 @@ public async Task TransformAsync_BearerSchemeExists_ShouldAddSecuritySchemeToDoc _ = _document.ShouldBeOfType(); _ = _document.Components.ShouldNotBeNull(); _ = _document.Components.SecuritySchemes.ShouldNotBeNull(); - _ = _document.Components.SecuritySchemes.ContainsKey("Bearer"); + _document.Components.SecuritySchemes.ContainsKey("Bearer").ShouldBe(true); _document.Components.SecuritySchemes["Bearer"].Scheme.ShouldBeEquivalentTo("bearer"); }); } diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs index d263d25..5e1c7eb 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs @@ -46,54 +46,55 @@ public void EmailAuthTokenConfig_ShouldConfigureEntityCorrectly() _ = userIdProperty.ShouldNotBeNull(); userIdProperty.GetColumnName().ShouldBe("user_id"); userIdProperty.GetColumnType().ShouldBe("uuid"); - (!userIdProperty.IsNullable).ShouldBeTrue(); + userIdProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade AccessToken var accessTokenProperty = entityType.FindProperty(nameof(EmailAuthToken.AccessToken)); _ = accessTokenProperty.ShouldNotBeNull(); accessTokenProperty.GetColumnName().ShouldBe("access_token"); accessTokenProperty.GetMaxLength().ShouldBe(512); - (!accessTokenProperty.IsNullable).ShouldBeTrue(); + accessTokenProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade RefreshToken var refreshTokenProperty = entityType.FindProperty(nameof(EmailAuthToken.RefreshToken)); _ = refreshTokenProperty.ShouldNotBeNull(); refreshTokenProperty.GetColumnName().ShouldBe("refresh_token"); refreshTokenProperty.GetMaxLength().ShouldBe(512); - (!refreshTokenProperty.IsNullable).ShouldBeTrue(); + refreshTokenProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade NonceValue var nonceValueProperty = entityType.FindProperty(nameof(EmailAuthToken.NonceValue)); _ = nonceValueProperty.ShouldNotBeNull(); nonceValueProperty.GetColumnName().ShouldBe("nonce_value"); nonceValueProperty.GetMaxLength().ShouldBe(64); - (!nonceValueProperty.IsNullable).ShouldBeTrue(); + nonceValueProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade TokenProvider var tokenProviderProperty = entityType.FindProperty(nameof(EmailAuthToken.TokenProvider)); _ = tokenProviderProperty.ShouldNotBeNull(); tokenProviderProperty.GetColumnName().ShouldBe("token_provider"); - (!tokenProviderProperty.IsNullable).ShouldBeTrue(); + tokenProviderProperty.GetMaxLength().ShouldBe(25); + tokenProviderProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade AccessTokenExpiry var accessTokenExpiryProperty = entityType.FindProperty(nameof(EmailAuthToken.AccessTokenExpiry)); _ = accessTokenExpiryProperty.ShouldNotBeNull(); accessTokenExpiryProperty.GetColumnName().ShouldBe("access_token_expiry"); accessTokenExpiryProperty.GetColumnType().ShouldBe("timestamp with time zone"); - (!accessTokenExpiryProperty.IsNullable).ShouldBeTrue(); + accessTokenExpiryProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade CreatedAt (herdada de EntityDefaults) var createdAtProperty = entityType.FindProperty(nameof(EmailAuthToken.CreatedAt)); _ = createdAtProperty.ShouldNotBeNull(); createdAtProperty.GetColumnName().ShouldBe("created_at"); createdAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); - (!createdAtProperty.IsNullable).ShouldBeTrue(); + createdAtProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade UpdatedAt (herdada de EntityDefaults) var updatedAtProperty = entityType.FindProperty(nameof(EmailAuthToken.UpdatedAt)); _ = updatedAtProperty.ShouldNotBeNull(); updatedAtProperty.GetColumnName().ShouldBe("updated_at"); updatedAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); - (!updatedAtProperty.IsNullable).ShouldBeTrue(); + updatedAtProperty.IsNullable.ShouldBeFalse(); } } diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs index 5dfb7ac..c8260a3 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs @@ -38,7 +38,7 @@ public void InvoiceConfig_ShouldConfigureEntityCorrectly() _ = idProperty.ShouldNotBeNull(); idProperty.GetColumnName().ShouldBe("id"); idProperty.GetColumnType().ShouldBe("uuid"); - (!idProperty.IsNullable).ShouldBeTrue(); + idProperty.IsNullable.ShouldBeFalse(); idProperty.ValueGenerated.ShouldBe(ValueGenerated.OnAdd); // Verifica propriedade UserId @@ -46,53 +46,53 @@ public void InvoiceConfig_ShouldConfigureEntityCorrectly() _ = userIdProperty.ShouldNotBeNull(); userIdProperty.GetColumnName().ShouldBe("user_id"); userIdProperty.GetColumnType().ShouldBe("uuid"); - (!userIdProperty.IsNullable).ShouldBeTrue(); + userIdProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade Bank var bankProperty = entityType.FindProperty(nameof(Invoice.Bank)); _ = bankProperty.ShouldNotBeNull(); bankProperty.GetColumnName().ShouldBe("bank"); - (!bankProperty.IsNullable).ShouldBeTrue(); + bankProperty.IsNullable.ShouldBeFalse(); bankProperty.GetMaxLength().ShouldBe(255); // Verifica propriedade Beneficiary var beneficiaryProperty = entityType.FindProperty(nameof(Invoice.Beneficiary)); _ = beneficiaryProperty.ShouldNotBeNull(); beneficiaryProperty.GetColumnName().ShouldBe("beneficiary"); - (!beneficiaryProperty.IsNullable).ShouldBeTrue(); + beneficiaryProperty.IsNullable.ShouldBeFalse(); beneficiaryProperty.GetMaxLength().ShouldBe(255); // Verifica propriedade Barcode var barcodeProperty = entityType.FindProperty(nameof(Invoice.Barcode)); _ = barcodeProperty.ShouldNotBeNull(); barcodeProperty.GetColumnName().ShouldBe("barcode"); - (!barcodeProperty.IsNullable).ShouldBeTrue(); + barcodeProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade Amount var amountProperty = entityType.FindProperty(nameof(Invoice.Amount)); _ = amountProperty.ShouldNotBeNull(); amountProperty.GetColumnName().ShouldBe("amount"); - (!amountProperty.IsNullable).ShouldBeTrue(); + amountProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade DueDate var dueDateProperty = entityType.FindProperty(nameof(Invoice.DueDate)); _ = dueDateProperty.ShouldNotBeNull(); dueDateProperty.GetColumnName().ShouldBe("due_date"); dueDateProperty.GetColumnType().ShouldBe("timestamp with time zone"); - (!dueDateProperty.IsNullable).ShouldBeTrue(); + dueDateProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade CreatedAt (herdada de EntityDefaults) var createdAtProperty = entityType.FindProperty(nameof(Invoice.CreatedAt)); _ = createdAtProperty.ShouldNotBeNull(); createdAtProperty.GetColumnName().ShouldBe("created_at"); createdAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); - (!createdAtProperty.IsNullable).ShouldBeTrue(); + createdAtProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade UpdatedAt (herdada de EntityDefaults) var updatedAtProperty = entityType.FindProperty(nameof(Invoice.UpdatedAt)); _ = updatedAtProperty.ShouldNotBeNull(); updatedAtProperty.GetColumnName().ShouldBe("updated_at"); updatedAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); - (!updatedAtProperty.IsNullable).ShouldBeTrue(); + updatedAtProperty.IsNullable.ShouldBeFalse(); } } diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs index 8ef91cd..4ce0c92 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs @@ -38,7 +38,7 @@ public void JobScheduleConfig_ShouldConfigureEntityCorrectly() _ = idProperty.ShouldNotBeNull(); idProperty.GetColumnName().ShouldBe("id"); idProperty.GetColumnType().ShouldBe("uuid"); - (!idProperty.IsNullable).ShouldBeTrue(); + idProperty.IsNullable.ShouldBeFalse(); idProperty.ValueGenerated.ShouldBe(ValueGenerated.OnAdd); // Verifica propriedade UserId @@ -46,27 +46,27 @@ public void JobScheduleConfig_ShouldConfigureEntityCorrectly() _ = userIdProperty.ShouldNotBeNull(); userIdProperty.GetColumnName().ShouldBe("user_id"); userIdProperty.GetColumnType().ShouldBe("uuid"); - (!userIdProperty.IsNullable).ShouldBeTrue(); + userIdProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade CronExpression var cronExpressionProperty = entityType.FindProperty(nameof(JobSchedule.CronExpression)); _ = cronExpressionProperty.ShouldNotBeNull(); cronExpressionProperty.GetColumnName().ShouldBe("cron_expression"); cronExpressionProperty.GetMaxLength().ShouldBe(255); - (!cronExpressionProperty.IsNullable).ShouldBeTrue(); + cronExpressionProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade CreatedAt (herdada de EntityDefaults) var createdAtProperty = entityType.FindProperty(nameof(JobSchedule.CreatedAt)); _ = createdAtProperty.ShouldNotBeNull(); createdAtProperty.GetColumnName().ShouldBe("created_at"); createdAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); - (!createdAtProperty.IsNullable).ShouldBeTrue(); + createdAtProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade UpdatedAt (herdada de EntityDefaults) var updatedAtProperty = entityType.FindProperty(nameof(JobSchedule.UpdatedAt)); _ = updatedAtProperty.ShouldNotBeNull(); updatedAtProperty.GetColumnName().ShouldBe("updated_at"); updatedAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); - (!updatedAtProperty.IsNullable).ShouldBeTrue(); + updatedAtProperty.IsNullable.ShouldBeFalse(); } } diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs index 549455f..c349520 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs @@ -38,7 +38,7 @@ public void ScanEmailDefinitionConfig_ShouldConfigureEntityCorrectly() _ = idProperty.ShouldNotBeNull(); idProperty.GetColumnName().ShouldBe("id"); idProperty.GetColumnType().ShouldBe("uuid"); - (!idProperty.IsNullable).ShouldBeTrue(); + idProperty.IsNullable.ShouldBeFalse(); idProperty.ValueGenerated.ShouldBe(ValueGenerated.OnAdd); // Verifica propriedade UserId @@ -46,54 +46,54 @@ public void ScanEmailDefinitionConfig_ShouldConfigureEntityCorrectly() _ = userIdProperty.ShouldNotBeNull(); userIdProperty.GetColumnName().ShouldBe("user_id"); userIdProperty.GetColumnType().ShouldBe("uuid"); - (!userIdProperty.IsNullable).ShouldBeTrue(); + userIdProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade InvoiceType var invoiceTypeProperty = entityType.FindProperty(nameof(ScanEmailDefinition.InvoiceType)); _ = invoiceTypeProperty.ShouldNotBeNull(); invoiceTypeProperty.GetColumnName().ShouldBe("invoice_type"); - (!invoiceTypeProperty.IsNullable).ShouldBeTrue(); + invoiceTypeProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade Beneficiary var beneficiaryProperty = entityType.FindProperty(nameof(ScanEmailDefinition.Beneficiary)); _ = beneficiaryProperty.ShouldNotBeNull(); beneficiaryProperty.GetColumnName().ShouldBe("beneficiary"); beneficiaryProperty.GetMaxLength().ShouldBe(255); - (!beneficiaryProperty.IsNullable).ShouldBeTrue(); + beneficiaryProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade Description var descriptionProperty = entityType.FindProperty(nameof(ScanEmailDefinition.Description)); _ = descriptionProperty.ShouldNotBeNull(); descriptionProperty.GetColumnName().ShouldBe("description"); descriptionProperty.GetMaxLength().ShouldBe(255); - (!descriptionProperty.IsNullable).ShouldBeTrue(); + descriptionProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade SenderEmailAddress var senderEmailAddressProperty = entityType.FindProperty(nameof(ScanEmailDefinition.SenderEmailAddress)); _ = senderEmailAddressProperty.ShouldNotBeNull(); senderEmailAddressProperty.GetColumnName().ShouldBe("sender_email_address"); senderEmailAddressProperty.GetMaxLength().ShouldBe(255); - (!senderEmailAddressProperty.IsNullable).ShouldBeTrue(); + senderEmailAddressProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade AttachmentFileName var attachmentFileNameProperty = entityType.FindProperty(nameof(ScanEmailDefinition.AttachmentFileName)); _ = attachmentFileNameProperty.ShouldNotBeNull(); attachmentFileNameProperty.GetColumnName().ShouldBe("attachment_filename"); attachmentFileNameProperty.GetMaxLength().ShouldBe(255); - (!attachmentFileNameProperty.IsNullable).ShouldBeTrue(); + attachmentFileNameProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade CreatedAt (herdada de EntityDefaults) var createdAtProperty = entityType.FindProperty(nameof(ScanEmailDefinition.CreatedAt)); _ = createdAtProperty.ShouldNotBeNull(); createdAtProperty.GetColumnName().ShouldBe("created_at"); createdAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); - (!createdAtProperty.IsNullable).ShouldBeTrue(); + createdAtProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade UpdatedAt (herdada de EntityDefaults) var updatedAtProperty = entityType.FindProperty(nameof(ScanEmailDefinition.UpdatedAt)); _ = updatedAtProperty.ShouldNotBeNull(); updatedAtProperty.GetColumnName().ShouldBe("updated_at"); updatedAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); - (!updatedAtProperty.IsNullable).ShouldBeTrue(); + updatedAtProperty.IsNullable.ShouldBeFalse(); } } diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs index 897e7ae..e511d81 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs @@ -45,7 +45,7 @@ public void UserConfig_ShouldConfigureEntityCorrectly() _ = idProperty.ShouldNotBeNull(); idProperty.GetColumnName().ShouldBe("id"); idProperty.GetColumnType().ShouldBe("uuid"); - (!idProperty.IsNullable).ShouldBeTrue(); + idProperty.IsNullable.ShouldBeFalse(); idProperty.ValueGenerated.ShouldBe(ValueGenerated.OnAdd); // Verifica propriedade TelegramChatId @@ -53,41 +53,41 @@ public void UserConfig_ShouldConfigureEntityCorrectly() _ = telegramChatIdProperty.ShouldNotBeNull(); telegramChatIdProperty.GetColumnName().ShouldBe("telegram_chat_id"); telegramChatIdProperty.GetColumnType().ShouldBe("bigint"); - (!telegramChatIdProperty.IsNullable).ShouldBeTrue(); + telegramChatIdProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade Name var nameProperty = entityType.FindProperty(nameof(User.Name)); _ = nameProperty.ShouldNotBeNull(); nameProperty.GetColumnName().ShouldBe("name"); nameProperty.GetMaxLength().ShouldBe(255); - (!nameProperty.IsNullable).ShouldBeTrue(); + nameProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade Email var emailProperty = entityType.FindProperty(nameof(User.Email)); _ = emailProperty.ShouldNotBeNull(); emailProperty.GetColumnName().ShouldBe("email"); emailProperty.GetMaxLength().ShouldBe(255); - (!emailProperty.IsNullable).ShouldBeTrue(); + emailProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade Password var passwordProperty = entityType.FindProperty(nameof(User.Password)); _ = passwordProperty.ShouldNotBeNull(); passwordProperty.GetColumnName().ShouldBe("password"); passwordProperty.GetMaxLength().ShouldBe(255); - (!passwordProperty.IsNullable).ShouldBeTrue(); + passwordProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade CreatedAt (herdada de EntityDefaults) var createdAtProperty = entityType.FindProperty(nameof(User.CreatedAt)); _ = createdAtProperty.ShouldNotBeNull(); createdAtProperty.GetColumnName().ShouldBe("created_at"); createdAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); - (!createdAtProperty.IsNullable).ShouldBeTrue(); + createdAtProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade UpdatedAt (herdada de EntityDefaults) var updatedAtProperty = entityType.FindProperty(nameof(User.UpdatedAt)); _ = updatedAtProperty.ShouldNotBeNull(); updatedAtProperty.GetColumnName().ShouldBe("updated_at"); updatedAtProperty.GetColumnType().ShouldBe("timestamp with time zone"); - (!updatedAtProperty.IsNullable).ShouldBeTrue(); + updatedAtProperty.IsNullable.ShouldBeFalse(); } } From 551a3d3acee9046871a99005cac4634b305a918d Mon Sep 17 00:00:00 2001 From: "Jefferson L. da Silva" Date: Wed, 31 Dec 2025 03:16:34 -0300 Subject: [PATCH 5/5] Improves tests with more robust assertions Refactors tests to use more reliable and accurate assertions. This change addresses potential issues with date comparisons in integration tests by asserting that timestamps fall within an acceptable range, preventing failures due to slight timing differences. Additionally, it strengthens assertions in other tests by using more specific checks (e.g., ShouldContainKey instead of ContainsKey) and adjusting expected counts to be more flexible. --- .../Data/Repository/BaseRepositoryIntegrationTests.cs | 11 +++++++---- .../BearerSecuritySchemeTransformerTests.cs | 2 +- .../Data/EntitiesConfig/UserConfigTests.cs | 1 + 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs b/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs index 845f6aa..a2d582c 100644 --- a/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs +++ b/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs @@ -118,12 +118,17 @@ public async Task BulkInsertAsync_Should_Set_CreatedAt_And_UpdatedAt() { // Arrange var users = TestData.UserFaker().Generate(3); + var beforeInsert = DateTime.UtcNow.AddSeconds(-1); // Act _ = await _userRepository.BulkInsertAsync(users, TestContext.CancellationToken); + var afterInsert = DateTime.UtcNow.AddSeconds(1); // Assert - users.ShouldAllBe(u => u.CreatedAt != default && u.UpdatedAt != default); + users.ShouldAllBe(u => + u.CreatedAt >= beforeInsert && u.CreatedAt <= afterInsert && + u.UpdatedAt >= beforeInsert && u.UpdatedAt <= afterInsert + ); } [TestMethod] @@ -473,8 +478,6 @@ public async Task Where_Should_Return_Filtered_Entities() public async Task Where_Should_Return_Multiple_Matching_Entities() { // Arrange - await _userRepository.BulkRemoveAsync([.. _userRepository.GetAll()], TestContext.CancellationToken); - var user1 = TestData.UserFaker() .RuleFor(u => u.Name, _ => "Jack Doe") .Generate(); @@ -494,7 +497,7 @@ public async Task Where_Should_Return_Multiple_Matching_Entities() var result = _userRepository.Where(u => u.Name.Contains("Jack")); // Assert - result.Count().ShouldBe(2); + result.Count().ShouldBeGreaterThanOrEqualTo(2); result.ShouldAllBe(u => u.Name.Contains("Jack")); } diff --git a/InvoiceReminder.UnitTests.API/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs b/InvoiceReminder.UnitTests.API/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs index b2d43e5..21c354d 100644 --- a/InvoiceReminder.UnitTests.API/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs +++ b/InvoiceReminder.UnitTests.API/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs @@ -49,7 +49,7 @@ public async Task TransformAsync_BearerSchemeExists_ShouldAddSecuritySchemeToDoc _ = _document.Components.ShouldNotBeNull(); _ = _document.Components.SecuritySchemes.ShouldNotBeNull(); - _document.Components.SecuritySchemes.ContainsKey("Bearer").ShouldBe(true); + _document.Components.SecuritySchemes.ShouldContainKey("Bearer"); _document.Components.SecuritySchemes["Bearer"].Scheme.ShouldBeEquivalentTo("bearer"); }); } diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs index e511d81..2d5fbf6 100644 --- a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs @@ -53,6 +53,7 @@ public void UserConfig_ShouldConfigureEntityCorrectly() _ = telegramChatIdProperty.ShouldNotBeNull(); telegramChatIdProperty.GetColumnName().ShouldBe("telegram_chat_id"); telegramChatIdProperty.GetColumnType().ShouldBe("bigint"); + telegramChatIdProperty.GetDefaultValue().ShouldBe(0L); telegramChatIdProperty.IsNullable.ShouldBeFalse(); // Verifica propriedade Name