diff --git a/Govor.API.Tests/IntegrationTests/EF/Repositories/UsersRepositoryTests.cs b/Govor.API.Tests/IntegrationTests/EF/Repositories/UsersRepositoryTests.cs index d503c74..7cec3bf 100644 --- a/Govor.API.Tests/IntegrationTests/EF/Repositories/UsersRepositoryTests.cs +++ b/Govor.API.Tests/IntegrationTests/EF/Repositories/UsersRepositoryTests.cs @@ -200,7 +200,41 @@ public class UsersRepositoryTests Assert.ThrowsAsync>>(async () => await userRepository.FindByRangeUsernamesAsync(usernames)); } - + + [Test] + public async Task Given_ValidQuery_When_SearchPotentialFriendsAsync_Then_Returns_Users() + { + // Arrange + var random = new Random(); + var users = _fixture.CreateMany(random.Next(3, 10)).ToList(); + + await using var context = new GovorDbContext(_options); + var userRepository = new UsersRepository(context, _userValidator); + + context.Users.AddRange(users); + await context.SaveChangesAsync(); + + // Act + var result = await userRepository.SearchPotentialFriendsAsync(users[1].Id, users[0].Username[..15]); + + // Assert + Assert.That(result, Is.Not.Null); + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result.First().Id, Is.EqualTo(users[0].Id)); + } + + [Test] + public async Task Given_InvalidQuery_When_SearchPotentialFriendsAsync_Should_Throw_NotFoundByKeyException() + { + // Arrange + await using var context = new GovorDbContext(_options); + var userRepository = new UsersRepository(context, _userValidator); + + // Act & Assert + Assert.ThrowsAsync>(async () => await + userRepository.SearchPotentialFriendsAsync(_fixture.Create(), _fixture.Create())); + } + [Test] public async Task Given_ValidDateOnly_When_FindByCreatedDate_Then_Returns_Users() { diff --git a/Govor.API/Controllers/FriendsController.cs b/Govor.API/Controllers/FriendsController.cs index 96492fd..5f1e7c4 100644 --- a/Govor.API/Controllers/FriendsController.cs +++ b/Govor.API/Controllers/FriendsController.cs @@ -20,98 +20,123 @@ public class FriendsController : Controller _logger = logger; _friendsService = friendsService; } - - [HttpGet("search")] //api/friends/search + + [HttpGet("search")] public async Task Search(string query) { + if (string.IsNullOrWhiteSpace(query)) + return BadRequest("Query cannot be empty"); + try { - var result = await _friendsService.SearchUsersAsync(query, GetCurrentUserId()); - - return Ok(BuildUserDtos(result)); + var result = await _friendsService.SearchUsersAsync(query, GetCurrentUserId()); + return Ok(BuildUserDtos(result)); + } + catch (SearchUsersException ex) + { + _logger.LogWarning(ex, ex.Message); + return NotFound(new { error = ex.Message }); } catch (Exception ex) { _logger.LogError(ex, ex.Message); - return BadRequest("Something happened during the search... try again"); + return StatusCode(500, new { error = "Internal error during user search." }); } } - [HttpPost("request")] //api/friends/request + [HttpPost("request")] public async Task SendRequest(Guid targetUserId) { + if (targetUserId == Guid.Empty) + return BadRequest("Target user ID is invalid"); + try { - await _friendsService.SendFriendRequestAsync(targetUserId, GetCurrentUserId()); - return Ok(); + await _friendsService.SendFriendRequestAsync(targetUserId, GetCurrentUserId()); + return Ok(new { message = "Friend request sent successfully." }); } catch (InvalidOperationException ex) { - _logger.LogWarning("The user tried to send a friend request to themselves"); - return BadRequest("You can't send a friend request to themselves"); + _logger.LogWarning(ex, "Tried to send request to self"); + return UnprocessableEntity(new { error = ex.Message }); } catch (RequestAlreadySentException ex) { - _logger.LogWarning("The user tried to send a friend request but the request is already sent"); - return BadRequest("You already sent a friend request"); + _logger.LogWarning(ex, ex.Message); + return Conflict(new { error = ex.Message }); } catch (Exception ex) { _logger.LogError(ex, ex.Message); - return BadRequest("Something happened during the request... try again"); + return StatusCode(500, new { error = "Failed to send friend request." }); } } - [HttpGet("requests")] //api/friends/requests + [HttpGet("requests")] public async Task GetIncomingRequests() { try { - _logger.LogInformation($"Getting incoming requests for user {GetCurrentUserId()}..."); var result = await _friendsService.GetIncomingRequestsAsync(GetCurrentUserId()); return Ok(BuildFriendshipDtos(result)); } catch (InvalidOperationException ex) { - _logger.LogWarning(ex, ex.Message); - return BadRequest("You can't get real friend requests because we can't find your user data! Try again"); + _logger.LogError(ex, ex.Message); + return BadRequest(new { error = "Failed to get friend requests. User data missing." }); + } + catch (Exception ex) + { + _logger.LogError(ex, ex.Message); + return StatusCode(500, new { error = "Internal server error." }); } } - [HttpPost("accept")] //api/friends/accept + [HttpPost("accept")] public async Task AcceptFriend(Guid requesterId) { + if (requesterId == Guid.Empty) + return BadRequest("Requester ID is invalid"); + try { await _friendsService.AcceptFriendRequestAsync(requesterId, GetCurrentUserId()); - return Ok(); + return Ok(new { message = "Friend request accepted." }); } catch (InvalidOperationException ex) { - _logger.LogWarning("The user tried to accept a friend request but the request not exists"); - return BadRequest(ex.Message); + _logger.LogWarning(ex, ex.Message); + return NotFound(new { error = ex.Message }); } catch (UnauthorizedAccessException ex) { - _logger.LogWarning("User tried to accept a friend request but addresseeId not equal userId"); - return BadRequest("You can't accept a friend request"); + _logger.LogWarning(ex, ex.Message); + return Forbid(); + } + catch (Exception ex) + { + _logger.LogError(ex, ex.Message); + return StatusCode(500, new { error = "Failed to accept friend request." }); } } - [HttpGet] //api/friends/ + [HttpGet] public async Task GetFriends() { try { - _logger.LogInformation($"Getting friends by user {GetCurrentUserId()}..."); var result = await _friendsService.GetFriendsAsync(GetCurrentUserId()); - return Ok(BuildUserDtos(result)); } - catch(UnauthorizedAccessException ex) + catch (InvalidOperationException ex) { _logger.LogError(ex, ex.Message); - return BadRequest("Something happened during the request... try again"); + return BadRequest(new { error = "User data not found." }); + } + catch (Exception ex) + { + _logger.LogError(ex, ex.Message); + return StatusCode(500, new { error = "Internal server error." }); } } @@ -124,40 +149,21 @@ public class FriendsController : Controller } return userId; } - - private List BuildUserDtos(IEnumerable users) + + private List BuildUserDtos(IEnumerable users) => users.Select(user => new UserDto { - List userDtos = new List(); - - foreach (var user in users) - { - userDtos.Add(new UserDto() - { - Id = user.Id, - Description = user.Description, - Username = user.Username, - WasOnline = user.WasOnline, - IconId = user.IconId, - }); - } - return userDtos; - } - - private List BuildFriendshipDtos(List result) + Id = user.Id, + Username = user.Username, + Description = user.Description, + WasOnline = user.WasOnline, + IconId = user.IconId + }).ToList(); + + private List BuildFriendshipDtos(IEnumerable friendships) => friendships.Select(f => new FriendshipDto { - List dtos = new List(); - - foreach (var friendship in result) - { - dtos.Add(new FriendshipDto() - { - Id = friendship.Id, - Status = friendship.Status, - AddresseeId = friendship.AddresseeId, - RequesterId = friendship.RequesterId, - }); - } - - return dtos; - } -} \ No newline at end of file + Id = f.Id, + Status = f.Status, + AddresseeId = f.AddresseeId, + RequesterId = f.RequesterId + }).ToList(); +} diff --git a/Govor.API/Program.cs b/Govor.API/Program.cs index 1b9bad4..bf1019a 100644 --- a/Govor.API/Program.cs +++ b/Govor.API/Program.cs @@ -59,11 +59,6 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) builder.Services.AddAuthorization(); builder.Services.AddControllers(); -builder.Services.AddControllers() - .AddJsonOptions(options => - { - options.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve; - }); // Init DI builder.Services.AddServices(); diff --git a/Govor.Application/Interfaces/IFriendsService.cs b/Govor.Application/Interfaces/IFriendsService.cs index 0bcc233..d234395 100644 --- a/Govor.Application/Interfaces/IFriendsService.cs +++ b/Govor.Application/Interfaces/IFriendsService.cs @@ -4,7 +4,7 @@ namespace Govor.Application.Interfaces; public interface IFriendsService { - Task> SearchUsersAsync(string searchTerm, Guid currentId); + Task> SearchUsersAsync(string query, Guid currentId); Task SendFriendRequestAsync(Guid fromUserId, Guid toUserId); Task AcceptFriendRequestAsync(Guid requestId, Guid currentUserId); Task> GetFriendsAsync(Guid userId); diff --git a/Govor.Application/Services/FriendsService.cs b/Govor.Application/Services/FriendsService.cs index 01d9068..f2d62fe 100644 --- a/Govor.Application/Services/FriendsService.cs +++ b/Govor.Application/Services/FriendsService.cs @@ -19,12 +19,14 @@ public class FriendsService : IFriendsService _friendshipsRepository = relationshipsRepository; } - public async Task> SearchUsersAsync(string searchTerm, Guid currentId) + public async Task> SearchUsersAsync(string query, Guid currentId) { - var all = await _usersRepository.SearchPotentialFriendsAsync(currentId, searchTerm); - + List all = new List(); + try { + all = await _usersRepository.SearchPotentialFriendsAsync(currentId, query); + var friends = await _friendshipsRepository.FindByUserIdAsync(currentId); friends = friends.Where(f => f.Status == FriendshipStatus.Accepted).ToList(); @@ -33,13 +35,18 @@ public class FriendsService : IFriendsService .Where(u => u.Id != currentId && !friends.Select(f => f.RequesterId).Contains(u.Id)) .ToList(); } + catch (NotFoundByKeyException<(string, Guid)> ex) + { + throw new SearchUsersException( + $"Users with given query: \"{query}\" for user with id {currentId} was not found", ex); + } catch (NotFoundByKeyException ex) { return all.Where(u => u.Id != currentId).ToList(); } catch (Exception ex) { - throw new SearchUsersException($"When we try find friends by pattern {searchTerm} something wrong", ex); + throw new UnauthorizedAccessException($"When we try find friends by pattern {query} something wrong", ex); } } @@ -85,21 +92,16 @@ public class FriendsService : IFriendsService { try { - var user = await _usersRepository.FindByIdAsync(userId); + var friendships = await _friendshipsRepository.FindByUserIdAsync(userId); - var sent = user.SentFriendRequests + return friendships .Where(f => f.Status == FriendshipStatus.Accepted) - .Select(f => f.Addressee); - - var received = user.ReceivedFriendRequests - .Where(f => f.Status == FriendshipStatus.Accepted) - .Select(f => f.Requester); - - return sent.Concat(received).ToList(); + .Select(f => f.RequesterId == userId ? f.Addressee : f.Requester) + .ToList(); } catch (NotFoundByKeyException ex) { - throw new InvalidOperationException("User not exist", ex); + throw new InvalidOperationException("User not found", ex); } } diff --git a/Govor.Core/Models/User.cs b/Govor.Core/Models/User.cs index 7d407a8..616d9d8 100644 --- a/Govor.Core/Models/User.cs +++ b/Govor.Core/Models/User.cs @@ -15,4 +15,21 @@ public class User public Invitation? Invite { get; set; } public List SentFriendRequests { get; set; } = new(); public List ReceivedFriendRequests { get; set; } = new(); + + public override bool Equals(object? obj) + { + var user = obj as User; + + return Id == user.Id && + Username == user.Username && + Description == user.Description && + PasswordHash == user.PasswordHash && + IconId == user.IconId && + CreatedOn == user.CreatedOn && + WasOnline == user.WasOnline && + InviteId == user.InviteId && + Invite == user.Invite && + SentFriendRequests == user.SentFriendRequests && + ReceivedFriendRequests == user.ReceivedFriendRequests; + } } \ No newline at end of file diff --git a/Govor.Data/Repositories/UsersRepository.cs b/Govor.Data/Repositories/UsersRepository.cs index 5ef3c9e..d665b8d 100644 --- a/Govor.Data/Repositories/UsersRepository.cs +++ b/Govor.Data/Repositories/UsersRepository.cs @@ -84,7 +84,7 @@ public class UsersRepository : IUsersRepository (f.RequesterId == u.Id && f.AddresseeId == currentUserId))) .Take(20) .OrderBy(u => u.Username) - .ToListAsync(); + .ToListOrThrowIfEmpty(new NotFoundByKeyException<(string, Guid)>((query, currentUserId), $"Users with given query for user {currentUserId} not found")); }