mirror of
https://github.com/Govor-team/Govor.git
synced 2026-07-21 11:44:56 +00:00
@@ -10,21 +10,24 @@ on:
|
||||
branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore --configuration Release
|
||||
- name: Test API
|
||||
run: dotnet test ./Govor.API.Tests/Govor.API.Tests.csproj
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build --no-restore --configuration Release
|
||||
|
||||
- name: Run tests
|
||||
run: dotnet test Govor.API.Tests/Govor.API.Tests.csproj
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Qodana Analysis
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
qodana:
|
||||
name: Run Qodana Code Analysis
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Qodana
|
||||
uses: JetBrains/qodana-action@v2024.1
|
||||
with:
|
||||
# Указываем путь к qodana.yaml (если он не в корне)
|
||||
args: --ide QDNET --project-dir .
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# Êîïèðóåì òîëüêî .sln è .csproj
|
||||
COPY *.sln ./
|
||||
COPY Govor.API/*.csproj ./Govor.API/
|
||||
COPY Govor.Application/*.csproj ./Govor.Application/
|
||||
COPY Govor.Core/*.csproj ./Govor.Core/
|
||||
COPY Govor.Data/*.csproj ./Govor.Data/
|
||||
COPY Govor.Contracts/*.csproj ./Govor.Contracts/
|
||||
|
||||
RUN dotnet restore Govor.API/Govor.API.csproj
|
||||
|
||||
# Êîïèðóåì âåñü êîä
|
||||
COPY . .
|
||||
WORKDIR /src/Govor.API
|
||||
RUN dotnet publish -c Release -o /app/publish
|
||||
|
||||
# Runtime image
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["dotnet", "Govor.API.dll"]
|
||||
@@ -0,0 +1,114 @@
|
||||
using System.Security.Claims;
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.Common.SignalR.Helpers;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(HubUserAccessor))]
|
||||
public class HubUserAccessorTests
|
||||
{
|
||||
private HubUserAccessor _accessor;
|
||||
private Mock<ILogger<HubUserAccessor>> _loggerMock;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_loggerMock = new Mock<ILogger<HubUserAccessor>>();
|
||||
_accessor = new HubUserAccessor(_loggerMock.Object);
|
||||
}
|
||||
|
||||
private HubCallerContext CreateContextWithClaims(params Claim[] claims)
|
||||
{
|
||||
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims));
|
||||
var contextMock = new Mock<HubCallerContext>();
|
||||
contextMock.Setup(c => c.User).Returns(principal);
|
||||
return contextMock.Object;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUserId_ValidClaim_ReturnsGuid()
|
||||
{
|
||||
// Arrange
|
||||
var expectedGuid = Guid.NewGuid();
|
||||
var context = CreateContextWithClaims(new Claim("userId", expectedGuid.ToString()));
|
||||
|
||||
// Act
|
||||
var result = _accessor.GetUserId(context);
|
||||
|
||||
// Assert
|
||||
Assert.That(expectedGuid, Is.EqualTo(result));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUserId_InvalidClaim_ThrowsException_WhenNotSuppressed()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContextWithClaims(new Claim("userId", "not-a-guid"));
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<UnauthorizedAccessException>(() => _accessor.GetUserId(context, suppressException: false));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUserId_InvalidClaim_ReturnsEmptyGuid_WhenSuppressed()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContextWithClaims(new Claim("userId", "not-a-guid"));
|
||||
|
||||
// Act
|
||||
var result = _accessor.GetUserId(context, suppressException: true);
|
||||
|
||||
// Assert
|
||||
Assert.That(Guid.Empty, Is.EqualTo(result));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUserId_NoClaim_ThrowsException_WhenNotSuppressed()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContextWithClaims();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<UnauthorizedAccessException>(() => _accessor.GetUserId(context, suppressException: false));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUserId_NoClaim_ReturnsEmptyGuid_WhenSuppressed()
|
||||
{
|
||||
// Arrange
|
||||
var context = CreateContextWithClaims();
|
||||
|
||||
// Act
|
||||
var result = _accessor.GetUserId(context, suppressException: true);
|
||||
|
||||
// Assert
|
||||
Assert.That(Guid.Empty, Is.EqualTo(result));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUserId_UserIsNull_ReturnsEmptyGuid_WhenSuppressed()
|
||||
{
|
||||
// Arrange
|
||||
var contextMock = new Mock<HubCallerContext>();
|
||||
contextMock.Setup(c => c.User).Returns((ClaimsPrincipal?)null);
|
||||
|
||||
// Act
|
||||
var result = _accessor.GetUserId(contextMock.Object, suppressException: true);
|
||||
|
||||
// Assert
|
||||
Assert.That(Guid.Empty, Is.EqualTo(result));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUserId_UserIsNull_ThrowsException_WhenNotSuppressed()
|
||||
{
|
||||
// Arrange
|
||||
var contextMock = new Mock<HubCallerContext>();
|
||||
contextMock.Setup(c => c.User).Returns((ClaimsPrincipal?)null);
|
||||
// Act & Assert
|
||||
Assert.Throws<UnauthorizedAccessException>(() => _accessor.GetUserId(contextMock.Object, suppressException: false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
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.Users;
|
||||
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<ILogger<SessionController>> _loggerMock;
|
||||
private Mock<ICurrentUserService> _currentUserServiceMock;
|
||||
private Mock<ICurrentUserSessionService> _currentUserSessionServiceMock;
|
||||
private Mock<IUserSessionReader> _userSessionReaderMock;
|
||||
private Mock<IUserSessionRevoker> _userSessionRevokerMock;
|
||||
private Mock<IMapper> _mapperMock;
|
||||
private SessionController _controller;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_loggerMock = new Mock<ILogger<SessionController>>();
|
||||
_currentUserServiceMock = new Mock<ICurrentUserService>();
|
||||
_currentUserSessionServiceMock = new Mock<ICurrentUserSessionService>();
|
||||
_userSessionReaderMock = new Mock<IUserSessionReader>();
|
||||
_userSessionRevokerMock = new Mock<IUserSessionRevoker>();
|
||||
_mapperMock = new Mock<IMapper>();
|
||||
_controller = new SessionController(
|
||||
_loggerMock.Object,
|
||||
_currentUserServiceMock.Object,
|
||||
_currentUserSessionServiceMock.Object,
|
||||
_userSessionReaderMock.Object,
|
||||
_userSessionRevokerMock.Object,
|
||||
_mapperMock.Object);
|
||||
}
|
||||
|
||||
#region GetAllSessions
|
||||
[Test]
|
||||
public async Task GetAllSessions_Successful_ReturnsOkWithMappedSessions()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var sessions = new List<UserSession> { new UserSession { Id = Guid.NewGuid(), UserId = userId } };
|
||||
var sessionDtos = new List<SessionDto> { 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<List<SessionDto>>(sessions)).Returns(sessionDtos);
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetAllSessions();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.TypeOf<OkObjectResult>());
|
||||
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<ForbidResult>());
|
||||
_loggerMock.Verify(l => l.Log(
|
||||
LogLevel.Warning,
|
||||
It.IsAny<EventId>(),
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
exception,
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()), 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<ObjectResult>());
|
||||
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<EventId>(),
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
exception,
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once());
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CloseSession
|
||||
[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<OkResult>());
|
||||
_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<BadRequestObjectResult>());
|
||||
var badRequestResult = result as BadRequestObjectResult;
|
||||
Assert.That(badRequestResult.Value, Is.EqualTo("Invalid sessionId."));
|
||||
_userSessionRevokerMock.Verify(r => r.CloseSessionByIdAsync(It.IsAny<Guid>(), It.IsAny<Guid>()), 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<ForbidResult>());
|
||||
_loggerMock.Verify(l => l.Log(
|
||||
LogLevel.Warning,
|
||||
It.IsAny<EventId>(),
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
exception,
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()), 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<NotFoundObjectResult>());
|
||||
var notFoundResult = result as NotFoundObjectResult;
|
||||
Assert.That(notFoundResult.Value, Is.EqualTo("Session not found"));
|
||||
_loggerMock.Verify(l => l.Log(
|
||||
LogLevel.Warning,
|
||||
It.IsAny<EventId>(),
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
exception,
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()), 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<BadRequestObjectResult>());
|
||||
var badRequestResult = result as BadRequestObjectResult;
|
||||
Assert.That(badRequestResult.Value, Is.EqualTo("Invalid operation"));
|
||||
_loggerMock.Verify(l => l.Log(
|
||||
LogLevel.Error,
|
||||
It.IsAny<EventId>(),
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
exception,
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once());
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CloseCurrentSession
|
||||
[Test]
|
||||
public async Task CloseCurrentSession_ValidSessionIdAndUserId_ReturnsOk()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var sessionId = Guid.NewGuid();
|
||||
_currentUserServiceMock.Setup(s => s.GetCurrentUserId()).Returns(userId);
|
||||
_currentUserSessionServiceMock.Setup(s => s.GetUserSessionId()).Returns(sessionId);
|
||||
|
||||
// Act
|
||||
var result = await _controller.CloseCurrent();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.TypeOf<OkResult>());
|
||||
_userSessionRevokerMock.Verify(r => r.CloseSessionByIdAsync(sessionId, userId), Times.Once());
|
||||
_loggerMock.VerifyNoOtherCalls();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CloseCurrentSession_UnauthorizedAccess_ReturnsForbid()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var sessionId = Guid.NewGuid();
|
||||
var exception = new UnauthorizedAccessException("Unauthorized");
|
||||
_currentUserServiceMock.Setup(s => s.GetCurrentUserId()).Returns(userId);
|
||||
_currentUserSessionServiceMock.Setup(s => s.GetUserSessionId()).Returns(sessionId);
|
||||
_userSessionRevokerMock.Setup(r => r.CloseSessionByIdAsync(sessionId, userId)).ThrowsAsync(exception);
|
||||
|
||||
// Act
|
||||
var result = await _controller.CloseCurrent();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.TypeOf<ForbidResult>());
|
||||
_loggerMock.Verify(l => l.Log(
|
||||
LogLevel.Warning,
|
||||
It.IsAny<EventId>(),
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
exception,
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CloseCurrentSession_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);
|
||||
_currentUserSessionServiceMock.Setup(s => s.GetUserSessionId()).Returns(sessionId);
|
||||
_userSessionRevokerMock.Setup(r => r.CloseSessionByIdAsync(sessionId, userId)).ThrowsAsync(exception);
|
||||
|
||||
// Act
|
||||
var result = await _controller.CloseCurrent();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.TypeOf<NotFoundObjectResult>());
|
||||
var notFoundResult = result as NotFoundObjectResult;
|
||||
Assert.That(notFoundResult.Value, Is.EqualTo("Session not found"));
|
||||
_loggerMock.Verify(l => l.Log(
|
||||
LogLevel.Warning,
|
||||
It.IsAny<EventId>(),
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
exception,
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CloseCurrentSession_InvalidOperation_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var sessionId = Guid.NewGuid();
|
||||
var exception = new InvalidOperationException("Invalid operation");
|
||||
_currentUserServiceMock.Setup(s => s.GetCurrentUserId()).Returns(userId);
|
||||
_currentUserSessionServiceMock.Setup(s => s.GetUserSessionId()).Returns(sessionId);
|
||||
_userSessionRevokerMock.Setup(r => r.CloseSessionByIdAsync(sessionId, userId)).ThrowsAsync(exception);
|
||||
|
||||
// Act
|
||||
var result = await _controller.CloseCurrent();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.TypeOf<BadRequestObjectResult>());
|
||||
var badRequestResult = result as BadRequestObjectResult;
|
||||
Assert.That(badRequestResult.Value, Is.EqualTo("Invalid operation"));
|
||||
_loggerMock.Verify(l => l.Log(
|
||||
LogLevel.Error,
|
||||
It.IsAny<EventId>(),
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
exception,
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once());
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CloseAllSessions
|
||||
[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<OkResult>());
|
||||
_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<ForbidResult>());
|
||||
_loggerMock.Verify(l => l.Log(
|
||||
LogLevel.Warning,
|
||||
It.IsAny<EventId>(),
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
exception,
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()), 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<ObjectResult>());
|
||||
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<EventId>(),
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
exception,
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once());
|
||||
}
|
||||
#endregion
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_controller?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
@@ -11,13 +11,14 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoFixture" Version="5.0.0-preview0012" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="NUnit" Version="4.2.2" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.3.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit" Version="4.4.0-beta.1" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.4.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"/>
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -25,8 +26,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Govor.API\Govor.API.csproj" />
|
||||
<ProjectReference Include="..\Govor.Data\Govor.Data.csproj" />
|
||||
<ProjectReference Include="..\Govor.API\Govor.API.csproj" />
|
||||
<ProjectReference Include="..\Govor.Data\Govor.Data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
using AutoFixture;
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Govor.API.Hubs;
|
||||
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.Hubs;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(PresenceHub))]
|
||||
public class PresenceHubTests
|
||||
{
|
||||
|
||||
private Mock<ILogger<PresenceHub>> _mockLogger;
|
||||
private Mock<IUserNotificationScopeService> _mockUserNotification;
|
||||
private Mock<IOnlineUserStore> _mockOnlineUserStore;
|
||||
private Mock<IHubUserAccessor> _mockHubUserAccessor;
|
||||
private Mock<IUsersRepository> _mockUserRepository;
|
||||
private Mock<IHubCallerClients> _clientsMock = null!;
|
||||
private Mock<IClientProxy> _clientProxyMock = null!;
|
||||
private Mock<HubCallerContext> _mockContext = null!;
|
||||
private Mock<IGroupManager> _groupManagerMock = null!;
|
||||
private Fixture _fixture;
|
||||
private PresenceHub _hub;
|
||||
private Guid _userId;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_mockLogger = new Mock<ILogger<PresenceHub>>();
|
||||
_mockUserNotification = new Mock<IUserNotificationScopeService>();
|
||||
_mockOnlineUserStore = new Mock<IOnlineUserStore>();
|
||||
_mockHubUserAccessor = new Mock<IHubUserAccessor>();
|
||||
_mockUserRepository = new Mock<IUsersRepository>();
|
||||
|
||||
_clientsMock = new Mock<IHubCallerClients>();
|
||||
_clientProxyMock = new Mock<IClientProxy>();
|
||||
|
||||
_userId = Guid.NewGuid();
|
||||
_mockHubUserAccessor.Setup(h => h.GetUserId(
|
||||
It.IsAny<HubCallerContext>(),
|
||||
It.IsAny<bool>()))
|
||||
.Returns(_userId);
|
||||
|
||||
|
||||
_clientsMock.Setup(c => c.Group(It.IsAny<string>())).Returns(_clientProxyMock.Object);
|
||||
_mockContext = new Mock<HubCallerContext>();
|
||||
_mockContext.Setup(c => c.ConnectionId).Returns("test-connection");
|
||||
|
||||
_groupManagerMock = new Mock<IGroupManager>();
|
||||
|
||||
_groupManagerMock.Setup(g => g.AddToGroupAsync(It.IsAny<string>(), It.IsAny<string>(), default))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
_hub = new PresenceHub(
|
||||
_mockLogger.Object,
|
||||
_mockUserNotification.Object,
|
||||
_mockOnlineUserStore.Object,
|
||||
_mockHubUserAccessor.Object,
|
||||
_mockUserRepository.Object)
|
||||
{
|
||||
Clients = _clientsMock.Object,
|
||||
Context = _mockContext.Object,
|
||||
Groups = _groupManagerMock.Object,
|
||||
};
|
||||
}
|
||||
|
||||
// Tests for OnConnectedAsync action
|
||||
[Test]
|
||||
public async Task OnConnectedAsync_SetsUserOnlineAndNotifiesFriends()
|
||||
{
|
||||
// Arrange
|
||||
_mockUserRepository.Setup(f => f.ExistsByIdAsync(_userId))
|
||||
.ReturnsAsync(true);
|
||||
|
||||
var friendsIds = _fixture.CreateMany<Guid>().ToList();
|
||||
|
||||
_mockUserNotification.Setup(n => n.GetNotifiedUsers(_userId))
|
||||
.ReturnsAsync(friendsIds);
|
||||
|
||||
// Act
|
||||
await _hub.OnConnectedAsync();
|
||||
|
||||
// Assert
|
||||
_mockOnlineUserStore.Verify(store => store.SetOnlineUser(_userId), Times.Once);
|
||||
|
||||
_clientsMock.Verify(c => c.Group(It.IsAny<string>()), Times.Exactly(friendsIds.Count));
|
||||
|
||||
foreach (var friendId in friendsIds)
|
||||
{
|
||||
_clientProxyMock.Verify(proxy =>
|
||||
proxy.SendCoreAsync("UserOnline",
|
||||
It.Is<object[]>(args => args.Length == 1 && (Guid)args[0] == _userId),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Exactly(friendsIds.Count));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task OnConnectedAsync_UserIdInvalidOrNotExists_AbortsConnection()
|
||||
{
|
||||
// Arrange
|
||||
_mockHubUserAccessor.Setup(h => h.GetUserId(It.IsAny<HubCallerContext>(), false))
|
||||
.Returns(Guid.Empty);
|
||||
|
||||
// Act
|
||||
await _hub.OnConnectedAsync();
|
||||
|
||||
// Assert
|
||||
_mockOnlineUserStore.Verify(store => store.SetOnlineUser(It.IsAny<Guid>()), Times.Never);
|
||||
_mockContext.Verify(c => c.Abort(), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task OnConnectedAsync_UserNotExists_AbortsConnection()
|
||||
{
|
||||
// Arrange
|
||||
_mockUserRepository.Setup(f => f.ExistsByIdAsync(_userId))
|
||||
.ReturnsAsync(false);
|
||||
|
||||
// Act
|
||||
await _hub.OnConnectedAsync();
|
||||
|
||||
// Assert
|
||||
_mockOnlineUserStore.Verify(store => store.SetOnlineUser(It.IsAny<Guid>()), Times.Never);
|
||||
_mockContext.Verify(c => c.Abort(), Times.Once);
|
||||
}
|
||||
|
||||
// Tests for OnDisconnectedAsync action
|
||||
[Test]
|
||||
public async Task OnDisconnectedAsync_SetsUserOffline_UpdatesWasOnline_NotifiesFriends()
|
||||
{
|
||||
// Arrange
|
||||
var user = new User { Id = _userId, WasOnline = DateTime.MinValue };
|
||||
|
||||
var friendsIds = _fixture.CreateMany<Guid>().ToList();
|
||||
_mockUserRepository.Setup(r => r.FindByIdAsync(_userId))
|
||||
.ReturnsAsync(user);
|
||||
|
||||
_mockUserNotification.Setup(n => n.GetNotifiedUsers(_userId))
|
||||
.ReturnsAsync(friendsIds);
|
||||
|
||||
// Act
|
||||
await _hub.OnDisconnectedAsync(null!);
|
||||
|
||||
// Assert
|
||||
_mockOnlineUserStore.Verify(store => store.SetOfflineUser(_userId), Times.Once);
|
||||
_mockUserRepository.Verify(r => r.UpdateAsync(It.Is<User>(u => u.Id == _userId && u.WasOnline > DateTime.MinValue)), Times.Once);
|
||||
_clientsMock.Verify(c => c.Group(It.IsAny<string>()), Times.AtLeastOnce);
|
||||
|
||||
foreach (var friendId in friendsIds)
|
||||
{
|
||||
_clientProxyMock.Verify(proxy =>
|
||||
proxy.SendCoreAsync("UserOffline",
|
||||
It.Is<object[]>(args => args.Length == 1 && (Guid)args[0] == _userId),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Exactly(friendsIds.Count));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task OnDisconnectedAsync_UserIdEmpty_LogsAndSkips()
|
||||
{
|
||||
// Arrange
|
||||
_mockHubUserAccessor.Setup(h => h.GetUserId(It.IsAny<HubCallerContext>(), true))
|
||||
.Returns(Guid.Empty);
|
||||
|
||||
// Act
|
||||
await _hub.OnDisconnectedAsync(null!);
|
||||
|
||||
// Assert
|
||||
_mockOnlineUserStore.Verify(s => s.SetOfflineUser(It.IsAny<Guid>()), Times.Never);
|
||||
_mockUserRepository.Verify(r => r.UpdateAsync(It.IsAny<User>()), Times.Never);
|
||||
|
||||
_clientProxyMock.Verify(proxy =>
|
||||
proxy.SendCoreAsync("UserOffline",
|
||||
It.Is<object[]>(args => args.Length == 1 && (Guid)args[0] == _userId),
|
||||
It.IsAny<CancellationToken>()),
|
||||
Times.Never);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_hub?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
using AutoFixture;
|
||||
using Govor.API.Controllers;
|
||||
using Govor.API.Services.Authentication.Interfaces;
|
||||
using Govor.API.Controllers.Authentication;
|
||||
using Govor.Application.Exceptions.AuthService;
|
||||
using Govor.Application.Exceptions.InvitesService;
|
||||
using Govor.Application.Interfaces.Authentication;
|
||||
using Govor.Application.Interfaces.UserSession;
|
||||
using Govor.Contracts.Requests;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Users;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
@@ -19,6 +20,7 @@ public class AuthControllerTests
|
||||
private Mock<IAccountService> _accountServiceMock;
|
||||
private Mock<IInvitesService> _invitesServiceMock;
|
||||
private Mock<ILogger<AuthController>> _loggerMock;
|
||||
private Mock<IUserSessionOpener> _userSessionOpenerMock;
|
||||
private AuthController _controller;
|
||||
|
||||
[SetUp]
|
||||
@@ -31,10 +33,12 @@ public class AuthControllerTests
|
||||
_accountServiceMock = new Mock<IAccountService>();
|
||||
_invitesServiceMock = new Mock<IInvitesService>();
|
||||
_loggerMock = new Mock<ILogger<AuthController>>();
|
||||
_userSessionOpenerMock = new Mock<IUserSessionOpener>();
|
||||
|
||||
_controller = new AuthController(
|
||||
_accountServiceMock.Object,
|
||||
_invitesServiceMock.Object,
|
||||
_userSessionOpenerMock.Object,
|
||||
_loggerMock.Object
|
||||
);
|
||||
}
|
||||
@@ -46,10 +50,19 @@ public class AuthControllerTests
|
||||
// Arrange
|
||||
var request = _fixture.Create<RegistrationRequest>();
|
||||
var invitation = _fixture.Create<Invitation>();
|
||||
var token = _fixture.Create<string>();
|
||||
var token = _fixture.Create<RefreshResult>();
|
||||
|
||||
_invitesServiceMock.Setup(s => s.Validate(request.InviteLink)).Returns(invitation);
|
||||
_accountServiceMock.Setup(s => s.RegistrationAsync(request.Name, request.Password, invitation)).ReturnsAsync(token);
|
||||
var user = _fixture.Build<User>()
|
||||
.With(x => x.Username).Create();
|
||||
|
||||
_invitesServiceMock.Setup(s => s.ValidateAsync(request.InviteLink)).ReturnsAsync(invitation);
|
||||
|
||||
|
||||
_accountServiceMock.Setup(l => l.RegistrationAsync(request.Name, request.Password, invitation))
|
||||
.ReturnsAsync(user);
|
||||
|
||||
_userSessionOpenerMock.Setup(f => f.OpenSessionAsync(user, request.DeviceInfo))
|
||||
.ReturnsAsync(token);
|
||||
|
||||
// Act
|
||||
var result = await _controller.Register(request);
|
||||
@@ -57,8 +70,11 @@ public class AuthControllerTests
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<OkObjectResult>());
|
||||
var okResult = result as OkObjectResult;
|
||||
dynamic value = okResult.Value;
|
||||
Assert.That((string)value.GetType().GetProperty("token").GetValue(value, null), Is.EqualTo(token));
|
||||
|
||||
var response = okResult?.Value as RefreshResult;
|
||||
Assert.That(response, Is.Not.Null);
|
||||
Assert.That(response.accessToken, Is.EqualTo(token.accessToken));
|
||||
Assert.That(response.refreshToken, Is.EqualTo(token.refreshToken));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -80,14 +96,14 @@ public class AuthControllerTests
|
||||
{
|
||||
// Arrange
|
||||
var request = _fixture.Create<RegistrationRequest>();
|
||||
_invitesServiceMock.Setup(s => s.Validate(request.InviteLink)).Throws(new InviteLinkInvalidException(request.InviteLink));
|
||||
_invitesServiceMock.Setup(s => s.ValidateAsync(request.InviteLink)).ThrowsAsync(new InviteLinkInvalidException(request.InviteLink));
|
||||
|
||||
// Act
|
||||
var result = await _controller.Register(request);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<NotFoundObjectResult>());
|
||||
var notFoundObjectResult = result as NotFoundObjectResult;
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
var notFoundObjectResult = result as BadRequestObjectResult;
|
||||
Assert.That(notFoundObjectResult.Value, Is.EqualTo("Invite link invalid."));
|
||||
}
|
||||
|
||||
@@ -97,7 +113,7 @@ public class AuthControllerTests
|
||||
// Arrange
|
||||
var request = _fixture.Create<RegistrationRequest>();
|
||||
var invitation = _fixture.Create<Invitation>();
|
||||
_invitesServiceMock.Setup(s => s.Validate(request.InviteLink)).Returns(invitation);
|
||||
_invitesServiceMock.Setup(s => s.ValidateAsync(request.InviteLink)).ReturnsAsync(invitation);
|
||||
_accountServiceMock.Setup(s => s.RegistrationAsync(request.Name, request.Password, invitation))
|
||||
.ThrowsAsync(new UserAlreadyExistException(request.Name));
|
||||
|
||||
@@ -116,7 +132,7 @@ public class AuthControllerTests
|
||||
// Arrange
|
||||
var request = _fixture.Create<RegistrationRequest>();
|
||||
var invitation = _fixture.Create<Invitation>();
|
||||
_invitesServiceMock.Setup(s => s.Validate(request.InviteLink)).Returns(invitation);
|
||||
_invitesServiceMock.Setup(s => s.ValidateAsync(request.InviteLink)).ReturnsAsync(invitation);
|
||||
_accountServiceMock.Setup(s => s.RegistrationAsync(request.Name, request.Password, invitation))
|
||||
.ThrowsAsync(new System.Exception("Generic error"));
|
||||
|
||||
@@ -136,16 +152,26 @@ public class AuthControllerTests
|
||||
{
|
||||
// Arrange
|
||||
var loginRequest = _fixture.Create<LoginRequest>();
|
||||
var token = _fixture.Create<string>();
|
||||
_accountServiceMock.Setup(l => l.LoginAsync(loginRequest.Name, loginRequest.Password)).ReturnsAsync(token);
|
||||
var token = _fixture.Create<RefreshResult>();
|
||||
|
||||
var user = _fixture.Build<User>()
|
||||
.With(x => x.Username).Create();
|
||||
|
||||
_accountServiceMock.Setup(l => l.LoginAsync(loginRequest.Name, loginRequest.Password)).ReturnsAsync(user);
|
||||
|
||||
_userSessionOpenerMock.Setup(f => f.OpenSessionAsync(user, loginRequest.DeviceInfo))
|
||||
.ReturnsAsync(token);
|
||||
|
||||
// Act
|
||||
var result = await _controller.Login(loginRequest);
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<OkObjectResult>());
|
||||
var okResult = result as OkObjectResult;
|
||||
dynamic value = okResult.Value;
|
||||
Assert.That((string)value.GetType().GetProperty("token").GetValue(value, null), Is.EqualTo(token));
|
||||
|
||||
var response = okResult?.Value as RefreshResult;
|
||||
Assert.That(response, Is.Not.Null);
|
||||
Assert.That(response.accessToken, Is.EqualTo(token.accessToken));
|
||||
Assert.That(response.refreshToken, Is.EqualTo(token.refreshToken));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
using AutoFixture;
|
||||
using AutoMapper;
|
||||
using Govor.API.Controllers;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Contracts.Requests;
|
||||
using Govor.Contracts.Responses;
|
||||
using Govor.Core.Models.Messages;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.IntegrationTests.Controllers;
|
||||
|
||||
[TestFixture]
|
||||
public class ChatLoadControllerTests
|
||||
{
|
||||
private Mock<ICurrentUserService> _currentUserServiceMock;
|
||||
private Mock<ILogger<ChatLoadController>> _loggerMock;
|
||||
private Mock<IMessagesLoader> _messagesLoaderMock;
|
||||
private Mock<IMapper> _mapperMock;
|
||||
private Fixture _fixture;
|
||||
private ChatLoadController _controller;
|
||||
|
||||
private Guid _userId = Guid.NewGuid();
|
||||
private Guid _chatId = Guid.NewGuid();
|
||||
private Guid _currentId = Guid.NewGuid();
|
||||
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_currentUserServiceMock = new Mock<ICurrentUserService>();
|
||||
_loggerMock = new Mock<ILogger<ChatLoadController>>();
|
||||
_messagesLoaderMock = new Mock<IMessagesLoader>();
|
||||
_mapperMock = new Mock<IMapper>();
|
||||
|
||||
_currentUserServiceMock.Setup(u => u.GetCurrentUserId()).Returns(_currentId);
|
||||
|
||||
_controller = new ChatLoadController(
|
||||
_loggerMock.Object,
|
||||
_messagesLoaderMock.Object,
|
||||
_currentUserServiceMock.Object,
|
||||
_mapperMock.Object);
|
||||
}
|
||||
|
||||
// Tests for GetChatMessages action
|
||||
[Test]
|
||||
public async Task GetChatMessages_ValidRequest_ReturnsOkResult()
|
||||
{
|
||||
// Arrange
|
||||
var random = new Random();
|
||||
var messages = _fixture.CreateMany<Message>(random.Next(3, 10)).ToList();
|
||||
var before = random.Next(1, 10);
|
||||
var after = random.Next(1, 10);
|
||||
|
||||
var query = new MessageQuery()
|
||||
{
|
||||
Before = before,
|
||||
After = after,
|
||||
StartMessageId = null
|
||||
};
|
||||
|
||||
_messagesLoaderMock.Setup(m =>
|
||||
m.LoadMessagesInChatGroup(_chatId, _currentId, null, before, after))
|
||||
.ReturnsAsync(messages);
|
||||
|
||||
var messagesResponse = messages.Select(m => new MessageResponse()
|
||||
{
|
||||
Id = m.Id,
|
||||
SenderId = m.SenderId,
|
||||
RecipientId = m.RecipientId,
|
||||
RecipientType = m.RecipientType,
|
||||
}).ToList();
|
||||
|
||||
_mapperMock.Setup(f => f.Map<List<MessageResponse>>(messages)).
|
||||
Returns(messagesResponse);
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetGroupMessages(_chatId, query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result, Is.InstanceOf<OkObjectResult>());
|
||||
|
||||
var okResult = (OkObjectResult)result;
|
||||
var values = okResult.Value as List<MessageResponse>;
|
||||
|
||||
Assert.That(values, Is.Not.Null);
|
||||
|
||||
Assert.That(values.Select(v => v.Id),
|
||||
Is.EquivalentTo(messagesResponse.Select(m => m.Id)));
|
||||
|
||||
Assert.That(values.Select(v => v.SenderId),
|
||||
Is.EquivalentTo(messagesResponse.Select(m => m.SenderId)));
|
||||
|
||||
Assert.That(values.Select(v => v.RecipientId),
|
||||
Is.EquivalentTo(messagesResponse.Select(m => m.RecipientId)));
|
||||
|
||||
Assert.That(values.Select(v => v.RecipientType),
|
||||
Is.EquivalentTo(messagesResponse.Select(m => m.RecipientType)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetGroupMessages_InvalidQuery_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var query = new MessageQuery { Before = -1, After = 10 };
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetGroupMessages(_chatId, query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetGroupMessages_UnauthorizedAccess_ReturnsForbid()
|
||||
{
|
||||
// Arrange
|
||||
var query = new MessageQuery { Before = 10, After = 10 };
|
||||
_messagesLoaderMock.Setup(m => m.LoadMessagesInChatGroup(It.IsAny<Guid>(), It.IsAny<Guid>(), null, It.IsAny<int>(), It.IsAny<int>()))
|
||||
.ThrowsAsync(new UnauthorizedAccessException());
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetGroupMessages(_chatId, query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ForbidResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetGroupMessages_NotFound_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var query = new MessageQuery { Before = 10, After = 10 };
|
||||
_messagesLoaderMock.Setup(m => m.LoadMessagesInChatGroup(It.IsAny<Guid>(), It.IsAny<Guid>(), null, It.IsAny<int>(), It.IsAny<int>()))
|
||||
.ThrowsAsync(new NotFoundException("Chat not found"));
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetGroupMessages(_chatId, query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetGroupMessages_ArgumentException_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var query = new MessageQuery { Before = 10, After = 10 };
|
||||
_messagesLoaderMock.Setup(m => m.LoadMessagesInChatGroup(It.IsAny<Guid>(), It.IsAny<Guid>(), null, It.IsAny<int>(), It.IsAny<int>()))
|
||||
.ThrowsAsync(new ArgumentException("Invalid argument"));
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetGroupMessages(_chatId, query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
}
|
||||
|
||||
// GetUserMessages tests
|
||||
[Test]
|
||||
public async Task GetUserMessages_ValidRequest_ReturnsOkResult()
|
||||
{
|
||||
// Arrange
|
||||
var messages = _fixture.CreateMany<Message>(5).ToList();
|
||||
var query = new MessageQuery { Before = 5, After = 5 };
|
||||
_messagesLoaderMock.Setup(m => m.LoadMessagesInUserChat(_userId, _currentId, null, 5, 5))
|
||||
.ReturnsAsync(messages);
|
||||
var messageResponses = _fixture.CreateMany<MessageResponse>(5).ToList();
|
||||
_mapperMock.Setup(m => m.Map<List<MessageResponse>>(messages)).Returns(messageResponses);
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetUserMessages(_userId, query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<OkObjectResult>());
|
||||
var okResult = result as OkObjectResult;
|
||||
Assert.That(okResult.Value, Is.EqualTo(messageResponses));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetUserMessages_InvalidQuery_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var query = new MessageQuery { Before = -1, After = 10 };
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetUserMessages(_userId, query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetUserMessages_InvalidOperation_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var query = new MessageQuery { Before = 10, After = 10 };
|
||||
_messagesLoaderMock.Setup(m => m.LoadMessagesInUserChat(It.IsAny<Guid>(), It.IsAny<Guid>(), null, It.IsAny<int>(), It.IsAny<int>()))
|
||||
.ThrowsAsync(new InvalidOperationException());
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetUserMessages(_userId, query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetUserMessages_UnauthorizedAccess_ReturnsForbid()
|
||||
{
|
||||
// Arrange
|
||||
var query = new MessageQuery { Before = 10, After = 10 };
|
||||
_messagesLoaderMock.Setup(m => m.LoadMessagesInUserChat(It.IsAny<Guid>(), It.IsAny<Guid>(), null, It.IsAny<int>(), It.IsAny<int>()))
|
||||
.ThrowsAsync(new UnauthorizedAccessException());
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetUserMessages(_userId, query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ForbidResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetUserMessages_NotFound_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var query = new MessageQuery { Before = 10, After = 10 };
|
||||
_messagesLoaderMock.Setup(m => m.LoadMessagesInUserChat(It.IsAny<Guid>(), It.IsAny<Guid>(), null, It.IsAny<int>(), It.IsAny<int>()))
|
||||
.ThrowsAsync(new NotFoundException("User not found"));
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetUserMessages(_userId, query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetUserMessages_ArgumentException_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var query = new MessageQuery { Before = 10, After = 10 };
|
||||
_messagesLoaderMock.Setup(m => m.LoadMessagesInUserChat(It.IsAny<Guid>(), It.IsAny<Guid>(), null, It.IsAny<int>(), It.IsAny<int>()))
|
||||
.ThrowsAsync(new ArgumentException("Invalid argument"));
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetUserMessages(_userId, query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_controller?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
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<ILogger<FriendsController>> _loggerMock;
|
||||
private Mock<IFriendsService> _friendsServiceMock;
|
||||
private Mock<ICurrentUserService> _currentUserServiceMock;
|
||||
private FriendsController _controller;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_loggerMock = new Mock<ILogger<FriendsController>>();
|
||||
_friendsServiceMock = new Mock<IFriendsService>();
|
||||
_currentUserServiceMock = new Mock<ICurrentUserService>();
|
||||
|
||||
_controller = new FriendsController(
|
||||
_loggerMock.Object,
|
||||
_friendsServiceMock.Object,
|
||||
_currentUserServiceMock.Object
|
||||
);
|
||||
}
|
||||
|
||||
// Tests for Search action
|
||||
[Test]
|
||||
public async Task Search_ValidRequest_ReturnsOkResult()
|
||||
{
|
||||
var users = _fixture.CreateMany<User>().ToList();
|
||||
var userId = _fixture.Create<Guid>();
|
||||
var query = _fixture.Create<string>();
|
||||
|
||||
_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<UserDto> userDtos = value as List<UserDto>;
|
||||
|
||||
// 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<BadRequestObjectResult>());
|
||||
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<string>(), It.IsAny<Guid>()))
|
||||
.ThrowsAsync(new SearchUsersException(_fixture.Create<string>()));
|
||||
|
||||
// Act
|
||||
var result = await _controller.Search(_fixture.Create<string>());
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<NotFoundObjectResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_StatusCode500_IfThrowsSomeException()
|
||||
{
|
||||
// Arrange
|
||||
_friendsServiceMock.Setup(f => f.SearchUsersAsync(It.IsAny<string>(), It.IsAny<Guid>()))
|
||||
.ThrowsAsync(new Exception(_fixture.Create<string>()));
|
||||
|
||||
// Act
|
||||
var result = await _controller.Search(_fixture.Create<string>());
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ObjectResult>());
|
||||
var objectResult = result as ObjectResult;
|
||||
Assert.That(objectResult.StatusCode, Is.EqualTo(500));
|
||||
}
|
||||
|
||||
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_controller.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
using AutoFixture;
|
||||
using AutoMapper;
|
||||
using Govor.API.Controllers.Friends;
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
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 FriendsRequestQueryControllerTests
|
||||
{
|
||||
private Fixture _fixture;
|
||||
private Mock<ILogger<FriendsRequestQueryController>> _loggerMock;
|
||||
private Mock<IFriendRequestQueryService> _friendsServiceMock;
|
||||
private Mock<ICurrentUserService> _currentUserServiceMock;
|
||||
private Mock<IMapper> _mapperMock;
|
||||
private FriendsRequestQueryController _controller;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_loggerMock = new Mock<ILogger<FriendsRequestQueryController>>();
|
||||
_friendsServiceMock = new Mock<IFriendRequestQueryService>();
|
||||
_currentUserServiceMock = new Mock<ICurrentUserService>();
|
||||
_mapperMock = new Mock<IMapper>();
|
||||
|
||||
_controller = new FriendsRequestQueryController(
|
||||
_loggerMock.Object,
|
||||
_friendsServiceMock.Object,
|
||||
_currentUserServiceMock.Object,
|
||||
_mapperMock.Object
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetIncomingRequests_ValidRequest_ReturnsOkResult()
|
||||
{
|
||||
// Arrange
|
||||
var currentId = _fixture.Create<Guid>();
|
||||
var friendships = _fixture.CreateMany<Friendship>().ToList();
|
||||
var dtos = friendships.Select(f => new FriendshipDto()
|
||||
{
|
||||
AddresseeId = f.AddresseeId,
|
||||
RequesterId = f.RequesterId
|
||||
}).ToList();
|
||||
|
||||
_currentUserServiceMock.Setup(c => c.GetCurrentUserId())
|
||||
.Returns(currentId);
|
||||
|
||||
_friendsServiceMock.Setup(f => f.GetIncomingAsync(currentId))
|
||||
.ReturnsAsync(friendships);
|
||||
|
||||
_mapperMock.Setup(f => f.Map<List<FriendshipDto>>(friendships)).Returns(dtos);
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetIncomingRequests();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<OkObjectResult>());
|
||||
var okResult = result as OkObjectResult;
|
||||
List<FriendshipDto> value = okResult.Value as List<FriendshipDto>;
|
||||
|
||||
Assert.That(value, Is.Not.Null);
|
||||
Assert.That(value.Count, Is.EqualTo(friendships.Count));
|
||||
Assert.That(value.Select(f => f.AddresseeId), Is.EquivalentTo(friendships.Select(f => f.AddresseeId)));
|
||||
Assert.That(value.Select(f => f.RequesterId), Is.EquivalentTo(friendships.Select(f => f.RequesterId)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetIncomingRequests_Throws_InvalidOperationException_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var currentId = _fixture.Create<Guid>();
|
||||
|
||||
_currentUserServiceMock.Setup(c => c.GetCurrentUserId())
|
||||
.Returns(currentId);
|
||||
|
||||
_friendsServiceMock.Setup(f => f.GetIncomingAsync(currentId))
|
||||
.ThrowsAsync(new InvalidOperationException());
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetIncomingRequests();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<OkObjectResult>());
|
||||
var okResult = result as OkObjectResult;
|
||||
List<FriendshipDto> value = okResult.Value as List<FriendshipDto>;
|
||||
|
||||
Assert.That(value, Is.Not.Null);
|
||||
Assert.That(value.Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetIncomingRequest_StatusCode500_IfThrowsSomeException()
|
||||
{
|
||||
// Arrange
|
||||
var currentId = _fixture.Create<Guid>();
|
||||
|
||||
_currentUserServiceMock.Setup(c => c.GetCurrentUserId())
|
||||
.Returns(currentId);
|
||||
|
||||
_friendsServiceMock.Setup(f => f.GetIncomingAsync(currentId))
|
||||
.ThrowsAsync(new Exception());
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetIncomingRequests();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ObjectResult>());
|
||||
var objectResult = result as ObjectResult;
|
||||
Assert.That(objectResult.StatusCode, Is.EqualTo(500));
|
||||
}
|
||||
|
||||
// Test for GetResponses action
|
||||
[Test]
|
||||
public async Task GetResponses_ValidRequest_ReturnsOkResult()
|
||||
{
|
||||
// Arrange
|
||||
var currentId = _fixture.Create<Guid>();
|
||||
var friendships = _fixture.CreateMany<Friendship>().ToList();
|
||||
var dtos = friendships.Select(f => new FriendshipDto()
|
||||
{
|
||||
AddresseeId = f.AddresseeId,
|
||||
RequesterId = f.RequesterId
|
||||
}).ToList();
|
||||
|
||||
_currentUserServiceMock.Setup(c => c.GetCurrentUserId())
|
||||
.Returns(currentId);
|
||||
|
||||
_friendsServiceMock.Setup(f => f.GetResponsesAsync(currentId))
|
||||
.ReturnsAsync(friendships);
|
||||
|
||||
_mapperMock.Setup(f => f.Map<List<FriendshipDto>>(friendships)).Returns(dtos);
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetResponses();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<OkObjectResult>());
|
||||
var okResult = result as OkObjectResult;
|
||||
List<FriendshipDto> value = okResult.Value as List<FriendshipDto>;
|
||||
|
||||
Assert.That(value, Is.Not.Null);
|
||||
Assert.That(value.Count, Is.EqualTo(friendships.Count));
|
||||
Assert.That(value.Select(f => f.AddresseeId), Is.EquivalentTo(friendships.Select(f => f.AddresseeId)));
|
||||
Assert.That(value.Select(f => f.RequesterId), Is.EquivalentTo(friendships.Select(f => f.RequesterId)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetResponses_Throws_InvalidOperationException_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var currentId = _fixture.Create<Guid>();
|
||||
|
||||
_currentUserServiceMock.Setup(c => c.GetCurrentUserId())
|
||||
.Returns(currentId);
|
||||
|
||||
_friendsServiceMock.Setup(f => f.GetResponsesAsync(currentId))
|
||||
.ThrowsAsync(new InvalidOperationException());
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetResponses();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<OkObjectResult>());
|
||||
var okResult = result as OkObjectResult;
|
||||
List<FriendshipDto> value = okResult.Value as List<FriendshipDto>;
|
||||
|
||||
Assert.That(value, Is.Not.Null);
|
||||
Assert.That(value.Count, Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetResponses_StatusCode500_IfThrowsSomeException()
|
||||
{
|
||||
// Arrange
|
||||
var currentId = _fixture.Create<Guid>();
|
||||
|
||||
_currentUserServiceMock.Setup(c => c.GetCurrentUserId())
|
||||
.Returns(currentId);
|
||||
|
||||
_friendsServiceMock.Setup(f => f.GetResponsesAsync(currentId))
|
||||
.ThrowsAsync(new Exception());
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetResponses();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ObjectResult>());
|
||||
var objectResult = result as ObjectResult;
|
||||
Assert.That(objectResult.StatusCode, Is.EqualTo(500));
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_controller?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using AutoFixture;
|
||||
using AutoMapper;
|
||||
using Govor.API.Controllers.Friends;
|
||||
using Govor.Application.Exceptions.FriendsService;
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Contracts.DTOs;
|
||||
using Govor.Core.Models.Users;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.IntegrationTests.Controllers;
|
||||
|
||||
[TestFixture]
|
||||
public class FriendshipControllerTests
|
||||
{
|
||||
private Fixture _fixture;
|
||||
private Mock<ILogger<FriendshipController>> _loggerMock;
|
||||
private Mock<IFriendshipService> _friendsServiceMock;
|
||||
private Mock<ICurrentUserService> _currentUserServiceMock;
|
||||
private Mock<IMapper> _mapperMock;
|
||||
private FriendshipController _controller;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_loggerMock = new Mock<ILogger<FriendshipController>>();
|
||||
_friendsServiceMock = new Mock<IFriendshipService>();
|
||||
_currentUserServiceMock = new Mock<ICurrentUserService>();
|
||||
_mapperMock = new Mock<IMapper>();
|
||||
|
||||
_controller = new FriendshipController(
|
||||
_loggerMock.Object,
|
||||
_friendsServiceMock.Object,
|
||||
_currentUserServiceMock.Object,
|
||||
_mapperMock.Object
|
||||
);
|
||||
}
|
||||
|
||||
// Tests for Search action
|
||||
[Test]
|
||||
public async Task Search_ValidRequest_ReturnsOkResult()
|
||||
{
|
||||
var users = _fixture.CreateMany<User>().ToList();
|
||||
var dtos = users.Select(u => new UserDto { Id = u.Id }).ToList();
|
||||
var userId = _fixture.Create<Guid>();
|
||||
var query = _fixture.Create<string>();
|
||||
|
||||
_currentUserServiceMock.Setup(c => c.GetCurrentUserId()).Returns(userId);
|
||||
|
||||
_friendsServiceMock.Setup(f => f.SearchUsersAsync(query, userId))
|
||||
.ReturnsAsync(users);
|
||||
|
||||
_mapperMock.Setup(m => m.Map<List<UserDto>>(users)).Returns(dtos);
|
||||
|
||||
// Act
|
||||
var result = await _controller.Search(query);
|
||||
|
||||
var okResult = result as OkObjectResult;
|
||||
dynamic value = okResult.Value;
|
||||
|
||||
List<UserDto> userDtos = value as List<UserDto>;
|
||||
|
||||
// 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<BadRequestObjectResult>());
|
||||
var badRequestResult = result as BadRequestObjectResult;
|
||||
Assert.That(badRequestResult.Value, Is.EqualTo("Query cannot be empty"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Search_StatusCode500_IfThrowsSomeException()
|
||||
{
|
||||
// Arrange
|
||||
_friendsServiceMock.Setup(f => f.SearchUsersAsync(It.IsAny<string>(), It.IsAny<Guid>()))
|
||||
.ThrowsAsync(new Exception(_fixture.Create<string>()));
|
||||
|
||||
// Act
|
||||
var result = await _controller.Search(_fixture.Create<string>());
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ObjectResult>());
|
||||
var objectResult = result as ObjectResult;
|
||||
Assert.That(objectResult.StatusCode, Is.EqualTo(500));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetFriends_ValidRequest_ReturnsOkResult()
|
||||
{
|
||||
// Arrange
|
||||
var currentId = _fixture.Create<Guid>();
|
||||
var users = _fixture.CreateMany<User>().ToList();
|
||||
var dtos = users.Select(u => new UserDto { Id = u.Id }).ToList();
|
||||
|
||||
_currentUserServiceMock.Setup(c => c.GetCurrentUserId())
|
||||
.Returns(currentId);
|
||||
|
||||
_friendsServiceMock.Setup(f => f.GetFriendsAsync(currentId))
|
||||
.ReturnsAsync(users);
|
||||
|
||||
_mapperMock.Setup(m => m.Map<List<UserDto>>(users)).Returns(dtos);
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetFriends();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<OkObjectResult>());
|
||||
var okResult = result as OkObjectResult;
|
||||
List<UserDto> value = okResult.Value as List<UserDto>;
|
||||
Assert.That(value, Is.Not.Null);
|
||||
Assert.That(value.Count, Is.EqualTo(users.Count));
|
||||
Assert.That(value.Select(u => u.Id), Is.EqualTo(users.Select(u => u.Id)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetFriends_InvalidOperationException_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var currentId = _fixture.Create<Guid>();
|
||||
var users = _fixture.CreateMany<User>().ToList();
|
||||
|
||||
_currentUserServiceMock.Setup(c => c.GetCurrentUserId())
|
||||
.Returns(currentId);
|
||||
|
||||
_friendsServiceMock.Setup(f => f.GetFriendsAsync(currentId))
|
||||
.ThrowsAsync(new InvalidOperationException());
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetFriends();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<OkObjectResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetFriends_Exception_ReturnsStatusCode500()
|
||||
{
|
||||
// Arrange
|
||||
var currentId = _fixture.Create<Guid>();
|
||||
var users = _fixture.CreateMany<User>().ToList();
|
||||
|
||||
_currentUserServiceMock.Setup(c => c.GetCurrentUserId())
|
||||
.Returns(currentId);
|
||||
|
||||
_friendsServiceMock.Setup(f => f.GetFriendsAsync(currentId))
|
||||
.ThrowsAsync(new Exception());
|
||||
|
||||
// Act
|
||||
var result = await _controller.GetFriends();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ObjectResult>());
|
||||
var objectResult = result as ObjectResult;
|
||||
Assert.That(objectResult.StatusCode, Is.EqualTo(500));
|
||||
}
|
||||
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_controller.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
using System.Text;
|
||||
using AutoFixture;
|
||||
using Govor.API.Controllers;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Application.Interfaces.Medias;
|
||||
using Govor.Contracts.Requests;
|
||||
using Govor.Core.Models.Messages;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.IntegrationTests.Controllers;
|
||||
|
||||
[TestFixture]
|
||||
public class MediaControllerTests
|
||||
{
|
||||
private Fixture _fixture;
|
||||
private Mock<ICurrentUserService> _currentUserMock;
|
||||
private Mock<ILogger<MediaController>> _loggerMock;
|
||||
private Mock<IMediaService> _mockMedia;
|
||||
private Mock<IAccesserToDownloadMedia> _mockAccesser;
|
||||
private MediaController _controller;
|
||||
private Guid _userId = Guid.NewGuid();
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_currentUserMock = new Mock<ICurrentUserService>();
|
||||
_loggerMock = new Mock<ILogger<MediaController>>();
|
||||
_mockAccesser = new Mock<IAccesserToDownloadMedia>();
|
||||
_mockMedia = new Mock<IMediaService>();
|
||||
|
||||
_currentUserMock.Setup(f => f.GetCurrentUserId()).Returns(_userId);
|
||||
|
||||
_controller = new MediaController(
|
||||
_loggerMock.Object,
|
||||
_mockMedia.Object,
|
||||
_mockAccesser.Object,
|
||||
_currentUserMock.Object);
|
||||
}
|
||||
|
||||
// Tests for Upload action
|
||||
[Test]
|
||||
public async Task Upload_ValidRequest_ReturnsOkResult()
|
||||
{
|
||||
// Arrange
|
||||
var content = "fake file content";
|
||||
var fileName = "testfile.txt";
|
||||
var fileBytes = System.Text.Encoding.UTF8.GetBytes(content);
|
||||
var stream = new MemoryStream(fileBytes);
|
||||
|
||||
var formFileMock = new Mock<IFormFile>();
|
||||
formFileMock.Setup(f => f.Length).Returns(fileBytes.Length);
|
||||
formFileMock.Setup(f => f.FileName).Returns(fileName);
|
||||
formFileMock.Setup(f => f.OpenReadStream()).Returns(stream);
|
||||
formFileMock.Setup(f => f.CopyToAsync(It.IsAny<Stream>(), default))
|
||||
.Returns<Stream, CancellationToken>((target, _) => stream.CopyToAsync(target));
|
||||
|
||||
var uploadRequest = _fixture.Build<MediaUploadRequest>()
|
||||
.With(r => r.FromFile, formFileMock.Object)
|
||||
.With(r => r.Type, MediaType.Image)
|
||||
.With(r => r.MimeType, "image/png")
|
||||
.With(r => r.EncryptedKey, "secret")
|
||||
.Create();
|
||||
|
||||
var uploadResult = _fixture.Create<MediaUploadResult>();
|
||||
|
||||
_mockMedia.Setup(f => f.UploadMediaAsync(It.IsAny<Media>()))
|
||||
.ReturnsAsync(uploadResult);
|
||||
|
||||
// Act
|
||||
var result = await _controller.Upload(uploadRequest);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<OkObjectResult>());
|
||||
|
||||
var okResult = result as OkObjectResult;
|
||||
var value = okResult?.Value as MediaUploadResult;
|
||||
|
||||
Assert.That(value, Is.Not.Null);
|
||||
Assert.That(value!.Url, Is.EqualTo(uploadResult.Url));
|
||||
Assert.That(value.MediaId, Is.EqualTo(uploadResult.MediaId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Upload_InvalidModelState_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
_controller.ModelState.AddModelError("Error", "Invalid model state");
|
||||
var content = "fake file content";
|
||||
var fileName = "testfile.txt";
|
||||
var fileBytes = System.Text.Encoding.UTF8.GetBytes(content);
|
||||
var stream = new MemoryStream(fileBytes);
|
||||
|
||||
var formFileMock = new Mock<IFormFile>();
|
||||
formFileMock.Setup(f => f.Length).Returns(fileBytes.Length);
|
||||
formFileMock.Setup(f => f.FileName).Returns(fileName);
|
||||
formFileMock.Setup(f => f.OpenReadStream()).Returns(stream);
|
||||
formFileMock.Setup(f => f.CopyToAsync(It.IsAny<Stream>(), default))
|
||||
.Returns<Stream, CancellationToken>((target, _) => stream.CopyToAsync(target));
|
||||
|
||||
var uploadRequest = _fixture.Build<MediaUploadRequest>()
|
||||
.With(r => r.FromFile, formFileMock.Object)
|
||||
.With(r => r.Type, MediaType.Image)
|
||||
.With(r => r.MimeType, "image/png")
|
||||
.With(r => r.EncryptedKey, "secret")
|
||||
.Create();
|
||||
|
||||
// Act
|
||||
var result = await _controller.Upload(uploadRequest);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Upload_NoFileUploaded_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var uploadRequest = _fixture.Build<MediaUploadRequest>()
|
||||
.With(r => r.FromFile, (IFormFile)null)
|
||||
.Create();
|
||||
|
||||
// Act
|
||||
var result = await _controller.Upload(uploadRequest);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
var badRequestResult = result as BadRequestObjectResult;
|
||||
Assert.That(badRequestResult?.Value, Is.EqualTo("No file uploaded"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Upload_EmptyFile_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var formFileMock = new Mock<IFormFile>();
|
||||
formFileMock.Setup(f => f.Length).Returns(0);
|
||||
|
||||
var uploadRequest = _fixture.Build<MediaUploadRequest>()
|
||||
.With(r => r.FromFile, formFileMock.Object)
|
||||
.Create();
|
||||
|
||||
// Act
|
||||
var result = await _controller.Upload(uploadRequest);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
var badRequestResult = result as BadRequestObjectResult;
|
||||
Assert.That(badRequestResult?.Value, Is.EqualTo("No file uploaded"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Upload_FileTooLarge_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var formFileMock = new Mock<IFormFile>();
|
||||
formFileMock.Setup(f => f.Length).Returns(20_000_001); // Just over 20MB
|
||||
|
||||
var uploadRequest = _fixture.Build<MediaUploadRequest>()
|
||||
.With(r => r.FromFile, formFileMock.Object)
|
||||
.Create();
|
||||
|
||||
// Act
|
||||
var result = await _controller.Upload(uploadRequest);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
var badRequestResult = result as BadRequestObjectResult;
|
||||
Assert.That(badRequestResult?.Value, Is.EqualTo("File is too large"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Upload_MissingMimeType_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var formFileMock = new Mock<IFormFile>();
|
||||
formFileMock.Setup(f => f.Length).Returns(1000);
|
||||
formFileMock.Setup(f => f.FileName).Returns("testfile.txt");
|
||||
|
||||
var uploadRequest = _fixture.Build<MediaUploadRequest>()
|
||||
.With(r => r.FromFile, formFileMock.Object)
|
||||
.With(r => r.MimeType, string.Empty)
|
||||
.Create();
|
||||
|
||||
// Act
|
||||
var result = await _controller.Upload(uploadRequest);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
var badRequestResult = result as BadRequestObjectResult;
|
||||
Assert.That(badRequestResult?.Value, Is.EqualTo("Missing MIME type"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Upload_UnauthorizedAccess_ReturnsUnauthorized()
|
||||
{
|
||||
// Arrange
|
||||
var formFileMock = new Mock<IFormFile>();
|
||||
formFileMock.Setup(f => f.Length).Returns(1000);
|
||||
formFileMock.Setup(f => f.FileName).Returns("testfile.txt");
|
||||
formFileMock.Setup(f => f.OpenReadStream()).Returns(new MemoryStream(new byte[1000]));
|
||||
|
||||
var uploadRequest = _fixture.Build<MediaUploadRequest>()
|
||||
.With(r => r.FromFile, formFileMock.Object)
|
||||
.With(r => r.MimeType, "text/plain")
|
||||
.Create();
|
||||
|
||||
_mockMedia.Setup(f => f.UploadMediaAsync(It.IsAny<Media>()))
|
||||
.ThrowsAsync(new UnauthorizedAccessException("Access denied"));
|
||||
|
||||
// Act
|
||||
var result = await _controller.Upload(uploadRequest);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ForbidResult>());
|
||||
var forbidResult = result as ForbidResult;
|
||||
Assert.That(forbidResult.AuthenticationSchemes.First(), Is.EqualTo("Access denied"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Upload_InvalidOperation_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var formFileMock = new Mock<IFormFile>();
|
||||
formFileMock.Setup(f => f.Length).Returns(1000);
|
||||
formFileMock.Setup(f => f.FileName).Returns("testfile.txt");
|
||||
formFileMock.Setup(f => f.OpenReadStream()).Returns(new MemoryStream(new byte[1000]));
|
||||
|
||||
var uploadRequest = _fixture.Build<MediaUploadRequest>()
|
||||
.With(r => r.FromFile, formFileMock.Object)
|
||||
.With(r => r.MimeType, "text/plain")
|
||||
.Create();
|
||||
|
||||
_mockMedia.Setup(f => f.UploadMediaAsync(It.IsAny<Media>()))
|
||||
.ThrowsAsync(new InvalidOperationException("Invalid operation"));
|
||||
|
||||
// Act
|
||||
var result = await _controller.Upload(uploadRequest);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
var badRequestResult = result as BadRequestObjectResult;
|
||||
Assert.That(badRequestResult?.Value, Is.EqualTo("Invalid operation"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Upload_GeneralException_ReturnsInternalServerError()
|
||||
{
|
||||
// Arrange
|
||||
var formFileMock = new Mock<IFormFile>();
|
||||
formFileMock.Setup(f => f.Length).Returns(1000);
|
||||
formFileMock.Setup(f => f.FileName).Returns("testfile.txt");
|
||||
formFileMock.Setup(f => f.OpenReadStream()).Returns(new MemoryStream(new byte[1000]));
|
||||
|
||||
var uploadRequest = _fixture.Build<MediaUploadRequest>()
|
||||
.With(r => r.FromFile, formFileMock.Object)
|
||||
.With(r => r.MimeType, "text/plain")
|
||||
.Create();
|
||||
|
||||
_mockMedia.Setup(f => f.UploadMediaAsync(It.IsAny<Media>()))
|
||||
.ThrowsAsync(new Exception("Something went wrong"));
|
||||
|
||||
// Act
|
||||
var result = await _controller.Upload(uploadRequest);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ObjectResult>());
|
||||
var objectResult = result as ObjectResult;
|
||||
Assert.That(objectResult.StatusCode, Is.EqualTo(500));
|
||||
}
|
||||
|
||||
// Tests for Download action
|
||||
[Test]
|
||||
public async Task Download_HasAccessAndMediaExists_ReturnsFile()
|
||||
{
|
||||
// Arrange
|
||||
var mediaId = Guid.NewGuid();
|
||||
var media = _fixture.Build<Media>()
|
||||
.With(m => m.Data, Encoding.UTF8.GetBytes("fake file content"))
|
||||
.With(m => m.MimeType, "application/octet-stream") // Ensure MimeType is set
|
||||
.With(m => m.FileName, "testfile.txt")
|
||||
.Create();
|
||||
|
||||
_mockAccesser.Setup(f => f.HasAccessAsync(mediaId, _userId)).ReturnsAsync(true);
|
||||
_mockMedia.Setup(f => f.GetMediaByIdAsync(mediaId)).ReturnsAsync(media);
|
||||
|
||||
// Act
|
||||
var result = await _controller.Download(mediaId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<FileContentResult>());
|
||||
var fileResult = result as FileContentResult;
|
||||
Assert.That(fileResult?.FileContents, Is.EqualTo(media.Data));
|
||||
Assert.That(fileResult?.ContentType, Is.EqualTo(media.MimeType)); // Changed MineType to MimeType
|
||||
Assert.That(fileResult?.FileDownloadName, Is.EqualTo(Path.GetFileName(media.FileName)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Download_NoAccess_ReturnsForbid()
|
||||
{
|
||||
// Arrange
|
||||
var mediaId = Guid.NewGuid();
|
||||
|
||||
_mockAccesser.Setup(f => f.HasAccessAsync(mediaId, _userId)).ReturnsAsync(false);
|
||||
|
||||
// Act
|
||||
var result = await _controller.Download(mediaId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ForbidResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Download_MediaNotFound_ReturnsNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var mediaId = Guid.NewGuid();
|
||||
|
||||
_mockAccesser.Setup(f => f.HasAccessAsync(mediaId, _userId)).ReturnsAsync(true);
|
||||
_mockMedia.Setup(f => f.GetMediaByIdAsync(mediaId))
|
||||
.ThrowsAsync(new KeyNotFoundException("Media not found"));
|
||||
|
||||
// Act
|
||||
var result = await _controller.Download(mediaId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<NotFoundObjectResult>());
|
||||
var notFoundResult = result as NotFoundObjectResult;
|
||||
Assert.That(notFoundResult?.Value, Is.EqualTo("Media not found"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Download_GeneralException_ReturnsInternalServerError()
|
||||
{
|
||||
// Arrange
|
||||
var mediaId = Guid.NewGuid();
|
||||
|
||||
_mockAccesser.Setup(f => f.HasAccessAsync(mediaId, _userId)).ReturnsAsync(true);
|
||||
_mockMedia.Setup(f => f.GetMediaByIdAsync(mediaId))
|
||||
.ThrowsAsync(new Exception("Something went wrong"));
|
||||
|
||||
// Act
|
||||
var result = await _controller.Download(mediaId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ObjectResult>());
|
||||
var objectResult = result as ObjectResult;
|
||||
Assert.That(objectResult.StatusCode, Is.EqualTo(500));
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_controller.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using Govor.API.Controllers;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.IntegrationTests.Controllers;
|
||||
|
||||
[TestFixture]
|
||||
public class OnlinePingingControllerTests
|
||||
{
|
||||
private Mock<ILogger<OnlinePingingController>> _loggerMock;
|
||||
private Mock<IPingHandlerService> _pingHandlerServiceMock;
|
||||
private Mock<ICurrentUserService> _currentUserServiceMock;
|
||||
private OnlinePingingController _controller;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_loggerMock = new Mock<ILogger<OnlinePingingController>>();
|
||||
_pingHandlerServiceMock = new Mock<IPingHandlerService>();
|
||||
_currentUserServiceMock = new Mock<ICurrentUserService>();
|
||||
_controller = new OnlinePingingController(_loggerMock.Object, _pingHandlerServiceMock.Object,
|
||||
_currentUserServiceMock.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Ping_ValidUserId_ReturnsOk()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
_currentUserServiceMock.Setup(x => x.GetCurrentUserId()).Returns(userId);
|
||||
_pingHandlerServiceMock.Setup(x => x.Ping(userId)).Returns(Task.CompletedTask);
|
||||
|
||||
// Act
|
||||
var result = await _controller.Ping();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<OkResult>());
|
||||
_pingHandlerServiceMock.Verify(x => x.Ping(userId), Times.Once());
|
||||
_loggerMock.VerifyLog(LogLevel.Information, $"Ping from user {userId} processed successfully", Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Ping_InvalidOperationException_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var exception = new InvalidOperationException("User not found");
|
||||
_currentUserServiceMock.Setup(x => x.GetCurrentUserId()).Returns(userId);
|
||||
_pingHandlerServiceMock.Setup(x => x.Ping(userId)).ThrowsAsync(exception);
|
||||
|
||||
// Act
|
||||
var result = await _controller.Ping();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
var badRequestResult = (BadRequestObjectResult)result;
|
||||
Assert.That(badRequestResult.Value, Is.EqualTo("User can't be found in our database."));
|
||||
_loggerMock.VerifyLog(LogLevel.Error, exception.Message, Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Ping_UnauthorizedAccessException_ReturnsForbid()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var exception = new UnauthorizedAccessException("Unauthorized");
|
||||
_currentUserServiceMock.Setup(x => x.GetCurrentUserId()).Returns(userId);
|
||||
_pingHandlerServiceMock.Setup(x => x.Ping(userId)).ThrowsAsync(exception);
|
||||
|
||||
// Act
|
||||
var result = await _controller.Ping();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ForbidResult>());
|
||||
_loggerMock.VerifyLog(LogLevel.Error, exception.Message, Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Ping_GeneralException_ReturnsStatusCode500()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var exception = new Exception("Unexpected error");
|
||||
_currentUserServiceMock.Setup(x => x.GetCurrentUserId()).Returns(userId);
|
||||
_pingHandlerServiceMock.Setup(x => x.Ping(userId)).ThrowsAsync(exception);
|
||||
|
||||
// Act
|
||||
var result = await _controller.Ping();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ObjectResult>());
|
||||
var objectResult = (ObjectResult)result;
|
||||
Assert.That(objectResult.StatusCode, Is.EqualTo(500));
|
||||
_loggerMock.VerifyLog(LogLevel.Error, exception.Message, Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Ping_UnauthorizedUserId_ThrowsUnauthorizedAccessException()
|
||||
{
|
||||
// Arrange
|
||||
var exception = new UnauthorizedAccessException("userID claim is missing or invalid");
|
||||
_currentUserServiceMock.Setup(x => x.GetCurrentUserId()).Throws(exception);
|
||||
|
||||
// Act
|
||||
var result = await _controller.Ping();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ForbidResult>());
|
||||
_loggerMock.VerifyLog(LogLevel.Error, exception.Message, Times.Once());
|
||||
_pingHandlerServiceMock.Verify(x => x.Ping(It.IsAny<Guid>()), Times.Never());
|
||||
}
|
||||
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_controller?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper extension for verifying logger calls
|
||||
public static class LoggerMockExtensions
|
||||
{
|
||||
public static void VerifyLog<T>(this Mock<ILogger<T>> logger, LogLevel level, string message, Times times)
|
||||
{
|
||||
logger.Verify(
|
||||
x => x.Log(
|
||||
It.Is<LogLevel>(l => l == level),
|
||||
It.IsAny<EventId>(),
|
||||
It.Is<It.IsAnyType>((v, t) => v.ToString().Contains(message)),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()),
|
||||
times);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
using System.Text;
|
||||
using AutoMapper;
|
||||
using Govor.API.Controllers;
|
||||
using Govor.API.Hubs;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Application.Interfaces.Medias;
|
||||
using Govor.Application.Profiles;
|
||||
using Govor.Contracts.DTOs;
|
||||
using Govor.Contracts.Requests;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Messages;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Govor.API.Tests;
|
||||
|
||||
[TestFixture]
|
||||
public class ProfileControllerTests
|
||||
{
|
||||
private Mock<ILogger<ProfileController>> _mockLogger = null!;
|
||||
private Mock<IMediaService> _mockMediaService = null!;
|
||||
private Mock<IProfileService> _mockProfileService = null!;
|
||||
private Mock<ICurrentUserService> _mockCurrentUserService = null!;
|
||||
private Mock<IHubContext<ProfileHub>> _mockProfileHubContext = null!;
|
||||
private Mock<IHubClients> _mockClients = null!;
|
||||
private Mock<IClientProxy> _mockClientProxy = null!;
|
||||
private Mock<IMapper> _mockMapper = null!;
|
||||
private ProfileController _controller = null!;
|
||||
|
||||
private Guid _userId;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_mockLogger = new Mock<ILogger<ProfileController>>();
|
||||
_mockMediaService = new Mock<IMediaService>();
|
||||
_mockProfileService = new Mock<IProfileService>();
|
||||
_mockCurrentUserService = new Mock<ICurrentUserService>();
|
||||
_mockMapper = new Mock<IMapper>();
|
||||
|
||||
_mockProfileHubContext = new Mock<IHubContext<ProfileHub>>();
|
||||
_mockClients = new Mock<IHubClients>();
|
||||
_mockClientProxy = new Mock<IClientProxy>();
|
||||
|
||||
_mockProfileHubContext.Setup(x => x.Clients).Returns(_mockClients.Object);
|
||||
_mockClients.Setup(x => x.All).Returns(_mockClientProxy.Object);
|
||||
_mockClientProxy.Setup(x => x.SendCoreAsync(It.IsAny<string>(), It.IsAny<object?[]?>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
_controller = new ProfileController(
|
||||
_mockLogger.Object,
|
||||
_mockMediaService.Object,
|
||||
_mockProfileService.Object,
|
||||
_mockCurrentUserService.Object,
|
||||
_mockProfileHubContext.Object,
|
||||
_mockMapper.Object);
|
||||
|
||||
_userId = Guid.NewGuid();
|
||||
_mockCurrentUserService.Setup(x => x.GetCurrentUserId()).Returns(_userId);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UploadAvatar_ReturnsBadRequest_WhenFileIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var request = new AvatarUploadRequest
|
||||
{
|
||||
FromFile = null
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _controller.UploadAvatar(request);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
var badRequestResult = (BadRequestObjectResult)result;
|
||||
Assert.That(badRequestResult.Value, Is.EqualTo("File is empty."));
|
||||
|
||||
_mockMediaService.Verify(s => s.UploadMediaAsync(It.IsAny<Media>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UploadAvatar_ReturnsBadRequest_WhenFileLengthIsZero()
|
||||
{
|
||||
// Arrange
|
||||
var mockFile = new Mock<IFormFile>();
|
||||
mockFile.Setup(f => f.Length).Returns(0);
|
||||
|
||||
var request = new AvatarUploadRequest
|
||||
{
|
||||
FromFile = mockFile.Object
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _controller.UploadAvatar(request);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
var badRequestResult = (BadRequestObjectResult)result;
|
||||
Assert.That(badRequestResult.Value, Is.EqualTo("File is empty."));
|
||||
|
||||
_mockMediaService.Verify(s => s.UploadMediaAsync(It.IsAny<Media>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UploadAvatar_ReturnsOkAndPerformsAllActions_WhenFileIsValid()
|
||||
{
|
||||
// Arrange
|
||||
var mediaId = Guid.NewGuid();
|
||||
var expectedMediaInfo = new MediaUploadResult(mediaId, $"/media/{mediaId}");
|
||||
|
||||
var fileName = "test_avatar.jpg";
|
||||
var fileContent = "This is a test image content";
|
||||
var stream = new MemoryStream(Encoding.UTF8.GetBytes(fileContent));
|
||||
|
||||
var mockFile = new Mock<IFormFile>();
|
||||
mockFile.Setup(f => f.FileName).Returns(fileName);
|
||||
mockFile.Setup(f => f.Length).Returns(stream.Length);
|
||||
mockFile.Setup(f => f.OpenReadStream()).Returns(stream);
|
||||
mockFile.Setup(f => f.CopyToAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>()))
|
||||
.Callback<Stream, CancellationToken>((s, t) => stream.CopyTo(s)); // Для ReadFileAsync
|
||||
|
||||
var request = new AvatarUploadRequest
|
||||
{
|
||||
FromFile = mockFile.Object,
|
||||
Type = MediaType.Image,
|
||||
MimeType = "image/jpeg"
|
||||
};
|
||||
|
||||
_mockMediaService.Setup(s => s.UploadMediaAsync(It.IsAny<Media>()))
|
||||
.ReturnsAsync(expectedMediaInfo);
|
||||
|
||||
_mockProfileService.Setup(s => s.SetNewIcon(_userId, mediaId))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
// Act
|
||||
var result = await _controller.UploadAvatar(request);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<OkObjectResult>());
|
||||
var okResult = (OkObjectResult)result;
|
||||
Assert.That(okResult.StatusCode, Is.EqualTo(200));
|
||||
Assert.That(okResult.Value, Is.EqualTo(expectedMediaInfo));
|
||||
|
||||
mockFile.Verify(f => f.CopyToAsync(It.IsAny<Stream>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
|
||||
_mockMediaService.Verify(s => s.UploadMediaAsync(It.Is<Media>(m =>
|
||||
m.OwnerId == _userId &&
|
||||
m.OwnerType == MediaOwnerType.Avatar &&
|
||||
m.FileName == fileName
|
||||
)), Times.Once);
|
||||
|
||||
_mockProfileService.Verify(s => s.SetNewIcon(_userId, mediaId), Times.Once);
|
||||
|
||||
_mockClientProxy.Verify(
|
||||
c => c.SendCoreAsync("AvatarUpdated", It.Is<object?[]>(
|
||||
args => args.Length == 1 &&
|
||||
JObject.FromObject(args[0]!).Value<Guid>("userId") == _userId &&
|
||||
JObject.FromObject(args[0]!).Value<Guid>("iconId") == mediaId
|
||||
), It.IsAny<CancellationToken>()),
|
||||
Times.Once,
|
||||
"Hub SendAsync must be called to notify clients."
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task UploadAvatar_ReturnsInternalServerError_WhenMediaServiceFails()
|
||||
{
|
||||
// Arrange
|
||||
var mockFile = new Mock<IFormFile>();
|
||||
mockFile.Setup(f => f.Length).Returns(100);
|
||||
mockFile.Setup(f => f.OpenReadStream()).Returns(new MemoryStream(Encoding.UTF8.GetBytes("test")));
|
||||
|
||||
var request = new AvatarUploadRequest { FromFile = mockFile.Object };
|
||||
|
||||
_mockMediaService.Setup(s => s.UploadMediaAsync(It.IsAny<Media>()))
|
||||
.ThrowsAsync(new System.Exception("Simulated upload failure"));
|
||||
|
||||
// Act
|
||||
var result = await _controller.UploadAvatar(request);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ObjectResult>());
|
||||
var objectResult = (ObjectResult)result;
|
||||
Assert.That(objectResult.StatusCode, Is.EqualTo(500));
|
||||
|
||||
_mockProfileService.Verify(s => s.SetNewIcon(It.IsAny<Guid>(), It.IsAny<Guid>()), Times.Never);
|
||||
|
||||
_mockClientProxy.Verify(
|
||||
c => c.SendCoreAsync(It.IsAny<string>(), It.IsAny<object?[]?>(), It.IsAny<CancellationToken>()),
|
||||
Times.Never,
|
||||
"The Hub should not be called in case of a service error."
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DowloadProfile_ReturnsOk_WhenProfileExists()
|
||||
{
|
||||
// Arrange
|
||||
var userProfile = new UserProfile
|
||||
{
|
||||
Id = _userId,
|
||||
Username = "test_user",
|
||||
Description = "test description",
|
||||
IconId = Guid.NewGuid()
|
||||
};
|
||||
|
||||
var userProfileDto = new UserProfileDto
|
||||
{
|
||||
Id = _userId,
|
||||
Username = "test_user",
|
||||
Description = "test description",
|
||||
IconId = userProfile.IconId
|
||||
};
|
||||
|
||||
_mockProfileService.Setup(s => s.GetUserProfileAsync(_userId))
|
||||
.ReturnsAsync(userProfile);
|
||||
|
||||
_mockMapper.Setup(m => m.Map<UserProfileDto>(userProfile))
|
||||
.Returns(userProfileDto);
|
||||
|
||||
// Act
|
||||
var result = await _controller.DownloadProfile();
|
||||
|
||||
// Assert
|
||||
var okResult = result as OkObjectResult;
|
||||
Assert.That(okResult, Is.Not.Null);
|
||||
Assert.That(okResult!.StatusCode, Is.EqualTo(200));
|
||||
Assert.That(okResult.Value, Is.EqualTo(userProfileDto));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DowloadProfile_ReturnsForbid_WhenUnauthorizedAccessExceptionThrown()
|
||||
{
|
||||
// Arrange
|
||||
_mockProfileService.Setup(s => s.GetUserProfileAsync(_userId))
|
||||
.ThrowsAsync(new UnauthorizedAccessException("Access denied"));
|
||||
|
||||
// Act
|
||||
var result = await _controller.DownloadProfile();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<ForbidResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DowloadProfile_ReturnsBadRequest_WhenNotFoundExceptionThrown()
|
||||
{
|
||||
// Arrange
|
||||
_mockProfileService.Setup(s => s.GetUserProfileAsync(_userId))
|
||||
.ThrowsAsync(new NotFoundException("Cannot find profile"));
|
||||
|
||||
// Act
|
||||
var result = await _controller.DownloadProfile();
|
||||
|
||||
// Assert
|
||||
var notFoundResult = result as NotFoundObjectResult;
|
||||
Assert.That(notFoundResult, Is.Not.Null);
|
||||
Assert.That(notFoundResult!.Value, Is.EqualTo("Profile not found"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DowloadProfile_Returns500_WhenUnexpectedExceptionThrown()
|
||||
{
|
||||
// Arrange
|
||||
_mockProfileService.Setup(s => s.GetUserProfileAsync(_userId))
|
||||
.ThrowsAsync(new Exception("Unexpected error"));
|
||||
|
||||
// Act
|
||||
var result = await _controller.DownloadProfile();
|
||||
|
||||
// Assert
|
||||
var objectResult = result as ObjectResult;
|
||||
Assert.That(objectResult, Is.Not.Null);
|
||||
Assert.That(objectResult!.StatusCode, Is.EqualTo(500));
|
||||
Assert.That(objectResult.Value, Is.EqualTo("Unexpected Error! Please try again later."));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using AutoFixture;
|
||||
using Govor.API.Controllers.Authentication;
|
||||
using Govor.Application.Interfaces.UserSession;
|
||||
using Govor.Contracts.Requests;
|
||||
using Govor.Contracts.Responses;
|
||||
using Microsoft.AspNetCore.Identity.Data;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.IntegrationTests.Controllers;
|
||||
|
||||
[TestFixture]
|
||||
public class RefreshControllerTests
|
||||
{
|
||||
private Fixture _fixture;
|
||||
private Mock<ILogger<RefreshController>> _mockLogger;
|
||||
private Mock<IUserSessionRefresher> _mockSessionRefresher;
|
||||
private RefreshController _controller;
|
||||
private RefreshTokenRequest _request;
|
||||
private RefreshResult _result;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
|
||||
_mockLogger = new Mock<ILogger<RefreshController>>();
|
||||
_mockSessionRefresher = new Mock<IUserSessionRefresher>();
|
||||
|
||||
_request = _fixture.Create<RefreshTokenRequest>();
|
||||
_result = _fixture.Create<RefreshResult>();
|
||||
|
||||
_controller = new RefreshController(_mockLogger.Object, _mockSessionRefresher.Object);
|
||||
}
|
||||
|
||||
// Tests for Refresh action
|
||||
[Test]
|
||||
public async Task Refresh_ValidRequest_ReturnsOkResult()
|
||||
{
|
||||
// Arrange
|
||||
_mockSessionRefresher.Setup(f => f.RefreshTokenAsync(_request.RefreshToken))
|
||||
.ReturnsAsync(_result);
|
||||
|
||||
// Act
|
||||
var result = await _controller.Refresh(_request);
|
||||
|
||||
//Assert
|
||||
Assert.That(result, Is.InstanceOf<OkObjectResult>());
|
||||
var okResult = result as OkObjectResult;
|
||||
|
||||
var response = okResult?.Value as RefreshTokenResponse;
|
||||
Assert.That(response, Is.Not.Null);
|
||||
Assert.That(response.AccessToken, Is.EqualTo(_result.accessToken));
|
||||
Assert.That(response.RefreshToken, Is.EqualTo(_result.refreshToken));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Refresh_InvalidModelState_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
_controller.ModelState.AddModelError("Error", "Sample error");
|
||||
|
||||
// Act
|
||||
var result = await _controller.Refresh(_request);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Refresh_InvalidRefreshToken_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
_request.RefreshToken = string.Empty;
|
||||
|
||||
// Act
|
||||
var result = await _controller.Refresh(_request);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
var badRequestResult = result as BadRequestObjectResult;
|
||||
var response = badRequestResult?.Value;
|
||||
Assert.That(response, Is.EqualTo("Refresh token cant be empty."));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Refresh_UnauthorizedAccessException_ReturnsUnauthorizedRequest()
|
||||
{
|
||||
// Arrange
|
||||
_mockSessionRefresher.Setup(f => f.RefreshTokenAsync(_request.RefreshToken))
|
||||
.ThrowsAsync(new UnauthorizedAccessException());
|
||||
|
||||
// Act
|
||||
var result = await _controller.Refresh(_request);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<UnauthorizedObjectResult>());
|
||||
var unauthorized = result as UnauthorizedObjectResult;
|
||||
var response = unauthorized?.Value;
|
||||
Assert.That(response, Is.EqualTo("Invalid refresh token"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Refresh_InvalidOperationException_ReturnsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
_mockSessionRefresher.Setup(f => f.RefreshTokenAsync(_request.RefreshToken))
|
||||
.ThrowsAsync(new InvalidOperationException());
|
||||
|
||||
// Act
|
||||
var result = await _controller.Refresh(_request);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.InstanceOf<BadRequestObjectResult>());
|
||||
}
|
||||
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_controller?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using AutoFixture;
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Govor.API.Hubs;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Messages;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.IntegrationTests.Hubs;
|
||||
|
||||
[TestFixture]
|
||||
public class ChatsHubTests
|
||||
{
|
||||
private Mock<ILogger<ChatsHub>> _loggerMock;
|
||||
private Mock<IMessageCommandService> _messageServiceMock;
|
||||
private Mock<IUserGroupsService> _userGroupsServiceMock;
|
||||
private Mock<IHubUserAccessor> _hubUserAccessorMock;
|
||||
private Fixture _fixture;
|
||||
private ChatsHub _chatsHub;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_messageServiceMock = new Mock<IMessageCommandService>();
|
||||
_userGroupsServiceMock = new Mock<IUserGroupsService>();
|
||||
_loggerMock = new Mock<ILogger<ChatsHub>>();
|
||||
_hubUserAccessorMock = new Mock<IHubUserAccessor>();
|
||||
|
||||
_chatsHub = new ChatsHub(
|
||||
_loggerMock.Object,
|
||||
_messageServiceMock.Object,
|
||||
_userGroupsServiceMock.Object,
|
||||
_hubUserAccessorMock.Object
|
||||
);
|
||||
}
|
||||
|
||||
// Test for Send action
|
||||
[Test]
|
||||
public void SendMessage_Success()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_chatsHub?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
using AutoFixture;
|
||||
using AutoMapper;
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Govor.API.Hubs;
|
||||
using Govor.Application.Exceptions.FriendsService;
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Contracts.DTOs;
|
||||
using Govor.Contracts.Responses.SignalR;
|
||||
using Govor.Core.Models;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.IntegrationTests.Hubs;
|
||||
|
||||
[TestFixture]
|
||||
public class FriendsHubTests
|
||||
{
|
||||
private Mock<IFriendRequestCommandService> _friendRequestServiceMock = null!;
|
||||
private Mock<IHubUserAccessor> _currentUserServiceMock = null!;
|
||||
private Mock<IHubCallerClients> _clientsMock = null!;
|
||||
private Mock<IClientProxy> _clientProxyMock = null!;
|
||||
private Mock<ILogger<FriendsHub>> _loggerMock = null!;
|
||||
private Mock<IMapper> _mapperMock = null!;
|
||||
private FriendsHub _hub = null!;
|
||||
private Fixture _fixture;
|
||||
private readonly Guid _userId = Guid.NewGuid();
|
||||
private readonly Guid _targetUserId = Guid.NewGuid();
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_friendRequestServiceMock = new Mock<IFriendRequestCommandService>();
|
||||
_currentUserServiceMock = new Mock<IHubUserAccessor>();
|
||||
_clientsMock = new Mock<IHubCallerClients>();
|
||||
_clientProxyMock = new Mock<IClientProxy>();
|
||||
_loggerMock = new Mock<ILogger<FriendsHub>>();
|
||||
_mapperMock = new Mock<IMapper>();
|
||||
|
||||
_currentUserServiceMock.Setup(x => x.GetUserId(
|
||||
It.IsAny<HubCallerContext>(),
|
||||
It.IsAny<bool>()))
|
||||
.Returns(_userId);
|
||||
|
||||
_clientsMock.Setup(c => c.Group(It.IsAny<string>())).Returns(_clientProxyMock.Object);
|
||||
|
||||
_hub = new FriendsHub(
|
||||
_loggerMock.Object,
|
||||
_friendRequestServiceMock.Object,
|
||||
_currentUserServiceMock.Object,
|
||||
_mapperMock.Object)
|
||||
{
|
||||
Clients = _clientsMock.Object
|
||||
};
|
||||
}
|
||||
|
||||
// Tests for SendRequest action
|
||||
[Test]
|
||||
public async Task SendRequest_ShouldReturnCreated_WhenSuccess()
|
||||
{
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Pending)
|
||||
.With(f => f.RequesterId, _userId)
|
||||
.With(f =>f.AddresseeId, _targetUserId)
|
||||
.Create();
|
||||
|
||||
var dto = new FriendshipDto()
|
||||
{
|
||||
Id = friendship.Id,
|
||||
Status = FriendshipStatus.Accepted,
|
||||
AddresseeId = friendship.AddresseeId,
|
||||
RequesterId = friendship.RequesterId,
|
||||
};
|
||||
|
||||
_mapperMock.Setup(f => f.Map<FriendshipDto>(friendship))
|
||||
.Returns(dto);
|
||||
|
||||
_friendRequestServiceMock.Setup(f => f.SendAsync(_userId, _targetUserId))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
// Act
|
||||
var result = await _hub.SendRequest(_targetUserId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.Status, Is.EqualTo(HubResultStatus.Created));
|
||||
|
||||
_friendRequestServiceMock.Verify(s => s.SendAsync(_userId, _targetUserId), Times.Once);
|
||||
|
||||
_clientProxyMock.Verify(c => c.SendCoreAsync(
|
||||
"FriendRequestReceived",
|
||||
It.Is<object[]>(o => o[0]!.Equals(dto)),
|
||||
default), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SendRequest_ShouldReturnBadRequest_WhenInvalidOperation()
|
||||
{
|
||||
// Arrange
|
||||
_friendRequestServiceMock
|
||||
.Setup(s => s.SendAsync(_userId, _targetUserId))
|
||||
.ThrowsAsync(new InvalidOperationException("Invalid"));
|
||||
|
||||
// Act
|
||||
var result = await _hub.SendRequest(_targetUserId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Status, Is.EqualTo(HubResultStatus.BadRequest));
|
||||
Assert.That(result.ErrorMessage, Is.EqualTo("Invalid"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SendRequest_ShouldReturnConflict_WhenAlreadySent()
|
||||
{
|
||||
// Arrange
|
||||
_friendRequestServiceMock
|
||||
.Setup(s => s.SendAsync(_userId, _targetUserId))
|
||||
.ThrowsAsync(new RequestAlreadySentException(_userId, _targetUserId));
|
||||
|
||||
// Act
|
||||
var result = await _hub.SendRequest(_targetUserId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Status, Is.EqualTo(HubResultStatus.Conflict));
|
||||
Assert.That(result.ErrorMessage, Is.EqualTo($"Request was already sent from {_userId} to {_targetUserId}"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SendRequest_ShouldReturnUnauthorized_WhenCurrentUserIsNotAuthenticated()
|
||||
{
|
||||
// Arrange
|
||||
_currentUserServiceMock.Setup(x => x.GetUserId(It.IsAny<HubCallerContext>(), It.IsAny<bool>()))
|
||||
.Throws(new UnauthorizedAccessException("userId claim is missing or invalid"));
|
||||
|
||||
// Act
|
||||
var result = await _hub.SendRequest(_targetUserId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Status, Is.EqualTo(HubResultStatus.Unauthorized));
|
||||
Assert.That(result.ErrorMessage, Is.EqualTo("userId claim is missing or invalid"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SendRequest_ShouldReturnError_WhenSomeExceptionOccurs()
|
||||
{
|
||||
// Arrange
|
||||
_friendRequestServiceMock
|
||||
.Setup(s => s.SendAsync(_userId, _targetUserId))
|
||||
.ThrowsAsync(new Exception("error"));
|
||||
|
||||
// Act
|
||||
var result = await _hub.SendRequest(_targetUserId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Status, Is.EqualTo(HubResultStatus.ServerError));
|
||||
Assert.That(result.ErrorMessage, Is.EqualTo("Unexpected error! Please try later!"));
|
||||
}
|
||||
|
||||
// Test for AcceptRequest action
|
||||
[Test]
|
||||
public async Task AcceptRequest_ShouldReturnOk_WhenSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Pending)
|
||||
.Create();
|
||||
var dto = new FriendshipDto()
|
||||
{
|
||||
Id = friendship.Id,
|
||||
Status = FriendshipStatus.Accepted,
|
||||
AddresseeId = friendship.AddresseeId,
|
||||
RequesterId = friendship.RequesterId
|
||||
};
|
||||
|
||||
_mapperMock.Setup(f => f.Map<FriendshipDto>(friendship))
|
||||
.Returns(dto);
|
||||
|
||||
_friendRequestServiceMock.Setup(f => f.AcceptAsync(friendship.Id, _userId))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
// Act
|
||||
var result = await _hub.AcceptRequest(friendship.Id);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Status, Is.EqualTo(HubResultStatus.Success));
|
||||
_friendRequestServiceMock.Verify(s => s.AcceptAsync(friendship.Id, _userId), Times.Once);
|
||||
|
||||
_clientProxyMock.Verify(c => c.SendCoreAsync(
|
||||
"FriendRequestAccepted",
|
||||
It.Is<object[]>(o => o.Length == 1 && o[0] == dto),
|
||||
default), Times.Once);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AcceptRequest_ShouldReturnBadRequest_InvalidOperation()
|
||||
{
|
||||
// Arrange
|
||||
var friendshipId = Guid.NewGuid();
|
||||
_friendRequestServiceMock
|
||||
.Setup(s => s.AcceptAsync(friendshipId, _userId))
|
||||
.ThrowsAsync(new InvalidOperationException("Invalid"));
|
||||
|
||||
// Act
|
||||
var result = await _hub.AcceptRequest(friendshipId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Status, Is.EqualTo(HubResultStatus.BadRequest));
|
||||
Assert.That(result.ErrorMessage, Is.EqualTo("Invalid"));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task AcceptRequest_ShouldReturnUnauthorized_UnauthorizedAccess()
|
||||
{
|
||||
// Arrange
|
||||
var friendshipId = Guid.NewGuid();
|
||||
_friendRequestServiceMock
|
||||
.Setup(s => s.AcceptAsync(friendshipId, _userId))
|
||||
.ThrowsAsync(new UnauthorizedAccessException("userId claim is missing or invalid"));
|
||||
|
||||
// Act
|
||||
var result = await _hub.AcceptRequest(friendshipId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Status, Is.EqualTo(HubResultStatus.Unauthorized));
|
||||
Assert.That(result.ErrorMessage, Is.EqualTo("userId claim is missing or invalid"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AcceptRequest_ShouldReturnError_WhenSomeExceptionOccurs()
|
||||
{
|
||||
// Arrange
|
||||
var friendshipId = Guid.NewGuid();
|
||||
_friendRequestServiceMock
|
||||
.Setup(s => s.AcceptAsync(friendshipId, _userId))
|
||||
.ThrowsAsync(new Exception("error"));
|
||||
|
||||
// Act
|
||||
var result = await _hub.AcceptRequest(friendshipId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Status, Is.EqualTo(HubResultStatus.ServerError));
|
||||
Assert.That(result.ErrorMessage, Is.EqualTo("Unexpected error! Please try later!"));
|
||||
}
|
||||
|
||||
// Test for RejectRequest action
|
||||
[Test]
|
||||
public async Task RejectRequest_ShouldReturnOk_WhenSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Pending)
|
||||
.Create();
|
||||
var dto = new FriendshipDto()
|
||||
{
|
||||
Id = friendship.Id,
|
||||
Status = FriendshipStatus.Accepted,
|
||||
AddresseeId = friendship.AddresseeId,
|
||||
RequesterId = friendship.RequesterId
|
||||
};
|
||||
|
||||
_mapperMock.Setup(f => f.Map<FriendshipDto>(friendship))
|
||||
.Returns(dto);
|
||||
|
||||
_friendRequestServiceMock.Setup(f => f.RejectAsync(friendship.Id, _userId))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
// Act
|
||||
var result = await _hub.RejectRequest(friendship.Id);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Status, Is.EqualTo(HubResultStatus.Success));
|
||||
_friendRequestServiceMock.Verify(s => s.RejectAsync(friendship.Id, _userId), Times.Once);
|
||||
|
||||
_clientProxyMock.Verify(c => c.SendCoreAsync(
|
||||
"FriendRequestRejected",
|
||||
It.Is<object[]>(o => o[0]!.Equals(dto)),
|
||||
default), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RejectRequest_ShouldReturnBadRequest_InvalidOperation()
|
||||
{
|
||||
// Arrange
|
||||
var friendshipId = Guid.NewGuid();
|
||||
_friendRequestServiceMock
|
||||
.Setup(s => s.RejectAsync(friendshipId, _userId))
|
||||
.ThrowsAsync(new InvalidOperationException("Invalid"));
|
||||
|
||||
// Act
|
||||
var result = await _hub.RejectRequest(friendshipId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Status, Is.EqualTo(HubResultStatus.BadRequest));
|
||||
Assert.That(result.ErrorMessage, Is.EqualTo("Invalid"));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task RejectRequest_ShouldReturnUnauthorized_UnauthorizedAccess()
|
||||
{
|
||||
// Arrange
|
||||
var friendshipId = Guid.NewGuid();
|
||||
_friendRequestServiceMock
|
||||
.Setup(s => s.RejectAsync(friendshipId, _userId))
|
||||
.ThrowsAsync(new UnauthorizedAccessException("userId claim is missing or invalid"));
|
||||
|
||||
// Act
|
||||
var result = await _hub.RejectRequest(friendshipId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Status, Is.EqualTo(HubResultStatus.Unauthorized));
|
||||
Assert.That(result.ErrorMessage, Is.EqualTo("userId claim is missing or invalid"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RejectRequest_ShouldReturnError_WhenSomeExceptionOccurs()
|
||||
{
|
||||
// Arrange
|
||||
var friendshipId = Guid.NewGuid();
|
||||
_friendRequestServiceMock
|
||||
.Setup(s => s.RejectAsync(friendshipId, _userId))
|
||||
.ThrowsAsync(new Exception("error"));
|
||||
|
||||
// Act
|
||||
var result = await _hub.RejectRequest(friendshipId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Status, Is.EqualTo(HubResultStatus.ServerError));
|
||||
Assert.That(result.ErrorMessage, Is.EqualTo("Unexpected error! Please try later!"));
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_hub?.Dispose();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ using Govor.Application.Services;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.UnitTests.Services;
|
||||
namespace Govor.Application.Tests.Services;
|
||||
|
||||
[TestFixture]
|
||||
public class LocalStorageServiceTests
|
||||
@@ -0,0 +1,118 @@
|
||||
using AutoFixture;
|
||||
using Govor.Core.Infrastructure.Validators;
|
||||
using Govor.Core.Models;
|
||||
|
||||
namespace Govor.API.Tests.UnitTests.Infrastructure.Validators;
|
||||
|
||||
[TestFixture]
|
||||
public class ChatGroupValidatorTests
|
||||
{
|
||||
private IObjectValidator<ChatGroup> _validator = new ChatGroupValidator();
|
||||
private Fixture _fixture;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
|
||||
_fixture.Behaviors
|
||||
.OfType<ThrowingRecursionBehavior>()
|
||||
.ToList()
|
||||
.ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_ChatGroup_Valid()
|
||||
{
|
||||
// Arrange
|
||||
var chat = _fixture.Create<ChatGroup>();
|
||||
|
||||
// Act & Assert
|
||||
Assert.DoesNotThrow(() => _validator.Validate(chat));
|
||||
Assert.That(_validator.TryValidate(chat), Is.True);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void Validate_ChatGroupIsNull_Invalid()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidObjectException<ChatGroup>>(() => _validator.Validate(default));
|
||||
Assert.That(_validator.TryValidate(default), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_ChatGroupIdIsEmpty_Invalid()
|
||||
{
|
||||
// Arrange
|
||||
var chat = _fixture.Create<ChatGroup>();
|
||||
chat.Id = Guid.Empty;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidObjectException<ChatGroup>>(() => _validator.Validate(chat));
|
||||
Assert.That(_validator.TryValidate(chat), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_ChatGroupNameIsEmpty_Invalid()
|
||||
{
|
||||
// Arrange
|
||||
var chat = _fixture.Create<ChatGroup>();
|
||||
chat.Name = "";
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidObjectException<ChatGroup>>(() => _validator.Validate(chat));
|
||||
Assert.That(_validator.TryValidate(chat), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_ChatGroupDescriptionIsNull_Invalid()
|
||||
{
|
||||
// Arrange
|
||||
var chat = _fixture.Create<ChatGroup>();
|
||||
chat.Description = default;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidObjectException<ChatGroup>>(() => _validator.Validate(chat));
|
||||
Assert.That(_validator.TryValidate(chat), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_ChatGroupImageIdIsEmpty_Invalid()
|
||||
{
|
||||
// Arrange
|
||||
var chat = _fixture.Create<ChatGroup>();
|
||||
chat.ImageId = Guid.Empty;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidObjectException<ChatGroup>>(() => _validator.Validate(chat));
|
||||
Assert.That(_validator.TryValidate(chat), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_ChatGroupIsPrivateAndInvitesCodesIsEmpty_Invalid()
|
||||
{
|
||||
// Arrange
|
||||
var chat = _fixture.Create<ChatGroup>();
|
||||
chat.IsPrivate = true;
|
||||
chat.InviteCodes = new();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidObjectException<ChatGroup>>(() => _validator.Validate(chat));
|
||||
Assert.That(_validator.TryValidate(chat), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_ChatGroupAdminsIsEmpty_Invalid()
|
||||
{
|
||||
// Arrange
|
||||
var chat = _fixture.Create<ChatGroup>();
|
||||
chat.Admins = new();
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidObjectException<ChatGroup>>(() => _validator.Validate(chat));
|
||||
Assert.That(_validator.TryValidate(chat), Is.False);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using AutoFixture;
|
||||
using Govor.Core.Infrastructure.Validators;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Messages;
|
||||
|
||||
namespace Govor.API.Tests.UnitTests.Infrastructure.Validators;
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using AutoFixture;
|
||||
using Govor.Core.Infrastructure.Validators;
|
||||
using Govor.Core.Models;
|
||||
|
||||
namespace Govor.API.Tests.UnitTests.Infrastructure.Validators;
|
||||
|
||||
[TestFixture]
|
||||
public class PrivateChatValidatorTests
|
||||
{
|
||||
private IObjectValidator<PrivateChat> _validator = new PrivateChatValidator();
|
||||
private Fixture _fixture;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
|
||||
_fixture.Behaviors
|
||||
.OfType<ThrowingRecursionBehavior>()
|
||||
.ToList()
|
||||
.ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_PrivateChat_Valid()
|
||||
{
|
||||
// Arrange
|
||||
var privateChat = _fixture.Create<PrivateChat>();
|
||||
|
||||
// Act & Assert
|
||||
Assert.DoesNotThrow(() => _validator.Validate(privateChat));
|
||||
Assert.That(_validator.TryValidate(privateChat), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_When_PrivateChatIsNull_Invalid()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidObjectException<PrivateChat>>(() => _validator.Validate(default));
|
||||
Assert.That(_validator.TryValidate(default), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_When_PrivateChatIdIsEmpty_Invalid()
|
||||
{
|
||||
// Arrange
|
||||
var privateChat = _fixture.Create<PrivateChat>();
|
||||
privateChat.Id = Guid.Empty;
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidObjectException<PrivateChat>>(() => _validator.Validate(privateChat));
|
||||
Assert.That(_validator.TryValidate(privateChat), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_When_UserAIdIsEmpty_Invalid()
|
||||
{
|
||||
// Arrange
|
||||
var privateChat = _fixture.Create<PrivateChat>();
|
||||
privateChat.UserAId = Guid.Empty;
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidObjectException<PrivateChat>>(() => _validator.Validate(privateChat));
|
||||
Assert.That(_validator.TryValidate(privateChat), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Validate_When_UserBIdIsEmpty_Invalid()
|
||||
{
|
||||
// Arrange
|
||||
var privateChat = _fixture.Create<PrivateChat>();
|
||||
privateChat.UserBId = Guid.Empty;
|
||||
// Act & Assert
|
||||
Assert.Throws<InvalidObjectException<PrivateChat>>(() => _validator.Validate(privateChat));
|
||||
Assert.That(_validator.TryValidate(privateChat), Is.False);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using AutoFixture;
|
||||
using Govor.Core.Infrastructure.Validators;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Users;
|
||||
|
||||
namespace Govor.API.Tests.UnitTests.Infrastructure.Validators;
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using AutoFixture;
|
||||
using Govor.API.Services.Authentication.Interfaces;
|
||||
using Govor.Application.Services;
|
||||
using Govor.Core.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.UnitTests.Services.Authentication;
|
||||
|
||||
[TestFixture]
|
||||
public class JwtServiceTests
|
||||
{
|
||||
private Fixture _fixture;
|
||||
private Mock<IOptions<JwtOption>> _jwtOptionsMock;
|
||||
private Mock<IInvitesService> _invitesServiceMock;
|
||||
private IJwtService _jwtService;
|
||||
|
||||
private JwtOption _testJwtOptions;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_testJwtOptions = new JwtOption
|
||||
{
|
||||
SecretKeу = "THIS IS A TEST SECRET KEY THAT IS LONG ENOUGH", // Ensure key size is sufficient for HMACSHA256
|
||||
Hours = 1
|
||||
};
|
||||
|
||||
_jwtOptionsMock = new Mock<IOptions<JwtOption>>();
|
||||
_jwtOptionsMock.Setup(o => o.Value).Returns(_testJwtOptions);
|
||||
|
||||
_invitesServiceMock = new Mock<IInvitesService>();
|
||||
|
||||
_jwtService = new JwtService(_jwtOptionsMock.Object, _invitesServiceMock.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GenerateJwtToken_ShouldReturnValidJwtString()
|
||||
{
|
||||
// Arrange
|
||||
var user = _fixture.Create<User>();
|
||||
var expectedRole = "User";
|
||||
_invitesServiceMock.Setup(s => s.GetRole(user)).Returns(Task.FromResult(expectedRole));
|
||||
// Act
|
||||
var tokenString = _jwtService.GenerateJwtToken(user);
|
||||
|
||||
// Assert
|
||||
Assert.That(tokenString, Is.Not.Null.And.Not.Empty);
|
||||
|
||||
// Attempt to parse the token to ensure it's a JWT
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
Assert.DoesNotThrow(() => handler.ReadJwtToken(tokenString));
|
||||
}
|
||||
}
|
||||
@@ -1,358 +0,0 @@
|
||||
using AutoFixture;
|
||||
using Govor.Application.Exceptions.FriendsService;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Services;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Repositories.Friendships;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.UnitTests.Services;
|
||||
|
||||
[TestFixture]
|
||||
public class FriendsServiceTests
|
||||
{
|
||||
private Fixture _fixture;
|
||||
private Mock<IUsersRepository> _usersRepositoryMock;
|
||||
private Mock<IFriendshipsRepository> _friendshipsRepositoryMock;
|
||||
private IFriendsService _service;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors
|
||||
.OfType<ThrowingRecursionBehavior>()
|
||||
.ToList()
|
||||
.ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_usersRepositoryMock = new Mock<IUsersRepository>();
|
||||
_friendshipsRepositoryMock = new Mock<IFriendshipsRepository>();
|
||||
|
||||
_service = new FriendsService(_usersRepositoryMock.Object, _friendshipsRepositoryMock.Object);
|
||||
}
|
||||
|
||||
// SearchUsersAsync
|
||||
[Test]
|
||||
public async Task SearchUsersAsync_ReturnsUsers_IfNotAlreadyFriends()
|
||||
{
|
||||
// Arrange
|
||||
var userId = _fixture.Create<Guid>();
|
||||
var user = _fixture.Create<User>();
|
||||
user.Id = Guid.NewGuid();
|
||||
|
||||
_usersRepositoryMock
|
||||
.Setup(u => u.SearchPotentialFriendsAsync(userId, It.IsAny<string>()))
|
||||
.ReturnsAsync(new List<User> { user });
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(f => f.FindByUserIdAsync(userId))
|
||||
.ReturnsAsync(new List<Friendship>()); // No friends
|
||||
|
||||
// Act
|
||||
var result = await _service.SearchUsersAsync("test", userId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Has.Count.EqualTo(1));
|
||||
Assert.That(result[0].Id, Is.EqualTo(user.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SearchUsersAsync_Throws_SearchUsersException_IfThrowsNotFoundByKeyException_String_Guid()
|
||||
{
|
||||
// Arrange
|
||||
_usersRepositoryMock
|
||||
.Setup(u => u.SearchPotentialFriendsAsync(It.IsAny<Guid>(), It.IsAny<string>()))
|
||||
.ThrowsAsync(new NotFoundByKeyException<(string,Guid)>(("test",Guid.NewGuid()),"test"));
|
||||
|
||||
// Axt & Assert
|
||||
Assert.ThrowsAsync<SearchUsersException>(async () => await _service.SearchUsersAsync("test", Guid.NewGuid()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SearchUsersAsync_ReturnsUsersWithoutFriendships_IfThrowsNotFoundByKeyException_Guid()
|
||||
{
|
||||
// Arrange
|
||||
var userId = _fixture.Create<Guid>();
|
||||
var user = _fixture.Create<User>();
|
||||
user.Id = Guid.NewGuid();
|
||||
|
||||
_usersRepositoryMock
|
||||
.Setup(u => u.SearchPotentialFriendsAsync(userId, It.IsAny<string>()))
|
||||
.ReturnsAsync(new List<User> { user });
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(f => f.FindByUserIdAsync(userId))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(userId, "test"));
|
||||
|
||||
// Act
|
||||
var result = await _service.SearchUsersAsync("test", userId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Has.Count.EqualTo(1));
|
||||
Assert.That(result[0].Id, Is.EqualTo(user.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SearchUsersAsync_Throws_UnauthorizedAccessException_IfThrowsSomeExceptionInUsersRepository()
|
||||
{
|
||||
// Arrange
|
||||
_usersRepositoryMock
|
||||
.Setup(u => u.SearchPotentialFriendsAsync(It.IsAny<Guid>(), It.IsAny<string>()))
|
||||
.ThrowsAsync(new Exception("test"));
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<UnauthorizedAccessException>(async () => await _service.SearchUsersAsync("test", Guid.NewGuid()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SearchUsersAsync_Throws_UnauthorizedAccessException_IfThrowsSomeExceptionInFriendshipsRepository()
|
||||
{
|
||||
// Arrange
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(u => u.FindByUserIdAsync(It.IsAny<Guid>()))
|
||||
.ThrowsAsync(new Exception("test"));
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<UnauthorizedAccessException>(async () => await _service.SearchUsersAsync("test", Guid.NewGuid()));
|
||||
}
|
||||
|
||||
// SendFriendRequestAsync
|
||||
|
||||
[Test]
|
||||
public void SendFriendRequestAsync_SendingToSelf_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
_service.SendFriendRequestAsync(userId, userId));
|
||||
|
||||
Assert.That(ex.Message, Is.EqualTo("Cannot send a request to self user"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SendFriendRequestAsync_RequestAlreadyExists_ThrowsRequestAlreadySentException()
|
||||
{
|
||||
// Arrange
|
||||
var fromUserId = Guid.NewGuid();
|
||||
var toUserId = Guid.NewGuid();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(repo => repo.Exist(fromUserId, toUserId))
|
||||
.Returns(true);
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<RequestAlreadySentException>(() =>
|
||||
_service.SendFriendRequestAsync(fromUserId, toUserId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SendFriendRequestAsync_ValidRequest_CallsAddAsync()
|
||||
{
|
||||
// Arrange
|
||||
var fromUserId = Guid.NewGuid();
|
||||
var toUserId = Guid.NewGuid();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(repo => repo.Exist(fromUserId, toUserId))
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
await _service.SendFriendRequestAsync(fromUserId, toUserId);
|
||||
|
||||
// Assert
|
||||
_friendshipsRepositoryMock.Verify(repo =>
|
||||
repo.AddAsync(It.Is<Friendship>(f =>
|
||||
f.RequesterId == fromUserId &&
|
||||
f.AddresseeId == toUserId &&
|
||||
f.Status == FriendshipStatus.Pending &&
|
||||
f.Id != Guid.Empty
|
||||
)),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
// AcceptFriendRequestAsync
|
||||
[Test]
|
||||
public async Task AcceptFriendRequestAsync_ValidRequest_CallsUpdateAsync()
|
||||
{
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Pending)
|
||||
.Create();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(friendship.Id))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
// Act: вызываем именно Accept, не Send
|
||||
await _service.AcceptFriendRequestAsync(friendship.Id, friendship.AddresseeId);
|
||||
|
||||
// Assert
|
||||
_friendshipsRepositoryMock.Verify(r => r.UpdateAsync(It.Is<Friendship>(f =>
|
||||
f.Id == friendship.Id &&
|
||||
f.RequesterId == friendship.RequesterId &&
|
||||
f.AddresseeId == friendship.AddresseeId &&
|
||||
f.Status == FriendshipStatus.Accepted
|
||||
)), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AcceptFriendRequestAsync_Throws_InvalidOperationException_IfStatusNotEqualPending()
|
||||
{
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Accepted)
|
||||
.Create();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(friendship.Id))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await _service.AcceptFriendRequestAsync(friendship.Id, friendship.AddresseeId));
|
||||
}
|
||||
[Test]
|
||||
public void AcceptFriendRequestAsync_Throws_UnauthorizedAccessException_IfCurrentUserIdIsNotAddressee()
|
||||
{
|
||||
// Arrange
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Pending)
|
||||
.Create();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(friendship.Id))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
var wrongUserId = Guid.NewGuid(); // не совпадает с AddresseeId
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
|
||||
await _service.AcceptFriendRequestAsync(friendship.Id, wrongUserId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AcceptFriendRequestAsync_Throws_InvalidOperationException_IfFriendshipNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var requestId = Guid.NewGuid();
|
||||
var userId = Guid.NewGuid();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(requestId))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(requestId));
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
await _service.AcceptFriendRequestAsync(requestId, userId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AcceptFriendRequestAsync_ChangesStatusToAccepted()
|
||||
{
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Pending)
|
||||
.Create();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(friendship.Id))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
Friendship updatedFriendship = null;
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.UpdateAsync(It.IsAny<Friendship>()))
|
||||
.Callback<Friendship>(f => updatedFriendship = f)
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
await _service.AcceptFriendRequestAsync(friendship.Id, friendship.AddresseeId);
|
||||
|
||||
Assert.That(updatedFriendship.Status, Is.EqualTo(FriendshipStatus.Accepted));
|
||||
}
|
||||
|
||||
// GetFriendsAsync
|
||||
[Test]
|
||||
public async Task GetFriendsAsync_ReturnsUsers_IfUserExists()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var friendships = _fixture.CreateMany<Friendship>().ToList();
|
||||
|
||||
friendships.ForEach(f =>
|
||||
{
|
||||
f.RequesterId = userId;
|
||||
f.Status = FriendshipStatus.Accepted;
|
||||
});
|
||||
|
||||
_friendshipsRepositoryMock.Setup(f => f.FindByUserIdAsync(userId))
|
||||
.ReturnsAsync(friendships);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetFriendsAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Count, Is.EqualTo(friendships.Count));
|
||||
Assert.That(result, Is.EquivalentTo(friendships.Select(f => f.Addressee)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetFriendsAsync_Throws_InvalidOperationException_IfUserNotExists()
|
||||
{
|
||||
// Arrange
|
||||
_friendshipsRepositoryMock.Setup(f => f.FindByUserIdAsync(It.IsAny<Guid>()))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(Guid.NewGuid()));
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await _service.GetFriendsAsync(Guid.NewGuid()));
|
||||
}
|
||||
|
||||
// GetIncomingRequestsAsync
|
||||
|
||||
[Test]
|
||||
public async Task GetIncomingRequestsAsync_ReturnsFriendships_IfFriendshipsExists()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
|
||||
var friendships = _fixture.CreateMany<Friendship>().ToList();
|
||||
|
||||
var user = _fixture.Build<User>()
|
||||
.With(u => u.Id, userId)
|
||||
.With(u => u.ReceivedFriendRequests, friendships)
|
||||
.Create();
|
||||
|
||||
friendships.ForEach(f =>
|
||||
{
|
||||
f.AddresseeId = userId;
|
||||
f.Addressee = user;
|
||||
f.Status = FriendshipStatus.Pending;
|
||||
});
|
||||
|
||||
_usersRepositoryMock.Setup(f => f.FindByIdAsync(userId))
|
||||
.ReturnsAsync(user);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetIncomingRequestsAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Count, Is.EqualTo(friendships.Count));
|
||||
Assert.That(result.Select(u => u.Id), Is.EquivalentTo(friendships.Select(f => f.Id)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetIncomingRequestsAsync_ThrowsInvalidOperationException_WhenUserNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
|
||||
_usersRepositoryMock
|
||||
.Setup(r => r.FindByIdAsync(userId))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(userId));
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
await _service.GetIncomingRequestsAsync(userId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Govor.Application.Services.Authentication;
|
||||
|
||||
namespace Govor.API.Common.Extensions;
|
||||
|
||||
public static class AddOptionExtensions
|
||||
{
|
||||
public static IServiceCollection AddOptionsConfiguration(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.Configure<JwtAccessOption>(configuration.GetSection(nameof(JwtAccessOption)));
|
||||
services.Configure<JwtRefreshOption>(configuration.GetSection(nameof(JwtRefreshOption)));
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
using Govor.API.Common.Mapping;
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Govor.Application.Infrastructure.AdminsStuff;
|
||||
using Govor.Application.Infrastructure.Extensions;
|
||||
using Govor.Application.Infrastructure.Validators;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Authentication;
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Application.Interfaces.Medias;
|
||||
using Govor.Application.Interfaces.Messages;
|
||||
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||
using Govor.Application.Interfaces.UserSession;
|
||||
using Govor.Application.Interfaces.UserSession.Crypto;
|
||||
using Govor.Application.Services;
|
||||
using Govor.Application.Services.Authentication;
|
||||
using Govor.Application.Services.Friends;
|
||||
using Govor.Application.Services.Medias;
|
||||
using Govor.Application.Services.Messages;
|
||||
using Govor.Application.Services.UserOnlineStatus;
|
||||
using Govor.Application.Services.UserSessions;
|
||||
using Govor.Application.Services.UserSessions.Crypto;
|
||||
using Govor.Core.Infrastructure.Extensions;
|
||||
using Govor.Core.Infrastructure.Validators;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Messages;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Core.Repositories.Admins;
|
||||
using Govor.Core.Repositories.Friendships;
|
||||
using Govor.Core.Repositories.Groups;
|
||||
using Govor.Core.Repositories.Invaites;
|
||||
using Govor.Core.Repositories.MediasAttachments;
|
||||
using Govor.Core.Repositories.Messages;
|
||||
using Govor.Core.Repositories.PrivateChats;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Govor.Core.Repositories.UserSessionsRepository;
|
||||
using Govor.Data;
|
||||
using Govor.Data.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Govor.API.Common.Extensions;
|
||||
|
||||
public static class ConfigurationProgramExtensions
|
||||
{
|
||||
public static void AddServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IPasswordHasher, PasswordHasher>();
|
||||
services.AddScoped<IJwtTokenHasher, JwtTokenHasher>();
|
||||
services.AddScoped<IJwtService, JwtService>();
|
||||
services.AddScoped<IAccountService, AuthService>();
|
||||
services.AddScoped<IUsersAdministration, UsersService>();
|
||||
services.AddScoped<IInvitesService, InvitesService>();
|
||||
services.AddScoped<IInvitationGenerator, InvitationGenerator>();
|
||||
services.AddScoped<IUsernameValidator, UsernameValidator>();
|
||||
|
||||
// Friends services
|
||||
services.AddScoped<IFriendshipService, FriendshipService>();
|
||||
services.AddScoped<IFriendRequestCommandService, FriendRequestCommandService>();
|
||||
services.AddScoped<IFriendRequestQueryService, FriendRequestQueryService>();
|
||||
services.AddScoped<IFriendsBlockService, FriendsBlockService>();
|
||||
|
||||
services.AddScoped<IStorageService>(sp =>
|
||||
{
|
||||
var env = sp.GetRequiredService<IWebHostEnvironment>();
|
||||
return new LocalStorageService(env.ContentRootPath);
|
||||
});
|
||||
|
||||
services.AddHttpContextAccessor(); // it's very important for CurrentUserService
|
||||
services.AddScoped<ICurrentUserService, CurrentUserService>();
|
||||
services.AddScoped<ICurrentUserSessionService, CurrentUserSessionService>();
|
||||
|
||||
services.AddMemoryCache();
|
||||
services.AddScoped<IPingHandlerService, PingHandlerService>();
|
||||
|
||||
services.AddScoped<IMessageCommandService, MessageCommandService>();
|
||||
services.AddScoped<IVerifyFriendship, VerifyFriendship>();
|
||||
services.AddScoped<IUserGroupsService, UserGroupsService>();
|
||||
services.AddScoped<IMessagesLoader, MessagesLoader>();
|
||||
services.AddScoped<IMediaService, MediaService>();
|
||||
services.AddScoped<IAccesserToDownloadMedia, AccesserToDownloadMediaService>();
|
||||
|
||||
// UserSession
|
||||
services.AddScoped<IUserSessionOpener, UserSessionOpener>();
|
||||
services.AddScoped<IUserSessionRefresher, UserSessionRefresher>();
|
||||
|
||||
services.AddScoped<IUserNotificationScopeService, UserNotificationScopeService>();
|
||||
services.AddScoped<IUserPresenceReader, UserPresenceReader>();
|
||||
services.AddSingleton<IOnlineUserStore, OnlineUserStore>();
|
||||
|
||||
// Auto Mapper
|
||||
services.AddAutoMapper(typeof(MappingProfile));
|
||||
|
||||
services.AddScoped<IHubUserAccessor, HubUserAccessor>();
|
||||
|
||||
services.AddScoped<IUserSessionReader, UserSessionReader>();
|
||||
services.AddScoped<IUserSessionRevoker, UserSessionRevoker>();
|
||||
|
||||
services.AddScoped<ISessionKeyAttacher, SessionKeyAttacher>();
|
||||
services.AddScoped<ISessionKeysReader, SessionKeysReader>();
|
||||
services.AddScoped<IOneTimePreKeysRotator, OneTimePreKeysRotator>();
|
||||
|
||||
services.AddScoped<IProfileService, ProfileService>();
|
||||
}
|
||||
|
||||
public static void AddRepositories(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IUsersRepository, UsersRepository>();
|
||||
services.AddScoped<IMessagesRepository, MessagesRepository>();
|
||||
services.AddScoped<IInvitesRepository, InvitesRepository>();
|
||||
services.AddScoped<IAdminsRepository, AdminsRepository>();
|
||||
services.AddScoped<IMediaAttachmentsRepository, MediaAttachmentsRepository>();
|
||||
services.AddScoped<IFriendshipsRepository, FriendshipsRepository>();
|
||||
services.AddScoped<IPrivateChatsRepository, PrivateChatsRepository>();
|
||||
services.AddScoped<IGroupsRepository, GroupRepository>();
|
||||
services.AddScoped<IUserSessionsRepository, UserSessionsRepository>();
|
||||
// other
|
||||
}
|
||||
|
||||
public static void AddValidators(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IObjectValidator<User>, UserValidator>();
|
||||
services.AddScoped<IObjectValidator<Message>, MessageValidator>();
|
||||
services.AddScoped<IObjectValidator<MediaAttachments>, MediaAttachmentsValidator>();
|
||||
services.AddScoped<IObjectValidator<Admin>, AdminValidator>();
|
||||
services.AddScoped<IObjectValidator<Invitation>, InvitationValidator>();
|
||||
services.AddScoped<IObjectValidator<Friendship>, FriendshipValidator>();
|
||||
services.AddScoped<IObjectValidator<PrivateChat>, PrivateChatValidator>();
|
||||
services.AddScoped<IObjectValidator<ChatGroup>, ChatGroupValidator>();
|
||||
}
|
||||
|
||||
public static void AddGovorDbContext(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var useMySql = configuration.GetValue<bool>("UseMySql");
|
||||
|
||||
if (useMySql)
|
||||
{
|
||||
services.AddDbContext<GovorDbContext>(options =>
|
||||
{
|
||||
options
|
||||
.UseMySql(
|
||||
configuration.GetConnectionString(nameof(GovorDbContext)),
|
||||
new MySqlServerVersion(new Version(8, 0, 21))
|
||||
);
|
||||
options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddDbContext<GovorDbContext>(options =>
|
||||
{
|
||||
options.UseNpgsql(configuration.GetConnectionString(nameof(GovorDbContext)));
|
||||
options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Govor.API.Filters;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Govor.API.Common.Extensions;
|
||||
|
||||
public static class ConfigurationSignalR
|
||||
{
|
||||
public static void AddSignalRConf(this IServiceCollection services)
|
||||
{
|
||||
services.AddSignalR(options =>
|
||||
{
|
||||
options.AddFilter<HubExceptionFilter>();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Serilog;
|
||||
|
||||
namespace Govor.API.Common.Extensions;
|
||||
|
||||
public static class ConfiguratorLoggerExtensions
|
||||
{
|
||||
public static void AddLogger(this WebApplicationBuilder builder)
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Information()
|
||||
.WriteTo.Console()
|
||||
.WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day) // Лог в файл, ежедневно
|
||||
.CreateLogger();
|
||||
|
||||
builder.Host.UseSerilog();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using AutoMapper;
|
||||
using Govor.API.Extensions.Mapping;
|
||||
using Govor.Application.Profiles;
|
||||
using Govor.Contracts.DTOs;
|
||||
using Govor.Contracts.Responses;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Messages;
|
||||
using Govor.Core.Models.Users;
|
||||
|
||||
namespace Govor.API.Common.Mapping;
|
||||
|
||||
public class MappingProfile : Profile
|
||||
{
|
||||
public MappingProfile()
|
||||
{
|
||||
CreateMap<Message, MessageResponse>();
|
||||
CreateMap<MediaAttachments, MediaAttachmentResponse>();
|
||||
CreateMap<MessageReaction, MessageReactionResponse>();
|
||||
CreateMap<MessageView, MessageViewResponse>();
|
||||
|
||||
CreateMap<User, UserDto>()
|
||||
.AfterMap<UserToUserDtoMappingAction>();
|
||||
|
||||
CreateMap<Friendship, FriendshipDto>();
|
||||
|
||||
CreateMap<UserSession, SessionDto>();
|
||||
|
||||
CreateMap<UserProfile, UserProfileDto>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using AutoMapper;
|
||||
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||
using Govor.Contracts.DTOs;
|
||||
using Govor.Core.Models.Users;
|
||||
|
||||
namespace Govor.API.Extensions.Mapping;
|
||||
|
||||
public class UserToUserDtoMappingAction : IMappingAction<User, UserDto>
|
||||
{
|
||||
private readonly IOnlineUserStore _onlineUserStore;
|
||||
|
||||
public UserToUserDtoMappingAction(IOnlineUserStore onlineUserStore)
|
||||
{
|
||||
_onlineUserStore = onlineUserStore;
|
||||
}
|
||||
|
||||
public void Process(User source, UserDto destination, ResolutionContext context)
|
||||
{
|
||||
destination.IsOnline = _onlineUserStore.IsOnline(source.Id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Govor.API.Common.SignalR.Helpers;
|
||||
|
||||
public class HubUserAccessor : IHubUserAccessor
|
||||
{
|
||||
private readonly ILogger<HubUserAccessor> _logger;
|
||||
|
||||
public HubUserAccessor(ILogger<HubUserAccessor> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Guid GetUserId(HubCallerContext context, bool suppressException = false)
|
||||
{
|
||||
var userIdClaim = context.User?.FindFirst("userId")?.Value;
|
||||
if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
|
||||
{
|
||||
if (!suppressException)
|
||||
{
|
||||
_logger.LogError("Could not retrieve sender userId. Claim was: {UserIDClaim}", userIdClaim);
|
||||
throw new UnauthorizedAccessException("userID claim is missing or invalid.");
|
||||
}
|
||||
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Govor.API.Common.SignalR.Helpers;
|
||||
|
||||
public interface IHubUserAccessor
|
||||
{
|
||||
Guid GetUserId(HubCallerContext context, bool suppressException = false);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using Govor.Contracts.DTOs;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Core.Repositories.Friendships;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Govor.API.Controllers.AdminStuff;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/admin/[controller]")]
|
||||
[Authorize(Roles = "Admin")]
|
||||
public class FriendshipsController : Controller
|
||||
{
|
||||
private readonly ILogger<FriendshipsController> _logger;
|
||||
private readonly IFriendshipsRepository _friendshipsRepository;
|
||||
|
||||
public FriendshipsController(ILogger<FriendshipsController> logger, IFriendshipsRepository friendshipsRepository)
|
||||
{
|
||||
_logger = logger;
|
||||
_friendshipsRepository = friendshipsRepository;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Get()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Get all friendships by administrator");
|
||||
var result = await _friendshipsRepository.GetAllAsync();
|
||||
return Ok(result);
|
||||
}
|
||||
catch (NotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex.Message);
|
||||
return NotFound(ex.Message);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, e.Message);
|
||||
return StatusCode(500, new { error = "Internal server error." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{userId}")]
|
||||
public async Task<IActionResult> GetByUserId(Guid userId)
|
||||
{
|
||||
if(userId == Guid.Empty)
|
||||
return BadRequest("User id can't be empty.");
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation($"Get user's {userId} all friendships by administrator");
|
||||
var result = await _friendshipsRepository.FindByUserIdAsync(userId);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (NotFoundByKeyException<Guid> ex)
|
||||
{
|
||||
_logger.LogWarning(ex.Message);
|
||||
return NotFound(ex.Message);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, e.Message);
|
||||
return StatusCode(500, new { error = "Internal server error." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> RemoveFriendship(Guid Id)
|
||||
{
|
||||
if(Id == Guid.Empty)
|
||||
return BadRequest("FS id can't be empty.");
|
||||
try
|
||||
{
|
||||
var result = await _friendshipsRepository.GetByIdAsync(Id);
|
||||
await _friendshipsRepository.RemoveAsync(result);
|
||||
return Ok();
|
||||
}
|
||||
catch (NotFoundByKeyException<Guid> ex)
|
||||
{
|
||||
_logger.LogWarning(ex.Message);
|
||||
return NotFound(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, new { error = "Internal server error." });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Govor.API.Services.AdminsStuff.Interfaces;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Contracts.DTOs;
|
||||
using Govor.Contracts.Requests;
|
||||
using Govor.Core.Repositories.Invaites;
|
||||
@@ -7,9 +7,9 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Govor.API.Controllers.AdminStuff;
|
||||
|
||||
[Route("api/[controller]")]
|
||||
[Route("api/admin/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize(Roles = "Admin")]
|
||||
//[Authorize(Roles = "Admin")]
|
||||
public class InviteUserController : Controller
|
||||
{
|
||||
private readonly IInvitesRepository _repository;
|
||||
@@ -52,13 +52,13 @@ public class InviteUserController : Controller
|
||||
_logger.LogInformation("Getting all active invitations by administrator");
|
||||
|
||||
var read = await _repository.GetAllAsync();
|
||||
var result = read.Where(x => x.IsActive == true).ToList();
|
||||
var result = read.Where(x => x.IsActive).ToList();
|
||||
|
||||
List<InvitationDto> dtos = new List<InvitationDto>();
|
||||
List<InvitationResponses> dtos = new List<InvitationResponses>();
|
||||
|
||||
foreach (var inv in result)
|
||||
{
|
||||
dtos.Add(new InvitationDto(){
|
||||
dtos.Add(new InvitationResponses(){
|
||||
Id = inv.Id,
|
||||
Description = inv.Description,
|
||||
IsAdmin = inv.IsAdmin,
|
||||
@@ -67,6 +67,7 @@ public class InviteUserController : Controller
|
||||
CreatedAt = inv.DateCreated,
|
||||
EndAt = inv.EndDate,
|
||||
IsActive = inv.IsActive,
|
||||
ParticipantCount = inv.Users.Count,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,11 +88,11 @@ public class InviteUserController : Controller
|
||||
_logger.LogInformation("Getting all invitations by administrator");
|
||||
var read = await _repository.GetAllAsync();
|
||||
|
||||
List<InvitationDto> dtos = new List<InvitationDto>();
|
||||
List<InvitationResponses> dtos = new List<InvitationResponses>();
|
||||
|
||||
foreach (var inv in read)
|
||||
{
|
||||
dtos.Add(new InvitationDto(){
|
||||
dtos.Add(new InvitationResponses(){
|
||||
Id = inv.Id,
|
||||
Description = inv.Description,
|
||||
IsAdmin = inv.IsAdmin,
|
||||
@@ -100,6 +101,7 @@ public class InviteUserController : Controller
|
||||
CreatedAt = inv.DateCreated,
|
||||
EndAt = inv.EndDate,
|
||||
IsActive = inv.IsActive,
|
||||
ParticipantCount = inv.Users.Count,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -120,18 +122,18 @@ public class InviteUserController : Controller
|
||||
_logger.LogInformation("Getting invitations {id} by administrator");
|
||||
var read = await _repository.FindByIdAsync(id);
|
||||
|
||||
var response = new InvitationDto(){
|
||||
Id = read.Id,
|
||||
Description = read.Description,
|
||||
IsAdmin = read.IsAdmin,
|
||||
MaxParticipants = read.MaxParticipants,
|
||||
Code = read.Code,
|
||||
CreatedAt = read.DateCreated,
|
||||
EndAt = read.EndDate,
|
||||
IsActive = read.IsActive,
|
||||
var response = new InvitationResponses(){
|
||||
Id = read.Id,
|
||||
Description = read.Description,
|
||||
IsAdmin = read.IsAdmin,
|
||||
MaxParticipants = read.MaxParticipants,
|
||||
Code = read.Code,
|
||||
CreatedAt = read.DateCreated,
|
||||
EndAt = read.EndDate,
|
||||
IsActive = read.IsActive,
|
||||
ParticipantCount = read.Users.Count,
|
||||
};
|
||||
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using Govor.API.Services.AdminsStuff.Interfaces;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Contracts.Responses.Admins;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@@ -22,15 +25,54 @@ public class UsersController : Controller
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> AllUsers()
|
||||
{
|
||||
_logger.LogInformation("Getting all users by administrator");
|
||||
var read = await _users.GetAllUsersAsync();
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Getting all users by administrator");
|
||||
var read = await _users.GetAllUsersAsync();
|
||||
|
||||
return Ok(BuildUserDtos(read));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, e.Message);
|
||||
return StatusCode(500, e.Message);
|
||||
}
|
||||
|
||||
return Ok(read);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<IActionResult> GetUserById(Guid id)
|
||||
{
|
||||
return Ok(id);
|
||||
try
|
||||
{
|
||||
_logger.LogInformation($"Getting user {id} by administrator");
|
||||
var read = await _users.GetUserById(id);
|
||||
return Ok(BuildUserDtos([read]).First());
|
||||
}
|
||||
catch (NotFoundByKeyException<Guid> ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return NotFound(ex.Message);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, e.Message);
|
||||
return StatusCode(500, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private List<UserResponse> BuildUserDtos(IEnumerable<User> users) => users.Select(user => new UserResponse
|
||||
{
|
||||
Id = user.Id,
|
||||
Username = user.Username,
|
||||
Description = user.Description,
|
||||
WasOnline = user.WasOnline,
|
||||
IconId = user.IconId,
|
||||
PasswordHash = user.PasswordHash,
|
||||
InviteId = user.InviteId,
|
||||
CreatedOn = user.CreatedOn,
|
||||
IsAdmin = user.Invite?.IsAdmin ?? false,
|
||||
}).ToList();
|
||||
|
||||
}
|
||||
+35
-20
@@ -1,44 +1,56 @@
|
||||
using Govor.API.Services.Authentication.Interfaces;
|
||||
using Govor.Application.Exceptions.AuthService;
|
||||
using Govor.Application.Exceptions.InvitesService;
|
||||
using Govor.Application.Interfaces.Authentication;
|
||||
using Govor.Application.Interfaces.UserSession;
|
||||
using Govor.Contracts.Requests;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Govor.API.Controllers;
|
||||
namespace Govor.API.Controllers.Authentication;
|
||||
|
||||
[ApiController]
|
||||
[AllowAnonymous]
|
||||
[Route("api/[controller]")]
|
||||
public class AuthController : Controller
|
||||
{
|
||||
private IUserSessionOpener _userSession;
|
||||
private IInvitesService _invitesService;
|
||||
private IAccountService _accountService;
|
||||
private ILogger<AuthController> _logger;
|
||||
|
||||
public AuthController(IAccountService accountService, IInvitesService invitesService, ILogger<AuthController> logger)
|
||||
public AuthController(
|
||||
IAccountService accountService,
|
||||
IInvitesService invitesService,
|
||||
IUserSessionOpener userSessionOpener,
|
||||
ILogger<AuthController> logger)
|
||||
{
|
||||
_userSession = userSessionOpener;
|
||||
_accountService = accountService;
|
||||
_invitesService = invitesService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
//[RequireHttps]
|
||||
[HttpPost("register")]// api/auth/register
|
||||
[RequireHttps]
|
||||
public async Task<IActionResult> Register([FromBody] RegistrationRequest registrationRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var invite = _invitesService.Validate(registrationRequest.InviteLink);
|
||||
var invite = await _invitesService.ValidateAsync(registrationRequest.InviteLink);
|
||||
|
||||
var token = await _accountService.RegistrationAsync(registrationRequest.Name, registrationRequest.Password,
|
||||
var user = await _accountService.RegistrationAsync(registrationRequest.Name, registrationRequest.Password,
|
||||
invite);
|
||||
_logger.LogInformation($"Register request for {registrationRequest.Name}");
|
||||
return Ok(new { token });
|
||||
|
||||
_logger.LogInformation($"Register request for {user.Username} with id {user.Id} processed successfully");
|
||||
|
||||
var tokens = await _userSession.OpenSessionAsync(user, registrationRequest.DeviceInfo);
|
||||
|
||||
_logger.LogInformation($"Session for user {user.Username} with id {user.Id} has been opened");
|
||||
|
||||
return Ok(tokens);
|
||||
}
|
||||
catch (UserAlreadyExistException ex)
|
||||
{
|
||||
@@ -62,34 +74,37 @@ public class AuthController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
//[RequireHttps]
|
||||
[HttpPost("login")]// api/auth/login
|
||||
[RequireHttps]
|
||||
public async Task<IActionResult> Login([FromBody] LoginRequest userRequest)
|
||||
public async Task<IActionResult> Login([FromBody] LoginRequest loginRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var token = await _accountService.LoginAsync(userRequest.Name, userRequest.Password);
|
||||
_logger.LogInformation($"Login request for {userRequest.Name}");
|
||||
return Ok(new { token });
|
||||
var user = await _accountService.LoginAsync(loginRequest.Name, loginRequest.Password);
|
||||
_logger.LogInformation($"Login request for {user.Username} with id {user.Id} processed successfully");
|
||||
|
||||
var tokens = await _userSession.OpenSessionAsync(user, loginRequest.DeviceInfo);
|
||||
|
||||
_logger.LogInformation($"Session for user {user.Username} with id {user.Id} has been opened");
|
||||
|
||||
return Ok(tokens);
|
||||
}
|
||||
catch (UserNotRegisteredException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Login failed for user {Name}", userRequest.Name);
|
||||
_logger.LogWarning(ex, "Login failed for user {Name}", loginRequest.Name);
|
||||
return BadRequest("Login failed: user does not exist.");
|
||||
}
|
||||
catch (LoginUserException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Login failed for user {Name}", userRequest.Name);
|
||||
_logger.LogWarning(ex, "Login failed for user {Name}", loginRequest.Name);
|
||||
return BadRequest("Login failed: username or password is incorrect.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error during login for user {Name}", userRequest.Name);
|
||||
_logger.LogError(ex, "Unexpected error during login for user {Name}", loginRequest.Name);
|
||||
return StatusCode(500, "An unexpected error occurred. Please try again later.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Govor.Application.Interfaces.UserSession;
|
||||
using Govor.Contracts.Requests;
|
||||
using Govor.Contracts.Responses;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Govor.API.Controllers.Authentication;
|
||||
|
||||
[ApiController]
|
||||
[AllowAnonymous]
|
||||
[Route("api/auth/token")]
|
||||
public class RefreshController : Controller
|
||||
{
|
||||
private readonly ILogger<RefreshController> _logger;
|
||||
private readonly IUserSessionRefresher _userSession;
|
||||
|
||||
public RefreshController(
|
||||
ILogger<RefreshController> logger,
|
||||
IUserSessionRefresher userSession)
|
||||
{
|
||||
_logger = logger;
|
||||
_userSession = userSession;
|
||||
}
|
||||
|
||||
//[RequireHttps]
|
||||
[HttpPost("refresh")] // api/auth/token/refresh
|
||||
public async Task<IActionResult> Refresh([FromBody] RefreshTokenRequest refreshRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
if (string.IsNullOrEmpty(refreshRequest.RefreshToken))
|
||||
throw new InvalidOperationException("Refresh token cant be empty.");
|
||||
|
||||
var result = await _userSession.RefreshTokenAsync(refreshRequest.RefreshToken);
|
||||
|
||||
return Ok(new RefreshTokenResponse()
|
||||
{
|
||||
AccessToken = result.accessToken,
|
||||
RefreshToken = result.refreshToken
|
||||
});
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Invalid refresh token.");
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Refresh token failed.");
|
||||
return Unauthorized("Invalid refresh token");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, "An unexpected error occurred.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Application.Interfaces.UserSession;
|
||||
using Govor.Application.Interfaces.UserSession.Crypto;
|
||||
using Govor.Contracts.Requests;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Govor.API.Controllers.Authentication;
|
||||
|
||||
[RequireHttps]
|
||||
[ApiController]
|
||||
[Route("api/session")]
|
||||
[Authorize(Roles = "Admin, User")]
|
||||
public class SessionKeysController : Controller
|
||||
{
|
||||
private readonly ILogger<SessionKeysController> _logger;
|
||||
private readonly IFriendshipService _friendshipService;
|
||||
private readonly ICurrentUserSessionService _currentSession;
|
||||
private readonly ISessionKeyAttacher _sessionKeyAttacher;
|
||||
private readonly ISessionKeysReader _sessionKeysReader;
|
||||
private readonly IOneTimePreKeysRotator _oneTimePreKeysRotator;
|
||||
private readonly ICurrentUserService _currentUser;
|
||||
|
||||
public SessionKeysController(
|
||||
ILogger<SessionKeysController> logger,
|
||||
IFriendshipService friendshipService,
|
||||
ICurrentUserSessionService currentSession,
|
||||
ISessionKeyAttacher sessionKeyAttacher,
|
||||
ISessionKeysReader sessionKeysReader,
|
||||
ICurrentUserService currentUser)
|
||||
{
|
||||
_logger = logger;
|
||||
_friendshipService = friendshipService;
|
||||
_currentSession = currentSession;
|
||||
_sessionKeyAttacher = sessionKeyAttacher;
|
||||
_sessionKeysReader = sessionKeysReader;
|
||||
_currentUser = currentUser;
|
||||
}
|
||||
|
||||
[HttpPost("keys")]
|
||||
public async Task<IActionResult> UploadSessionKeys([FromBody] UploadKeysRequest request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
if(request.OneTimePreKeys.Count > 100)
|
||||
return BadRequest("Too many one time pre keys");
|
||||
|
||||
var sessionId = _currentSession.GetUserSessionId();
|
||||
|
||||
await _sessionKeyAttacher.AttachKeysAsync(sessionId,
|
||||
request.IdentityKey,
|
||||
request.SignedPreKey,
|
||||
request.SignedPreKeySignature,
|
||||
request.OneTimePreKeys);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet("users/{userId}/keys")]
|
||||
public async Task<IActionResult> GetUserPublicKeys(Guid userId)
|
||||
{
|
||||
var requesterId = _currentUser.GetCurrentUserId();
|
||||
|
||||
if (!(await _friendshipService.GetFriendsAsync(userId)).Select(u => u.Id).Contains(requesterId))
|
||||
return Forbid("You can only access keys of your friends");
|
||||
|
||||
var keys = await _sessionKeysReader.GetAllActiveKeysAsync(userId);
|
||||
|
||||
return Ok(keys);
|
||||
}
|
||||
|
||||
[HttpPost("keys/rotate")]
|
||||
public async Task<IActionResult> RotateOneTimePreKeys([FromBody] RotateOneTimePreKeysRequest request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
if (request.NewOneTimePreKeys.Count > 100)
|
||||
return BadRequest("Too many new one time pre keys");
|
||||
|
||||
var sessionId = _currentSession.GetUserSessionId();
|
||||
|
||||
await _oneTimePreKeysRotator.RotateOneTimePreKeysAsync(sessionId, request.NewOneTimePreKeys);
|
||||
|
||||
return Ok("One-Time PreKeys rotated successfully.");
|
||||
}
|
||||
|
||||
[HttpGet("keys/remaining")]
|
||||
public async Task<IActionResult> GetRemainingOneTimePreKeysCount()
|
||||
{
|
||||
var sessionId = _currentSession.GetUserSessionId();
|
||||
|
||||
var count = await _sessionKeysReader.GetRemainingOneTimePreKeysCountAsync(sessionId);
|
||||
|
||||
return Ok(new { remaining = count });
|
||||
}
|
||||
|
||||
[HttpPost("keys/{preKeyId}/used")]
|
||||
public async Task<IActionResult> MarkOneTimePreKeyAsUsed([FromRoute] Guid preKeyId)
|
||||
{
|
||||
var sessionId = _currentSession.GetUserSessionId();
|
||||
|
||||
await _oneTimePreKeysRotator.MarkOneTimePreKeyAsUsedAsync(sessionId, preKeyId);
|
||||
|
||||
return Ok("Marked as used.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using AutoMapper;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Contracts.Requests;
|
||||
using Govor.Contracts.Responses;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Govor.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Authorize(Roles = "Admin, User")]
|
||||
[Route("api/chats")]
|
||||
public class ChatLoadController : Controller
|
||||
{
|
||||
private readonly ICurrentUserService _currentUser;
|
||||
private readonly ILogger<ChatLoadController> _logger;
|
||||
private readonly IMessagesLoader _messagesLoader;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public ChatLoadController(
|
||||
ILogger<ChatLoadController> logger,
|
||||
IMessagesLoader messagesLoader,
|
||||
ICurrentUserService currentUser,
|
||||
IMapper mapper)
|
||||
{
|
||||
_logger = logger;
|
||||
_messagesLoader = messagesLoader;
|
||||
_currentUser = currentUser;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpGet("group-messages")]
|
||||
public async Task<IActionResult> GetGroupMessages(
|
||||
Guid chatId,
|
||||
[FromQuery] MessageQuery query)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (query.Before < 0 || query.After < 0 || query.After + query.Before > 100)
|
||||
return BadRequest("Values must be non-negative and total must not exceed 100.");
|
||||
|
||||
var result = await _messagesLoader.LoadMessagesInChatGroup(
|
||||
chatId,
|
||||
_currentUser.GetCurrentUserId(),
|
||||
query.StartMessageId,
|
||||
query.Before,
|
||||
query.After);
|
||||
|
||||
var response = _mapper.Map<List<MessageResponse>>(result);
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex.Message);
|
||||
return Forbid(ex.Message);
|
||||
}
|
||||
catch (NotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, "Unexpected Error! Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("user-messages")]
|
||||
public async Task<IActionResult> GetUserMessages(
|
||||
Guid userId,
|
||||
[FromQuery] MessageQuery query)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (query.Before < 0 || query.After < 0 || query.After + query.Before > 100)
|
||||
return BadRequest("Values must be non-negative and total must not exceed 100.");
|
||||
|
||||
var result = await _messagesLoader.LoadMessagesInUserChat(
|
||||
userId,
|
||||
_currentUser.GetCurrentUserId(),
|
||||
query.StartMessageId,
|
||||
query.Before,
|
||||
query.After);
|
||||
|
||||
var response = _mapper.Map<List<MessageResponse>>(result);
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex.Message);
|
||||
return Forbid(ex.Message);
|
||||
}
|
||||
catch (NotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, "Unexpected Error! Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using AutoMapper;
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Contracts.DTOs;
|
||||
using Govor.Core.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Govor.API.Controllers.Friends;
|
||||
|
||||
|
||||
[Authorize]
|
||||
[Route("api/friends")]
|
||||
[ApiController]
|
||||
public class FriendsRequestQueryController : Controller
|
||||
{
|
||||
private readonly ILogger<FriendsRequestQueryController> _logger;
|
||||
private readonly IFriendRequestQueryService _friendsService;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public FriendsRequestQueryController(
|
||||
ILogger<FriendsRequestQueryController> logger,
|
||||
IFriendRequestQueryService friendsService,
|
||||
ICurrentUserService currentUserService,
|
||||
IMapper mapper)
|
||||
{
|
||||
_logger = logger;
|
||||
_mapper = mapper;
|
||||
_friendsService = friendsService;
|
||||
_currentUserService = currentUserService;
|
||||
}
|
||||
|
||||
[HttpGet("requests")] // api/friends/requests
|
||||
public async Task<IActionResult> GetIncomingRequests()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _friendsService.GetIncomingAsync(_currentUserService.GetCurrentUserId());
|
||||
var response = _mapper.Map<List<FriendshipDto>>(result);
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return Ok(new List<FriendshipDto>());
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return Forbid(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, new { error = "Internal server error." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("responses")]// api/friends/responses
|
||||
public async Task<IActionResult> GetResponses()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = _currentUserService.GetCurrentUserId();
|
||||
|
||||
_logger.LogInformation("Getting responses by user {userId}", userId);
|
||||
|
||||
var result = await _friendsService.GetResponsesAsync(userId);
|
||||
var response = _mapper.Map<List<FriendshipDto>>(result);
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return Ok(new List<FriendshipDto>());
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return Forbid(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, new { error = "Internal server error." });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using AutoMapper;
|
||||
using Govor.Application.Exceptions.FriendsService;
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Contracts.DTOs;
|
||||
using Govor.Core.Models.Users;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Govor.API.Controllers.Friends;
|
||||
|
||||
[Route("api/friends")]
|
||||
public class FriendshipController : Controller
|
||||
{
|
||||
private readonly ILogger<FriendshipController> _logger;
|
||||
private readonly IFriendshipService _friendsService;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public FriendshipController(
|
||||
ILogger<FriendshipController> logger,
|
||||
IFriendshipService friendsService,
|
||||
ICurrentUserService currentUserService,
|
||||
IMapper mapper)
|
||||
{
|
||||
_logger = logger;
|
||||
_mapper = mapper;
|
||||
_friendsService = friendsService;
|
||||
_currentUserService = currentUserService;
|
||||
}
|
||||
|
||||
[HttpGet("search")] // api/friends/search?query=
|
||||
public async Task<IActionResult> Search(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return BadRequest("Query cannot be empty");
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _friendsService.SearchUsersAsync(query, _currentUserService.GetCurrentUserId());
|
||||
|
||||
var response = _mapper.Map<List<UserDto>>(result);
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return Forbid(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, new { error = "Internal error during user search." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet] // api/friends
|
||||
public async Task<IActionResult> GetFriends()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _friendsService.GetFriendsAsync(_currentUserService.GetCurrentUserId());
|
||||
|
||||
var response = _mapper.Map<List<UserDto>>(result);
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return Ok(Array.Empty<UserDto>());
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return Forbid(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, new { error = "Internal server error." });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
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;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Govor.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize(Roles = "User,Admin")]
|
||||
public class FriendsController : Controller
|
||||
{
|
||||
private readonly ILogger<FriendsController> _logger;
|
||||
private readonly IFriendsService _friendsService;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
|
||||
public FriendsController(ILogger<FriendsController> logger,
|
||||
IFriendsService friendsService,
|
||||
ICurrentUserService currentUserService)
|
||||
{
|
||||
_logger = logger;
|
||||
_friendsService = friendsService;
|
||||
_currentUserService = currentUserService;
|
||||
}
|
||||
|
||||
[HttpGet("search")]
|
||||
public async Task<IActionResult> Search(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return BadRequest("Query cannot be empty");
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _friendsService.SearchUsersAsync(query, _currentUserService.GetCurrentUserId());
|
||||
return Ok(BuildUserDtos(result));
|
||||
}
|
||||
catch (SearchUsersException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return NotFound(new { error = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, new { error = "Internal error during user search." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("request")]
|
||||
public async Task<IActionResult> SendRequest(Guid targetUserId)
|
||||
{
|
||||
if (targetUserId == Guid.Empty)
|
||||
return BadRequest("Target user ID is invalid");
|
||||
|
||||
try
|
||||
{
|
||||
await _friendsService.SendFriendRequestAsync(targetUserId, _currentUserService.GetCurrentUserId());
|
||||
return Ok(new { message = "Friend request sent successfully." });
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Tried to send request to self");
|
||||
return UnprocessableEntity(new { error = ex.Message });
|
||||
}
|
||||
catch (RequestAlreadySentException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return Conflict(new { error = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, new { error = "Failed to send friend request." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("requests")]
|
||||
public async Task<IActionResult> GetIncomingRequests()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _friendsService.GetIncomingRequestsAsync(_currentUserService.GetCurrentUserId());
|
||||
return Ok(BuildFriendshipDtos(result));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return BadRequest(new { error = "Failed to get friend requests. User data missing." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, new { error = "Internal server error." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("accept")]
|
||||
public async Task<IActionResult> AcceptFriend(Guid requesterId)
|
||||
{
|
||||
if (requesterId == Guid.Empty)
|
||||
return BadRequest("Requester ID is invalid");
|
||||
|
||||
try
|
||||
{
|
||||
await _friendsService.AcceptFriendRequestAsync(requesterId, _currentUserService.GetCurrentUserId());
|
||||
return Ok(new { message = "Friend request accepted." });
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return NotFound(new { error = ex.Message });
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return Forbid();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, new { error = "Failed to accept friend request." });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetFriends()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _friendsService.GetFriendsAsync(_currentUserService.GetCurrentUserId());
|
||||
return Ok(BuildUserDtos(result));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return BadRequest(new { error = "User data not found." });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, new { error = "Internal server error." });
|
||||
}
|
||||
}
|
||||
|
||||
private List<UserDto> BuildUserDtos(IEnumerable<User> users) => users.Select(user => new UserDto
|
||||
{
|
||||
Id = user.Id,
|
||||
Username = user.Username,
|
||||
Description = user.Description,
|
||||
WasOnline = user.WasOnline,
|
||||
IconId = user.IconId
|
||||
}).ToList();
|
||||
|
||||
private List<FriendshipDto> BuildFriendshipDtos(IEnumerable<Friendship> friendships) => friendships.Select(f => new FriendshipDto
|
||||
{
|
||||
Id = f.Id,
|
||||
Status = f.Status,
|
||||
AddresseeId = f.AddresseeId,
|
||||
RequesterId = f.RequesterId
|
||||
}).ToList();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Govor.API.Services;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@@ -9,24 +10,36 @@ namespace Govor.API.Controllers;
|
||||
public class InviteController : ControllerBase
|
||||
{
|
||||
private readonly IGroupService _groupService;
|
||||
|
||||
public InviteController(IGroupService groupService)
|
||||
private readonly ILogger<InviteController> _logger;
|
||||
private readonly ICurrentUserService _currentUser;
|
||||
public InviteController(IGroupService groupService,
|
||||
ILogger<InviteController> logger)
|
||||
{
|
||||
_groupService = groupService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet("{code}")]
|
||||
[Authorize]
|
||||
[HttpGet("{code}")]
|
||||
public IActionResult JoinGroup(string code)
|
||||
{
|
||||
var group = _groupService.GetGroupByInvite(code);
|
||||
if (group == null) return NotFound();
|
||||
|
||||
return Ok(new
|
||||
try
|
||||
{
|
||||
groupId = group.Id,
|
||||
name = group.Name,
|
||||
isChannel = group.IsChannel
|
||||
});
|
||||
_groupService.AddUserToGroupByInvitationAsync(_currentUser.GetCurrentUserId(), code);
|
||||
|
||||
var group = _groupService.GetGroupByInviteCode(code);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
groupId = group.Id,
|
||||
name = group.Name,
|
||||
isChannel = group.IsChannel
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Application.Interfaces.Medias;
|
||||
using Govor.Contracts.Requests;
|
||||
using Govor.Core.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Govor.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/media")]
|
||||
[Authorize(Roles = "User,Admin")]
|
||||
public class MediaController : Controller
|
||||
{
|
||||
private readonly ILogger<MediaController> _logger;
|
||||
private readonly IMediaService _mediaService;
|
||||
private readonly IAccesserToDownloadMedia _accesser;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
|
||||
public MediaController(
|
||||
ILogger<MediaController> logger,
|
||||
IMediaService mediaService,
|
||||
IAccesserToDownloadMedia accesser,
|
||||
ICurrentUserService currentUserService)
|
||||
{
|
||||
_logger = logger;
|
||||
_accesser = accesser;
|
||||
_mediaService = mediaService;
|
||||
_currentUserService = currentUserService;
|
||||
}
|
||||
|
||||
[HttpPost("upload")]
|
||||
[RequestSizeLimit(20_000_000)] // ~20MB
|
||||
public async Task<IActionResult> Upload([FromForm] MediaUploadRequest request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(ModelState);
|
||||
|
||||
if (request?.FromFile is null || request.FromFile.Length == 0)
|
||||
return BadRequest("No file uploaded");
|
||||
|
||||
if (request.FromFile.Length > 20_000_000)
|
||||
return BadRequest("File is too large");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.MimeType))
|
||||
return BadRequest("Missing MIME type");
|
||||
|
||||
try
|
||||
{
|
||||
byte[] fileBytes = await ReadFileAsync(request.FromFile);
|
||||
|
||||
var media = new Media(
|
||||
_currentUserService.GetCurrentUserId(),
|
||||
DateTime.UtcNow,
|
||||
Path.GetFileName(request.FromFile.FileName),
|
||||
fileBytes,
|
||||
request.Type,
|
||||
request.MimeType,
|
||||
request.EncryptedKey,
|
||||
request.OwnerType,
|
||||
null)
|
||||
{
|
||||
OwnerType = request.OwnerType,
|
||||
OwnerId = request.OwnerType == MediaOwnerType.Avatar
|
||||
? _currentUserService.GetCurrentUserId()
|
||||
: null,
|
||||
};
|
||||
|
||||
var result = await _mediaService.UploadMediaAsync(media);
|
||||
|
||||
_logger.LogInformation("Uploaded file {FileName} from user {UserId}",
|
||||
media.FileName, media.UploaderId);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return Forbid(ex.Message);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error uploading media");
|
||||
return StatusCode(500, "Internal server error");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<byte[]> ReadFileAsync(IFormFile file)
|
||||
{
|
||||
await using var ms = new MemoryStream();
|
||||
await file.CopyToAsync(ms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("download/{id}")]
|
||||
public async Task<IActionResult> Download(Guid id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = _currentUserService.GetCurrentUserId();
|
||||
|
||||
if (!await _accesser.HasAccessAsync(id, userId))
|
||||
return Forbid();
|
||||
|
||||
var media = await _mediaService.GetMediaByIdAsync(id);
|
||||
|
||||
return File(media.Data, media.MimeType, Path.GetFileName(media.FileName));
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return NotFound("Media not found");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error downloading media");
|
||||
return StatusCode(500, "Internal server error");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Govor.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/online")]
|
||||
[Authorize(Roles = "User,Admin")]
|
||||
public class OnlinePingingController : Controller
|
||||
{
|
||||
private readonly ILogger<OnlinePingingController> _logger;
|
||||
private readonly IPingHandlerService _ping;
|
||||
private readonly IUserPresenceReader _presenceReader;
|
||||
private readonly IOnlineUserStore _userOnlineStore;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
|
||||
public OnlinePingingController(ILogger<OnlinePingingController> logger,
|
||||
IPingHandlerService ping,
|
||||
ICurrentUserService currentUserService)
|
||||
{
|
||||
_logger = logger;
|
||||
_ping = ping;
|
||||
_currentUserService = currentUserService;
|
||||
}
|
||||
|
||||
|
||||
[HttpPatch("ping")]// api/online/ping
|
||||
public async Task<IActionResult> Ping()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Ping...");
|
||||
|
||||
var id = _currentUserService.GetCurrentUserId();
|
||||
await _ping.Ping(id);
|
||||
|
||||
_logger.LogInformation($"Ping from user {id} processed successfully");
|
||||
return Ok();
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
_logger.LogError(e, e.Message);
|
||||
return BadRequest("User can't be found in our database.");
|
||||
}
|
||||
catch (UnauthorizedAccessException e)
|
||||
{
|
||||
_logger.LogError(e, e.Message);
|
||||
return Forbid(e.Message);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, e.Message);
|
||||
return StatusCode(500, "Failed to ping.");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("status/{userId}")]
|
||||
public async Task<IActionResult> GetStatus(Guid userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var isOnline = _userOnlineStore.IsOnline(userId);
|
||||
var lastSeen = await _presenceReader.GetLastSeenAsync(userId);
|
||||
|
||||
return Ok(new {
|
||||
isOnline,
|
||||
lastSeen
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, e.Message);
|
||||
return StatusCode(500, "Internal server error.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using AutoMapper;
|
||||
using Govor.API.Hubs;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Application.Interfaces.Medias;
|
||||
using Govor.Contracts.DTOs;
|
||||
using Govor.Contracts.Requests;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Govor.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/profile")]
|
||||
[Authorize(Roles = "Admin,User")]
|
||||
public class ProfileController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<ProfileController> _logger;
|
||||
private readonly IMediaService _mediaService;
|
||||
private readonly IProfileService _profileService;
|
||||
private readonly ICurrentUserService _currentUserService;
|
||||
private readonly IHubContext<ProfileHub> _profileHubContext;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public ProfileController(
|
||||
ILogger<ProfileController> logger,
|
||||
IMediaService mediaService,
|
||||
IProfileService profileService,
|
||||
ICurrentUserService currentUserService,
|
||||
IHubContext<ProfileHub> profileHubContext,
|
||||
IMapper mapper)
|
||||
{
|
||||
_logger = logger;
|
||||
_mediaService = mediaService;
|
||||
_profileService = profileService;
|
||||
_profileHubContext = profileHubContext;
|
||||
_currentUserService = currentUserService;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
[HttpPost("avatar")] // api/profile/avatar
|
||||
public async Task<IActionResult> UploadAvatar([FromForm] AvatarUploadRequest request)
|
||||
{
|
||||
|
||||
var userId = _currentUserService.GetCurrentUserId();
|
||||
|
||||
if (request.FromFile == null || request.FromFile.Length == 0)
|
||||
{
|
||||
return BadRequest("File is empty.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
byte[] fileBytes = await ReadFileAsync(request.FromFile);
|
||||
|
||||
var media = new Media(
|
||||
_currentUserService.GetCurrentUserId(),
|
||||
DateTime.UtcNow,
|
||||
Path.GetFileName(request.FromFile.FileName),
|
||||
fileBytes,
|
||||
request.Type,
|
||||
request.MimeType,
|
||||
String.Empty,
|
||||
MediaOwnerType.Avatar,
|
||||
userId);
|
||||
|
||||
var mediaInfo = await _mediaService.UploadMediaAsync(media);
|
||||
|
||||
await _profileService.SetNewIcon(userId, mediaInfo.MediaId);
|
||||
var iconId = mediaInfo.MediaId;
|
||||
|
||||
var payload = new { userId, iconId };
|
||||
|
||||
await _profileHubContext.Clients.All.SendAsync(
|
||||
"AvatarUpdated",
|
||||
payload
|
||||
);
|
||||
|
||||
|
||||
return Ok(mediaInfo);
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
return StatusCode(500, new { Message = "Avatar processing error.", Details = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<byte[]> ReadFileAsync(IFormFile file)
|
||||
{
|
||||
await using var ms = new MemoryStream();
|
||||
await file.CopyToAsync(ms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
[HttpGet("download/me")]
|
||||
public async Task<IActionResult> DownloadProfile()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = _currentUserService.GetCurrentUserId();
|
||||
var user = await _profileService.GetUserProfileAsync(userId);
|
||||
|
||||
var dto = _mapper.Map<UserProfileDto>(user);
|
||||
return Ok(dto);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex.Message);
|
||||
return Forbid(ex.Message);
|
||||
}
|
||||
catch (NotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return NotFound("Profile not found");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, "Unexpected Error! Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("download/{id}")]
|
||||
public async Task<IActionResult> DowloadProfileByUserId(Guid id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await _profileService.GetUserProfileAsync(id);
|
||||
|
||||
var dto = _mapper.Map<UserProfileDto>(user);
|
||||
return Ok(dto);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex.Message);
|
||||
return Forbid(ex.Message);
|
||||
}
|
||||
catch (NotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return NotFound("Profile not found");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return StatusCode(500, "Unexpected Error! Please try again later.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
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 ICurrentUserSessionService _currentUserSessionService;
|
||||
private readonly IUserSessionReader _userSessionReader;
|
||||
private readonly IUserSessionRevoker _userSessionRevoker;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public SessionController(
|
||||
ILogger<SessionController> logger,
|
||||
ICurrentUserService currentUserService,
|
||||
ICurrentUserSessionService currentUserSessionService,
|
||||
IUserSessionReader userSessionReader,
|
||||
IUserSessionRevoker userSessionRevoker,
|
||||
IMapper mapper)
|
||||
{
|
||||
_logger = logger;
|
||||
_currentUserService = currentUserService;
|
||||
_currentUserSessionService = currentUserSessionService;
|
||||
_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")]
|
||||
public async Task<IActionResult> CloseCurrent()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _userSessionRevoker.CloseSessionByIdAsync(
|
||||
_currentUserSessionService.GetUserSessionId(),
|
||||
_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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
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;
|
||||
using Govor.Core.Infrastructure.Validators;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Repositories.Admins;
|
||||
using Govor.Core.Repositories.Friendships;
|
||||
using Govor.Core.Repositories.Invaites;
|
||||
using Govor.Core.Repositories.MediasAttachments;
|
||||
using Govor.Core.Repositories.Messages;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Govor.Data;
|
||||
using Govor.Data.Repositories;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Govor.API.Extensions;
|
||||
|
||||
public static class ConfigurationProgramExtensions
|
||||
{
|
||||
public static void AddServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IPasswordHasher, PasswordHasher>();
|
||||
services.AddScoped<IJwtService, JwtService>();
|
||||
services.AddScoped<IAccountService, AuthService>();
|
||||
services.AddScoped<IUsersAdministration, UsersService>();
|
||||
services.AddScoped<IInvitesService, InvitesService>();
|
||||
services.AddScoped<IInvitationGenerator, InvitationGenerator>();
|
||||
services.AddScoped<IUsernameValidator, UsernameValidator>();
|
||||
services.AddScoped<IFriendsService, FriendsService>();
|
||||
|
||||
services.AddScoped<IStorageService>(sp =>
|
||||
{
|
||||
var env = sp.GetRequiredService<IWebHostEnvironment>();
|
||||
return new LocalStorageService(env.ContentRootPath);
|
||||
});
|
||||
|
||||
services.AddHttpContextAccessor(); // it's very important for CurrentUserService
|
||||
services.AddScoped<ICurrentUserService, CurrentUserService>();
|
||||
}
|
||||
|
||||
public static void AddRepositories(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IUsersRepository, UsersRepository>();
|
||||
services.AddScoped<IMessagesRepository, MessagesRepository>();
|
||||
services.AddScoped<IInvitesRepository, InvitesRepository>();
|
||||
services.AddScoped<IAdminsRepository, AdminsRepository>();
|
||||
services.AddScoped<IMediaAttachmentsRepository, MediaAttachmentsRepository>();
|
||||
services.AddScoped<IFriendshipsRepository, FriendshipsRepository>();
|
||||
}
|
||||
|
||||
public static void AddValidators(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IObjectValidator<User>, UserValidator>();
|
||||
services.AddScoped<IObjectValidator<Message>, MessageValidator>();
|
||||
services.AddScoped<IObjectValidator<MediaAttachments>, MediaAttachmentsValidator>();
|
||||
services.AddScoped<IObjectValidator<Admin>, AdminValidator>();
|
||||
services.AddScoped<IObjectValidator<Invitation>, InvitationValidator>();
|
||||
services.AddScoped<IObjectValidator<Friendship>, FriendshipValidator>();
|
||||
}
|
||||
|
||||
public static void AddGovorDbContext(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddDbContext<GovorDbContext>(
|
||||
options =>
|
||||
{
|
||||
options.UseNpgsql(configuration.GetConnectionString(nameof(GovorDbContext)));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using Govor.Contracts.Responses.SignalR;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Govor.API.Filters;
|
||||
|
||||
public class HubExceptionFilter : IHubFilter
|
||||
{
|
||||
private readonly ILogger<HubExceptionFilter> _logger;
|
||||
|
||||
public HubExceptionFilter(ILogger<HubExceptionFilter> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async ValueTask<object?> InvokeMethodAsync(
|
||||
HubInvocationContext context,
|
||||
Func<HubInvocationContext, ValueTask<object?>> next)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await next(context);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Bad request in {Method}", context.HubMethodName);
|
||||
return CreateHubErrorResult(context, HubResultStatus.BadRequest, ex.Message);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Unauthorized in {Method}", context.HubMethodName);
|
||||
return CreateHubErrorResult(context, HubResultStatus.Unauthorized, ex.Message);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Not found in {Method}", context.HubMethodName);
|
||||
return CreateHubErrorResult(context, HubResultStatus.NotFound, ex.Message);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Invalid operation in {Method}", context.HubMethodName);
|
||||
return CreateHubErrorResult(context, HubResultStatus.Conflict, ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unhandled error in {Method}", context.HubMethodName);
|
||||
return CreateHubErrorResult(context, HubResultStatus.ServerError, "Internal server error");
|
||||
}
|
||||
}
|
||||
|
||||
private static object CreateHubErrorResult(HubInvocationContext context, HubResultStatus status, string message)
|
||||
{
|
||||
var returnType = context.HubMethod.ReturnType;
|
||||
|
||||
// Поддержка Task<HubResult<T>>
|
||||
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
{
|
||||
var resultType = returnType.GenericTypeArguments[0];
|
||||
|
||||
if (resultType.IsGenericType && resultType.GetGenericTypeDefinition() == typeof(HubResult<>))
|
||||
{
|
||||
var hubResultType = resultType;
|
||||
var errorResult = Activator.CreateInstance(hubResultType)!;
|
||||
|
||||
hubResultType.GetProperty(nameof(HubResult<object>.Status))!
|
||||
.SetValue(errorResult, status);
|
||||
hubResultType.GetProperty(nameof(HubResult<object>.ErrorMessage))!
|
||||
.SetValue(errorResult, message);
|
||||
|
||||
return Task.FromResult(errorResult);
|
||||
}
|
||||
}
|
||||
|
||||
// Ничего не возвращаем, если не поддерживается
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+18
-14
@@ -1,29 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>disable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||
<PackageReference Include="AutoMapper" Version="12.0.1" />
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.6">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6" />
|
||||
<PackageReference Include="NSwag.AspNetCore" Version="14.4.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.1" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Govor.Application\Govor.Application.csproj" />
|
||||
<ProjectReference Include="..\Govor.Contracts\Govor.Contracts.csproj" />
|
||||
<ProjectReference Include="..\Govor.Application\Govor.Application.csproj" />
|
||||
<ProjectReference Include="..\Govor.Contracts\Govor.Contracts.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Contracts\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
+267
-54
@@ -1,103 +1,316 @@
|
||||
using System.Security.Claims;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Govor.Application.Exceptions.VerifyFriendship;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Messages;
|
||||
using Govor.Application.Interfaces.Messages.Parameters;
|
||||
using Govor.Contracts.Requests.SignalR;
|
||||
using Govor.Contracts.Responses.SignalR;
|
||||
using Govor.Core.Models.Messages;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Govor.API.Hubs;
|
||||
|
||||
[Authorize]
|
||||
[Authorize] // api/chats
|
||||
public class ChatsHub : Hub
|
||||
{
|
||||
private readonly IUsersRepository _usersRepository;
|
||||
private readonly ILogger<ChatsHub> _logger;
|
||||
private readonly IMessageCommandService _messageCommandService;
|
||||
private readonly IUserGroupsService _userService;
|
||||
private readonly IHubUserAccessor _userAccessor;
|
||||
|
||||
public ChatsHub(IUsersRepository usersRepository, ILogger<ChatsHub> logger)
|
||||
public ChatsHub(
|
||||
ILogger<ChatsHub> logger,
|
||||
IMessageCommandService messageCommandService,
|
||||
IUserGroupsService userService,
|
||||
IHubUserAccessor userAccessor)
|
||||
{
|
||||
_usersRepository = usersRepository;
|
||||
_logger = logger;
|
||||
_messageCommandService = messageCommandService;
|
||||
_userService = userService;
|
||||
_userAccessor = userAccessor;
|
||||
}
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
var userId = GetUserId();
|
||||
if (userId != Guid.Empty)
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
if (userId == Guid.Empty)
|
||||
{
|
||||
// Привязываем ConnectionId к UserId
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, userId.ToString());
|
||||
_logger.LogInformation("User {UserId} connected with ConnectionId {ConnectionId}", userId, Context.ConnectionId);
|
||||
_logger.LogWarning("User connected with invalid UserID claim.");
|
||||
Context.Abort();
|
||||
return;
|
||||
}
|
||||
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, userId.ToString());
|
||||
_logger.LogInformation("User {UserId} connected with ConnectionId {ConnectionId} and added to their group",
|
||||
userId, Context.ConnectionId);
|
||||
|
||||
var userGroups = await _userService.GetUserGroupsAsync(userId);
|
||||
|
||||
foreach (var group in userGroups)
|
||||
{
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, $"group_{group.Id}");
|
||||
}
|
||||
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
public override async Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var userId = _userAccessor.GetUserId(Context, true);
|
||||
if (userId != Guid.Empty)
|
||||
{
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, userId.ToString());
|
||||
_logger.LogInformation("User {UserId} disconnected", userId);
|
||||
_logger.LogInformation("User {UserId} disconnected with ConnectionId {ConnectionId} and removed from their group",
|
||||
userId, Context.ConnectionId);
|
||||
|
||||
var userGroups = await _userService.GetUserGroupsAsync(userId);
|
||||
|
||||
foreach (var group in userGroups)
|
||||
{
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"group_{group.Id}");
|
||||
}
|
||||
}
|
||||
else if (exception != null)
|
||||
{
|
||||
_logger.LogWarning(exception,
|
||||
"User disconnected with an exception and invalid UserID claim. ConnectionId: {ConnectionId}",
|
||||
Context.ConnectionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"User disconnected with no exception and invalid UserID claim. ConnectionId: {ConnectionId}",
|
||||
Context.ConnectionId);
|
||||
}
|
||||
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
|
||||
public async Task Edit(string newMessage, Guid messageId)
|
||||
public async Task<HubResult<UserMessageResponse>> Send(MessageRequest request)
|
||||
{
|
||||
var senderId = _userAccessor.GetUserId(Context);
|
||||
|
||||
}
|
||||
|
||||
public async Task Send(string message, Guid toUserId)
|
||||
{
|
||||
// Валидация входных данных
|
||||
if (string.IsNullOrWhiteSpace(message))
|
||||
if (string.IsNullOrWhiteSpace(request.EncryptedContent) &&
|
||||
(request.MediaAttachments == null || !request.MediaAttachments.Any()))
|
||||
{
|
||||
_logger.LogWarning("Empty message received from user {UserId}", GetUserId());
|
||||
throw new ArgumentException("Message cannot be empty", nameof(message));
|
||||
_logger.LogWarning("Empty message received from user {UserId}", senderId);
|
||||
return HubResult<UserMessageResponse>.BadRequest("Message must contain content or media.");
|
||||
}
|
||||
|
||||
var senderId = GetUserId();
|
||||
if (request.EncryptedContent.Length > 50_000)
|
||||
{
|
||||
_logger.LogWarning("User {SenderId} tried to send a too long message (length: {Length})", senderId, request.EncryptedContent.Length);
|
||||
return HubResult<UserMessageResponse>.BadRequest("Message cannot exceed 50,000 characters.");
|
||||
}
|
||||
|
||||
// Проверка существования получателя
|
||||
try
|
||||
{
|
||||
await _usersRepository.FindByIdAsync(toUserId);
|
||||
}
|
||||
catch (NotFoundByKeyException<User> ex)
|
||||
{
|
||||
_logger.LogWarning("Recipient user {ToUserId} not found", toUserId);
|
||||
throw;
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
_logger.LogWarning("Invalid recipient userId received from user {UserId}", GetUserId());
|
||||
throw;
|
||||
}
|
||||
_logger.LogInformation("Sending message from {SenderId} to {RecipientId} ({RecipientType})",
|
||||
senderId, request.RecipientId, request.RecipientType);
|
||||
|
||||
var sendMessageParams = new SendMessage(
|
||||
EncryptContent: request.EncryptedContent,
|
||||
ReplyToMessageId: request.ReplyToMessageId,
|
||||
FromUserId: senderId,
|
||||
RecipientId: request.RecipientId,
|
||||
RecipientType: request.RecipientType,
|
||||
SendAt: DateTime.UtcNow,
|
||||
Media: request.MediaAttachments?.Select(f => new SendMedia(f.MediaId, f.EncryptedKey)) ??
|
||||
Array.Empty<SendMedia>()
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Message sent from {SenderId} to {RecipientId}: {Message}", senderId, toUserId, message);
|
||||
var result = await _messageCommandService.SendMessageAsync(sendMessageParams);
|
||||
|
||||
// Отправка сообщения отправителю и получателю
|
||||
await Clients.Group(toUserId.ToString()).SendAsync("Receive", message, senderId);
|
||||
await Clients.Group(senderId.ToString()).SendAsync("Receive", message, senderId);
|
||||
if (!result.IsSuccess || result.Message.Id == Guid.Empty)
|
||||
return LogAndError<UserMessageResponse>(senderId, request.RecipientId, "Failed to send message", result.Exception);
|
||||
|
||||
var response = BuildUserMessageResponse(result.Message, request.ReplyToMessageId);
|
||||
|
||||
await NotifyClientsAboutMessage(response);
|
||||
|
||||
return HubResult<UserMessageResponse>.Ok(response);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
return LogAndUnauthorized<UserMessageResponse>(ex, "Unauthorized sending attempt", senderId,
|
||||
request.RecipientId);
|
||||
}
|
||||
catch (FriendshipException)
|
||||
{
|
||||
return HubResult<UserMessageResponse>.Unauthorized(
|
||||
"You cannot send this message because you are not friends.");
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return HubResult<UserMessageResponse>.BadRequest("Invalid message content.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error sending message from {SenderId} to {RecipientId}", senderId, toUserId);
|
||||
throw;
|
||||
return LogAndError<UserMessageResponse>(senderId, request.RecipientId, "Unhandled exception", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Guid GetUserId()
|
||||
public async Task<HubResult<MessageRemovedResponse>> Remove(RemoveMessageRequest request)
|
||||
{
|
||||
var userIdClaim = Context.User?.FindFirst("userID")?.Value;
|
||||
if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
|
||||
var removerId = _userAccessor.GetUserId(Context);
|
||||
_logger.LogInformation("Removing message {MessageId} by user {RemoverId}", request.MessageId, removerId);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogError("Could not retrieve sender userId");
|
||||
throw new UnauthorizedAccessException("userID claim is missing or invalid");
|
||||
var result = await _messageCommandService.DeleteMessageAsync(new DeleteMessage(removerId, request.MessageId));
|
||||
|
||||
if (!result.IsSuccess || result.OriginalMessage == null)
|
||||
return LogAndError<MessageRemovedResponse>(removerId, request.MessageId, "Message deletion failed", result.Exception);
|
||||
|
||||
var notification = new MessageRemovedResponse
|
||||
{
|
||||
MessageId = request.MessageId,
|
||||
SenderId = result.OriginalMessage.SenderId,
|
||||
RecipientId = result.OriginalMessage.RecipientId,
|
||||
RecipientType = result.OriginalMessage.RecipientType
|
||||
};
|
||||
|
||||
await NotifyClientsAboutRemoval(notification);
|
||||
|
||||
_logger.LogInformation("Message {MessageId} removed successfully by {RemoverId}", request.MessageId, removerId);
|
||||
return HubResult<MessageRemovedResponse>.Ok(notification);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
return LogAndUnauthorized<MessageRemovedResponse>(ex, "Unauthorized removal", removerId, request.MessageId);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
return LogAndNotFound<MessageRemovedResponse>(ex, "Message not found", removerId, request.MessageId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return LogAndError<MessageRemovedResponse>(removerId, request.MessageId, "Unhandled deletion error", ex);
|
||||
}
|
||||
return userId;
|
||||
}
|
||||
|
||||
public async Task<HubResult<MessageEditResponse>> Edit(EditMessageRequest request)
|
||||
{
|
||||
var editor = _userAccessor.GetUserId(Context);
|
||||
_logger.LogInformation("Editing message {MessageId} by user {EditorId}", request.MessageId, editor);
|
||||
|
||||
var editMessageParam = new EditMessage(editor,
|
||||
request.MessageId,
|
||||
request.NewEncryptedContent,
|
||||
DateTime.UtcNow);
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _messageCommandService.EditMessageAsync(editMessageParam);
|
||||
|
||||
if (!result.IsSuccess || result.OriginalMessage == null)
|
||||
return LogAndError<MessageEditResponse>(editor, request.MessageId, "Edit message error",
|
||||
result.Exception);
|
||||
|
||||
var response = new MessageEditResponse()
|
||||
{
|
||||
MessageId = result.messageId,
|
||||
EditorId = editor,
|
||||
RecipientId = result.OriginalMessage.RecipientId,
|
||||
RecipientType = result.OriginalMessage.RecipientType,
|
||||
NewEncryptedContent = request.NewEncryptedContent,
|
||||
EditedAt = editMessageParam.EditedAt,
|
||||
};
|
||||
|
||||
await NotifyClientsAboutEdit(response);
|
||||
|
||||
_logger.LogInformation("Message {MessageId} edited successfully by {editor}", request.MessageId, editor);
|
||||
|
||||
return HubResult<MessageEditResponse>.Ok(response);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
return LogAndUnauthorized<MessageEditResponse>(ex, "Unauthorized editing", editor, request.MessageId);
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
return LogAndNotFound<MessageEditResponse>(ex, "Message not found", editor, request.MessageId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return LogAndError<MessageEditResponse>(editor, request.MessageId, "Unhandled exception error", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region common
|
||||
private UserMessageResponse BuildUserMessageResponse(Message message, Guid? replyToId)
|
||||
{
|
||||
return new UserMessageResponse
|
||||
{
|
||||
MessageId = message.Id,
|
||||
SenderId = message.SenderId,
|
||||
RecipientId = message.RecipientId,
|
||||
RecipientType = message.RecipientType,
|
||||
EncryptedContent = message.EncryptedContent,
|
||||
SentAt = message.SentAt,
|
||||
IsEdited = false,
|
||||
MediaAttachments = message.MediaAttachments.Select(m => m.MediaFile).ToList(),
|
||||
ReplyToMessageId = replyToId
|
||||
};
|
||||
}
|
||||
|
||||
private async Task NotifyClientsAboutMessage(UserMessageResponse response)
|
||||
{
|
||||
string group = response.RecipientType == RecipientType.User
|
||||
? response.RecipientId.ToString()
|
||||
: $"group_{response.RecipientId}";
|
||||
|
||||
await Clients.Group(group).SendAsync("ReceiveMessage", response);
|
||||
await Clients.Caller.SendAsync("MessageSent", response);
|
||||
}
|
||||
|
||||
private async Task NotifyClientsAboutRemoval(MessageRemovedResponse response)
|
||||
{
|
||||
if (response.RecipientType == RecipientType.User)
|
||||
{
|
||||
await Clients.Group(response.SenderId.ToString()).SendAsync("MessageRemoved", response);
|
||||
if (response.SenderId != response.RecipientId)
|
||||
await Clients.Group(response.RecipientId.ToString()).SendAsync("MessageRemoved", response);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Clients.Group($"group_{response.RecipientId}").SendAsync("MessageRemoved", response);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task NotifyClientsAboutEdit(MessageEditResponse response)
|
||||
{
|
||||
if (response.RecipientType == RecipientType.User)
|
||||
{
|
||||
await Clients.Group(response.EditorId.ToString()).SendAsync("MessageEdit", response);
|
||||
if (response.EditorId != response.RecipientId)
|
||||
await Clients.Group(response.RecipientId.ToString()).SendAsync("MessageEdit", response);
|
||||
}
|
||||
else
|
||||
{
|
||||
await Clients.Group($"group_{response.RecipientId}").SendAsync("MessageEdit", response);
|
||||
}
|
||||
}
|
||||
|
||||
// Logging helpers
|
||||
private HubResult<T> LogAndError<T>(Guid userId, Guid targetId, string message, Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "{Message} from {UserId} to {TargetId}", message, userId, targetId);
|
||||
return HubResult<T>.Error(ex?.Message ?? "Internal server error");
|
||||
}
|
||||
|
||||
private HubResult<T> LogAndUnauthorized<T>(Exception ex, string msg, Guid userId, Guid targetId)
|
||||
{
|
||||
_logger.LogWarning(ex, "{Msg}: {UserId} -> {TargetId}", msg, userId, targetId);
|
||||
return HubResult<T>.Unauthorized("You are not authorized to perform this action.");
|
||||
}
|
||||
|
||||
private HubResult<T> LogAndNotFound<T>(Exception ex, string msg, Guid userId, Guid targetId)
|
||||
{
|
||||
_logger.LogWarning(ex, "{Msg}: {UserId} -> {TargetId}", msg, userId, targetId);
|
||||
return HubResult<T>.NotFound("Message not found.");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
using AutoMapper;
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Govor.Application.Exceptions.FriendsService;
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
using Govor.Contracts.DTOs;
|
||||
using Govor.Contracts.Responses.SignalR;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Govor.API.Hubs;
|
||||
|
||||
public class FriendsHub : Hub
|
||||
{
|
||||
private readonly ILogger<FriendsHub> _logger;
|
||||
private readonly IFriendRequestCommandService _friendRequestService;
|
||||
private readonly IHubUserAccessor _userAccessor;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public FriendsHub(
|
||||
ILogger<FriendsHub> logger,
|
||||
IFriendRequestCommandService friendRequestService,
|
||||
IHubUserAccessor userAccessor,
|
||||
IMapper mapper)
|
||||
{
|
||||
_logger = logger;
|
||||
_friendRequestService = friendRequestService;
|
||||
_userAccessor = userAccessor;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
if (userId == Guid.Empty)
|
||||
{
|
||||
_logger.LogWarning("User connected with invalid UserID claim.");
|
||||
Context.Abort();
|
||||
return;
|
||||
}
|
||||
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, userId.ToString());
|
||||
_logger.LogInformation("User {UserId} connected with ConnectionId {ConnectionId} and added to their group",
|
||||
userId, Context.ConnectionId);
|
||||
|
||||
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
var userId = _userAccessor.GetUserId(Context, true);
|
||||
if (userId != Guid.Empty)
|
||||
{
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, userId.ToString());
|
||||
_logger.LogInformation(
|
||||
"User {UserId} disconnected with ConnectionId {ConnectionId} and removed from their group", userId,
|
||||
Context.ConnectionId);
|
||||
}
|
||||
else if (exception != null)
|
||||
{
|
||||
_logger.LogWarning(exception,
|
||||
"User disconnected with an exception and invalid UserId claim. ConnectionId: {ConnectionId}",
|
||||
Context.ConnectionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"User disconnected with no exception and invalid UserId claim. ConnectionId: {ConnectionId}",
|
||||
Context.ConnectionId);
|
||||
}
|
||||
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
|
||||
public async Task<HubResult<object>> SendRequest(Guid targetUserId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
var friendship = await _friendRequestService.SendAsync(userId, targetUserId);
|
||||
|
||||
await Clients.Group(targetUserId.ToString())
|
||||
.SendAsync("FriendRequestReceived", _mapper.Map<FriendshipDto>(friendship));
|
||||
|
||||
_logger.LogInformation($"Friend request received for user {targetUserId} from {userId}.");
|
||||
return HubResult<object>.Created();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return HubResult<object>.BadRequest(ex.Message);
|
||||
}
|
||||
catch (RequestAlreadySentException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return HubResult<object>.Conflict(ex.Message);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return HubResult<object>.Unauthorized(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return HubResult<object>.Error("Unexpected error! Please try later!");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<HubResult<object>> AcceptRequest(Guid friendshipId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
var friendship = await _friendRequestService.AcceptAsync(friendshipId, userId);
|
||||
|
||||
await Clients.Group(userId.ToString())
|
||||
.SendAsync("FriendRequestAccepted", _mapper.Map<FriendshipDto>(friendship));
|
||||
|
||||
await Clients.Group(friendship.RequesterId.ToString())
|
||||
.SendAsync( "YourFriendRequestAccepted", _mapper.Map<UserDto>(friendship.Addressee));
|
||||
|
||||
_logger.LogInformation($"Friend request accepted for user {userId} from {userId}.");
|
||||
return HubResult<object>.Ok();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return HubResult<object>.BadRequest(ex.Message);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return HubResult<object>.Unauthorized(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return HubResult<object>.Error("Unexpected error! Please try later!");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<HubResult<object>> RejectRequest(Guid friendshipId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
var friendship = await _friendRequestService.RejectAsync(friendshipId, userId);
|
||||
var dto = _mapper.Map<FriendshipDto>(friendship);
|
||||
|
||||
await Clients.Group(userId.ToString())
|
||||
.SendAsync("FriendRequestRejected", dto);
|
||||
|
||||
await Clients.Group(friendship.RequesterId.ToString())
|
||||
.SendAsync("YourFriendRequestRejected", dto);
|
||||
|
||||
_logger.LogInformation($"Friend request rejected for user {userId} from {userId}.");
|
||||
return HubResult<object>.Ok();
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return HubResult<object>.BadRequest(ex.Message);
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, ex.Message);
|
||||
return HubResult<object>.Unauthorized(ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, ex.Message);
|
||||
return HubResult<object>.Error("Unexpected error! Please try later!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Govor.API.Hubs;
|
||||
|
||||
public class UsersHub : Hub
|
||||
public class GroupsHub : Hub
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Govor.API.Hubs;
|
||||
|
||||
[Authorize(Roles = "Admin, User")]
|
||||
[Route("hubs/presence")]
|
||||
public class PresenceHub : Hub
|
||||
{
|
||||
private readonly ILogger<PresenceHub> _logger;
|
||||
private readonly IUserNotificationScopeService _scopeService;
|
||||
private readonly IOnlineUserStore _onlineUserStore;
|
||||
private readonly IHubUserAccessor _userAccessor;
|
||||
private readonly IUsersRepository _users;
|
||||
|
||||
public PresenceHub(
|
||||
ILogger<PresenceHub> logger,
|
||||
IUserNotificationScopeService scopeService,
|
||||
IOnlineUserStore onlineUserStore,
|
||||
IHubUserAccessor userAccessor,
|
||||
IUsersRepository users)
|
||||
{
|
||||
_logger = logger;
|
||||
_users = users;
|
||||
_scopeService = scopeService;
|
||||
_onlineUserStore = onlineUserStore;
|
||||
_userAccessor = userAccessor;
|
||||
}
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
if (userId == Guid.Empty || await _users.ExistsByIdAsync(userId) == false)
|
||||
{
|
||||
_logger.LogWarning("User connected with invalid UserId claim.");
|
||||
Context.Abort();
|
||||
return;
|
||||
}
|
||||
|
||||
_onlineUserStore.SetOnlineUser(userId);
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, userId.ToString());
|
||||
|
||||
var friends = await _scopeService.GetNotifiedUsers(userId);
|
||||
|
||||
foreach (var recipient in friends)
|
||||
{
|
||||
await Clients.Group(recipient.ToString())
|
||||
.SendAsync("UserOnline", userId);
|
||||
}
|
||||
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
var userId = _userAccessor.GetUserId(Context, true);
|
||||
|
||||
if (userId != Guid.Empty)
|
||||
{
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, userId.ToString());
|
||||
_logger.LogInformation(
|
||||
"User {UserId} disconnected with ConnectionId {ConnectionId} and removed from their group", userId,
|
||||
Context.ConnectionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"User disconnected with no exception and invalid UserId claim. ConnectionId: {ConnectionId}",
|
||||
Context.ConnectionId);
|
||||
return;
|
||||
}
|
||||
|
||||
_onlineUserStore.SetOfflineUser(userId);
|
||||
|
||||
// Updating was online
|
||||
var user = await _users.FindByIdAsync(userId);
|
||||
user.WasOnline = DateTime.UtcNow;
|
||||
await _users.UpdateAsync(user);
|
||||
|
||||
var friends = await _scopeService.GetNotifiedUsers(userId);
|
||||
|
||||
foreach (var recipient in friends)
|
||||
{
|
||||
await Clients.Group(recipient.ToString())
|
||||
.SendAsync("UserOffline", userId);
|
||||
}
|
||||
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using Govor.API.Common.SignalR.Helpers;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Application.Interfaces.Medias;
|
||||
using Govor.Contracts.Responses.SignalR;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Core.Repositories.Groups;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Govor.API.Hubs;
|
||||
|
||||
[Authorize]
|
||||
public class ProfileHub : Hub
|
||||
{
|
||||
private readonly IGroupsRepository _groupsRepository;
|
||||
private readonly IFriendshipService _friendsService;
|
||||
private readonly IProfileService _profileService;
|
||||
private readonly IHubUserAccessor _userAccessor;
|
||||
private readonly ILogger<ProfileHub> _logger;
|
||||
private readonly IMediaService _mediaService;
|
||||
|
||||
public ProfileHub(
|
||||
IGroupsRepository groupsRepository,
|
||||
IFriendshipService friendsService,
|
||||
IProfileService profileService,
|
||||
IHubUserAccessor userAccessor,
|
||||
ILogger<ProfileHub> logger)
|
||||
{
|
||||
_groupsRepository = groupsRepository;
|
||||
_friendsService = friendsService;
|
||||
_profileService = profileService;
|
||||
_userAccessor = userAccessor;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, $"user-{userId}");
|
||||
|
||||
// Friends
|
||||
try
|
||||
{
|
||||
var friendships = await _friendsService.GetFriendsAsync(userId);
|
||||
foreach (var friends in friendships)
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, $"friends-{friends.Id}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to get friends for user {userId}", userId);
|
||||
}
|
||||
|
||||
// Groups
|
||||
try
|
||||
{
|
||||
var groups = await _groupsRepository.GetByUserIdAsync(userId);
|
||||
foreach (var group in groups)
|
||||
await Groups.AddToGroupAsync(Context.ConnectionId, $"group-{group.Id}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to get groups for user {userId}", userId);
|
||||
}
|
||||
|
||||
_logger.LogInformation("User {UserId} connected with ConnectionId {ConnectionId}", userId, Context.ConnectionId);
|
||||
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"user-{userId}");
|
||||
|
||||
// Friends
|
||||
try
|
||||
{
|
||||
var friendships = await _friendsService.GetFriendsAsync(userId);
|
||||
|
||||
foreach (var friendship in friendships)
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"friends-{friendship.Id}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to remove user {userId} from friend groups", userId);
|
||||
}
|
||||
|
||||
// Groups
|
||||
try
|
||||
{
|
||||
var groups = await _groupsRepository.GetByUserIdAsync(userId);
|
||||
|
||||
foreach (var group in groups)
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"group-{group.Id}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to remove user {userId} from groups", userId);
|
||||
}
|
||||
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
|
||||
public async Task<HubResult<bool>> SetDescription(string description)
|
||||
{
|
||||
if(description.Length > 500)
|
||||
return HubResult<bool>.Error("The description cannot be longer than 500 characters.");
|
||||
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
|
||||
try
|
||||
{
|
||||
await _profileService.SetDescription(description, userId);
|
||||
|
||||
var payload = new { userId, description };
|
||||
|
||||
await NotifyFriendsAndGroupsAsync(userId, "DescriptionUpdated", payload);
|
||||
|
||||
return HubResult<bool>.Ok(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An error occurred while updating the user's {userId} descripton!", userId);
|
||||
return HubResult<bool>.Error("An unaccounted error on the server!");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<HubResult<bool>> SetAvatar(Guid iconId)
|
||||
{
|
||||
var userId = _userAccessor.GetUserId(Context);
|
||||
|
||||
try
|
||||
{
|
||||
if (iconId == Guid.Empty)
|
||||
return HubResult<bool>.BadRequest("IconId can't be empty!");
|
||||
|
||||
await _profileService.SetNewIcon(userId, iconId);
|
||||
|
||||
var payload = new { userId, iconId };
|
||||
|
||||
await NotifyFriendsAndGroupsAsync(userId, "AvatarUpdated", payload);
|
||||
|
||||
return HubResult<bool>.Ok(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "An error occurred while updating the user's {userId} avatar {iconId}", userId, iconId);
|
||||
return HubResult<bool>.Error("An unaccounted error on the server!");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task NotifyFriendsAndGroupsAsync(Guid userId, string eventName, object payload)
|
||||
{
|
||||
var friendIds = Enumerable.Empty<User>();
|
||||
var groups = Enumerable.Empty<ChatGroup>();
|
||||
|
||||
try
|
||||
{
|
||||
friendIds = await _friendsService.GetFriendsAsync(userId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load friend list for notifications. userId: {userId}", userId);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
groups = await _groupsRepository.GetByUserIdAsync(userId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load group list for notifications. userId: {userId}", userId);
|
||||
}
|
||||
|
||||
var groupIds = groups.Select(g => g.Id).ToList();
|
||||
|
||||
// Current user
|
||||
await Clients.Group($"user-{userId}")
|
||||
.SendAsync(eventName, payload);
|
||||
|
||||
// Friends
|
||||
foreach (var friend in friendIds)
|
||||
{
|
||||
await Clients.Group($"friends-{friend.Id}")
|
||||
.SendAsync(eventName, payload);
|
||||
}
|
||||
|
||||
// Groups
|
||||
foreach (var groupId in groupIds)
|
||||
{
|
||||
await Clients.Group($"group-{groupId}")
|
||||
.SendAsync(eventName, payload);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Sent {EventName} for {UserId} to {Friends} friends and {Groups} groups",
|
||||
eventName, userId, friendIds.Count(), groupIds.Count);
|
||||
}
|
||||
}
|
||||
+33
-15
@@ -1,7 +1,7 @@
|
||||
using System.Text;
|
||||
using Govor.API.Extensions;
|
||||
using System.Text;
|
||||
using Govor.API.Common.Extensions;
|
||||
using Govor.API.Hubs;
|
||||
using Govor.Application.Services;
|
||||
using Govor.Application.Services.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Models;
|
||||
@@ -11,23 +11,30 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
var configuration = builder.Configuration;
|
||||
var services = builder.Services;
|
||||
|
||||
builder.AddLogger();// Serilog
|
||||
|
||||
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowFrontend", policy =>
|
||||
{
|
||||
policy.WithOrigins("http://localhost:3000", "https://localhost:3000")
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials();
|
||||
policy.WithOrigins(
|
||||
"https://localhost:7155",
|
||||
"http://localhost:7155",
|
||||
"https://govor-team-govor-8ce1.twc1.net",
|
||||
"http://govor-team-govor-8ce1.twc1.net"
|
||||
)
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials();
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.Configure<JwtOption>(configuration.GetSection(nameof(JwtOption)));
|
||||
builder.Services.Configure<JwtAccessOption>(configuration.GetSection(nameof(JwtAccessOption)));
|
||||
|
||||
// Add services
|
||||
builder.Services.AddSignalR();
|
||||
builder.Services.AddSignalRConf();// signalR
|
||||
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
|
||||
@@ -39,7 +46,7 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(
|
||||
Encoding.UTF8.GetBytes(builder.Configuration["JwtOption:SecretKeу"]!))
|
||||
Encoding.UTF8.GetBytes(builder.Configuration["JwtAccessOption:SecretKey"]!))
|
||||
};
|
||||
options.Events = new JwtBearerEvents
|
||||
{
|
||||
@@ -65,6 +72,8 @@ builder.Services.AddServices();
|
||||
builder.Services.AddRepositories();
|
||||
builder.Services.AddValidators();
|
||||
|
||||
builder.Services.AddOptionsConfiguration(configuration);
|
||||
|
||||
builder.Services.AddGovorDbContext(configuration); // GovorDbContext init
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
@@ -94,18 +103,23 @@ builder.Services.AddSwaggerGen(options =>
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
//builder.Services.AddOpenApi();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
//app.MapOpenApi();
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
builder.WebHost.UseUrls("http://0.0.0.0:8080");
|
||||
}
|
||||
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
|
||||
app.UseCors("AllowFrontend");
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
@@ -114,9 +128,13 @@ app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
app.MapHub<ChatsHub>("/api/chats");
|
||||
|
||||
app.MapSwagger().RequireAuthorization();
|
||||
app.MapHub<ChatsHub>("/hubs/chats");
|
||||
app.MapHub<FriendsHub>("/hubs/friends");
|
||||
app.MapHub<ProfileHub>("/hubs/profiles");
|
||||
|
||||
app.MapSwagger()
|
||||
.RequireAuthorization();
|
||||
|
||||
app.Map("/", () => "Not for browsers");
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5041",
|
||||
"applicationUrl": "http://0.0.0.0:8080;",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
@@ -13,8 +13,8 @@
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7155;http://localhost:5041",
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://0.0.0.0:8080;",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
||||
@@ -6,11 +6,18 @@
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"GovorDbContext": "Host=localhost;Port=5432;Database=DbGovor;Username=postgres;Password=stalcker;"
|
||||
"GovorDbContext": "Host=localhost;Port=5432;Database=GovorDb;Username=postgres;Password=stalcker;"
|
||||
},
|
||||
"UseMySql": false,
|
||||
"AllowedHosts": "*",
|
||||
"JwtOption": {
|
||||
"SecretKeу": "MY VERY SECRET KEY asdasdpafjhasofafpajsfj",
|
||||
"Hours": "24"
|
||||
"JwtAccessOption": {
|
||||
"SecretKey": "Q89eY7zP7C4+TqLmHF4kw9xkF1E8Ru4Zpg+up9wFt9g=",
|
||||
"Minutes": 10
|
||||
},
|
||||
"JwtRefreshOption": {
|
||||
"RefreshTokenLifetimeDays": 30
|
||||
},
|
||||
"EncryptionOption": {
|
||||
"Secret": "4r8B2j9kP5mX7nQwE2zY3A=="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoFixture" Version="5.0.0-preview0012" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="NUnit" Version="4.4.0-beta.1" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.4.0"/>
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="NUnit.Framework"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Govor.Application\Govor.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Validators\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
using AutoFixture;
|
||||
using Govor.API.Services.AdminsStuff.Interfaces;
|
||||
using Govor.Application.Interfaces.AdminsStuff;
|
||||
using Govor.Application.Infrastructure.AdminsStuff;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Repositories.Invaites;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.UnitTests.Services.AdminStuff;
|
||||
namespace Govor.Application.Tests.Infrastructure.AdminsStuff;
|
||||
|
||||
[TestFixture]
|
||||
public class InvitationGeneratorTests
|
||||
@@ -0,0 +1,107 @@
|
||||
using System.Security.Claims;
|
||||
using Govor.Application.Infrastructure.Extensions;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.Application.Tests.Infrastructure.Extensions;
|
||||
|
||||
[TestFixture]
|
||||
public class CurrentUserServiceTests
|
||||
{
|
||||
private Mock<IHttpContextAccessor> _httpContextAccessorMock;
|
||||
private ICurrentUserService _currentUserService;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_httpContextAccessorMock = new Mock<IHttpContextAccessor>();
|
||||
_currentUserService = new CurrentUserService(_httpContextAccessorMock.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCurrentUserId_ValidUserIdClaim_ReturnsGuid()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var claims = new[] { new Claim("userId", userId.ToString()) };
|
||||
var identity = new ClaimsIdentity(claims);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(x => x.User).Returns(principal);
|
||||
|
||||
_httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object);
|
||||
|
||||
// Act
|
||||
var result = _currentUserService.GetCurrentUserId();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(userId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCurrentUserId_NoHttpContext_ThrowsUnauthorizedAccessException()
|
||||
{
|
||||
// Arrange
|
||||
_httpContextAccessorMock.Setup(x => x.HttpContext).Returns((HttpContext)null);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<UnauthorizedAccessException>(() => _currentUserService.GetCurrentUserId());
|
||||
Assert.That(ex.Message, Is.EqualTo("userID claim is missing or invalid"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCurrentUserId_NoUserIdClaim_ThrowsUnauthorizedAccessException()
|
||||
{
|
||||
// Arrange
|
||||
var claims = new[] { new Claim("otherClaim", "value") };
|
||||
var identity = new ClaimsIdentity(claims);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(x => x.User).Returns(principal);
|
||||
|
||||
_httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<UnauthorizedAccessException>(() => _currentUserService.GetCurrentUserId());
|
||||
Assert.That(ex.Message, Is.EqualTo("userID claim is missing or invalid"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCurrentUserId_InvalidUserIdClaim_ThrowsUnauthorizedAccessException()
|
||||
{
|
||||
// Arrange
|
||||
var claims = new[] { new Claim("userId", "invalid-guid") };
|
||||
var identity = new ClaimsIdentity(claims);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(x => x.User).Returns(principal);
|
||||
|
||||
_httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<UnauthorizedAccessException>(() => _currentUserService.GetCurrentUserId());
|
||||
Assert.That(ex.Message, Is.EqualTo("userID claim is missing or invalid"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCurrentUserId_EmptyUserIdClaim_ThrowsUnauthorizedAccessException()
|
||||
{
|
||||
// Arrange
|
||||
var claims = new[] { new Claim("userId", "") };
|
||||
var identity = new ClaimsIdentity(claims);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(x => x.User).Returns(principal);
|
||||
|
||||
_httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<UnauthorizedAccessException>(() => _currentUserService.GetCurrentUserId());
|
||||
Assert.That(ex.Message, Is.EqualTo("userID claim is missing or invalid"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Security.Claims;
|
||||
using Govor.Application.Infrastructure.Extensions;
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.Application.Tests.Infrastructure.Extensions;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(CurrentUserSessionService))]
|
||||
public class CurrentUserSessionServiceTests
|
||||
{
|
||||
private Mock<IHttpContextAccessor> _httpContextAccessorMock;
|
||||
private ICurrentUserSessionService _sessionService;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_httpContextAccessorMock = new Mock<IHttpContextAccessor>();
|
||||
_sessionService = new CurrentUserSessionService(_httpContextAccessorMock.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCurrentSessionId_ValidSidClaim_ReturnsGuid()
|
||||
{
|
||||
// Arrange
|
||||
var sid = Guid.NewGuid();
|
||||
var claims = new[] { new Claim("sid", sid.ToString()) };
|
||||
var identity = new ClaimsIdentity(claims);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(x => x.User).Returns(principal);
|
||||
|
||||
_httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object);
|
||||
|
||||
// Act
|
||||
var result = _sessionService.GetUserSessionId();
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(sid));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetCurrentSessionId_NoHttpContext_ThrowsUnauthorizedAccessException()
|
||||
{
|
||||
// Arrange
|
||||
_httpContextAccessorMock.Setup(x => x.HttpContext).Returns((HttpContext)null);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<UnauthorizedAccessException>(() => _sessionService.GetUserSessionId());
|
||||
Assert.That(ex.Message, Is.EqualTo("Session id (sid) claim is missing or invalid"));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void GetCurrentSessionId_NoSidlaim_ThrowsUnauthorizedAccessException()
|
||||
{
|
||||
// Arrange
|
||||
var claims = new[] { new Claim("otherClaim", "value") };
|
||||
var identity = new ClaimsIdentity(claims);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(x => x.User).Returns(principal);
|
||||
|
||||
_httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<UnauthorizedAccessException>(() => _sessionService.GetUserSessionId());
|
||||
Assert.That(ex.Message, Is.EqualTo("Session id (sid) claim is missing or invalid"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUserSessionId_InvalidSidValueClaim_ThrowsUnauthorizedAccessException()
|
||||
{
|
||||
// Arrange
|
||||
var claims = new[] { new Claim("sid", "invalid-guid") };
|
||||
var identity = new ClaimsIdentity(claims);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(x => x.User).Returns(principal);
|
||||
|
||||
_httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<UnauthorizedAccessException>(() => _sessionService.GetUserSessionId());
|
||||
Assert.That(ex.Message, Is.EqualTo("Session id (sid) claim is missing or invalid"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUserSessionId_EmptySidValueClaim_ThrowsUnauthorizedAccessException()
|
||||
{
|
||||
// Arrange
|
||||
var claims = new[] { new Claim("sid", "") };
|
||||
var identity = new ClaimsIdentity(claims);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
|
||||
var httpContextMock = new Mock<HttpContext>();
|
||||
httpContextMock.Setup(x => x.User).Returns(principal);
|
||||
|
||||
_httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContextMock.Object);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<UnauthorizedAccessException>(() => _sessionService.GetUserSessionId());
|
||||
Assert.That(ex.Message, Is.EqualTo("Session id (sid) claim is missing or invalid"));
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
using Govor.Application.Validators; // или другой namespace
|
||||
using Govor.Application.Exceptions.AuthService;
|
||||
using Govor.Application.Infrastructure.Validators;
|
||||
|
||||
namespace Govor.API.Tests.UnitTests.Services.Validators;
|
||||
namespace Govor.Application.Tests.Infrastructure.Validators;
|
||||
|
||||
[TestFixture]
|
||||
public class UsernameValidatorTests
|
||||
@@ -17,6 +17,7 @@ public class UsernameValidatorTests
|
||||
[TestCase("Иван")]
|
||||
[TestCase("Алексей")]
|
||||
[TestCase("Ёжик")]
|
||||
[TestCase("Иван123")] // содержит цифры
|
||||
public void Validate_ValidUsernames_ShouldNotThrow(string username)
|
||||
{
|
||||
Assert.DoesNotThrow(() => _validator.Validate(username));
|
||||
@@ -24,7 +25,6 @@ public class UsernameValidatorTests
|
||||
|
||||
[TestCase("Ivan")] // не кириллица
|
||||
[TestCase("123Иван")] // начинается не с буквы
|
||||
[TestCase("Иван123")] // содержит цифры
|
||||
[TestCase("!@#$")] // спецсимволы
|
||||
[TestCase("")] // пусто
|
||||
[TestCase("И")] // меньше минимума
|
||||
+4
-4
@@ -2,14 +2,14 @@ using AutoFixture;
|
||||
using Govor.Core.Infrastructure.Extensions;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Govor.API.Services.Authentication.Interfaces;
|
||||
using Govor.Application.Exceptions.AuthService;
|
||||
using Govor.Application.Interfaces.Authentication;
|
||||
using Govor.Application.Services;
|
||||
using Govor.Application.Services.Authentication;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Core.Repositories.Admins;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.API.Tests.UnitTests.Services.Authentication;
|
||||
namespace Govor.Application.Tests.Services.Authentication;
|
||||
|
||||
[TestFixture]
|
||||
public class AuthServiceTests
|
||||
@@ -43,13 +43,13 @@ public class AuthServiceTests
|
||||
|
||||
_accountService = new AuthService(
|
||||
_usersRepositoryMock.Object,
|
||||
_jwtServiceMock.Object,
|
||||
_passwordHasherMock.Object,
|
||||
_adminsRepositoryMock.Object,
|
||||
_usernameValidatorMock.Object
|
||||
);
|
||||
}
|
||||
|
||||
// Tests for Register action
|
||||
[Test]
|
||||
public void Given_ExistUser_When_Register_Should_Throw_UserAlreadyExistsException()
|
||||
{
|
||||
@@ -0,0 +1,157 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using AutoFixture;
|
||||
using Govor.Application.Interfaces.Authentication;
|
||||
using Govor.Application.Services.Authentication;
|
||||
using Govor.Core.Models.Users;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.Application.Tests.Services.Authentication;
|
||||
|
||||
[TestFixture]
|
||||
public class JwtServiceTests
|
||||
{
|
||||
private Fixture _fixture;
|
||||
private Mock<IOptions<JwtAccessOption>> _jwtOptionsMock;
|
||||
private Mock<IOptions<JwtRefreshOption>> _jwtRefreshOptionsMock;
|
||||
private Mock<IInvitesService> _invitesServiceMock;
|
||||
private IJwtService _jwtService;
|
||||
|
||||
private JwtAccessOption _testJwtAccessOptions;
|
||||
private JwtRefreshOption _testJwtRefreshOptions;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_testJwtAccessOptions = new JwtAccessOption
|
||||
{
|
||||
SecretKey = "THIS_IS_A_TEST_SECRET_KEY_THAT_IS_LONG_ENOUGH_1234", // Ensure key size is sufficient for HMACSHA256
|
||||
Minutes = 5
|
||||
};
|
||||
|
||||
_testJwtRefreshOptions = new JwtRefreshOption()
|
||||
{
|
||||
RefreshTokenLifetimeDays = 30
|
||||
};
|
||||
|
||||
_jwtOptionsMock = new Mock<IOptions<JwtAccessOption>>();
|
||||
_jwtOptionsMock.Setup(o => o.Value).Returns(_testJwtAccessOptions);
|
||||
|
||||
_jwtRefreshOptionsMock = new Mock<IOptions<JwtRefreshOption>>();
|
||||
_jwtRefreshOptionsMock.Setup(o => o.Value).Returns(_testJwtRefreshOptions);
|
||||
|
||||
_invitesServiceMock = new Mock<IInvitesService>();
|
||||
|
||||
_jwtService = new JwtService(
|
||||
_jwtOptionsMock.Object,
|
||||
_jwtRefreshOptionsMock.Object,
|
||||
_invitesServiceMock.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GenerateJwtToken_ShouldReturnValidJwtString_WithCorrectClaims()
|
||||
{
|
||||
// Arrange
|
||||
var user = _fixture.Create<User>();
|
||||
var sessionId = Guid.NewGuid();
|
||||
var expectedRole = "User";
|
||||
_invitesServiceMock.Setup(s => s.GetRoleAsync(user)).ReturnsAsync(expectedRole);
|
||||
|
||||
// Act
|
||||
var tokenString = await _jwtService.GenerateAccessTokenAsync(user, sessionId);
|
||||
|
||||
// Assert
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var jwt = handler.ReadJwtToken(tokenString);
|
||||
|
||||
var claims = jwt.Claims.ToDictionary(c => c.Type, c => c.Value);
|
||||
|
||||
Assert.That(claims["userId"], Is.EqualTo(user.Id.ToString()));
|
||||
Assert.That(claims["sid"], Is.EqualTo(sessionId.ToString()));
|
||||
Assert.That(claims[ClaimTypes.Role], Is.EqualTo(expectedRole));
|
||||
Assert.That(jwt.ValidTo, Is.GreaterThan(DateTime.UtcNow));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GenerateAccessTokenAsync_ShouldIncludeSessionIdAndRole()
|
||||
{
|
||||
// Arrange
|
||||
var user = new User { Id = Guid.NewGuid(), Username = "TestUser" };
|
||||
var sessionId = Guid.NewGuid();
|
||||
var role = "Admin";
|
||||
_invitesServiceMock.Setup(s => s.GetRoleAsync(user)).ReturnsAsync(role);
|
||||
|
||||
// Act
|
||||
var token = await _jwtService.GenerateAccessTokenAsync(user, sessionId);
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var jwt = handler.ReadJwtToken(token);
|
||||
|
||||
// Assert
|
||||
var sidClaim = jwt.Claims.FirstOrDefault(c => c.Type == "sid");
|
||||
var roleClaim = jwt.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role);
|
||||
|
||||
Assert.That(sidClaim?.Value, Is.EqualTo(sessionId.ToString()));
|
||||
Assert.That(roleClaim?.Value, Is.EqualTo(role));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task GenerateRefreshTokenAsync_ReturnsValidRefreshToken()
|
||||
{
|
||||
// Arrange
|
||||
var user = new User { Id = Guid.NewGuid() };
|
||||
|
||||
// Act
|
||||
var token = await _jwtService.GenerateRefreshTokenAsync(user);
|
||||
|
||||
// Assert
|
||||
Assert.That(token, Is.Not.Null);
|
||||
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var jwt = handler.ReadJwtToken(token);
|
||||
|
||||
Assert.That(user.Id.ToString(), Is.EqualTo(jwt.Claims.First(c => c.Type == "userId").Value));
|
||||
Assert.That("refresh",Is.EqualTo(jwt.Claims.First(c => c.Type == "tokenType").Value));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetPrincipalFromExpiredToken_ReturnsValidClaimsPrincipal()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_testJwtAccessOptions.SecretKey));
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var token = handler.CreateToken(new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim("userId", userId.ToString())
|
||||
}),
|
||||
NotBefore = now.AddSeconds(-10),
|
||||
IssuedAt = now.AddSeconds(-10),
|
||||
Expires = now.AddSeconds(-5),
|
||||
SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
|
||||
});
|
||||
|
||||
var expiredToken = handler.WriteToken(token);
|
||||
|
||||
// Act
|
||||
var principal = _jwtService.GetPrincipalFromExpiredToken(expiredToken);
|
||||
|
||||
// Assert
|
||||
Assert.That(principal, Is.Not.Null);
|
||||
var claim = principal.FindFirst("userId");
|
||||
Assert.That(claim, Is.Not.Null);
|
||||
Assert.That(userId.ToString(), Is.EqualTo(claim.Value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using AutoFixture;
|
||||
using Govor.Application.Interfaces.Authentication;
|
||||
using Govor.Application.Services.Authentication;
|
||||
|
||||
namespace Govor.Application.Tests.Services.Authentication;
|
||||
|
||||
[TestFixture]
|
||||
public class JwtTokenHasherTests
|
||||
{
|
||||
private IJwtTokenHasher _jwtTokenHasher;
|
||||
private Fixture _fixture;
|
||||
|
||||
[SetUp]
|
||||
public void StarUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_jwtTokenHasher = new JwtTokenHasher();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Given_Token_When_Hash_Then_Hash_And_Verify_Then_Result_Should_Be_True()
|
||||
{
|
||||
// Arrange
|
||||
string token = _fixture.Create<string>();
|
||||
|
||||
// Act
|
||||
string hash = _jwtTokenHasher.HashToken(token);
|
||||
|
||||
var result = _jwtTokenHasher.VerifyToken(token, hash);
|
||||
|
||||
// Assert
|
||||
Assert.That(hash, Is.Not.EqualTo(token));
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Given_Token_NotToken_When_Hash_Should_Not_Be_True_Then_Result_Should_Be_False()
|
||||
{
|
||||
// Arrange
|
||||
string token = _fixture.Create<string>();
|
||||
string notToken = _fixture.Create<string>();
|
||||
|
||||
// Act
|
||||
string hash = _jwtTokenHasher.HashToken(token);
|
||||
|
||||
var result = _jwtTokenHasher.VerifyToken(notToken, hash);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
using AutoFixture;
|
||||
using Govor.Application.Exceptions.FriendsService;
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
using Govor.Application.Services.Friends;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Repositories.Friendships;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.Application.Tests.Services.Friends;
|
||||
|
||||
[TestFixture]
|
||||
public class FriendRequestCommandServiceTests
|
||||
{
|
||||
private Fixture _fixture;
|
||||
private Mock<IUsersRepository> _usersRepositoryMock;
|
||||
private Mock<IFriendshipsRepository> _friendshipsRepositoryMock;
|
||||
private IFriendRequestCommandService _service;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors
|
||||
.OfType<ThrowingRecursionBehavior>()
|
||||
.ToList()
|
||||
.ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_usersRepositoryMock = new Mock<IUsersRepository>();
|
||||
_friendshipsRepositoryMock = new Mock<IFriendshipsRepository>();
|
||||
|
||||
_service = new FriendRequestCommandService(_friendshipsRepositoryMock.Object);
|
||||
}
|
||||
|
||||
// SendFriendRequestAsync
|
||||
[Test]
|
||||
public void SendFriendRequestAsync_SendingToSelf_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.ThrowsAsync<InvalidOperationException>(() =>
|
||||
_service.SendAsync(userId, userId));
|
||||
|
||||
Assert.That(ex.Message, Is.EqualTo("Cannot send a request to self user"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SendFriendRequestAsync_RequestAlreadyExists_ThrowsRequestAlreadySentException()
|
||||
{
|
||||
// Arrange
|
||||
var fromUserId = Guid.NewGuid();
|
||||
var toUserId = Guid.NewGuid();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(repo => repo.Exist(fromUserId, toUserId))
|
||||
.Returns(true);
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<RequestAlreadySentException>(() =>
|
||||
_service.SendAsync(fromUserId, toUserId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SendFriendRequestAsync_ValidRequest_CallsAddAsync()
|
||||
{
|
||||
// Arrange
|
||||
var fromUserId = Guid.NewGuid();
|
||||
var toUserId = Guid.NewGuid();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(repo => repo.Exist(fromUserId, toUserId))
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
await _service.SendAsync(fromUserId, toUserId);
|
||||
|
||||
// Assert
|
||||
_friendshipsRepositoryMock.Verify(repo =>
|
||||
repo.AddAsync(It.Is<Friendship>(f =>
|
||||
f.RequesterId == fromUserId &&
|
||||
f.AddresseeId == toUserId &&
|
||||
f.Status == FriendshipStatus.Pending &&
|
||||
f.Id != Guid.Empty
|
||||
)),
|
||||
Times.Once);
|
||||
}
|
||||
|
||||
// AcceptFriendRequestAsync
|
||||
[Test]
|
||||
public async Task AcceptFriendRequestAsync_ValidRequest_CallsUpdateAsync()
|
||||
{
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Pending)
|
||||
.Create();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(friendship.Id))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
// Act: вызываем именно Accept, не Send
|
||||
await _service.AcceptAsync(friendship.Id, friendship.AddresseeId);
|
||||
|
||||
// Assert
|
||||
_friendshipsRepositoryMock.Verify(r => r.UpdateAsync(It.Is<Friendship>(f =>
|
||||
f.Id == friendship.Id &&
|
||||
f.RequesterId == friendship.RequesterId &&
|
||||
f.AddresseeId == friendship.AddresseeId &&
|
||||
f.Status == FriendshipStatus.Accepted
|
||||
)), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AcceptFriendRequestAsync_Throws_InvalidOperationException_IfStatusNotEqualPending()
|
||||
{
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Accepted)
|
||||
.Create();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(friendship.Id))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await _service.AcceptAsync(friendship.Id, friendship.AddresseeId));
|
||||
}
|
||||
[Test]
|
||||
public void AcceptFriendRequestAsync_Throws_UnauthorizedAccessException_IfCurrentUserIdIsNotAddressee()
|
||||
{
|
||||
// Arrange
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Pending)
|
||||
.Create();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(friendship.Id))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
var wrongUserId = Guid.NewGuid(); // не совпадает с AddresseeId
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
|
||||
await _service.AcceptAsync(friendship.Id, wrongUserId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AcceptFriendRequestAsync_Throws_InvalidOperationException_IfFriendshipNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var requestId = Guid.NewGuid();
|
||||
var userId = Guid.NewGuid();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(requestId))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(requestId));
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
await _service.AcceptAsync(requestId, userId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AcceptFriendRequestAsync_ChangesStatusToAccepted()
|
||||
{
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Pending)
|
||||
.Create();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(friendship.Id))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
Friendship updatedFriendship = null;
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.UpdateAsync(It.IsAny<Friendship>()))
|
||||
.Callback<Friendship>(f => updatedFriendship = f)
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
await _service.AcceptAsync(friendship.Id, friendship.AddresseeId);
|
||||
|
||||
Assert.That(updatedFriendship.Status, Is.EqualTo(FriendshipStatus.Accepted));
|
||||
}
|
||||
|
||||
// RejectFriend
|
||||
[Test]
|
||||
public async Task RejectFriendRequestAsync_ValidRequest_CallsRemoveAsync()
|
||||
{
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Pending)
|
||||
.Create();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(friendship.Id))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
await _service.RejectAsync(friendship.Id, friendship.AddresseeId);
|
||||
|
||||
// Assert
|
||||
_friendshipsRepositoryMock.Verify(r => r.UpdateAsync(It.Is<Friendship>(f =>
|
||||
f.Id == friendship.Id &&
|
||||
f.RequesterId == friendship.RequesterId &&
|
||||
f.AddresseeId == friendship.AddresseeId &&
|
||||
f.Status == FriendshipStatus.Rejected
|
||||
)), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RejectFriendRequestAsync_Throws_InvalidOperationException_IfStatusNotEqualPendingOrReject()
|
||||
{
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Accepted)
|
||||
.Create();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(friendship.Id))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await _service.RejectAsync(friendship.Id, friendship.AddresseeId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RejectFriendRequestAsync_Throws_UnauthorizedAccessException_IfCurrentUserIdIsNotAddressee()
|
||||
{
|
||||
// Arrange
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Pending)
|
||||
.Create();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(friendship.Id))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
var wrongUserId = Guid.NewGuid(); // не совпадает с AddresseeId
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
|
||||
await _service.RejectAsync(friendship.Id, wrongUserId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RejectFriendRequestAsync_Throws_InvalidOperationException_IfFriendshipNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var requestId = Guid.NewGuid();
|
||||
var userId = Guid.NewGuid();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(requestId))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(requestId));
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
await _service.RejectAsync(requestId, userId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RejectFriendRequestAsync_ChangesStatusToAccepted()
|
||||
{
|
||||
var friendship = _fixture.Build<Friendship>()
|
||||
.With(f => f.Status, FriendshipStatus.Pending)
|
||||
.Create();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.GetByIdAsync(friendship.Id))
|
||||
.ReturnsAsync(friendship);
|
||||
|
||||
Friendship updatedFriendship = null;
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.UpdateAsync(It.IsAny<Friendship>()))
|
||||
.Callback<Friendship>(f => updatedFriendship = f)
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
await _service.RejectAsync(friendship.Id, friendship.AddresseeId);
|
||||
|
||||
Assert.That(updatedFriendship.Status, Is.EqualTo(FriendshipStatus.Rejected));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using AutoFixture;
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
using Govor.Application.Services.Friends;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Core.Repositories.Friendships;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.Application.Tests.Services.Friends;
|
||||
|
||||
[TestFixture]
|
||||
public class FriendRequestQueryServiceTests
|
||||
{
|
||||
private Fixture _fixture;
|
||||
private Mock<IUsersRepository> _usersRepositoryMock;
|
||||
private Mock<IFriendshipsRepository> _friendshipsRepositoryMock;
|
||||
private IFriendRequestQueryService _service;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors
|
||||
.OfType<ThrowingRecursionBehavior>()
|
||||
.ToList()
|
||||
.ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_usersRepositoryMock = new Mock<IUsersRepository>();
|
||||
_friendshipsRepositoryMock = new Mock<IFriendshipsRepository>();
|
||||
|
||||
_service = new FriendRequestQueryService(_friendshipsRepositoryMock.Object);
|
||||
}
|
||||
|
||||
// GetIncomingRequestsAsync
|
||||
[Test]
|
||||
public async Task GetIncomingRequestsAsync_ReturnsFriendships_IfFriendshipsExists()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
|
||||
var friendships = _fixture.CreateMany<Friendship>().ToList();
|
||||
|
||||
var user = _fixture.Build<User>()
|
||||
.With(u => u.Id, userId)
|
||||
.With(u => u.ReceivedFriendRequests, friendships)
|
||||
.Create();
|
||||
|
||||
friendships.ForEach(f =>
|
||||
{
|
||||
f.AddresseeId = userId;
|
||||
f.Addressee = user;
|
||||
f.Status = FriendshipStatus.Pending;
|
||||
});
|
||||
|
||||
_friendshipsRepositoryMock.Setup(f => f.FindByUserIdAsync(userId))
|
||||
.ReturnsAsync(friendships);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetIncomingAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Count, Is.EqualTo(friendships.Count));
|
||||
Assert.That(result.Select(u => u.Id), Is.EquivalentTo(friendships.Select(f => f.Id)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetIncomingRequestsAsync_ThrowsInvalidOperationException_WhenUserNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.FindByUserIdAsync(userId))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(userId));
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
await _service.GetIncomingAsync(userId));
|
||||
}
|
||||
|
||||
// GetResponsesAsync
|
||||
[Test]
|
||||
public async Task GetResponsesAsync_ReturnsFriendships_IfFriendshipsExists()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
|
||||
var friendships = _fixture.CreateMany<Friendship>().ToList();
|
||||
|
||||
var user = _fixture.Build<User>()
|
||||
.With(u => u.Id, userId)
|
||||
.With(u => u.ReceivedFriendRequests, friendships)
|
||||
.Create();
|
||||
|
||||
friendships.ForEach(f =>
|
||||
{
|
||||
f.RequesterId = userId;
|
||||
f.Requester = user;
|
||||
f.Status = FriendshipStatus.Rejected;
|
||||
});
|
||||
|
||||
_friendshipsRepositoryMock.Setup(f => f.FindByUserIdAsync(userId))
|
||||
.ReturnsAsync(friendships);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetResponsesAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Count, Is.EqualTo(friendships.Count));
|
||||
Assert.That(result.Select(u => u.Id), Is.EquivalentTo(friendships.Select(f => f.Id)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetResponsesAsync_ThrowsInvalidOperationException_WhenUserNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(r => r.FindByUserIdAsync(userId))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(userId));
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
await _service.GetResponsesAsync(userId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using AutoFixture;
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
using Govor.Application.Services.Friends;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Core.Repositories.Friendships;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.Application.Tests.Services.Friends;
|
||||
|
||||
[TestFixture]
|
||||
public class FriendshipServiceTests
|
||||
{
|
||||
private Fixture _fixture;
|
||||
private Mock<IUsersRepository> _usersRepositoryMock;
|
||||
private Mock<IFriendshipsRepository> _friendshipsRepositoryMock;
|
||||
IFriendshipService _service;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors
|
||||
.OfType<ThrowingRecursionBehavior>()
|
||||
.ToList()
|
||||
.ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_usersRepositoryMock = new Mock<IUsersRepository>();
|
||||
_friendshipsRepositoryMock = new Mock<IFriendshipsRepository>();
|
||||
|
||||
_service = new FriendshipService(_usersRepositoryMock.Object, _friendshipsRepositoryMock.Object);
|
||||
}
|
||||
|
||||
// SearchUsersAsync
|
||||
[Test]
|
||||
public async Task SearchUsersAsync_ReturnsUsers_IfNotAlreadyFriends()
|
||||
{
|
||||
// Arrange
|
||||
var userId = _fixture.Create<Guid>();
|
||||
var user = _fixture.Create<User>();
|
||||
user.Id = Guid.NewGuid();
|
||||
|
||||
_usersRepositoryMock
|
||||
.Setup(u => u.SearchPotentialFriendsAsync(userId, It.IsAny<string>()))
|
||||
.ReturnsAsync(new List<User> { user });
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(f => f.FindByUserIdAsync(userId))
|
||||
.ReturnsAsync(new List<Friendship>()); // No friends
|
||||
|
||||
// Act
|
||||
var result = await _service.SearchUsersAsync("test", userId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Has.Count.EqualTo(1));
|
||||
Assert.That(result[0].Id, Is.EqualTo(user.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SearchUsersAsync_Returns_EmptyList_IfThrowsNotFoundByKeyException_String_Guid()
|
||||
{
|
||||
// Arrange
|
||||
_usersRepositoryMock
|
||||
.Setup(u => u.SearchPotentialFriendsAsync(It.IsAny<Guid>(), It.IsAny<string>()))
|
||||
.ThrowsAsync(new NotFoundByKeyException<(string,Guid)>(("test",Guid.NewGuid()),"test"));
|
||||
|
||||
// Act
|
||||
var users = await _service.SearchUsersAsync("test", Guid.NewGuid());
|
||||
|
||||
// & Assert
|
||||
Assert.That(users, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SearchUsersAsync_ReturnsUsersWithoutFriendships_IfThrowsNotFoundByKeyException_Guid()
|
||||
{
|
||||
// Arrange
|
||||
var userId = _fixture.Create<Guid>();
|
||||
var user = _fixture.Create<User>();
|
||||
user.Id = Guid.NewGuid();
|
||||
|
||||
_usersRepositoryMock
|
||||
.Setup(u => u.SearchPotentialFriendsAsync(userId, It.IsAny<string>()))
|
||||
.ReturnsAsync(new List<User> { user });
|
||||
|
||||
_friendshipsRepositoryMock
|
||||
.Setup(f => f.FindByUserIdAsync(userId))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(userId, "test"));
|
||||
|
||||
// Act
|
||||
var result = await _service.SearchUsersAsync("test", userId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Has.Count.EqualTo(1));
|
||||
Assert.That(result[0].Id, Is.EqualTo(user.Id));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SearchUsersAsync_Throws_UnauthorizedAccessException_IfThrowsSomeExceptionInUsersRepository()
|
||||
{
|
||||
// Arrange
|
||||
_usersRepositoryMock
|
||||
.Setup(u => u.SearchPotentialFriendsAsync(It.IsAny<Guid>(), It.IsAny<string>()))
|
||||
.ThrowsAsync(new Exception("test"));
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<UnauthorizedAccessException>(async () => await _service.SearchUsersAsync("test", Guid.NewGuid()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SearchUsersAsync_Throws_UnauthorizedAccessException_IfThrowsSomeExceptionInFriendshipsRepository()
|
||||
{
|
||||
// Arrange
|
||||
_usersRepositoryMock
|
||||
.Setup(u => u.SearchPotentialFriendsAsync(It.IsAny<Guid>(), "test"))
|
||||
.ThrowsAsync(new Exception("test"));
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<UnauthorizedAccessException>(
|
||||
async () => await _service.SearchUsersAsync("test", Guid.NewGuid())
|
||||
);
|
||||
}
|
||||
|
||||
// GetFriendsAsync
|
||||
[Test]
|
||||
public async Task GetFriendsAsync_ReturnsUsers_IfUserExists()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var friendships = _fixture.CreateMany<Friendship>().ToList();
|
||||
|
||||
friendships.ForEach(f =>
|
||||
{
|
||||
f.RequesterId = userId;
|
||||
f.Status = FriendshipStatus.Accepted;
|
||||
});
|
||||
|
||||
_friendshipsRepositoryMock.Setup(f => f.FindByUserIdAsync(userId))
|
||||
.ReturnsAsync(friendships);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetFriendsAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.Count, Is.EqualTo(friendships.Count));
|
||||
Assert.That(result, Is.EquivalentTo(friendships.Select(f => f.Addressee)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetFriendsAsync_Throws_InvalidOperationException_IfUserNotExists()
|
||||
{
|
||||
// Arrange
|
||||
_friendshipsRepositoryMock.Setup(f => f.FindByUserIdAsync(It.IsAny<Guid>()))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(Guid.NewGuid()));
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await _service.GetFriendsAsync(Guid.NewGuid()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
using Govor.Application.Services.Medias;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Messages;
|
||||
using Govor.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Govor.Application.Tests.Services.Medias;
|
||||
|
||||
[TestFixture]
|
||||
public class AccesserToDownloadMediaServiceTests
|
||||
{
|
||||
private GovorDbContext _dbContext = null!;
|
||||
private AccesserToDownloadMediaService _accesser = null!;
|
||||
private Guid _userId;
|
||||
private Guid _otherUserId;
|
||||
private Guid _groupId;
|
||||
private Guid _mediaFileId;
|
||||
|
||||
[SetUp]
|
||||
public async Task SetUp()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<GovorDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
|
||||
_dbContext = new GovorDbContext(options);
|
||||
_accesser = new AccesserToDownloadMediaService(_dbContext);
|
||||
|
||||
_userId = Guid.NewGuid();
|
||||
_otherUserId = Guid.NewGuid();
|
||||
_groupId = Guid.NewGuid();
|
||||
_mediaFileId = Guid.NewGuid();
|
||||
|
||||
// ������� ��������� � �����
|
||||
var message = new Message
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
SenderId = _userId,
|
||||
RecipientId = _otherUserId,
|
||||
RecipientType = RecipientType.User
|
||||
};
|
||||
|
||||
var media = new MediaFile
|
||||
{
|
||||
Id = _mediaFileId,
|
||||
Url = "/media/test.png",
|
||||
MineType = "image/png",
|
||||
MediaType = MediaType.Image,
|
||||
UploaderId = _userId,
|
||||
DateCreated = DateTime.UtcNow,
|
||||
OwnerType = MediaOwnerType.Message,
|
||||
OwnerId = message.Id
|
||||
};
|
||||
|
||||
var attachment = new MediaAttachments
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
MediaFileId = _mediaFileId,
|
||||
MessageId = message.Id,
|
||||
Message = message,
|
||||
MediaFile = media
|
||||
};
|
||||
|
||||
await _dbContext.Messages.AddAsync(message);
|
||||
await _dbContext.MediaFiles.AddAsync(media);
|
||||
await _dbContext.MediaAttachments.AddAsync(attachment);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HasAccessAsync_ReturnsTrue_ForSender()
|
||||
{
|
||||
var result = await _accesser.HasAccessAsync(_mediaFileId, _userId);
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HasAccessAsync_ReturnsTrue_ForRecipient()
|
||||
{
|
||||
var result = await _accesser.HasAccessAsync(_mediaFileId, _otherUserId);
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HasAccessAsync_ReturnsFalse_ForUnrelatedUser()
|
||||
{
|
||||
var unrelatedUserId = Guid.NewGuid();
|
||||
var result = await _accesser.HasAccessAsync(_mediaFileId, unrelatedUserId);
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HasAccessAsync_ReturnsTrue_ForGroupMember()
|
||||
{
|
||||
var groupMediaId = Guid.NewGuid();
|
||||
|
||||
var groupMessage = new Message
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
SenderId = _userId,
|
||||
RecipientId = _groupId,
|
||||
RecipientType = RecipientType.Group
|
||||
};
|
||||
|
||||
var media = new MediaFile
|
||||
{
|
||||
Id = groupMediaId,
|
||||
Url = "/media/group.png",
|
||||
MineType = "image/png",
|
||||
MediaType = MediaType.Image,
|
||||
UploaderId = _userId,
|
||||
DateCreated = DateTime.UtcNow,
|
||||
OwnerType = MediaOwnerType.Message,
|
||||
OwnerId = groupMessage.Id
|
||||
};
|
||||
|
||||
var attachment = new MediaAttachments
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
MediaFileId = groupMediaId,
|
||||
MessageId = groupMessage.Id,
|
||||
Message = groupMessage,
|
||||
MediaFile = media
|
||||
};
|
||||
|
||||
var membership = new GroupMembership
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
GroupId = _groupId,
|
||||
UserId = _otherUserId
|
||||
};
|
||||
|
||||
await _dbContext.Messages.AddAsync(groupMessage);
|
||||
await _dbContext.MediaFiles.AddAsync(media);
|
||||
await _dbContext.MediaAttachments.AddAsync(attachment);
|
||||
await _dbContext.GroupMemberships.AddAsync(membership);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
var result = await _accesser.HasAccessAsync(groupMediaId, _otherUserId);
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HasAccessAsync_ReturnsFalse_IfMediaNotAttached()
|
||||
{
|
||||
var result = await _accesser.HasAccessAsync(Guid.NewGuid(), _userId);
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HasAccessAsync_ReturnsTrue_ForUserAvatar()
|
||||
{
|
||||
var avatarMedia = new MediaFile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Url = "/media/avatar.png",
|
||||
MineType = "image/png",
|
||||
MediaType = MediaType.Image,
|
||||
UploaderId = _userId,
|
||||
OwnerType = MediaOwnerType.Avatar,
|
||||
OwnerId = _userId,
|
||||
DateCreated = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _dbContext.MediaFiles.AddAsync(avatarMedia);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
var result = await _accesser.HasAccessAsync(avatarMedia.Id, _userId);
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HasAccessAsync_ReturnsTrue_ForOtherUserAvatar()
|
||||
{
|
||||
var avatarMedia = new MediaFile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Url = "/media/avatar2.png",
|
||||
MineType = "image/png",
|
||||
MediaType = MediaType.Image,
|
||||
UploaderId = _otherUserId,
|
||||
OwnerType = MediaOwnerType.Avatar,
|
||||
OwnerId = _otherUserId,
|
||||
DateCreated = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _dbContext.MediaFiles.AddAsync(avatarMedia);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
var result = await _accesser.HasAccessAsync(avatarMedia.Id, _userId);
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HasAccessAsync_ReturnsTrue_ForGroupAvatarMember()
|
||||
{
|
||||
var groupAvatar = new MediaFile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Url = "/media/group_avatar.png",
|
||||
MineType = "image/png",
|
||||
MediaType = MediaType.Image,
|
||||
UploaderId = _userId,
|
||||
OwnerType = MediaOwnerType.GroupAvatar,
|
||||
OwnerId = _groupId,
|
||||
DateCreated = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var membership = new GroupMembership
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
GroupId = _groupId,
|
||||
UserId = _otherUserId
|
||||
};
|
||||
|
||||
await _dbContext.MediaFiles.AddAsync(groupAvatar);
|
||||
await _dbContext.GroupMemberships.AddAsync(membership);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
var result = await _accesser.HasAccessAsync(groupAvatar.Id, _otherUserId);
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HasAccessAsync_ReturnsFalse_ForGroupAvatarNonMember()
|
||||
{
|
||||
var groupAvatar = new MediaFile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Url = "/media/group_avatar2.png",
|
||||
MineType = "image/png",
|
||||
MediaType = MediaType.Image,
|
||||
UploaderId = _userId,
|
||||
OwnerType = MediaOwnerType.GroupAvatar,
|
||||
OwnerId = _groupId,
|
||||
DateCreated = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _dbContext.MediaFiles.AddAsync(groupAvatar);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
var unrelatedUserId = Guid.NewGuid();
|
||||
var result = await _accesser.HasAccessAsync(groupAvatar.Id, unrelatedUserId);
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HasAccessAsync_ReturnsTrue_ForSystemMedia()
|
||||
{
|
||||
var systemMedia = new MediaFile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Url = "/media/system.png",
|
||||
MineType = "image/png",
|
||||
MediaType = MediaType.Image,
|
||||
UploaderId = _userId,
|
||||
OwnerType = MediaOwnerType.System,
|
||||
DateCreated = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _dbContext.MediaFiles.AddAsync(systemMedia);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
var result = await _accesser.HasAccessAsync(systemMedia.Id, Guid.NewGuid());
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task HasAccessAsync_ReturnsFalse_ForUnknownOwnerType()
|
||||
{
|
||||
var unknownMedia = new MediaFile
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Url = "/media/unknown.png",
|
||||
MineType = "image/png",
|
||||
MediaType = MediaType.Image,
|
||||
UploaderId = _userId,
|
||||
OwnerType = (MediaOwnerType)999, // ����������� ���
|
||||
DateCreated = DateTime.UtcNow
|
||||
};
|
||||
|
||||
await _dbContext.MediaFiles.AddAsync(unknownMedia);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
var result = await _accesser.HasAccessAsync(unknownMedia.Id, _userId);
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_dbContext.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
using Govor.Application.Exceptions.VerifyFriendship;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Interfaces.Medias;
|
||||
using Govor.Application.Interfaces.Messages.Parameters;
|
||||
using Govor.Application.Services.Messages;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Messages;
|
||||
using Govor.Core.Repositories.Groups;
|
||||
using Govor.Core.Repositories.Messages;
|
||||
using Govor.Core.Repositories.PrivateChats;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.Application.Tests.Services.Messages;
|
||||
|
||||
[TestFixture]
|
||||
public class MessageCommandServiceTests
|
||||
{
|
||||
private Mock<IMessagesRepository> _mockMessagesRepo;
|
||||
private Mock<IUsersRepository> _mockUsersRepo;
|
||||
private Mock<IGroupsRepository> _mockGroupsRepo;
|
||||
private Mock<IVerifyFriendship> _mockVerifyFriendship;
|
||||
private Mock<IPrivateChatsRepository> _mockPrivateChats;
|
||||
private Mock<IMediaService> _mockMediaService;
|
||||
private Mock<ILogger<MessageCommandService>> _mockLogger;
|
||||
private MessageCommandService _messageService;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_mockMessagesRepo = new Mock<IMessagesRepository>();
|
||||
_mockUsersRepo = new Mock<IUsersRepository>();
|
||||
_mockGroupsRepo = new Mock<IGroupsRepository>();
|
||||
_mockVerifyFriendship = new Mock<IVerifyFriendship>();
|
||||
_mockPrivateChats = new Mock<IPrivateChatsRepository>();
|
||||
_mockMediaService = new Mock<IMediaService>();
|
||||
_mockLogger = new Mock<ILogger<MessageCommandService>>();
|
||||
|
||||
_messageService = new MessageCommandService(
|
||||
_mockMessagesRepo.Object,
|
||||
_mockUsersRepo.Object,
|
||||
_mockGroupsRepo.Object,
|
||||
_mockVerifyFriendship.Object,
|
||||
_mockPrivateChats.Object,
|
||||
_mockMediaService.Object,
|
||||
_mockLogger.Object);
|
||||
}
|
||||
|
||||
|
||||
// Test for SendMessageAsync action
|
||||
[Test]
|
||||
public async Task SendMessageAsync_ToUser_Success()
|
||||
{
|
||||
// Arrange
|
||||
var senderId = Guid.NewGuid();
|
||||
var recipientId = Guid.NewGuid();
|
||||
var sendMessageParams = new SendMessage("Hello",
|
||||
null,
|
||||
recipientId,
|
||||
RecipientType.User,
|
||||
senderId,
|
||||
DateTime.UtcNow,
|
||||
new List<SendMedia>());
|
||||
|
||||
_mockUsersRepo.Setup(r => r.ExistsByIdAsync(recipientId)).ReturnsAsync(true);
|
||||
_mockVerifyFriendship.Setup(v => v.VerifyAsync(senderId, recipientId)).Returns(Task.CompletedTask);
|
||||
_mockMessagesRepo.Setup(r => r.AddAsync(It.IsAny<Message>())).Returns(Task.CompletedTask);
|
||||
_mockPrivateChats.Setup(c => c.Exist(senderId, recipientId)).Returns(true);
|
||||
_mockPrivateChats.Setup(c => c.GetByMembersAsync(senderId, recipientId)).ReturnsAsync(new PrivateChat(){Id = recipientId});
|
||||
|
||||
// Act
|
||||
var result = await _messageService.SendMessageAsync(sendMessageParams);
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.IsSuccess, Is.True);
|
||||
Assert.That(result.Exception, Is.Null);
|
||||
|
||||
_mockMessagesRepo.Verify(r => r.AddAsync(It.Is<Message>(m =>
|
||||
m.SenderId == senderId &&
|
||||
m.RecipientId == recipientId &&
|
||||
m.RecipientType == RecipientType.User &&
|
||||
m.EncryptedContent == "Hello")), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SendMessageAsync_ToUser_When_AttachMediaThrowsException_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var senderId = Guid.NewGuid();
|
||||
var recipientId = Guid.NewGuid();
|
||||
var sendMediaId = Guid.NewGuid();
|
||||
|
||||
var sendMessageParams = new SendMessage(
|
||||
"Hello",
|
||||
null,
|
||||
recipientId,
|
||||
RecipientType.User,
|
||||
senderId,
|
||||
DateTime.UtcNow,
|
||||
new List<SendMedia> { new SendMedia(sendMediaId, string.Empty) });
|
||||
|
||||
_mockUsersRepo.Setup(r => r.ExistsByIdAsync(recipientId)).ReturnsAsync(true);
|
||||
_mockVerifyFriendship.Setup(v => v.VerifyAsync(senderId, recipientId)).Returns(Task.CompletedTask);
|
||||
_mockMessagesRepo.Setup(r => r.AddAsync(It.IsAny<Message>())).Returns(Task.CompletedTask);
|
||||
_mockPrivateChats.Setup(c => c.Exist(senderId, recipientId)).Returns(true);
|
||||
_mockPrivateChats.Setup(c => c.GetByMembersAsync(senderId, recipientId))
|
||||
.ReturnsAsync(new PrivateChat { Id = recipientId });
|
||||
|
||||
// !
|
||||
_mockMediaService
|
||||
.Setup(m => m.AttachToMessageAsync(sendMediaId, It.IsAny<Guid>()))
|
||||
.ThrowsAsync(new Exception("Unexpected DB error"));
|
||||
|
||||
// Act
|
||||
var result = await _messageService.SendMessageAsync(sendMessageParams);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.IsSuccess, Is.False);
|
||||
Assert.That(result.Exception, Is.Not.Null);
|
||||
Assert.That(result.Exception.Message, Is.EqualTo("Unexpected DB error"));
|
||||
|
||||
// Проверяем, что сообщение не было сохранено
|
||||
_mockMessagesRepo.Verify(r => r.AddAsync(It.IsAny<Message>()), Times.Never);
|
||||
|
||||
// Проверяем, что метод прикрепления медиа вызывался
|
||||
_mockMediaService.Verify(m => m.AttachToMessageAsync(sendMediaId, It.IsAny<Guid>()), Times.Once);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public async Task SendMessageAsync_ToGroup_Success()
|
||||
{
|
||||
// Arrange
|
||||
var senderId = Guid.NewGuid();
|
||||
var groupId = Guid.NewGuid();
|
||||
|
||||
var sendMessageParams = new SendMessage("Hello Group",
|
||||
null,
|
||||
groupId,
|
||||
RecipientType.Group,
|
||||
senderId,
|
||||
DateTime.UtcNow,
|
||||
new List<SendMedia>());
|
||||
|
||||
_mockGroupsRepo.Setup(r => r.Exist(groupId)).Returns(true);
|
||||
_mockGroupsRepo.Setup(r => r.IsUserMemberOfGroupAsync(senderId, groupId)).ReturnsAsync(true);
|
||||
_mockMessagesRepo.Setup(r => r.AddAsync(It.IsAny<Message>())).Returns(Task.CompletedTask);
|
||||
|
||||
// Act
|
||||
var result = await _messageService.SendMessageAsync(sendMessageParams);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.IsSuccess, Is.True);
|
||||
Assert.That(result.Exception, Is.Null);
|
||||
_mockMessagesRepo.Verify(r => r.AddAsync(It.Is<Message>(m =>
|
||||
m.RecipientId == groupId &&
|
||||
m.RecipientType == RecipientType.Group)), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SendMessageAsync_ToUser_RecipientNotFound_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var senderId = Guid.NewGuid();
|
||||
var recipientId = Guid.NewGuid();
|
||||
|
||||
var sendMessageParams = new SendMessage("Hello",
|
||||
null,
|
||||
recipientId,
|
||||
RecipientType.User,
|
||||
senderId,
|
||||
DateTime.UtcNow,
|
||||
new List<SendMedia>());
|
||||
|
||||
_mockUsersRepo.Setup(r => r.ExistsByIdAsync(recipientId)).ReturnsAsync(false);
|
||||
|
||||
// Act
|
||||
var result = await _messageService.SendMessageAsync(sendMessageParams);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.IsSuccess, Is.False);
|
||||
Assert.That(result.Exception, Is.Not.Null);
|
||||
Assert.That(result.Exception,Is.TypeOf<KeyNotFoundException>());
|
||||
Assert.That(result.Message, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SendMessageAsync_ToUser_FriendshipVerificationFails_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var senderId = Guid.NewGuid();
|
||||
var recipientId = Guid.NewGuid();
|
||||
|
||||
var sendMessageParams = new SendMessage("Hello",
|
||||
null,
|
||||
recipientId,
|
||||
RecipientType.User,
|
||||
senderId,
|
||||
DateTime.UtcNow,
|
||||
new List<SendMedia>()
|
||||
);
|
||||
|
||||
_mockUsersRepo.Setup(r => r.ExistsByIdAsync(recipientId)).ReturnsAsync(true);
|
||||
_mockVerifyFriendship.Setup(v => v.VerifyAsync(senderId, recipientId)).ThrowsAsync(new FriendshipException("Not friends"));
|
||||
|
||||
// Act
|
||||
var result = await _messageService.SendMessageAsync(sendMessageParams);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.IsSuccess, Is.False);
|
||||
Assert.That(result.Exception, Is.Not.Null);
|
||||
Assert.That(result.Exception,Is.TypeOf<FriendshipException>());
|
||||
Assert.That(result.Message, Is.Null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Test for EditMessageAsync action
|
||||
[Test]
|
||||
public async Task EditMessageAsync_Success()
|
||||
{
|
||||
// Arrange
|
||||
var editorId = Guid.NewGuid();
|
||||
var messageId = Guid.NewGuid();
|
||||
|
||||
var originalMessage = new Message
|
||||
{
|
||||
Id = messageId,
|
||||
SenderId = editorId,
|
||||
EncryptedContent = "Old",
|
||||
RecipientId = Guid.NewGuid(),
|
||||
RecipientType = RecipientType.User
|
||||
};
|
||||
|
||||
var editParams = new EditMessage(editorId, messageId, "New Content", DateTime.UtcNow);
|
||||
|
||||
_mockMessagesRepo.Setup(r => r.FindByIdAsync(messageId)).ReturnsAsync(originalMessage);
|
||||
_mockMessagesRepo.Setup(r => r.UpdateAsync(It.IsAny<Message>())).Returns(Task.CompletedTask);
|
||||
|
||||
// Act
|
||||
var result = await _messageService.EditMessageAsync(editParams);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.IsSuccess, Is.True);
|
||||
Assert.That(result.OriginalMessage, Is.Not.Null);
|
||||
|
||||
Assert.That(messageId, Is.EqualTo(result.OriginalMessage!.Id));
|
||||
|
||||
_mockMessagesRepo.Verify(r => r.UpdateAsync(It.Is<Message>(m =>
|
||||
m.Id == messageId &&
|
||||
m.EncryptedContent == "New Content" &&
|
||||
m.IsEdited == true &&
|
||||
m.EditedAt == editParams.EditedAt)), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task EditMessageAsync_MessageNotFound_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var editorId = Guid.NewGuid();
|
||||
var messageId = Guid.NewGuid();
|
||||
var editParams = new EditMessage(editorId, messageId, "New Content", DateTime.UtcNow);
|
||||
|
||||
_mockMessagesRepo.Setup(r => r.FindByIdAsync(messageId)).
|
||||
ThrowsAsync(new NotFoundByKeyException<Guid>(messageId));
|
||||
|
||||
// Act
|
||||
var result = await _messageService.EditMessageAsync(editParams);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.IsSuccess, Is.False);
|
||||
Assert.That(result.Exception, Is.Not.Null);
|
||||
Assert.That(result.Exception,Is.TypeOf<NotFoundByKeyException<Guid>>());
|
||||
Assert.That(result.OriginalMessage, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task EditMessageAsync_NotSender_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var editorId = Guid.NewGuid();
|
||||
var senderId = Guid.NewGuid(); // Different from editorId
|
||||
var messageId = Guid.NewGuid();
|
||||
var originalMessage = new Message { Id = messageId, SenderId = senderId, EncryptedContent = "Old" };
|
||||
var editParams = new EditMessage(editorId, messageId, "New Content", DateTime.UtcNow);
|
||||
|
||||
_mockMessagesRepo.Setup(r => r.FindByIdAsync(messageId)).ReturnsAsync(originalMessage);
|
||||
|
||||
// Act
|
||||
var result = await _messageService.EditMessageAsync(editParams);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.IsSuccess, Is.False);
|
||||
Assert.That(result.Exception, Is.Not.Null);
|
||||
Assert.That(result.Exception,Is.TypeOf<UnauthorizedAccessException>());
|
||||
Assert.That(result.OriginalMessage, Is.Null);
|
||||
}
|
||||
|
||||
// Test for DeleteMessageAsync action
|
||||
[Test]
|
||||
public async Task DeleteMessageAsync_Success()
|
||||
{
|
||||
// Arrange
|
||||
var deleterId = Guid.NewGuid();
|
||||
var messageId = Guid.NewGuid();
|
||||
var originalMessage = new Message { Id = messageId, SenderId = deleterId, RecipientId = Guid.NewGuid(), RecipientType = RecipientType.User };
|
||||
var deleteParams = new DeleteMessage(deleterId, messageId);
|
||||
|
||||
_mockMessagesRepo.Setup(r => r.FindByIdAsync(messageId)).ReturnsAsync(originalMessage);
|
||||
_mockMessagesRepo.Setup(r => r.RemoveAsync(messageId)).Returns(Task.CompletedTask);
|
||||
|
||||
// Act
|
||||
var result = await _messageService.DeleteMessageAsync(deleteParams);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.IsSuccess, Is.True);
|
||||
Assert.That(result.OriginalMessage, Is.Not.Null);
|
||||
Assert.That(messageId, Is.EqualTo(result.OriginalMessage!.Id));
|
||||
_mockMessagesRepo.Verify(r => r.RemoveAsync(messageId), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DeleteMessageAsync_MessageNotFound_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var deleterId = Guid.NewGuid();
|
||||
var messageId = Guid.NewGuid();
|
||||
var deleteParams = new DeleteMessage(deleterId, messageId);
|
||||
|
||||
_mockMessagesRepo.Setup(r => r.FindByIdAsync(messageId)).
|
||||
ThrowsAsync(new NotFoundByKeyException<Guid>(messageId));
|
||||
|
||||
// Act
|
||||
var result = await _messageService.DeleteMessageAsync(deleteParams);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.IsSuccess, Is.False);
|
||||
Assert.That(result.Exception, Is.Not.Null);
|
||||
Assert.That(result.Exception,Is.TypeOf<KeyNotFoundException>());
|
||||
Assert.That(result.OriginalMessage, Is.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DeleteMessageAsync_NotSender_ReturnsFailure()
|
||||
{
|
||||
// Arrange
|
||||
var deleterId = Guid.NewGuid();
|
||||
var senderId = Guid.NewGuid(); // Different
|
||||
var messageId = Guid.NewGuid();
|
||||
var originalMessage = new Message { Id = messageId, SenderId = senderId };
|
||||
var deleteParams = new DeleteMessage(deleterId, messageId);
|
||||
|
||||
_mockMessagesRepo.Setup(r => r.FindByIdAsync(messageId)).ReturnsAsync(originalMessage);
|
||||
|
||||
// Act
|
||||
var result = await _messageService.DeleteMessageAsync(deleteParams);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.IsSuccess, Is.False);
|
||||
Assert.That(result.Exception, Is.Not.Null);
|
||||
Assert.That(result.Exception,Is.TypeOf<UnauthorizedAccessException>());
|
||||
Assert.That(result.OriginalMessage, Is.Null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Services.Messages;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Messages;
|
||||
using Govor.Core.Repositories.Groups;
|
||||
using Govor.Core.Repositories.PrivateChats;
|
||||
using Govor.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.Application.Tests.Services.Messages;
|
||||
|
||||
[TestFixture]
|
||||
public class MessagesLoaderTests
|
||||
{
|
||||
private IMessagesLoader _loader;
|
||||
private Mock<IPrivateChatsRepository> _privateChatsRepoMock;
|
||||
private Mock<IGroupsRepository> _groupsRepoMock;
|
||||
private GovorDbContext _dbContext;
|
||||
|
||||
private Guid _currentUserId = Guid.NewGuid();
|
||||
private Guid _otherUserId = Guid.NewGuid();
|
||||
private Guid _groupChatId = Guid.NewGuid();
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_privateChatsRepoMock = new Mock<IPrivateChatsRepository>();
|
||||
_groupsRepoMock = new Mock<IGroupsRepository>();
|
||||
|
||||
var options = new DbContextOptionsBuilder<GovorDbContext>()
|
||||
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
_dbContext = new GovorDbContext(options);
|
||||
|
||||
_loader = new MessagesLoader(_groupsRepoMock.Object, _privateChatsRepoMock.Object, _dbContext);
|
||||
}
|
||||
|
||||
// Tests for LoadLastMessagesInUserChat action
|
||||
[Test]
|
||||
public async Task LoadLastMessagesInUserChat_ReturnsMessages_WhenChatExists()
|
||||
{
|
||||
// Arrange
|
||||
var chatId = Guid.NewGuid();
|
||||
|
||||
_privateChatsRepoMock.Setup(r => r.Exist(_currentUserId, _otherUserId)).Returns(true);
|
||||
_privateChatsRepoMock.Setup(r => r.GetByMembersAsync(_currentUserId, _otherUserId))
|
||||
.ReturnsAsync(new PrivateChat() { Id = chatId });
|
||||
|
||||
var messages = new List<Message>
|
||||
{
|
||||
new() { Id = Guid.NewGuid(), RecipientId = chatId, RecipientType = RecipientType.User }
|
||||
};
|
||||
|
||||
await _dbContext.Messages.AddRangeAsync(messages);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
var result = await _loader.LoadMessagesInUserChat(_currentUserId, _otherUserId, null);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Has.Count.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task LoadLastMessagesInUserChat_ReturnsEmpty_WhenNoMessages()
|
||||
{
|
||||
// Arrange
|
||||
_privateChatsRepoMock.Setup(r => r.Exist(_currentUserId, _otherUserId)).Returns(true);
|
||||
_privateChatsRepoMock.Setup(r => r.GetByMembersAsync(_currentUserId, _otherUserId))
|
||||
.ReturnsAsync(new PrivateChat { Id = Guid.NewGuid() });
|
||||
// Act
|
||||
var result = await _loader.LoadMessagesInUserChat(_currentUserId, _otherUserId, null);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LoadLastMessagesInUserChat_Throws_WhenUserIdIsEmpty()
|
||||
{
|
||||
// Act & Assert
|
||||
var ex = Assert.ThrowsAsync<ArgumentException>(async () =>
|
||||
await _loader.LoadMessagesInUserChat(Guid.Empty, _currentUserId, null));
|
||||
|
||||
Assert.That(ex.Message, Does.Contain("User id cannot be empty"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LoadLastMessagesInUserChat_Throws_WhenChatNotExists()
|
||||
{
|
||||
// Arrange
|
||||
_privateChatsRepoMock.Setup(r => r.Exist(It.IsAny<Guid>(), It.IsAny<Guid>())).Returns(false);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
await _loader.LoadMessagesInUserChat(_currentUserId, _otherUserId, null));
|
||||
|
||||
Assert.That(ex.Message, Is.EqualTo("Private chat not found"));
|
||||
}
|
||||
|
||||
// Tests for LoadLastMessagesInChatGroup action
|
||||
[Test]
|
||||
public async Task LoadLastMessagesInChatGroup_ReturnsMessages_WhenUserIsMember()
|
||||
{
|
||||
// Arrange
|
||||
_groupsRepoMock.Setup(g => g.IsUserMemberOfGroupAsync(_currentUserId, _groupChatId))
|
||||
.ReturnsAsync(true);
|
||||
|
||||
var messages = new List<Message>
|
||||
{
|
||||
new() { Id = Guid.NewGuid(), RecipientId = _groupChatId, RecipientType = RecipientType.Group }
|
||||
};
|
||||
|
||||
await _dbContext.Messages.AddRangeAsync(messages);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
// Act
|
||||
var result = await _loader.LoadMessagesInChatGroup(_groupChatId, _currentUserId, null);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Has.Count.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LoadLastMessagesInChatGroup_Throws_WhenChatIdEmpty()
|
||||
{
|
||||
// Act & Assert
|
||||
var ex = Assert.ThrowsAsync<ArgumentException>(async () =>
|
||||
await _loader.LoadMessagesInChatGroup(Guid.Empty, _currentUserId, null));
|
||||
|
||||
Assert.That(ex.Message, Is.EqualTo("Chat id cannot be empty"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LoadLastMessagesInChatGroup_Throws_WhenUserNotInGroup()
|
||||
{
|
||||
// Arrange
|
||||
_groupsRepoMock.Setup(g => g.IsUserMemberOfGroupAsync(_currentUserId, _groupChatId))
|
||||
.ReturnsAsync(false);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
|
||||
await _loader.LoadMessagesInChatGroup(_groupChatId, _currentUserId, null));
|
||||
|
||||
Assert.That(ex.Message, Is.EqualTo("You are not a member of this group."));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task LoadLastMessagesInChatGroup_ReturnsEmpty_WhenNoMessages()
|
||||
{
|
||||
// Arrange
|
||||
_groupsRepoMock.Setup(g => g.IsUserMemberOfGroupAsync(_currentUserId, _groupChatId))
|
||||
.ReturnsAsync(true);
|
||||
|
||||
// Act
|
||||
var result = await _loader.LoadMessagesInChatGroup(_groupChatId, _currentUserId, null);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_dbContext.Dispose();
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using AutoFixture;
|
||||
using Govor.Application.Services;
|
||||
using Govor.Application.Services.Authentication;
|
||||
using Govor.Core.Infrastructure.Extensions;
|
||||
|
||||
namespace Govor.API.Tests.UnitTests.Services;
|
||||
namespace Govor.Application.Tests.Services;
|
||||
|
||||
[TestFixture]
|
||||
public class PasswordHasherTests
|
||||
@@ -0,0 +1,65 @@
|
||||
using Govor.Application.Services;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace Govor.Application.Tests.Services;
|
||||
|
||||
[TestFixture]
|
||||
public class PingHandlerServiceTests
|
||||
{
|
||||
private GovorDbContext _dbContext = null!;
|
||||
private IMemoryCache _memoryCache = null!;
|
||||
private PingHandlerService _service = null!;
|
||||
private Guid _userId;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<GovorDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
|
||||
_dbContext = new GovorDbContext(options);
|
||||
_memoryCache = new MemoryCache(new MemoryCacheOptions());
|
||||
_service = new PingHandlerService(_dbContext, _memoryCache);
|
||||
|
||||
_userId = Guid.NewGuid();
|
||||
_dbContext.Users.Add(new User
|
||||
{
|
||||
Id = _userId,
|
||||
Username = "TestUser",
|
||||
WasOnline = DateTime.UtcNow.AddHours(-1),
|
||||
Description = "Test description",
|
||||
PasswordHash = "hashed_password_here"
|
||||
});
|
||||
_dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Ping_DoesNotUpdate_WhenPingTooRecent()
|
||||
{
|
||||
// Arrange
|
||||
var initial = DateTime.UtcNow.AddMinutes(-1);
|
||||
_memoryCache.Set($"LastPing_{_userId}", DateTime.UtcNow);
|
||||
|
||||
var user = await _dbContext.Users.FirstAsync(u => u.Id == _userId);
|
||||
var originalTime = user.WasOnline;
|
||||
|
||||
// Act
|
||||
await _service.Ping(_userId);
|
||||
|
||||
var updatedUser = await _dbContext.Users.FirstAsync(u => u.Id == _userId);
|
||||
|
||||
// Assert
|
||||
Assert.That(updatedUser.WasOnline, Is.EqualTo(originalTime));
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
_dbContext?.Dispose();
|
||||
_memoryCache?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
using Govor.Application.Services;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.Application.Tests.Services;
|
||||
|
||||
[TestFixture]
|
||||
public class ProfileServiceTests
|
||||
{
|
||||
private Mock<IUsersRepository> _mockUsersRepo = null!;
|
||||
private Mock<ILogger<ProfileService>> _mockLogger = null!;
|
||||
private ProfileService _service = null!;
|
||||
private User _testUser = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_mockUsersRepo = new Mock<IUsersRepository>();
|
||||
_mockLogger = new Mock<ILogger<ProfileService>>();
|
||||
|
||||
_service = new ProfileService(_mockUsersRepo.Object, _mockLogger.Object);
|
||||
|
||||
_testUser = new User
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Username = "testuser",
|
||||
Description = "old description",
|
||||
IconId = Guid.NewGuid()
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- GetUserProfileAsync ----------
|
||||
|
||||
[Test]
|
||||
public async Task GetUserProfileAsync_ReturnsCorrectData()
|
||||
{
|
||||
// Arrange
|
||||
_mockUsersRepo.Setup(r => r.FindByIdAsync(_testUser.Id))
|
||||
.ReturnsAsync(_testUser);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetUserProfileAsync(_testUser.Id);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.Id, Is.EqualTo(_testUser.Id));
|
||||
Assert.That(result.Username, Is.EqualTo("testuser"));
|
||||
Assert.That(result.Description, Is.EqualTo("old description"));
|
||||
Assert.That(result.IconId, Is.EqualTo(_testUser.IconId));
|
||||
|
||||
_mockUsersRepo.Verify(r => r.FindByIdAsync(_testUser.Id), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUserProfileAsync_Throws_WhenUserNotFound()
|
||||
{
|
||||
// Arrange
|
||||
_mockUsersRepo.Setup(r => r.FindByIdAsync(It.IsAny<Guid>()))
|
||||
.ReturnsAsync((User)null!);
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsAsync<NullReferenceException>(() =>
|
||||
_service.GetUserProfileAsync(Guid.NewGuid()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetUserProfileAsync_ThrowsNotFoundException_WhenNotFoundByKeyException()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
_mockUsersRepo.Setup(r => r.FindByIdAsync(It.IsAny<Guid>()))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(userId));
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
_service.GetUserProfileAsync(userId));
|
||||
}
|
||||
// ---------- SetDescription ----------
|
||||
|
||||
[Test]
|
||||
public async Task SetDescription_UpdatesUserDescription()
|
||||
{
|
||||
// Arrange
|
||||
var newDescription = "new cool description";
|
||||
_mockUsersRepo.Setup(r => r.FindByIdAsync(_testUser.Id))
|
||||
.ReturnsAsync(_testUser);
|
||||
_mockUsersRepo.Setup(r => r.UpdateAsync(It.IsAny<User>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
// Act
|
||||
await _service.SetDescription(newDescription, _testUser.Id);
|
||||
|
||||
// Assert
|
||||
Assert.That(_testUser.Description, Is.EqualTo(newDescription));
|
||||
_mockUsersRepo.Verify(r => r.UpdateAsync(It.Is<User>(u => u.Description == newDescription)), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetDescription_Throws_WhenUserNotFound()
|
||||
{
|
||||
// Arrange
|
||||
_mockUsersRepo.Setup(r => r.FindByIdAsync(It.IsAny<Guid>()))
|
||||
.ReturnsAsync((User)null!);
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsAsync<NullReferenceException>(() =>
|
||||
_service.SetDescription("desc", Guid.NewGuid()));
|
||||
}
|
||||
|
||||
// ---------- SetNewIcon ----------
|
||||
|
||||
[Test]
|
||||
public async Task SetNewIcon_UpdatesUserIconId()
|
||||
{
|
||||
// Arrange
|
||||
var newIconId = Guid.NewGuid();
|
||||
_mockUsersRepo.Setup(r => r.FindByIdAsync(_testUser.Id))
|
||||
.ReturnsAsync(_testUser);
|
||||
_mockUsersRepo.Setup(r => r.UpdateAsync(It.IsAny<User>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
// Act
|
||||
await _service.SetNewIcon(_testUser.Id, newIconId);
|
||||
|
||||
// Assert
|
||||
Assert.That(_testUser.IconId, Is.EqualTo(newIconId));
|
||||
_mockUsersRepo.Verify(r => r.UpdateAsync(It.Is<User>(u => u.IconId == newIconId)), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetNewIcon_Throws_WhenUserNotFound()
|
||||
{
|
||||
// Arrange
|
||||
_mockUsersRepo.Setup(r => r.FindByIdAsync(It.IsAny<Guid>()))
|
||||
.ReturnsAsync((User)null!);
|
||||
|
||||
// Act + Assert
|
||||
Assert.ThrowsAsync<NullReferenceException>(() =>
|
||||
_service.SetNewIcon(Guid.NewGuid(), Guid.NewGuid()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using AutoFixture;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Application.Services;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Repositories.Groups;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.Application.Tests.Services;
|
||||
|
||||
[TestFixture]
|
||||
public class UserGroupsServiceTests
|
||||
{
|
||||
private Fixture _fixture;
|
||||
private Mock<IGroupsRepository> _repositoryMock;
|
||||
private IUserGroupsService _service;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
|
||||
_fixture.Behaviors
|
||||
.OfType<ThrowingRecursionBehavior>()
|
||||
.ToList()
|
||||
.ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_repositoryMock = new Mock<IGroupsRepository>();
|
||||
|
||||
_service = new UserGroupsService(_repositoryMock.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetUserGroups_ShouldReturnAllUserGroups()
|
||||
{
|
||||
// Arrange
|
||||
var chats = _fixture.CreateMany<ChatGroup>();
|
||||
var userId = chats.First().Members.First().Id;
|
||||
|
||||
_repositoryMock.Setup(r => r.GetByUserIdAsync(userId))
|
||||
.ReturnsAsync([chats.First()]);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetUserGroupsAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.Count(), Is.EqualTo(1));
|
||||
Assert.That(result, Is.EquivalentTo([chats.First()]));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetUserGroups_ButGroupDoesNotExist_ShouldReturnEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var userId = _fixture.Create<Guid>();
|
||||
|
||||
_repositoryMock.Setup(r => r.GetByUserIdAsync(userId))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(userId));
|
||||
|
||||
// Act
|
||||
var result = await _service.GetUserGroupsAsync(userId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.Count(), Is.EqualTo(0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||
using Govor.Application.Services.UserOnlineStatus;
|
||||
|
||||
namespace Govor.Application.Tests.Services.UserOnlineStatus;
|
||||
|
||||
[TestFixture]
|
||||
public class OnlineUserStoreTests
|
||||
{
|
||||
private IOnlineUserStore _store;
|
||||
private Guid _userId1;
|
||||
private Guid _userId2;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_store = new OnlineUserStore();
|
||||
_userId1 = Guid.NewGuid();
|
||||
_userId2 = Guid.NewGuid();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetOnlineUser_UserIsMarkedOnline()
|
||||
{
|
||||
// Act
|
||||
_store.SetOnlineUser(_userId1);
|
||||
|
||||
// Assert
|
||||
Assert.That(_store.IsOnline(_userId1), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetOfflineUser_UserIsNoLongerOnline()
|
||||
{
|
||||
// Act
|
||||
_store.SetOnlineUser(_userId1);
|
||||
_store.SetOfflineUser(_userId1);
|
||||
|
||||
// Assert
|
||||
Assert.That(_store.IsOnline(_userId1), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsOnline_ReturnsFalse_ForUnknownUser()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.That(_store.IsOnline(Guid.NewGuid()), Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetAllOnlineUsers_ReturnsAllCurrentlyOnlineUsers()
|
||||
{
|
||||
// Arrange
|
||||
_store.SetOnlineUser(_userId1);
|
||||
_store.SetOnlineUser(_userId2);
|
||||
|
||||
// Act
|
||||
var onlineUsers = _store.GetAllOnlineUsers();
|
||||
|
||||
// Assert
|
||||
Assert.That(onlineUsers, Is.EquivalentTo(new[] { _userId1, _userId2 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetOnlineUser_Twice_DoesNotThrow()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.DoesNotThrow(() =>
|
||||
{
|
||||
_store.SetOnlineUser(_userId1);
|
||||
_store.SetOnlineUser(_userId1);
|
||||
});
|
||||
|
||||
Assert.That(_store.IsOnline(_userId1), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetOfflineUser_ForUnknownUser_DoesNotThrow()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.DoesNotThrow(() => _store.SetOfflineUser(Guid.NewGuid()));
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
using AutoFixture;
|
||||
using Govor.Application.Interfaces.Friends;
|
||||
using Govor.Application.Interfaces.UserOnlineStatus;
|
||||
using Govor.Application.Services.UserOnlineStatus;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.Application.Tests.Services.UserOnlineStatus;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(UserNotificationScopeService))]
|
||||
public class UserNotificationScopeServiceTests
|
||||
{
|
||||
private Mock<ILogger<UserNotificationScopeService>> _mockLogger;
|
||||
private Mock<IFriendshipService> _mockFriendshipService;
|
||||
private IUserNotificationScopeService _service;
|
||||
private Fixture _fixture;
|
||||
private Guid _userId;
|
||||
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList().ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_userId = Guid.NewGuid();
|
||||
|
||||
_mockLogger = new Mock<ILogger<UserNotificationScopeService>>();
|
||||
_mockFriendshipService = new Mock<IFriendshipService>();
|
||||
|
||||
_service = new UserNotificationScopeService(_mockLogger.Object, _mockFriendshipService.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetNotifiedUsers_ReturnsNotifiedUsers()
|
||||
{
|
||||
// Arrange
|
||||
var random = new Random();
|
||||
var users = _fixture.CreateMany<User>(random.Next(3, 10)).ToList();
|
||||
|
||||
_mockFriendshipService.Setup(f => f.GetFriendsAsync(_userId))
|
||||
.ReturnsAsync(users);
|
||||
|
||||
// Act
|
||||
var result = await _service.GetNotifiedUsers(_userId);
|
||||
// Assert
|
||||
Assert.That(result, Is.Not.Null);
|
||||
Assert.That(result.Count, Is.EqualTo(users.Count));
|
||||
Assert.That(result, Is.EquivalentTo(users.Select(u => u.Id)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetNotifiedUsers_ThrowsInvalidOperationException_WhenUserDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
_mockFriendshipService.Setup(f => f.GetFriendsAsync(_userId))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(_userId, "Database is empty."));
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.ThrowsAsync<InvalidOperationException>(async () => await _service.GetNotifiedUsers(_userId));
|
||||
Assert.That(ex.Message, Is.EqualTo("User not found"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
using Govor.Application.Services.UserSessions;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Core.Repositories.UserSessionsRepository;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Govor.Application.Interfaces.Authentication;
|
||||
using Govor.Application.Services.Authentication;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
|
||||
namespace Govor.Application.Tests.Services.UserSessions;
|
||||
|
||||
[TestFixture]
|
||||
public class UserSessionOpenerTests
|
||||
{
|
||||
private Mock<IUserSessionsRepository> _repositoryMock;
|
||||
private Mock<IJwtService> _jwtServiceMock;
|
||||
private Mock<IJwtTokenHasher> _jwtTokenHasherMock;
|
||||
private UserSessionOpener _service;
|
||||
private User _user;
|
||||
|
||||
private const string DeviceInfo = "Chrome on Windows";
|
||||
private const string RefreshToken = "new-refresh-token";
|
||||
private const string AccessToken = "new-access-token";
|
||||
private const string TokenHash = "hashed-refresh-token";
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_repositoryMock = new Mock<IUserSessionsRepository>();
|
||||
_jwtServiceMock = new Mock<IJwtService>();
|
||||
var loggerMock = new Mock<ILogger<UserSessionOpener>>();
|
||||
_jwtTokenHasherMock = new Mock<IJwtTokenHasher>();
|
||||
var options = Options.Create(new JwtRefreshOption { RefreshTokenLifetimeDays = 30 });
|
||||
|
||||
_user = new User
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Username = "test",
|
||||
Description = "some description",
|
||||
PasswordHash = "hashed-password",
|
||||
IconId = Guid.NewGuid(),
|
||||
CreatedOn = DateOnly.FromDateTime(DateTime.Now),
|
||||
WasOnline = DateTime.Now,
|
||||
InviteId = Guid.NewGuid(),
|
||||
// ...
|
||||
};
|
||||
|
||||
_jwtServiceMock.Setup(j => j.GenerateRefreshTokenAsync(_user)).ReturnsAsync(RefreshToken);
|
||||
_jwtTokenHasherMock.Setup(h => h.HashToken(RefreshToken)).Returns(TokenHash);
|
||||
|
||||
_jwtServiceMock.Setup(j => j.GenerateAccessTokenAsync(_user, It.IsAny<Guid>())).ReturnsAsync(AccessToken);
|
||||
|
||||
|
||||
_service = new UserSessionOpener(
|
||||
_repositoryMock.Object,
|
||||
_jwtServiceMock.Object,
|
||||
_jwtTokenHasherMock.Object,
|
||||
options,
|
||||
loggerMock.Object
|
||||
);
|
||||
}
|
||||
|
||||
// UpdateExistingSessionAsync
|
||||
[Test]
|
||||
public async Task OpenSessionAsync_ShouldUpdateExistingSession_WhenFoundByDeviceInfo()
|
||||
{
|
||||
// Arrange
|
||||
var existingSessionId = Guid.NewGuid();
|
||||
var existingSession = new UserSession
|
||||
{
|
||||
Id = existingSessionId,
|
||||
UserId = _user.Id,
|
||||
DeviceInfo = DeviceInfo,
|
||||
RefreshTokenHash = "old-hash",
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-10),
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(10),
|
||||
IsRevoked = true
|
||||
};
|
||||
|
||||
_repositoryMock.Setup(r => r.GetByUserIdAsync(_user.Id)).ReturnsAsync(new List<UserSession> { existingSession });
|
||||
|
||||
// Act
|
||||
var result = await _service.OpenSessionAsync(_user, DeviceInfo);
|
||||
|
||||
// Assert
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(result.refreshToken, Is.EqualTo(RefreshToken), "Должен быть возвращен новый RefreshToken.");
|
||||
Assert.That(result.accessToken, Is.EqualTo(AccessToken), "Должен быть возвращен новый AccessToken.");
|
||||
});
|
||||
|
||||
_repositoryMock.Verify(r => r.UpdateAsync(It.Is<UserSession>(s =>
|
||||
s.Id == existingSessionId &&
|
||||
s.RefreshTokenHash == TokenHash &&
|
||||
s.IsRevoked == false &&
|
||||
s.ExpiresAt > DateTime.UtcNow
|
||||
)), Times.Once, "Должен быть вызван метод UpdateAsync с новыми данными.");
|
||||
|
||||
_repositoryMock.Verify(r => r.AddAsync(It.IsAny<UserSession>()), Times.Never);
|
||||
}
|
||||
|
||||
// CreateNewSessionAsync
|
||||
|
||||
[Test]
|
||||
public async Task OpenSessionAsync_ShouldCreateNewSession_IfNoneFound()
|
||||
{
|
||||
// Arrange
|
||||
_repositoryMock.Setup(r => r.GetByUserIdAsync(_user.Id)).ReturnsAsync(new List<UserSession>());
|
||||
|
||||
// Act
|
||||
var result = await _service.OpenSessionAsync(_user, DeviceInfo);
|
||||
|
||||
// Assert
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(result.refreshToken, Is.EqualTo(RefreshToken), "Должен быть возвращен новый RefreshToken.");
|
||||
Assert.That(result.accessToken, Is.EqualTo(AccessToken), "Должен быть возвращен новый AccessToken.");
|
||||
});
|
||||
|
||||
_repositoryMock.Verify(r => r.AddAsync(It.Is<UserSession>(s =>
|
||||
s.UserId == _user.Id &&
|
||||
s.DeviceInfo == DeviceInfo &&
|
||||
s.RefreshTokenHash == TokenHash &&
|
||||
s.IsRevoked == false
|
||||
)), Times.Once, "Должен быть вызван метод AddAsync для создания новой сессии.");
|
||||
|
||||
_repositoryMock.Verify(r => r.UpdateAsync(It.IsAny<UserSession>()), Times.Never);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task OpenSessionAsync_ShouldCreateNewSession_WhenRepositoryThrowsNotFoundByKeyException()
|
||||
{
|
||||
// Arrange
|
||||
_repositoryMock
|
||||
.Setup(r => r.GetByUserIdAsync(_user.Id))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(_user.Id, "userId"));
|
||||
|
||||
// Act
|
||||
var result = await _service.OpenSessionAsync(_user, DeviceInfo);
|
||||
|
||||
// Assert
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(result.refreshToken, Is.EqualTo(RefreshToken), "Должен быть возвращен новый RefreshToken.");
|
||||
Assert.That(result.accessToken, Is.EqualTo(AccessToken), "Должен быть возвращен новый AccessToken.");
|
||||
});
|
||||
|
||||
_repositoryMock.Verify(r => r.AddAsync(It.Is<UserSession>(s =>
|
||||
s.RefreshTokenHash == TokenHash
|
||||
)), Times.Once, "Должен быть вызван AddAsync после перехвата исключения.");
|
||||
|
||||
_repositoryMock.Verify(r => r.UpdateAsync(It.IsAny<UserSession>()), Times.Never);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using AutoFixture;
|
||||
using Govor.Application.Interfaces.UserSession;
|
||||
using Govor.Application.Services.UserSessions;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Users;
|
||||
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();
|
||||
_fixture.Behaviors.OfType<ThrowingRecursionBehavior>()
|
||||
.ToList()
|
||||
.ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_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.Build<UserSession>()
|
||||
.With(f => f.IsRevoked, false)
|
||||
.CreateMany().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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
using Govor.Application.Interfaces.Authentication;
|
||||
using Govor.Application.Services.Authentication;
|
||||
using Govor.Application.Services.UserSessions;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Govor.Core.Repositories.UserSessionsRepository;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.Application.Tests.Services.UserSessions;
|
||||
|
||||
[TestFixture]
|
||||
public class UserSessionRefresherTests
|
||||
{
|
||||
private Mock<IUserSessionsRepository> _sessionsRepoMock;
|
||||
private Mock<IUsersRepository> _usersRepoMock;
|
||||
private Mock<IJwtService> _jwtServiceMock;
|
||||
private Mock<ILogger<UserSessionRefresher>> _loggerMock;
|
||||
private Mock<IOptions<JwtRefreshOption>> _optionsMock;
|
||||
private JwtRefreshOption _options;
|
||||
private UserSessionRefresher _refresher;
|
||||
private const string OldRefreshToken = "old-refresh-token";
|
||||
private const string NewRefreshToken = "new-refresh-token";
|
||||
private const string NewAccessToken = "new-access-token";
|
||||
private User _user;
|
||||
private UserSession _session;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_sessionsRepoMock = new Mock<IUserSessionsRepository>();
|
||||
_usersRepoMock = new Mock<IUsersRepository>();
|
||||
_jwtServiceMock = new Mock<IJwtService>();
|
||||
_loggerMock = new Mock<ILogger<UserSessionRefresher>>();
|
||||
_optionsMock = new Mock<IOptions<JwtRefreshOption>>();
|
||||
|
||||
_options = new JwtRefreshOption { RefreshTokenLifetimeDays = 30 };
|
||||
|
||||
_optionsMock.SetupGet(o => o.Value).Returns(_options);
|
||||
|
||||
_refresher = new UserSessionRefresher(
|
||||
_sessionsRepoMock.Object,
|
||||
_loggerMock.Object,
|
||||
_usersRepoMock.Object,
|
||||
_optionsMock.Object,
|
||||
_jwtServiceMock.Object);
|
||||
|
||||
_user = new User
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Username = "TestUser",
|
||||
PasswordHash = "hash",
|
||||
InviteId = Guid.NewGuid()
|
||||
};
|
||||
|
||||
_session = new UserSession
|
||||
{
|
||||
RefreshTokenHash = OldRefreshToken,
|
||||
UserId = _user.Id,
|
||||
DeviceInfo = "Chrome",
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-5),
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(5),
|
||||
IsRevoked = false
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RefreshTokenAsync_ValidToken_ReturnsNewTokensAndCreatesNewSession()
|
||||
{
|
||||
// Arrange
|
||||
_sessionsRepoMock.Setup(r => r.GetByHashedRefreshTokenAsync(OldRefreshToken)).ReturnsAsync(_session);
|
||||
_usersRepoMock.Setup(r => r.FindByIdAsync(_user.Id)).ReturnsAsync(_user);
|
||||
_jwtServiceMock.Setup(j => j.GenerateAccessTokenAsync(_user, _session.Id)).ReturnsAsync(NewAccessToken);
|
||||
_jwtServiceMock.Setup(j => j.GenerateRefreshTokenAsync(_user)).ReturnsAsync(NewRefreshToken);
|
||||
|
||||
// Act
|
||||
var result = await _refresher.RefreshTokenAsync(OldRefreshToken);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.accessToken, Is.EqualTo(NewAccessToken));
|
||||
Assert.That(result.refreshToken, Is.EqualTo(NewRefreshToken));
|
||||
Assert.That(_session.IsRevoked, Is.True);
|
||||
|
||||
_sessionsRepoMock.Verify(r => r.UpdateAsync(_session), Times.Once);
|
||||
_sessionsRepoMock.Verify(r => r.AddAsync(It.Is<UserSession>(s =>
|
||||
s.UserId == _user.Id &&
|
||||
s.RefreshTokenHash == NewRefreshToken &&
|
||||
s.DeviceInfo == _session.DeviceInfo)), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RefreshTokenAsync_RevokedToken_ThrowsUnauthorizedAccessException()
|
||||
{
|
||||
// Arrange
|
||||
_session.IsRevoked = true;
|
||||
_sessionsRepoMock.Setup(r => r.GetByHashedRefreshTokenAsync(OldRefreshToken)).ReturnsAsync(_session);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
|
||||
await _refresher.RefreshTokenAsync(OldRefreshToken));
|
||||
|
||||
Assert.That(ex.Message, Contains.Substring("Refresh token is invalid or expired"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RefreshTokenAsync_ExpiredToken_ThrowsUnauthorizedAccessException()
|
||||
{
|
||||
// Arrange
|
||||
_session.ExpiresAt = DateTime.UtcNow.AddMinutes(-1);
|
||||
_sessionsRepoMock.Setup(r => r.GetByHashedRefreshTokenAsync(OldRefreshToken)).ReturnsAsync(_session);
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
|
||||
await _refresher.RefreshTokenAsync(OldRefreshToken));
|
||||
|
||||
Assert.That(ex.Message, Contains.Substring("Refresh token is invalid or expired"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RefreshTokenAsync_TokenNotFound_ThrowsUnauthorizedAccessException()
|
||||
{
|
||||
// Arrange
|
||||
_sessionsRepoMock.Setup(r => r.GetByHashedRefreshTokenAsync(OldRefreshToken))
|
||||
.ThrowsAsync(new NotFoundByKeyException<string>("token", OldRefreshToken));
|
||||
// Act & Assert
|
||||
var ex = Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
|
||||
await _refresher.RefreshTokenAsync(OldRefreshToken));
|
||||
|
||||
Assert.That(ex.Message, Contains.Substring("Invalid refresh token"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
using Govor.Application.Services.UserSessions;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Users;
|
||||
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<IUserSessionsRepository> _sessionsRepositoryMock;
|
||||
private Mock<ILogger<UserSessionRevoker>> _loggerMock;
|
||||
private UserSessionRevoker _revoker;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_sessionsRepositoryMock = new Mock<IUserSessionsRepository>();
|
||||
_loggerMock = new Mock<ILogger<UserSessionRevoker>>();
|
||||
_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<UnauthorizedAccessException>(() =>
|
||||
_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<EventId>(),
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once());
|
||||
_sessionsRepositoryMock.Verify(r => r.UpdateAsync(It.IsAny<UserSession>()), 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<UserSession>()), 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<Guid>(sessionId));
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
_revoker.CloseSessionByIdAsync(sessionId, userId));
|
||||
Assert.That(ex.Message, Is.EqualTo("Session not found"));
|
||||
_loggerMock.Verify(l => l.Log(
|
||||
LogLevel.Warning,
|
||||
It.IsAny<EventId>(),
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once());
|
||||
_sessionsRepositoryMock.Verify(r => r.UpdateAsync(It.IsAny<UserSession>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CloseAllSessionsAsync_MultipleSessions_RevokesNonRevokedSessions()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var sessions = new List<UserSession>
|
||||
{
|
||||
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<Guid>(userId));
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
_revoker.CloseAllSessionsAsync(userId));
|
||||
Assert.That(ex.Message, Is.EqualTo("Session not found"));
|
||||
_loggerMock.Verify(l => l.Log(
|
||||
LogLevel.Warning,
|
||||
It.IsAny<EventId>(),
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once());
|
||||
_sessionsRepositoryMock.Verify(r => r.UpdateAsync(It.IsAny<UserSession>()), Times.Never());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using AutoFixture;
|
||||
using Govor.Application.Exceptions.VerifyFriendship;
|
||||
using Govor.Application.Services;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Repositories.Friendships;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Govor.Application.Tests.Services;
|
||||
|
||||
[TestFixture]
|
||||
public class VerifyFriendshipTests
|
||||
{
|
||||
private Fixture _fixture;
|
||||
private Mock<ILogger<VerifyFriendship>> _mockLogger;
|
||||
private Mock<IFriendshipsRepository> _mockFriendships;
|
||||
private VerifyFriendship _service;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
_fixture = new Fixture();
|
||||
_fixture.Behaviors
|
||||
.OfType<ThrowingRecursionBehavior>()
|
||||
.ToList()
|
||||
.ForEach(b => _fixture.Behaviors.Remove(b));
|
||||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior());
|
||||
|
||||
_mockLogger = new Mock<ILogger<VerifyFriendship>>();
|
||||
_mockFriendships = new Mock<IFriendshipsRepository>();
|
||||
|
||||
_service = new VerifyFriendship(_mockFriendships.Object, _mockLogger.Object);
|
||||
}
|
||||
|
||||
// Test for VerifyAsync action
|
||||
[Test]
|
||||
public async Task VerifyAsync_Success()
|
||||
{
|
||||
// Arrange
|
||||
var friendship1 = _fixture.Create<Friendship>();
|
||||
friendship1.Status = FriendshipStatus.Accepted;
|
||||
_mockFriendships.Setup(f => f.FindByUserIdAsync(friendship1.RequesterId))
|
||||
.ReturnsAsync([friendship1]);
|
||||
// Act
|
||||
await _service.VerifyAsync(friendship1.RequesterId, friendship1.AddresseeId);
|
||||
// Assert
|
||||
_mockFriendships.Verify(f => f.FindByUserIdAsync(friendship1.RequesterId), Times.Once());
|
||||
Assert.That(await _service.TryVerifyAsync(friendship1.RequesterId, friendship1.AddresseeId), Is.EqualTo(true));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task VerifyAsync_IDsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var friendship1 = _fixture.Create<Friendship>();
|
||||
friendship1.RequesterId = Guid.Empty;
|
||||
friendship1.AddresseeId = Guid.Empty;
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<ArgumentException>(() => _service.VerifyAsync(friendship1.RequesterId, friendship1.AddresseeId));
|
||||
Assert.That(await _service.TryVerifyAsync(friendship1.RequesterId, friendship1.AddresseeId), Is.EqualTo(false));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task VerifyAsync_ThrowsNotFoundByKeyException_ThrowsFriendshipException()
|
||||
{
|
||||
// Arrange
|
||||
var friendship1 = _fixture.Create<Friendship>();
|
||||
friendship1.Status = FriendshipStatus.Accepted;
|
||||
|
||||
_mockFriendships.Setup(f => f.FindByUserIdAsync(friendship1.RequesterId))
|
||||
.ThrowsAsync(new NotFoundByKeyException<Guid>(friendship1.RequesterId));
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<FriendshipException>(async () => await _service.VerifyAsync(friendship1.RequesterId, friendship1.AddresseeId));
|
||||
_mockFriendships.Verify(f => f.FindByUserIdAsync(friendship1.RequesterId), Times.Once());
|
||||
Assert.That(await _service.TryVerifyAsync(friendship1.RequesterId, friendship1.AddresseeId), Is.EqualTo(false));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task VerifyAsync_ReturnsEmptyFriendships_ThrowsFriendshipException()
|
||||
{
|
||||
// Arrange
|
||||
var friendship1 = _fixture.Create<Friendship>();
|
||||
friendship1.Status = FriendshipStatus.Accepted;
|
||||
|
||||
_mockFriendships.Setup(f => f.FindByUserIdAsync(friendship1.RequesterId))
|
||||
.ReturnsAsync(new List<Friendship>());
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<FriendshipException>(async () => await _service.VerifyAsync(friendship1.RequesterId, friendship1.AddresseeId));
|
||||
_mockFriendships.Verify(f => f.FindByUserIdAsync(friendship1.RequesterId), Times.Once());
|
||||
Assert.That(await _service.TryVerifyAsync(friendship1.RequesterId, friendship1.AddresseeId), Is.EqualTo(false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Govor.Core;
|
||||
|
||||
namespace Govor.Application.Exceptions.VerifyFriendship;
|
||||
|
||||
public class FriendshipException : GovorCoreException
|
||||
{
|
||||
public FriendshipException(string s)
|
||||
:base(s) { }
|
||||
}
|
||||
@@ -1,21 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Govor.Core\Govor.Core.csproj" />
|
||||
<ProjectReference Include="..\Govor.Data\Govor.Data.csproj" />
|
||||
<ProjectReference Include="..\Govor.Core\Govor.Core.csproj" />
|
||||
<ProjectReference Include="..\Govor.Data\Govor.Data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.0.1" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.0.1" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
using Govor.API.Services.AdminsStuff.Interfaces;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Repositories.Invaites;
|
||||
|
||||
namespace Govor.Application.Interfaces.AdminsStuff;
|
||||
namespace Govor.Application.Infrastructure.AdminsStuff;
|
||||
|
||||
public class InvitationGenerator(IInvitesRepository repository) : IInvitationGenerator
|
||||
{
|
||||
@@ -19,7 +19,7 @@ public class InvitationGenerator(IInvitesRepository repository) : IInvitationGen
|
||||
IsAdmin = isAdmin
|
||||
};
|
||||
|
||||
await repository.AddAsync(newInvitation);
|
||||
await repository.AddAsync(newInvitation);
|
||||
|
||||
return newInvitation.Code;
|
||||
}
|
||||
+10
-3
@@ -1,9 +1,9 @@
|
||||
using Govor.API.Services.AdminsStuff.Interfaces;
|
||||
using Govor.Core.Models;
|
||||
using Govor.Application.Interfaces;
|
||||
using Govor.Core.Models.Users;
|
||||
using Govor.Core.Repositories.Users;
|
||||
using Govor.Data.Repositories.Exceptions;
|
||||
|
||||
namespace Govor.Application.Interfaces.AdminsStuff;
|
||||
namespace Govor.Application.Infrastructure.AdminsStuff;
|
||||
|
||||
public class UsersService : IUsersAdministration
|
||||
{
|
||||
@@ -26,4 +26,11 @@ public class UsersService : IUsersAdministration
|
||||
return new List<User>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<User> GetUserById(Guid userId)
|
||||
{
|
||||
var result = await _usersRepository.FindByIdAsync(userId);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -6,21 +6,23 @@ namespace Govor.Application.Infrastructure.Extensions;
|
||||
|
||||
public class CurrentUserService : ICurrentUserService
|
||||
{
|
||||
private readonly ClaimsPrincipal _user;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public CurrentUserService(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_user = httpContextAccessor.HttpContext.User;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public Guid GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = _user.FindFirst("userId")?.Value;
|
||||
var user = _httpContextAccessor.HttpContext?.User;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Govor.Application.Infrastructure.Extensions;
|
||||
|
||||
public class CurrentUserSessionService : ICurrentUserSessionService
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public CurrentUserSessionService(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public Guid GetUserSessionId()
|
||||
{
|
||||
var user = _httpContextAccessor.HttpContext?.User;
|
||||
var userIdClaim = user?.FindFirst("sid")?.Value;
|
||||
|
||||
if (string.IsNullOrEmpty(userIdClaim) || !Guid.TryParse(userIdClaim, out var userId))
|
||||
{
|
||||
throw new UnauthorizedAccessException("Session id (sid) claim is missing or invalid");
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using Govor.Application.Exceptions.AuthService;
|
||||
using Govor.Application.Interfaces.Authentication;
|
||||
using Govor.Core.Infrastructure.Validators;
|
||||
|
||||
namespace Govor.Application.Validators;
|
||||
namespace Govor.Application.Infrastructure.Validators;
|
||||
|
||||
public class UsernameValidator : IUsernameValidator
|
||||
{
|
||||
private readonly Regex _usernameRegex = new(@"^[А-Яа-яЁё]+$", RegexOptions.Compiled);
|
||||
private readonly Regex _usernameRegex = new(@"^[А-Яа-яЁё]+[А-Яа-яЁё0-9]*$", RegexOptions.Compiled);
|
||||
|
||||
public void Validate(string username)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Users;
|
||||
|
||||
namespace Govor.Application.Interfaces.Authentication;
|
||||
|
||||
public interface IAccountService
|
||||
{
|
||||
public Task<string> RegistrationAsync(string name, string password, Invitation invitation);
|
||||
public Task<string> LoginAsync(string name, string password);
|
||||
public Task<User> RegistrationAsync(string name, string password, Invitation invitation);
|
||||
public Task<User> LoginAsync(string name, string password);
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Users;
|
||||
|
||||
namespace Govor.API.Services.Authentication.Interfaces;
|
||||
namespace Govor.Application.Interfaces.Authentication;
|
||||
|
||||
public interface IInvitesService
|
||||
{
|
||||
public Task<string> GetRole(User user);
|
||||
public Invitation Validate(string inviteCode);
|
||||
public Task<string> GetRoleAsync(User user);
|
||||
public Task<Invitation> ValidateAsync(string inviteCode);
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
using Govor.Core.Models;
|
||||
using System.Security.Claims;
|
||||
using Govor.Core.Models.Users;
|
||||
|
||||
namespace Govor.API.Services.Authentication.Interfaces;
|
||||
namespace Govor.Application.Interfaces.Authentication;
|
||||
|
||||
public interface IJwtService
|
||||
{
|
||||
string GenerateJwtToken(User user);
|
||||
Task<string> GenerateAccessTokenAsync(User user, Guid sessionId);
|
||||
Task<string> GenerateRefreshTokenAsync(User user);
|
||||
ClaimsPrincipal GetPrincipalFromExpiredToken(string token);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Govor.Application.Interfaces.Authentication;
|
||||
|
||||
public interface IJwtTokenHasher
|
||||
{
|
||||
string HashToken(string token);
|
||||
bool VerifyToken(string token, string storedHash);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Govor.Core.Models;
|
||||
|
||||
namespace Govor.Application.Interfaces.Friends;
|
||||
|
||||
public interface IFriendRequestCommandService
|
||||
{
|
||||
Task<Friendship> SendAsync(Guid fromUserId, Guid toUserId);
|
||||
Task<Friendship> AcceptAsync(Guid requestId, Guid currentUserId);
|
||||
Task<Friendship> RejectAsync(Guid requestId, Guid currentUserId);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using Govor.Core.Models;
|
||||
|
||||
namespace Govor.Application.Interfaces.Friends;
|
||||
|
||||
|
||||
public interface IFriendRequestQueryService
|
||||
{
|
||||
Task<List<Friendship>> GetIncomingAsync(Guid userId);
|
||||
Task<List<Friendship>> GetResponsesAsync(Guid userId);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Govor.Application.Interfaces;
|
||||
|
||||
public interface IFriendsBlockService
|
||||
{
|
||||
Task BlockFriendRequestAsync(Guid userId, Guid currentUserId);
|
||||
Task UnblockFriendRequestAsync(Guid userId, Guid currentUserId);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using Govor.Core.Models.Users;
|
||||
|
||||
namespace Govor.Application.Interfaces.Friends;
|
||||
|
||||
public interface IFriendshipService
|
||||
{
|
||||
Task<List<User>> GetFriendsAsync(Guid userId);
|
||||
Task<List<User>> SearchUsersAsync(string query, Guid currentId);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Govor.Core.Models;
|
||||
using Govor.Core.Models.Users;
|
||||
|
||||
namespace Govor.Application.Interfaces;
|
||||
|
||||
@@ -7,6 +8,10 @@ public interface IFriendsService
|
||||
Task<List<User>> SearchUsersAsync(string query, Guid currentId);
|
||||
Task SendFriendRequestAsync(Guid fromUserId, Guid toUserId);
|
||||
Task AcceptFriendRequestAsync(Guid requestId, Guid currentUserId);
|
||||
Task RejectFriendRequestAsync(Guid requestId, Guid currentUserId);
|
||||
Task<List<User>> GetFriendsAsync(Guid userId);
|
||||
Task<List<Friendship>> GetResponsesAsync(Guid userId);
|
||||
Task<List<Friendship>> GetIncomingRequestsAsync(Guid userId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user