Тесты

This commit is contained in:
Халимов Рустам
2026-03-19 02:00:49 +03:00
parent 4410f2d98b
commit d61dfd217c
6 changed files with 264 additions and 9 deletions

View File

@@ -20,6 +20,11 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Knot.Modules.Chats.UnitTests" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using NSubstitute;
using Xunit;
using Knot.Shared.Kernel;
using Knot.Modules.Chats.Domain;
using Knot.Modules.Chats.Application.Abstractions;
using Knot.Modules.Chats.Application.Chats.Create;
namespace Knot.Modules.Chats.UnitTests.Chats;
public class CreateChatCommandHandlerTests
{
private readonly IChatRepository _chatRepository;
private readonly IChatsUnitOfWork _unitOfWork;
private readonly CreateChatCommandHandler _handler;
public CreateChatCommandHandlerTests()
{
_chatRepository = Substitute.For<IChatRepository>();
_unitOfWork = Substitute.For<IChatsUnitOfWork>();
_handler = new CreateChatCommandHandler(_chatRepository, _unitOfWork);
}
[Fact]
public async Task Handle_ShouldCreateChatAndAddMembers()
{
// Arrange
var request = new CreateChatCommand("Test Group", ChatType.Group, new List<Guid> { Guid.NewGuid(), Guid.NewGuid() });
// Act
var result = await _handler.Handle(request, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBeEmpty();
_chatRepository.Received(1).Add(Arg.Is<Chat>(c =>
c.Name == "Test Group" &&
c.Type == ChatType.Group &&
c.Members.Count == 2 &&
c.Members.First().Role == ChatRole.Owner &&
c.Members.Last().Role == ChatRole.Member));
await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using NSubstitute;
using Xunit;
using Knot.Shared.Kernel;
using Knot.Modules.Chats.Domain;
using Knot.Modules.Chats.Application.Abstractions;
using Knot.Modules.Chats.Application.Chats.GetChats;
using Knot.Modules.Chats.Application.DTOs;
namespace Knot.Modules.Chats.UnitTests.Chats;
public class GetChatsQueryHandlerTests
{
private readonly IChatRepository _chatRepository;
private readonly IUserDisplayNameProvider _userProvider;
private readonly IMessageRepository _messageRepository;
private readonly GetChatsQueryHandler _handler;
public GetChatsQueryHandlerTests()
{
_chatRepository = Substitute.For<IChatRepository>();
_userProvider = Substitute.For<IUserDisplayNameProvider>();
_messageRepository = Substitute.For<IMessageRepository>();
_handler = new GetChatsQueryHandler(_chatRepository, _userProvider, _messageRepository);
}
[Fact]
public async Task Handle_ShouldReturnUserChats_WhenTheyExist()
{
// Arrange
var userId = Guid.NewGuid();
var request = new GetChatsQuery(userId);
var chat1 = Chat.Create("Test Chat", ChatType.Group);
chat1.GetType().GetProperty("Id")?.SetValue(chat1, Guid.NewGuid());
chat1.AddMember(userId, ChatRole.Owner);
var chat2 = Chat.Create("Personal", ChatType.Personal);
chat2.GetType().GetProperty("Id")?.SetValue(chat2, Guid.NewGuid());
chat2.AddMember(userId, ChatRole.Member);
_chatRepository.GetUserChatsAsync(userId, Arg.Any<CancellationToken>())
.Returns(new List<Chat> { chat1, chat2 });
_messageRepository.GetChatMessagesAsync(Arg.Any<Guid>(), 1, 0, Arg.Any<CancellationToken>())
.Returns(new List<Message>());
_userProvider.GetUsersInfoAsync(Arg.Any<IEnumerable<Guid>>(), Arg.Any<CancellationToken>())
.Returns(new Dictionary<Guid, UserInfo>());
_messageRepository.GetUnreadCountAsync(Arg.Any<Guid>(), userId, Arg.Any<CancellationToken>())
.Returns(0);
// Act
var result = await _handler.Handle(request, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBeNull();
// It always appends synthetic "favorites" chat at the end if not found
result.Value.Count.Should().Be(3);
result.Value.Any(c => c.Name == "Test Chat").Should().BeTrue();
result.Value.Any(c => c.Type == "favorites").Should().BeTrue();
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using NSubstitute;
using Xunit;
using Knot.Shared.Kernel;
using Knot.Modules.Chats.Domain;
using Knot.Modules.Chats.Application.Abstractions;
using Knot.Modules.Chats.Application.Messages.Send;
namespace Knot.Modules.Chats.UnitTests.Messages;
public class SendMessageCommandHandlerTests
{
private readonly IChatRepository _chatRepository;
private readonly IMessageRepository _messageRepository;
private readonly IChatsUnitOfWork _unitOfWork;
private readonly SendMessageCommandHandler _handler;
public SendMessageCommandHandlerTests()
{
_chatRepository = Substitute.For<IChatRepository>();
_messageRepository = Substitute.For<IMessageRepository>();
_unitOfWork = Substitute.For<IChatsUnitOfWork>();
_handler = new SendMessageCommandHandler(_chatRepository, _messageRepository, _unitOfWork);
}
[Fact]
public async Task Handle_ShouldReturnFailure_WhenChatNotFound()
{
// Arrange
var command = new SendMessageCommand(Guid.NewGuid(), Guid.NewGuid(), "Hello", "text");
_chatRepository.GetByIdAsync(command.ChatId, Arg.Any<CancellationToken>())
.Returns((Chat)null!);
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.IsFailure.Should().BeTrue();
result.Error.Code.Should().Be(ChatErrors.ChatsNotFound.Code);
}
[Fact]
public async Task Handle_ShouldReturnFailure_WhenSenderIsNotMember()
{
// Arrange
var chat = Chat.Create("Test", ChatType.Group);
var command = new SendMessageCommand(chat.Id, Guid.NewGuid(), "Hello", "text");
_chatRepository.GetByIdAsync(command.ChatId, Arg.Any<CancellationToken>())
.Returns(chat);
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.IsFailure.Should().BeTrue();
result.Error.Code.Should().Be(ChatErrors.ChatsForbidden.Code);
}
[Fact]
public async Task Handle_ShouldCreateMessage_WhenAuthorized()
{
// Arrange
var senderId = Guid.NewGuid();
var chat = Chat.Create("Test", ChatType.Group);
chat.AddMember(senderId, ChatRole.Member);
var command = new SendMessageCommand(chat.Id, senderId, "Hello", "text");
_chatRepository.GetByIdAsync(command.ChatId, Arg.Any<CancellationToken>())
.Returns(chat);
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBeEmpty();
_messageRepository.Received(1).Add(Arg.Is<Message>(m =>
m.ChatId == chat.Id &&
m.SenderId == senderId &&
m.Content == "Hello" &&
m.Type == "text"));
await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
}
}

View File

@@ -4,8 +4,10 @@ using Knot.Modules.Identity.Application.Abstractions;
using Knot.Modules.Identity.Application.Users.Login;
using Knot.Modules.Identity.Domain;
using Knot.Shared.Kernel;
using Knot.Modules.Identity.Application.Users.Auth;
using Xunit;
using BCrypt.Net;
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Modules.Identity.UnitTests;
@@ -28,7 +30,7 @@ public class LoginUserCommandHandlerTests
// Arrange
var password = "password123";
var passwordHash = BCrypt.Net.BCrypt.HashPassword(password);
var user = User.Create("testuser", passwordHash, "Test User");
var user = User.Create("testuser", passwordHash, "Test User", null, null);
var command = new LoginUserCommand("testuser", password);
@@ -42,7 +44,8 @@ public class LoginUserCommandHandlerTests
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Should().Be("valid-jwt-token");
result.Value.Token.Should().Be("valid-jwt-token");
result.Value.User.Username.Should().Be("testuser");
}
[Fact]
@@ -58,7 +61,7 @@ public class LoginUserCommandHandlerTests
// Assert
result.IsFailure.Should().BeTrue();
result.Error.Code.Should().Be("Identity.InvalidCredentials");
result.Error.Code.Should().Be(IdentityErrors.IdentityInvalidCredentials.Code);
}
[Fact]
@@ -67,7 +70,7 @@ public class LoginUserCommandHandlerTests
// Arrange
var correctPassword = "correctPassword";
var passwordHash = BCrypt.Net.BCrypt.HashPassword(correctPassword);
var user = User.Create("testuser", passwordHash, "Test User");
var user = User.Create("testuser", passwordHash, "Test User", null, null);
var command = new LoginUserCommand("testuser", "wrongPassword");
@@ -79,6 +82,6 @@ public class LoginUserCommandHandlerTests
// Assert
result.IsFailure.Should().BeTrue();
result.Error.Code.Should().Be("Identity.InvalidCredentials");
result.Error.Code.Should().Be(IdentityErrors.IdentityInvalidCredentials.Code);
}
}

View File

@@ -4,7 +4,10 @@ using Knot.Modules.Identity.Application.Abstractions;
using Knot.Modules.Identity.Application.Users.Register;
using Knot.Modules.Identity.Domain;
using Knot.Shared.Kernel;
using Knot.Shared.Kernel.Configuration;
using Xunit;
using System.Threading;
using System.Threading.Tasks;
namespace Knot.Modules.Identity.UnitTests;
@@ -12,13 +15,21 @@ public class RegisterUserCommandHandlerTests
{
private readonly IUserRepository _userRepository;
private readonly IIdentityUnitOfWork _unitOfWork;
private readonly ISettingsService _settings;
private readonly IJwtTokenProvider _tokenProvider;
private readonly RegisterUserCommandHandler _handler;
public RegisterUserCommandHandlerTests()
{
_userRepository = Substitute.For<IUserRepository>();
_unitOfWork = Substitute.For<IIdentityUnitOfWork>();
_handler = new RegisterUserCommandHandler(_userRepository, _unitOfWork);
_settings = Substitute.For<ISettingsService>();
_tokenProvider = Substitute.For<IJwtTokenProvider>();
var options = new SystemSettingsDto { EnableRegistration = true };
_settings.Current.Returns(options);
_handler = new RegisterUserCommandHandler(_userRepository, _unitOfWork, _settings, _tokenProvider);
}
[Fact]
@@ -28,13 +39,14 @@ public class RegisterUserCommandHandlerTests
var command = new RegisterUserCommand("testuser", "password123", "Test User", "test@example.com", "My bio");
_userRepository.IsUsernameUniqueAsync(command.Username, Arg.Any<CancellationToken>())
.Returns(true);
_tokenProvider.Generate(Arg.Any<User>()).Returns("my-token");
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Should().NotBeEmpty();
result.Value.Token.Should().Be("my-token");
_userRepository.Received(1).Add(Arg.Is<User>(u =>
u.Username == command.Username &&
@@ -57,7 +69,25 @@ public class RegisterUserCommandHandlerTests
// Assert
result.IsFailure.Should().BeTrue();
result.Error.Code.Should().Be("Identity.UsernameNotUnique");
result.Error.Code.Should().Be(IdentityErrors.IdentityUsernameNotUnique.Code);
_userRepository.DidNotReceive().Add(Arg.Any<User>());
await _unitOfWork.DidNotReceive().SaveChangesAsync(Arg.Any<CancellationToken>());
}
[Fact]
public async Task Handle_ShouldReturnFailure_WhenRegistrationDisabled()
{
// Arrange
_settings.Current.Returns(new SystemSettingsDto { EnableRegistration = false });
var command = new RegisterUserCommand("testuser", "password123", "Test User", null, null);
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.IsFailure.Should().BeTrue();
result.Error.Code.Should().Be(IdentityErrors.IdentityRegistrationDisabled.Code);
_userRepository.DidNotReceive().Add(Arg.Any<User>());
await _unitOfWork.DidNotReceive().SaveChangesAsync(Arg.Any<CancellationToken>());