From fad93d4aac6d4fd4eee0d1884fc0443ec03aa527 Mon Sep 17 00:00:00 2001 From: Rafael Pi Date: Tue, 20 Jan 2026 17:12:05 -0300 Subject: [PATCH 1/2] Fix #98: add unit tests for SendVerificationEmail --- Directory.Packages.props | 1 + src/Web/Controllers/ManageController.cs | 35 +++-- tests/UnitTests/UnitTests.csproj | 1 + .../Web/Controllers/ManageControllerTests.cs | 131 ++++++++++++++++++ 4 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 tests/UnitTests/Web/Controllers/ManageControllerTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 80fd26b8..36a36101 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -49,6 +49,7 @@ + diff --git a/src/Web/Controllers/ManageController.cs b/src/Web/Controllers/ManageController.cs index e4748b64..202b5085 100644 --- a/src/Web/Controllers/ManageController.cs +++ b/src/Web/Controllers/ManageController.cs @@ -107,16 +107,18 @@ public async Task SendVerificationEmail(IndexViewModel model) var user = await GetCurrentUserAsync(); - var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); - var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme); - Guard.Against.Null(callbackUrl, nameof(callbackUrl)); - var email = user.Email; - if (email == null) - { - throw new ApplicationException($"No email associated with user {user.UserName}'."); - } + await SendVerificationEmailInternalAsync(user); - await _emailSender.SendEmailConfirmationAsync(email, callbackUrl); + //var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + //var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme); + //Guard.Against.Null(callbackUrl, nameof(callbackUrl)); + //var email = user.Email; + //if (email == null) + //{ + // throw new ApplicationException($"No email associated with user {user.UserName}'."); + //} + + //await _emailSender.SendEmailConfirmationAsync(email, callbackUrl); StatusMessage = "Verification email sent. Please check your email."; return RedirectToAction(nameof(MyAccount)); @@ -491,4 +493,19 @@ private async Task GetCurrentUserAsync() return user ?? throw new UserNotFoundException(_userManager.GetUserId(User) ?? string.Empty); } + protected virtual async Task SendVerificationEmailInternalAsync(ApplicationUser user) + { + var email = user.Email; + if(email == null) + { + throw new ApplicationException($"No email associated with user {user.UserName}'."); + } + + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme); + Guard.Against.Null(callbackUrl, nameof(callbackUrl)); + + await _emailSender.SendEmailConfirmationAsync(email, callbackUrl); + } + } diff --git a/tests/UnitTests/UnitTests.csproj b/tests/UnitTests/UnitTests.csproj index d969b6f4..a60f15f8 100644 --- a/tests/UnitTests/UnitTests.csproj +++ b/tests/UnitTests/UnitTests.csproj @@ -14,6 +14,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + all diff --git a/tests/UnitTests/Web/Controllers/ManageControllerTests.cs b/tests/UnitTests/Web/Controllers/ManageControllerTests.cs new file mode 100644 index 00000000..e1a23310 --- /dev/null +++ b/tests/UnitTests/Web/Controllers/ManageControllerTests.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Text; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.eShopWeb.Infrastructure.Identity; +using Microsoft.eShopWeb.Web.Controllers; +using Microsoft.eShopWeb.Web.Services; +using Moq; +using Xunit; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Microsoft.eShopWeb.UnitTests.Web.Controllers; +public class ManageControllerTests +{ + [Fact] + public async Task SendVerificationEmail_sends_email() + { + // Arrange + var user = new ApplicationUser + { + Id = "user-id", + Email = "test@test.com", + UserName = "test" + }; + + var userManager = TestUserManager(user); + var signInManager = TestSignInManager(userManager); + + var emailSender = new Mock(); + emailSender.Setup(e => e.SendEmailAsync( + user.Email, + It.IsAny(), + It.IsAny())) + .Returns(Task.CompletedTask); + + var logger = new Mock>(); + var urlEncoder = UrlEncoder.Default; + + var controller = new TestableManageController( + userManager, + signInManager, + emailSender.Object, + logger.Object, + urlEncoder); + + controller.ControllerContext = new ControllerContext + { + HttpContext = new DefaultHttpContext + { + User = new ClaimsPrincipal(new ClaimsIdentity( + new[] { new Claim(ClaimTypes.NameIdentifier, user.Id) })) + } + }; + + var urlHelper = new Mock(); + urlHelper.Setup(u => u.Action(It.IsAny())) + .Returns("https://localhost/confirm-email"); + + controller.Url = urlHelper.Object; + + // Act + await controller.InvokeSendVerificationEmail(user); + + // Assert + emailSender.Verify(e => e.SendEmailAsync( + user.Email, + It.IsAny(), + It.IsAny()), + Times.Once); + } + + #region Helpers + private static UserManager TestUserManager(ApplicationUser user) + { + var store = new Mock>(); + + var manager = new Mock>( + store.Object, + null, + null, + null, + null, + null, + null, + null, + null); + + manager.Setup(m => m.GetUserAsync(It.IsAny())) + .ReturnsAsync(user); + + manager.Setup(m => m.GenerateEmailConfirmationTokenAsync(user)) + .ReturnsAsync("token"); + + return manager.Object; + } + + private static SignInManager TestSignInManager( + UserManager userManager) + { + return new SignInManager( + userManager, + new Mock().Object, + new Mock>().Object, + null, + null, + null, + null); + } + #endregion + + + internal class TestableManageController : ManageController + { + public TestableManageController( + UserManager userManager, + SignInManager signInManager, + IEmailSender emailSender, + IAppLogger logger, + UrlEncoder urlEncoder) + : base(userManager, signInManager, emailSender, logger, urlEncoder) + { + } + + public Task InvokeSendVerificationEmail(ApplicationUser user) => SendVerificationEmailInternalAsync(user); + } +} From 7671496691e52104f2d11d05b02c26d318181045 Mon Sep 17 00:00:00 2001 From: Rafael Pi Date: Tue, 20 Jan 2026 19:17:24 -0300 Subject: [PATCH 2/2] Removed commented code lines --- src/Web/Controllers/ManageController.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/Web/Controllers/ManageController.cs b/src/Web/Controllers/ManageController.cs index 202b5085..4471e91c 100644 --- a/src/Web/Controllers/ManageController.cs +++ b/src/Web/Controllers/ManageController.cs @@ -109,17 +109,6 @@ public async Task SendVerificationEmail(IndexViewModel model) await SendVerificationEmailInternalAsync(user); - //var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); - //var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme); - //Guard.Against.Null(callbackUrl, nameof(callbackUrl)); - //var email = user.Email; - //if (email == null) - //{ - // throw new ApplicationException($"No email associated with user {user.UserName}'."); - //} - - //await _emailSender.SendEmailConfirmationAsync(email, callbackUrl); - StatusMessage = "Verification email sent. Please check your email."; return RedirectToAction(nameof(MyAccount)); }