2024-08-17 16:38:40 +02:00
|
|
|
|
using FluentAssertions;
|
|
|
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
|
using NSubstitute;
|
|
|
|
|
|
using System.Security.Claims;
|
2025-06-12 18:22:37 +02:00
|
|
|
|
using Vegasco.Server.Api.Authentication;
|
2024-08-17 16:38:40 +02:00
|
|
|
|
|
2025-06-12 18:22:37 +02:00
|
|
|
|
namespace Vegasco.Server.Api.Tests.Unit.Authentication;
|
2024-08-17 16:38:40 +02:00
|
|
|
|
public sealed class UserAccessorTests
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly UserAccessor _sut;
|
|
|
|
|
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
|
|
|
|
|
|
|
|
|
|
|
private static readonly string _nameClaimType = "name";
|
|
|
|
|
|
private readonly JwtOptions _jwtOptions = new()
|
|
|
|
|
|
{
|
|
|
|
|
|
NameClaimType = _nameClaimType
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
private readonly IOptions<JwtOptions> _options = Substitute.For<IOptions<JwtOptions>>();
|
|
|
|
|
|
|
|
|
|
|
|
private static readonly string _defaultUsername = "username";
|
|
|
|
|
|
private static readonly string _defaultId = "id";
|
|
|
|
|
|
private readonly ClaimsPrincipal _defaultUser = new(new ClaimsIdentity(
|
|
|
|
|
|
[
|
|
|
|
|
|
new Claim(_nameClaimType, _defaultUsername),
|
|
|
|
|
|
new Claim(ClaimTypes.NameIdentifier, _defaultId)
|
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
|
|
public UserAccessorTests()
|
|
|
|
|
|
{
|
|
|
|
|
|
_httpContextAccessor = new HttpContextAccessor
|
|
|
|
|
|
{
|
|
|
|
|
|
HttpContext = new DefaultHttpContext()
|
|
|
|
|
|
{
|
|
|
|
|
|
User = _defaultUser
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
_options.Value.Returns(_jwtOptions);
|
|
|
|
|
|
|
|
|
|
|
|
_sut = new UserAccessor(_httpContextAccessor, _options);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region GetUsername
|
|
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
|
public void GetUsername_ShouldReturnUsername_WhenOptionsNameClaimTypeMatches()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Arrange
|
|
|
|
|
|
|
|
|
|
|
|
// Act
|
2025-06-12 17:43:22 +02:00
|
|
|
|
string result = _sut.GetUsername();
|
2024-08-17 16:38:40 +02:00
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
|
result.Should().Be(_defaultUsername);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
|
public void GetUsername_ShouldReturnUsername_WhenNameClaimTypeIsNotSetAndUsernameIsInUriNameClaimType()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Arrange
|
|
|
|
|
|
_jwtOptions.NameClaimType = null;
|
|
|
|
|
|
_httpContextAccessor.HttpContext!.User = new ClaimsPrincipal(new ClaimsIdentity(
|
|
|
|
|
|
[
|
|
|
|
|
|
new Claim(ClaimTypes.Name, _defaultUsername)
|
|
|
|
|
|
]));
|
|
|
|
|
|
|
|
|
|
|
|
// Act
|
2025-06-12 17:43:22 +02:00
|
|
|
|
string result = _sut.GetUsername();
|
2024-08-17 16:38:40 +02:00
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
|
result.Should().Be(_defaultUsername);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
|
public void GetUsername_ShouldCacheUsername_WhenFirstCalled()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Arrange
|
|
|
|
|
|
_ = _sut.GetUsername();
|
|
|
|
|
|
_options.ClearReceivedCalls();
|
|
|
|
|
|
|
|
|
|
|
|
// Act
|
2025-06-12 17:43:22 +02:00
|
|
|
|
string result = _sut.GetUsername();
|
2024-08-17 16:38:40 +02:00
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
|
result.Should().Be(_defaultUsername);
|
|
|
|
|
|
_ = _options.Received(0).Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
|
public void GetUsername_ShouldThrowInvalidOperationException_WhenHttpContextIsNull()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Arrange
|
|
|
|
|
|
_httpContextAccessor.HttpContext = null;
|
|
|
|
|
|
|
|
|
|
|
|
// Act
|
2025-06-12 17:43:22 +02:00
|
|
|
|
Func<string> action = () => _sut.GetUsername();
|
2024-08-17 16:38:40 +02:00
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
|
action.Should().ThrowExactly<InvalidOperationException>()
|
|
|
|
|
|
.Which.Message.Should().Be("No HttpContext available.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
|
public void GetUsername_ShouldThrowInvalidOperationException_WhenNameClaimIsNotFound()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Arrange
|
|
|
|
|
|
_httpContextAccessor.HttpContext!.User = new ClaimsPrincipal();
|
|
|
|
|
|
|
|
|
|
|
|
// Act
|
2025-06-12 17:43:22 +02:00
|
|
|
|
Func<string> action = () => _sut.GetUsername();
|
2024-08-17 16:38:40 +02:00
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
|
action.Should().ThrowExactly<InvalidOperationException>()
|
|
|
|
|
|
.Which.Message.Should().Be($"No claim of type '{_nameClaimType}' found on the current user.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region GetUserId
|
|
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
|
public void GetUserId_ShouldReturnUserId_WhenUserIdClaimExists()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Arrange
|
|
|
|
|
|
|
|
|
|
|
|
// Act
|
2025-06-12 17:43:22 +02:00
|
|
|
|
string result = _sut.GetUserId();
|
2024-08-17 16:38:40 +02:00
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
|
result.Should().Be(_defaultId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
|
public void GetUserId_ShouldCacheUserId_WhenFirstCalled()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Arrange
|
|
|
|
|
|
_ = _sut.GetUserId();
|
|
|
|
|
|
_options.ClearReceivedCalls();
|
|
|
|
|
|
|
|
|
|
|
|
// Act
|
2025-06-12 17:43:22 +02:00
|
|
|
|
string result = _sut.GetUserId();
|
2024-08-17 16:38:40 +02:00
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
|
result.Should().Be(_defaultId);
|
|
|
|
|
|
_ = _options.Received(0).Value;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
|
public void GetUserId_ShouldThrowInvalidOperationException_WhenHttpContextIsNull()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Arrange
|
|
|
|
|
|
_httpContextAccessor.HttpContext = null;
|
|
|
|
|
|
|
|
|
|
|
|
// Act
|
2025-06-12 17:43:22 +02:00
|
|
|
|
Func<string> action = () => _sut.GetUserId();
|
2024-08-17 16:38:40 +02:00
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
|
action.Should().ThrowExactly<InvalidOperationException>()
|
|
|
|
|
|
.Which.Message.Should().Be("No HttpContext available.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
|
public void GetUserId_ShouldThrowInvalidOperationException_WhenIdClaimIsNotFound()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Arrange
|
|
|
|
|
|
_httpContextAccessor.HttpContext!.User = new ClaimsPrincipal();
|
|
|
|
|
|
|
|
|
|
|
|
// Act
|
2025-06-12 17:43:22 +02:00
|
|
|
|
Func<string> action = () => _sut.GetUserId();
|
2024-08-17 16:38:40 +02:00
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
|
action.Should().ThrowExactly<InvalidOperationException>()
|
|
|
|
|
|
.Which.Message.Should().Be($"No claim of type '{ClaimTypes.NameIdentifier}' found on the current user.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|