Files
jdescopingtool/NEW/tests/JdeScoping.Api.Tests/Controllers/AuthControllerTests.cs
T
Joseph Doherty 7e36bb4225 refactor: remove unused classes and consolidate ViewModels in Core
Remove 9 unused types from Core (duplicate extension classes, TableSpec, ColumnSpec, LotLocation), move ComponentLotViewModel and OperatorViewModel from Client to Core, and refactor DataSync.Dev to use pipeline-based configuration. Fix Login.razor to use UserInfoDto directly.
2026-01-19 00:13:12 -05:00

220 lines
8.2 KiB
C#

// NEW/tests/JdeScoping.Api.Tests/Controllers/AuthControllerTests.cs
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using JdeScoping.Api.Controllers;
using JdeScoping.Core.ApiContracts.Auth;
using JdeScoping.Core.Interfaces;
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Auth;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NSubstitute;
using Shouldly;
namespace JdeScoping.Api.Tests.Controllers;
public class AuthControllerTests
{
private readonly IAuthService _authService;
private readonly IRsaKeyService _rsaKeyService;
private readonly ILogger<AuthController> _logger;
private readonly AuthController _controller;
private readonly RSA _testRsa;
public AuthControllerTests()
{
_authService = Substitute.For<IAuthService>();
_rsaKeyService = Substitute.For<IRsaKeyService>();
_logger = Substitute.For<ILogger<AuthController>>();
// Setup test RSA key pair
_testRsa = RSA.Create(2048);
var publicKeyPem = _testRsa.ExportSubjectPublicKeyInfoPem();
_rsaKeyService.GetPublicKeyPem().Returns(publicKeyPem);
_rsaKeyService.Decrypt(Arg.Any<byte[]>())
.Returns(callInfo =>
{
var ciphertext = callInfo.Arg<byte[]>();
return _testRsa.Decrypt(ciphertext, RSAEncryptionPadding.OaepSHA256);
});
_controller = new AuthController(_authService, _rsaKeyService, _logger);
}
private EncryptedLoginRequest EncryptLoginModel(LoginModel model)
{
var json = JsonSerializer.Serialize(model);
var plaintext = Encoding.UTF8.GetBytes(json);
var ciphertext = _testRsa.Encrypt(plaintext, RSAEncryptionPadding.OaepSHA256);
return new EncryptedLoginRequest(Convert.ToBase64String(ciphertext));
}
[Fact]
public void GetPublicKey_ReturnsPublicKeyPem()
{
// Act
var result = _controller.GetPublicKey();
// Assert
result.Result.ShouldBeOfType<OkObjectResult>();
var okResult = (OkObjectResult)result.Result!;
var response = okResult.Value.ShouldBeOfType<PublicKeyResponse>();
response.PublicKeyPem.ShouldStartWith("-----BEGIN PUBLIC KEY-----");
}
[Fact]
public async Task Login_WithValidCredentials_ReturnsLoginResultWithUser()
{
// Arrange
var loginModel = new LoginModel { Username = "testuser", Password = "password123" };
var request = EncryptLoginModel(loginModel);
var user = new UserInfo
{
Dn = "CN=testuser,DC=example,DC=com",
Username = "testuser",
FirstName = "Test",
LastName = "User",
EmailAddress = "test@example.com",
Title = "Developer"
};
_authService.AuthenticateAsync("testuser", "password123", Arg.Any<CancellationToken>())
.Returns(new AuthResult(true, user, null));
var httpContext = CreateMockHttpContext();
_controller.ControllerContext = new ControllerContext { HttpContext = httpContext };
// Act
var result = await _controller.Login(request, CancellationToken.None);
// Assert
result.Result.ShouldBeOfType<OkObjectResult>();
var okResult = (OkObjectResult)result.Result!;
var loginResult = okResult.Value.ShouldBeOfType<LoginResultModel>();
loginResult.Success.ShouldBeTrue();
loginResult.User.ShouldNotBeNull();
loginResult.User.Username.ShouldBe("testuser");
}
[Fact]
public async Task Login_WithInvalidCredentials_Returns401WithError()
{
// Arrange
var loginModel = new LoginModel { Username = "testuser", Password = "wrongpassword" };
var request = EncryptLoginModel(loginModel);
_authService.AuthenticateAsync("testuser", "wrongpassword", Arg.Any<CancellationToken>())
.Returns(new AuthResult(false, null, "Incorrect username or password"));
var httpContext = CreateMockHttpContext();
_controller.ControllerContext = new ControllerContext { HttpContext = httpContext };
// Act
var result = await _controller.Login(request, CancellationToken.None);
// Assert
result.Result.ShouldBeOfType<UnauthorizedObjectResult>();
var unauthorizedResult = (UnauthorizedObjectResult)result.Result!;
var loginResult = unauthorizedResult.Value.ShouldBeOfType<LoginResultModel>();
loginResult.Success.ShouldBeFalse();
loginResult.ErrorMessage.ShouldBe("Incorrect username or password");
}
[Fact]
public async Task Login_WithInvalidEncryptedData_ReturnsBadRequest()
{
// Arrange
var request = new EncryptedLoginRequest("not-valid-base64!!!");
var httpContext = CreateMockHttpContext();
_controller.ControllerContext = new ControllerContext { HttpContext = httpContext };
// Act
var result = await _controller.Login(request, CancellationToken.None);
// Assert
result.Result.ShouldBeOfType<BadRequestObjectResult>();
var badRequestResult = (BadRequestObjectResult)result.Result!;
var loginResult = badRequestResult.Value.ShouldBeOfType<LoginResultModel>();
loginResult.Success.ShouldBeFalse();
loginResult.ErrorMessage.ShouldBe("Invalid encrypted payload");
}
[Fact]
public async Task Logout_ClearsAuthentication()
{
// Arrange
var httpContext = CreateAuthenticatedHttpContext("testuser");
_controller.ControllerContext = new ControllerContext { HttpContext = httpContext };
// Act
var result = await _controller.Logout();
// Assert
result.ShouldBeOfType<OkResult>();
}
[Fact]
public void GetCurrentUser_WhenAuthenticated_ReturnsUserInfo()
{
// Arrange
var claims = new List<Claim>
{
new(ClaimTypes.Name, "testuser"),
new(ClaimTypes.GivenName, "Test"),
new(ClaimTypes.Surname, "User"),
new(ClaimTypes.Email, "test@example.com"),
new("title", "Developer"),
new("dn", "CN=testuser,DC=example,DC=com")
};
var identity = new ClaimsIdentity(claims, "Test");
var principal = new ClaimsPrincipal(identity);
var httpContext = new DefaultHttpContext { User = principal };
_controller.ControllerContext = new ControllerContext { HttpContext = httpContext };
// Act
var result = _controller.GetCurrentUser();
// Assert
result.Result.ShouldBeOfType<OkObjectResult>();
var okResult = (OkObjectResult)result.Result!;
var user = okResult.Value.ShouldBeOfType<UserInfoDto>();
user.Username.ShouldBe("testuser");
}
private static HttpContext CreateMockHttpContext()
{
var authServiceMock = Substitute.For<IAuthenticationService>();
authServiceMock.SignOutAsync(Arg.Any<HttpContext>(), Arg.Any<string>(), Arg.Any<AuthenticationProperties>())
.Returns(Task.CompletedTask);
authServiceMock.SignInAsync(Arg.Any<HttpContext>(), Arg.Any<string>(), Arg.Any<ClaimsPrincipal>(), Arg.Any<AuthenticationProperties>())
.Returns(Task.CompletedTask);
var serviceProvider = Substitute.For<IServiceProvider>();
serviceProvider.GetService(typeof(IAuthenticationService)).Returns(authServiceMock);
var httpContext = new DefaultHttpContext
{
RequestServices = serviceProvider
};
return httpContext;
}
private static HttpContext CreateAuthenticatedHttpContext(string username)
{
var httpContext = CreateMockHttpContext();
var claims = new List<Claim>
{
new(ClaimTypes.Name, username),
new("dn", $"CN={username},DC=example,DC=com")
};
var identity = new ClaimsIdentity(claims, "Test");
httpContext.User = new ClaimsPrincipal(identity);
return httpContext;
}
}