diff --git a/.github/workflows/workflow-ci.yml b/.github/workflows/workflow-ci.yml index 3fbf196..36a5e86 100644 --- a/.github/workflows/workflow-ci.yml +++ b/.github/workflows/workflow-ci.yml @@ -34,7 +34,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" | sort) 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/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/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..4ffba79 100644 --- a/InvoiceReminder.Data/Migrations/20250930210104_Initial_Create.cs +++ b/InvoiceReminder.Data/Migrations/20250930210104_Initial_Create.cs @@ -3,217 +3,216 @@ #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.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..682ed70 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; @@ -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 4944e3d..e57c94b 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; @@ -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/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/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.Data/Repository/UnitOfWork.cs b/InvoiceReminder.Data/Repository/UnitOfWork.cs index 3091e9f..56f1c50 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); + await transaction?.RollbackAsync(CancellationToken.None); - 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/EntitiesConfig/InvoiceConfigTests.cs b/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/InvoiceConfigTests.cs deleted file mode 100644 index 7842369..0000000 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/InvoiceConfigTests.cs +++ /dev/null @@ -1,18 +0,0 @@ -using InvoiceReminder.Data.Persistence.EntitiesConfig; -using Shouldly; - -namespace InvoiceReminder.Infrastructure.UnitTests.Data.EntitiesConfig; - -[TestClass] -public sealed class InvoiceConfigTests -{ - [TestMethod] - public void InvoiceConfig_ShouldNotThrowErrorWhenInstantiated() - { - // Arrange && Act - Action action = () => _ = new InvoiceConfig(); - - // Assert - action.ShouldNotThrow(); - } -} diff --git a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/JobScheduleConfigTests.cs b/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/JobScheduleConfigTests.cs deleted file mode 100644 index 000cd87..0000000 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/JobScheduleConfigTests.cs +++ /dev/null @@ -1,18 +0,0 @@ -using InvoiceReminder.Data.Persistence.EntitiesConfig; -using Shouldly; - -namespace InvoiceReminder.Infrastructure.UnitTests.Data.EntitiesConfig; - -[TestClass] -public sealed class JobScheduleConfigTests -{ - [TestMethod] - public void JobScheduleConfig_ShouldNotThrowErrorWhenInstantiated() - { - // Arrange && Act - Action action = () => _ = new JobScheduleConfig(); - - // Assert - action.ShouldNotThrow(); - } -} diff --git a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs b/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs deleted file mode 100644 index 16b63a6..0000000 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs +++ /dev/null @@ -1,18 +0,0 @@ -using InvoiceReminder.Data.Persistence.EntitiesConfig; -using Shouldly; - -namespace InvoiceReminder.Infrastructure.UnitTests.Data.EntitiesConfig; - -[TestClass] -public sealed class ScanEmailDefinitionConfigTests -{ - [TestMethod] - public void ScanEmailDefinitionConfig_ShouldNotThrowErrorWhenInstantiated() - { - // Arrange && Act - Action action = () => _ = new ScanEmailDefinitionConfig(); - - // Assert - action.ShouldNotThrow(); - } -} diff --git a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/UserConfigTests.cs b/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/UserConfigTests.cs deleted file mode 100644 index 43b0a44..0000000 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/EntitiesConfig/UserConfigTests.cs +++ /dev/null @@ -1,18 +0,0 @@ -using InvoiceReminder.Data.Persistence.EntitiesConfig; -using Shouldly; - -namespace InvoiceReminder.Infrastructure.UnitTests.Data.EntitiesConfig; - -[TestClass] -public sealed class UserConfigTests -{ - [TestMethod] - public void UserConfig_ShouldNotThrowErrorWhenInstantiated() - { - // Arrange && Act - Action action = () => _ = new UserConfig(); - - // Assert - action.ShouldNotThrow(); - } -} 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/InvoiceRepositoryTests.cs b/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/InvoiceRepositoryTests.cs deleted file mode 100644 index 83ea47d..0000000 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/InvoiceRepositoryTests.cs +++ /dev/null @@ -1,72 +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.Infrastructure.UnitTests.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() - { - var options = new DbContextOptionsBuilder() - .UseNpgsql(default) - .Options; - - _dbContext = Substitute.ForPartsOf(options); - _logger = Substitute.For>(); - _repository = 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(); - }); - } - - [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.Infrastructure.UnitTests/Data/Repository/JobScheduleRepositoryTests.cs deleted file mode 100644 index d0d478f..0000000 --- a/InvoiceReminder.Infrastructure.UnitTests/Data/Repository/JobScheduleRepositoryTests.cs +++ /dev/null @@ -1,72 +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.Infrastructure.UnitTests.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() - { - var options = new DbContextOptionsBuilder() - .UseNpgsql(default) - .Options; - - _dbContext = Substitute.ForPartsOf(options); - _logger = Substitute.For>(); - _repository = 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(); - }); - } - - [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.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..db076be --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/ContainerSetup/DatabaseFixture.cs @@ -0,0 +1,63 @@ +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) + { + 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); + } + catch (Exception ex) + { + context.WriteLine($"Failed to initialize test database: {ex.Message}"); + throw; + } + } + + [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..a2d582c --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Repository/BaseRepositoryIntegrationTests.cs @@ -0,0 +1,605 @@ +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; +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 AddAsync Tests + + [TestMethod] + public async Task AddAsync_Should_Add_Entity_To_DbSet() + { + // Arrange + var user = TestData.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 = TestData.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 = TestData.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 = TestData.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 = 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 >= beforeInsert && u.CreatedAt <= afterInsert && + u.UpdatedAt >= beforeInsert && u.UpdatedAt <= afterInsert + ); + } + + [TestMethod] + public async Task BulkInsertAsync_Should_Persist_Entities_To_Database() + { + // Arrange + var users = TestData.UserFaker().Generate(3); + + // Act + _ = await _userRepository.BulkInsertAsync(users, TestContext.CancellationToken); + + // Assert + var userIds = users.Select(u => u.Id).ToList(); + var count = _userRepository.Where(u => userIds.Contains(u.Id)).Count(); + + 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 = TestData.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 = TestData.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 = TestData.UserFaker().Generate(3); + + _ = await _userRepository.BulkInsertAsync(users, TestContext.CancellationToken); + + // Dispose current context to ensure we read from fresh one + _userRepository.Dispose(); + await _dbContext.DisposeAsync(); + + using var repository = CreateFreshRepository(); + + // 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 = TestData.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 invoices = _invoiceRepository.GetAll().ToList(); + await _invoiceRepository.BulkRemoveAsync(invoices, TestContext.CancellationToken); + + // Act + var result = _invoiceRepository.GetAll(); + + // Assert + _ = result.ShouldNotBeNull(); + result.ShouldBeEmpty(); + } + + #endregion + + #region Remove Tests + + [TestMethod] + public async Task Remove_Should_Mark_Entity_As_Deleted() + { + // Arrange + var user = TestData.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 = TestData.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 = TestData.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 = TestData.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 = TestData.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 = TestData.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 = TestData.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 = TestData.UserFaker() + .RuleFor(u => u.Email, _ => "test1@example.com") + .Generate(); + var user2 = TestData.UserFaker() + .RuleFor(u => u.Email, _ => "test2@example.com") + .Generate(); + var user3 = TestData.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 = TestData.UserFaker() + .RuleFor(u => u.Name, _ => "Jack Doe") + .Generate(); + var user2 = TestData.UserFaker() + .RuleFor(u => u.Name, _ => "Jane Smith") + .Generate(); + var user3 = TestData.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().ShouldBeGreaterThanOrEqualTo(2); + result.ShouldAllBe(u => u.Name.Contains("Jack")); + } + + [TestMethod] + public async Task Where_Should_Return_Empty_Collection_When_No_Match() + { + // Arrange + var user = TestData.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 = TestData.UserFaker() + .RuleFor(u => u.Name, _ => "Alice") + .RuleFor(u => u.TelegramChatId, _ => 1000) + .Generate(); + var user2 = TestData.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 + + #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 new file mode 100644 index 0000000..e7167b3 --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Repository/EmailAuthTokenRepositoryIntegrationTests.cs @@ -0,0 +1,286 @@ +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; +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); + } + + [TestCleanup] + public void TestCleanup() + { + _unitOfWork?.Dispose(); + _repository?.Dispose(); + _dbContext?.Dispose(); + } + + [TestMethod] + public void EmailAuthTokenRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() + { + // Arrange && Act + var repository = new EmailAuthTokenRepository(_dbContext, _repositoryLogger); + + // Assert + repository.ShouldSatisfyAllConditions(() => + { + _ = repository.ShouldBeAssignableTo(); + _ = repository.ShouldBeAssignableTo>(); + _ = repository.ShouldBeAssignableTo>(); + + _ = repository.ShouldNotBeNull(); + _ = repository.ShouldBeOfType(); + }); + } + + #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 = TestData.EmailAuthTokenFaker() + .RuleFor(e => e.UserId, _ => user.Id) + .RuleFor(e => e.TokenProvider, _ => "Google") + .Generate(); + var microsoftToken = TestData.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 + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetByUserIdAsync(Guid.NewGuid(), "Google", cts.Token) + ); + + _repositoryLogger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #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 new file mode 100644 index 0000000..27006df --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Repository/InvoiceRepositoryIntegrationTests.cs @@ -0,0 +1,291 @@ +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; +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); + } + + [TestCleanup] + public void TestCleanup() + { + _unitOfWork?.Dispose(); + _repository?.Dispose(); + _dbContext?.Dispose(); + } + + [TestMethod] + public void InvoiceRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() + { + // Arrange && Act + var repository = new InvoiceRepository(_dbContext, _repositoryLogger); + + // Assert + repository.ShouldSatisfyAllConditions(() => + { + _ = repository.ShouldBeAssignableTo(); + _ = repository.ShouldBeAssignableTo>(); + _ = repository.ShouldBeAssignableTo>(); + + _ = repository.ShouldNotBeNull(); + _ = repository.ShouldBeOfType(); + }); + } + + #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 = TestData.InvoiceFaker() + .RuleFor(i => i.UserId, _ => user.Id) + .Generate(); + var invoice2 = TestData.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 + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetByBarcodeAsync("##########", cts.Token) + ); + + _repositoryLogger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #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 new file mode 100644 index 0000000..8ed0ce3 --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Repository/JobScheduleRepositoryIntegrationTests.cs @@ -0,0 +1,309 @@ +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; +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); + } + + [TestCleanup] + public void TestCleanup() + { + _unitOfWork?.Dispose(); + _repository?.Dispose(); + _dbContext?.Dispose(); + } + + [TestMethod] + public void JobScheduleRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() + { + // Arrange && Act + var repository = new JobScheduleRepository(_dbContext, _repositoryLogger); + + // Assert + repository.ShouldSatisfyAllConditions(() => + { + _ = repository.ShouldBeAssignableTo(); + _ = repository.ShouldBeAssignableTo>(); + _ = repository.ShouldBeAssignableTo>(); + + _ = repository.ShouldNotBeNull(); + _ = repository.ShouldBeOfType(); + }); + } + + #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 = TestData.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 = TestData.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 = TestData.JobScheduleFaker() + .RuleFor(j => j.UserId, _ => user1.Id) + .Generate(); + var jobSchedule2 = TestData.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 + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetByUserIdAsync(Guid.NewGuid(), cts.Token) + ); + + _repositoryLogger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #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 new file mode 100644 index 0000000..28e861b --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Repository/ScanEmailDefinitionRepositoryIntegrationTests.cs @@ -0,0 +1,612 @@ +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; +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); + } + + [TestCleanup] + public void TestCleanup() + { + _unitOfWork?.Dispose(); + _repository?.Dispose(); + _dbContext?.Dispose(); + } + + [TestMethod] + public void ScanEmailDefinitionRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() + { + // Arrange && Act + var repository = new ScanEmailDefinitionRepository(_dbContext, _repositoryLogger); + + // Assert + repository.ShouldSatisfyAllConditions(() => + { + _ = repository.ShouldBeAssignableTo(); + _ = repository.ShouldBeAssignableTo>(); + _ = repository.ShouldBeAssignableTo>(); + + _ = repository.ShouldNotBeNull(); + _ = repository.ShouldBeOfType(); + }); + } + + #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 = TestData.ScanEmailDefinitionFaker() + .RuleFor(s => s.UserId, _ => user.Id) + .Generate(); + var definition2 = TestData.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 = TestData.ScanEmailDefinitionFaker() + .RuleFor(s => s.UserId, _ => user.Id) + .Generate(); + var definition2 = TestData.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 = TestData.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 = TestData.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 = TestData.ScanEmailDefinitionFaker() + .RuleFor(s => s.UserId, _ => user1.Id) + .Generate(); + var definition2 = TestData.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 + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetBySenderEmailAddressAsync("any@mail.com", Guid.NewGuid(), 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 + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetBySenderBeneficiaryAsync("Beneficiary", Guid.NewGuid(), 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 + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetByUserIdAsync(Guid.NewGuid(), cts.Token) + ); + + _repositoryLogger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #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 new file mode 100644 index 0000000..191de0b --- /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..7068d06 --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Repository/UserRepositoryIntegrationTests.cs @@ -0,0 +1,325 @@ +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; +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); + } + + [TestCleanup] + public void TestCleanup() + { + _unitOfWork?.Dispose(); + _repository?.Dispose(); + _dbContext?.Dispose(); + } + + [TestMethod] + public void UserRepository_ShouldBeAssignableToItsInterface_And_GenericInterface_And_GenericRepository() + { + // Arrange && Act + var repository = new UserRepository(_dbContext, _repositoryLogger); + + // Assert + repository.ShouldSatisfyAllConditions(() => + { + _ = repository.ShouldBeAssignableTo(); + _ = repository.ShouldBeAssignableTo>(); + _ = repository.ShouldBeAssignableTo>(); + + _ = repository.ShouldNotBeNull(); + _ = repository.ShouldBeOfType(); + }); + } + + #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 = TestData.UserFaker().Generate(); + var invoice = TestData.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 = TestData.UserFaker().Generate(); + var emailToken = TestData.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 + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetByIdAsync(Guid.NewGuid(), 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 + using var cts = new CancellationTokenSource(); + await cts.CancelAsync(); + + _ = _repositoryLogger.IsEnabled(Arg.Any()).Returns(true); + + // Act & Assert + _ = await Should.ThrowAsync( + async () => await _repository.GetByEmailAsync("any@mail.com", cts.Token) + ); + + _repositoryLogger.Received(1).Log( + LogLevel.Warning, + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any>() + ); + } + + #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..67a0bc2 --- /dev/null +++ b/InvoiceReminder.IntegrationTests/Data/Utils/TestData.cs @@ -0,0 +1,86 @@ +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, 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() + { + 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, e) => faker.Date.Between(e.CreatedAt, DateTime.UtcNow).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, i) => faker.Date.Between(i.CreatedAt, DateTime.UtcNow).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, j) => faker.Date.Between(j.CreatedAt, DateTime.UtcNow).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, s) => faker.Date.Between(s.CreatedAt, DateTime.UtcNow).ToUniversalTime()); + } +} 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 95% rename from InvoiceReminder.API.UnitTests/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs rename to InvoiceReminder.UnitTests.API/AuthenticationSetup/BearerSecuritySchemeTransformerTests.cs index 03d6310..21c354d 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 @@ -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.ShouldContainKey("Bearer"); _document.Components.SecuritySchemes["Bearer"].Scheme.ShouldBeEquivalentTo("bearer"); }); } 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.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs new file mode 100644 index 0000000..5e1c7eb --- /dev/null +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/EmailAuthTokenConfigTests.cs @@ -0,0 +1,100 @@ +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_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.ShouldBeFalse(); + + // Verifica propriedade AccessToken + var accessTokenProperty = entityType.FindProperty(nameof(EmailAuthToken.AccessToken)); + _ = accessTokenProperty.ShouldNotBeNull(); + accessTokenProperty.GetColumnName().ShouldBe("access_token"); + accessTokenProperty.GetMaxLength().ShouldBe(512); + 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.ShouldBeFalse(); + + // Verifica propriedade NonceValue + var nonceValueProperty = entityType.FindProperty(nameof(EmailAuthToken.NonceValue)); + _ = nonceValueProperty.ShouldNotBeNull(); + nonceValueProperty.GetColumnName().ShouldBe("nonce_value"); + nonceValueProperty.GetMaxLength().ShouldBe(64); + nonceValueProperty.IsNullable.ShouldBeFalse(); + + // Verifica propriedade TokenProvider + var tokenProviderProperty = entityType.FindProperty(nameof(EmailAuthToken.TokenProvider)); + _ = tokenProviderProperty.ShouldNotBeNull(); + tokenProviderProperty.GetColumnName().ShouldBe("token_provider"); + 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.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.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.ShouldBeFalse(); + } +} diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs new file mode 100644 index 0000000..c8260a3 --- /dev/null +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/InvoiceConfigTests.cs @@ -0,0 +1,98 @@ +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 InvoiceConfigTests +{ + [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.ShouldBeFalse(); + 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.ShouldBeFalse(); + + // Verifica propriedade Bank + var bankProperty = entityType.FindProperty(nameof(Invoice.Bank)); + _ = bankProperty.ShouldNotBeNull(); + bankProperty.GetColumnName().ShouldBe("bank"); + 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.ShouldBeFalse(); + beneficiaryProperty.GetMaxLength().ShouldBe(255); + + // Verifica propriedade Barcode + var barcodeProperty = entityType.FindProperty(nameof(Invoice.Barcode)); + _ = barcodeProperty.ShouldNotBeNull(); + barcodeProperty.GetColumnName().ShouldBe("barcode"); + barcodeProperty.IsNullable.ShouldBeFalse(); + + // Verifica propriedade Amount + var amountProperty = entityType.FindProperty(nameof(Invoice.Amount)); + _ = amountProperty.ShouldNotBeNull(); + amountProperty.GetColumnName().ShouldBe("amount"); + 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.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.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.ShouldBeFalse(); + } +} diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs new file mode 100644 index 0000000..4ce0c92 --- /dev/null +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/JobScheduleConfigTests.cs @@ -0,0 +1,72 @@ +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 JobScheduleConfigTests +{ + [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.ShouldBeFalse(); + 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.ShouldBeFalse(); + + // Verifica propriedade CronExpression + var cronExpressionProperty = entityType.FindProperty(nameof(JobSchedule.CronExpression)); + _ = cronExpressionProperty.ShouldNotBeNull(); + cronExpressionProperty.GetColumnName().ShouldBe("cron_expression"); + cronExpressionProperty.GetMaxLength().ShouldBe(255); + 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.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.ShouldBeFalse(); + } +} diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs new file mode 100644 index 0000000..c349520 --- /dev/null +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/ScanEmailDefinitionConfigTests.cs @@ -0,0 +1,99 @@ +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 ScanEmailDefinitionConfigTests +{ + [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.ShouldBeFalse(); + 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.ShouldBeFalse(); + + // Verifica propriedade InvoiceType + var invoiceTypeProperty = entityType.FindProperty(nameof(ScanEmailDefinition.InvoiceType)); + _ = invoiceTypeProperty.ShouldNotBeNull(); + invoiceTypeProperty.GetColumnName().ShouldBe("invoice_type"); + 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.ShouldBeFalse(); + + // Verifica propriedade Description + var descriptionProperty = entityType.FindProperty(nameof(ScanEmailDefinition.Description)); + _ = descriptionProperty.ShouldNotBeNull(); + descriptionProperty.GetColumnName().ShouldBe("description"); + descriptionProperty.GetMaxLength().ShouldBe(255); + 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.ShouldBeFalse(); + + // Verifica propriedade AttachmentFileName + var attachmentFileNameProperty = entityType.FindProperty(nameof(ScanEmailDefinition.AttachmentFileName)); + _ = attachmentFileNameProperty.ShouldNotBeNull(); + attachmentFileNameProperty.GetColumnName().ShouldBe("attachment_filename"); + attachmentFileNameProperty.GetMaxLength().ShouldBe(255); + 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.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.ShouldBeFalse(); + } +} diff --git a/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs new file mode 100644 index 0000000..2d5fbf6 --- /dev/null +++ b/InvoiceReminder.UnitTests.Infrastructure/Data/EntitiesConfig/UserConfigTests.cs @@ -0,0 +1,94 @@ +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 UserConfigTests +{ + [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 í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(); + idProperty.GetColumnName().ShouldBe("id"); + idProperty.GetColumnType().ShouldBe("uuid"); + idProperty.IsNullable.ShouldBeFalse(); + 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.GetDefaultValue().ShouldBe(0L); + 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.ShouldBeFalse(); + + // Verifica propriedade Email + var emailProperty = entityType.FindProperty(nameof(User.Email)); + _ = emailProperty.ShouldNotBeNull(); + emailProperty.GetColumnName().ShouldBe("email"); + emailProperty.GetMaxLength().ShouldBe(255); + 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.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.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.ShouldBeFalse(); + } +} diff --git a/InvoiceReminder.Infrastructure.UnitTests/InvoiceReminder.Infrastructure.UnitTests.csproj b/InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj similarity index 65% rename from InvoiceReminder.Infrastructure.UnitTests/InvoiceReminder.Infrastructure.UnitTests.csproj rename to InvoiceReminder.UnitTests.Infrastructure/InvoiceReminder.UnitTests.Infrastructure.csproj index 1f094f2..f025a73 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,11 @@ - + - - - 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..24902de 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.Is(o => o.ToString().Contains("CronJob inválido:")), + 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.Is(o => o.ToString().Contains("CronJob inválido:")), + 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..1095705 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.Received(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...")), + null, + 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