mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-07-16 09:37:23 +00:00
174 lines
5.9 KiB
C#
174 lines
5.9 KiB
C#
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Security.Claims;
|
|
using Domain.Entities;
|
|
using Domain.Exceptions;
|
|
using Features.Auth.Repository;
|
|
using Features.Auth.Services;
|
|
using FluentAssertions;
|
|
using Infrastructure.Jwt;
|
|
using Moq;
|
|
|
|
namespace Features.Auth.Tests.Services;
|
|
|
|
public class TokenServiceRefreshTests
|
|
{
|
|
private readonly Mock<IAuthRepository> _authRepositoryMock;
|
|
private readonly Mock<ITokenInfrastructure> _tokenInfraMock;
|
|
private readonly TokenService _tokenService;
|
|
|
|
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"
|
|
);
|
|
|
|
_tokenService = new TokenService(_tokenInfraMock.Object, _authRepositoryMock.Object);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RefreshTokenAsync_WithValidRefreshToken_ReturnsNewTokens()
|
|
{
|
|
// 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);
|
|
|
|
UserAccount userAccount = new()
|
|
{
|
|
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
|
|
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*");
|
|
}
|
|
}
|