code style cleanup

This commit is contained in:
Aaron Po
2026-06-20 13:55:17 -04:00
parent 0c3b0e99e8
commit 5b882ac51c
167 changed files with 3711 additions and 3522 deletions

View File

@@ -2,10 +2,11 @@ using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Domain.Entities;
using Domain.Exceptions;
using FluentAssertions;
using Features.Auth.Commands.ConfirmUser;
using Features.Auth.Dtos;
using Features.Auth.Repository;
using Features.Auth.Services;
using FluentAssertions;
using Moq;
namespace Features.Auth.Tests.Commands;
@@ -13,8 +14,8 @@ namespace Features.Auth.Tests.Commands;
public class ConfirmUserHandlerTests
{
private readonly Mock<IAuthRepository> _authRepositoryMock;
private readonly Mock<ITokenService> _tokenServiceMock;
private readonly ConfirmUserHandler _handler;
private readonly Mock<ITokenService> _tokenServiceMock;
public ConfirmUserHandlerTests()
{
@@ -25,30 +26,30 @@ public class ConfirmUserHandlerTests
private static ValidatedToken MakeValidatedToken(Guid userId, string username)
{
var claims = new List<Claim>
List<Claim> claims = new()
{
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
new(JwtRegisteredClaimNames.UniqueName, username),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims));
ClaimsPrincipal principal = new(new ClaimsIdentity(claims));
return new ValidatedToken(userId, username, principal);
}
[Fact]
public async Task Handle_WithValidConfirmationToken_ConfirmsUser()
{
var userId = Guid.NewGuid();
Guid userId = Guid.NewGuid();
const string username = "testuser";
const string confirmationToken = "valid-confirmation-token";
var validatedToken = MakeValidatedToken(userId, username);
var userAccount = new UserAccount { UserAccountId = userId, Username = username };
ValidatedToken validatedToken = MakeValidatedToken(userId, username);
UserAccount userAccount = new() { UserAccountId = userId, Username = username };
_tokenServiceMock.Setup(x => x.ValidateConfirmationTokenAsync(confirmationToken)).ReturnsAsync(validatedToken);
_authRepositoryMock.Setup(x => x.ConfirmUserAccountAsync(userId)).ReturnsAsync(userAccount);
var result = await _handler.Handle(new ConfirmUserCommand(confirmationToken), CancellationToken.None);
ConfirmationPayload result = await _handler.Handle(new ConfirmUserCommand(confirmationToken), CancellationToken.None);
result.Should().NotBeNull();
result.UserAccountId.Should().Be(userId);
@@ -63,7 +64,7 @@ public class ConfirmUserHandlerTests
.Setup(x => x.ValidateConfirmationTokenAsync(invalidToken))
.ThrowsAsync(new UnauthorizedException("Invalid confirmation token"));
var act = async () => await _handler.Handle(new ConfirmUserCommand(invalidToken), CancellationToken.None);
Func<Task<ConfirmationPayload>> act = async () => await _handler.Handle(new ConfirmUserCommand(invalidToken), CancellationToken.None);
await act.Should().ThrowAsync<UnauthorizedException>();
}
@@ -71,7 +72,7 @@ public class ConfirmUserHandlerTests
[Fact]
public async Task Handle_WithNonExistentUser_ThrowsUnauthorizedException()
{
var userId = Guid.NewGuid();
Guid userId = Guid.NewGuid();
const string username = "nonexistent";
const string confirmationToken = "valid-token-for-nonexistent-user";
@@ -80,8 +81,8 @@ public class ConfirmUserHandlerTests
.ReturnsAsync(MakeValidatedToken(userId, username));
_authRepositoryMock.Setup(x => x.ConfirmUserAccountAsync(userId)).ReturnsAsync((UserAccount?)null);
var act = async () => await _handler.Handle(new ConfirmUserCommand(confirmationToken), CancellationToken.None);
Func<Task<ConfirmationPayload>> act = async () => await _handler.Handle(new ConfirmUserCommand(confirmationToken), CancellationToken.None);
await act.Should().ThrowAsync<UnauthorizedException>().WithMessage("*User account not found*");
}
}
}

View File

@@ -1,7 +1,8 @@
using Domain.Entities;
using FluentAssertions;
using Features.Auth.Commands.RefreshToken;
using Features.Auth.Dtos;
using Features.Auth.Services;
using FluentAssertions;
using Moq;
namespace Features.Auth.Tests.Commands;
@@ -11,20 +12,20 @@ public class RefreshTokenHandlerTests
[Fact]
public async Task Handle_MapsTokenServiceResult_ToLoginPayload()
{
var tokenServiceMock = new Mock<ITokenService>();
var handler = new RefreshTokenHandler(tokenServiceMock.Object);
var userId = Guid.NewGuid();
var user = new UserAccount { UserAccountId = userId, Username = "testuser" };
Mock<ITokenService> tokenServiceMock = new();
RefreshTokenHandler handler = new(tokenServiceMock.Object);
Guid userId = Guid.NewGuid();
UserAccount user = new() { UserAccountId = userId, Username = "testuser" };
tokenServiceMock
.Setup(x => x.RefreshTokenAsync("old-refresh-token"))
.ReturnsAsync(new RefreshTokenResult(user, "new-refresh-token", "new-access-token"));
var result = await handler.Handle(new RefreshTokenCommand("old-refresh-token"), CancellationToken.None);
LoginPayload result = await handler.Handle(new RefreshTokenCommand("old-refresh-token"), CancellationToken.None);
result.UserAccountId.Should().Be(userId);
result.Username.Should().Be("testuser");
result.RefreshToken.Should().Be("new-refresh-token");
result.AccessToken.Should().Be("new-access-token");
}
}
}

View File

