Update auth service, move JWT handling out of controller

This commit is contained in:
Aaron Po
2026-02-15 18:30:33 -05:00
parent 7eb81039aa
commit 05dac79c4e
15 changed files with 244 additions and 332 deletions

View File

@@ -1,8 +1,6 @@
using Domain.Entities;
using Domain.Exceptions;
using FluentAssertions;
using Infrastructure.Email;
using Infrastructure.Email.Templates.Rendering;
using Infrastructure.PasswordHashing;
using Infrastructure.Repository.Auth;
using Moq;
@@ -13,27 +11,24 @@ public class RegisterServiceTest
{
private readonly Mock<IAuthRepository> _authRepoMock;
private readonly Mock<IPasswordInfrastructure> _passwordInfraMock;
private readonly Mock<IEmailProvider> _emailProviderMock;
private readonly Mock<IEmailTemplateProvider> _emailTemplateProviderMock;
private readonly Mock<ITokenService> _tokenServiceMock;
private readonly RegisterService _registerService;
public RegisterServiceTest()
{
_authRepoMock = new Mock<IAuthRepository>();
_passwordInfraMock = new Mock<IPasswordInfrastructure>();
_emailProviderMock = new Mock<IEmailProvider>();
_emailTemplateProviderMock = new Mock<IEmailTemplateProvider>();
_tokenServiceMock = new Mock<ITokenService>();
_registerService = new RegisterService(
_authRepoMock.Object,
_passwordInfraMock.Object,
_emailProviderMock.Object,
_emailTemplateProviderMock.Object
_tokenServiceMock.Object
);
}
[Fact]
public async Task RegisterAsync_WithValidData_CreatesUserAndSendsEmail()
public async Task RegisterAsync_WithValidData_CreatesUserAndReturnsAuthServiceReturn()
{
// Arrange
var userAccount = new UserAccount
@@ -48,7 +43,6 @@ public class RegisterServiceTest
const string password = "SecurePassword123!";
const string hashedPassword = "hashed_password_value";
var expectedUserId = Guid.NewGuid();
const string expectedEmailHtml = "<html><body>Welcome!</body></html>";
// Mock: No existing user
_authRepoMock
@@ -89,36 +83,28 @@ public class RegisterServiceTest
}
);
// Mock: Email template rendering
_emailTemplateProviderMock
.Setup(x =>
x.RenderUserRegisteredEmailAsync(
userAccount.FirstName,
It.IsAny<string>()
)
)
.ReturnsAsync(expectedEmailHtml);
// Mock: Token generation
_tokenServiceMock
.Setup(x => x.GenerateAccessToken(It.IsAny<UserAccount>()))
.Returns("access-token");
// Mock: Email sending
_emailProviderMock
.Setup(x =>
x.SendAsync(
userAccount.Email,
"Welcome to The Biergarten App!",
expectedEmailHtml,
true
)
)
.Returns(Task.CompletedTask);
_tokenServiceMock
.Setup(x => x.GenerateRefreshToken(It.IsAny<UserAccount>()))
.Returns("refresh-token");
// Act
var result = await _registerService.RegisterAsync(userAccount, password);
var result = await _registerService.RegisterAsync(
userAccount,
password
);
// Assert
result.Should().NotBeNull();
result.UserAccountId.Should().Be(expectedUserId);
result.Username.Should().Be(userAccount.Username);
result.Email.Should().Be(userAccount.Email);
result.UserAccount.UserAccountId.Should().Be(expectedUserId);
result.UserAccount.Username.Should().Be(userAccount.Username);
result.UserAccount.Email.Should().Be(userAccount.Email);
result.AccessToken.Should().Be("access-token");
result.RefreshToken.Should().Be("refresh-token");
// Verify all mocks were called as expected
_authRepoMock.Verify(
@@ -142,24 +128,6 @@ public class RegisterServiceTest
),
Times.Once
);
_emailTemplateProviderMock.Verify(
x =>
x.RenderUserRegisteredEmailAsync(
userAccount.FirstName,
It.IsAny<string>()
),
Times.Once
);
_emailProviderMock.Verify(
x =>
x.SendAsync(
userAccount.Email,
"Welcome to The Biergarten App!",
expectedEmailHtml,
true
),
Times.Once
);
}
[Fact]
@@ -195,7 +163,8 @@ public class RegisterServiceTest
.ReturnsAsync((UserAccount?)null);
// Act
var act = async () => await _registerService.RegisterAsync(userAccount, password);
var act = async () =>
await _registerService.RegisterAsync(userAccount, password);
// Assert
await act.Should()
@@ -215,18 +184,6 @@ public class RegisterServiceTest
),
Times.Never
);
// Verify email was never sent
_emailProviderMock.Verify(
x =>
x.SendAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<bool>()
),
Times.Never
);
}
[Fact]
@@ -262,7 +219,8 @@ public class RegisterServiceTest
.ReturnsAsync(existingUser);
// Act
var act = async () => await _registerService.RegisterAsync(userAccount, password);
var act = async () =>
await _registerService.RegisterAsync(userAccount, password);
// Assert
await act.Should()
@@ -323,14 +281,13 @@ public class RegisterServiceTest
)
.ReturnsAsync(new UserAccount { UserAccountId = Guid.NewGuid() });
_emailTemplateProviderMock
.Setup(x =>
x.RenderUserRegisteredEmailAsync(
It.IsAny<string>(),
It.IsAny<string>()
)
)
.ReturnsAsync("<html></html>");
_tokenServiceMock
.Setup(x => x.GenerateAccessToken(It.IsAny<UserAccount>()))
.Returns("access-token");
_tokenServiceMock
.Setup(x => x.GenerateRefreshToken(It.IsAny<UserAccount>()))
.Returns("refresh-token");
// Act
await _registerService.RegisterAsync(userAccount, plainPassword);
@@ -350,152 +307,4 @@ public class RegisterServiceTest
Times.Once
);
}
[Fact]
public async Task RegisterAsync_EmailConfirmationLink_ContainsUserEmail()
{
// Arrange
var userAccount = new UserAccount
{
Username = "testuser",
FirstName = "Test",
LastName = "User",
Email = "test@example.com",
DateOfBirth = new DateTime(1990, 1, 1),
};
var password = "Password123!";
string? capturedConfirmationLink = null;
_authRepoMock
.Setup(x => x.GetUserByUsernameAsync(It.IsAny<string>()))
.ReturnsAsync((UserAccount?)null);
_authRepoMock
.Setup(x => x.GetUserByEmailAsync(It.IsAny<string>()))
.ReturnsAsync((UserAccount?)null);
_passwordInfraMock
.Setup(x => x.Hash(It.IsAny<string>()))
.Returns("hashed");
_authRepoMock
.Setup(x =>
x.RegisterUserAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<DateTime>(),
It.IsAny<string>()
)
)
.ReturnsAsync(
new UserAccount
{
UserAccountId = Guid.NewGuid(),
Username = userAccount.Username,
FirstName = userAccount.FirstName,
LastName = userAccount.LastName,
Email = userAccount.Email,
DateOfBirth = userAccount.DateOfBirth,
}
);
_emailTemplateProviderMock
.Setup(x =>
x.RenderUserRegisteredEmailAsync(
It.IsAny<string>(),
It.IsAny<string>()
)
)
.Callback<string, string>(
(_, link) => capturedConfirmationLink = link
)
.ReturnsAsync("<html></html>");
// Act
await _registerService.RegisterAsync(userAccount, password);
// Assert
capturedConfirmationLink.Should().NotBeNull();
capturedConfirmationLink
.Should()
.Contain(Uri.EscapeDataString(userAccount.Email));
}
[Fact]
public async Task RegisterAsync_WhenEmailSendingFails_ExceptionPropagates()
{
// Arrange
var userAccount = new UserAccount
{
Username = "testuser",
FirstName = "Test",
LastName = "User",
Email = "test@example.com",
DateOfBirth = new DateTime(1990, 1, 1),
};
var password = "Password123!";
_authRepoMock
.Setup(x => x.GetUserByUsernameAsync(It.IsAny<string>()))
.ReturnsAsync((UserAccount?)null);
_authRepoMock
.Setup(x => x.GetUserByEmailAsync(It.IsAny<string>()))
.ReturnsAsync((UserAccount?)null);
_passwordInfraMock
.Setup(x => x.Hash(It.IsAny<string>()))
.Returns("hashed");
_authRepoMock
.Setup(x =>
x.RegisterUserAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<DateTime>(),
It.IsAny<string>()
)
)
.ReturnsAsync(
new UserAccount
{
UserAccountId = Guid.NewGuid(),
Email = userAccount.Email,
}
);
_emailTemplateProviderMock
.Setup(x =>
x.RenderUserRegisteredEmailAsync(
It.IsAny<string>(),
It.IsAny<string>()
)
)
.ReturnsAsync("<html></html>");
_emailProviderMock
.Setup(x =>
x.SendAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<bool>()
)
)
.ThrowsAsync(
new InvalidOperationException("SMTP server unavailable")
);
// Act
var act = async () => await _registerService.RegisterAsync(userAccount, password);
// Assert
await act.Should()
.ThrowAsync<InvalidOperationException>()
.WithMessage("SMTP server unavailable");
}
}