Эндпоинты
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\IntegrationTests.Shared\Knot.IntegrationTests.Shared.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Admin\Knot.Modules.Admin.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Modules\Admin\Knot.Modules.Admin.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,58 @@
|
||||
using FluentAssertions;
|
||||
using Knot.Modules.Admin.Application.Admin.Commands;
|
||||
using Knot.Modules.Auth.Application.Abstractions;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Admin.UnitTests;
|
||||
|
||||
public class ResetUserPasswordCommandHandlerTests
|
||||
{
|
||||
private readonly IAuthUnitOfWork _authUnitOfWork;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly ResetUserPasswordCommandHandler _handler;
|
||||
|
||||
public ResetUserPasswordCommandHandlerTests()
|
||||
{
|
||||
_authUnitOfWork = Substitute.For<IAuthUnitOfWork>();
|
||||
_userRepository = Substitute.For<IUserRepository>();
|
||||
_handler = new ResetUserPasswordCommandHandler(_userRepository, _authUnitOfWork);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldReturnError_WhenUserNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var command = new ResetUserPasswordCommand(Guid.NewGuid(), "NewP@ssw0rd");
|
||||
_userRepository.GetByIdAsync(command.UserId, Arg.Any<CancellationToken>()).Returns((User?)null);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsFailure.Should().BeTrue();
|
||||
result.Error.Should().Be(AuthErrors.UserNotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldSucceed_WhenUserFound()
|
||||
{
|
||||
// Arrange
|
||||
var user = User.Create("User", "pass", "salt", "admin");
|
||||
var command = new ResetUserPasswordCommand(user.Id, "NewP@ssw0rd");
|
||||
|
||||
_userRepository.GetByIdAsync(command.UserId, Arg.Any<CancellationToken>()).Returns(user);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
await _authUnitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Modules\Auth\Knot.Modules.Auth.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
||||
88
backend/Tests/Auth/UnitTests/LoginUserCommandHandlerTests.cs
Normal file
88
backend/Tests/Auth/UnitTests/LoginUserCommandHandlerTests.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using FluentAssertions;
|
||||
using NSubstitute;
|
||||
using Knot.Modules.Auth.Application.Abstractions;
|
||||
using Knot.Modules.Auth.Application.Users.Login;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Auth.Application.Users.Auth;
|
||||
using Xunit;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Auth.UnitTests;
|
||||
|
||||
public class LoginUserCommandHandlerTests
|
||||
{
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IJwtTokenProvider _tokenProvider;
|
||||
private readonly LoginUserCommandHandler _handler;
|
||||
|
||||
public LoginUserCommandHandlerTests()
|
||||
{
|
||||
_userRepository = Substitute.For<IUserRepository>();
|
||||
_tokenProvider = Substitute.For<IJwtTokenProvider>();
|
||||
_handler = new LoginUserCommandHandler(_userRepository, _tokenProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldReturnToken_WhenCredentialsAreValid()
|
||||
{
|
||||
// Arrange
|
||||
var password = "password123";
|
||||
var passwordHash = BCrypt.Net.BCrypt.HashPassword(password);
|
||||
var user = User.Create("testuser", passwordHash, "Test User", null, null);
|
||||
|
||||
var command = new LoginUserCommand("testuser", password);
|
||||
|
||||
_userRepository.GetByUsernameAsync(command.Username, Arg.Any<CancellationToken>())
|
||||
.Returns(user);
|
||||
|
||||
_tokenProvider.Generate(user).Returns("valid-jwt-token");
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Token.Should().Be("valid-jwt-token");
|
||||
result.Value.User.Username.Should().Be("testuser");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldReturnFailure_WhenUserDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var command = new LoginUserCommand("nonexistent", "password123");
|
||||
_userRepository.GetByUsernameAsync(command.Username, Arg.Any<CancellationToken>())
|
||||
.Returns((User)null!);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsFailure.Should().BeTrue();
|
||||
result.Error.Code.Should().Be(AuthErrors.IdentityInvalidCredentials.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldReturnFailure_WhenPasswordIsInvalid()
|
||||
{
|
||||
// Arrange
|
||||
var correctPassword = "correctPassword";
|
||||
var passwordHash = BCrypt.Net.BCrypt.HashPassword(correctPassword);
|
||||
var user = User.Create("testuser", passwordHash, "Test User", null, null);
|
||||
|
||||
var command = new LoginUserCommand("testuser", "wrongPassword");
|
||||
|
||||
_userRepository.GetByUsernameAsync(command.Username, Arg.Any<CancellationToken>())
|
||||
.Returns(user);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsFailure.Should().BeTrue();
|
||||
result.Error.Code.Should().Be(AuthErrors.IdentityInvalidCredentials.Code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
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.Conversations.Domain;
|
||||
using Knot.Modules.Messaging.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Application.Chats.Create;
|
||||
|
||||
namespace Knot.Modules.Conversations.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>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
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.Conversations.Domain;
|
||||
using Knot.Modules.Messaging.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Application.Chats.GetChats;
|
||||
using Knot.Modules.Conversations.Application.DTOs;
|
||||
|
||||
namespace Knot.Modules.Conversations.UnitTests.Chats;
|
||||
|
||||
public class GetChatsQueryHandlerTests
|
||||
{
|
||||
private readonly IChatRepository _chatRepository;
|
||||
private readonly IUserDisplayNameProvider _userProvider;
|
||||
private readonly IMessageRepository _messageRepository;
|
||||
private readonly IMessageReactionRepository _reactionRepository;
|
||||
private readonly GetChatsQueryHandler _handler;
|
||||
|
||||
public GetChatsQueryHandlerTests()
|
||||
{
|
||||
_chatRepository = Substitute.For<IChatRepository>();
|
||||
_userProvider = Substitute.For<IUserDisplayNameProvider>();
|
||||
_messageRepository = Substitute.For<IMessageRepository>();
|
||||
_reactionRepository = Substitute.For<IMessageReactionRepository>();
|
||||
|
||||
_handler = new GetChatsQueryHandler(_chatRepository, _userProvider, _messageRepository, _reactionRepository);
|
||||
}
|
||||
|
||||
[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>());
|
||||
|
||||
// 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(2);
|
||||
result.Value.Any(c => c.Name == "Test Chat").Should().BeTrue();
|
||||
result.Value.Any(c => c.Type == "favorites").Should().BeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
using FluentAssertions;
|
||||
using NSubstitute;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Application.Chats.GetOrCreateFavorites;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Messaging.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using Xunit;
|
||||
|
||||
namespace Knot.Modules.Conversations.UnitTests;
|
||||
|
||||
public class GetOrCreateFavoritesCommandHandlerTests
|
||||
{
|
||||
private readonly IChatRepository _chatRepository;
|
||||
private readonly IChatsUnitOfWork _unitOfWork;
|
||||
private readonly GetOrCreateFavoritesCommandHandler _handler;
|
||||
|
||||
public GetOrCreateFavoritesCommandHandlerTests()
|
||||
{
|
||||
_chatRepository = Substitute.For<IChatRepository>();
|
||||
_unitOfWork = Substitute.For<IChatsUnitOfWork>();
|
||||
_handler = new GetOrCreateFavoritesCommandHandler(_chatRepository, _unitOfWork);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldReturnExistingChat_WhenFavoritesAlreadyExists()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var existingChat = Chat.Create("Избранное", ChatType.Favorites);
|
||||
|
||||
_chatRepository.GetFavoritesAsync(userId, Arg.Any<CancellationToken>())
|
||||
.Returns(existingChat);
|
||||
|
||||
var command = new GetOrCreateFavoritesCommand(userId);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().Be(existingChat.Id);
|
||||
|
||||
_chatRepository.DidNotReceive().Add(Arg.Any<Chat>());
|
||||
await _unitOfWork.DidNotReceive().SaveChangesAsync(Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldCreateNewChat_WhenFavoritesDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
_chatRepository.GetFavoritesAsync(userId, Arg.Any<CancellationToken>())
|
||||
.Returns((Chat)null!);
|
||||
|
||||
var command = new GetOrCreateFavoritesCommand(userId);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
result.Value.Should().NotBeEmpty();
|
||||
|
||||
_chatRepository.Received(1).Add(Arg.Is<Chat>(c =>
|
||||
c.Type == ChatType.Favorites &&
|
||||
c.Name == "Избранное" &&
|
||||
c.Members.Any(m => m.UserId == userId)));
|
||||
|
||||
await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Modules\Chats\Knot.Modules.Conversations.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Messaging\Knot.Modules.Messaging.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Conversations\Knot.Modules.Conversations.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using MediatR;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Conversations.Domain;
|
||||
using Knot.Modules.Messaging.Domain;
|
||||
using Knot.Modules.Conversations.Application.Abstractions;
|
||||
using Knot.Modules.Conversations.Application.Messages.Send;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
|
||||
namespace Knot.Modules.Conversations.UnitTests.Messages;
|
||||
|
||||
public class SendMessageCommandHandlerTests
|
||||
{
|
||||
private readonly IChatRepository _chatRepository;
|
||||
private readonly IMessageRepository _messageRepository;
|
||||
private readonly IChatsUnitOfWork _unitOfWork;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IMessagesSettings _messagesSettings;
|
||||
private readonly SendMessageCommandHandler _handler;
|
||||
|
||||
public SendMessageCommandHandlerTests()
|
||||
{
|
||||
_chatRepository = Substitute.For<IChatRepository>();
|
||||
_messageRepository = Substitute.For<IMessageRepository>();
|
||||
_unitOfWork = Substitute.For<IChatsUnitOfWork>();
|
||||
_mediator = Substitute.For<IMediator>();
|
||||
_messagesSettings = Substitute.For<IMessagesSettings>();
|
||||
|
||||
var config = new Knot.Modules.Settings.Application.Settings.DTOs.MessagesConfig();
|
||||
_messagesSettings.Current.Returns(config);
|
||||
|
||||
_handler = new SendMessageCommandHandler(_chatRepository, _messageRepository, _unitOfWork, _mediator, _messagesSettings);
|
||||
}
|
||||
|
||||
[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>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Modules\Federation\Knot.Modules.Federation.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Modules\Klipy\Knot.Modules.Klipy.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Modules\Relations\Knot.Modules.Relations.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net.Http;
|
||||
using Testcontainers.PostgreSql;
|
||||
using Testcontainers.MongoDb;
|
||||
using Xunit;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using Knot.Shared.Kernel;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Knot.IntegrationTests;
|
||||
|
||||
public abstract class BaseIntegrationTest : IAsyncLifetime
|
||||
{
|
||||
protected readonly PostgreSqlContainer _postgresContainer = new PostgreSqlBuilder()
|
||||
.WithImage("postgres:15-alpine")
|
||||
.Build();
|
||||
|
||||
protected readonly MongoDbContainer _mongoContainer = new MongoDbBuilder()
|
||||
.WithImage("mongo:6.0")
|
||||
.Build();
|
||||
|
||||
protected HttpClient _client;
|
||||
protected WebApplicationFactory<Program> _factory;
|
||||
protected readonly Guid _userId = Guid.NewGuid();
|
||||
protected readonly IUserContext _userContextMock = Substitute.For<IUserContext>();
|
||||
|
||||
public virtual async Task InitializeAsync()
|
||||
{
|
||||
await _postgresContainer.StartAsync();
|
||||
await _mongoContainer.StartAsync();
|
||||
|
||||
_userContextMock.UserId.Returns(_userId);
|
||||
_userContextMock.IsAuthenticated.Returns(true);
|
||||
|
||||
_factory = new IntegrationTestWebApplicationFactory(
|
||||
_postgresContainer.GetConnectionString(),
|
||||
_mongoContainer.GetConnectionString(),
|
||||
_userContextMock);
|
||||
|
||||
_client = _factory.CreateClient();
|
||||
}
|
||||
|
||||
public virtual async Task DisposeAsync()
|
||||
{
|
||||
await _postgresContainer.StopAsync();
|
||||
await _mongoContainer.StopAsync();
|
||||
_factory?.Dispose();
|
||||
_client?.Dispose();
|
||||
}
|
||||
|
||||
private class IntegrationTestWebApplicationFactory : WebApplicationFactory<Program>
|
||||
{
|
||||
private readonly string _pgConnectionString;
|
||||
private readonly string _mongoConnectionString;
|
||||
private readonly IUserContext _userContext;
|
||||
|
||||
public IntegrationTestWebApplicationFactory(string pg, string mongo, IUserContext userContext)
|
||||
{
|
||||
_pgConnectionString = pg;
|
||||
_mongoConnectionString = mongo;
|
||||
_userContext = userContext;
|
||||
}
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
builder.ConfigureAppConfiguration((context, config) =>
|
||||
{
|
||||
config.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:DefaultConnection"] = _pgConnectionString,
|
||||
["ConnectionStrings:MongoConnection"] = _mongoConnectionString,
|
||||
["DATABASE_URL"] = _pgConnectionString,
|
||||
["MONGO_CONNECTION"] = _mongoConnectionString,
|
||||
["KNOT_MASTER_ENCRYPTION_KEY"] = "TestEncryptionKey_32CharactersLong!"
|
||||
});
|
||||
});
|
||||
|
||||
builder.ConfigureTestServices(services => {
|
||||
services.AddScoped(_ => _userContext);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.9.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="Respawn" Version="7.0.0" />
|
||||
<PackageReference Include="Testcontainers.MongoDb" Version="4.11.0" />
|
||||
<PackageReference Include="Testcontainers.PostgreSql" Version="4.11.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Host\Host.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Infrastructure\Knot.Shared.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Modules\Storage\Knot.Modules.Storage.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\IntegrationTests.Shared\Knot.IntegrationTests.Shared.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Stories\Knot.Modules.Stories.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
34
backend/Tests/Stories/IntegrationTests/StoriesTests.cs
Normal file
34
backend/Tests/Stories/IntegrationTests/StoriesTests.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Knot.IntegrationTests;
|
||||
using FluentAssertions;
|
||||
using Knot.Modules.Stories.Application.Stories.Commands.CreateStory;
|
||||
using Knot.Shared.Kernel;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NSubstitute;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using Xunit;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.IntegrationTests.Stories;
|
||||
|
||||
public class StoriesTests : BaseIntegrationTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task CreateStory_ShouldReturnOk_WhenValidRequest()
|
||||
{
|
||||
// Arrange
|
||||
var request = new CreateStoryRequest("Text", null, "Hello Integration Test", null);
|
||||
|
||||
// Act
|
||||
var response = await _client.PostAsJsonAsync("api/stories", request);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
|
||||
var content = await response.Content.ReadFromJsonAsync<dynamic>();
|
||||
}
|
||||
}
|
||||
|
||||
public record CreateStoryRequest(string Type, string? MediaUrl, string? Content, string? BgColor);
|
||||
@@ -0,0 +1,76 @@
|
||||
using FluentAssertions;
|
||||
using Knot.Modules.Stories.Application.Stories.Commands.CreateStory;
|
||||
using Knot.Modules.Stories.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
using Knot.Modules.Stories.Application.Abstractions;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Stories.UnitTests;
|
||||
|
||||
public class CreateStoryCommandHandlerTests
|
||||
{
|
||||
private readonly IStoryRepository _storyRepository;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly CreateStoryCommandHandler _handler;
|
||||
|
||||
public CreateStoryCommandHandlerTests()
|
||||
{
|
||||
_storyRepository = Substitute.For<IStoryRepository>();
|
||||
_settingsService = Substitute.For<ISettingsService>();
|
||||
_handler = new CreateStoryCommandHandler(_storyRepository, _settingsService);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldSucceed_WhenValidRequest()
|
||||
{
|
||||
// Arrange
|
||||
var command = new CreateStoryCommand(Guid.NewGuid(), "Text", null, "Hello Story", null);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
await _storyRepository.Received(1).AddAsync(Arg.Any<Story>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldFail_WhenKlipyDisabledAndKlipyType()
|
||||
{
|
||||
// Arrange
|
||||
var command = new CreateStoryCommand(Guid.NewGuid(), "Klipy", "http://klipy.com/gif", null, null);
|
||||
var settings = new SystemSettingsDto { Klipy = new KlipyConfig { Enabled = false } };
|
||||
_settingsService.GetSettingsAsync(Arg.Any<CancellationToken>()).Returns(settings);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsFailure.Should().BeTrue();
|
||||
result.Error.Code.Should().Be("Stories.KlipyDisabled");
|
||||
await _storyRepository.DidNotReceive().AddAsync(Arg.Any<Story>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldSucceed_WhenKlipyEnabledAndKlipyType()
|
||||
{
|
||||
// Arrange
|
||||
var command = new CreateStoryCommand(Guid.NewGuid(), "Klipy", "http://klipy.com/gif", null, null);
|
||||
var settings = new SystemSettingsDto { Klipy = new KlipyConfig { Enabled = true } };
|
||||
_settingsService.GetSettingsAsync(Arg.Any<CancellationToken>()).Returns(settings);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
await _storyRepository.Received(1).AddAsync(Arg.Any<Story>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
using FluentAssertions;
|
||||
using Knot.Modules.Stories.Application.Stories.Commands.DeleteStory;
|
||||
using Knot.Modules.Stories.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using Knot.Modules.Stories.Application.Abstractions;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Stories.UnitTests;
|
||||
|
||||
public class DeleteStoryCommandHandlerTests
|
||||
{
|
||||
private readonly IStoryRepository _storyRepository;
|
||||
private readonly DeleteStoryCommandHandler _handler;
|
||||
|
||||
public DeleteStoryCommandHandlerTests()
|
||||
{
|
||||
_storyRepository = Substitute.For<IStoryRepository>();
|
||||
_handler = new DeleteStoryCommandHandler(_storyRepository);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldReturnError_WhenStoryNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var command = new DeleteStoryCommand(Guid.NewGuid(), Guid.NewGuid());
|
||||
_storyRepository.GetByIdAsync(command.StoryId, Arg.Any<CancellationToken>()).Returns((Story?)null);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsFailure.Should().BeTrue();
|
||||
result.Error.Should().Be(StoryErrors.StoryNotFound);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldReturnError_WhenUserIsNotOwner()
|
||||
{
|
||||
// Arrange
|
||||
var ownerId = Guid.NewGuid();
|
||||
var viewerId = Guid.NewGuid();
|
||||
var story = Story.Create(ownerId, StoryType.Text, null, "Content", null);
|
||||
var command = new DeleteStoryCommand(viewerId, story.Id);
|
||||
|
||||
_storyRepository.GetByIdAsync(command.StoryId, Arg.Any<CancellationToken>()).Returns(story);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsFailure.Should().BeTrue();
|
||||
result.Error.Should().Be(StoryErrors.Unauthorized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldSucceed_WhenOwnerDeletes()
|
||||
{
|
||||
// Arrange
|
||||
var ownerId = Guid.NewGuid();
|
||||
var story = Story.Create(ownerId, StoryType.Text, null, "Content", null);
|
||||
var command = new DeleteStoryCommand(ownerId, story.Id);
|
||||
|
||||
_storyRepository.GetByIdAsync(command.StoryId, Arg.Any<CancellationToken>()).Returns(story);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsSuccess.Should().BeTrue();
|
||||
await _storyRepository.Received(1).DeleteAsync(story, Arg.Any<CancellationToken>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Modules\Stories\Knot.Modules.Stories.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Modules\TelegramImport\Knot.Modules.TelegramImport.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Modules\Profiles\Knot.Modules.Profiles.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using FluentAssertions;
|
||||
using Knot.Modules.Profiles.Application.Profiles.UpdateProfile;
|
||||
using Knot.Modules.Profiles.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Profiles.Application.Abstractions;
|
||||
|
||||
namespace Knot.Modules.Profiles.UnitTests;
|
||||
|
||||
public class UpdateProfileCommandHandlerTests
|
||||
{
|
||||
private readonly IProfileRepository _profileRepository;
|
||||
private readonly UpdateProfileCommandHandler _handler;
|
||||
|
||||
public UpdateProfileCommandHandlerTests()
|
||||
{
|
||||
_profileRepository = Substitute.For<IProfileRepository>();
|
||||
_handler = new UpdateProfileCommandHandler(_profileRepository);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_ShouldReturnError_WhenProfileNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var command = new UpdateProfileCommand(Guid.NewGuid(), "FirstName", "Bio", null);
|
||||
_profileRepository.GetByIdAsync(command.UserId, Arg.Any<CancellationToken>()).Returns((ProfileDocument?)null);
|
||||
|
||||
// Act
|
||||
var result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
result.IsFailure.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageReference Include="FluentAssertions" Version="8.0.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\Modules\WebRtc\Knot.Modules.WebRtc.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Shared\Knot.Shared.Kernel\Knot.Shared.Kernel.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Modules\Settings\Knot.Modules.Settings.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Auth.Domain;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
|
||||
namespace Knot.Modules.Auth.Application.Abstractions;
|
||||
|
||||
public interface IAuthDbContext
|
||||
{
|
||||
DbSet<User> Users { get; }
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
namespace Knot.Modules.Auth.Application.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Unit of Work специфичный для модуля Identity.
|
||||
/// </summary>
|
||||
public interface IAuthUnitOfWork : IUnitOfWork
|
||||
{
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using Knot.Modules.Auth.Domain;
|
||||
|
||||
namespace Knot.Modules.Auth.Application.Abstractions;
|
||||
|
||||
public interface IJwtTokenProvider
|
||||
{
|
||||
string Generate(User user);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace Knot.Modules.Auth.Application.Auth.DTOs;
|
||||
|
||||
public record AuthResponseDto(
|
||||
string Token,
|
||||
AuthUserDto User
|
||||
);
|
||||
|
||||
public record AuthUserDto(
|
||||
Guid Id,
|
||||
string Username,
|
||||
string DisplayName,
|
||||
string? Email,
|
||||
string? Bio,
|
||||
string? Avatar,
|
||||
DateTime? Birthday,
|
||||
bool IsOnline,
|
||||
DateTime CreatedAt
|
||||
);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
|
||||
namespace Knot.Modules.Auth.Application.Users.Auth;
|
||||
|
||||
public record AuthResponseDto(
|
||||
string Token,
|
||||
AuthUserDto User
|
||||
);
|
||||
|
||||
public record AuthUserDto(
|
||||
Guid Id,
|
||||
string Username,
|
||||
string DisplayName,
|
||||
string? Email,
|
||||
string? Bio,
|
||||
string? Avatar,
|
||||
DateTime? Birthday,
|
||||
bool IsOnline,
|
||||
DateTime CreatedAt
|
||||
);
|
||||
|
||||
15
backend/src/Modules/Auth/Domain/AuthErrors.cs
Normal file
15
backend/src/Modules/Auth/Domain/AuthErrors.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Knot.Shared.Kernel;
|
||||
|
||||
namespace Knot.Modules.Auth.Domain;
|
||||
|
||||
public static class AuthErrors
|
||||
{
|
||||
public static readonly Error FriendsNotFound = new Error("Friends.NotFound", "Friendship not found");
|
||||
public static readonly Error FriendsSelf = new Error("Friends.Self", "Cannot add yourself");
|
||||
public static readonly Error FriendsExists = new Error("Friends.Exists", "Friendship already exists");
|
||||
public static readonly Error UserNotFound = new Error("User.NotFound", "User not found");
|
||||
public static readonly Error IdentityInvalidCredentials = new Error("Identity.InvalidCredentials", "Неверное имя пользователя или пароль.");
|
||||
public static readonly Error IdentityRegistrationDisabled = new Error("Identity.RegistrationDisabled", "Registration is disabled by the administrator.");
|
||||
public static readonly Error IdentityUsernameNotUnique = new Error("Identity.UsernameNotUnique", "Это имя пользователя уже занято.");
|
||||
}
|
||||
|
||||
19
backend/src/Modules/Auth/Domain/IUserRepository.cs
Normal file
19
backend/src/Modules/Auth/Domain/IUserRepository.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Knot.Modules.Auth.Domain;
|
||||
|
||||
namespace Knot.Modules.Auth.Domain;
|
||||
|
||||
/// <summary>
|
||||
/// Интерфейс репозитория для работы с пользователями.
|
||||
/// </summary>
|
||||
public interface IUserRepository
|
||||
{
|
||||
Task<User?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
|
||||
Task<List<User>> GetByIdsAsync(IEnumerable<Guid> ids, CancellationToken cancellationToken = default);
|
||||
Task<User?> GetByUsernameAsync(string username, CancellationToken cancellationToken = default);
|
||||
Task<bool> IsUsernameUniqueAsync(string username, CancellationToken cancellationToken = default);
|
||||
Task<List<User>> SearchUsersAsync(string query, CancellationToken cancellationToken = default);
|
||||
void Add(User user);
|
||||
void Update(User user);
|
||||
void Remove(User user);
|
||||
}
|
||||
|
||||
@@ -61,8 +61,8 @@ public static class MongoDbMapConfigurator
|
||||
BsonClassMap.RegisterClassMap<PollMessage>(cm =>
|
||||
{
|
||||
cm.AutoMap();
|
||||
cm.MapField("_options").SetElementName("Options");
|
||||
cm.MapField("_votes").SetElementName("Votes");
|
||||
cm.MapProperty(c => c.Options).SetElementName("_options");
|
||||
cm.MapProperty(c => c.Votes).SetElementName("_votes");
|
||||
cm.SetDiscriminator("PollMessage");
|
||||
});
|
||||
|
||||
|
||||
11
backend/src/Modules/Profiles/Domain/IAvatarStorageService.cs
Normal file
11
backend/src/Modules/Profiles/Domain/IAvatarStorageService.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
|
||||
public interface IAvatarStorageService
|
||||
{
|
||||
Task<string> UploadAsync(Stream content, string fileName, string contentType, CancellationToken ct = default);
|
||||
Task DeleteAsync(string fileId, CancellationToken ct = default);
|
||||
}
|
||||
15
backend/src/Modules/Profiles/Domain/IProfileRepository.cs
Normal file
15
backend/src/Modules/Profiles/Domain/IProfileRepository.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
|
||||
public interface IProfileRepository
|
||||
{
|
||||
Task<ProfileDocument?> GetByIdAsync(Guid id, CancellationToken ct = default);
|
||||
Task<ProfileDocument?> GetByUsernameAsync(string username, CancellationToken ct = default);
|
||||
Task AddAsync(ProfileDocument profile, CancellationToken ct = default);
|
||||
Task UpdateAsync(ProfileDocument profile, CancellationToken ct = default);
|
||||
Task<List<ProfileDocument>> SearchAsync(string query, CancellationToken ct = default);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
|
||||
public interface IProfilesUnitOfWork
|
||||
{
|
||||
Task SaveChangesAsync(CancellationToken ct = default);
|
||||
}
|
||||
10
backend/src/Modules/Profiles/Domain/ProfilesErrors.cs
Normal file
10
backend/src/Modules/Profiles/Domain/ProfilesErrors.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Knot.Modules.Profiles.Domain;
|
||||
using Knot.Shared.Kernel;
|
||||
public static class ProfilesErrors
|
||||
{
|
||||
public static readonly Error ProfileNotFound = new("Profile.NotFound", "Profile not found");
|
||||
}
|
||||
public static class IdentityErrors
|
||||
{
|
||||
public static readonly Error UserNotFound = new("Profile.NotFound", "Profile not found");
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
|
||||
namespace Knot.Modules.Settings.Application.Settings.Abstractions;
|
||||
|
||||
public interface ISettingsService
|
||||
{
|
||||
Task<SystemSettingsDto> GetSettingsAsync(CancellationToken cancellationToken = default);
|
||||
Task UpdateSettingsAsync(SystemSettingsDto settings, CancellationToken cancellationToken = default);
|
||||
SystemSettingsDto Current { get; }
|
||||
}
|
||||
|
||||
public interface ISystemSettings
|
||||
{
|
||||
SystemConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IStoriesSettings
|
||||
{
|
||||
StoriesConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IChatsSettings
|
||||
{
|
||||
ChatsConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IMessagesSettings
|
||||
{
|
||||
MessagesConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IWebRtcSettings
|
||||
{
|
||||
WebRtcConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IKlipySettings
|
||||
{
|
||||
KlipyConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IImportSettings
|
||||
{
|
||||
ImportConfig Current { get; }
|
||||
}
|
||||
|
||||
public interface IFederationSettings
|
||||
{
|
||||
FederationConfig Current { get; }
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using System.Collections.Generic;
|
||||
using Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
|
||||
namespace Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
|
||||
public record PublicConfigDto
|
||||
{
|
||||
public SystemConfigDto System { get; init; } = new();
|
||||
public StoriesConfigDto Stories { get; init; } = new();
|
||||
public ChatsConfigDto Chats { get; init; } = new();
|
||||
public MessagesConfigDto Messages { get; init; } = new();
|
||||
public WebRtcConfigDto WebRtc { get; init; } = new();
|
||||
public KlipyConfigDto Klipy { get; init; } = new();
|
||||
public ImportConfigDto Import { get; init; } = new();
|
||||
public FederationConfigDto Federation { get; init; } = new();
|
||||
|
||||
public static PublicConfigDto FromSettings(SystemSettingsDto settings)
|
||||
{
|
||||
return new PublicConfigDto
|
||||
{
|
||||
System = new SystemConfigDto
|
||||
{
|
||||
DomainUrl = settings.System.DomainUrl,
|
||||
EnableRegistration = settings.System.EnableRegistration
|
||||
},
|
||||
Stories = new StoriesConfigDto
|
||||
{
|
||||
Enabled = settings.Stories.Enabled,
|
||||
MaxStoriesPerPeriod = settings.Stories.MaxStoriesPerPeriod,
|
||||
StoryLifetimeHours = settings.Stories.StoryLifetimeHours,
|
||||
TextStoriesEnabled = settings.Stories.TextStoriesEnabled,
|
||||
TextStoryDurationSeconds = settings.Stories.TextStoryDurationSeconds,
|
||||
MediaStoryMaxDurationSeconds = settings.Stories.MediaStoryMaxDurationSeconds,
|
||||
MaxMediaSizeBytes = settings.Stories.MaxMediaSizeBytes
|
||||
},
|
||||
Chats = new ChatsConfigDto
|
||||
{
|
||||
SupportGroups = settings.Chats.SupportGroups,
|
||||
MaxGroupParticipants = settings.Chats.MaxGroupParticipants,
|
||||
AllowChatToGroupConversion = settings.Chats.AllowChatToGroupConversion,
|
||||
EnableFolders = settings.Chats.EnableFolders
|
||||
},
|
||||
Messages = new MessagesConfigDto
|
||||
{
|
||||
DailyMessageLimitPerUser = settings.Messages.DailyMessageLimitPerUser,
|
||||
ChatMessageLimit = settings.Messages.ChatMessageLimit,
|
||||
AllowMedia = settings.Messages.AllowMedia,
|
||||
MaxMediaSizeBytes = settings.Messages.MaxMediaSizeBytes,
|
||||
AllowedMediaTypes = settings.Messages.AllowedMediaTypes,
|
||||
AllowVoiceMessages = settings.Messages.AllowVoiceMessages,
|
||||
AllowForwarding = settings.Messages.AllowForwarding,
|
||||
AllowReactions = settings.Messages.AllowReactions,
|
||||
AllowReplies = settings.Messages.AllowReplies,
|
||||
AllowQuoting = settings.Messages.AllowQuoting,
|
||||
AllowMessageDeletion = settings.Messages.AllowMessageDeletion,
|
||||
ForbidCopying = settings.Messages.ForbidCopying,
|
||||
AllowLinks = settings.Messages.AllowLinks,
|
||||
AllowPolls = settings.Messages.AllowPolls,
|
||||
AllowPinning = settings.Messages.AllowPinning
|
||||
},
|
||||
WebRtc = new WebRtcConfigDto
|
||||
{
|
||||
Enabled = settings.WebRtc.Enabled,
|
||||
EnableVoiceCalls = settings.WebRtc.EnableVoiceCalls,
|
||||
EnableVideoCalls = settings.WebRtc.EnableVideoCalls,
|
||||
EnableScreenSharing = settings.WebRtc.EnableScreenSharing,
|
||||
TurnHost = settings.WebRtc.TurnHost,
|
||||
TurnPort = settings.WebRtc.TurnPort
|
||||
},
|
||||
Klipy = new KlipyConfigDto
|
||||
{
|
||||
Enabled = settings.Klipy.Enabled,
|
||||
AppName = settings.Klipy.AppName
|
||||
},
|
||||
Import = new ImportConfigDto
|
||||
{
|
||||
Enabled = settings.Import.EnableTelegramImport
|
||||
},
|
||||
Federation = new FederationConfigDto
|
||||
{
|
||||
Enabled = settings.Federation.Enabled,
|
||||
ServerDescription = settings.Federation.ServerDescription,
|
||||
AllowedDomains = settings.Federation.AllowedDomains
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public record SystemConfigDto
|
||||
{
|
||||
public string DomainUrl { get; init; } = string.Empty;
|
||||
public bool EnableRegistration { get; init; }
|
||||
}
|
||||
|
||||
public record StoriesConfigDto
|
||||
{
|
||||
public bool Enabled { get; init; }
|
||||
public int MaxStoriesPerPeriod { get; init; }
|
||||
public int StoryLifetimeHours { get; init; }
|
||||
public bool TextStoriesEnabled { get; init; }
|
||||
public int TextStoryDurationSeconds { get; init; }
|
||||
public int MediaStoryMaxDurationSeconds { get; init; }
|
||||
public int MaxMediaSizeBytes { get; init; }
|
||||
}
|
||||
|
||||
public record ChatsConfigDto
|
||||
{
|
||||
public bool SupportGroups { get; init; }
|
||||
public int MaxGroupParticipants { get; init; }
|
||||
public bool AllowChatToGroupConversion { get; init; }
|
||||
public bool EnableFolders { get; init; }
|
||||
}
|
||||
|
||||
public record MessagesConfigDto
|
||||
{
|
||||
public int DailyMessageLimitPerUser { get; init; }
|
||||
public int ChatMessageLimit { get; init; }
|
||||
public bool AllowMedia { get; init; }
|
||||
public int MaxMediaSizeBytes { get; init; }
|
||||
public List<string> AllowedMediaTypes { get; init; } = new();
|
||||
public bool AllowVoiceMessages { get; init; }
|
||||
public bool AllowForwarding { get; init; }
|
||||
public bool AllowReactions { get; init; }
|
||||
public bool AllowReplies { get; init; }
|
||||
public bool AllowQuoting { get; init; }
|
||||
public bool AllowMessageDeletion { get; init; }
|
||||
public bool ForbidCopying { get; init; }
|
||||
public bool AllowLinks { get; init; }
|
||||
public bool AllowPolls { get; init; }
|
||||
public bool AllowPinning { get; init; }
|
||||
}
|
||||
|
||||
public record WebRtcConfigDto
|
||||
{
|
||||
public bool Enabled { get; init; }
|
||||
public bool EnableVoiceCalls { get; init; }
|
||||
public bool EnableVideoCalls { get; init; }
|
||||
public bool EnableScreenSharing { get; init; }
|
||||
public string TurnHost { get; init; } = string.Empty;
|
||||
public int TurnPort { get; init; }
|
||||
}
|
||||
|
||||
public record KlipyConfigDto
|
||||
{
|
||||
public bool Enabled { get; init; }
|
||||
public string AppName { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
public record ImportConfigDto
|
||||
{
|
||||
public bool Enabled { get; init; }
|
||||
}
|
||||
|
||||
public record FederationConfigDto
|
||||
{
|
||||
public bool Enabled { get; init; }
|
||||
public string ServerDescription { get; init; } = string.Empty;
|
||||
public List<FederationDomainConfig> AllowedDomains { get; init; } = new();
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Knot.Modules.Settings.Application.Settings.DTOs;
|
||||
|
||||
public class SystemConfig
|
||||
{
|
||||
public string ServerTimezone { get; set; } = "UTC";
|
||||
public string DomainUrl { get; set; } = "https://example.com";
|
||||
public string AdminRoute { get; set; } = "admin";
|
||||
public bool EnableRegistration { get; set; } = true;
|
||||
}
|
||||
|
||||
public class StoriesConfig
|
||||
{
|
||||
public bool Enabled { get; set; } = true;
|
||||
public int MaxStoriesPerPeriod { get; set; } = 5;
|
||||
public int StoryLifetimeHours { get; set; } = 24;
|
||||
public bool TextStoriesEnabled { get; set; } = true;
|
||||
public int TextStoryDurationSeconds { get; set; } = 15;
|
||||
public int MediaStoryMaxDurationSeconds { get; set; } = 30;
|
||||
public int MaxMediaSizeBytes { get; set; } = 15 * 1024 * 1024;
|
||||
}
|
||||
|
||||
public class ChatsConfig
|
||||
{
|
||||
public bool SupportGroups { get; set; } = true;
|
||||
public int MaxGroupParticipants { get; set; } = 200000;
|
||||
public bool AutoCleanChats { get; set; } = false;
|
||||
public bool AllowChatToGroupConversion { get; set; } = true;
|
||||
public bool EnableFolders { get; set; } = true;
|
||||
}
|
||||
|
||||
public class MessagesConfig
|
||||
{
|
||||
public int DailyMessageLimitPerUser { get; set; } = 0;
|
||||
public int ChatMessageLimit { get; set; } = 0;
|
||||
public bool AllowMedia { get; set; } = true;
|
||||
public int MaxMediaSizeBytes { get; set; } = 50 * 1024 * 1024;
|
||||
public List<string> AllowedMediaTypes { get; set; } = new() { "image/jpeg", "image/png", "video/mp4", "image/gif" };
|
||||
public bool AllowVoiceMessages { get; set; } = true;
|
||||
public bool AllowForwarding { get; set; } = true;
|
||||
public bool AllowReactions { get; set; } = true;
|
||||
public bool AllowReplies { get; set; } = true;
|
||||
public bool AllowQuoting { get; set; } = true;
|
||||
public bool AllowMessageDeletion { get; set; } = true;
|
||||
public bool ForbidCopying { get; set; } = false;
|
||||
public bool AllowLinks { get; set; } = true;
|
||||
public bool AllowPolls { get; set; } = true;
|
||||
public bool AllowPinning { get; set; } = true;
|
||||
}
|
||||
|
||||
public class WebRtcConfig
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
public bool EnableVoiceCalls { get; set; } = true;
|
||||
public bool EnableVideoCalls { get; set; } = true;
|
||||
public bool EnableScreenSharing { get; set; } = true;
|
||||
public string TurnHost { get; set; } = string.Empty;
|
||||
public int TurnPort { get; set; } = 3478;
|
||||
public string TurnUser { get; set; } = string.Empty;
|
||||
public string TurnSecret { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class KlipyConfig
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
public string AppName { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class ImportConfig
|
||||
{
|
||||
public bool EnableTelegramImport { get; set; } = false;
|
||||
}
|
||||
|
||||
public class FederationDomainConfig
|
||||
{
|
||||
public string Domain { get; set; } = string.Empty;
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
public string? PublicKey { get; set; }
|
||||
public RemoteCapabilities? Capabilities { get; set; }
|
||||
}
|
||||
|
||||
public class RemoteCapabilities
|
||||
{
|
||||
public bool AllowMedia { get; set; }
|
||||
public bool AllowPolls { get; set; }
|
||||
public bool AllowVoiceMessages { get; set; }
|
||||
public bool AllowVideoCalls { get; set; }
|
||||
public bool AllowScreenSharing { get; set; }
|
||||
}
|
||||
|
||||
public class FederationConfig
|
||||
{
|
||||
public bool Enabled { get; set; } = false;
|
||||
public string ServerDescription { get; set; } = string.Empty;
|
||||
public string? PrivateKey { get; set; }
|
||||
public string? PublicKey { get; set; }
|
||||
public List<FederationDomainConfig> AllowedDomains { get; set; } = new();
|
||||
}
|
||||
|
||||
public class SystemSettingsDto
|
||||
{
|
||||
public SystemConfig System { get; set; } = new();
|
||||
public StoriesConfig Stories { get; set; } = new();
|
||||
public ChatsConfig Chats { get; set; } = new();
|
||||
public MessagesConfig Messages { get; set; } = new();
|
||||
public WebRtcConfig WebRtc { get; set; } = new();
|
||||
public KlipyConfig Klipy { get; set; } = new();
|
||||
public ImportConfig Import { get; set; } = new();
|
||||
public FederationConfig Federation { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Stories.Application.Abstractions;
|
||||
|
||||
public interface IKlipyClient
|
||||
{
|
||||
Task<bool> TestConnectionAsync(string apiKey, CancellationToken ct = default);
|
||||
Task<List<string>> SearchVideosAsync(string apiKey, string query, int limit, CancellationToken ct = default);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Knot.Modules.Stories.Domain;
|
||||
using Knot.Modules.Relations.Domain;
|
||||
|
||||
namespace Knot.Modules.Stories.Application.Abstractions;
|
||||
|
||||
public interface IStoriesDbContext
|
||||
{
|
||||
DbSet<Story> Stories { get; }
|
||||
DbSet<Friendship> Friendships { get; }
|
||||
DbSet<StoryViewer> StoryViewers { get; }
|
||||
DbSet<TEntity> Set<TEntity>() where TEntity : class;
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public interface IStoriesUnitOfWork
|
||||
{
|
||||
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
40
backend/src/Modules/Stories/Infrastructure/External/KlipyClient.cs
vendored
Normal file
40
backend/src/Modules/Stories/Infrastructure/External/KlipyClient.cs
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using Knot.Modules.Stories.Application.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Knot.Modules.Stories.Infrastructure.External;
|
||||
|
||||
public sealed class KlipyClient : IKlipyClient
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public KlipyClient(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
public async Task<bool> TestConnectionAsync(string apiKey, CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.klipy.co/v1/trending?limit=1");
|
||||
request.Headers.Add("X-API-KEY", apiKey);
|
||||
|
||||
using var response = await _httpClient.SendAsync(request, ct);
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<string>> SearchVideosAsync(string apiKey, string query, int limit, CancellationToken ct = default)
|
||||
{
|
||||
// В реальном проекте: десериализация ответа от Klipy API
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
@@ -47,9 +47,8 @@ export class StoryApi {
|
||||
}
|
||||
|
||||
static async removeStoryReaction(storyId: string, emoji: string) {
|
||||
return httpClient.request<{ message: string }>(`/stories/${storyId}/reaction`, {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify({ emoji }),
|
||||
return httpClient.request<{ message: string }>(`/stories/${storyId}/reaction/delete?emoji=${encodeURIComponent(emoji)}`, {
|
||||
method: 'POST',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user