@@ -1,9 +1,10 @@
using Domain.Entities;
using Domain.Exceptions;
using FluentAssertions;
using Features.Auth.Commands.RegisterUser;
using Features.Auth.Dtos;
using Features.Auth.Repository;
using Features.Auth.Services;
using FluentAssertions;
using Infrastructure.PasswordHashing;
using MediatR;
using Moq;
@@ -14,10 +15,10 @@ namespace Features.Auth.Tests.Commands;
public class RegisterUserHandlerTests
{
private readonly Mock<IAuthRepository> _authRepoMock;
private readonly RegisterUserHandler _handler;
private readonly Mock<IMediator> _mediatorMock;
private readonly Mock<IPasswordInfrastructure> _passwordInfraMock;
private readonly Mock<ITokenService> _tokenServiceMock;
private readonly Mock<IMediator> _mediatorMock;
private readonly RegisterUserHandler _handler;
public RegisterUserHandlerTests()
{
@@ -34,22 +35,25 @@ public class RegisterUserHandlerTests
);
}
private static RegisterUserCommand ValidCommand(string username = "newuser", string email = "john.doe@example.com") =>
new(username, "John", "Doe", email, new DateTime(1990, 1, 1), "SecurePassword123!");
private static RegisterUserCommand ValidCommand(string username = "newuser", string email = "john.doe@example.com")
{
return new RegisterUserCommand(username, "John", "Doe", email, new DateTime(1990, 1, 1), "SecurePassword123!");
}
[Fact]
public async Task Handle_WithValidData_CreatesUserAndReturnsPayload()
{
var command = ValidCommand();
RegisterUserCommand command = ValidCommand();
const string hashedPassword = "hashed_password_value";
var expectedUserId = Guid.NewGuid();
Guid expectedUserId = Guid.NewGuid();
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(command.Username)).ReturnsAsync((UserAccount?)null);
_authRepoMock.Setup(x => x.GetUserByEmailAsync(command.Email)).ReturnsAsync((UserAccount?)null);
_passwordInfraMock.Setup(x => x.Hash(command.Password)).Returns(hashedPassword);
_authRepoMock
.Setup(x => x.RegisterUserAsync(command.Username, command.FirstName, command.LastName, command.Email, command.DateOfBirth, hashedPassword))
.Setup(x => x.RegisterUserAsync(command.Username, command.FirstName, command.LastName, command.Email,
command.DateOfBirth, hashedPassword))
.ReturnsAsync(new UserAccount
{
UserAccountId = expectedUserId,
@@ -58,14 +62,15 @@ public class RegisterUserHandlerTests
LastName = command.LastName,
Email = command.Email,
DateOfBirth = command.DateOfBirth,
CreatedAt = DateTime.UtcNow,
CreatedAt = DateTime.UtcNow
});
_tokenServiceMock.Setup(x => x.GenerateAccessToken(It.IsAny<UserAccount>())).Returns("access-token");
_tokenServiceMock.Setup(x => x.GenerateRefreshToken(It.IsAny<UserAccount>())).Returns("refresh-token");
_tokenServiceMock.Setup(x => x.GenerateConfirmationToken(It.IsAny<UserAccount>())).Returns("confirmation-token");
_tokenServiceMock.Setup(x => x.GenerateConfirmationToken(It.IsAny<UserAccount>()))
.Returns("confirmation-token");
var result = await _handler.Handle(command, CancellationToken.None);
RegistrationPayload result = await _handler.Handle(command, CancellationToken.None);
result.Should().NotBeNull();
result.UserAccountId.Should().Be(expectedUserId);
@@ -83,18 +88,19 @@ public class RegisterUserHandlerTests
[Fact]
public async Task Handle_WithExistingUsername_ThrowsConflictException()
{
var command = ValidCommand(username: "existinguser");
var existingUser = new UserAccount { UserAccountId = Guid.NewGuid(), Username = "existinguser" };
RegisterUserCommand command = ValidCommand("existinguser");
UserAccount existingUser = new() { UserAccountId = Guid.NewGuid(), Username = "existinguser" };
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(command.Username)).ReturnsAsync(existingUser);
_authRepoMock.Setup(x => x.GetUserByEmailAsync(command.Email)).ReturnsAsync((UserAccount?)null);
var act = async () => await _handler.Handle(command, CancellationToken.None);
Func<Task<RegistrationPayload>> act = async () => await _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync<ConflictException>().WithMessage("Username or email already exists");
_authRepoMock.Verify(
x => x.RegisterUserAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DateTime>(), It.IsAny<string>()),
x => x.RegisterUserAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<DateTime>(), It.IsAny<string>()),
Times.Never
);
}
@@ -102,13 +108,13 @@ public class RegisterUserHandlerTests
[Fact]
public async Task Handle_WithExistingEmail_ThrowsConflictException()
{
var command = ValidCommand(email: "existing@example.com");
var existingUser = new UserAccount { UserAccountId = Guid.NewGuid(), Email = "existing@example.com" };
RegisterUserCommand command = ValidCommand(email: "existing@example.com");
UserAccount existingUser = new() { UserAccountId = Guid.NewGuid(), Email = "existing@example.com" };
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(command.Username)).ReturnsAsync((UserAccount?)null);
_authRepoMock.Setup(x => x.GetUserByEmailAsync(command.Email)).ReturnsAsync(existingUser);
var act = async () => await _handler.Handle(command, CancellationToken.None);
Func<Task<RegistrationPayload>> act = async () => await _handler.Handle(command, CancellationToken.None);
await act.Should().ThrowAsync<ConflictException>().WithMessage("Username or email already exists");
}
@@ -116,7 +122,7 @@ public class RegisterUserHandlerTests
[Fact]
public async Task Handle_PasswordIsHashed_BeforeStoringInDatabase()
{
var command = ValidCommand();
RegisterUserCommand command = ValidCommand();
const string hashedPassword = "hashed_secure_password";
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(It.IsAny<string>())).ReturnsAsync((UserAccount?)null);
@@ -124,7 +130,8 @@ public class RegisterUserHandlerTests
_passwordInfraMock.Setup(x => x.Hash(command.Password)).Returns(hashedPassword);
_authRepoMock
.Setup(x => x.RegisterUserAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(), It.IsAny<DateTime>(), hashedPassword))
.Setup(x => x.RegisterUserAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<string>(), It.IsAny<DateTime>(), hashedPassword))
.ReturnsAsync(new UserAccount { UserAccountId = Guid.NewGuid() });
_tokenServiceMock.Setup(x => x.GenerateAccessToken(It.IsAny<UserAccount>())).Returns("access-token");
@@ -134,7 +141,8 @@ public class RegisterUserHandlerTests
_passwordInfraMock.Verify(x => x.Hash(command.Password), Times.Once);
_authRepoMock.Verify(
x => x.RegisterUserAsync(command.Username, command.FirstName, command.LastName, command.Email, command.DateOfBirth, hashedPassword),
x => x.RegisterUserAsync(command.Username, command.FirstName, command.LastName, command.Email,
command.DateOfBirth, hashedPassword),
Times.Once
);
}
@@ -142,14 +150,16 @@ public class RegisterUserHandlerTests
[Fact]
public async Task Handle_SwallowsEmailFailure_AndReportsEmailNotSent()
{
var command = ValidCommand();
RegisterUserCommand command = ValidCommand();
_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 = command.Username, Email = command.Email });
.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 = command.Username, Email = command.Email });
_tokenServiceMock.Setup(x => x.GenerateAccessToken(It.IsAny<UserAccount>())).Returns("access-token");
_tokenServiceMock.Setup(x => x.GenerateRefreshToken(It.IsAny<UserAccount>())).Returns("refresh-token");
@@ -157,9 +167,9 @@ public class RegisterUserHandlerTests
.Setup(x => x.Send(It.IsAny<SendRegistrationEmailCommand>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new Exception("smtp down"));
var result = await _handler.Handle(command, CancellationToken.None);
RegistrationPayload result = await _handler.Handle(command, CancellationToken.None);
result.ConfirmationEmailSent.Should().BeFalse();
result.AccessToken.Should().Be("access-token");
}
}
}

