diff --git a/apps/server-net/src/Modules/Chats/Knot.Modules.Chats.csproj b/apps/server-net/src/Modules/Chats/Knot.Modules.Chats.csproj
index 66029ca..a16e4d5 100644
--- a/apps/server-net/src/Modules/Chats/Knot.Modules.Chats.csproj
+++ b/apps/server-net/src/Modules/Chats/Knot.Modules.Chats.csproj
@@ -20,6 +20,11 @@
+
+
+
+
+
net10.0
enable
diff --git a/apps/server-net/tests/Knot.Modules.Chats.UnitTests/Chats/CreateChatCommandHandlerTests.cs b/apps/server-net/tests/Knot.Modules.Chats.UnitTests/Chats/CreateChatCommandHandlerTests.cs
new file mode 100644
index 0000000..0c56093
--- /dev/null
+++ b/apps/server-net/tests/Knot.Modules.Chats.UnitTests/Chats/CreateChatCommandHandlerTests.cs
@@ -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();
+ _unitOfWork = Substitute.For();
+ _handler = new CreateChatCommandHandler(_chatRepository, _unitOfWork);
+ }
+
+ [Fact]
+ public async Task Handle_ShouldCreateChatAndAddMembers()
+ {
+ // Arrange
+ var request = new CreateChatCommand("Test Group", ChatType.Group, new List { 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(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());
+ }
+}
diff --git a/apps/server-net/tests/Knot.Modules.Chats.UnitTests/Chats/GetChatsQueryHandlerTests.cs b/apps/server-net/tests/Knot.Modules.Chats.UnitTests/Chats/GetChatsQueryHandlerTests.cs
new file mode 100644
index 0000000..458c684
--- /dev/null
+++ b/apps/server-net/tests/Knot.Modules.Chats.UnitTests/Chats/GetChatsQueryHandlerTests.cs
@@ -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();
+ _userProvider = Substitute.For();
+ _messageRepository = Substitute.For();
+
+ _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())
+ .Returns(new List { chat1, chat2 });
+
+ _messageRepository.GetChatMessagesAsync(Arg.Any(), 1, 0, Arg.Any())
+ .Returns(new List());
+
+ _userProvider.GetUsersInfoAsync(Arg.Any>(), Arg.Any())
+ .Returns(new Dictionary());
+
+ _messageRepository.GetUnreadCountAsync(Arg.Any(), userId, Arg.Any())
+ .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();
+ }
+}
diff --git a/apps/server-net/tests/Knot.Modules.Chats.UnitTests/Messages/SendMessageCommandHandlerTests.cs b/apps/server-net/tests/Knot.Modules.Chats.UnitTests/Messages/SendMessageCommandHandlerTests.cs
new file mode 100644
index 0000000..26650dc
--- /dev/null
+++ b/apps/server-net/tests/Knot.Modules.Chats.UnitTests/Messages/SendMessageCommandHandlerTests.cs
@@ -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();
+ _messageRepository = Substitute.For();
+ _unitOfWork = Substitute.For();
+
+ _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())
+ .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())
+ .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())
+ .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(m =>
+ m.ChatId == chat.Id &&
+ m.SenderId == senderId &&
+ m.Content == "Hello" &&
+ m.Type == "text"));
+
+ await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any());
+ }
+}
diff --git a/apps/server-net/tests/Knot.Modules.Identity.UnitTests/LoginUserCommandHandlerTests.cs b/apps/server-net/tests/Knot.Modules.Identity.UnitTests/LoginUserCommandHandlerTests.cs
index ee1d486..e634e8c 100644
--- a/apps/server-net/tests/Knot.Modules.Identity.UnitTests/LoginUserCommandHandlerTests.cs
+++ b/apps/server-net/tests/Knot.Modules.Identity.UnitTests/LoginUserCommandHandlerTests.cs
@@ -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);
}
}
diff --git a/apps/server-net/tests/Knot.Modules.Identity.UnitTests/RegisterUserCommandHandlerTests.cs b/apps/server-net/tests/Knot.Modules.Identity.UnitTests/RegisterUserCommandHandlerTests.cs
index ef6447b..efdb4d0 100644
--- a/apps/server-net/tests/Knot.Modules.Identity.UnitTests/RegisterUserCommandHandlerTests.cs
+++ b/apps/server-net/tests/Knot.Modules.Identity.UnitTests/RegisterUserCommandHandlerTests.cs
@@ -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();
_unitOfWork = Substitute.For();
- _handler = new RegisterUserCommandHandler(_userRepository, _unitOfWork);
+ _settings = Substitute.For();
+ _tokenProvider = Substitute.For();
+
+ 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())
.Returns(true);
+ _tokenProvider.Generate(Arg.Any()).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(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());
+ await _unitOfWork.DidNotReceive().SaveChangesAsync(Arg.Any());
+ }
+
+ [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());
await _unitOfWork.DidNotReceive().SaveChangesAsync(Arg.Any());