From a0ef087110bda7bacfff0c7e0653eb0d1923c90c Mon Sep 17 00:00:00 2001 From: Artemy <109195690+stalcker2288969@users.noreply.github.com> Date: Thu, 24 Jul 2025 18:29:35 +0700 Subject: [PATCH] SessionControllerTests --- .../Controllers/SessionControllerTests.cs | 278 ++++++++++++++++++ .../UserSessions/UserSessionReaderTests.cs | 5 +- .../UserSessions/UserSessionRevokerTests.cs | 152 ++++++++++ Govor.Core/Models/GroupMembership.cs | 1 + 4 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 Govor.API.Tests/Controllers/SessionControllerTests.cs create mode 100644 Govor.Application.Tests/Services/UserSessions/UserSessionRevokerTests.cs diff --git a/Govor.API.Tests/Controllers/SessionControllerTests.cs b/Govor.API.Tests/Controllers/SessionControllerTests.cs new file mode 100644 index 0000000..3b95be3 --- /dev/null +++ b/Govor.API.Tests/Controllers/SessionControllerTests.cs @@ -0,0 +1,278 @@ +using AutoMapper; +using Govor.API.Controllers; +using Govor.Application.Interfaces.Infrastructure.Extensions; +using Govor.Application.Interfaces.UserSession; +using Govor.Contracts.DTOs; +using Govor.Core.Models; +using Govor.Data.Repositories.Exceptions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Moq; + +namespace Govor.API.Tests.Controllers; + +[TestFixture] +[TestOf(typeof(SessionController))] +public class SessionControllerTests +{ + private Mock> _loggerMock; + private Mock _currentUserServiceMock; + private Mock _userSessionReaderMock; + private Mock _userSessionRevokerMock; + private Mock _mapperMock; + private SessionController _controller; + + [SetUp] + public void SetUp() + { + _loggerMock = new Mock>(); + _currentUserServiceMock = new Mock(); + _userSessionReaderMock = new Mock(); + _userSessionRevokerMock = new Mock(); + _mapperMock = new Mock(); + _controller = new SessionController( + _loggerMock.Object, + _currentUserServiceMock.Object, + _userSessionReaderMock.Object, + _userSessionRevokerMock.Object, + _mapperMock.Object); + } + + [Test] + public async Task GetAllSessions_Successful_ReturnsOkWithMappedSessions() + { + // Arrange + var userId = Guid.NewGuid(); + var sessions = new List { new UserSession { Id = Guid.NewGuid(), UserId = userId } }; + var sessionDtos = new List { new SessionDto { Id = sessions[0].Id } }; + _currentUserServiceMock.Setup(s => s.GetCurrentUserId()).Returns(userId); + _userSessionReaderMock.Setup(r => r.GetAllSessionsAsync(userId)).ReturnsAsync(sessions); + _mapperMock.Setup(m => m.Map>(sessions)).Returns(sessionDtos); + + // Act + var result = await _controller.GetAllSessions(); + + // Assert + Assert.That(result, Is.TypeOf()); + var okResult = result as OkObjectResult; + Assert.That(okResult.Value, Is.EqualTo(sessionDtos)); + _loggerMock.VerifyNoOtherCalls(); + } + + [Test] + public async Task GetAllSessions_UnauthorizedAccess_ReturnsForbid() + { + // Arrange + var userId = Guid.NewGuid(); + var exception = new UnauthorizedAccessException("Unauthorized"); + _currentUserServiceMock.Setup(s => s.GetCurrentUserId()).Returns(userId); + _userSessionReaderMock.Setup(r => r.GetAllSessionsAsync(userId)).ThrowsAsync(exception); + + // Act + var result = await _controller.GetAllSessions(); + + // Assert + Assert.That(result, Is.TypeOf()); + _loggerMock.Verify(l => l.Log( + LogLevel.Warning, + It.IsAny(), + It.IsAny(), + exception, + It.IsAny>()), Times.Once()); + } + + [Test] + public async Task GetAllSessions_UnexpectedError_Returns500() + { + // Arrange + var userId = Guid.NewGuid(); + var exception = new Exception("Unexpected error"); + _currentUserServiceMock.Setup(s => s.GetCurrentUserId()).Returns(userId); + _userSessionReaderMock.Setup(r => r.GetAllSessionsAsync(userId)).ThrowsAsync(exception); + + // Act + var result = await _controller.GetAllSessions(); + + // Assert + Assert.That(result, Is.TypeOf()); + var statusResult = result as ObjectResult; + Assert.That(statusResult.StatusCode, Is.EqualTo(500)); + Assert.That(statusResult.Value, Is.EqualTo("Unexpected Error! Please try again later.")); + _loggerMock.Verify(l => l.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + exception, + It.IsAny>()), Times.Once()); + } + + [Test] + public async Task CloseSession_ValidSessionId_ReturnsOk() + { + // Arrange + var userId = Guid.NewGuid(); + var sessionId = Guid.NewGuid(); + _currentUserServiceMock.Setup(s => s.GetCurrentUserId()).Returns(userId); + + // Act + var result = await _controller.CloseSession(sessionId); + + // Assert + Assert.That(result, Is.TypeOf()); + _userSessionRevokerMock.Verify(r => r.CloseSessionByIdAsync(sessionId, userId), Times.Once()); + _loggerMock.VerifyNoOtherCalls(); + } + + [Test] + public async Task CloseSession_EmptySessionId_ReturnsBadRequest() + { + // Arrange + var sessionId = Guid.Empty; + + // Act + var result = await _controller.CloseSession(sessionId); + + // Assert + Assert.That(result, Is.TypeOf()); + var badRequestResult = result as BadRequestObjectResult; + Assert.That(badRequestResult.Value, Is.EqualTo("Invalid sessionId.")); + _userSessionRevokerMock.Verify(r => r.CloseSessionByIdAsync(It.IsAny(), It.IsAny()), Times.Never()); + _loggerMock.VerifyNoOtherCalls(); + } + + [Test] + public async Task CloseSession_UnauthorizedAccess_ReturnsForbid() + { + // Arrange + var userId = Guid.NewGuid(); + var sessionId = Guid.NewGuid(); + var exception = new UnauthorizedAccessException("Unauthorized"); + _currentUserServiceMock.Setup(s => s.GetCurrentUserId()).Returns(userId); + _userSessionRevokerMock.Setup(r => r.CloseSessionByIdAsync(sessionId, userId)).ThrowsAsync(exception); + + // Act + var result = await _controller.CloseSession(sessionId); + + // Assert + Assert.That(result, Is.TypeOf()); + _loggerMock.Verify(l => l.Log( + LogLevel.Warning, + It.IsAny(), + It.IsAny(), + exception, + It.IsAny>()), Times.Once()); + } + + [Test] + public async Task CloseSession_NotFound_ReturnsNotFound() + { + // Arrange + var userId = Guid.NewGuid(); + var sessionId = Guid.NewGuid(); + var exception = new NotFoundException("Session not found"); + _currentUserServiceMock.Setup(s => s.GetCurrentUserId()).Returns(userId); + _userSessionRevokerMock.Setup(r => r.CloseSessionByIdAsync(sessionId, userId)).ThrowsAsync(exception); + + // Act + var result = await _controller.CloseSession(sessionId); + + // Assert + Assert.That(result, Is.TypeOf()); + var notFoundResult = result as NotFoundObjectResult; + Assert.That(notFoundResult.Value, Is.EqualTo("Session not found")); + _loggerMock.Verify(l => l.Log( + LogLevel.Warning, + It.IsAny(), + It.IsAny(), + exception, + It.IsAny>()), Times.Once()); + } + + [Test] + public async Task CloseSession_InvalidOperation_ReturnsBadRequest() + { + // Arrange + var userId = Guid.NewGuid(); + var sessionId = Guid.NewGuid(); + var exception = new InvalidOperationException("Invalid operation"); + _currentUserServiceMock.Setup(s => s.GetCurrentUserId()).Returns(userId); + _userSessionRevokerMock.Setup(r => r.CloseSessionByIdAsync(sessionId, userId)).ThrowsAsync(exception); + + // Act + var result = await _controller.CloseSession(sessionId); + + // Assert + Assert.That(result, Is.TypeOf()); + var badRequestResult = result as BadRequestObjectResult; + Assert.That(badRequestResult.Value, Is.EqualTo("Invalid operation")); + _loggerMock.Verify(l => l.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + exception, + It.IsAny>()), Times.Once()); + } + + [Test] + public async Task CloseAllSessions_Successful_ReturnsOk() + { + // Arrange + var userId = Guid.NewGuid(); + _currentUserServiceMock.Setup(s => s.GetCurrentUserId()).Returns(userId); + + // Act + var result = await _controller.CloseAllSessions(); + + // Assert + Assert.That(result, Is.TypeOf()); + _userSessionRevokerMock.Verify(r => r.CloseAllSessionsAsync(userId), Times.Once()); + _loggerMock.VerifyNoOtherCalls(); + } + + [Test] + public async Task CloseAllSessions_UnauthorizedAccess_ReturnsForbid() + { + // Arrange + var userId = Guid.NewGuid(); + var exception = new UnauthorizedAccessException("Unauthorized"); + _currentUserServiceMock.Setup(s => s.GetCurrentUserId()).Returns(userId); + _userSessionRevokerMock.Setup(r => r.CloseAllSessionsAsync(userId)).ThrowsAsync(exception); + + // Act + var result = await _controller.CloseAllSessions(); + + // Assert + Assert.That(result, Is.TypeOf()); + _loggerMock.Verify(l => l.Log( + LogLevel.Warning, + It.IsAny(), + It.IsAny(), + exception, + It.IsAny>()), Times.Once()); + } + + [Test] + public async Task CloseAllSessions_UnexpectedError_Returns500() + { + // Arrange + var userId = Guid.NewGuid(); + var exception = new Exception("Unexpected error"); + _currentUserServiceMock.Setup(s => s.GetCurrentUserId()).Returns(userId); + _userSessionRevokerMock.Setup(r => r.CloseAllSessionsAsync(userId)).ThrowsAsync(exception); + + // Act + var result = await _controller.CloseAllSessions(); + + // Assert + Assert.That(result, Is.TypeOf()); + var statusResult = result as ObjectResult; + Assert.That(statusResult.StatusCode, Is.EqualTo(500)); + Assert.That(statusResult.Value, Is.EqualTo("Unexpected Error! Please try again later.")); + _loggerMock.Verify(l => l.Log( + LogLevel.Error, + It.IsAny(), + It.IsAny(), + exception, + It.IsAny>()), Times.Once()); + } +} \ No newline at end of file diff --git a/Govor.Application.Tests/Services/UserSessions/UserSessionReaderTests.cs b/Govor.Application.Tests/Services/UserSessions/UserSessionReaderTests.cs index 145c2d6..1b8e698 100644 --- a/Govor.Application.Tests/Services/UserSessions/UserSessionReaderTests.cs +++ b/Govor.Application.Tests/Services/UserSessions/UserSessionReaderTests.cs @@ -34,7 +34,10 @@ public class UserSessionReaderTests public async Task GetAllUserSessionsAsync_ShouldReturnAllUserSessions() { // Arrange - var sessios = _fixture.CreateMany().ToList(); + var sessios = _fixture.Build() + .With(f => f.IsRevoked, false) + .CreateMany().ToList(); + var userId = Guid.NewGuid(); _mockUserSessionsRepository.Setup(f => f.GetByUserIdAsync(userId)) diff --git a/Govor.Application.Tests/Services/UserSessions/UserSessionRevokerTests.cs b/Govor.Application.Tests/Services/UserSessions/UserSessionRevokerTests.cs new file mode 100644 index 0000000..26066ee --- /dev/null +++ b/Govor.Application.Tests/Services/UserSessions/UserSessionRevokerTests.cs @@ -0,0 +1,152 @@ +using Govor.Application.Services.UserSessions; +using Govor.Core.Models; +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 UserSessionRevoker _revoker; + + [SetUp] + public void SetUp() + { + _sessionsRepositoryMock = new Mock(); + _loggerMock = new Mock>(); + _revoker = new UserSessionRevoker(_sessionsRepositoryMock.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()); + _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()); + } + + [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()); + _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()); + } + + [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()); + _sessionsRepositoryMock.Verify(r => r.UpdateAsync(sessions[1]), 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()); + _sessionsRepositoryMock.Verify(r => r.UpdateAsync(It.IsAny()), Times.Never()); + } +} \ No newline at end of file diff --git a/Govor.Core/Models/GroupMembership.cs b/Govor.Core/Models/GroupMembership.cs index 866cc59..509b19e 100644 --- a/Govor.Core/Models/GroupMembership.cs +++ b/Govor.Core/Models/GroupMembership.cs @@ -7,4 +7,5 @@ public class GroupMembership public Guid UserId { get; set; } public Guid? InvitationId { get; set; } public bool IsBanned { get; set; } + public DateTime MemberSince { get; set; } } \ No newline at end of file