View File

@@ -11,20 +11,21 @@ namespace Features.Auth.Tests.Commands;
public class ResendConfirmationEmailHandlerTests
{
private readonly Mock<IAuthRepository> _authRepositoryMock = new();
private readonly Mock<ITokenService> _tokenServiceMock = new();
private readonly Mock<IMediator> _mediatorMock = new();
private readonly ResendConfirmationEmailHandler _handler;
private readonly Mock<IMediator> _mediatorMock = new();
private readonly Mock<ITokenService> _tokenServiceMock = new();
public ResendConfirmationEmailHandlerTests()
{
_handler = new ResendConfirmationEmailHandler(_authRepositoryMock.Object, _tokenServiceMock.Object, _mediatorMock.Object);
_handler = new ResendConfirmationEmailHandler(_authRepositoryMock.Object, _tokenServiceMock.Object,
_mediatorMock.Object);
}
[Fact]
public async Task Handle_SendsFreshConfirmationEmail_WhenUserExistsAndUnverified()
{
var userId = Guid.NewGuid();
var user = new UserAccount { UserAccountId = userId, FirstName = "Aaron", Email = "aaron@example.com" };
Guid userId = Guid.NewGuid();
UserAccount user = new() { UserAccountId = userId, FirstName = "Aaron", Email = "aaron@example.com" };
_authRepositoryMock.Setup(x => x.GetUserByIdAsync(userId)).ReturnsAsync(user);
_authRepositoryMock.Setup(x => x.IsUserVerifiedAsync(userId)).ReturnsAsync(false);
@@ -43,7 +44,7 @@ public class ResendConfirmationEmailHandlerTests
[Fact]
public async Task Handle_DoesNothing_WhenUserDoesNotExist()
{
var userId = Guid.NewGuid();
Guid userId = Guid.NewGuid();
_authRepositoryMock.Setup(x => x.GetUserByIdAsync(userId)).ReturnsAsync((UserAccount?)null);
await _handler.Handle(new ResendConfirmationEmailCommand(userId), CancellationToken.None);
@@ -57,8 +58,8 @@ public class ResendConfirmationEmailHandlerTests
[Fact]
public async Task Handle_DoesNothing_WhenUserAlreadyVerified()
{
var userId = Guid.NewGuid();
var user = new UserAccount { UserAccountId = userId };
Guid userId = Guid.NewGuid();
UserAccount user = new() { UserAccountId = userId };
_authRepositoryMock.Setup(x => x.GetUserByIdAsync(userId)).ReturnsAsync(user);
_authRepositoryMock.Setup(x => x.IsUserVerifiedAsync(userId)).ReturnsAsync(true);
@@ -70,4 +71,4 @@ public class ResendConfirmationEmailHandlerTests
Times.Never
);
}
}
}

View File

@@ -1,26 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<RootNamespace>Features.Auth.Tests</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<RootNamespace>Features.Auth.Tests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="FluentAssertions" Version="6.9.0" />
<PackageReference Include="DbMocker" Version="1.26.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1"/>
<PackageReference Include="xunit" Version="2.9.2"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2"/>
<PackageReference Include="Moq" Version="4.20.72"/>
<PackageReference Include="FluentAssertions" Version="6.9.0"/>
<PackageReference Include="DbMocker" Version="1.26.0"/>
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Features.Auth\Features.Auth.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Features.Auth\Features.Auth.csproj"/>
</ItemGroup>
</Project>

View File

