// 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 _logger; private readonly AuthController _controller; private readonly RSA _testRsa; public AuthControllerTests() { _authService = Substitute.For(); _rsaKeyService = Substitute.For(); _logger = Substitute.For>(); // Setup test RSA key pair _testRsa = RSA.Create(2048); var publicKeyPem = _testRsa.ExportSubjectPublicKeyInfoPem(); _rsaKeyService.GetPublicKeyPem().Returns(publicKeyPem); _rsaKeyService.Decrypt(Arg.Any()) .Returns(callInfo => { var ciphertext = callInfo.Arg(); 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(); var okResult = (OkObjectResult)result.Result!; var response = okResult.Value.ShouldBeOfType(); 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()) .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(); var okResult = (OkObjectResult)result.Result!; var loginResult = okResult.Value.ShouldBeOfType(); 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()) .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(); var unauthorizedResult = (UnauthorizedObjectResult)result.Result!; var loginResult = unauthorizedResult.Value.ShouldBeOfType(); 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(); var badRequestResult = (BadRequestObjectResult)result.Result!; var loginResult = badRequestResult.Value.ShouldBeOfType(); 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(); } [Fact] public void GetCurrentUser_WhenAuthenticated_ReturnsUserInfo() { // Arrange var claims = new List { 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(); var okResult = (OkObjectResult)result.Result!; var user = okResult.Value.ShouldBeOfType(); user.Username.ShouldBe("testuser"); } private static HttpContext CreateMockHttpContext() { var authServiceMock = Substitute.For(); authServiceMock.SignOutAsync(Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.CompletedTask); authServiceMock.SignInAsync(Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) .Returns(Task.CompletedTask); var serviceProvider = Substitute.For(); 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 { 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; } }