From f53ee6644b6b0c0797302506660f9b057e830f09 Mon Sep 17 00:00:00 2001 From: Artemy <109195690+stalcker2288969@users.noreply.github.com> Date: Mon, 30 Jun 2025 17:37:02 +0700 Subject: [PATCH] Refactor user context handling with CurrentUserService Introduced ICurrentUserService and its implementation to centralize retrieval of the current user's ID from claims. Updated FriendsController to use this service instead of direct claim access. Registered the service and IHttpContextAccessor in DI. Added integration tests for FriendsController. Moved UsernameValidator to Infrastructure/Validators. --- .../Controllers/AuthControllerTests.cs | 6 +- .../Controllers/FriendsControllerTests.cs | 115 ++++++++++++++++++ Govor.API/Controllers/FriendsController.cs | 29 ++--- .../ConfigurationProgramExtensions.cs | 5 + .../Extensions/CurrentUserService.cs | 26 ++++ .../Validators/UsernameValidator.cs | 0 .../Extensions/ICurrentUserService.cs | 6 + 7 files changed, 167 insertions(+), 20 deletions(-) create mode 100644 Govor.API.Tests/IntegrationTests/Controllers/FriendsControllerTests.cs create mode 100644 Govor.Application/Infrastructure/Extensions/CurrentUserService.cs rename Govor.Application/{ => Infrastructure}/Validators/UsernameValidator.cs (100%) create mode 100644 Govor.Application/Interfaces/Infrastructure/Extensions/ICurrentUserService.cs diff --git a/Govor.API.Tests/IntegrationTests/Controllers/AuthControllerTests.cs b/Govor.API.Tests/IntegrationTests/Controllers/AuthControllerTests.cs index b449ffa..82808d2 100644 --- a/Govor.API.Tests/IntegrationTests/Controllers/AuthControllerTests.cs +++ b/Govor.API.Tests/IntegrationTests/Controllers/AuthControllerTests.cs @@ -86,9 +86,9 @@ public class AuthControllerTests var result = await _controller.Register(request); // Assert - Assert.That(result, Is.InstanceOf()); - var badRequestResult = result as BadRequestObjectResult; - Assert.That(badRequestResult.Value, Is.EqualTo("Invite link invalid.")); + Assert.That(result, Is.InstanceOf()); + var notFoundObjectResult = result as NotFoundObjectResult; + Assert.That(notFoundObjectResult.Value, Is.EqualTo("Invite link invalid.")); } [Test] diff --git a/Govor.API.Tests/IntegrationTests/Controllers/FriendsControllerTests.cs b/Govor.API.Tests/IntegrationTests/Controllers/FriendsControllerTests.cs new file mode 100644 index 0000000..124d67d --- /dev/null +++ b/Govor.API.Tests/IntegrationTests/Controllers/FriendsControllerTests.cs @@ -0,0 +1,115 @@ +using AutoFixture; +using Govor.API.Controllers; +using Govor.Application.Exceptions.FriendsService; +using Govor.Application.Interfaces; +using Govor.Application.Interfaces.Infrastructure.Extensions; +using Govor.Contracts.DTOs; +using Govor.Core.Models; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Moq; + +namespace Govor.API.Tests.IntegrationTests.Controllers; + +[TestFixture] +public class FriendsControllerTests +{ + private Fixture _fixture; + private Mock> _loggerMock; + private Mock _friendsServiceMock; + private Mock _currentUserServiceMock; + private FriendsController _controller; + + [SetUp] + public void SetUp() + { + _fixture = new Fixture(); + _fixture.Behaviors.OfType().ToList().ForEach(b => _fixture.Behaviors.Remove(b)); + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + _loggerMock = new Mock>(); + _friendsServiceMock = new Mock(); + _currentUserServiceMock = new Mock(); + + _controller = new FriendsController( + _loggerMock.Object, + _friendsServiceMock.Object, + _currentUserServiceMock.Object + ); + } + + // Tests for Search action + [Test] + public async Task Search_ValidRequest_ReturnsOkResult() + { + var users = _fixture.CreateMany().ToList(); + var userId = _fixture.Create(); + var query = _fixture.Create(); + + _currentUserServiceMock.Setup(c => c.GetCurrentUserId()).Returns(userId); + + _friendsServiceMock.Setup(f => f.SearchUsersAsync(query, userId)) + .ReturnsAsync(users); + + // Act + var result = await _controller.Search(query); + + var okResult = result as OkObjectResult; + dynamic value = okResult.Value; + + List userDtos = value as List; + + // Assert + Assert.That(value, Is.Not.Null); + Assert.That(value.Count, Is.EqualTo(users.Count)); + Assert.That(userDtos.Select(u => u.Id), Is.EqualTo(users.Select(u => u.Id))); + } + + [Test] + public async Task Search_InvalidQuery_BadRequest() + { + // Act + var result = await _controller.Search(string.Empty); + // Assert + Assert.That(result, Is.InstanceOf()); + var badRequestResult = result as BadRequestObjectResult; + Assert.That(badRequestResult.Value, Is.EqualTo("Query cannot be empty")); + } + + + [Test] + public async Task Search_NotFound_IfThrowsSearchUsersException() + { + // Arrange + _friendsServiceMock.Setup(f => f.SearchUsersAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new SearchUsersException(_fixture.Create())); + + // Act + var result = await _controller.Search(_fixture.Create()); + // Assert + Assert.That(result, Is.InstanceOf()); + } + + [Test] + public async Task Search_StatusCode500_IfThrowsSomeException() + { + // Arrange + _friendsServiceMock.Setup(f => f.SearchUsersAsync(It.IsAny(), It.IsAny())) + .ThrowsAsync(new Exception(_fixture.Create())); + + // Act + var result = await _controller.Search(_fixture.Create()); + // Assert + Assert.That(result, Is.InstanceOf()); + var objectResult = result as ObjectResult; + Assert.That(objectResult.StatusCode, Is.EqualTo(500)); + } + + + + [TearDown] + public void TearDown() + { + _controller.Dispose(); + } +} \ No newline at end of file diff --git a/Govor.API/Controllers/FriendsController.cs b/Govor.API/Controllers/FriendsController.cs index 5f1e7c4..7000c2a 100644 --- a/Govor.API/Controllers/FriendsController.cs +++ b/Govor.API/Controllers/FriendsController.cs @@ -1,5 +1,6 @@ using Govor.Application.Exceptions.FriendsService; using Govor.Application.Interfaces; +using Govor.Application.Interfaces.Infrastructure.Extensions; using Govor.Contracts.DTOs; using Govor.Core.Models; using Microsoft.AspNetCore.Authorization; @@ -14,11 +15,15 @@ public class FriendsController : Controller { private readonly ILogger _logger; private readonly IFriendsService _friendsService; - - public FriendsController(ILogger logger, IFriendsService friendsService) + private readonly ICurrentUserService _currentUserService; + + public FriendsController(ILogger logger, + IFriendsService friendsService, + ICurrentUserService currentUserService) { _logger = logger; _friendsService = friendsService; + _currentUserService = currentUserService; } [HttpGet("search")] @@ -29,7 +34,7 @@ public class FriendsController : Controller try { - var result = await _friendsService.SearchUsersAsync(query, GetCurrentUserId()); + var result = await _friendsService.SearchUsersAsync(query, _currentUserService.GetCurrentUserId()); return Ok(BuildUserDtos(result)); } catch (SearchUsersException ex) @@ -52,7 +57,7 @@ public class FriendsController : Controller try { - await _friendsService.SendFriendRequestAsync(targetUserId, GetCurrentUserId()); + await _friendsService.SendFriendRequestAsync(targetUserId, _currentUserService.GetCurrentUserId()); return Ok(new { message = "Friend request sent successfully." }); } catch (InvalidOperationException ex) @@ -77,7 +82,7 @@ public class FriendsController : Controller { try { - var result = await _friendsService.GetIncomingRequestsAsync(GetCurrentUserId()); + var result = await _friendsService.GetIncomingRequestsAsync(_currentUserService.GetCurrentUserId()); return Ok(BuildFriendshipDtos(result)); } catch (InvalidOperationException ex) @@ -100,7 +105,7 @@ public class FriendsController : Controller try { - await _friendsService.AcceptFriendRequestAsync(requesterId, GetCurrentUserId()); + await _friendsService.AcceptFriendRequestAsync(requesterId, _currentUserService.GetCurrentUserId()); return Ok(new { message = "Friend request accepted." }); } catch (InvalidOperationException ex) @@ -125,7 +130,7 @@ public class FriendsController : Controller { try { - var result = await _friendsService.GetFriendsAsync(GetCurrentUserId()); + var result = await _friendsService.GetFriendsAsync(_currentUserService.GetCurrentUserId()); return Ok(BuildUserDtos(result)); } catch (InvalidOperationException ex) @@ -140,16 +145,6 @@ public class FriendsController : Controller } } - private Guid GetCurrentUserId() - { - var userIdClaim = HttpContext.User?.FindFirst("userID")?.Value; - if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId)) - { - throw new UnauthorizedAccessException("userID claim is missing or invalid"); - } - return userId; - } - private List BuildUserDtos(IEnumerable users) => users.Select(user => new UserDto { Id = user.Id, diff --git a/Govor.API/Extensions/ConfigurationProgramExtensions.cs b/Govor.API/Extensions/ConfigurationProgramExtensions.cs index 3658265..76876ef 100644 --- a/Govor.API/Extensions/ConfigurationProgramExtensions.cs +++ b/Govor.API/Extensions/ConfigurationProgramExtensions.cs @@ -1,8 +1,10 @@ using Govor.API.Services.AdminsStuff.Interfaces; using Govor.API.Services.Authentication.Interfaces; +using Govor.Application.Infrastructure.Extensions; using Govor.Application.Interfaces; using Govor.Application.Interfaces.AdminsStuff; using Govor.Application.Interfaces.Authentication; +using Govor.Application.Interfaces.Infrastructure.Extensions; using Govor.Application.Services; using Govor.Application.Validators; using Govor.Core.Infrastructure.Extensions; @@ -38,6 +40,9 @@ public static class ConfigurationProgramExtensions var env = sp.GetRequiredService(); return new LocalStorageService(env.ContentRootPath); }); + + services.AddHttpContextAccessor(); // it's very important for CurrentUserService + services.AddScoped(); } public static void AddRepositories(this IServiceCollection services) diff --git a/Govor.Application/Infrastructure/Extensions/CurrentUserService.cs b/Govor.Application/Infrastructure/Extensions/CurrentUserService.cs new file mode 100644 index 0000000..3cb4b67 --- /dev/null +++ b/Govor.Application/Infrastructure/Extensions/CurrentUserService.cs @@ -0,0 +1,26 @@ +using System.Security.Claims; +using Govor.Application.Interfaces.Infrastructure.Extensions; +using Microsoft.AspNetCore.Http; + +namespace Govor.Application.Infrastructure.Extensions; + +public class CurrentUserService : ICurrentUserService +{ + private readonly ClaimsPrincipal _user; + + public CurrentUserService(IHttpContextAccessor httpContextAccessor) + { + _user = httpContextAccessor.HttpContext.User; + } + + public Guid GetCurrentUserId() + { + var userIdClaim = _user.FindFirst("userId")?.Value; + + if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId)) + { + throw new UnauthorizedAccessException("userID claim is missing or invalid"); + } + return userId; + } +} \ No newline at end of file diff --git a/Govor.Application/Validators/UsernameValidator.cs b/Govor.Application/Infrastructure/Validators/UsernameValidator.cs similarity index 100% rename from Govor.Application/Validators/UsernameValidator.cs rename to Govor.Application/Infrastructure/Validators/UsernameValidator.cs diff --git a/Govor.Application/Interfaces/Infrastructure/Extensions/ICurrentUserService.cs b/Govor.Application/Interfaces/Infrastructure/Extensions/ICurrentUserService.cs new file mode 100644 index 0000000..42e1156 --- /dev/null +++ b/Govor.Application/Interfaces/Infrastructure/Extensions/ICurrentUserService.cs @@ -0,0 +1,6 @@ +namespace Govor.Application.Interfaces.Infrastructure.Extensions; + +public interface ICurrentUserService +{ + Guid GetCurrentUserId(); +} \ No newline at end of file