@@ -1,9 +1,10 @@
using Domain.Entities;
using Domain.Exceptions;
using FluentAssertions;
using Features.Auth.Dtos;
using Features.Auth.Queries.Login;
using Features.Auth.Repository;
using Features.Auth.Services;
using FluentAssertions;
using Infrastructure.PasswordHashing;
using Moq;
@@ -12,9 +13,9 @@ namespace Features.Auth.Tests.Queries;
public class LoginHandlerTests
{
private readonly Mock<IAuthRepository> _authRepoMock;
private readonly LoginHandler _handler;
private readonly Mock<IPasswordInfrastructure> _passwordInfraMock;
private readonly Mock<ITokenService> _tokenServiceMock;
private readonly LoginHandler _handler;
public LoginHandlerTests()
{
@@ -28,24 +29,24 @@ public class LoginHandlerTests
public async Task Handle_WithValidData_ReturnsPayloadWithMatchingUsername()
{
const string username = "CogitoErgoSum";
var userAccountId = Guid.NewGuid();
Guid userAccountId = Guid.NewGuid();
var userAccount = new UserAccount
UserAccount userAccount = new()
{
UserAccountId = userAccountId,
Username = username,
FirstName = "René",
LastName = "Descartes",
Email = "r.descartes@example.com",
DateOfBirth = new DateTime(1596, 03, 31),
DateOfBirth = new DateTime(1596, 03, 31)
};
var userCredential = new UserCredential
UserCredential userCredential = new()
{
UserCredentialId = Guid.NewGuid(),
UserAccountId = userAccountId,
Hash = "some-hash",
Expiry = DateTime.MaxValue,
Expiry = DateTime.MaxValue
};
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(username)).ReturnsAsync(userAccount);
@@ -54,7 +55,7 @@ public class LoginHandlerTests
_tokenServiceMock.Setup(x => x.GenerateAccessToken(It.IsAny<UserAccount>())).Returns("access-token");
_tokenServiceMock.Setup(x => x.GenerateRefreshToken(It.IsAny<UserAccount>())).Returns("refresh-token");
var result = await _handler.Handle(new LoginQuery(username, "any-password"), CancellationToken.None);
LoginPayload result = await _handler.Handle(new LoginQuery(username, "any-password"), CancellationToken.None);
result.Should().NotBeNull();
result.UserAccountId.Should().Be(userAccountId);
@@ -69,7 +70,7 @@ public class LoginHandlerTests
const string username = "de_beauvoir";
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(username)).ReturnsAsync((UserAccount?)null);
var act = async () => await _handler.Handle(new LoginQuery(username, "password"), CancellationToken.None);
Func<Task<LoginPayload>> act = async () => await _handler.Handle(new LoginQuery(username, "password"), CancellationToken.None);
await act.Should().ThrowAsync<UnauthorizedException>();
_authRepoMock.Verify(x => x.GetActiveCredentialByUserAccountIdAsync(It.IsAny<Guid>()), Times.Never);
@@ -79,13 +80,14 @@ public class LoginHandlerTests
public async Task Handle_WithNoActiveCredential_ThrowsUnauthorizedException()
{
const string username = "BRussell";
var userAccountId = Guid.NewGuid();
var userAccount = new UserAccount { UserAccountId = userAccountId, Username = username };
Guid userAccountId = Guid.NewGuid();
UserAccount userAccount = new() { UserAccountId = userAccountId, Username = username };
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(username)).ReturnsAsync(userAccount);
_authRepoMock.Setup(x => x.GetActiveCredentialByUserAccountIdAsync(userAccountId)).ReturnsAsync((UserCredential?)null);
_authRepoMock.Setup(x => x.GetActiveCredentialByUserAccountIdAsync(userAccountId))
.ReturnsAsync((UserCredential?)null);
var act = async () => await _handler.Handle(new LoginQuery(username, "password"), CancellationToken.None);
Func<Task<LoginPayload>> act = async () => await _handler.Handle(new LoginQuery(username, "password"), CancellationToken.None);
await act.Should().ThrowAsync<UnauthorizedException>().WithMessage("Invalid username or password.");
_passwordInfraMock.Verify(x => x.Verify(It.IsAny<string>(), It.IsAny<string>()), Times.Never);
@@ -95,16 +97,16 @@ public class LoginHandlerTests
public async Task Handle_WithIncorrectPassword_ThrowsUnauthorizedException()
{
const string username = "RCarnap";
var userAccountId = Guid.NewGuid();
var userAccount = new UserAccount { UserAccountId = userAccountId, Username = username };
var userCredential = new UserCredential { UserCredentialId = Guid.NewGuid(), UserAccountId = userAccountId, Hash = "hashed-password" };
Guid userAccountId = Guid.NewGuid();
UserAccount userAccount = new() { UserAccountId = userAccountId, Username = username };
UserCredential userCredential = new() { UserCredentialId = Guid.NewGuid(), UserAccountId = userAccountId, Hash = "hashed-password" };
_authRepoMock.Setup(x => x.GetUserByUsernameAsync(username)).ReturnsAsync(userAccount);
_authRepoMock.Setup(x => x.GetActiveCredentialByUserAccountIdAsync(userAccountId)).ReturnsAsync(userCredential);
_passwordInfraMock.Setup(x => x.Verify(It.IsAny<string>(), It.IsAny<string>())).Returns(false);
var act = async () => await _handler.Handle(new LoginQuery(username, "wrong-password"), CancellationToken.None);
Func<Task<LoginPayload>> act = async () => await _handler.Handle(new LoginQuery(username, "wrong-password"), CancellationToken.None);
await act.Should().ThrowAsync<UnauthorizedException>().WithMessage("Invalid username or password.");
}
}
}

View File

