From 606211365509243bd87450de9277532d7f6634c5 Mon Sep 17 00:00:00 2001 From: Artemy <109195690+stalcker2288969@users.noreply.github.com> Date: Sun, 6 Jul 2025 15:44:45 +0700 Subject: [PATCH] Add unit tests and implement group repository interfaces Introduces comprehensive unit tests for MessageService covering send, edit, and delete message scenarios. Adds and implements IGroupsRepository, IGroupsReader, and IGroupsWriter interfaces with method stubs in GroupRepository. Updates ChatGroup model with Description and ImageId fields, and registers GroupRepository in DI. Fixes parameter order in SendMessage record. --- .../UnitTests/Services/MessageServiceTests.cs | 318 ++++++++++++++++++ .../ConfigurationProgramExtensions.cs | 2 + Govor.API/Hubs/ChatsHub.cs | 2 +- .../Messages/Parameters/SendMessage.cs | 2 +- Govor.Core/Models/ChatGroup.cs | 3 + .../Repositories/Groups/IGroupsReader.cs | 9 +- .../Repositories/Groups/IGroupsRepository.cs | 2 +- .../Repositories/Groups/IGroupsWriter.cs | 6 +- Govor.Data/Repositories/GroupRepository.cs | 60 +++- 9 files changed, 397 insertions(+), 7 deletions(-) create mode 100644 Govor.API.Tests/UnitTests/Services/MessageServiceTests.cs diff --git a/Govor.API.Tests/UnitTests/Services/MessageServiceTests.cs b/Govor.API.Tests/UnitTests/Services/MessageServiceTests.cs new file mode 100644 index 0000000..d789064 --- /dev/null +++ b/Govor.API.Tests/UnitTests/Services/MessageServiceTests.cs @@ -0,0 +1,318 @@ +using Govor.Application.Exceptions.VerifyFriendship; +using Govor.Application.Interfaces; +using Govor.Application.Interfaces.Messages.Parameters; +using Govor.Application.Services; +using Govor.Core.Models; +using Govor.Core.Repositories.Groups; +using Govor.Core.Repositories.Messages; +using Govor.Core.Repositories.Users; +using Govor.Data.Repositories.Exceptions; +using Microsoft.Extensions.Logging; +using Moq; + +namespace Govor.API.Tests.UnitTests.Services; + +[TestFixture] +public class MessageServiceTests +{ + private Mock _mockMessagesRepo; + private Mock _mockUsersRepo; + private Mock _mockGroupsRepo; + private Mock _mockVerifyFriendship; + private Mock> _mockLogger; + private MessageService _messageService; + + [SetUp] + public void SetUp() + { + _mockMessagesRepo = new Mock(); + _mockUsersRepo = new Mock(); + _mockGroupsRepo = new Mock(); + _mockVerifyFriendship = new Mock(); + _mockLogger = new Mock>(); + + _messageService = new MessageService( + _mockMessagesRepo.Object, + _mockUsersRepo.Object, + _mockGroupsRepo.Object, + _mockVerifyFriendship.Object, + _mockLogger.Object); + } + + + // Test for SendMessageAsync action + [Test] + public async Task SendMessageAsync_ToUser_Success() + { + // Arrange + var senderId = Guid.NewGuid(); + var recipientId = Guid.NewGuid(); + + var sendMessageParams = new SendMessage("Hello", + null, + recipientId, + RecipientType.User, + senderId, + DateTime.UtcNow, + new List()); + + _mockUsersRepo.Setup(r => r.ExistsByIdAsync(recipientId)).ReturnsAsync(true); + _mockVerifyFriendship.Setup(v => v.VerifyAsync(senderId, recipientId)).Returns(Task.CompletedTask); + _mockMessagesRepo.Setup(r => r.AddAsync(It.IsAny())).Returns(Task.CompletedTask); + + // Act + var result = await _messageService.SendMessageAsync(sendMessageParams); + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.IsSuccess, Is.EqualTo(true)); + Assert.That(result.Exception, Is.Null); + + _mockMessagesRepo.Verify(r => r.AddAsync(It.Is(m => + m.SenderId == senderId && + m.RecipientId == recipientId && + m.RecipientType == RecipientType.User && + m.EncryptedContent == "Hello")), Times.Once); + } + + [Test] + public async Task SendMessageAsync_ToGroup_Success() + { + // Arrange + var senderId = Guid.NewGuid(); + var groupId = Guid.NewGuid(); + + var sendMessageParams = new SendMessage("Hello Group", + null, + groupId, + RecipientType.Group, + senderId, + DateTime.UtcNow, + new List()); + + _mockGroupsRepo.Setup(r => r.Exists(groupId)).Returns(true); + _mockGroupsRepo.Setup(r => r.IsUserMemberOfGroupAsync(senderId, groupId)).Returns(true); + _mockMessagesRepo.Setup(r => r.AddAsync(It.IsAny())).Returns(Task.CompletedTask); + + // Act + var result = await _messageService.SendMessageAsync(sendMessageParams); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.IsSuccess, Is.EqualTo(true)); + Assert.That(result.Exception, Is.Null); + _mockMessagesRepo.Verify(r => r.AddAsync(It.Is(m => + m.RecipientId == groupId && + m.RecipientType == RecipientType.Group)), Times.Once); + } + + [Test] + public async Task SendMessageAsync_ToUser_RecipientNotFound_ReturnsFailure() + { + // Arrange + var senderId = Guid.NewGuid(); + var recipientId = Guid.NewGuid(); + + var sendMessageParams = new SendMessage("Hello", + null, + recipientId, + RecipientType.User, + senderId, + DateTime.UtcNow, + new List()); + + _mockUsersRepo.Setup(r => r.ExistsByIdAsync(recipientId)).ReturnsAsync(false); + + // Act + var result = await _messageService.SendMessageAsync(sendMessageParams); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.IsSuccess, Is.EqualTo(false)); + Assert.That(result.Exception, Is.Not.Null); + Assert.That(result.Exception,Is.TypeOf()); + Assert.That(result.MessageId, Is.EqualTo(Guid.Empty)); + } + + [Test] + public async Task SendMessageAsync_ToUser_FriendshipVerificationFails_ReturnsFailure() + { + // Arrange + var senderId = Guid.NewGuid(); + var recipientId = Guid.NewGuid(); + + var sendMessageParams = new SendMessage("Hello", + null, + recipientId, + RecipientType.User, + senderId, + DateTime.UtcNow, + new List() + ); + + _mockUsersRepo.Setup(r => r.ExistsByIdAsync(recipientId)).ReturnsAsync(true); + _mockVerifyFriendship.Setup(v => v.VerifyAsync(senderId, recipientId)).ThrowsAsync(new FriendshipException("Not friends")); + + // Act + var result = await _messageService.SendMessageAsync(sendMessageParams); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.IsSuccess, Is.EqualTo(false)); + Assert.That(result.Exception, Is.Not.Null); + Assert.That(result.Exception,Is.TypeOf()); + Assert.That(result.MessageId, Is.EqualTo(Guid.Empty)); + } + + // Test for EditMessageAsync action + [Test] + public async Task EditMessageAsync_Success() + { + // Arrange + var editorId = Guid.NewGuid(); + var messageId = Guid.NewGuid(); + + var originalMessage = new Message + { + Id = messageId, + SenderId = editorId, + EncryptedContent = "Old", + RecipientId = Guid.NewGuid(), + RecipientType = RecipientType.User + }; + + var editParams = new EditMessage(editorId, messageId, "New Content", DateTime.UtcNow); + + _mockMessagesRepo.Setup(r => r.FindByIdAsync(messageId)).ReturnsAsync(originalMessage); + _mockMessagesRepo.Setup(r => r.UpdateAsync(It.IsAny())).Returns(Task.CompletedTask); + + // Act + var result = await _messageService.EditMessageAsync(editParams); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.IsSuccess, Is.EqualTo(true)); + Assert.That(result.OriginalMessage, Is.Not.Null); + + Assert.That(messageId, Is.EqualTo(result.OriginalMessage!.Id)); + + _mockMessagesRepo.Verify(r => r.UpdateAsync(It.Is(m => + m.Id == messageId && + m.EncryptedContent == "New Content" && + m.IsEdited == true && + m.EditedAt == editParams.EditedAt)), Times.Once); + } + + [Test] + public async Task EditMessageAsync_MessageNotFound_ReturnsFailure() + { + // Arrange + var editorId = Guid.NewGuid(); + var messageId = Guid.NewGuid(); + var editParams = new EditMessage(editorId, messageId, "New Content", DateTime.UtcNow); + + _mockMessagesRepo.Setup(r => r.FindByIdAsync(messageId)). + ThrowsAsync(new NotFoundByKeyException(messageId)); + + // Act + var result = await _messageService.EditMessageAsync(editParams); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.IsSuccess, Is.EqualTo(false)); + Assert.That(result.Exception, Is.Not.Null); + Assert.That(result.Exception,Is.TypeOf>()); + Assert.That(result.OriginalMessage, Is.Null); + } + + [Test] + public async Task EditMessageAsync_NotSender_ReturnsFailure() + { + // Arrange + var editorId = Guid.NewGuid(); + var senderId = Guid.NewGuid(); // Different from editorId + var messageId = Guid.NewGuid(); + var originalMessage = new Message { Id = messageId, SenderId = senderId, EncryptedContent = "Old" }; + var editParams = new EditMessage(editorId, messageId, "New Content", DateTime.UtcNow); + + _mockMessagesRepo.Setup(r => r.FindByIdAsync(messageId)).ReturnsAsync(originalMessage); + + // Act + var result = await _messageService.EditMessageAsync(editParams); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.IsSuccess, Is.EqualTo(false)); + Assert.That(result.Exception, Is.Not.Null); + Assert.That(result.Exception,Is.TypeOf()); + Assert.That(result.OriginalMessage, Is.Null); + } + + // Test for DeleteMessageAsync action + [Test] + public async Task DeleteMessageAsync_Success() + { + // Arrange + var deleterId = Guid.NewGuid(); + var messageId = Guid.NewGuid(); + var originalMessage = new Message { Id = messageId, SenderId = deleterId, RecipientId = Guid.NewGuid(), RecipientType = RecipientType.User }; + var deleteParams = new DeleteMessage(deleterId, messageId); + + _mockMessagesRepo.Setup(r => r.FindByIdAsync(messageId)).ReturnsAsync(originalMessage); + _mockMessagesRepo.Setup(r => r.RemoveAsync(messageId)).Returns(Task.CompletedTask); + + // Act + var result = await _messageService.DeleteMessageAsync(deleteParams); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.IsSuccess, Is.EqualTo(true)); + Assert.That(result.OriginalMessage, Is.Not.Null); + Assert.That(messageId, Is.EqualTo(result.OriginalMessage!.Id)); + _mockMessagesRepo.Verify(r => r.RemoveAsync(messageId), Times.Once); + } + + [Test] + public async Task DeleteMessageAsync_MessageNotFound_ReturnsFailure() + { + // Arrange + var deleterId = Guid.NewGuid(); + var messageId = Guid.NewGuid(); + var deleteParams = new DeleteMessage(deleterId, messageId); + + _mockMessagesRepo.Setup(r => r.FindByIdAsync(messageId)). + ThrowsAsync(new NotFoundByKeyException(messageId)); + + // Act + var result = await _messageService.DeleteMessageAsync(deleteParams); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.IsSuccess, Is.EqualTo(false)); + Assert.That(result.Exception, Is.Not.Null); + Assert.That(result.Exception,Is.TypeOf>()); + Assert.That(result.OriginalMessage, Is.Null); + } + + [Test] + public async Task DeleteMessageAsync_NotSender_ReturnsFailure() + { + // Arrange + var deleterId = Guid.NewGuid(); + var senderId = Guid.NewGuid(); // Different + var messageId = Guid.NewGuid(); + var originalMessage = new Message { Id = messageId, SenderId = senderId }; + var deleteParams = new DeleteMessage(deleterId, messageId); + + _mockMessagesRepo.Setup(r => r.FindByIdAsync(messageId)).ReturnsAsync(originalMessage); + + // Act + var result = await _messageService.DeleteMessageAsync(deleteParams); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.IsSuccess, Is.EqualTo(false)); + Assert.That(result.Exception, Is.Not.Null); + Assert.That(result.Exception,Is.TypeOf()); + Assert.That(result.OriginalMessage, Is.Null); + } +} \ No newline at end of file diff --git a/Govor.API/Extensions/ConfigurationProgramExtensions.cs b/Govor.API/Extensions/ConfigurationProgramExtensions.cs index 985c5b7..44d19da 100644 --- a/Govor.API/Extensions/ConfigurationProgramExtensions.cs +++ b/Govor.API/Extensions/ConfigurationProgramExtensions.cs @@ -13,6 +13,7 @@ using Govor.Core.Infrastructure.Validators; using Govor.Core.Models; using Govor.Core.Repositories.Admins; using Govor.Core.Repositories.Friendships; +using Govor.Core.Repositories.Groups; using Govor.Core.Repositories.Invaites; using Govor.Core.Repositories.MediasAttachments; using Govor.Core.Repositories.Messages; @@ -60,6 +61,7 @@ public static class ConfigurationProgramExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); } public static void AddValidators(this IServiceCollection services) diff --git a/Govor.API/Hubs/ChatsHub.cs b/Govor.API/Hubs/ChatsHub.cs index dd7f4eb..737db0f 100644 --- a/Govor.API/Hubs/ChatsHub.cs +++ b/Govor.API/Hubs/ChatsHub.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.SignalR; namespace Govor.API.Hubs; -[Authorize] +[Authorize] // api/chats public class ChatsHub : Hub { private readonly ILogger _logger; diff --git a/Govor.Application/Interfaces/Messages/Parameters/SendMessage.cs b/Govor.Application/Interfaces/Messages/Parameters/SendMessage.cs index b302276..f59e222 100644 --- a/Govor.Application/Interfaces/Messages/Parameters/SendMessage.cs +++ b/Govor.Application/Interfaces/Messages/Parameters/SendMessage.cs @@ -5,8 +5,8 @@ namespace Govor.Application.Interfaces.Messages.Parameters; public record SendMessage( string EncryptContent, Guid? ReplyToMessageId, - RecipientType RecipientType, Guid RecipientId, + RecipientType RecipientType, Guid FromUserId, DateTime SendAt, IEnumerable Media); \ No newline at end of file diff --git a/Govor.Core/Models/ChatGroup.cs b/Govor.Core/Models/ChatGroup.cs index 1912474..b1ae3c2 100644 --- a/Govor.Core/Models/ChatGroup.cs +++ b/Govor.Core/Models/ChatGroup.cs @@ -4,6 +4,9 @@ public class ChatGroup { public Guid Id { get; set; } public string Name { get; set; } + public string Description { get; set; } + public Guid ImageId { get; set; } + public List InviteCode { get; set; } public bool IsChannel { get; set; } public bool IsPrivate { get; set; } diff --git a/Govor.Core/Repositories/Groups/IGroupsReader.cs b/Govor.Core/Repositories/Groups/IGroupsReader.cs index df38bb8..752817d 100644 --- a/Govor.Core/Repositories/Groups/IGroupsReader.cs +++ b/Govor.Core/Repositories/Groups/IGroupsReader.cs @@ -1,6 +1,13 @@ +using System.Text.RegularExpressions; + namespace Govor.Core.Repositories.Groups; public interface IGroupsReader { - + public Task> GetAllAsync(); + public Task GetByIdAsync(Guid id); + public Task> FindByNameAsync(string name); + public Task> GetByAdminIdAsync(Guid adminId); + public Task> GetByUserIdAsync(Guid adminId); + public bool IsUserMemberOfGroupAsync(Guid userId, Guid groupId); } \ No newline at end of file diff --git a/Govor.Core/Repositories/Groups/IGroupsRepository.cs b/Govor.Core/Repositories/Groups/IGroupsRepository.cs index cf9482f..9b2faa7 100644 --- a/Govor.Core/Repositories/Groups/IGroupsRepository.cs +++ b/Govor.Core/Repositories/Groups/IGroupsRepository.cs @@ -1,6 +1,6 @@ namespace Govor.Core.Repositories.Groups; -public interface IGroupsRepository : IGroupsReader, IGroupsExist, IGroupsWriter +public interface IGroupsRepository : IGroupsReader, IGroupsWriter, IGroupsExist { } \ No newline at end of file diff --git a/Govor.Core/Repositories/Groups/IGroupsWriter.cs b/Govor.Core/Repositories/Groups/IGroupsWriter.cs index 2fc631c..dbb2ce4 100644 --- a/Govor.Core/Repositories/Groups/IGroupsWriter.cs +++ b/Govor.Core/Repositories/Groups/IGroupsWriter.cs @@ -1,6 +1,10 @@ +using System.Text.RegularExpressions; + namespace Govor.Core.Repositories.Groups; public interface IGroupsWriter { - + Task Add(Group group); + Task Update(Group group); + Task Remove(Guid groupId); } \ No newline at end of file diff --git a/Govor.Data/Repositories/GroupRepository.cs b/Govor.Data/Repositories/GroupRepository.cs index 18b2dbb..cfbc592 100644 --- a/Govor.Data/Repositories/GroupRepository.cs +++ b/Govor.Data/Repositories/GroupRepository.cs @@ -1,6 +1,62 @@ +using System.Text.RegularExpressions; +using Govor.Core.Models; +using Govor.Core.Repositories.Groups; + namespace Govor.Data.Repositories; -public class GroupRepository +public class GroupRepository : IGroupsRepository { + public Task> GetAllAsync() + { + throw new NotImplementedException(); + } + + public Task GetByIdAsync(Guid id) + { + throw new NotImplementedException(); + } + + public Task> FindByNameAsync(string name) + { + throw new NotImplementedException(); + } -} \ No newline at end of file + public Task> GetByAdminIdAsync(Guid adminId) + { + throw new NotImplementedException(); + } + + public Task> GetByUserIdAsync(Guid adminId) + { + throw new NotImplementedException(); + } + + public Task Add(Group group) + { + throw new NotImplementedException(); + } + + public Task Update(Group group) + { + throw new NotImplementedException(); + } + + public Task Remove(Guid groupId) + { + throw new NotImplementedException(); + } + public bool Exists(Guid groupId) + { + throw new NotImplementedException(); + } + + public bool Exists(ChatGroup chatGroup) + { + throw new NotImplementedException(); + } + + public bool IsUserMemberOfGroupAsync(Guid userId, Guid groupId) + { + throw new NotImplementedException(); + } +}