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 _authRepositoryMock; private readonly Mock _tokenInfraMock; private readonly TokenService _tokenService; public TokenServiceRefreshTests() { _tokenInfraMock = new Mock(); _authRepositoryMock = new Mock(); // 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 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())) .ReturnsAsync(principal); // Mock the generation of new tokens _tokenInfraMock .Setup(x => x.GenerateJwt(userId, username, It.IsAny(), It.IsAny())) .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(), It.IsAny(), It.IsAny(), It.IsAny() ), 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())) .ThrowsAsync(new UnauthorizedException("Invalid refresh token")); // Act & Assert await FluentActions .Invoking(async () => await _tokenService.RefreshTokenAsync(invalidToken)) .Should() .ThrowAsync(); } [Fact] public async Task RefreshTokenAsync_WithExpiredRefreshToken_ThrowsUnauthorizedException() { // Arrange const string expiredToken = "expired-refresh-token"; _tokenInfraMock .Setup(x => x.ValidateJwtAsync(expiredToken, It.IsAny())) .ThrowsAsync(new UnauthorizedException("Refresh token has expired")); // Act & Assert await FluentActions .Invoking(async () => await _tokenService.RefreshTokenAsync(expiredToken)) .Should() .ThrowAsync(); } [Fact] public async Task RefreshTokenAsync_WithNonExistentUser_ThrowsUnauthorizedException() { // Arrange Guid userId = Guid.NewGuid(); const string username = "testuser"; const string refreshToken = "valid-refresh-token"; List 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())) .ReturnsAsync(principal); _authRepositoryMock.Setup(x => x.GetUserByIdAsync(userId)).ReturnsAsync((UserAccount?)null); // Act & Assert await FluentActions .Invoking(async () => await _tokenService.RefreshTokenAsync(refreshToken)) .Should() .ThrowAsync() .WithMessage("*User account not found*"); } }