@@ -1,19 +1,22 @@
using Apps72.Dev.Data.DbMocker;
using FluentAssertions;
using Domain.Entities;
using Features.Auth.Repository;
using FluentAssertions;
namespace Features.Auth.Tests.Repository;
public class AuthRepositoryTests
{
private static AuthRepository CreateRepo(MockDbConnection conn) =>
new(new TestConnectionFactory(conn));
private static AuthRepository CreateRepo(MockDbConnection conn)
{
return new AuthRepository(new TestConnectionFactory(conn));
}
[Fact]
public async Task RegisterUserAsync_CreatesUserWithCredential_ReturnsUserAccount()
{
var expectedUserId = Guid.NewGuid();
var conn = new MockDbConnection();
Guid expectedUserId = Guid.NewGuid();
MockDbConnection conn = new();
conn.Mocks.When(cmd => cmd.CommandText == "USP_RegisterUser")
.ReturnsScalar(expectedUserId);
@@ -46,14 +49,14 @@ public class AuthRepositoryTests
)
);
var repo = CreateRepo(conn);
var result = await repo.RegisterUserAsync(
username: "testuser",
firstName: "Test",
lastName: "User",
email: "test@example.com",
dateOfBirth: new DateTime(1990, 1, 1),
passwordHash: "hashedpassword123"
AuthRepository repo = CreateRepo(conn);
UserAccount result = await repo.RegisterUserAsync(
"testuser",
"Test",
"User",
"test@example.com",
new DateTime(1990, 1, 1),
"hashedpassword123"
);
result.Should().NotBeNull();
@@ -68,8 +71,8 @@ public class AuthRepositoryTests
[Fact]
public async Task GetUserByEmailAsync_ReturnsUser_WhenExists()
{
var userId = Guid.NewGuid();
var conn = new MockDbConnection();
Guid userId = Guid.NewGuid();
MockDbConnection conn = new();
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
.ReturnsTable(
@@ -98,8 +101,8 @@ public class AuthRepositoryTests
)
);
var repo = CreateRepo(conn);
var result = await repo.GetUserByEmailAsync("emailuser@example.com");
AuthRepository repo = CreateRepo(conn);
UserAccount? result = await repo.GetUserByEmailAsync("emailuser@example.com");
result.Should().NotBeNull();
result!.UserAccountId.Should().Be(userId);
@@ -112,13 +115,13 @@ public class AuthRepositoryTests
[Fact]
public async Task GetUserByEmailAsync_ReturnsNull_WhenNotExists()
{
var conn = new MockDbConnection();
MockDbConnection conn = new();
conn.Mocks.When(cmd => cmd.CommandText == "usp_GetUserAccountByEmail")
.ReturnsTable(MockTable.Empty());
var repo = CreateRepo(conn);
var result = await repo.GetUserByEmailAsync("nonexistent@example.com");
AuthRepository repo = CreateRepo(conn);
UserAccount? result = await repo.GetUserByEmailAsync("nonexistent@example.com");
result.Should().BeNull();
}
@@ -126,8 +129,8 @@ public class AuthRepositoryTests
[Fact]
public async Task GetUserByUsernameAsync_ReturnsUser_WhenExists()
{
var userId = Guid.NewGuid();
var conn = new MockDbConnection();
Guid userId = Guid.NewGuid();
MockDbConnection conn = new();
conn.Mocks.When(cmd =>
cmd.CommandText == "usp_GetUserAccountByUsername"
@@ -158,8 +161,8 @@ public class AuthRepositoryTests
)
);
var repo = CreateRepo(conn);
var result = await repo.GetUserByUsernameAsync("usernameuser");
AuthRepository repo = CreateRepo(conn);
UserAccount? result = await repo.GetUserByUsernameAsync("usernameuser");
result.Should().NotBeNull();
result!.UserAccountId.Should().Be(userId);
@@ -170,15 +173,15 @@ public class AuthRepositoryTests
[Fact]
public async Task GetUserByUsernameAsync_ReturnsNull_WhenNotExists()
{
var conn = new MockDbConnection();
MockDbConnection conn = new();
conn.Mocks.When(cmd =>
cmd.CommandText == "usp_GetUserAccountByUsername"
)
.ReturnsTable(MockTable.Empty());
var repo = CreateRepo(conn);
var result = await repo.GetUserByUsernameAsync("nonexistent");
AuthRepository repo = CreateRepo(conn);
UserAccount? result = await repo.GetUserByUsernameAsync("nonexistent");
result.Should().BeNull();
}
@@ -186,9 +189,9 @@ public class AuthRepositoryTests
[Fact]
public async Task GetActiveCredentialByUserAccountIdAsync_ReturnsCredential_WhenExists()
{
var userId = Guid.NewGuid();
var credentialId = Guid.NewGuid();
var conn = new MockDbConnection();
Guid userId = Guid.NewGuid();
Guid credentialId = Guid.NewGuid();
MockDbConnection conn = new();
conn.Mocks.When(cmd =>
cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId"
@@ -211,8 +214,8 @@ public class AuthRepositoryTests
)
);
var repo = CreateRepo(conn);
var result = await repo.GetActiveCredentialByUserAccountIdAsync(userId);
AuthRepository repo = CreateRepo(conn);
UserCredential? result = await repo.GetActiveCredentialByUserAccountIdAsync(userId);
result.Should().NotBeNull();
result!.UserCredentialId.Should().Be(credentialId);
@@ -223,16 +226,16 @@ public class AuthRepositoryTests
[Fact]
public async Task GetActiveCredentialByUserAccountIdAsync_ReturnsNull_WhenNotExists()
{
var userId = Guid.NewGuid();
var conn = new MockDbConnection();
Guid userId = Guid.NewGuid();
MockDbConnection conn = new();
conn.Mocks.When(cmd =>
cmd.CommandText == "USP_GetActiveUserCredentialByUserAccountId"
)
.ReturnsTable(MockTable.Empty());
var repo = CreateRepo(conn);
var result = await repo.GetActiveCredentialByUserAccountIdAsync(userId);
AuthRepository repo = CreateRepo(conn);
UserCredential? result = await repo.GetActiveCredentialByUserAccountIdAsync(userId);
result.Should().BeNull();
}
@@ -240,18 +243,18 @@ public class AuthRepositoryTests
[Fact]
public async Task RotateCredentialAsync_ExecutesSuccessfully()
{
var userId = Guid.NewGuid();
var newPasswordHash = "new_hashed_password";
var conn = new MockDbConnection();
Guid userId = Guid.NewGuid();
string newPasswordHash = "new_hashed_password";
MockDbConnection conn = new();
conn.Mocks.When(cmd => cmd.CommandText == "USP_RotateUserCredential")
.ReturnsScalar(1);
var repo = CreateRepo(conn);
AuthRepository repo = CreateRepo(conn);
// Should not throw
var act = async () =>
Func<Task> act = async () =>
await repo.RotateCredentialAsync(userId, newPasswordHash);
await act.Should().NotThrowAsync();
}
}
}

View File

@@ -7,5 +7,8 @@ internal class TestConnectionFactory(DbConnection conn) : ISqlConnectionFactory
{
private readonly DbConnection _conn = conn;
public DbConnection CreateConnection() => _conn;
}
public DbConnection CreateConnection()
{
return _conn;
}
}

View File

