using Govor.Application.Services.UserSessions; using Govor.Core.Models; using Govor.Core.Models.Users; using Govor.Core.Repositories.PushTokens; using Govor.Core.Repositories.UserSessionsRepository; using Govor.Data.Repositories.Exceptions; using Microsoft.Extensions.Logging; using Moq; namespace Govor.Application.Tests.Services.UserSessions; [TestFixture] [TestOf(typeof(UserSessionRevoker))] public class UserSessionRevokerTests { private Mock _sessionsRepositoryMock; private Mock> _loggerMock; private Mock _pushTokenRepositoryMock; private UserSessionRevoker _revoker; [SetUp] public void SetUp() { _sessionsRepositoryMock = new Mock(); _loggerMock = new Mock>(); _pushTokenRepositoryMock = new Mock(); _revoker = new UserSessionRevoker(_sessionsRepositoryMock.Object, _pushTokenRepositoryMock.Object, _loggerMock.Object); } [Test] public async Task CloseSessionByIdAsync_ValidSessionAndUserId_SetsIsRevokedAndUpdates() { // Arrange var sessionId = Guid.NewGuid(); var userId = Guid.NewGuid(); var session = new UserSession { Id = sessionId, UserId = userId, IsRevoked = false }; _sessionsRepositoryMock.Setup(r => r.GetByIdAsync(sessionId)).ReturnsAsync(session); // Act await _revoker.CloseSessionByIdAsync(sessionId, userId); // Assert Assert.That(session.IsRevoked, Is.True); _sessionsRepositoryMock.Verify(r => r.UpdateAsync(session), Times.Once()); _pushTokenRepositoryMock.Verify(r => r.DeactivateTokenBySessionAsync(sessionId), Times.Once()); _loggerMock.VerifyNoOtherCalls(); } [Test] public void CloseSessionByIdAsync_UnauthorizedUser_ThrowsUnauthorizedAccessException() { // Arrange var sessionId = Guid.NewGuid(); var userId = Guid.NewGuid(); var differentUserId = Guid.NewGuid(); var session = new UserSession { Id = sessionId, UserId = userId, IsRevoked = false }; _sessionsRepositoryMock.Setup(r => r.GetByIdAsync(sessionId)).ReturnsAsync(session); // Act & Assert var ex = Assert.ThrowsAsync(() => _revoker.CloseSessionByIdAsync(sessionId, differentUserId)); Assert.That(ex.Message, Contains.Substring($"User {differentUserId} does not belong to this session {sessionId}")); _loggerMock.Verify(l => l.Log( LogLevel.Warning, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once()); _sessionsRepositoryMock.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never()); _pushTokenRepositoryMock.Verify(r => r.DeactivateTokenBySessionAsync(sessionId), Times.Never()); } [Test] public async Task CloseSessionByIdAsync_AlreadyRevokedSession_DoesNotUpdate() { // Arrange var sessionId = Guid.NewGuid(); var userId = Guid.NewGuid(); var session = new UserSession { Id = sessionId, UserId = userId, IsRevoked = true }; _sessionsRepositoryMock.Setup(r => r.GetByIdAsync(sessionId)).ReturnsAsync(session); // Act await _revoker.CloseSessionByIdAsync(sessionId, userId); // Assert _sessionsRepositoryMock.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never()); _pushTokenRepositoryMock.Verify(r => r.DeactivateTokenBySessionAsync(sessionId), Times.Never()); _loggerMock.VerifyNoOtherCalls(); } [Test] public void CloseSessionByIdAsync_NonExistentSession_ThrowsNotFoundException() { // Arrange var sessionId = Guid.NewGuid(); var userId = Guid.NewGuid(); _sessionsRepositoryMock.Setup(r => r.GetByIdAsync(sessionId)) .ThrowsAsync(new NotFoundByKeyException(sessionId)); // Act & Assert var ex = Assert.ThrowsAsync(() => _revoker.CloseSessionByIdAsync(sessionId, userId)); Assert.That(ex.Message, Is.EqualTo("Session not found")); _loggerMock.Verify(l => l.Log( LogLevel.Warning, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once()); _sessionsRepositoryMock.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never()); _pushTokenRepositoryMock.Verify(r => r.DeactivateTokenBySessionAsync(sessionId), Times.Never()); } [Test] public async Task CloseAllSessionsAsync_MultipleSessions_RevokesNonRevokedSessions() { // Arrange var userId = Guid.NewGuid(); var sessions = new List { new UserSession { Id = Guid.NewGuid(), UserId = userId, IsRevoked = false }, new UserSession { Id = Guid.NewGuid(), UserId = userId, IsRevoked = true }, new UserSession { Id = Guid.NewGuid(), UserId = userId, IsRevoked = false } }; _sessionsRepositoryMock.Setup(r => r.GetByUserIdAsync(userId)).ReturnsAsync(sessions); // Act await _revoker.CloseAllSessionsAsync(userId); // Assert Assert.That(sessions[0].IsRevoked, Is.True); Assert.That(sessions[1].IsRevoked, Is.True); Assert.That(sessions[2].IsRevoked, Is.True); _sessionsRepositoryMock.Verify(r => r.UpdateAsync(sessions[0]), Times.Once()); _sessionsRepositoryMock.Verify(r => r.UpdateAsync(sessions[2]), Times.Once()); _pushTokenRepositoryMock.Verify(r => r.DeactivateTokenBySessionAsync(sessions[0].Id), Times.Once()); _pushTokenRepositoryMock.Verify(r => r.DeactivateTokenBySessionAsync(sessions[2].Id), Times.Once()); _sessionsRepositoryMock.Verify(r => r.UpdateAsync(sessions[1]), Times.Never()); _pushTokenRepositoryMock.Verify(r => r.DeactivateTokenBySessionAsync(sessions[1].Id), Times.Never); _loggerMock.VerifyNoOtherCalls(); } [Test] public void CloseAllSessionsAsync_NoSessions_ThrowsNotFoundException() { // Arrange var userId = Guid.NewGuid(); _sessionsRepositoryMock.Setup(r => r.GetByUserIdAsync(userId)) .ThrowsAsync(new NotFoundByKeyException(userId)); // Act & Assert var ex = Assert.ThrowsAsync(() => _revoker.CloseAllSessionsAsync(userId)); Assert.That(ex.Message, Is.EqualTo("Session not found")); _loggerMock.Verify(l => l.Log( LogLevel.Warning, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>()), Times.Once()); _pushTokenRepositoryMock.Verify(r => r.DeactivateTokenBySessionAsync(It.IsAny()), Times.Never); _sessionsRepositoryMock.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never()); } }