using MikrocopApi.Dtos; using MikrocopApi.Exceptions; using MikrocopApi.Services; using MikrocopDb.Entities; using MikrocopDb.Repositories; using Moq; namespace MikrocopTests; [TestFixture] public sealed class AuthServiceTests { [Test] public async Task LoginAsync_ReturnsToken_WhenCredentialsAreValid() { var user = CreateUser(); var repositoryMock = new Mock(MockBehavior.Strict); var hashingServiceMock = new Mock(MockBehavior.Strict); var jwtTokenServiceMock = new Mock(MockBehavior.Strict); var request = new LoginRequestDto { UserName = user.UserName, Password = "ValidPassword!123" }; var expectedExpiry = DateTime.UtcNow.AddHours(1); repositoryMock .Setup(x => x.GetByUserNameAsync(request.UserName, It.IsAny())) .ReturnsAsync(user); hashingServiceMock .Setup(x => x.VerifyPassword(request.Password, user.PasswordHash, user.PasswordSalt)) .Returns(true); jwtTokenServiceMock .Setup(x => x.Generate(user)) .Returns(("token-value", expectedExpiry)); var sut = new AuthService(repositoryMock.Object, hashingServiceMock.Object, jwtTokenServiceMock.Object); var result = await sut.LoginAsync(request); Assert.That(result.AccessToken, Is.EqualTo("token-value")); Assert.That(result.ExpiresAtUtc, Is.EqualTo(expectedExpiry)); Assert.That(result.TokenType, Is.EqualTo("Bearer")); repositoryMock.VerifyAll(); hashingServiceMock.VerifyAll(); jwtTokenServiceMock.VerifyAll(); } [Test] public void LoginAsync_ThrowsUnauthorized_WhenUserDoesNotExist() { var repositoryMock = new Mock(MockBehavior.Strict); var hashingServiceMock = new Mock(MockBehavior.Strict); var jwtTokenServiceMock = new Mock(MockBehavior.Strict); var request = new LoginRequestDto { UserName = "missing", Password = "ValidPassword!123" }; repositoryMock .Setup(x => x.GetByUserNameAsync(request.UserName, It.IsAny())) .ReturnsAsync((UserEntity?)null); var sut = new AuthService(repositoryMock.Object, hashingServiceMock.Object, jwtTokenServiceMock.Object); Assert.ThrowsAsync(() => sut.LoginAsync(request)); repositoryMock.VerifyAll(); hashingServiceMock.VerifyNoOtherCalls(); jwtTokenServiceMock.VerifyNoOtherCalls(); } [Test] public void LoginAsync_ThrowsUnauthorized_WhenPasswordIsInvalid() { var user = CreateUser(); var repositoryMock = new Mock(MockBehavior.Strict); var hashingServiceMock = new Mock(MockBehavior.Strict); var jwtTokenServiceMock = new Mock(MockBehavior.Strict); var request = new LoginRequestDto { UserName = user.UserName, Password = "WrongPassword!123" }; repositoryMock .Setup(x => x.GetByUserNameAsync(request.UserName, It.IsAny())) .ReturnsAsync(user); hashingServiceMock .Setup(x => x.VerifyPassword(request.Password, user.PasswordHash, user.PasswordSalt)) .Returns(false); var sut = new AuthService(repositoryMock.Object, hashingServiceMock.Object, jwtTokenServiceMock.Object); Assert.ThrowsAsync(() => sut.LoginAsync(request)); repositoryMock.VerifyAll(); hashingServiceMock.VerifyAll(); jwtTokenServiceMock.VerifyNoOtherCalls(); } private static UserEntity CreateUser() { return new UserEntity { Id = Guid.NewGuid(), UserName = "test-user", FullName = "Test User", Email = "test@example.com", MobileNumber = "+38640111222", Language = "en", Culture = "en-US", PasswordHash = "hash", PasswordSalt = "salt" }; } }