Created SessionController and UserSessionReader

+ tests for UserSessionReader
This commit is contained in:
Artemy
2025-07-24 17:03:51 +07:00
parent 19f1ccdfd5
commit 15f9370dbe
7 changed files with 224 additions and 1 deletions
@@ -87,6 +87,8 @@ public static class ConfigurationProgramExtensions
services.AddAutoMapper(typeof(MappingProfile)); services.AddAutoMapper(typeof(MappingProfile));
services.AddScoped<IHubUserAccessor, HubUserAccessor>(); services.AddScoped<IHubUserAccessor, HubUserAccessor>();
services.AddScoped<IUserSessionReader, UserSessionReader>();
} }
public static void AddRepositories(this IServiceCollection services) public static void AddRepositories(this IServiceCollection services)
@@ -21,5 +21,7 @@ public class MappingProfile : Profile
.AfterMap<UserToUserDtoMappingAction>(); .AfterMap<UserToUserDtoMappingAction>();
CreateMap<Friendship, FriendshipDto>(); CreateMap<Friendship, FriendshipDto>();
CreateMap<UserSession, SessionDto>();
} }
} }
+109
View File
@@ -0,0 +1,109 @@
using AutoMapper;
using Govor.Application.Interfaces.Infrastructure.Extensions;
using Govor.Application.Interfaces.UserSession;
using Govor.Contracts.DTOs;
using Govor.Data.Repositories.Exceptions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Govor.API.Controllers;
[ApiController]
[Authorize(Roles = "Admin,User")]
[Route("api/[controller]")]
public class SessionController : Controller
{
private readonly ILogger<SessionController> _logger;
private readonly ICurrentUserService _currentUserService;
private readonly IUserSessionReader _userSessionReader;
private readonly IUserSessionRevoker _userSessionRevoker;
private readonly IMapper _mapper;
public SessionController(
ILogger<SessionController> logger,
ICurrentUserService currentUserService,
IUserSessionReader userSessionReader,
IUserSessionRevoker userSessionRevoker,
IMapper mapper)
{
_logger = logger;
_currentUserService = currentUserService;
_userSessionReader = userSessionReader;
_userSessionRevoker = userSessionRevoker;
_mapper = mapper;
}
[HttpGet("all")]
public async Task<IActionResult> GetAllSessions()
{
try
{
var sessions = await _userSessionReader.GetAllSessionsAsync(_currentUserService.GetCurrentUserId());
return Ok(_mapper.Map<List<SessionDto>>(sessions));
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, ex.Message);
return Forbid(ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, "Unexpected Error! Please try again later.");
}
}
[HttpDelete("close/{sessionId}")]
public async Task<IActionResult> CloseSession(Guid sessionId)
{
try
{
if (sessionId == Guid.Empty)
return BadRequest("Invalid sessionId.");
await _userSessionRevoker.CloseSessionByIdAsync(sessionId,
_currentUserService.GetCurrentUserId());
return Ok();
}
catch (InvalidOperationException ex)
{
_logger.LogError(ex, ex.Message);
return BadRequest(ex.Message);
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, ex.Message);
return Forbid(ex.Message);
}
catch (NotFoundException ex)
{
_logger.LogWarning(ex, ex.Message);
return NotFound(ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, "Unexpected Error! Please try again later.");
}
}
[HttpDelete("close/all")]
public async Task<IActionResult> CloseAllSessions()
{
try
{
await _userSessionRevoker.CloseAllSessionsAsync(_currentUserService.GetCurrentUserId());
return Ok();
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, ex.Message);
return Forbid(ex.Message);
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
return StatusCode(500, "Unexpected Error! Please try again later.");
}
}
}
@@ -0,0 +1,66 @@
using AutoFixture;
using Govor.Application.Interfaces.UserSession;
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(UserSessionReader))]
public class UserSessionReaderTests
{
private Mock<IUserSessionsRepository> _mockUserSessionsRepository;
private Mock<ILogger<UserSessionReader>> _mockLogger;
private Fixture _fixture;
private IUserSessionReader _userSessionReader;
[SetUp]
public void SetUp()
{
_fixture = new Fixture();
_mockUserSessionsRepository = new Mock<IUserSessionsRepository>();
_mockLogger = new Mock<ILogger<UserSessionReader>>();
_userSessionReader = new UserSessionReader(_mockUserSessionsRepository.Object,
_mockLogger.Object);
}
[Test]
public async Task GetAllUserSessionsAsync_ShouldReturnAllUserSessions()
{
// Arrange
var sessios = _fixture.CreateMany<UserSession>().ToList();
var userId = Guid.NewGuid();
_mockUserSessionsRepository.Setup(f => f.GetByUserIdAsync(userId))
.ReturnsAsync(sessios);
// Act
var result = await _userSessionReader.GetAllSessionsAsync(userId);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.EquivalentTo(sessios));
}
[Test]
public async Task GetAllUserSessionsAsync_ThrowsNotFoundByKeyException_ReturnsEmptyList()
{
// Arrange
var userId = Guid.NewGuid();
_mockUserSessionsRepository.Setup(f => f.GetByUserIdAsync(userId))
.ThrowsAsync(new NotFoundByKeyException<Guid>(userId));
// Act
var result = await _userSessionReader.GetAllSessionsAsync(userId);
// Assert
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.Empty);
}
}
@@ -3,6 +3,6 @@ namespace Govor.Application.Interfaces.UserSession;
public interface IUserSessionRevoker public interface IUserSessionRevoker
{ {
Task CloseSessionByRefreshTokenAsync(string refreshToken); Task CloseSessionByIdAsync(Guid sessionId, Guid userId);
Task CloseAllSessionsAsync(Guid userId); Task CloseAllSessionsAsync(Guid userId);
} }
@@ -0,0 +1,34 @@
using Govor.Application.Interfaces.UserSession;
using Govor.Core.Models;
using Govor.Core.Repositories.UserSessionsRepository;
using Govor.Data.Repositories.Exceptions;
using Microsoft.Extensions.Logging;
namespace Govor.Application.Services.UserSessions;
public class UserSessionReader : IUserSessionReader
{
private readonly IUserSessionsRepository _repository;
private readonly ILogger<UserSessionReader> _logger;
public UserSessionReader(IUserSessionsRepository repository, ILogger<UserSessionReader> logger)
{
_repository = repository;
_logger = logger;
}
public async Task<List<UserSession>> GetAllSessionsAsync(Guid userId)
{
try
{
_logger.LogInformation($"Getting all sessions for user {userId}");
var sessions = await _repository.GetByUserIdAsync(userId);
return sessions;
}
catch (NotFoundByKeyException<Guid> ex)
{
_logger.LogWarning("The user has no active sessions.");
return [];
}
}
}
+10
View File
@@ -0,0 +1,10 @@
namespace Govor.Contracts.DTOs;
public record SessionDto
{
public Guid Id { get; init; }
public string DeviceInfo { get; init; } = string.Empty;
public DateTime CreatedAt { get; init; }
public DateTime ExpiresAt { get; init; }
public bool IsRevoked { get; init; }
}