@@ -2,9 +2,9 @@ using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Domain.Entities;
using Domain.Exceptions;
using FluentAssertions;
using Features.Auth.Repository;
using Features.Auth.Services;
using FluentAssertions;
using Infrastructure.Jwt;
using Moq;
@@ -12,152 +12,153 @@ namespace Features.Auth.Tests.Services;
public class TokenServiceRefreshTests
{
private readonly Mock<ITokenInfrastructure> _tokenInfraMock;
private readonly Mock<IAuthRepository> _authRepositoryMock;
private readonly TokenService _tokenService;
private readonly Mock<IAuthRepository> _authRepositoryMock;
private readonly Mock<ITokenInfrastructure> _tokenInfraMock;
private readonly TokenService _tokenService;
public TokenServiceRefreshTests()
{
_tokenInfraMock = new Mock<ITokenInfrastructure>();
_authRepositoryMock = new Mock<IAuthRepository>();
public TokenServiceRefreshTests()
{
_tokenInfraMock = new Mock<ITokenInfrastructure>();
_authRepositoryMock = new Mock<IAuthRepository>();
// Set environment variables for tokens
Environment.SetEnvironmentVariable("ACCESS_TOKEN_SECRET", "test-access-secret-that-is-very-long-1234567890");
Environment.SetEnvironmentVariable("REFRESH_TOKEN_SECRET", "test-refresh-secret-that-is-very-long-1234567890");
Environment.SetEnvironmentVariable("CONFIRMATION_TOKEN_SECRET", "test-confirmation-secret-that-is-very-long-1234567890");
// Set environment variables for tokens
Environment.SetEnvironmentVariable("ACCESS_TOKEN_SECRET", "test-access-secret-that-is-very-long-1234567890");
Environment.SetEnvironmentVariable("REFRESH_TOKEN_SECRET", "test-refresh-secret-that-is-very-long-1234567890");
Environment.SetEnvironmentVariable("CONFIRMATION_TOKEN_SECRET",
"test-confirmation-secret-that-is-very-long-1234567890");
_tokenService = new TokenService(
_tokenInfraMock.Object,
_authRepositoryMock.Object
);
}
_tokenService = new TokenService(
_tokenInfraMock.Object,
_authRepositoryMock.Object
);
}
[Fact]
public async Task RefreshTokenAsync_WithValidRefreshToken_ReturnsNewTokens()
{
// Arrange
var userId = Guid.NewGuid();
const string username = "testuser";
const string refreshToken = "valid-refresh-token";
[Fact]
public async Task RefreshTokenAsync_WithValidRefreshToken_ReturnsNewTokens()
{
// Arrange
Guid userId = Guid.NewGuid();
const string username = "testuser";
const string refreshToken = "valid-refresh-token";
var claims = new List<Claim>
List<Claim> claims = new()
{
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
new(JwtRegisteredClaimNames.UniqueName, username),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var claimsIdentity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(claimsIdentity);
ClaimsIdentity claimsIdentity = new(claims);
ClaimsPrincipal principal = new(claimsIdentity);
var userAccount = new UserAccount
{
UserAccountId = userId,
Username = username,
FirstName = "Test",
LastName = "User",
Email = "test@example.com",
DateOfBirth = new DateTime(1990, 1, 1),
};
// Mock the validation of refresh token
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(refreshToken, It.IsAny<string>()))
.ReturnsAsync(principal);
// Mock the generation of new tokens
_tokenInfraMock
.Setup(x => x.GenerateJwt(userId, username, It.IsAny<DateTime>(), It.IsAny<string>()))
.Returns((Guid _, string _, DateTime _, string _) => $"generated-token-{Guid.NewGuid()}");
_authRepositoryMock
.Setup(x => x.GetUserByIdAsync(userId))
.ReturnsAsync(userAccount);
// Act
var result = await _tokenService.RefreshTokenAsync(refreshToken);
// Assert
result.Should().NotBeNull();
result.UserAccount.UserAccountId.Should().Be(userId);
result.UserAccount.Username.Should().Be(username);
result.AccessToken.Should().NotBeEmpty();
result.RefreshToken.Should().NotBeEmpty();
_authRepositoryMock.Verify(
x => x.GetUserByIdAsync(userId),
Times.Once
);
// Verify tokens were generated (called twice - once for access, once for refresh)
_tokenInfraMock.Verify(
x => x.GenerateJwt(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<DateTime>(), It.IsAny<string>()),
Times.Exactly(2)
);
}
[Fact]
public async Task RefreshTokenAsync_WithInvalidRefreshToken_ThrowsUnauthorizedException()
{
// Arrange
const string invalidToken = "invalid-refresh-token";
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(invalidToken, It.IsAny<string>()))
.ThrowsAsync(new UnauthorizedException("Invalid refresh token"));
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.RefreshTokenAsync(invalidToken)
).Should().ThrowAsync<UnauthorizedException>();
}
[Fact]
public async Task RefreshTokenAsync_WithExpiredRefreshToken_ThrowsUnauthorizedException()
{
// Arrange
const string expiredToken = "expired-refresh-token";
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(expiredToken, It.IsAny<string>()))
.ThrowsAsync(new UnauthorizedException("Refresh token has expired"));
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.RefreshTokenAsync(expiredToken)
).Should().ThrowAsync<UnauthorizedException>();
}
[Fact]
public async Task RefreshTokenAsync_WithNonExistentUser_ThrowsUnauthorizedException()
{
// Arrange
var userId = Guid.NewGuid();
const string username = "testuser";
const string refreshToken = "valid-refresh-token";
var claims = new List<Claim>
UserAccount userAccount = new()
{
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
new(JwtRegisteredClaimNames.UniqueName, username),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
UserAccountId = userId,
Username = username,
FirstName = "Test",
LastName = "User",
Email = "test@example.com",
DateOfBirth = new DateTime(1990, 1, 1)
};
var claimsIdentity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(claimsIdentity);
// Mock the validation of refresh token
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(refreshToken, It.IsAny<string>()))
.ReturnsAsync(principal);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(refreshToken, It.IsAny<string>()))
.ReturnsAsync(principal);
// Mock the generation of new tokens
_tokenInfraMock
.Setup(x => x.GenerateJwt(userId, username, It.IsAny<DateTime>(), It.IsAny<string>()))
.Returns((Guid _, string _, DateTime _, string _) => $"generated-token-{Guid.NewGuid()}");
_authRepositoryMock
.Setup(x => x.GetUserByIdAsync(userId))
.ReturnsAsync((UserAccount?)null);
_authRepositoryMock
.Setup(x => x.GetUserByIdAsync(userId))
.ReturnsAsync(userAccount);
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.RefreshTokenAsync(refreshToken)
).Should().ThrowAsync<UnauthorizedException>()
.WithMessage("*User account not found*");
}
}
// Act
RefreshTokenResult result = await _tokenService.RefreshTokenAsync(refreshToken);
// Assert
result.Should().NotBeNull();
result.UserAccount.UserAccountId.Should().Be(userId);
result.UserAccount.Username.Should().Be(username);
result.AccessToken.Should().NotBeEmpty();
result.RefreshToken.Should().NotBeEmpty();
_authRepositoryMock.Verify(
x => x.GetUserByIdAsync(userId),
Times.Once
);
// Verify tokens were generated (called twice - once for access, once for refresh)
_tokenInfraMock.Verify(
x => x.GenerateJwt(It.IsAny<Guid>(), It.IsAny<string>(), It.IsAny<DateTime>(), It.IsAny<string>()),
Times.Exactly(2)
);
}
[Fact]
public async Task RefreshTokenAsync_WithInvalidRefreshToken_ThrowsUnauthorizedException()
{
// Arrange
const string invalidToken = "invalid-refresh-token";
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(invalidToken, It.IsAny<string>()))
.ThrowsAsync(new UnauthorizedException("Invalid refresh token"));
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.RefreshTokenAsync(invalidToken)
).Should().ThrowAsync<UnauthorizedException>();
}
[Fact]
public async Task RefreshTokenAsync_WithExpiredRefreshToken_ThrowsUnauthorizedException()
{
// Arrange
const string expiredToken = "expired-refresh-token";
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(expiredToken, It.IsAny<string>()))
.ThrowsAsync(new UnauthorizedException("Refresh token has expired"));
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.RefreshTokenAsync(expiredToken)
).Should().ThrowAsync<UnauthorizedException>();
}
[Fact]
public async Task RefreshTokenAsync_WithNonExistentUser_ThrowsUnauthorizedException()
{
// Arrange
Guid userId = Guid.NewGuid();
const string username = "testuser";
const string refreshToken = "valid-refresh-token";
List<Claim> claims = new()
{
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
ClaimsIdentity claimsIdentity = new(claims);
ClaimsPrincipal principal = new(claimsIdentity);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(refreshToken, It.IsAny<string>()))
.ReturnsAsync(principal);
_authRepositoryMock
.Setup(x => x.GetUserByIdAsync(userId))
.ReturnsAsync((UserAccount?)null);
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.RefreshTokenAsync(refreshToken)
).Should().ThrowAsync<UnauthorizedException>()
.WithMessage("*User account not found*");
}
}

