From 20891917c53560aea076e4fbc64a35f4a3e0fe51 Mon Sep 17 00:00:00 2001 From: RafaelKC Date: Tue, 25 Nov 2025 20:36:42 -0300 Subject: [PATCH 1/3] FIN-68 implemented mail sender --- .../.idea/copilot.data.migration.ask.xml | 6 + .../copilot.data.migration.ask2agent.xml | 6 + .../.idea/copilot.data.migration.edit.xml | 6 + .../.idea/db-forest-config.xml | 6 + Fin.Api/appsettings.json | 6 + .../Services/AuthenticationService.cs | 22 ++- .../Utils/AuthenticationTemplates.cs | 13 +- .../NotificationDeliveryService.cs | 14 +- .../Users/Services/UserCreateService.cs | 18 ++- .../Users/Services/UserDeleteService.cs | 104 ++++++++++-- .../Users/Utils/AbortDeleteUserTemplates.cs | 151 +++++++++++++++++ .../Users/Utils/AccountDeletedTemplates.cs | 153 ++++++++++++++++++ .../Users/Utils/CreateUserTemplates.cs | 14 ++ .../Users/Utils/DeleteUserTemplates.cs | 153 ++++++++++++++++++ .../Constants/MailServicesConst.cs | 9 ++ .../EmailSenders/Dto/SendEmailDto.cs | 10 ++ .../EmailSenders/EmailSenderService.cs | 48 ++++-- .../MailSender/MailSenderClient.cs | 65 ++++++++ .../MailSender/MailSenderClientExtension.cs | 16 ++ .../MailSender/MailSenderConstants.cs | 8 + .../Extensions/AddInfrastructureExtension.cs | 4 +- 21 files changed, 792 insertions(+), 40 deletions(-) create mode 100644 .idea/.idea.Fin-Backend/.idea/copilot.data.migration.ask.xml create mode 100644 .idea/.idea.Fin-Backend/.idea/copilot.data.migration.ask2agent.xml create mode 100644 .idea/.idea.Fin-Backend/.idea/copilot.data.migration.edit.xml create mode 100644 .idea/.idea.Fin-Backend/.idea/db-forest-config.xml create mode 100644 Fin.Application/Users/Utils/AbortDeleteUserTemplates.cs create mode 100644 Fin.Application/Users/Utils/AccountDeletedTemplates.cs create mode 100644 Fin.Application/Users/Utils/DeleteUserTemplates.cs create mode 100644 Fin.Infrastructure/EmailSenders/Constants/MailServicesConst.cs create mode 100644 Fin.Infrastructure/EmailSenders/Dto/SendEmailDto.cs create mode 100644 Fin.Infrastructure/EmailSenders/MailSender/MailSenderClient.cs create mode 100644 Fin.Infrastructure/EmailSenders/MailSender/MailSenderClientExtension.cs create mode 100644 Fin.Infrastructure/EmailSenders/MailSender/MailSenderConstants.cs diff --git a/.idea/.idea.Fin-Backend/.idea/copilot.data.migration.ask.xml b/.idea/.idea.Fin-Backend/.idea/copilot.data.migration.ask.xml new file mode 100644 index 0000000..7ef04e2 --- /dev/null +++ b/.idea/.idea.Fin-Backend/.idea/copilot.data.migration.ask.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.Fin-Backend/.idea/copilot.data.migration.ask2agent.xml b/.idea/.idea.Fin-Backend/.idea/copilot.data.migration.ask2agent.xml new file mode 100644 index 0000000..1f2ea11 --- /dev/null +++ b/.idea/.idea.Fin-Backend/.idea/copilot.data.migration.ask2agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.Fin-Backend/.idea/copilot.data.migration.edit.xml b/.idea/.idea.Fin-Backend/.idea/copilot.data.migration.edit.xml new file mode 100644 index 0000000..8648f94 --- /dev/null +++ b/.idea/.idea.Fin-Backend/.idea/copilot.data.migration.edit.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.Fin-Backend/.idea/db-forest-config.xml b/.idea/.idea.Fin-Backend/.idea/db-forest-config.xml new file mode 100644 index 0000000..454b8f6 --- /dev/null +++ b/.idea/.idea.Fin-Backend/.idea/db-forest-config.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/Fin.Api/appsettings.json b/Fin.Api/appsettings.json index 10a640f..b2ab7e9 100644 --- a/Fin.Api/appsettings.json +++ b/Fin.Api/appsettings.json @@ -13,6 +13,12 @@ "Redis": "localhost:6379", "Version": "EXEMPLE", "EmailSender": { + "MailService": "MailKit", + + "MailSenderApiKey": "EXEMPLE", + "MailSenderName": "FinApp Team", + "MailSenderAddress": "EXEMPLE", + "EmailAddress": "EXEMPLE", "Password": "EXEMPLE" }, diff --git a/Fin.Application/Authentications/Services/AuthenticationService.cs b/Fin.Application/Authentications/Services/AuthenticationService.cs index a03487a..eb77c87 100644 --- a/Fin.Application/Authentications/Services/AuthenticationService.cs +++ b/Fin.Application/Authentications/Services/AuthenticationService.cs @@ -15,6 +15,7 @@ using Fin.Infrastructure.Constants; using Fin.Infrastructure.Database.Repositories; using Fin.Infrastructure.EmailSenders; +using Fin.Infrastructure.EmailSenders.Dto; using Fin.Infrastructure.Redis; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Distributed; @@ -68,7 +69,7 @@ public AuthenticationService( public async Task SendResetPasswordEmail(SendResetPasswordEmailInput input) { var encryptedEmail = _cryptoHelper.Encrypt(input.Email); - var credential = _credentialRepository.Query() + var credential = _credentialRepository .Include(c => c.User) .FirstOrDefault(c => c.EncryptedEmail == encryptedEmail); @@ -89,13 +90,28 @@ public async Task SendResetPasswordEmail(SendResetPasswordEmailInput input) var logoIconUrl = $"{frontUrl}/icons/fin.png"; var resetLink = $"{frontUrl}/authentication/reset-password?token={token}"; - var body = AuthenticationTemplates.ResetPasswordEmailTempalte + var subject = AuthenticationTemplates.ResetPasswordEmailSubject + .Replace("{{appName}}", AppConstants.AppName); + + var plainBody = AuthenticationTemplates.ResetPasswordEmailPlainTemplate + .Replace("{{appName}}", AppConstants.AppName) + .Replace("{{linkLifeTime}}", tokenLifeTimeInHours.ToString()) + .Replace("{{resetLink}}", resetLink); + + var htmlBody = AuthenticationTemplates.ResetPasswordEmailTemplate .Replace("{{appName}}", AppConstants.AppName) .Replace("{{logoIconUrl}}", logoIconUrl) .Replace("{{linkLifeTime}}", tokenLifeTimeInHours.ToString()) .Replace("{{resetLink}}", resetLink); - await _emailSender.SendEmailAsync(input.Email, "Fin - Reset Password", body); + await _emailSender.SendEmailAsync(new SendEmailDto + { + Subject = subject, + ToEmail = input.Email, + PlainBody = plainBody, + HtmlBody = htmlBody, + ToName = credential.User.DisplayName + }); } public async Task> ResetPassword(ResetPasswordInput input) diff --git a/Fin.Application/Authentications/Utils/AuthenticationTemplates.cs b/Fin.Application/Authentications/Utils/AuthenticationTemplates.cs index d2cfb29..c67ce2d 100644 --- a/Fin.Application/Authentications/Utils/AuthenticationTemplates.cs +++ b/Fin.Application/Authentications/Utils/AuthenticationTemplates.cs @@ -111,7 +111,18 @@ public static class AuthenticationTemplates "; - public const string ResetPasswordEmailTempalte = @" + public const string ResetPasswordEmailSubject = "{{appName}} - Reset Your Password"; + + public const string ResetPasswordEmailPlainTemplate = @" +{{appName}} - Password Reset +We received a request to reset your password. +To create a new password, please copy and paste the link below into your browser: +{{resetLink}} +This link expires in {{linkLifeTime}} hours. +If you didn't request this, please ignore this email. +"; + + public const string ResetPasswordEmailTemplate = @" diff --git a/Fin.Application/Notifications/Services/DeliveryServices/NotificationDeliveryService.cs b/Fin.Application/Notifications/Services/DeliveryServices/NotificationDeliveryService.cs index b16006b..9223387 100644 --- a/Fin.Application/Notifications/Services/DeliveryServices/NotificationDeliveryService.cs +++ b/Fin.Application/Notifications/Services/DeliveryServices/NotificationDeliveryService.cs @@ -9,6 +9,7 @@ using Fin.Infrastructure.Database.Repositories; using Fin.Infrastructure.DateTimes; using Fin.Infrastructure.EmailSenders; +using Fin.Infrastructure.EmailSenders.Dto; using Fin.Infrastructure.Firebases; using Fin.Infrastructure.Notifications.Hubs; using FirebaseAdmin.Messaging; @@ -191,12 +192,21 @@ private async Task SendFirebase(NotifyUserDto notify, UserNotificationSettings u private async Task SendEmail(NotifyUserDto notification) { - var userCredencial = await credencialRepository.Query(false) + var userCredencial = await credencialRepository + .AsNoTracking() + .Include(c => c.User) .FirstOrDefaultAsync(n => n.UserId == notification.UserId); if (userCredencial == null) throw new Exception("User not found to send email notification."); var email = _cryptoHelper.Decrypt(userCredencial.EncryptedEmail); - await emailSenderService.SendEmailAsync(email, notification.Title, notification.HtmlBody); + await emailSenderService.SendEmailAsync(new SendEmailDto + { + ToEmail = email, + Subject = notification.Title, + ToName = userCredencial.User.DisplayName, + HtmlBody = notification.HtmlBody, + PlainBody = notification.TextBody, + }); } } \ No newline at end of file diff --git a/Fin.Application/Users/Services/UserCreateService.cs b/Fin.Application/Users/Services/UserCreateService.cs index 5314079..48392f3 100644 --- a/Fin.Application/Users/Services/UserCreateService.cs +++ b/Fin.Application/Users/Services/UserCreateService.cs @@ -17,6 +17,7 @@ using Fin.Infrastructure.Database.Repositories; using Fin.Infrastructure.DateTimes; using Fin.Infrastructure.EmailSenders; +using Fin.Infrastructure.EmailSenders.Dto; using Fin.Infrastructure.Redis; using Fin.Infrastructure.UnitOfWorks; using Microsoft.EntityFrameworkCore; @@ -284,11 +285,24 @@ private async Task SendConfirmationCode(string email, string confirmationCode) var frontUrl = _configuration.GetSection(AppConstants.FrontUrlConfigKey).Get(); var logoIconUrl = $"{frontUrl}/icons/fin.png"; - var body = CreateUserTemplates.SendConfirmationCodeTemplate + var htmlBody = CreateUserTemplates.SendConfirmationCodeTemplate .Replace("{{appName}}", AppConstants.AppName) .Replace("{{logoIconUrl}}", logoIconUrl) .Replace("{{confirmationCode}}", confirmationCode); - await _emailSender.SendEmailAsync(email, "Fin - Email Confirmation", body); + var plainBody = CreateUserTemplates.SendConfirmationCodePlainTemplate + .Replace("{{appName}}", AppConstants.AppName) + .Replace("{{confirmationCode}}", confirmationCode); + + var subject = CreateUserTemplates.SendConfirmationCodeSubject + .Replace("{{appName}}", AppConstants.AppName); + + await _emailSender.SendEmailAsync(new SendEmailDto + { + ToEmail = email, + Subject = subject, + HtmlBody = htmlBody, + PlainBody = plainBody + }); } } \ No newline at end of file diff --git a/Fin.Application/Users/Services/UserDeleteService.cs b/Fin.Application/Users/Services/UserDeleteService.cs index 96f3938..a767b9b 100644 --- a/Fin.Application/Users/Services/UserDeleteService.cs +++ b/Fin.Application/Users/Services/UserDeleteService.cs @@ -1,4 +1,5 @@ using System.Security; +using Fin.Application.Users.Utils; using Fin.Domain.Global; using Fin.Domain.Global.Classes; using Fin.Domain.Notifications.Dtos; @@ -9,10 +10,12 @@ using Fin.Infrastructure.AmbientDatas; using Fin.Infrastructure.Authentications.Constants; using Fin.Infrastructure.AutoServices.Interfaces; +using Fin.Infrastructure.Constants; using Fin.Infrastructure.Database.Extensions; using Fin.Infrastructure.Database.Repositories; using Fin.Infrastructure.DateTimes; using Fin.Infrastructure.EmailSenders; +using Fin.Infrastructure.EmailSenders.Dto; using Fin.Infrastructure.UnitOfWorks; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; @@ -65,7 +68,7 @@ public async Task RequestDeleteUser(CancellationToken cancellationToken = user.RequestDelete(dateTimeProvider.UtcNow()); await userRepo.UpdateAsync(user, true, cancellationToken); - await emailSender.SendEmailAsync(userEmail, "Solicitação de deleção", "Recebemos sua solicitação de deleção de conta. Sua conta foi inativada e será deletada em 30 dias. Caso você se arrependa entre em contato com nosso suporte para abortar a deleção."); + await SendDeleteAccountEmailAsync(cancellationToken, userEmail); return true; } @@ -98,9 +101,7 @@ public async Task AbortDeleteUser(Guid userId, CancellationToken cancellat deleteRequest.Abort(ambientData.UserId.Value, now); await userDeleteRequestRepo.UpdateAsync(deleteRequest, true, cancellationToken); - var userEmail = _cryptoHelper.Decrypt(deleteRequest.User.Credential.EncryptedEmail); - await emailSender.SendEmailAsync(userEmail, "Solicitação de deleção abortada", "Sua solicitação de deleção do FinApp foi abortada e sua conta não será mais deletada."); - + await SendAbortDeleteEmailAsync(cancellationToken, deleteRequest); return true; } @@ -118,35 +119,35 @@ private async Task DeleteUser(Guid userId, CancellationToken cancellationToken = { var today = DateOnly.FromDateTime(dateTimeProvider.UtcNow()); - var deleteRequest = await userDeleteRequestRepo.Query() + var deleteRequest = await userDeleteRequestRepo .FirstOrDefaultAsync(u => u.UserId == userId && !u.Aborted && u.DeleteEffectivatedAt <= today, cancellationToken); if (deleteRequest == null) return; // TODO here need to add all related tables; - var user = await userRepo.Query().FirstOrDefaultAsync(u => u.Id == userId, cancellationToken); - var credential = await credentialRepo.Query().FirstOrDefaultAsync(u => u.UserId == userId, cancellationToken); - var tenantsUser = await tenantUserRepo.Query() + var user = await userRepo.FirstOrDefaultAsync(u => u.Id == userId, cancellationToken); + var credential = await credentialRepo.FirstOrDefaultAsync(u => u.UserId == userId, cancellationToken); + var tenantsUser = await tenantUserRepo .Where(u => u.UserId == userId) .ToListAsync(cancellationToken); var tenantIds = tenantsUser.Select(t => t.TenantId).ToList(); - var tenants = await tenantRepo.Query() + var tenants = await tenantRepo .Where(t => tenantIds.Contains(t.Id)) .ToListAsync(cancellationToken); var userEmail = _cryptoHelper.Decrypt(credential.EncryptedEmail); - var otherDeleteRequests = await userDeleteRequestRepo.Query() + var otherDeleteRequests = await userDeleteRequestRepo .Where(u => u.UserId == userId && u.Id != deleteRequest.Id) .ToListAsync(cancellationToken); - var notificationSetting = await notificationSettingsRepo.Query() + var notificationSetting = await notificationSettingsRepo .FirstOrDefaultAsync(n => n.UserId == userId, cancellationToken); var rememberSetting = - await rememberRepo.Query().FirstOrDefaultAsync(n => n.UserId == userId, cancellationToken); + await rememberRepo.FirstOrDefaultAsync(n => n.UserId == userId, cancellationToken); - var notificationDeliveries = await notificationDeliveryRepo.Query() + var notificationDeliveries = await notificationDeliveryRepo .Include(n => n.Notification) .ThenInclude(n => n.UserDeliveries) .Where(n => n.UserId == userId).ToListAsync(cancellationToken); @@ -177,10 +178,83 @@ private async Task DeleteUser(Guid userId, CancellationToken cancellationToken = await userDeleteRequestRepo.DeleteAsync(deleteRequest, cancellationToken); await userRepo.DeleteAsync(user, cancellationToken); - await emailSender.SendEmailAsync(userEmail, "Conta deletada", - "Sua conta no FinApp foi deletada. Agora você não poderá mais acessar seus dados e eles foram removidos da plataforma."); + await SendAccountDeletedEmailAsync(cancellationToken, userEmail); await scope.CompleteAsync(cancellationToken); } } + + private async Task SendAbortDeleteEmailAsync(CancellationToken cancellationToken, UserDeleteRequest deleteRequest) + { + var userEmail = _cryptoHelper.Decrypt(deleteRequest.User.Credential.EncryptedEmail); + + var frontUrl = configuration.GetSection(AppConstants.FrontUrlConfigKey).Get(); + var logoIconUrl = $"{frontUrl}/icons/fin.png"; + + var htmlBody = AbortDeleteUserTemplates.AbortDeletionTemplate + .Replace("{{appName}}", AppConstants.AppName) + .Replace("{{logoIconUrl}}", logoIconUrl); + + var plainBody = AbortDeleteUserTemplates.AbortDeletionPlainTemplate + .Replace("{{appName}}", AppConstants.AppName); + + var subject = AbortDeleteUserTemplates.AbortDeletionSubject + .Replace("{{appName}}", AppConstants.AppName); + + return await emailSender.SendEmailAsync(new SendEmailDto + { + ToEmail = userEmail, + Subject = subject, + HtmlBody = htmlBody, + PlainBody = plainBody + }, cancellationToken); + } + + private async Task SendDeleteAccountEmailAsync(CancellationToken cancellationToken, string userEmail) + { + var frontUrl = configuration.GetSection(AppConstants.FrontUrlConfigKey).Get(); + var logoIconUrl = $"{frontUrl}/icons/fin.png"; + + var htmlBody = DeleteUserTemplates.AccountDeletionTemplate + .Replace("{{appName}}", AppConstants.AppName) + .Replace("{{logoIconUrl}}", logoIconUrl); + + var plainBody = DeleteUserTemplates.AccountDeletionPlainTemplate + .Replace("{{appName}}", AppConstants.AppName); + + var subject = DeleteUserTemplates.AccountDeletionSubject + .Replace("{{appName}}", AppConstants.AppName); + + return await emailSender.SendEmailAsync(new SendEmailDto + { + ToEmail = userEmail, + Subject = subject, + HtmlBody = htmlBody, + PlainBody = plainBody + }, cancellationToken); + } + + private async Task SendAccountDeletedEmailAsync(CancellationToken cancellationToken, string userEmail) + { + var frontUrl = configuration.GetSection(AppConstants.FrontUrlConfigKey).Get(); + var logoIconUrl = $"{frontUrl}/icons/fin.png"; + + var htmlBody = AccountDeletedTemplates.AccountDeletedTemplate + .Replace("{{appName}}", AppConstants.AppName) + .Replace("{{logoIconUrl}}", logoIconUrl); + + var plainBody = AccountDeletedTemplates.AccountDeletedPlainTemplate + .Replace("{{appName}}", AppConstants.AppName); + + var subject = AccountDeletedTemplates.AccountDeletedSubject + .Replace("{{appName}}", AppConstants.AppName); + + return await emailSender.SendEmailAsync(new SendEmailDto + { + ToEmail = userEmail, + Subject = subject, + HtmlBody = htmlBody, + PlainBody = plainBody + }, cancellationToken); + } } \ No newline at end of file diff --git a/Fin.Application/Users/Utils/AbortDeleteUserTemplates.cs b/Fin.Application/Users/Utils/AbortDeleteUserTemplates.cs new file mode 100644 index 0000000..755e3ff --- /dev/null +++ b/Fin.Application/Users/Utils/AbortDeleteUserTemplates.cs @@ -0,0 +1,151 @@ +namespace Fin.Application.Users.Utils; + +public static class AbortDeleteUserTemplates +{ + public const string AbortDeletionSubject = "{{appName}} - Solicitação de deleção abortada"; + + public const string AbortDeletionPlainTemplate = @" +{{appName}} - Solicitação de deleção abortada + +Sua solicitação de deleção do {{appName}} foi abortada. + +Sua conta não será mais deletada e continua ativa. +"; + + public const string AbortDeletionTemplate = @" + + + + + + Solicitação de deleção abortada + + + +
+
+
+ {{appName}} logo +
+

{{appName}}

+
+ +
+

Solicitação de deleção abortada

+ +

+ Recebemos sua confirmação para cancelar o processo de exclusão. +

+ +
+

Conta Segura

+

Sua solicitação de deleção do {{appName}} foi abortada e sua conta não será mais deletada.

+
+ +

+ Você pode continuar utilizando nossos serviços normalmente. +

+
+ + +
+ + +"; +} \ No newline at end of file diff --git a/Fin.Application/Users/Utils/AccountDeletedTemplates.cs b/Fin.Application/Users/Utils/AccountDeletedTemplates.cs new file mode 100644 index 0000000..3a3230e --- /dev/null +++ b/Fin.Application/Users/Utils/AccountDeletedTemplates.cs @@ -0,0 +1,153 @@ +namespace Fin.Application.Users.Utils; + +public static class AccountDeletedTemplates +{ + public const string AccountDeletedSubject = "{{appName}} - Conta deletada"; + + public const string AccountDeletedPlainTemplate = @" +{{appName}} - Conta deletada + +Sua conta no {{appName}} foi deletada. + +Agora você não poderá mais acessar seus dados e eles foram removidos da plataforma. + +Agradecemos pelo tempo que passou conosco. +"; + + public const string AccountDeletedTemplate = @" + + + + + + Conta deletada + + + +
+
+
+ {{appName}} logo +
+

{{appName}}

+
+ +
+

Conta Deletada

+ +

+ Informamos que o processo de exclusão foi concluído. +

+ +
+

Acesso Removido

+

Sua conta no {{appName}} foi deletada. Agora você não poderá mais acessar seus dados e eles foram removidos da plataforma.

+
+ +

+ Agradecemos pelo tempo em que esteve conosco. +

+
+ + +
+ + +"; +} \ No newline at end of file diff --git a/Fin.Application/Users/Utils/CreateUserTemplates.cs b/Fin.Application/Users/Utils/CreateUserTemplates.cs index 28be298..6af6eec 100644 --- a/Fin.Application/Users/Utils/CreateUserTemplates.cs +++ b/Fin.Application/Users/Utils/CreateUserTemplates.cs @@ -2,6 +2,20 @@ public static class CreateUserTemplates { + public const string SendConfirmationCodeSubject = "{{appName}} - Email Confirmation"; + + public const string SendConfirmationCodePlainTemplate = @" +{{appName}} - Confirm Your Email +To complete your registration, please use the confirmation code below: +Confirmation Code: {{confirmationCode}} +How to use: +1. Return to the app or website. +2. Enter the code above to activate your account. +Important: This code expires in 10 minutes. If you didn't request this code, please ignore this email. + +If you're unable to confirm your email, please contact our support team. +"; + public const string SendConfirmationCodeTemplate = @" diff --git a/Fin.Application/Users/Utils/DeleteUserTemplates.cs b/Fin.Application/Users/Utils/DeleteUserTemplates.cs new file mode 100644 index 0000000..77acf43 --- /dev/null +++ b/Fin.Application/Users/Utils/DeleteUserTemplates.cs @@ -0,0 +1,153 @@ +namespace Fin.Application.Users.Utils; + +public static class DeleteUserTemplates +{ + public const string AccountDeletionSubject = "{{appName}} - Solicitação de Deleção"; + + public const string AccountDeletionPlainTemplate = @" +{{appName}} - Solicitação de Deleção + +Recebemos sua solicitação de deleção de conta. + +Sua conta foi inativada e será deletada em 30 dias. + +Caso você se arrependa, entre em contato com nosso suporte para abortar a deleção. +"; + + public const string AccountDeletionTemplate = @" + + + + + + Solicitação de Deleção + + + +
+
+
+ {{appName}} logo +
+

{{appName}}

+
+ +
+

Solicitação de Deleção

+ +

+ Recebemos sua solicitação de deleção de conta. +

+ +
+

Conta Inativada

+

Sua conta está programada para ser excluída permanentemente em 30 dias.

+
+ +

+ Caso você se arrependa ou tenha feito isso por engano, entre em contato com nosso suporte imediatamente para abortar a deleção. +

+
+ + +
+ + +"; +} \ No newline at end of file diff --git a/Fin.Infrastructure/EmailSenders/Constants/MailServicesConst.cs b/Fin.Infrastructure/EmailSenders/Constants/MailServicesConst.cs new file mode 100644 index 0000000..4562cb2 --- /dev/null +++ b/Fin.Infrastructure/EmailSenders/Constants/MailServicesConst.cs @@ -0,0 +1,9 @@ +namespace Fin.Infrastructure.EmailSenders.Constants; + +public static class MailServicesConst +{ + public const string MailKit = "MailKit"; + public const string MailSender = "MailSender"; + + public const string MailServiceConfigurationKey = "ApiSettings:EmailSender:MailService"; +} \ No newline at end of file diff --git a/Fin.Infrastructure/EmailSenders/Dto/SendEmailDto.cs b/Fin.Infrastructure/EmailSenders/Dto/SendEmailDto.cs new file mode 100644 index 0000000..ebcfefa --- /dev/null +++ b/Fin.Infrastructure/EmailSenders/Dto/SendEmailDto.cs @@ -0,0 +1,10 @@ +namespace Fin.Infrastructure.EmailSenders.Dto; + +public class SendEmailDto +{ + public string ToEmail { get; set; } + public string ToName { get; set; } + public string Subject { get; set; } + public string PlainBody { get; set; } + public string HtmlBody { get; set; } +} \ No newline at end of file diff --git a/Fin.Infrastructure/EmailSenders/EmailSenderService.cs b/Fin.Infrastructure/EmailSenders/EmailSenderService.cs index 8e6b366..aaada33 100644 --- a/Fin.Infrastructure/EmailSenders/EmailSenderService.cs +++ b/Fin.Infrastructure/EmailSenders/EmailSenderService.cs @@ -1,4 +1,7 @@ using Fin.Infrastructure.AutoServices.Interfaces; +using Fin.Infrastructure.EmailSenders.Constants; +using Fin.Infrastructure.EmailSenders.Dto; +using Fin.Infrastructure.EmailSenders.MailSender; using MailKit.Net.Smtp; using MailKit.Security; using Microsoft.Extensions.Configuration; @@ -8,36 +11,49 @@ namespace Fin.Infrastructure.EmailSenders; public interface IEmailSenderService { - public Task SendEmailAsync(string toEmail, string subject, string body); + public Task SendEmailAsync(SendEmailDto dto, CancellationToken cancellationToken = default); } -public class EmailSenderService: IEmailSenderService, IAutoTransient +public class EmailSenderService( + IConfiguration configuration, + IMailSenderClient mailSenderClient + ) : IEmailSenderService, IAutoTransient { - private readonly IConfiguration _configuration; - private const string EmailConfigKey = "ApiSettings:EmailSender:EmailAddress"; private const string PasswordConfigKey = "ApiSettings:EmailSender:Password"; - public EmailSenderService(IConfiguration configuration) + public async Task SendEmailAsync(SendEmailDto dto, CancellationToken cancellationToken = default) { - _configuration = configuration; + return GetMailService() switch + { + MailServicesConst.MailSender => await mailSenderClient.SendEmailAsync(dto, cancellationToken), + _ => await SendEmailWithMailKit(dto, cancellationToken) + }; + } + + private string GetMailService() + { + var mailService = configuration.GetSection(MailServicesConst.MailServiceConfigurationKey).Value; + return mailService ?? ""; } - public async Task SendEmailAsync(string toEmail, string subject, string body) + private async Task SendEmailWithMailKit(SendEmailDto dto, CancellationToken cancellationToken) { - var emailAddress = _configuration.GetSection(EmailConfigKey).Value ?? ""; - var emailPassword = _configuration.GetSection(PasswordConfigKey).Value ?? ""; + var emailAddress = configuration.GetSection(EmailConfigKey).Value ?? ""; + var emailPassword = configuration.GetSection(PasswordConfigKey).Value ?? ""; var email = new MimeMessage(); email.From.Add(MailboxAddress.Parse(emailAddress)); - email.To.Add(MailboxAddress.Parse(toEmail)); - email.Subject = subject; - email.Body = new TextPart("html") { Text = body }; + email.To.Add(MailboxAddress.Parse(dto.ToEmail)); + email.Subject = dto.Subject; + email.Body = new TextPart("html") { Text = dto.HtmlBody }; using var smtp = new SmtpClient(); - await smtp.ConnectAsync("smtp.gmail.com", 587, SecureSocketOptions.StartTls); - await smtp.AuthenticateAsync(emailAddress, emailPassword); - await smtp.SendAsync(email); - await smtp.DisconnectAsync(true); + await smtp.ConnectAsync("smtp.gmail.com", 587, SecureSocketOptions.StartTls, cancellationToken); + await smtp.AuthenticateAsync(emailAddress, emailPassword, cancellationToken); + await smtp.SendAsync(email, cancellationToken); + await smtp.DisconnectAsync(true, cancellationToken); + + return true; } } \ No newline at end of file diff --git a/Fin.Infrastructure/EmailSenders/MailSender/MailSenderClient.cs b/Fin.Infrastructure/EmailSenders/MailSender/MailSenderClient.cs new file mode 100644 index 0000000..978e3c4 --- /dev/null +++ b/Fin.Infrastructure/EmailSenders/MailSender/MailSenderClient.cs @@ -0,0 +1,65 @@ +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using Fin.Infrastructure.EmailSenders.Dto; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Fin.Infrastructure.EmailSenders.MailSender; + +public interface IMailSenderClient +{ + public Task SendEmailAsync(SendEmailDto dto, CancellationToken cancellationToken = default); +} + +public class MailSenderClient(HttpClient httpClient, IConfiguration configuration, ILogger logger) : IMailSenderClient +{ + + public async Task SendEmailAsync(SendEmailDto dto, CancellationToken cancellationToken = default) + { + try + { + var apiKey = configuration[MailSenderConstants.MailSenderApiKeyConfigurationKey]; + var fromEmail = configuration[MailSenderConstants.MailSenderAddressConfigurationKey]; + var fromName = configuration[MailSenderConstants.MailSenderNameConfigurationKey]; + + if (string.IsNullOrEmpty(apiKey)) + { + throw new InvalidOperationException("MailerSend API Key was not set."); + } + + var payload = new + { + from = new + { + email = fromEmail, + name = fromName + }, + to = new[] + { + new + { + email = dto.ToEmail, + name = dto.ToName + } + }, + subject = dto.Subject, + text = dto.PlainBody, + html = dto.HtmlBody + }; + + var jsonContent = JsonSerializer.Serialize(payload); + var request = new HttpRequestMessage(HttpMethod.Post, "email"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); + request.Headers.Add("X-Requested-With", "XMLHttpRequest"); + request.Content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); + var response = await httpClient.SendAsync(request, cancellationToken); + return response.IsSuccessStatusCode; + } + catch (Exception ex) + { + logger.LogError("Erro on send email: {}", ex.Message); + return false; + } + } +} \ No newline at end of file diff --git a/Fin.Infrastructure/EmailSenders/MailSender/MailSenderClientExtension.cs b/Fin.Infrastructure/EmailSenders/MailSender/MailSenderClientExtension.cs new file mode 100644 index 0000000..dca0132 --- /dev/null +++ b/Fin.Infrastructure/EmailSenders/MailSender/MailSenderClientExtension.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Fin.Infrastructure.EmailSenders.MailSender; + +public static class MailSenderClientExtension +{ + public static IServiceCollection AddMailSenderClient(this IServiceCollection services) + { + services.AddHttpClient(client => + { + client.BaseAddress = new Uri("https://api.mailersend.com/v1/email"); + }); + + return services; + } +} \ No newline at end of file diff --git a/Fin.Infrastructure/EmailSenders/MailSender/MailSenderConstants.cs b/Fin.Infrastructure/EmailSenders/MailSender/MailSenderConstants.cs new file mode 100644 index 0000000..2b9dd29 --- /dev/null +++ b/Fin.Infrastructure/EmailSenders/MailSender/MailSenderConstants.cs @@ -0,0 +1,8 @@ +namespace Fin.Infrastructure.EmailSenders.MailSender; + +public class MailSenderConstants +{ + public const string MailSenderApiKeyConfigurationKey = "ApiSettings:EmailSender:MailSenderApiKey"; + public const string MailSenderNameConfigurationKey = "ApiSettings:EmailSender:MailSenderName"; + public const string MailSenderAddressConfigurationKey = "ApiSettings:EmailSender:MailSenderAddress"; +} \ No newline at end of file diff --git a/Fin.Infrastructure/Extensions/AddInfrastructureExtension.cs b/Fin.Infrastructure/Extensions/AddInfrastructureExtension.cs index c3f4670..5580daa 100644 --- a/Fin.Infrastructure/Extensions/AddInfrastructureExtension.cs +++ b/Fin.Infrastructure/Extensions/AddInfrastructureExtension.cs @@ -3,6 +3,7 @@ using Fin.Infrastructure.AutoServices.Extensions; using Fin.Infrastructure.BackgroundJobs; using Fin.Infrastructure.Database.Extensions; +using Fin.Infrastructure.EmailSenders.MailSender; using Fin.Infrastructure.Firebases; using Fin.Infrastructure.Notifications.Hubs; using Fin.Infrastructure.Redis; @@ -27,7 +28,8 @@ public static IServiceCollection AddInfrastructure(this IServiceCollection servi .AddDatabase(configuration) .AddFirebase(configuration) .AddSeeders() - .AddNotifications(); + .AddNotifications() + .AddMailSenderClient(); return services; } From bf613741aac2d12be89de5108a1531fc59550125 Mon Sep 17 00:00:00 2001 From: RafaelKC Date: Tue, 25 Nov 2025 21:05:14 -0300 Subject: [PATCH 2/3] FIN-68 adding fixing test and add test to email --- Fin-Backend.sln.DotSettings.user | 6 +- .../NotificationDeliveryService.cs | 14 +- .../Users/Services/UserCreateService.cs | 2 +- .../AuthenticationServiceTest.cs | 7 +- .../EmailSenders/EmailSenderServiceTest.cs | 259 ++++++++++++++ Fin.Test/EmailSenders/MailSenderClientTest.cs | 319 ++++++++++++++++++ .../NotificationDeliveryServiceTest.cs | 3 +- Fin.Test/Users/UserCreateServiceTest.cs | 20 +- Fin.Test/Users/UserDeleteServiceTest.cs | 48 +-- 9 files changed, 630 insertions(+), 48 deletions(-) create mode 100644 Fin.Test/EmailSenders/EmailSenderServiceTest.cs create mode 100644 Fin.Test/EmailSenders/MailSenderClientTest.cs diff --git a/Fin-Backend.sln.DotSettings.user b/Fin-Backend.sln.DotSettings.user index e037b2a..0a010a0 100644 --- a/Fin-Backend.sln.DotSettings.user +++ b/Fin-Backend.sln.DotSettings.user @@ -1,5 +1,6 @@  ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -11,9 +12,12 @@ ForceIncluded ForceIncluded ForceIncluded - <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <SessionState ContinuousTestingMode="0" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <Solution /> </SessionState> + <SessionState ContinuousTestingMode="0" IsActive="True" Name="MailSenderClientTest" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <Project Location="/home/rafaelchicovis/git/fin-backend/Fin.Test" Presentation="&lt;Fin.Test&gt;" /> +</SessionState> diff --git a/Fin.Application/Notifications/Services/DeliveryServices/NotificationDeliveryService.cs b/Fin.Application/Notifications/Services/DeliveryServices/NotificationDeliveryService.cs index 9223387..5fad64e 100644 --- a/Fin.Application/Notifications/Services/DeliveryServices/NotificationDeliveryService.cs +++ b/Fin.Application/Notifications/Services/DeliveryServices/NotificationDeliveryService.cs @@ -51,13 +51,13 @@ public class NotificationDeliveryService( public async Task SendNotification(NotifyUserDto notifyUser, bool autoSave = true) { - var notificationDelivery = await deliveryRepository.Query() + var notificationDelivery = await deliveryRepository .FirstOrDefaultAsync(n => n.NotificationId == notifyUser.NotificationId && n.UserId == notifyUser.UserId); if (notificationDelivery == null) throw new Exception( $"Notification not found to send. NotificationId {notifyUser.NotificationId}, UserId {notifyUser.UserId}"); - var userSettings = await userSettingsRepository.Query() + var userSettings = await userSettingsRepository .FirstOrDefaultAsync(u => u.UserId == notifyUser.UserId); if (userSettings == null) throw new Exception( @@ -104,8 +104,8 @@ public async Task MarkAsVisualized(Guid notificationId, bool autoSave = tr if (!ambientData.IsLogged) throw new UnauthorizedAccessException("User not logged"); - var userId = ambientData.UserId.Value; - var notification = await deliveryRepository.Query() + var userId = ambientData.UserId.GetValueOrDefault(); + var notification = await deliveryRepository .FirstOrDefaultAsync(n => n.NotificationId == notificationId && n.UserId == userId); if (notification == null) return false; @@ -120,7 +120,7 @@ public async Task> GetUnvisualizedNotifications(bool autoSav if (!ambientData.IsLogged) throw new UnauthorizedAccessException("User not logged"); - var userId = ambientData.UserId.Value; + var userId = ambientData.UserId.GetValueOrDefault(); var now = dateTimeProvider.UtcNow(); var userNotification = await deliveryRepository.Query(tracking: false) @@ -140,7 +140,7 @@ public async Task> GetUnvisualizedNotifications(bool autoSav .Select(u => u.NotificationId) .ToList(); - await deliveryRepository.Query() + await deliveryRepository .Where(n => notificationToMarkAsDelivery.Contains(n.NotificationId)) .ExecuteUpdateAsync(x => x .SetProperty(a => a.Delivery, true)); @@ -207,6 +207,6 @@ await emailSenderService.SendEmailAsync(new SendEmailDto ToName = userCredencial.User.DisplayName, HtmlBody = notification.HtmlBody, PlainBody = notification.TextBody, - }); + }, CancellationToken.None); } } \ No newline at end of file diff --git a/Fin.Application/Users/Services/UserCreateService.cs b/Fin.Application/Users/Services/UserCreateService.cs index 48392f3..6f3a449 100644 --- a/Fin.Application/Users/Services/UserCreateService.cs +++ b/Fin.Application/Users/Services/UserCreateService.cs @@ -303,6 +303,6 @@ await _emailSender.SendEmailAsync(new SendEmailDto Subject = subject, HtmlBody = htmlBody, PlainBody = plainBody - }); + }, CancellationToken.None); } } \ No newline at end of file diff --git a/Fin.Test/Authentications/AuthenticationServiceTest.cs b/Fin.Test/Authentications/AuthenticationServiceTest.cs index 76d3798..693e132 100644 --- a/Fin.Test/Authentications/AuthenticationServiceTest.cs +++ b/Fin.Test/Authentications/AuthenticationServiceTest.cs @@ -14,6 +14,7 @@ using Fin.Infrastructure.Constants; using Fin.Infrastructure.Database.Repositories; using Fin.Infrastructure.EmailSenders; +using Fin.Infrastructure.EmailSenders.Dto; using Fin.Infrastructure.Redis; using FluentAssertions; using Microsoft.EntityFrameworkCore; @@ -61,7 +62,7 @@ public async Task SendResetPasswordEmail_ExistEmail() .Verify(c => c.SetAsync($"reset-token-{credential.ResetToken}", credential.UserId, It.IsAny()), Times.Once);; resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(email, It.IsAny(), It.Is(b => b.Contains(credential.ResetToken))), Times.Once); + .Verify(e => e.SendEmailAsync(It.Is(dto => dto.ToEmail == email && dto.HtmlBody.Contains(credential.ResetToken)), It.IsAny()), Times.Once); } [Fact] @@ -96,7 +97,7 @@ public async Task SendResetPasswordEmail_InactivatedUser() .Verify(c => c.SetAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);; resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -119,7 +120,7 @@ public async Task SendResetPasswordEmail_EmailNoExists() .Verify(c => c.SetAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);; resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny()), Times.Never); } #endregion diff --git a/Fin.Test/EmailSenders/EmailSenderServiceTest.cs b/Fin.Test/EmailSenders/EmailSenderServiceTest.cs new file mode 100644 index 0000000..ef5ae27 --- /dev/null +++ b/Fin.Test/EmailSenders/EmailSenderServiceTest.cs @@ -0,0 +1,259 @@ +using Fin.Infrastructure.EmailSenders; +using Fin.Infrastructure.EmailSenders.Constants; +using Fin.Infrastructure.EmailSenders.Dto; +using Fin.Infrastructure.EmailSenders.MailSender; +using FluentAssertions; +using Microsoft.Extensions.Configuration; +using Moq; + +namespace Fin.Test.EmailSenders; + +public class EmailSenderServiceTest +{ + private readonly Mock _configurationMock = new(); + private readonly Mock _mailSenderClientMock = new(); + private readonly Mock _mailServiceSectionMock = new(); + + #region SendEmailAsync - MailSender + + [Fact] + public async Task SendEmailAsync_ShouldUseMailSender_WhenConfiguredAsMailSender() + { + // Arrange + SetupMailService(MailServicesConst.MailSender); + var service = GetService(); + var dto = GetValidSendEmailDto(); + + _mailSenderClientMock + .Setup(m => m.SendEmailAsync(dto, default)) + .ReturnsAsync(true); + + // Act + var result = await service.SendEmailAsync(dto); + + // Assert + result.Should().BeTrue(); + _mailSenderClientMock.Verify(m => m.SendEmailAsync(dto, default), Times.Once); + } + + [Fact] + public async Task SendEmailAsync_ShouldReturnTrue_WhenMailSenderSucceeds() + { + // Arrange + SetupMailService(MailServicesConst.MailSender); + var service = GetService(); + var dto = GetValidSendEmailDto(); + + _mailSenderClientMock + .Setup(m => m.SendEmailAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(true); + + // Act + var result = await service.SendEmailAsync(dto); + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public async Task SendEmailAsync_ShouldReturnFalse_WhenMailSenderFails() + { + // Arrange + SetupMailService(MailServicesConst.MailSender); + var service = GetService(); + var dto = GetValidSendEmailDto(); + + _mailSenderClientMock + .Setup(m => m.SendEmailAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync(false); + + // Act + var result = await service.SendEmailAsync(dto); + + // Assert + result.Should().BeFalse(); + } + + [Fact] + public async Task SendEmailAsync_ShouldPassCancellationToken_WhenUsingMailSender() + { + // Arrange + SetupMailService(MailServicesConst.MailSender); + var service = GetService(); + var dto = GetValidSendEmailDto(); + var cts = new CancellationTokenSource(); + + _mailSenderClientMock + .Setup(m => m.SendEmailAsync(dto, cts.Token)) + .ReturnsAsync(true); + + // Act + await service.SendEmailAsync(dto, cts.Token); + + // Assert + _mailSenderClientMock.Verify(m => m.SendEmailAsync(dto, cts.Token), Times.Once); + } + + #endregion + + #region SendEmailAsync - Different Email Types + + [Fact] + public async Task SendEmailAsync_ShouldHandle_SimpleEmail() + { + // Arrange + SetupMailService(MailServicesConst.MailSender); + var service = GetService(); + var dto = new SendEmailDto + { + ToEmail = "user@test.com", + ToName = "Test User", + Subject = "Simple Subject", + PlainBody = "Simple plain text", + HtmlBody = "

Simple HTML

" + }; + + _mailSenderClientMock + .Setup(m => m.SendEmailAsync(dto, default)) + .ReturnsAsync(true); + + // Act + var result = await service.SendEmailAsync(dto); + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public async Task SendEmailAsync_ShouldHandle_EmailWithLongContent() + { + // Arrange + SetupMailService(MailServicesConst.MailSender); + var service = GetService(); + var dto = new SendEmailDto + { + ToEmail = "user@test.com", + ToName = "Test User", + Subject = "Long Email Subject", + PlainBody = new string('A', 5000), + HtmlBody = $"

{new string('B', 5000)}

" + }; + + _mailSenderClientMock + .Setup(m => m.SendEmailAsync(dto, default)) + .ReturnsAsync(true); + + // Act + var result = await service.SendEmailAsync(dto); + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public async Task SendEmailAsync_ShouldHandle_EmailWithSpecialCharacters() + { + // Arrange + SetupMailService(MailServicesConst.MailSender); + var service = GetService(); + var dto = new SendEmailDto + { + ToEmail = "user@test.com", + ToName = "Test User", + Subject = "Special <>&\" Characters", + PlainBody = "Body with special chars: <>&\"'", + HtmlBody = "

HTML with special: <>&"

" + }; + + _mailSenderClientMock + .Setup(m => m.SendEmailAsync(dto, default)) + .ReturnsAsync(true); + + // Act + var result = await service.SendEmailAsync(dto); + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public async Task SendEmailAsync_ShouldHandle_EmailWithUnicodeCharacters() + { + // Arrange + SetupMailService(MailServicesConst.MailSender); + var service = GetService(); + var dto = new SendEmailDto + { + ToEmail = "user@test.com", + ToName = "Usuário Tëst", + Subject = "Assunto com çãão", + PlainBody = "Corpo com ãéíóú", + HtmlBody = "

HTML com émojis 😀🎉

" + }; + + _mailSenderClientMock + .Setup(m => m.SendEmailAsync(dto, default)) + .ReturnsAsync(true); + + // Act + var result = await service.SendEmailAsync(dto); + + // Assert + result.Should().BeTrue(); + } + + #endregion + + #region Exception Handling + + [Fact] + public async Task SendEmailAsync_ShouldPropagateException_WhenMailSenderThrows() + { + // Arrange + SetupMailService(MailServicesConst.MailSender); + var service = GetService(); + var dto = GetValidSendEmailDto(); + + _mailSenderClientMock + .Setup(m => m.SendEmailAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new InvalidOperationException("Test exception")); + + // Act + Func act = async () => await service.SendEmailAsync(dto); + + // Assert + await act.Should().ThrowAsync() + .WithMessage("Test exception"); + } + + #endregion + + #region Helper Methods + + private EmailSenderService GetService() + { + return new EmailSenderService(_configurationMock.Object, _mailSenderClientMock.Object); + } + + private SendEmailDto GetValidSendEmailDto() + { + return new SendEmailDto + { + ToEmail = "recipient@test.com", + ToName = "Recipient Name", + Subject = "Test Email", + PlainBody = "This is a test email", + HtmlBody = "

This is a test email

" + }; + } + + private void SetupMailService(string mailService) + { + _mailServiceSectionMock.Setup(s => s.Value).Returns(mailService); + _configurationMock + .Setup(c => c.GetSection(MailServicesConst.MailServiceConfigurationKey)) + .Returns(_mailServiceSectionMock.Object); + } + + #endregion +} \ No newline at end of file diff --git a/Fin.Test/EmailSenders/MailSenderClientTest.cs b/Fin.Test/EmailSenders/MailSenderClientTest.cs new file mode 100644 index 0000000..0d4c863 --- /dev/null +++ b/Fin.Test/EmailSenders/MailSenderClientTest.cs @@ -0,0 +1,319 @@ +using System.Net; +using Fin.Infrastructure.EmailSenders.Dto; +using Fin.Infrastructure.EmailSenders.MailSender; +using FluentAssertions; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Moq; +using Moq.Protected; + +namespace Fin.Test.EmailSenders; + +public class MailSenderClientTest +{ + private readonly Mock _configurationMock; + private readonly Mock> _loggerMock; + private readonly Mock _httpMessageHandlerMock; + private readonly HttpClient _httpClient; + + public MailSenderClientTest() + { + _configurationMock = new Mock(); + _loggerMock = new Mock>(); + _httpMessageHandlerMock = new Mock(); + _httpClient = new HttpClient(_httpMessageHandlerMock.Object) + { + BaseAddress = new Uri("https://api.mailersend.com/v1/") + }; + } + + #region SendEmailAsync + + [Fact] + public async Task SendEmailAsync_ShouldReturnTrue_WhenEmailSentSuccessfully() + { + // Arrange + SetupConfiguration("test-api-key", "from@test.com", "Test Sender"); + var client = GetClient(); + var dto = GetValidSendEmailDto(); + + SetupHttpResponse(HttpStatusCode.OK); + + // Act + var result = await client.SendEmailAsync(dto); + + // Assert + result.Should().BeTrue(); + VerifyHttpRequestSent(); + } + + [Fact] + public async Task SendEmailAsync_ShouldIncludeFromInformation_WhenConfigured() + { + // Arrange + SetupConfiguration("test-api-key", "from@test.com", "Test Sender"); + var client = GetClient(); + var dto = GetValidSendEmailDto(); + + HttpRequestMessage? capturedRequest = null; + _httpMessageHandlerMock + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ) + .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)) + .Callback((req, _) => capturedRequest = req); + + // Act + await client.SendEmailAsync(dto); + + // Assert + var content = await capturedRequest!.Content!.ReadAsStringAsync(); + content.Should().Contain("from@test.com"); + content.Should().Contain("Test Sender"); + } + + [Fact] + public async Task SendEmailAsync_ShouldReturnFalse_WhenHttpRequestFails() + { + // Arrange + SetupConfiguration("test-api-key", "from@test.com", "Test Sender"); + var client = GetClient(); + var dto = GetValidSendEmailDto(); + + SetupHttpResponse(HttpStatusCode.BadRequest); + + // Act + var result = await client.SendEmailAsync(dto); + + // Assert + result.Should().BeFalse(); + } + + [Fact] + public async Task SendEmailAsync_ShouldReturnFalse_WhenHttpRequestThrowsException() + { + // Arrange + SetupConfiguration("test-api-key", "from@test.com", "Test Sender"); + var client = GetClient(); + var dto = GetValidSendEmailDto(); + + _httpMessageHandlerMock + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ) + .ThrowsAsync(new HttpRequestException("Network error")); + + // Act + var result = await client.SendEmailAsync(dto); + + // Assert + result.Should().BeFalse(); + VerifyLoggerError(); + } + + [Fact] + public async Task SendEmailAsync_ShouldThrowException_WhenApiKeyIsNull() + { + // Arrange + SetupConfiguration(null, "from@test.com", "Test Sender"); + var client = GetClient(); + var dto = GetValidSendEmailDto(); + + // Act + var result = await client.SendEmailAsync(dto); + + // Assert + result.Should().BeFalse(); + } + + [Fact] + public async Task SendEmailAsync_ShouldThrowException_WhenApiKeyIsEmpty() + { + // Arrange + SetupConfiguration("", "from@test.com", "Test Sender"); + var client = GetClient(); + var dto = GetValidSendEmailDto(); + + // Act + var result = await client.SendEmailAsync(dto); + + // Assert + result.Should().BeFalse(); + } + + [Fact] + public async Task SendEmailAsync_ShouldIncludeXRequestedWithHeader() + { + // Arrange + SetupConfiguration("test-api-key", "from@test.com", "Test Sender"); + var client = GetClient(); + var dto = GetValidSendEmailDto(); + + HttpRequestMessage? capturedRequest = null; + _httpMessageHandlerMock + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ) + .ReturnsAsync(new HttpResponseMessage(HttpStatusCode.OK)) + .Callback((req, _) => capturedRequest = req); + + // Act + await client.SendEmailAsync(dto); + + // Assert + capturedRequest!.Headers.Should().ContainKey("X-Requested-With"); + capturedRequest.Headers.GetValues("X-Requested-With").First().Should().Be("XMLHttpRequest"); + } + + [Fact] + public async Task SendEmailAsync_ShouldHandleCancellation() + { + // Arrange + SetupConfiguration("test-api-key", "from@test.com", "Test Sender"); + var client = GetClient(); + var dto = GetValidSendEmailDto(); + var cts = new CancellationTokenSource(); + cts.Cancel(); + + _httpMessageHandlerMock + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ) + .ThrowsAsync(new TaskCanceledException()); + + // Act + var result = await client.SendEmailAsync(dto, cts.Token); + + // Assert + result.Should().BeFalse(); + VerifyLoggerError(); + } + + [Theory] + [InlineData(HttpStatusCode.OK)] + [InlineData(HttpStatusCode.Created)] + [InlineData(HttpStatusCode.Accepted)] + public async Task SendEmailAsync_ShouldReturnTrue_ForSuccessStatusCodes(HttpStatusCode statusCode) + { + // Arrange + SetupConfiguration("test-api-key", "from@test.com", "Test Sender"); + var client = GetClient(); + var dto = GetValidSendEmailDto(); + + SetupHttpResponse(statusCode); + + // Act + var result = await client.SendEmailAsync(dto); + + // Assert + result.Should().BeTrue(); + } + + [Theory] + [InlineData(HttpStatusCode.BadRequest)] + [InlineData(HttpStatusCode.Unauthorized)] + [InlineData(HttpStatusCode.Forbidden)] + [InlineData(HttpStatusCode.NotFound)] + [InlineData(HttpStatusCode.InternalServerError)] + public async Task SendEmailAsync_ShouldReturnFalse_ForErrorStatusCodes(HttpStatusCode statusCode) + { + // Arrange + SetupConfiguration("test-api-key", "from@test.com", "Test Sender"); + var client = GetClient(); + var dto = GetValidSendEmailDto(); + + SetupHttpResponse(statusCode); + + // Act + var result = await client.SendEmailAsync(dto); + + // Assert + result.Should().BeFalse(); + } + + #endregion + + #region Helper Methods + + private MailSenderClient GetClient() + { + return new MailSenderClient(_httpClient, _configurationMock.Object, _loggerMock.Object); + } + + private SendEmailDto GetValidSendEmailDto() + { + return new SendEmailDto + { + ToEmail = "recipient@test.com", + ToName = "Recipient Name", + Subject = "Test Email", + PlainBody = "This is a test email", + HtmlBody = "

This is a test email

" + }; + } + + private void SetupConfiguration(string? apiKey, string fromEmail, string fromName) + { + _configurationMock + .Setup(c => c[MailSenderConstants.MailSenderApiKeyConfigurationKey]) + .Returns(apiKey); + + _configurationMock + .Setup(c => c[MailSenderConstants.MailSenderAddressConfigurationKey]) + .Returns(fromEmail); + + _configurationMock + .Setup(c => c[MailSenderConstants.MailSenderNameConfigurationKey]) + .Returns(fromName); + } + + private void SetupHttpResponse(HttpStatusCode statusCode) + { + _httpMessageHandlerMock + .Protected() + .Setup>( + "SendAsync", + ItExpr.IsAny(), + ItExpr.IsAny() + ) + .ReturnsAsync(new HttpResponseMessage(statusCode)); + } + + private void VerifyHttpRequestSent() + { + _httpMessageHandlerMock + .Protected() + .Verify( + "SendAsync", + Times.Once(), + ItExpr.IsAny(), + ItExpr.IsAny() + ); + } + + private void VerifyLoggerError() + { + _loggerMock.Verify( + x => x.Log( + LogLevel.Error, + It.IsAny(), + It.Is((v, t) => true), + It.IsAny(), + It.IsAny>()), + Times.Once); + } + + #endregion +} \ No newline at end of file diff --git a/Fin.Test/Notifications/Services/NotificationDeliveryServiceTest.cs b/Fin.Test/Notifications/Services/NotificationDeliveryServiceTest.cs index 9c30143..f3ae35b 100644 --- a/Fin.Test/Notifications/Services/NotificationDeliveryServiceTest.cs +++ b/Fin.Test/Notifications/Services/NotificationDeliveryServiceTest.cs @@ -8,6 +8,7 @@ using Fin.Infrastructure.Authentications.Constants; using Fin.Infrastructure.Database.Repositories; using Fin.Infrastructure.EmailSenders; +using Fin.Infrastructure.EmailSenders.Dto; using Fin.Infrastructure.Firebases; using Fin.Infrastructure.Notifications.Hubs; using FirebaseAdmin.Messaging; @@ -164,7 +165,7 @@ public async Task SendNotification_ShouldSendEmail_WhenAllowed() await service.SendNotification(notifyDto); // Assert - resources.FakeEmailSender.Verify(e => e.SendEmailAsync(email, notifyDto.Title, notifyDto.HtmlBody), Times.Once); + resources.FakeEmailSender.Verify(e => e.SendEmailAsync(It.Is(dto => dto.ToEmail == email && dto.Subject == notifyDto.Title && dto.HtmlBody == notifyDto.HtmlBody), It.IsAny()), Times.Once); } #endregion diff --git a/Fin.Test/Users/UserCreateServiceTest.cs b/Fin.Test/Users/UserCreateServiceTest.cs index 98edd20..f297443 100644 --- a/Fin.Test/Users/UserCreateServiceTest.cs +++ b/Fin.Test/Users/UserCreateServiceTest.cs @@ -13,6 +13,7 @@ using Fin.Infrastructure.Constants; using Fin.Infrastructure.Database.Repositories; using Fin.Infrastructure.EmailSenders; +using Fin.Infrastructure.EmailSenders.Dto; using Fin.Infrastructure.Redis; using FluentAssertions; using Microsoft.EntityFrameworkCore; @@ -53,7 +54,7 @@ public async Task StartCreateUser_InvalidPassword() c => c.SetAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny()), Times.Never); resources.FakeCodeGenerator .Verify(c => c.Generate(), Times.Never); @@ -86,7 +87,7 @@ public async Task StartCreateUser_NotSamePassword() c => c.SetAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny()), Times.Never); resources.FakeCodeGenerator .Verify(c => c.Generate(), Times.Never); @@ -128,7 +129,7 @@ public async Task StartCreateUser_EmailInUse() c => c.SetAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny()), Times.Never); resources.FakeCodeGenerator .Verify(c => c.Generate(), Times.Never); @@ -178,10 +179,9 @@ public async Task StartCreateUser_Success() u.Token == result.Data.CreationToken && u.EmailConfirmationCode == confirmationCode && u.EmailSentDateTime == sentDateTime && !u.ValidatedEmail), It.IsAny()), Times.Once); + resources.FakeEmailSender - .Verify( - e => e.SendEmailAsync(input.Email, It.IsAny(), - It.Is(b => b.Contains(confirmationCode))), Times.Once); + .Verify(e => e.SendEmailAsync(It.Is(dto => dto.ToEmail == input.Email), It.IsAny()), Times.Once); resources.FakeCodeGenerator .Verify(c => c.Generate(), Times.Once); @@ -229,7 +229,7 @@ public async Task ResendConfirmationEmail_UnauthorizedCode() c => c.SetAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny()), Times.Never); resources.FakeCodeGenerator .Verify(c => c.Generate(), Times.Never); @@ -268,7 +268,7 @@ public async Task ResendConfirmationEmail_LessThan1Minute() c => c.SetAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny()), Times.Never); resources.FakeCodeGenerator .Verify(c => c.Generate(), Times.Never); @@ -318,9 +318,7 @@ public async Task ResendConfirmationEmail_Success() u.EmailConfirmationCode == confirmationCode && u.EmailSentDateTime == now && !u.ValidatedEmail), It.IsAny()), Times.Once); resources.FakeEmailSender - .Verify( - e => e.SendEmailAsync(email, It.IsAny(), - It.Is(b => b.Contains(confirmationCode))), Times.Once); + .Verify(e => e.SendEmailAsync(It.Is(dto => dto.ToEmail == email), It.IsAny()), Times.Once); resources.FakeCodeGenerator .Verify(c => c.Generate(), Times.Once); diff --git a/Fin.Test/Users/UserDeleteServiceTest.cs b/Fin.Test/Users/UserDeleteServiceTest.cs index c0c22f3..8511a42 100644 --- a/Fin.Test/Users/UserDeleteServiceTest.cs +++ b/Fin.Test/Users/UserDeleteServiceTest.cs @@ -7,8 +7,10 @@ using Fin.Domain.Users.Entities; using Fin.Domain.Users.Factories; using Fin.Infrastructure.Authentications.Constants; +using Fin.Infrastructure.Constants; using Fin.Infrastructure.Database.Repositories; using Fin.Infrastructure.EmailSenders; +using Fin.Infrastructure.EmailSenders.Dto; using FluentAssertions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -38,8 +40,6 @@ public async Task RequestDeleteUser_AlreadyRequest_ReturnsFalse() result.Should().BeFalse(); DateTimeProvider.Verify(d => d.UtcNow(), Times.Never); - resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -58,7 +58,7 @@ public async Task RequestDeleteUser_UserNotFound_ReturnsFalse() DateTimeProvider.Verify(d => d.UtcNow(), Times.Never); resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -72,7 +72,7 @@ public async Task RequestDeleteUser_Success() var now = TestUtils.UtcDateTimes[0]; DateTimeProvider.Setup(d => d.UtcNow()).Returns(now); - var user = await resources.UserRepository.Query(false).FirstOrDefaultAsync(); + var user = await resources.UserRepository.AsNoTracking().FirstOrDefaultAsync(); var email = TestUtils.Strings[2]; var encryptedEmail = resources.CryptoHelper.Encrypt(email); user.Credential = UserCredentialFactory.Create(user.Id, encryptedEmail, null, UserCredentialFactoryType.Password); @@ -84,14 +84,13 @@ public async Task RequestDeleteUser_Success() // Assert result.Should().BeTrue(); - var updatedUser = await resources.UserRepository.Query().FirstAsync(u => u.Id == AmbientData.UserId.Value); + var updatedUser = await resources.UserRepository.FirstAsync(u => u.Id == AmbientData.UserId.Value); updatedUser.DeleteRequests.First().DeleteRequestedAt.Should().Be(now); updatedUser.IsActivity.Should().BeFalse(); DateTimeProvider.Verify(d => d.UtcNow(), Times.Once); resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(email, "Solicitação de deleção", - It.Is(body => body.Contains("Recebemos sua solicitação"))), Times.Once); + .Verify(e => e.SendEmailAsync(It.Is(dto => dto.ToEmail == email), It.IsAny()), Times.Once); } #endregion @@ -126,7 +125,7 @@ public async Task EffectiveDeleteUsers_WithUsersToDelete_Success() DateTimeProvider.Setup(d => d.UtcNow()).Returns(TestUtils.UtcDateTimes[0]); - var user = await resources.UserRepository.Query().Include(u => u.Credential).FirstAsync(); + var user = await resources.UserRepository.Include(u => u.Credential).FirstAsync(); var email = TestUtils.Strings[2]; var encryptedEmail = resources.CryptoHelper.Encrypt(email); @@ -149,18 +148,17 @@ public async Task EffectiveDeleteUsers_WithUsersToDelete_Success() // Assert result.Should().BeTrue(); - var deletedUser = await resources.UserRepository.Query().FirstOrDefaultAsync(u => u.Id == user.Id); + var deletedUser = await resources.UserRepository.FirstOrDefaultAsync(u => u.Id == user.Id); deletedUser.Should().BeNull(); - var deletedCredential = await resources.CredentialRepository.Query().FirstOrDefaultAsync(c => c.UserId == user.Id); + var deletedCredential = await resources.CredentialRepository.FirstOrDefaultAsync(c => c.UserId == user.Id); deletedCredential.Should().BeNull(); - var deletedRequest = await resources.UserDeleteRequestRepository.Query().FirstOrDefaultAsync(r => r.UserId == user.Id); + var deletedRequest = await resources.UserDeleteRequestRepository.FirstOrDefaultAsync(r => r.UserId == user.Id); deletedRequest.Should().BeNull(); resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(email, "Conta deletada", - It.Is(body => body.Contains("foi deletada"))), Times.Once); + .Verify(e => e.SendEmailAsync(It.Is(dto => dto.ToEmail == email), It.IsAny()), Times.Once); } #endregion @@ -184,7 +182,7 @@ public async Task AbortDeleteUser_NotAdminAndNotOwner_ThrowsSecurityException() DateTimeProvider.Verify(d => d.UtcNow(), Times.Never); resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -204,7 +202,7 @@ public async Task AbortDeleteUser_NoAmbientUserId_ThrowsSecurityException() DateTimeProvider.Verify(d => d.UtcNow(), Times.Never); resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -225,7 +223,7 @@ public async Task AbortDeleteUser_DeleteRequestNotFound_ReturnsFalse() DateTimeProvider.Verify(d => d.UtcNow(), Times.Once); resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + .Verify(e => e.SendEmailAsync(It.IsAny(), It.IsAny()), Times.Never); } [Fact] @@ -257,7 +255,7 @@ public async Task AbortDeleteUser_AsAdmin_Success() // Assert result.Should().BeTrue(); - var updatedRequest = await resources.UserDeleteRequestRepository.Query() + var updatedRequest = await resources.UserDeleteRequestRepository .FirstAsync(r => r.Id == deleteRequest.Id); updatedRequest.Aborted.Should().BeTrue(); updatedRequest.AbortedAt.Should().Be(now); @@ -265,8 +263,7 @@ public async Task AbortDeleteUser_AsAdmin_Success() DateTimeProvider.Verify(d => d.UtcNow(), Times.Once); resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(email, "Solicitação de deleção abortada", - It.Is(body => body.Contains("foi abortada"))), Times.Once); + .Verify(e => e.SendEmailAsync(It.Is(dto => dto.ToEmail == email), It.IsAny()), Times.Once); } [Fact] @@ -277,7 +274,7 @@ public async Task AbortDeleteUser_AsOwner_Success() var resources = GetResources(); var service = GetService(resources); - var user = await resources.UserRepository.Query().Include(u => u.Credential).FirstAsync(); + var user = await resources.UserRepository.Include(u => u.Credential).FirstAsync(); await resources.CredentialRepository.DeleteAsync(user.Credential, true); var email = TestUtils.Strings[2]; @@ -297,15 +294,14 @@ public async Task AbortDeleteUser_AsOwner_Success() // Assert result.Should().BeTrue(); - var updatedRequest = await resources.UserDeleteRequestRepository.Query() + var updatedRequest = await resources.UserDeleteRequestRepository .FirstAsync(r => r.Id == deleteRequest.Id); updatedRequest.Aborted.Should().BeTrue(); updatedRequest.AbortedAt.Should().Be(now); updatedRequest.UserAbortedId.Should().Be(AmbientData.UserId.Value); resources.FakeEmailSender - .Verify(e => e.SendEmailAsync(email, "Solicitação de deleção abortada", - It.Is(body => body.Contains("foi abortada"))), Times.Once); + .Verify(e => e.SendEmailAsync(It.Is(dto => dto.ToEmail == email), It.IsAny()), Times.Once); } #endregion @@ -342,7 +338,7 @@ public async Task GetList_WithData_ReturnsPagedResult() var resources = GetResources(); var service = GetService(resources); - var user1 = await resources.UserRepository.Query().FirstAsync(); + var user1 = await resources.UserRepository.FirstAsync(); var user2 = new User { Id = TestUtils.Guids[3], DisplayName = TestUtils.Strings[1], Credential = new UserCredential()}; await resources.UserRepository.AddAsync(user2, true); @@ -427,6 +423,10 @@ private Resources GetResources() resources.FakeConfiguration .Setup(c => c.GetSection(AuthenticationConstants.EncryptIvConfigKey).Value) .Returns("1234567890qwerty"); + + resources.FakeConfiguration + .Setup(c => c.GetSection(AppConstants.FrontUrlConfigKey).Value) + .Returns("http://localhost:4200"); var encryptKey = resources.FakeConfiguration.Object.GetSection(AuthenticationConstants.EncryptKeyConfigKey).Value ?? ""; var encryptIv = resources.FakeConfiguration.Object.GetSection(AuthenticationConstants.EncryptIvConfigKey).Value ?? ""; From 7c5d997677789fe447d3a2e6ecd896eed8b8f5ff Mon Sep 17 00:00:00 2001 From: RafaelKC Date: Tue, 25 Nov 2025 21:10:51 -0300 Subject: [PATCH 3/3] FIN-68 removed Obsolete .Query method from IRepository --- .../Services/AuthenticationService.cs | 4 ++-- .../CardBrands/CardBrandService.cs | 10 +++++----- .../CreditCards/Services/CreditCardService.cs | 10 +++++----- .../Services/CreditCardValidationService.cs | 8 ++++---- .../FinancialInstitutionService.cs | 12 +++++------ Fin.Application/Menus/MenuService.cs | 10 +++++----- .../CrudServices/NotificationService.cs | 8 ++++---- .../UserNotificationSettingService.cs | 6 +++--- .../UserRememberUseSettingsService.cs | 4 ++-- .../NotificationDeliveryService.cs | 2 +- .../SchedulerServices/UserSchedulerService.cs | 6 +++--- .../TitleCategories/TitleCategoryService.cs | 14 ++++++------- .../Titles/Services/TitleService.cs | 2 +- .../Deletes/TitleDeleteMustExistValidation.cs | 2 +- .../TitleInputDuplicatedValidation.cs | 2 +- .../TitleInputMustExistValidation.cs | 2 +- .../TitleInputWalletValidation.cs | 2 +- .../Users/Services/UserCreateService.cs | 4 ++-- .../Users/Services/UserDeleteService.cs | 10 +++++----- Fin.Application/Users/Services/UserService.cs | 4 ++-- .../Wallets/Services/WalletBalaceService.cs | 8 ++++---- .../Wallets/Services/WalletService.cs | 10 +++++----- .../Services/WalletValidationService.cs | 14 ++++++------- .../ActivatedUserMiddleware.cs | 2 +- .../AuthenticationTokenService.cs | 4 ++-- .../Database/Repositories/IRepository.cs | 2 -- .../Database/Repositories/Repository.cs | 5 ----- .../Seeders/Seeders/DefaultMenusSeeder.cs | 2 +- .../AuthenticationServiceTest.cs | 10 +++++----- .../AuthenticationTokenServiceTest.cs | 2 +- .../Services/CreditCardServiceTest.cs | 16 +++++++-------- .../FinancialInstitutionServiceTest.cs | 18 ++++++++--------- .../Menus/Services/NotificationServiceTest.cs | 6 +++--- .../NotificationDeliveryServiceTest.cs | 8 ++++---- .../Services/NotificationServiceTest.cs | 8 ++++---- .../UserNotificationSettingServiceTest.cs | 6 +++--- .../UserRememberUseSchedulerServiceTest.cs | 2 +- .../UserRememberUseSettingsService.cs | 2 +- .../Services/TitleCategoryServiceTest.cs | 10 +++++----- Fin.Test/Users/UserCreateServiceTest.cs | 20 +++++++++---------- .../Wallets/Services/WalletServiceTest.cs | 18 ++++++++--------- 41 files changed, 144 insertions(+), 151 deletions(-) diff --git a/Fin.Application/Authentications/Services/AuthenticationService.cs b/Fin.Application/Authentications/Services/AuthenticationService.cs index eb77c87..a056f6f 100644 --- a/Fin.Application/Authentications/Services/AuthenticationService.cs +++ b/Fin.Application/Authentications/Services/AuthenticationService.cs @@ -133,7 +133,7 @@ public async Task> ResetPasswo ErrorCode = ResetPasswordErrorCode.NotSamePassword }; - var credential = await _credentialRepository.Query().FirstOrDefaultAsync(c => c.ResetToken == input.ResetToken); + var credential = await _credentialRepository.FirstOrDefaultAsync(c => c.ResetToken == input.ResetToken); if (credential == null) return new ValidationResultDto { @@ -172,7 +172,7 @@ public async Task LoginOrSingInWithGoogle(LoginWithGoogle { var encryptedEmail = _cryptoHelper.Encrypt(input.Email); - var credential = _credentialRepository.Query() + var credential = _credentialRepository .Include(c => c.User) .ThenInclude(c => c.Tenants) .FirstOrDefault(c => c.EncryptedEmail == encryptedEmail); diff --git a/Fin.Application/CardBrands/CardBrandService.cs b/Fin.Application/CardBrands/CardBrandService.cs index fdf84a6..851786e 100644 --- a/Fin.Application/CardBrands/CardBrandService.cs +++ b/Fin.Application/CardBrands/CardBrandService.cs @@ -28,14 +28,14 @@ IAmbientData ambientData { public async Task Get(Guid id) { - var entity = await repository.Query() + var entity = await repository .FirstOrDefaultAsync(n => n.Id == id); return entity != null ? new CardBrandOutput(entity) : null; } public async Task> GetList(PagedFilteredAndSortedInput input) { - return await repository.Query(false) + return await repository.AsNoTracking() .OrderBy(m => m.Name) .ApplyFilterAndSorter(input) .Select(n => new CardBrandOutput(n)) @@ -44,7 +44,7 @@ public async Task> GetList(PagedFilteredAndSortedIn public async Task> GetListForSideNav() { - return await repository.Query(false) + return await repository.AsNoTracking() .OrderBy(m => m.Name) .Select(m => new CardBrandOutput(m)) .ToListAsync(); @@ -61,7 +61,7 @@ public async Task Create(CardBrandInput input, bool autoSave = public async Task Update(Guid id, CardBrandInput input, bool autoSave = false) { ValidarInput(input); - var cardBrand = await repository.Query() + var cardBrand = await repository .FirstOrDefaultAsync(u => u.Id == id); if (cardBrand == null) return false; @@ -73,7 +73,7 @@ public async Task Update(Guid id, CardBrandInput input, bool autoSave = fa public async Task Delete(Guid id, bool autoSave = false) { - var cardBrand = await repository.Query() + var cardBrand = await repository .FirstOrDefaultAsync(u => u.Id == id); if (cardBrand == null) return false; diff --git a/Fin.Application/CreditCards/Services/CreditCardService.cs b/Fin.Application/CreditCards/Services/CreditCardService.cs index b3b185e..4d1820a 100644 --- a/Fin.Application/CreditCards/Services/CreditCardService.cs +++ b/Fin.Application/CreditCards/Services/CreditCardService.cs @@ -28,13 +28,13 @@ ICreditCardValidationService validationService { public async Task Get(Guid id) { - var entity = await repository.Query(false).FirstOrDefaultAsync(n => n.Id == id); + var entity = await repository.AsNoTracking().FirstOrDefaultAsync(n => n.Id == id); return entity != null ? new CreditCardOutput(entity) : null; } public async Task> GetList(CreditCardGetListInput input) { - return await repository.Query(false) + return await repository.AsNoTracking() .WhereIf(input.Inactivated.HasValue, n => n.Inactivated == input.Inactivated.Value) .WhereIf(input.CardBrandIds.Any(), creditCard => input.CardBrandIds.Contains(creditCard.CardBrandId)) .WhereIf(input.DebitWalletIds.Any(), creditCard => input.DebitWalletIds.Contains(creditCard.DebitWalletId)) @@ -62,7 +62,7 @@ public async Task> var validation = await validationService.ValidateInput(input, id); if (!validation.Success) return validation; - var creditCard = await repository.Query().FirstAsync(u => u.Id == id); + var creditCard = await repository.FirstAsync(u => u.Id == id); creditCard.Update(input); await repository.UpdateAsync(creditCard, autoSave); @@ -75,7 +75,7 @@ public async Task> Delete(G var validation = await validationService.ValidateDelete(id); if (!validation.Success) return validation; - var creditCard = await repository.Query().FirstAsync(u => u.Id == id); + var creditCard = await repository.FirstAsync(u => u.Id == id); await repository.DeleteAsync(creditCard, autoSave); validation.Success = true; @@ -87,7 +87,7 @@ public async Task> var validation = await validationService.ValidateToggleInactive(id); if (!validation.Success) return validation; - var creditCard = await repository.Query().FirstAsync(u => u.Id == id); + var creditCard = await repository.FirstAsync(u => u.Id == id); creditCard.ToggleInactivated(); await repository.UpdateAsync(creditCard, autoSave); diff --git a/Fin.Application/CreditCards/Services/CreditCardValidationService.cs b/Fin.Application/CreditCards/Services/CreditCardValidationService.cs index abbaed3..ccabba8 100644 --- a/Fin.Application/CreditCards/Services/CreditCardValidationService.cs +++ b/Fin.Application/CreditCards/Services/CreditCardValidationService.cs @@ -31,7 +31,7 @@ public async Task> { var validationResult = new ValidationResultDto(); - var creditCard = await repository.Query(tracking: false).FirstOrDefaultAsync(n => n.Id == creditCardId); + var creditCard = await repository.AsNoTracking().FirstOrDefaultAsync(n => n.Id == creditCardId); if (creditCard is null) { validationResult.ErrorCode = CreditCardToggleInactiveErrorCode.CreditCardNotFound; @@ -47,7 +47,7 @@ public async Task> Validate { var validationResult = new ValidationResultDto(); - var creditCardExists = await repository.Query().AnyAsync(n => n.Id == creditCardId); + var creditCardExists = await repository.AnyAsync(n => n.Id == creditCardId); if (!creditCardExists) { validationResult.ErrorCode = CreditCardDeleteErrorCode.CreditCardNotFound; @@ -68,7 +68,7 @@ public async Task> Val if (editingId.HasValue) { - var creditCardExists = await repository.Query() + var creditCardExists = await repository .AnyAsync(n => n.Id == editingId.Value); if (!creditCardExists) { @@ -120,7 +120,7 @@ public async Task> Val return validationResult; } - var nameAlredInUse = await repository.Query() + var nameAlredInUse = await repository .AnyAsync(n => n.Name.ToLower() == input.Name.ToLower() && (!editingId.HasValue || n.Id != editingId)); if (nameAlredInUse) { diff --git a/Fin.Application/FinancialInstitutions/FinancialInstitutionService.cs b/Fin.Application/FinancialInstitutions/FinancialInstitutionService.cs index 4c26dda..93b704a 100644 --- a/Fin.Application/FinancialInstitutions/FinancialInstitutionService.cs +++ b/Fin.Application/FinancialInstitutions/FinancialInstitutionService.cs @@ -27,14 +27,14 @@ IRepository repository { public async Task Get(Guid id) { - var entity = await repository.Query(false) + var entity = await repository.AsNoTracking() .FirstOrDefaultAsync(f => f.Id == id); return entity != null ? new FinancialInstitutionOutput(entity) : null; } public async Task> GetList(FinancialInstitutionGetListInput input) { - return await repository.Query(false) + return await repository.AsNoTracking() .WhereIf(input.Inactive.HasValue, f => f.Inactive == input.Inactive.Value) .WhereIf(input.Type.HasValue, f => f.Type == input.Type.Value) .OrderBy(f => f.Inactive) @@ -56,7 +56,7 @@ public async Task Create(FinancialInstitutionInput i public async Task Update(Guid id, FinancialInstitutionInput input, bool autoSave = false) { await ValidateInput(input, id); - var institution = await repository.Query() + var institution = await repository .FirstOrDefaultAsync(f => f.Id == id); if (institution == null) return false; @@ -70,7 +70,7 @@ public async Task Update(Guid id, FinancialInstitutionInput input, bool au public async Task Delete(Guid id, bool autoSave = false) { - var institution = await repository.Query() + var institution = await repository .Include(f => f.Wallets) .FirstOrDefaultAsync(f => f.Id == id); if (institution == null || institution.Wallets.Any()) return false; @@ -81,7 +81,7 @@ public async Task Delete(Guid id, bool autoSave = false) public async Task ToggleInactive(Guid id, bool autoSave = false) { - var institution = await repository.Query() + var institution = await repository .Include(f => f.Wallets) .FirstOrDefaultAsync(f => f.Id == id); if (institution == null || (!institution.Inactive && institution.Wallets.Any(w => !w.Inactivated))) return false; @@ -112,7 +112,7 @@ private async Task ValidateInput(FinancialInstitutionInput input, Guid? editingI if(!string.IsNullOrWhiteSpace(input.Code) && input.Code.Count() > 15) throw new BadHttpRequestException("Code must be at most 15 characters long"); - var existingName = await repository.Query() + var existingName = await repository .Where(f => f.Name == input.Name) .WhereIf(editingId.HasValue, f => f.Id != editingId.Value) .AnyAsync(); diff --git a/Fin.Application/Menus/MenuService.cs b/Fin.Application/Menus/MenuService.cs index 23426df..d7ac9ed 100644 --- a/Fin.Application/Menus/MenuService.cs +++ b/Fin.Application/Menus/MenuService.cs @@ -28,14 +28,14 @@ IAmbientData ambientData { public async Task Get(Guid id) { - var entity = await repository.Query() + var entity = await repository .FirstOrDefaultAsync(n => n.Id == id); return entity != null ? new MenuOutput(entity) : null; } public async Task> GetList(PagedFilteredAndSortedInput input) { - return await repository.Query(false) + return await repository.AsNoTracking() .WhereIf(!ambientData.IsAdmin, m => !m.OnlyForAdmin) .OrderBy(m => m.Name) .ApplyFilterAndSorter(input) @@ -45,7 +45,7 @@ public async Task> GetList(PagedFilteredAndSortedInput i public async Task> GetListForSideNav() { - return await repository.Query(false) + return await repository.AsNoTracking() .OrderBy(m => m.Name) .Where(m => m.Position != MenuPosition.Hide) .WhereIf(!ambientData.IsAdmin, m => !m.OnlyForAdmin) @@ -64,7 +64,7 @@ public async Task Create(MenuInput input, bool autoSave = false) public async Task Update(Guid id, MenuInput input, bool autoSave = false) { ValidateInput(input); - var menu = await repository.Query() + var menu = await repository .FirstOrDefaultAsync(u => u.Id == id); if (menu == null) return false; @@ -76,7 +76,7 @@ public async Task Update(Guid id, MenuInput input, bool autoSave = false) public async Task Delete(Guid id, bool autoSave = false) { - var menu = await repository.Query() + var menu = await repository .FirstOrDefaultAsync(u => u.Id == id); if (menu == null) return false; diff --git a/Fin.Application/Notifications/Services/CrudServices/NotificationService.cs b/Fin.Application/Notifications/Services/CrudServices/NotificationService.cs index ea51e2a..4b73d3e 100644 --- a/Fin.Application/Notifications/Services/CrudServices/NotificationService.cs +++ b/Fin.Application/Notifications/Services/CrudServices/NotificationService.cs @@ -27,7 +27,7 @@ IUserSchedulerService schedulerService { public async Task Get(Guid id) { - var entity = await repository.Query() + var entity = await repository .Include(n => n.UserDeliveries) .FirstOrDefaultAsync(n => n.Id == id); return entity != null ? new NotificationOutput(entity) : null; @@ -35,7 +35,7 @@ public async Task Get(Guid id) public async Task> GetList(PagedFilteredAndSortedInput input) { - return await repository.Query(false) + return await repository.AsNoTracking() .Include(n => n.UserDeliveries) .ApplyFilterAndSorter(input) .Select(n => new NotificationOutput(n)) @@ -56,7 +56,7 @@ public async Task Create(NotificationInput input, bool autoS public async Task Update(Guid id, NotificationInput input, bool autoSave = false) { - var notification = await repository.Query() + var notification = await repository .Include(u => u.UserDeliveries) .FirstOrDefaultAsync(u => u.Id == id); if (notification == null) return false; @@ -84,7 +84,7 @@ public async Task Update(Guid id, NotificationInput input, bool autoSave = public async Task Delete(Guid id, bool autoSave = false) { - var notification = await repository.Query() + var notification = await repository .Include(n => n.UserDeliveries) .FirstOrDefaultAsync(u => u.Id == id); if (notification == null) return false; diff --git a/Fin.Application/Notifications/Services/CrudServices/UserNotificationSettingService.cs b/Fin.Application/Notifications/Services/CrudServices/UserNotificationSettingService.cs index 7d1ee88..aeec4a3 100644 --- a/Fin.Application/Notifications/Services/CrudServices/UserNotificationSettingService.cs +++ b/Fin.Application/Notifications/Services/CrudServices/UserNotificationSettingService.cs @@ -19,14 +19,14 @@ public class UserNotificationSettingService(IRepository GetByCurrentUser() { - var entity = await repository.Query() + var entity = await repository .FirstOrDefaultAsync(u => u.UserId == ambientData.UserId); return entity == null ? null : new UserNotificationSettingsOutput(entity); } public async Task UpdateByCurrentUser(UserNotificationSettingsInput input, bool autoSave = false) { - var setting = await repository.Query() + var setting = await repository .FirstOrDefaultAsync(u => u.UserId == ambientData.UserId); if (setting == null) return false; @@ -37,7 +37,7 @@ public async Task UpdateByCurrentUser(UserNotificationSettingsInput input, public async Task AddFirebaseToken(string token, bool autoSave = false) { - var setting = await repository.Query() + var setting = await repository .FirstOrDefaultAsync(u => u.UserId == ambientData.UserId); if (setting == null) return false; diff --git a/Fin.Application/Notifications/Services/CrudServices/UserRememberUseSettingsService.cs b/Fin.Application/Notifications/Services/CrudServices/UserRememberUseSettingsService.cs index 9c3abdd..288b1e4 100644 --- a/Fin.Application/Notifications/Services/CrudServices/UserRememberUseSettingsService.cs +++ b/Fin.Application/Notifications/Services/CrudServices/UserRememberUseSettingsService.cs @@ -18,14 +18,14 @@ public class UserRememberUseSettingsService(IRepository { public async Task GetByCurrentUser() { - var entity = await repository.Query() + var entity = await repository .FirstOrDefaultAsync(u => u.UserId == ambientData.UserId); return entity == null ? null : new UserRememberUseSettingOutput(entity); } public async Task UpdateByCurrentUser(UserRememberUseSettingInput input, bool autoSave = false) { - var setting = await repository.Query() + var setting = await repository .FirstOrDefaultAsync(u => u.UserId == ambientData.UserId); if (setting == null) return false; diff --git a/Fin.Application/Notifications/Services/DeliveryServices/NotificationDeliveryService.cs b/Fin.Application/Notifications/Services/DeliveryServices/NotificationDeliveryService.cs index 5fad64e..583227f 100644 --- a/Fin.Application/Notifications/Services/DeliveryServices/NotificationDeliveryService.cs +++ b/Fin.Application/Notifications/Services/DeliveryServices/NotificationDeliveryService.cs @@ -123,7 +123,7 @@ public async Task> GetUnvisualizedNotifications(bool autoSav var userId = ambientData.UserId.GetValueOrDefault(); var now = dateTimeProvider.UtcNow(); - var userNotification = await deliveryRepository.Query(tracking: false) + var userNotification = await deliveryRepository.AsNoTracking() .Include(u => u.Notification) .Where(n => !n.Visualized && n.UserId == userId) .Where(n => n.Notification.StartToDelivery <= now.AddMinutes(1)) diff --git a/Fin.Application/Notifications/Services/SchedulerServices/UserSchedulerService.cs b/Fin.Application/Notifications/Services/SchedulerServices/UserSchedulerService.cs index 7194bf8..37e4d96 100644 --- a/Fin.Application/Notifications/Services/SchedulerServices/UserSchedulerService.cs +++ b/Fin.Application/Notifications/Services/SchedulerServices/UserSchedulerService.cs @@ -34,7 +34,7 @@ public async Task ScheduleDailyNotifications(CancellationToken cancellationToken var startOfDay = dateTimeProvider.UtcNow().Date; var endOfDay = startOfDay.AddDays(1).AddTicks(-1); - var notifications = await notificationRepository.Query() + var notifications = await notificationRepository .Include(n => n.UserDeliveries) .Where(n => startOfDay <= n.StartToDelivery && n.StartToDelivery <= endOfDay) .Where(n => n.UserDeliveries.Any(n => !n.Delivery)) @@ -53,7 +53,7 @@ public async Task ScheduleDailyNotifications(CancellationToken cancellationToken public async Task ScheduleNotification(Guid notificationId, bool autosave = true, CancellationToken cancellationToken = default) { - var notification = await notificationRepository.Query(true) + var notification = await notificationRepository .Include(n => n.UserDeliveries).FirstOrDefaultAsync(n => n.Id == notificationId, cancellationToken); await ScheduleNotification(notification, autosave, cancellationToken); } @@ -78,7 +78,7 @@ public async Task ScheduleNotification(Notification notification, bool autosave public async Task UnscheduleNotification(Guid notificationId, List userIds, CancellationToken cancellationToken = default) { - var jobsIds = await notificationUserRepository.Query(false) + var jobsIds = await notificationUserRepository.AsNoTracking() .Where(n => n.NotificationId == notificationId && userIds.Contains(n.UserId) && !string.IsNullOrWhiteSpace(n.BackgroundJobId)) diff --git a/Fin.Application/TitleCategories/TitleCategoryService.cs b/Fin.Application/TitleCategories/TitleCategoryService.cs index 3903504..ed0ceeb 100644 --- a/Fin.Application/TitleCategories/TitleCategoryService.cs +++ b/Fin.Application/TitleCategories/TitleCategoryService.cs @@ -27,13 +27,13 @@ IRepository repository { public async Task Get(Guid id) { - var entity = await repository.Query(false).FirstOrDefaultAsync(n => n.Id == id); + var entity = await repository.AsNoTracking().FirstOrDefaultAsync(n => n.Id == id); return entity != null ? new TitleCategoryOutput(entity) : null; } public async Task> GetList(TitleCategoryGetListInput input) { - return await repository.Query(false) + return await repository.AsNoTracking() .WhereIf(input.Inactivated.HasValue, n => n.Inactivated == input.Inactivated.Value) .WhereIf(input.Type.HasValue, n => n.Type == input.Type.Value) .OrderBy(m => m.Inactivated) @@ -59,7 +59,7 @@ public async Task(input, id); if (!validation.Success) return validation; - var titleCategory = await repository.Query().FirstAsync(u => u.Id == id); + var titleCategory = await repository.FirstAsync(u => u.Id == id); titleCategory.Update(input); await repository.UpdateAsync(titleCategory, autoSave); @@ -69,7 +69,7 @@ public async Task Delete(Guid id, bool autoSave = false) { - var titleCategory = await repository.Query() + var titleCategory = await repository .FirstOrDefaultAsync(u => u.Id == id); if (titleCategory == null) return false; @@ -79,7 +79,7 @@ public async Task Delete(Guid id, bool autoSave = false) public async Task ToggleInactive(Guid id, bool autoSave = false) { - var titleCategory = await repository.Query() + var titleCategory = await repository .FirstOrDefaultAsync(u => u.Id == id); if (titleCategory == null) return false; @@ -95,7 +95,7 @@ private async Task> if (editingId.HasValue) { - var titleExists = await repository.Query() + var titleExists = await repository .AnyAsync(n => n.Id == editingId.Value); if (!titleExists) { @@ -143,7 +143,7 @@ private async Task> validationResult.Message = "Name is too long. Max 100 characters."; return validationResult; } - var nameAlredInUse = await repository.Query() + var nameAlredInUse = await repository .AnyAsync(n => n.Name == input.Name && (!editingId.HasValue || n.Id != editingId)); if (nameAlredInUse) { diff --git a/Fin.Application/Titles/Services/TitleService.cs b/Fin.Application/Titles/Services/TitleService.cs index 0aed1c8..f037d35 100644 --- a/Fin.Application/Titles/Services/TitleService.cs +++ b/Fin.Application/Titles/Services/TitleService.cs @@ -124,7 +124,7 @@ public async Task> Delete(Guid i var validationResultDto = validationResult.ToValidationResult(); if (!validationResultDto.Success) return validationResultDto; - var title = await titleRepository.Query(tracking: true).FirstAsync(title => title.Id == id, cancellationToken); + var title = await titleRepository.FirstAsync(title => title.Id == id, cancellationToken); var titlesToReprocess = await updateHelpService.GetTitlesForReprocessing(title.WalletId, title.Date, title.Id, cancellationToken); await using (var scope = await unitOfWork.BeginTransactionAsync(cancellationToken)) diff --git a/Fin.Application/Titles/Validations/Deletes/TitleDeleteMustExistValidation.cs b/Fin.Application/Titles/Validations/Deletes/TitleDeleteMustExistValidation.cs index 2df9820..c3dd86e 100644 --- a/Fin.Application/Titles/Validations/Deletes/TitleDeleteMustExistValidation.cs +++ b/Fin.Application/Titles/Validations/Deletes/TitleDeleteMustExistValidation.cs @@ -12,7 +12,7 @@ public class TitleDeleteMustExistValidation(IRepository titleRepository): public async Task<ValidationPipelineOutput<TitleDeleteErrorCode>> ValidateAsync(Guid titleId, Guid? _, CancellationToken cancellationToken = default) { var validation = new ValidationPipelineOutput<TitleDeleteErrorCode>(); - var title = await titleRepository.Query(tracking: false).FirstOrDefaultAsync(t => t.Id == titleId, cancellationToken); + var title = await titleRepository.AsNoTracking().FirstOrDefaultAsync(t => t.Id == titleId, cancellationToken); return title == null ? validation.AddError(TitleDeleteErrorCode.TitleNotFound) : validation; } } \ No newline at end of file diff --git a/Fin.Application/Titles/Validations/UpdateOrCrestes/TitleInputDuplicatedValidation.cs b/Fin.Application/Titles/Validations/UpdateOrCrestes/TitleInputDuplicatedValidation.cs index a8d2124..e872444 100644 --- a/Fin.Application/Titles/Validations/UpdateOrCrestes/TitleInputDuplicatedValidation.cs +++ b/Fin.Application/Titles/Validations/UpdateOrCrestes/TitleInputDuplicatedValidation.cs @@ -14,7 +14,7 @@ public async Task<ValidationPipelineOutput<TitleCreateOrUpdateErrorCode>> Valida { var validation = new ValidationPipelineOutput<TitleCreateOrUpdateErrorCode>(); - var duplicateExists = await titleRepository.Query(tracking: false) + var duplicateExists = await titleRepository.AsNoTracking() .Where(t => t.Description == input.Description.Trim() && t.WalletId == input.WalletId && t.Date.Year == input.Date.Year diff --git a/Fin.Application/Titles/Validations/UpdateOrCrestes/TitleInputMustExistValidation.cs b/Fin.Application/Titles/Validations/UpdateOrCrestes/TitleInputMustExistValidation.cs index bfcffd9..ee3d270 100644 --- a/Fin.Application/Titles/Validations/UpdateOrCrestes/TitleInputMustExistValidation.cs +++ b/Fin.Application/Titles/Validations/UpdateOrCrestes/TitleInputMustExistValidation.cs @@ -15,7 +15,7 @@ public async Task<ValidationPipelineOutput<TitleCreateOrUpdateErrorCode>> Valida var validation = new ValidationPipelineOutput<TitleCreateOrUpdateErrorCode>(); if (!editingId.HasValue) return validation; - var title = await titleRepository.Query(tracking: false).FirstOrDefaultAsync(t => t.Id == editingId, cancellationToken); + var title = await titleRepository.AsNoTracking().FirstOrDefaultAsync(t => t.Id == editingId, cancellationToken); return title == null ? validation.AddError(TitleCreateOrUpdateErrorCode.TitleNotFound) : validation; } } \ No newline at end of file diff --git a/Fin.Application/Titles/Validations/UpdateOrCrestes/TitleInputWalletValidation.cs b/Fin.Application/Titles/Validations/UpdateOrCrestes/TitleInputWalletValidation.cs index c5f6f7b..cd14513 100644 --- a/Fin.Application/Titles/Validations/UpdateOrCrestes/TitleInputWalletValidation.cs +++ b/Fin.Application/Titles/Validations/UpdateOrCrestes/TitleInputWalletValidation.cs @@ -15,7 +15,7 @@ public async Task<ValidationPipelineOutput<TitleCreateOrUpdateErrorCode, List<Gu { var validation = new ValidationPipelineOutput<TitleCreateOrUpdateErrorCode, List<Guid>>(); - var wallet = await walletRepository.Query(tracking: false) + var wallet = await walletRepository.AsNoTracking() .FirstOrDefaultAsync(t => t.Id == input.WalletId, cancellationToken); if (wallet == null) diff --git a/Fin.Application/Users/Services/UserCreateService.cs b/Fin.Application/Users/Services/UserCreateService.cs index 6f3a449..94ee8ec 100644 --- a/Fin.Application/Users/Services/UserCreateService.cs +++ b/Fin.Application/Users/Services/UserCreateService.cs @@ -95,7 +95,7 @@ public async Task<ValidationResultDto<UserStartCreateOutput, UserStartCreateErro return GetResultWithError(UserStartCreateErrorCode.NotSamePassword, "Password confirmation do not match"); var encryptedEmail = _cryptoHelper.Encrypt(input.Email); - var emailAlreadyInUse = await _credentialRepository.Query().AnyAsync(c => c.EncryptedEmail == encryptedEmail); + var emailAlreadyInUse = await _credentialRepository.AnyAsync(c => c.EncryptedEmail == encryptedEmail); if (emailAlreadyInUse) return GetResultWithError(UserStartCreateErrorCode.EmailAlreadyInUse, "Email already in use"); @@ -211,7 +211,7 @@ public async Task<ValidationResultDto<UserDto>> CreateUser(string creationToken, public async Task<ValidationResultDto<UserDto>> CreateUser(string googleId, string email, UserUpdateOrCreateInput input) { var encryptedEmail = _cryptoHelper.Encrypt(email); - if (await _credentialRepository.Query().AnyAsync(c => c.EncryptedEmail == encryptedEmail)) + if (await _credentialRepository.AnyAsync(c => c.EncryptedEmail == encryptedEmail)) return new ValidationResultDto<UserDto> { Success = false, diff --git a/Fin.Application/Users/Services/UserDeleteService.cs b/Fin.Application/Users/Services/UserDeleteService.cs index a767b9b..b7ae556 100644 --- a/Fin.Application/Users/Services/UserDeleteService.cs +++ b/Fin.Application/Users/Services/UserDeleteService.cs @@ -56,11 +56,11 @@ public async Task<bool> RequestDeleteUser(CancellationToken cancellationToken = { var userId = ambientData.UserId; - var alreadyRequest = await userDeleteRequestRepo.Query(false) + var alreadyRequest = await userDeleteRequestRepo.AsNoTracking() .FirstOrDefaultAsync(u => u.UserId == userId && !u.Aborted, cancellationToken); if (alreadyRequest != null) return false; - var user = userRepo.Query().Include(u => u.Credential).FirstOrDefault(u => u.Id == userId); + var user = userRepo.Include(u => u.Credential).FirstOrDefault(u => u.Id == userId); if (user == null) return false; var userEmail = _cryptoHelper.Decrypt(user.Credential.EncryptedEmail); @@ -75,7 +75,7 @@ public async Task<bool> RequestDeleteUser(CancellationToken cancellationToken = public async Task<bool> EffectiveDeleteUsers(CancellationToken cancellationToken = default) { var today = DateOnly.FromDateTime(dateTimeProvider.UtcNow()); - var userIds = await userDeleteRequestRepo.Query() + var userIds = await userDeleteRequestRepo .Where(u => !u.Aborted && u.DeleteEffectivatedAt == today) .Select(u => u.UserId) .ToListAsync(cancellationToken); @@ -92,7 +92,7 @@ public async Task<bool> AbortDeleteUser(Guid userId, CancellationToken cancellat throw new SecurityException("You are not admin"); var now = dateTimeProvider.UtcNow(); - var deleteRequest = await userDeleteRequestRepo.Query() + var deleteRequest = await userDeleteRequestRepo .Include(u => u.User) .ThenInclude(u => u.Credential) .FirstOrDefaultAsync(u => u.UserId == userId && !u.Aborted, cancellationToken); @@ -107,7 +107,7 @@ public async Task<bool> AbortDeleteUser(Guid userId, CancellationToken cancellat public async Task<PagedOutput<UserDeleteRequestDto>> GetList(PagedFilteredAndSortedInput input, CancellationToken cancellationToken = default) { - return await userDeleteRequestRepo.Query(false) + return await userDeleteRequestRepo.AsNoTracking() .Include(u => u.User) .Include(u => u.UserAborted) .ApplyFilterAndSorter(input) diff --git a/Fin.Application/Users/Services/UserService.cs b/Fin.Application/Users/Services/UserService.cs index ee8a47e..4f25ffa 100644 --- a/Fin.Application/Users/Services/UserService.cs +++ b/Fin.Application/Users/Services/UserService.cs @@ -23,14 +23,14 @@ public async Task<UserDto> Get(Guid id) if (!ambientData.IsAdmin && id != ambientData.UserId) throw new SecurityException("You are not authorized to access this resource"); - var entity = await repository.Query() + var entity = await repository .FirstOrDefaultAsync(user => user.Id == id); return entity != null ? new UserDto(entity) : null; } public async Task<PagedOutput<UserDto>> GetList(PagedFilteredAndSortedInput input) { - return await repository.Query(false) + return await repository.AsNoTracking() .WhereIf(!ambientData.IsAdmin, user => user.Id == ambientData.UserId) .ApplyFilterAndSorter(input) .Select(user => new UserDto(user)) diff --git a/Fin.Application/Wallets/Services/WalletBalaceService.cs b/Fin.Application/Wallets/Services/WalletBalaceService.cs index c140ea3..2985588 100644 --- a/Fin.Application/Wallets/Services/WalletBalaceService.cs +++ b/Fin.Application/Wallets/Services/WalletBalaceService.cs @@ -30,7 +30,7 @@ IUnitOfWork unitOfWork { public async Task<decimal> GetBalanceAt(Guid walletId, DateTime dateTime, CancellationToken cancellationToken = default) { - var wallet = await walletRepository.Query(tracking: false) + var wallet = await walletRepository.AsNoTracking() .Include(wallet => wallet.Titles) .FirstAsync(wallet => wallet.Id == walletId, cancellationToken); return wallet.CalculateBalanceAt(dateTime); @@ -44,7 +44,7 @@ public Task<decimal> GetBalanceNow(Guid walletId, CancellationToken cancellation public async Task ReprocessBalance(Guid walletId, decimal newInitialBalance, bool autoSave = false, CancellationToken cancellationToken = default) { - var wallet = await walletRepository.Query() + var wallet = await walletRepository .Include(wallet => wallet.Titles) .FirstAsync(wallet => wallet.Id == walletId, cancellationToken); await ReprocessBalance(wallet.Titles.ToList(), newInitialBalance, autoSave, cancellationToken); @@ -73,7 +73,7 @@ public async Task ReprocessBalance(List<Title> titles, decimal initialBalance, b public async Task ReprocessBalanceFrom(Title fromTitle, bool autoSave = false, CancellationToken cancellationToken = default) { - var titles = await titleRepository.Query(tracking: true) + var titles = await titleRepository .Where(title => title.WalletId == fromTitle.WalletId) .Where(title => title.Date >= fromTitle.Date) .Where(title => title.Id > fromTitle.Id) @@ -83,7 +83,7 @@ public async Task ReprocessBalanceFrom(Title fromTitle, bool autoSave = false, C public async Task ReprocessBalanceFrom(Guid titleId, bool autoSave = false, CancellationToken cancellationToken = default) { - var title = await titleRepository.Query(tracking: false) + var title = await titleRepository.AsNoTracking() .FirstAsync(title => title.Id == titleId, cancellationToken); await ReprocessBalanceFrom(title, autoSave, cancellationToken); } diff --git a/Fin.Application/Wallets/Services/WalletService.cs b/Fin.Application/Wallets/Services/WalletService.cs index b08c2dd..07a8aeb 100644 --- a/Fin.Application/Wallets/Services/WalletService.cs +++ b/Fin.Application/Wallets/Services/WalletService.cs @@ -30,14 +30,14 @@ IDateTimeProvider dateTimeProvider { public async Task<WalletOutput> Get(Guid id) { - var entity = await repository.Query(false).Include(wallet => wallet.Titles).FirstOrDefaultAsync(n => n.Id == id); + var entity = await repository.AsNoTracking().Include(wallet => wallet.Titles).FirstOrDefaultAsync(n => n.Id == id); return entity != null ? new WalletOutput(entity, dateTimeProvider.UtcNow()) : null; } public async Task<PagedOutput<WalletOutput>> GetList(WalletGetListInput input) { var now = dateTimeProvider.UtcNow(); - return await repository.Query(false) + return await repository.AsNoTracking() .Include(wallet => wallet.Titles) .WhereIf(input.Inactivated.HasValue, n => n.Inactivated == input.Inactivated.Value) .OrderBy(m => m.Inactivated) @@ -63,7 +63,7 @@ public async Task<ValidationResultDto<bool, WalletCreateOrUpdateErrorCode>> Upda var validation = await validationService.ValidateInput<bool>(input, id); if (!validation.Success) return validation; - var wallet = await repository.Query().FirstAsync(u => u.Id == id); + var wallet = await repository.FirstAsync(u => u.Id == id); wallet.Update(input); // TODO reprocesses CurrentBalance await repository.UpdateAsync(wallet, autoSave); @@ -77,7 +77,7 @@ public async Task<ValidationResultDto<bool, WalletDeleteErrorCode>> Delete(Guid var validation = await validationService.ValidateDelete(id); if (!validation.Success) return validation; - var wallet = await repository.Query().FirstAsync(u => u.Id == id); + var wallet = await repository.FirstAsync(u => u.Id == id); await repository.DeleteAsync(wallet, autoSave); validation.Success = true; @@ -89,7 +89,7 @@ public async Task<ValidationResultDto<bool, WalletToggleInactiveErrorCode>> Togg var validation = await validationService.ValidateToggleInactive(id); if (!validation.Success) return validation; - var wallet = await repository.Query().FirstAsync(u => u.Id == id); + var wallet = await repository.FirstAsync(u => u.Id == id); wallet.ToggleInactivated(); await repository.UpdateAsync(wallet, autoSave); diff --git a/Fin.Application/Wallets/Services/WalletValidationService.cs b/Fin.Application/Wallets/Services/WalletValidationService.cs index bc2e75e..8e06ce6 100644 --- a/Fin.Application/Wallets/Services/WalletValidationService.cs +++ b/Fin.Application/Wallets/Services/WalletValidationService.cs @@ -31,7 +31,7 @@ public async Task<ValidationResultDto<bool, WalletToggleInactiveErrorCode>> Vali { var validationResult = new ValidationResultDto<bool, WalletToggleInactiveErrorCode>(); - var wallet = await walletRepository.Query(tracking: false).FirstOrDefaultAsync(n => n.Id == walletId); + var wallet = await walletRepository.AsNoTracking().FirstOrDefaultAsync(n => n.Id == walletId); if (wallet is null) { validationResult.ErrorCode = WalletToggleInactiveErrorCode.WalletNotFound; @@ -39,7 +39,7 @@ public async Task<ValidationResultDto<bool, WalletToggleInactiveErrorCode>> Vali return validationResult; } - var walletInUseByActivatedCreditCard = await creditCardRepository.Query().AnyAsync(n => n.DebitWalletId == walletId && !n.Inactivated); + var walletInUseByActivatedCreditCard = await creditCardRepository.AnyAsync(n => n.DebitWalletId == walletId && !n.Inactivated); if (walletInUseByActivatedCreditCard) { validationResult.ErrorCode = WalletToggleInactiveErrorCode.WalletInUseByActivatedCreditCards; @@ -55,15 +55,15 @@ public async Task<ValidationResultDto<bool, WalletDeleteErrorCode>> ValidateDele { var validationResult = new ValidationResultDto<bool, WalletDeleteErrorCode>(); - var walletExists = await walletRepository.Query().AnyAsync(n => n.Id == walletId); + var walletExists = await walletRepository.AnyAsync(n => n.Id == walletId); if (!walletExists) { validationResult.ErrorCode = WalletDeleteErrorCode.WalletNotFound; return validationResult; } - var walletInUseByCreditCard = await creditCardRepository.Query().AnyAsync(n => n.DebitWalletId == walletId); - var walletInUseByTitle = await titleRepository.Query().AnyAsync(n => n.WalletId == walletId); + var walletInUseByCreditCard = await creditCardRepository.AnyAsync(n => n.DebitWalletId == walletId); + var walletInUseByTitle = await titleRepository.AnyAsync(n => n.WalletId == walletId); return walletInUseByTitle switch { @@ -83,7 +83,7 @@ public async Task<ValidationResultDto<T, WalletCreateOrUpdateErrorCode>> Validat if (editingId.HasValue) { - var walletExists = await walletRepository.Query() + var walletExists = await walletRepository .AnyAsync(n => n.Id == editingId.Value); if (!walletExists) { @@ -135,7 +135,7 @@ public async Task<ValidationResultDto<T, WalletCreateOrUpdateErrorCode>> Validat return validationResult; } - var nameAlredInUse = await walletRepository.Query() + var nameAlredInUse = await walletRepository .AnyAsync(n => n.Name.ToLower() == input.Name.ToLower() && (!editingId.HasValue || n.Id != editingId)); if (nameAlredInUse) { diff --git a/Fin.Infrastructure/Authentications/ActivatedUserMiddleware.cs b/Fin.Infrastructure/Authentications/ActivatedUserMiddleware.cs index 26152c6..81fc2c1 100644 --- a/Fin.Infrastructure/Authentications/ActivatedUserMiddleware.cs +++ b/Fin.Infrastructure/Authentications/ActivatedUserMiddleware.cs @@ -13,7 +13,7 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next) { if (ambientData.IsLogged) { - var userExistAndActivated = await userRepo.Query(false).AnyAsync(u => u.IsActivity && u.Id == ambientData.UserId); + var userExistAndActivated = await userRepo.AsNoTracking().AnyAsync(u => u.IsActivity && u.Id == ambientData.UserId); if (!userExistAndActivated) { diff --git a/Fin.Infrastructure/Authentications/AuthenticationTokenService.cs b/Fin.Infrastructure/Authentications/AuthenticationTokenService.cs index f7d9e14..2948bd3 100644 --- a/Fin.Infrastructure/Authentications/AuthenticationTokenService.cs +++ b/Fin.Infrastructure/Authentications/AuthenticationTokenService.cs @@ -61,7 +61,7 @@ public async Task<LoginOutput> Login(LoginInput input) var encryptedEmail = _cryptoHelper.Encrypt(input.Email); var encryptedPassword = _cryptoHelper.Encrypt(input.Password); - var credential = await _credentialRepository.Query() + var credential = await _credentialRepository .Include(c => c.User) .ThenInclude(c => c.Tenants) .FirstOrDefaultAsync(c => c.EncryptedEmail == encryptedEmail); @@ -89,7 +89,7 @@ public async Task<LoginOutput> RefreshToken(string refreshToken) if (userId == Guid.Empty) return new LoginOutput { ErrorCode = LoginErrorCode.InvalidRefreshToken }; - var user = await _userRepository.Query(false) + var user = await _userRepository.AsNoTracking() .Include(c => c.Tenants) .FirstOrDefaultAsync(c => c.Id == userId); if (user == null) diff --git a/Fin.Infrastructure/Database/Repositories/IRepository.cs b/Fin.Infrastructure/Database/Repositories/IRepository.cs index 27e8db0..5881e02 100644 --- a/Fin.Infrastructure/Database/Repositories/IRepository.cs +++ b/Fin.Infrastructure/Database/Repositories/IRepository.cs @@ -2,8 +2,6 @@ public interface IRepository<T>: IQueryable<T> where T : class { - [Obsolete("Unnecessary, now you can query direct on repository")] - IQueryable<T> Query(bool tracking = true); Task AddAsync(T entity, bool autoSave = false, CancellationToken cancellationToken = default); Task AddAsync(T entity, CancellationToken cancellationToken); Task AddRangeAsync(IEnumerable<T> entities, bool autoSave = false, CancellationToken cancellationToken = default); diff --git a/Fin.Infrastructure/Database/Repositories/Repository.cs b/Fin.Infrastructure/Database/Repositories/Repository.cs index 1dbd5b1..adfc127 100644 --- a/Fin.Infrastructure/Database/Repositories/Repository.cs +++ b/Fin.Infrastructure/Database/Repositories/Repository.cs @@ -32,11 +32,6 @@ public Repository(FinDbContext context) public async Task<T?> FindAsync(object[] keyValues, CancellationToken cancellationToken = default) => await _dbSet.FindAsync(keyValues, cancellationToken); - public IQueryable<T> Query(bool tracking = true) - { - return tracking ? _dbSet : _dbSet.AsNoTracking(); - } - public async Task AddAsync(T entity, bool autoSave = false, CancellationToken cancellationToken = default) { await _dbSet.AddAsync(entity, cancellationToken); diff --git a/Fin.Infrastructure/Seeders/Seeders/DefaultMenusSeeder.cs b/Fin.Infrastructure/Seeders/Seeders/DefaultMenusSeeder.cs index 92632fd..4230be3 100644 --- a/Fin.Infrastructure/Seeders/Seeders/DefaultMenusSeeder.cs +++ b/Fin.Infrastructure/Seeders/Seeders/DefaultMenusSeeder.cs @@ -119,7 +119,7 @@ public async Task SeedAsync() } }; var defaultMenusIds = defaultMenus.Select(x => x.Id).ToList(); - var menusIdsAlreadyCreated = await menusRepository.Query(false) + var menusIdsAlreadyCreated = await menusRepository.AsNoTracking() .Where(x => defaultMenusIds.Contains(x.Id)) .Select(x => x.Id).ToListAsync(); var menusToCreate = defaultMenus.Where(x => !menusIdsAlreadyCreated.Contains(x.Id)).ToList(); diff --git a/Fin.Test/Authentications/AuthenticationServiceTest.cs b/Fin.Test/Authentications/AuthenticationServiceTest.cs index 693e132..d901585 100644 --- a/Fin.Test/Authentications/AuthenticationServiceTest.cs +++ b/Fin.Test/Authentications/AuthenticationServiceTest.cs @@ -55,7 +55,7 @@ public async Task SendResetPasswordEmail_ExistEmail() await service.SendResetPasswordEmail(intput); // Assert - credential = await resources.CredentialRepository.Query(false).FirstAsync(); + credential = await resources.CredentialRepository.AsNoTracking().FirstAsync(); credential.ResetToken.Should().NotBeNullOrEmpty(); resources.FakeCache @@ -90,7 +90,7 @@ public async Task SendResetPasswordEmail_InactivatedUser() await service.SendResetPasswordEmail(intput); // Assert - credential = await resources.CredentialRepository.Query(false).FirstAsync(); + credential = await resources.CredentialRepository.AsNoTracking().FirstAsync(); credential.ResetToken.Should().BeNullOrEmpty(); resources.FakeCache @@ -326,7 +326,7 @@ public async Task ResetPassword_Success() .Verify(c => c.GetAsync<Guid>(It.Is<string>(f => f.Contains(input.ResetToken))), Times.Once);; var encryptedPassword = resources.CryptoHelper.Encrypt(input.Password); - var dbCredential = await resources.CredentialRepository.Query(false).FirstAsync(); + var dbCredential = await resources.CredentialRepository.AsNoTracking().FirstAsync(); dbCredential.ResetToken.Should().BeNullOrEmpty(); dbCredential.EncryptedPassword.Should().Be(encryptedPassword); } @@ -585,7 +585,7 @@ public async Task LoginOrSingInWithGoogle_Success_Not_CreatingUser_NeverGoogle() resources.FakeTokenService .Verify(u => u.GenerateTokenAsync(It.Is<UserDto>(u => u.Id == user.Id)), Times.Once); - var dbCredential = await resources.CredentialRepository.Query(false).FirstAsync(); + var dbCredential = await resources.CredentialRepository.AsNoTracking().FirstAsync(); dbCredential.GoogleId.Should().Be(input.GoogleId); } @@ -643,7 +643,7 @@ public async Task LoginOrSingInWithGoogle_Success_Not_CreatingUser_AlreadyGoogle resources.FakeTokenService .Verify(u => u.GenerateTokenAsync(It.Is<UserDto>(u => u.Id == user.Id)), Times.Once); - var dbCredential = await resources.CredentialRepository.Query(false).FirstAsync(); + var dbCredential = await resources.CredentialRepository.AsNoTracking().FirstAsync(); dbCredential.GoogleId.Should().Be(input.GoogleId); } diff --git a/Fin.Test/Authentications/AuthenticationTokenServiceTest.cs b/Fin.Test/Authentications/AuthenticationTokenServiceTest.cs index d093302..1128d0a 100644 --- a/Fin.Test/Authentications/AuthenticationTokenServiceTest.cs +++ b/Fin.Test/Authentications/AuthenticationTokenServiceTest.cs @@ -142,7 +142,7 @@ public async Task Login_Fail(LoginErrorCode code, string credentialEmail, string result.Token.Should().BeNull(); result.RefreshToken.Should().BeNull(); - var credentialFromDb = await resources.CredentialRepository.Query().FirstOrDefaultAsync(); + var credentialFromDb = await resources.CredentialRepository.FirstOrDefaultAsync(); if (code == LoginErrorCode.InvalidPassword) credentialFromDb.FailLoginAttempts.Should().Be(previusesAttempts + 1); } diff --git a/Fin.Test/CreditCards/Services/CreditCardServiceTest.cs b/Fin.Test/CreditCards/Services/CreditCardServiceTest.cs index 49e2896..139f2f6 100644 --- a/Fin.Test/CreditCards/Services/CreditCardServiceTest.cs +++ b/Fin.Test/CreditCards/Services/CreditCardServiceTest.cs @@ -309,7 +309,7 @@ public async Task Create_ShouldReturnSuccessAndCreditCard_WhenInputIsValid() result.Success.Should().BeTrue(); result.Data.Should().NotBeNull(); - var dbCreditCard = await resources.CreditCardRepository.Query(false) + var dbCreditCard = await resources.CreditCardRepository.AsNoTracking() .FirstOrDefaultAsync(a => a.Id == result.Data.Id); dbCreditCard.Should().NotBeNull(); dbCreditCard.Name.Should().Be(input.Name); @@ -341,7 +341,7 @@ public async Task Create_ShouldReturnFailure_WhenValidationFails() result.ErrorCode.Should().Be(CreditCardCreateOrUpdateErrorCode.NameIsRequired); result.Data.Should().BeNull(); - (await resources.CreditCardRepository.Query(false).CountAsync()).Should().Be(0); + (await resources.CreditCardRepository.AsNoTracking().CountAsync()).Should().Be(0); } #endregion @@ -384,7 +384,7 @@ public async Task Update_ShouldReturnSuccess_WhenInputIsValid() result.Success.Should().BeTrue(); result.Data.Should().BeTrue(); - var dbCreditCard = await resources.CreditCardRepository.Query(false).FirstAsync(a => a.Id == creditCard.Id); + var dbCreditCard = await resources.CreditCardRepository.AsNoTracking().FirstAsync(a => a.Id == creditCard.Id); dbCreditCard.Name.Should().Be(updatedInput.Name); dbCreditCard.Limit.Should().Be(updatedInput.Limit); dbCreditCard.DueDay.Should().Be(updatedInput.DueDay); @@ -424,7 +424,7 @@ public async Task Update_ShouldReturnFailure_WhenValidationFails() result.ErrorCode.Should().Be(CreditCardCreateOrUpdateErrorCode.CardBrandNotFound); result.Data.Should().BeFalse(); - var dbCreditCard = await resources.CreditCardRepository.Query(false).FirstAsync(a => a.Id == creditCard.Id); + var dbCreditCard = await resources.CreditCardRepository.AsNoTracking().FirstAsync(a => a.Id == creditCard.Id); dbCreditCard.Name.Should().Be(originalInput.Name); } @@ -450,7 +450,7 @@ public async Task Delete_ShouldReturnSuccess_WhenValid() result.Should().NotBeNull(); result.Success.Should().BeTrue(); - (await resources.CreditCardRepository.Query(false).FirstOrDefaultAsync(a => a.Id == creditCard.Id)).Should() + (await resources.CreditCardRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == creditCard.Id)).Should() .BeNull(); } @@ -478,7 +478,7 @@ public async Task Delete_ShouldReturnFailure_WhenValidationFails() result.Should().NotBeNull(); result.Success.Should().BeFalse(); result.ErrorCode.Should().Be(CreditCardDeleteErrorCode.CreditCardInUse); - (await resources.CreditCardRepository.Query(false).FirstOrDefaultAsync(a => a.Id == creditCard.Id)).Should() + (await resources.CreditCardRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == creditCard.Id)).Should() .NotBeNull(); } @@ -507,7 +507,7 @@ public async Task ToggleInactive_ShouldReturnSuccess_WhenValidAndDeactivate() result.Should().NotBeNull(); result.Success.Should().BeTrue(); result.Data.Should().BeTrue(); - var dbCreditCard = await resources.CreditCardRepository.Query(false).FirstAsync(a => a.Id == creditCard.Id); + var dbCreditCard = await resources.CreditCardRepository.AsNoTracking().FirstAsync(a => a.Id == creditCard.Id); dbCreditCard.Inactivated.Should().BeTrue(); } @@ -537,7 +537,7 @@ public async Task ToggleInactive_ShouldReturnFailure_WhenValidationFails() result.Should().NotBeNull(); result.Success.Should().BeFalse(); result.ErrorCode.Should().Be(CreditCardToggleInactiveErrorCode.CreditCardNotFound); - var dbCreditCard = await resources.CreditCardRepository.Query(false).FirstAsync(a => a.Id == creditCard.Id); + var dbCreditCard = await resources.CreditCardRepository.AsNoTracking().FirstAsync(a => a.Id == creditCard.Id); dbCreditCard.Inactivated.Should().BeFalse(); } diff --git a/Fin.Test/FinancialInstitutions/Services/FinancialInstitutionServiceTest.cs b/Fin.Test/FinancialInstitutions/Services/FinancialInstitutionServiceTest.cs index f0b5a02..46306cd 100644 --- a/Fin.Test/FinancialInstitutions/Services/FinancialInstitutionServiceTest.cs +++ b/Fin.Test/FinancialInstitutions/Services/FinancialInstitutionServiceTest.cs @@ -160,7 +160,7 @@ public async Task Create_ShouldAddInstitutionAndReturnOutput() // Assert result.Should().NotBeNull(); result.Name.Should().Be(input.Name); - var dbInstitution = await resources.FinancialInstitutionRepository.Query(false).FirstOrDefaultAsync(a => a.Id == result.Id); + var dbInstitution = await resources.FinancialInstitutionRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == result.Id); dbInstitution.Should().NotBeNull(); dbInstitution.Name.Should().Be(input.Name); } @@ -238,7 +238,7 @@ public async Task Update_ShouldModifyInstitutionAndReturnTrue() // Assert result.Should().BeTrue(); - var dbInstitution = await resources.FinancialInstitutionRepository.Query(false).FirstAsync(f => f.Id == institution.Id); + var dbInstitution = await resources.FinancialInstitutionRepository.AsNoTracking().FirstAsync(f => f.Id == institution.Id); dbInstitution.Name.Should().Be(input.Name); dbInstitution.Code.Should().Be("999"); } @@ -300,7 +300,7 @@ public async Task Update_ShouldSucceed_WhenNameIsUnchanged() // Assert result.Should().BeTrue(); - var dbInstitution = await resources.FinancialInstitutionRepository.Query(false).FirstAsync(f => f.Id == institution.Id); + var dbInstitution = await resources.FinancialInstitutionRepository.AsNoTracking().FirstAsync(f => f.Id == institution.Id); dbInstitution.Color.Should().Be("#AAAAAA"); } @@ -322,7 +322,7 @@ public async Task Delete_ShouldReturnTrue_WhenInstitutionDeleted() // Assert result.Should().BeTrue(); - (await resources.FinancialInstitutionRepository.Query(false).FirstOrDefaultAsync(f => f.Id == institution.Id)).Should().BeNull(); + (await resources.FinancialInstitutionRepository.AsNoTracking().FirstOrDefaultAsync(f => f.Id == institution.Id)).Should().BeNull(); } [Fact] @@ -356,7 +356,7 @@ public async Task Delete_ShouldReturnFalse_WhenInstitutionHasWallets() // Assert result.Should().BeFalse(); - (await resources.FinancialInstitutionRepository.Query(false).FirstOrDefaultAsync(f => f.Id == institution.Id)).Should().NotBeNull(); + (await resources.FinancialInstitutionRepository.AsNoTracking().FirstOrDefaultAsync(f => f.Id == institution.Id)).Should().NotBeNull(); } #endregion @@ -378,7 +378,7 @@ public async Task ToggleInactive_ShouldReturnTrue_AndDeactivate() // Assert result.Should().BeTrue(); - var dbInstitution = await resources.FinancialInstitutionRepository.Query(false).FirstAsync(f => f.Id == institution.Id); + var dbInstitution = await resources.FinancialInstitutionRepository.AsNoTracking().FirstAsync(f => f.Id == institution.Id); dbInstitution.Inactive.Should().BeTrue(); } @@ -398,7 +398,7 @@ public async Task ToggleInactive_ShouldReturnTrue_AndReactivate() // Assert result.Should().BeTrue(); - var dbInstitution = await resources.FinancialInstitutionRepository.Query(false).FirstAsync(f => f.Id == institution.Id); + var dbInstitution = await resources.FinancialInstitutionRepository.AsNoTracking().FirstAsync(f => f.Id == institution.Id); dbInstitution.Inactive.Should().BeFalse(); } @@ -434,7 +434,7 @@ public async Task ToggleInactive_ShouldReturnFalse_WhenHasActiveWallets() // Assert result.Should().BeFalse(); // Verify institution status did not change - var dbInstitution = await resources.FinancialInstitutionRepository.Query(false).FirstAsync(f => f.Id == institution.Id); + var dbInstitution = await resources.FinancialInstitutionRepository.AsNoTracking().FirstAsync(f => f.Id == institution.Id); dbInstitution.Inactive.Should().BeFalse(); } @@ -458,7 +458,7 @@ public async Task ToggleInactive_ShouldReturnTrue_WhenHasOnlyInactiveWallets() // Assert result.Should().BeTrue(); // Verify institution status changed - var dbInstitution = await resources.FinancialInstitutionRepository.Query(false).FirstAsync(f => f.Id == institution.Id); + var dbInstitution = await resources.FinancialInstitutionRepository.AsNoTracking().FirstAsync(f => f.Id == institution.Id); dbInstitution.Inactive.Should().BeTrue(); } diff --git a/Fin.Test/Menus/Services/NotificationServiceTest.cs b/Fin.Test/Menus/Services/NotificationServiceTest.cs index 9cc101d..c9794e1 100644 --- a/Fin.Test/Menus/Services/NotificationServiceTest.cs +++ b/Fin.Test/Menus/Services/NotificationServiceTest.cs @@ -96,7 +96,7 @@ public async Task Create() // Assert result.Should().NotBeNull(); - var dbMenu = await resources.MenuRepository.Query(false).FirstOrDefaultAsync(a => a.Id == result.Id); + var dbMenu = await resources.MenuRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == result.Id); dbMenu.Should().NotBeNull(); dbMenu.Name.Should().Be(input.Name); dbMenu.FrontRoute.Should().Be(input.FrontRoute); @@ -200,7 +200,7 @@ public async Task Update_ShouldReturnTrue() // Assert result.Should().BeTrue(); - var dbMenu = await resources.MenuRepository.Query(false).FirstOrDefaultAsync(a => a.Id == menu.Id); + var dbMenu = await resources.MenuRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == menu.Id); dbMenu.Should().NotBeNull(); dbMenu.Name.Should().Be(input.Name); dbMenu.FrontRoute.Should().Be(input.FrontRoute); @@ -298,7 +298,7 @@ public async Task Delete_ShoulReturnTrue() // Assert result.Should().BeTrue(); - (await resources.MenuRepository.Query(false).FirstOrDefaultAsync(a => a.Id == menu.Id)).Should().BeNull(); + (await resources.MenuRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == menu.Id)).Should().BeNull(); } #endregion diff --git a/Fin.Test/Notifications/Services/NotificationDeliveryServiceTest.cs b/Fin.Test/Notifications/Services/NotificationDeliveryServiceTest.cs index f3ae35b..0b9bcfb 100644 --- a/Fin.Test/Notifications/Services/NotificationDeliveryServiceTest.cs +++ b/Fin.Test/Notifications/Services/NotificationDeliveryServiceTest.cs @@ -89,7 +89,7 @@ public async Task SendNotification_ShouldSendToHub_ForSnackWay() // Assert resources.FakeClientProxy.Verify(c => c.SendCoreAsync("ReceiveNotification", It.Is<object[]>(o => o[0] == notifyDto), default), Times.Once); - var dbDelivery = await resources.DeliveryRepository.Query(false).FirstOrDefaultAsync(a => a.NotificationId == delivery.NotificationId); + var dbDelivery = await resources.DeliveryRepository.AsNoTracking().FirstOrDefaultAsync(a => a.NotificationId == delivery.NotificationId); dbDelivery.Delivery.Should().BeTrue(); } @@ -130,7 +130,7 @@ public async Task SendNotification_ShouldSendPush_AndRemoveInvalidTokens() // Assert resources.FakeFirebaseNotification.Verify(f => f.SendPushNotificationAsync(It.Is<List<Message>>(l => l.Count == 2)), Times.Once); - var dbSettings = await resources.UserSettingsRepository.Query(false).FirstAsync(s => s.UserId == userId); + var dbSettings = await resources.UserSettingsRepository.AsNoTracking().FirstAsync(s => s.UserId == userId); dbSettings.FirebaseTokens.Should().HaveCount(1); dbSettings.FirebaseTokens.Should().Contain(token1); dbSettings.FirebaseTokens.Should().NotContain(token2); @@ -222,7 +222,7 @@ public async Task MarkAsVisualized_ShouldUpdateAndReturnTrue_WhenFound() // Assert result.Should().BeTrue(); - var dbDelivery = await resources.DeliveryRepository.Query(false).FirstAsync(s => s.NotificationId == delivery.NotificationId); + var dbDelivery = await resources.DeliveryRepository.AsNoTracking().FirstAsync(s => s.NotificationId == delivery.NotificationId); dbDelivery.Visualized.Should().BeTrue(); } @@ -264,7 +264,7 @@ public async Task GetUnvisualizedNotifications_ShouldReturnActiveAndMarkDelivery result.Should().HaveCount(1); result[0].NotificationId.Should().Be(activeNotification.Id); - var dbDelivery = await resources.DeliveryRepository.Query(false).FirstAsync(d => d.NotificationId == activeDelivery.NotificationId); + var dbDelivery = await resources.DeliveryRepository.AsNoTracking().FirstAsync(d => d.NotificationId == activeDelivery.NotificationId); dbDelivery.Delivery.Should().BeTrue(); } diff --git a/Fin.Test/Notifications/Services/NotificationServiceTest.cs b/Fin.Test/Notifications/Services/NotificationServiceTest.cs index 8657cbb..bd65aba 100644 --- a/Fin.Test/Notifications/Services/NotificationServiceTest.cs +++ b/Fin.Test/Notifications/Services/NotificationServiceTest.cs @@ -97,7 +97,7 @@ public async Task Create_ShouldSchedule_WhenDateIsForToday() // Assert result.Should().NotBeNull(); - var dbNotification = await resources.NotificationRepository.Query(false).FirstOrDefaultAsync(a => a.Id == result.Id); + var dbNotification = await resources.NotificationRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == result.Id); dbNotification.Should().NotBeNull(); resources.FakeSchedulerService @@ -124,7 +124,7 @@ public async Task Create_ShouldNotSchedule_WhenDateIsNotForToday() // Assert result.Should().NotBeNull(); - var dbNotification = await resources.NotificationRepository.Query(false).FirstOrDefaultAsync(a => a.Id == result.Id); + var dbNotification = await resources.NotificationRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == result.Id); dbNotification.Should().NotBeNull(); resources.FakeSchedulerService @@ -274,7 +274,7 @@ public async Task Delete_ShouldUnschedule_WhenDateIsForToday() // Assert result.Should().BeTrue(); - (await resources.NotificationRepository.Query(false).FirstOrDefaultAsync(a => a.Id == notification.Id)).Should().BeNull(); + (await resources.NotificationRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == notification.Id)).Should().BeNull(); resources.FakeSchedulerService .Verify(s => s.UnscheduleNotification(notification.Id, It.IsAny<List<Guid>>(), It.IsAny<CancellationToken>()), Times.Once); } @@ -295,7 +295,7 @@ public async Task Delete_ShouldNotUnschedule_WhenDateIsNotForToday() // Assert result.Should().BeTrue(); - (await resources.NotificationRepository.Query(false).FirstOrDefaultAsync(a => a.Id == notification.Id)).Should().BeNull(); + (await resources.NotificationRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == notification.Id)).Should().BeNull(); resources.FakeSchedulerService .Verify(s => s.UnscheduleNotification(It.IsAny<Guid>(), It.IsAny<List<Guid>>(), It.IsAny<CancellationToken>()), Times.Never); } diff --git a/Fin.Test/Notifications/Services/UserNotificationSettingServiceTest.cs b/Fin.Test/Notifications/Services/UserNotificationSettingServiceTest.cs index 2b57e7e..b39244a 100644 --- a/Fin.Test/Notifications/Services/UserNotificationSettingServiceTest.cs +++ b/Fin.Test/Notifications/Services/UserNotificationSettingServiceTest.cs @@ -91,7 +91,7 @@ public async Task UpdateByCurrentUser_ShouldUpdateAndReturnTrue_WhenExists() // Assert result.Should().BeTrue(); - var dbSettings = await resources.Repository.Query(false).FirstAsync(s => s.UserId == userId); + var dbSettings = await resources.Repository.AsNoTracking().FirstAsync(s => s.UserId == userId); dbSettings.Enabled.Should().Be(true); dbSettings.AllowedWays.Should().BeEquivalentTo(input.AllowedWays); } @@ -136,7 +136,7 @@ public async Task AddFirebaseToken_ShouldAddToken_WhenTokenIsNew() // Assert result.Should().BeTrue(); - var dbSettings = await resources.Repository.Query(false).FirstAsync(s => s.UserId == userId); + var dbSettings = await resources.Repository.AsNoTracking().FirstAsync(s => s.UserId == userId); dbSettings.FirebaseTokens.Should().Contain(newToken); dbSettings.FirebaseTokens.Should().HaveCount(1); } @@ -163,7 +163,7 @@ public async Task AddFirebaseToken_ShouldNotAddDuplicate_WhenTokenExists() // Assert result.Should().BeTrue(); - var dbSettings = await resources.Repository.Query(false).FirstAsync(s => s.UserId == userId); + var dbSettings = await resources.Repository.AsNoTracking().FirstAsync(s => s.UserId == userId); dbSettings.FirebaseTokens.Should().Contain(existingToken); dbSettings.FirebaseTokens.Should().HaveCount(1); } diff --git a/Fin.Test/Notifications/Services/UserRememberUseSchedulerServiceTest.cs b/Fin.Test/Notifications/Services/UserRememberUseSchedulerServiceTest.cs index b9d7ddd..2f4fcb2 100644 --- a/Fin.Test/Notifications/Services/UserRememberUseSchedulerServiceTest.cs +++ b/Fin.Test/Notifications/Services/UserRememberUseSchedulerServiceTest.cs @@ -62,7 +62,7 @@ public async Task ScheduleTodayNotification_ShouldCreateNotifications_OnlyForMat await service.ScheduleTodayNotification(true); // Assert - var createdNotifications = await resources.NotificationRepository.Query(false).Include(e => e.UserDeliveries).ToListAsync(); + var createdNotifications = await resources.NotificationRepository.AsNoTracking().Include(e => e.UserDeliveries).ToListAsync(); // Only settings for User 1 and User 3 should have resulted in a new notification. createdNotifications.Should().HaveCount(2); diff --git a/Fin.Test/Notifications/Services/UserRememberUseSettingsService.cs b/Fin.Test/Notifications/Services/UserRememberUseSettingsService.cs index 1435b3f..b9390ec 100644 --- a/Fin.Test/Notifications/Services/UserRememberUseSettingsService.cs +++ b/Fin.Test/Notifications/Services/UserRememberUseSettingsService.cs @@ -91,7 +91,7 @@ public async Task UpdateByCurrentUser_ShouldUpdateAndReturnTrue_WhenExists() // Assert result.Should().BeTrue(); - var dbSettings = await resources.Repository.Query(false).FirstAsync(s => s.UserId == currentUserId); + var dbSettings = await resources.Repository.AsNoTracking().FirstAsync(s => s.UserId == currentUserId); dbSettings.NotifyOn.Should().Be(input.NotifyOn); } diff --git a/Fin.Test/TitleCategories/Services/TitleCategoryServiceTest.cs b/Fin.Test/TitleCategories/Services/TitleCategoryServiceTest.cs index 6549b05..6319c00 100644 --- a/Fin.Test/TitleCategories/Services/TitleCategoryServiceTest.cs +++ b/Fin.Test/TitleCategories/Services/TitleCategoryServiceTest.cs @@ -154,7 +154,7 @@ public async Task Create_ShouldReturnSuccessAndTitleCategory_WhenInputIsValid() result.Success.Should().BeTrue(); result.Data.Should().NotBeNull(); - var dbTitleCategory = await resources.TitleCategoryRepository.Query(false).FirstOrDefaultAsync(a => a.Id == result.Data.Id); + var dbTitleCategory = await resources.TitleCategoryRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == result.Data.Id); dbTitleCategory.Should().NotBeNull(); dbTitleCategory.Name.Should().Be(input.Name); dbTitleCategory.Color.Should().Be(input.Color); @@ -302,7 +302,7 @@ public async Task Update_ShouldReturnSuccessAndTrue() result.Success.Should().BeTrue(); result.Data.Should().BeTrue(); - var dbTitleCategory = await resources.TitleCategoryRepository.Query(false).FirstAsync(a => a.Id == titleCategory.Id); + var dbTitleCategory = await resources.TitleCategoryRepository.AsNoTracking().FirstAsync(a => a.Id == titleCategory.Id); dbTitleCategory.Name.Should().Be(input.Name); dbTitleCategory.Color.Should().Be(input.Color); dbTitleCategory.Icon.Should().Be(input.Icon); @@ -437,7 +437,7 @@ public async Task Delete_ShouldReturnTrue() // Assert result.Should().BeTrue(); - (await resources.TitleCategoryRepository.Query(false).FirstOrDefaultAsync(a => a.Id == titleCategory.Id)).Should().BeNull(); + (await resources.TitleCategoryRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == titleCategory.Id)).Should().BeNull(); } #endregion @@ -474,7 +474,7 @@ public async Task ToggleInactive_ShouldDeactivate() // Assert result.Should().BeTrue(); - var dbTitleCategory = await resources.TitleCategoryRepository.Query(false).FirstOrDefaultAsync(a => a.Id == titleCategory.Id); + var dbTitleCategory = await resources.TitleCategoryRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == titleCategory.Id); dbTitleCategory.Should().NotBeNull(); dbTitleCategory.Inactivated.Should().BeTrue(); } @@ -496,7 +496,7 @@ public async Task ToggleInactive_ShouldReactivate() // Assert result.Should().BeTrue(); - var dbTitleCategory = await resources.TitleCategoryRepository.Query(false).FirstOrDefaultAsync(a => a.Id == titleCategory.Id); + var dbTitleCategory = await resources.TitleCategoryRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == titleCategory.Id); dbTitleCategory.Should().NotBeNull(); dbTitleCategory.Inactivated.Should().BeFalse(); } diff --git a/Fin.Test/Users/UserCreateServiceTest.cs b/Fin.Test/Users/UserCreateServiceTest.cs index f297443..7293a8e 100644 --- a/Fin.Test/Users/UserCreateServiceTest.cs +++ b/Fin.Test/Users/UserCreateServiceTest.cs @@ -558,11 +558,11 @@ public async Task CreateUser_Success() var result = await service.CreateUser(process.Token, input); // Assert - var tenant = await resources.TenantRepository.Query(false).Include(t => t.Users).FirstAsync(); - var credential = await resources.CredentialRepository.Query(false).FirstAsync(); - var user = await resources.UserRepository.Query(false).FirstAsync(); - var notificationSettings = await resources.UserNotificationSettings.Query(false).FirstAsync(); - var userRemember = await resources.UserRememberUseSettings.Query(false).FirstAsync(); + var tenant = await resources.TenantRepository.AsNoTracking().Include(t => t.Users).FirstAsync(); + var credential = await resources.CredentialRepository.AsNoTracking().FirstAsync(); + var user = await resources.UserRepository.AsNoTracking().FirstAsync(); + var notificationSettings = await resources.UserNotificationSettings.AsNoTracking().FirstAsync(); + var userRemember = await resources.UserRememberUseSettings.AsNoTracking().FirstAsync(); credential.EncryptedEmail.Should().Be(process.EncryptedEmail); credential.EncryptedPassword.Should().Be(process.EncryptedPassword); @@ -636,11 +636,11 @@ public async Task CreateUser_WithGoogle_Success() var result = await service.CreateUser(googleId, email, input); // Assert - var tenant = await resources.TenantRepository.Query(false).Include(t => t.Users).FirstAsync(); - var credential = await resources.CredentialRepository.Query(false).FirstAsync(); - var user = await resources.UserRepository.Query(false).FirstAsync(); - var notificationSettings = await resources.UserNotificationSettings.Query(false).FirstAsync(); - var userRemember = await resources.UserRememberUseSettings.Query(false).FirstAsync(); + var tenant = await resources.TenantRepository.AsNoTracking().Include(t => t.Users).FirstAsync(); + var credential = await resources.CredentialRepository.AsNoTracking().FirstAsync(); + var user = await resources.UserRepository.AsNoTracking().FirstAsync(); + var notificationSettings = await resources.UserNotificationSettings.AsNoTracking().FirstAsync(); + var userRemember = await resources.UserRememberUseSettings.AsNoTracking().FirstAsync(); credential.EncryptedEmail.Should().Be(encryptedEmail); credential.EncryptedPassword.Should().BeNullOrEmpty(); diff --git a/Fin.Test/Wallets/Services/WalletServiceTest.cs b/Fin.Test/Wallets/Services/WalletServiceTest.cs index b3fac9f..f8cd443 100644 --- a/Fin.Test/Wallets/Services/WalletServiceTest.cs +++ b/Fin.Test/Wallets/Services/WalletServiceTest.cs @@ -134,7 +134,7 @@ public async Task Create_ShouldReturnSuccessAndWallet_WhenInputIsValid() result.Success.Should().BeTrue(); result.Data.Should().NotBeNull(); - var dbWallet = await resources.WalletRepository.Query(false).FirstOrDefaultAsync(a => a.Id == result.Data.Id); + var dbWallet = await resources.WalletRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == result.Data.Id); dbWallet.Should().NotBeNull(); dbWallet.Name.Should().Be(input.Name); dbWallet.InitialBalance.Should().Be(input.InitialBalance); @@ -165,7 +165,7 @@ public async Task Create_ShouldReturnFailure_WhenValidationFails() result.ErrorCode.Should().Be(WalletCreateOrUpdateErrorCode.NameIsRequired); result.Data.Should().BeNull(); - (await resources.WalletRepository.Query(false).CountAsync()).Should().Be(0); // Ensure no addition to DB + (await resources.WalletRepository.AsNoTracking().CountAsync()).Should().Be(0); // Ensure no addition to DB } #endregion @@ -194,7 +194,7 @@ public async Task Update_ShouldReturnSuccess_WhenInputIsValid() result.Success.Should().BeTrue(); result.Data.Should().BeTrue(); - var dbWallet = await resources.WalletRepository.Query(false).FirstAsync(a => a.Id == wallet.Id); + var dbWallet = await resources.WalletRepository.AsNoTracking().FirstAsync(a => a.Id == wallet.Id); dbWallet.Name.Should().Be(input.Name); dbWallet.InitialBalance.Should().Be(input.InitialBalance); } @@ -227,7 +227,7 @@ public async Task Update_ShouldReturnFailure_WhenValidationFails() result.ErrorCode.Should().Be(WalletCreateOrUpdateErrorCode.WalletNotFound); result.Data.Should().BeFalse(); - var dbWallet = await resources.WalletRepository.Query(false).FirstAsync(a => a.Id == wallet.Id); + var dbWallet = await resources.WalletRepository.AsNoTracking().FirstAsync(a => a.Id == wallet.Id); dbWallet.Name.Should().Be(TestUtils.Strings[0]); // Ensure no update happened } @@ -253,7 +253,7 @@ public async Task Delete_ShouldReturnSuccess_WhenValid() // Assert result.Should().NotBeNull(); result.Success.Should().BeTrue(); - (await resources.WalletRepository.Query(false).FirstOrDefaultAsync(a => a.Id == wallet.Id)).Should().BeNull(); + (await resources.WalletRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == wallet.Id)).Should().BeNull(); } [Fact] @@ -280,7 +280,7 @@ public async Task Delete_ShouldReturnFailure_WhenValidationFails() result.Should().NotBeNull(); result.Success.Should().BeFalse(); result.ErrorCode.Should().Be(WalletDeleteErrorCode.WalletInUseByCreditCardsAndTitle); - (await resources.WalletRepository.Query(false).FirstOrDefaultAsync(a => a.Id == wallet.Id)).Should().NotBeNull(); // Ensure no deletion + (await resources.WalletRepository.AsNoTracking().FirstOrDefaultAsync(a => a.Id == wallet.Id)).Should().NotBeNull(); // Ensure no deletion } #endregion @@ -307,7 +307,7 @@ public async Task ToggleInactive_ShouldReturnSuccess_WhenValidAndDeactivate() result.Should().NotBeNull(); result.Success.Should().BeTrue(); result.Data.Should().BeTrue(); - var dbWallet = await resources.WalletRepository.Query(false).FirstAsync(a => a.Id == wallet.Id); + var dbWallet = await resources.WalletRepository.AsNoTracking().FirstAsync(a => a.Id == wallet.Id); dbWallet.Inactivated.Should().BeTrue(); } @@ -332,7 +332,7 @@ public async Task ToggleInactive_ShouldReturnSuccess_WhenValidAndReactivate() result.Should().NotBeNull(); result.Success.Should().BeTrue(); result.Data.Should().BeTrue(); - var dbWallet = await resources.WalletRepository.Query(false).FirstAsync(a => a.Id == wallet.Id); + var dbWallet = await resources.WalletRepository.AsNoTracking().FirstAsync(a => a.Id == wallet.Id); dbWallet.Inactivated.Should().BeFalse(); } @@ -361,7 +361,7 @@ public async Task ToggleInactive_ShouldReturnFailure_WhenValidationFails() result.Should().NotBeNull(); result.Success.Should().BeFalse(); result.ErrorCode.Should().Be(WalletToggleInactiveErrorCode.WalletNotFound); - var dbWallet = await resources.WalletRepository.Query(false).FirstAsync(a => a.Id == wallet.Id); + var dbWallet = await resources.WalletRepository.AsNoTracking().FirstAsync(a => a.Id == wallet.Id); dbWallet.Inactivated.Should().BeFalse(); // Ensure no status change }