View File

@@ -1,10 +1,9 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Domain.Entities;
using Domain.Exceptions;
using FluentAssertions;
using Features.Auth.Repository;
using Features.Auth.Services;
using FluentAssertions;
using Infrastructure.Jwt;
using Moq;
@@ -12,272 +11,273 @@ namespace Features.Auth.Tests.Services;
public class TokenServiceValidationTests
{
private readonly Mock<ITokenInfrastructure> _tokenInfraMock;
private readonly Mock<IAuthRepository> _authRepositoryMock;
private readonly TokenService _tokenService;
private readonly Mock<IAuthRepository> _authRepositoryMock;
private readonly Mock<ITokenInfrastructure> _tokenInfraMock;
private readonly TokenService _tokenService;
public TokenServiceValidationTests()
{
_tokenInfraMock = new Mock<ITokenInfrastructure>();
_authRepositoryMock = new Mock<IAuthRepository>();
public TokenServiceValidationTests()
{
_tokenInfraMock = new Mock<ITokenInfrastructure>();
_authRepositoryMock = new Mock<IAuthRepository>();
// Set environment variables for tokens
Environment.SetEnvironmentVariable("ACCESS_TOKEN_SECRET", "test-access-secret-that-is-very-long-1234567890");
Environment.SetEnvironmentVariable("REFRESH_TOKEN_SECRET", "test-refresh-secret-that-is-very-long-1234567890");
Environment.SetEnvironmentVariable("CONFIRMATION_TOKEN_SECRET", "test-confirmation-secret-that-is-very-long-1234567890");
// Set environment variables for tokens
Environment.SetEnvironmentVariable("ACCESS_TOKEN_SECRET", "test-access-secret-that-is-very-long-1234567890");
Environment.SetEnvironmentVariable("REFRESH_TOKEN_SECRET", "test-refresh-secret-that-is-very-long-1234567890");
Environment.SetEnvironmentVariable("CONFIRMATION_TOKEN_SECRET",
"test-confirmation-secret-that-is-very-long-1234567890");
_tokenService = new TokenService(
_tokenInfraMock.Object,
_authRepositoryMock.Object
);
}
_tokenService = new TokenService(
_tokenInfraMock.Object,
_authRepositoryMock.Object
);
}
[Fact]
public async Task ValidateAccessTokenAsync_WithValidToken_ReturnsValidatedToken()
{
// Arrange
var userId = Guid.NewGuid();
const string username = "testuser";
const string token = "valid-access-token";
[Fact]
public async Task ValidateAccessTokenAsync_WithValidToken_ReturnsValidatedToken()
{
// Arrange
Guid userId = Guid.NewGuid();
const string username = "testuser";
const string token = "valid-access-token";
var claims = new List<Claim>
List<Claim> claims = new()
{
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
new(JwtRegisteredClaimNames.UniqueName, username),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var claimsIdentity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(claimsIdentity);
ClaimsIdentity claimsIdentity = new(claims);
ClaimsPrincipal principal = new(claimsIdentity);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ReturnsAsync(principal);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ReturnsAsync(principal);
// Act
var result =
await _tokenService.ValidateAccessTokenAsync(token);
// Act
ValidatedToken result =
await _tokenService.ValidateAccessTokenAsync(token);
// Assert
result.Should().NotBeNull();
result.UserId.Should().Be(userId);
result.Username.Should().Be(username);
result.Principal.Should().NotBeNull();
result.Principal.FindFirst(JwtRegisteredClaimNames.Sub)?.Value.Should().Be(userId.ToString());
}
// Assert
result.Should().NotBeNull();
result.UserId.Should().Be(userId);
result.Username.Should().Be(username);
result.Principal.Should().NotBeNull();
result.Principal.FindFirst(JwtRegisteredClaimNames.Sub)?.Value.Should().Be(userId.ToString());
}
[Fact]
public async Task ValidateRefreshTokenAsync_WithValidToken_ReturnsValidatedToken()
{
// Arrange
var userId = Guid.NewGuid();
const string username = "testuser";
const string token = "valid-refresh-token";
[Fact]
public async Task ValidateRefreshTokenAsync_WithValidToken_ReturnsValidatedToken()
{
// Arrange
Guid userId = Guid.NewGuid();
const string username = "testuser";
const string token = "valid-refresh-token";
var claims = new List<Claim>
List<Claim> claims = new()
{
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
new(JwtRegisteredClaimNames.UniqueName, username),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var claimsIdentity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(claimsIdentity);
ClaimsIdentity claimsIdentity = new(claims);
ClaimsPrincipal principal = new(claimsIdentity);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ReturnsAsync(principal);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ReturnsAsync(principal);
// Act
var result =
await _tokenService.ValidateRefreshTokenAsync(token);
// Act
ValidatedToken result =
await _tokenService.ValidateRefreshTokenAsync(token);
// Assert
result.Should().NotBeNull();
result.UserId.Should().Be(userId);
result.Username.Should().Be(username);
}
// Assert
result.Should().NotBeNull();
result.UserId.Should().Be(userId);
result.Username.Should().Be(username);
}
[Fact]
public async Task ValidateConfirmationTokenAsync_WithValidToken_ReturnsValidatedToken()
{
// Arrange
var userId = Guid.NewGuid();
const string username = "testuser";
const string token = "valid-confirmation-token";
[Fact]
public async Task ValidateConfirmationTokenAsync_WithValidToken_ReturnsValidatedToken()
{
// Arrange
Guid userId = Guid.NewGuid();
const string username = "testuser";
const string token = "valid-confirmation-token";
var claims = new List<Claim>
List<Claim> claims = new()
{
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
new(JwtRegisteredClaimNames.UniqueName, username),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var claimsIdentity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(claimsIdentity);
ClaimsIdentity claimsIdentity = new(claims);
ClaimsPrincipal principal = new(claimsIdentity);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ReturnsAsync(principal);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ReturnsAsync(principal);
// Act
var result =
await _tokenService.ValidateConfirmationTokenAsync(token);
// Act
ValidatedToken result =
await _tokenService.ValidateConfirmationTokenAsync(token);
// Assert
result.Should().NotBeNull();
result.UserId.Should().Be(userId);
result.Username.Should().Be(username);
}
// Assert
result.Should().NotBeNull();
result.UserId.Should().Be(userId);
result.Username.Should().Be(username);
}
[Fact]
public async Task ValidateAccessTokenAsync_WithInvalidToken_ThrowsUnauthorizedException()
{
// Arrange
const string token = "invalid-token";
[Fact]
public async Task ValidateAccessTokenAsync_WithInvalidToken_ThrowsUnauthorizedException()
{
// Arrange
const string token = "invalid-token";
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ThrowsAsync(new UnauthorizedException("Invalid token"));
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ThrowsAsync(new UnauthorizedException("Invalid token"));
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateAccessTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>();
}
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateAccessTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>();
}
[Fact]
public async Task ValidateAccessTokenAsync_WithExpiredToken_ThrowsUnauthorizedException()
{
// Arrange
const string token = "expired-token";
[Fact]
public async Task ValidateAccessTokenAsync_WithExpiredToken_ThrowsUnauthorizedException()
{
// Arrange
const string token = "expired-token";
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ThrowsAsync(new UnauthorizedException(
"Token has expired"
));
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ThrowsAsync(new UnauthorizedException(
"Token has expired"
));
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateAccessTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>();
}
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateAccessTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>();
}
[Fact]
public async Task ValidateAccessTokenAsync_WithMissingUserIdClaim_ThrowsUnauthorizedException()
{
// Arrange
const string username = "testuser";
const string token = "token-without-user-id";
[Fact]
public async Task ValidateAccessTokenAsync_WithMissingUserIdClaim_ThrowsUnauthorizedException()
{
// Arrange
const string username = "testuser";
const string token = "token-without-user-id";
// Claims without Sub (user ID)
var claims = new List<Claim>
// Claims without Sub (user ID)
List<Claim> claims = new()
{
new(JwtRegisteredClaimNames.UniqueName, username),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.UniqueName, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var claimsIdentity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(claimsIdentity);
ClaimsIdentity claimsIdentity = new(claims);
ClaimsPrincipal principal = new(claimsIdentity);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ReturnsAsync(principal);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ReturnsAsync(principal);
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateAccessTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>()
.WithMessage("*missing required claims*");
}
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateAccessTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>()
.WithMessage("*missing required claims*");
}
[Fact]
public async Task ValidateAccessTokenAsync_WithMissingUsernameClaim_ThrowsUnauthorizedException()
{
// Arrange
var userId = Guid.NewGuid();
const string token = "token-without-username";
[Fact]
public async Task ValidateAccessTokenAsync_WithMissingUsernameClaim_ThrowsUnauthorizedException()
{
// Arrange
Guid userId = Guid.NewGuid();
const string token = "token-without-username";
// Claims without UniqueName (username)
var claims = new List<Claim>
// Claims without UniqueName (username)
List<Claim> claims = new()
{
new(JwtRegisteredClaimNames.Sub, userId.ToString()),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Sub, userId.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var claimsIdentity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(claimsIdentity);
ClaimsIdentity claimsIdentity = new(claims);
ClaimsPrincipal principal = new(claimsIdentity);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ReturnsAsync(principal);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ReturnsAsync(principal);
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateAccessTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>()
.WithMessage("*missing required claims*");
}
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateAccessTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>()
.WithMessage("*missing required claims*");
}
[Fact]
public async Task ValidateAccessTokenAsync_WithMalformedUserId_ThrowsUnauthorizedException()
{
// Arrange
const string username = "testuser";
const string token = "token-with-malformed-user-id";
[Fact]
public async Task ValidateAccessTokenAsync_WithMalformedUserId_ThrowsUnauthorizedException()
{
// Arrange
const string username = "testuser";
const string token = "token-with-malformed-user-id";
// Claims with invalid GUID format
var claims = new List<Claim>
// Claims with invalid GUID format
List<Claim> claims = new()
{
new(JwtRegisteredClaimNames.Sub, "not-a-valid-guid"),
new(JwtRegisteredClaimNames.UniqueName, username),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Sub, "not-a-valid-guid"),
new Claim(JwtRegisteredClaimNames.UniqueName, username),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var claimsIdentity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(claimsIdentity);
ClaimsIdentity claimsIdentity = new(claims);
ClaimsPrincipal principal = new(claimsIdentity);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ReturnsAsync(principal);
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ReturnsAsync(principal);
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateAccessTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>()
.WithMessage("*malformed user ID*");
}
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateAccessTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>()
.WithMessage("*malformed user ID*");
}
[Fact]
public async Task ValidateRefreshTokenAsync_WithInvalidToken_ThrowsUnauthorizedException()
{
// Arrange
const string token = "invalid-refresh-token";
[Fact]
public async Task ValidateRefreshTokenAsync_WithInvalidToken_ThrowsUnauthorizedException()
{
// Arrange
const string token = "invalid-refresh-token";
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ThrowsAsync(new UnauthorizedException("Invalid token"));
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ThrowsAsync(new UnauthorizedException("Invalid token"));
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateRefreshTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>();
}
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateRefreshTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>();
}
[Fact]
public async Task ValidateConfirmationTokenAsync_WithInvalidToken_ThrowsUnauthorizedException()
{
// Arrange
const string token = "invalid-confirmation-token";
[Fact]
public async Task ValidateConfirmationTokenAsync_WithInvalidToken_ThrowsUnauthorizedException()
{
// Arrange
const string token = "invalid-confirmation-token";
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ThrowsAsync(new UnauthorizedException("Invalid token"));
_tokenInfraMock
.Setup(x => x.ValidateJwtAsync(token, It.IsAny<string>()))
.ThrowsAsync(new UnauthorizedException("Invalid token"));
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateConfirmationTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>();
}
}
// Act & Assert
await FluentActions.Invoking(async () =>
await _tokenService.ValidateConfirmationTokenAsync(token)
).Should().ThrowAsync<UnauthorizedException>();
}
}