From 5c30fc21e000cbbb42c5ddde474cb65aa73ca1be Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Wed, 11 Oct 2023 14:27:05 +0300 Subject: [PATCH] fix formatting, fix models, add more tests --- .../ApiExtentions/GraphQlExtention.cs | 1 + .../Queries/WishlistsQuery.cs | 15 ++ .../IRepositories/IWishlistRepository.cs | 6 +- .../IServices/IWishlistService.cs | 3 + .../Models/CreateDtos/WishlistCreateDto.cs | 1 + .../Models/Dtos/MessageDto.cs | 1 + .../Models/Dtos/WishlistDto.cs | 1 + .../Entities/Message.cs | 3 +- .../Entities/Wishlist.cs | 7 +- .../Enums/MessageRoles.cs | 2 +- .../Services/WishlistsService.cs | 19 ++- .../PersistanceExtentions/DbInitialaizer.cs | 8 +- .../Repositories/WishlistsRepository.cs | 7 + .../Tests/WishlistsTests.cs | 154 ++++++++++++++++-- 14 files changed, 203 insertions(+), 25 deletions(-) create mode 100644 ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs diff --git a/ShoppingAssistantApi.Api/ApiExtentions/GraphQlExtention.cs b/ShoppingAssistantApi.Api/ApiExtentions/GraphQlExtention.cs index 9a214e5..c534a56 100644 --- a/ShoppingAssistantApi.Api/ApiExtentions/GraphQlExtention.cs +++ b/ShoppingAssistantApi.Api/ApiExtentions/GraphQlExtention.cs @@ -12,6 +12,7 @@ public static class GraphQlExtention .AddQueryType() .AddTypeExtension() .AddTypeExtension() + .AddTypeExtension() .AddMutationType() .AddTypeExtension() .AddTypeExtension() diff --git a/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs b/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs new file mode 100644 index 0000000..896ccd4 --- /dev/null +++ b/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs @@ -0,0 +1,15 @@ +using HotChocolate.Authorization; +using ShoppingAssistantApi.Application.IServices; +using ShoppingAssistantApi.Application.Models.Dtos; +using ShoppingAssistantApi.Application.Paging; + +namespace ShoppingAssistantApi.Api.Queries; + +[ExtendObjectType(OperationTypeNames.Query)] +public class WishlistsQuery +{ + [Authorize] + public Task> GetPersonalWishlistsPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken, + [Service] IWishlistsService wishlistsService) + => wishlistsService.GetPersonalWishlistsPageAsync(pageNumber, pageSize, cancellationToken); +} diff --git a/ShoppingAssistantApi.Application/IRepositories/IWishlistRepository.cs b/ShoppingAssistantApi.Application/IRepositories/IWishlistRepository.cs index f5789e5..b8c6abf 100644 --- a/ShoppingAssistantApi.Application/IRepositories/IWishlistRepository.cs +++ b/ShoppingAssistantApi.Application/IRepositories/IWishlistRepository.cs @@ -1,5 +1,9 @@ +using System.Linq.Expressions; using ShoppingAssistantApi.Domain.Entities; namespace ShoppingAssistantApi.Application.IRepositories; -public interface IWishlistsRepository : IBaseRepository { } +public interface IWishlistsRepository : IBaseRepository +{ + public Task GetWishlistAsync(Expression> predicate, CancellationToken cancellationToken); +} diff --git a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs index 5ba8be9..696909e 100644 --- a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs +++ b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs @@ -1,5 +1,6 @@ using ShoppingAssistantApi.Application.Models.CreateDtos; using ShoppingAssistantApi.Application.Models.Dtos; +using ShoppingAssistantApi.Application.Paging; namespace ShoppingAssistantApi.Application.IServices; @@ -8,4 +9,6 @@ public interface IWishlistsService Task StartPersonalWishlistAsync(WishlistCreateDto dto, CancellationToken cancellationToken); Task AddMessageToPersonalWishlistAsync(string wishlistId, MessageCreateDto dto, CancellationToken cancellationToken); + + Task> GetPersonalWishlistsPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken); } diff --git a/ShoppingAssistantApi.Application/Models/CreateDtos/WishlistCreateDto.cs b/ShoppingAssistantApi.Application/Models/CreateDtos/WishlistCreateDto.cs index 375f70d..7a5f7a6 100644 --- a/ShoppingAssistantApi.Application/Models/CreateDtos/WishlistCreateDto.cs +++ b/ShoppingAssistantApi.Application/Models/CreateDtos/WishlistCreateDto.cs @@ -3,5 +3,6 @@ namespace ShoppingAssistantApi.Application.Models.CreateDtos; public class WishlistCreateDto { public required string Type { get; set; } + public required string FirstMessageText { get; set; } } diff --git a/ShoppingAssistantApi.Application/Models/Dtos/MessageDto.cs b/ShoppingAssistantApi.Application/Models/Dtos/MessageDto.cs index d6882a1..a33c92e 100644 --- a/ShoppingAssistantApi.Application/Models/Dtos/MessageDto.cs +++ b/ShoppingAssistantApi.Application/Models/Dtos/MessageDto.cs @@ -5,6 +5,7 @@ public class MessageDto public required string Id { get; set; } public required string Text { get; set; } + public required string Role { get; set; } public string? CreatedById { get; set; } = null; diff --git a/ShoppingAssistantApi.Application/Models/Dtos/WishlistDto.cs b/ShoppingAssistantApi.Application/Models/Dtos/WishlistDto.cs index 6e21f8e..9398c26 100644 --- a/ShoppingAssistantApi.Application/Models/Dtos/WishlistDto.cs +++ b/ShoppingAssistantApi.Application/Models/Dtos/WishlistDto.cs @@ -5,6 +5,7 @@ public class WishlistDto public required string Id { get; set; } public required string Name { get; set; } + public required string Type { get; set; } public string CreatedById { get; set; } = null!; diff --git a/ShoppingAssistantApi.Domain/Entities/Message.cs b/ShoppingAssistantApi.Domain/Entities/Message.cs index a2621bf..8a50457 100644 --- a/ShoppingAssistantApi.Domain/Entities/Message.cs +++ b/ShoppingAssistantApi.Domain/Entities/Message.cs @@ -6,7 +6,8 @@ namespace ShoppingAssistantApi.Domain.Entities; public class Message : EntityBase { public required string Text { get; set; } + public required string Role { get; set; } - public ObjectId? WishlistId { get; set; } = null; + public ObjectId WishlistId { get; set; } } diff --git a/ShoppingAssistantApi.Domain/Entities/Wishlist.cs b/ShoppingAssistantApi.Domain/Entities/Wishlist.cs index 0f3d9e6..11fe978 100644 --- a/ShoppingAssistantApi.Domain/Entities/Wishlist.cs +++ b/ShoppingAssistantApi.Domain/Entities/Wishlist.cs @@ -1,4 +1,3 @@ -using MongoDB.Bson; using ShoppingAssistantApi.Domain.Common; namespace ShoppingAssistantApi.Domain.Entities; @@ -6,8 +5,8 @@ namespace ShoppingAssistantApi.Domain.Entities; public class Wishlist : EntityBase { public required string Name { get; set; } - public required string Type { get; set; } - public ICollection? Messages { get; set; } = null; - public required ObjectId UserId { get; set; } + public required string Type { get; set; } + + public ICollection? Messages { get; set; } } diff --git a/ShoppingAssistantApi.Domain/Enums/MessageRoles.cs b/ShoppingAssistantApi.Domain/Enums/MessageRoles.cs index c33ee4b..8e8cd5d 100644 --- a/ShoppingAssistantApi.Domain/Enums/MessageRoles.cs +++ b/ShoppingAssistantApi.Domain/Enums/MessageRoles.cs @@ -3,5 +3,5 @@ namespace ShoppingAssistantApi.Domain.Enums; public enum MessageRoles { User = 0, - Application = 0 + Application = 1 } diff --git a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs index f667126..f066848 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs @@ -15,7 +15,9 @@ namespace ShoppingAssistantApi.Infrastructure.Services; public class WishlistsService : IWishlistsService { private readonly IWishlistsRepository _wishlistsRepository; + private readonly IMessagesRepository _messagesRepository; + private readonly IMapper _mapper; public WishlistsService(IWishlistsRepository wishlistRepository, IMessagesRepository messageRepository, IMapper mapper) @@ -29,6 +31,12 @@ public class WishlistsService : IWishlistsService { var newWishlist = _mapper.Map(dto); + if (!Enum.TryParse(newWishlist.Type, true, out var enumValue) || + !Enum.GetValues().Contains(enumValue)) + { + throw new InvalidDataException("Provided type is invalid."); + } + newWishlist.CreatedById = (ObjectId) GlobalUser.Id; newWishlist.CreatedDateUtc = DateTime.UtcNow; newWishlist.Name = $"{newWishlist.Type} Search"; @@ -59,8 +67,7 @@ public class WishlistsService : IWishlistsService newMessage.CreatedById = (ObjectId) GlobalUser.Id; newMessage.CreatedDateUtc = DateTime.UtcNow; - var relatedWishlistPage = await _wishlistsRepository.GetPageAsync(1, 1, x => x.Id == wishlistObjectId && x.CreatedById == GlobalUser.Id, cancellationToken); - var relatedWishlist = relatedWishlistPage.FirstOrDefault(); + var relatedWishlist = await _wishlistsRepository.GetWishlistAsync(x => x.Id == wishlistObjectId && x.CreatedById == GlobalUser.Id, cancellationToken); if (relatedWishlist == null) { @@ -71,4 +78,12 @@ public class WishlistsService : IWishlistsService return _mapper.Map(createdMessage); } + + public async Task> GetPersonalWishlistsPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken) + { + var entities = await _wishlistsRepository.GetPageAsync(pageNumber, pageSize, cancellationToken); + var dtos = _mapper.Map>(entities); + var count = await _wishlistsRepository.GetTotalCountAsync(); + return new PagedList(dtos, pageNumber, pageSize, count); + } } diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index 5832f7a..a9068c6 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -16,9 +16,13 @@ namespace ShoppingAssistantApi.Persistance.PersistanceExtentions; public class DbInitialaizer { private readonly IUsersService _usersService; + private readonly IUserManager _userManager; + private readonly IRolesService _rolesService; + private readonly ITokensService _tokensService; + private readonly IMongoCollection _wishlistCollection; public IEnumerable Roles { get; set; } @@ -171,7 +175,7 @@ public class DbInitialaizer { Name = "Grandma's Birthday Gift", Type = WishlistTypes.Gift.ToString(), - UserId = ObjectId.Parse(userList[0].Id), + CreatedById = ObjectId.Parse(userList[0].Id), Messages = new Message[] { new Message @@ -190,7 +194,7 @@ public class DbInitialaizer { Name = "Gaming PC", Type = WishlistTypes.Product.ToString(), - UserId = ObjectId.Parse(userList[1].Id), + CreatedById = ObjectId.Parse(userList[1].Id), Messages = new Message[] { new Message diff --git a/ShoppingAssistantApi.Persistance/Repositories/WishlistsRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/WishlistsRepository.cs index da92066..04aca96 100644 --- a/ShoppingAssistantApi.Persistance/Repositories/WishlistsRepository.cs +++ b/ShoppingAssistantApi.Persistance/Repositories/WishlistsRepository.cs @@ -1,3 +1,5 @@ +using System.Linq.Expressions; +using MongoDB.Driver; using ShoppingAssistantApi.Application.IRepositories; using ShoppingAssistantApi.Domain.Entities; using ShoppingAssistantApi.Persistance.Database; @@ -7,4 +9,9 @@ namespace ShoppingAssistantApi.Persistance.Repositories; public class WishlistsRepository : BaseRepository, IWishlistsRepository { public WishlistsRepository(MongoDbContext db) : base(db, "Wishlists") { } + + public async Task GetWishlistAsync(Expression> predicate, CancellationToken cancellationToken) + { + return await (await _collection.FindAsync(predicate)).FirstOrDefaultAsync(cancellationToken); + } } diff --git a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs index e513dc5..b67ad6a 100644 --- a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -4,7 +4,6 @@ using System.Net; using System.Text; using Xunit; using Newtonsoft.Json; -using ShoppingAssistantApi.Application.Models.Dtos; using ShoppingAssistantApi.Domain.Enums; namespace ShoppingAssistantApi.Tests.Tests; @@ -21,13 +20,13 @@ public class WishlistsTests : IClassFixture> } [Fact] - public async Task StartPersonalWishlistAndAddMessageAsync_ValidWishlistAndMessageModels_ReturnsNewWishlistAndMessageModels() + public async Task StartPersonalWishlistAsync_ValidWishlistModel_ReturnsNewWishlistModels() { - var tokensModel = await AccessExtention.CreateGuest(new Guid().ToString(), _httpClient); + var tokensModel = await AccessExtention.Login("shopping.assistant.team@gmail.com", "Yuiop12345", _httpClient); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); var user = await UserExtention.GetCurrentUser(_httpClient); - var startPersonalWishlistMutation = new + var mutation = new { query = "mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { startPersonalWishlist (dto: $dto) { id, name, type, createdById } }", variables = new @@ -40,33 +39,104 @@ public class WishlistsTests : IClassFixture> } }; - var jsonPayload = JsonConvert.SerializeObject(startPersonalWishlistMutation); + var jsonPayload = JsonConvert.SerializeObject(mutation); var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); - using var startPersonalWishlistResponse = await _httpClient.PostAsync("graphql", content); - startPersonalWishlistResponse.EnsureSuccessStatusCode(); - Assert.Equal(HttpStatusCode.OK, startPersonalWishlistResponse.StatusCode); + using var response = await _httpClient.PostAsync("graphql", content); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var responseString = await startPersonalWishlistResponse.Content.ReadAsStringAsync(); + var responseString = await response.Content.ReadAsStringAsync(); var document = JsonConvert.DeserializeObject(responseString); var wishlistId = (string) document.data.startPersonalWishlist.id; var wishlistCreatedById = (string) document.data.startPersonalWishlist.createdById; var wishlistType = (string) document.data.startPersonalWishlist.type; var wishlistName = (string) document.data.startPersonalWishlist.name; - + Assert.Equal(user.Id, wishlistCreatedById); Assert.Equal(WishlistTypes.Product.ToString(), wishlistType); Assert.Equal($"{WishlistTypes.Product} Search", wishlistName); + } + + [Fact] + public async Task GetPersonalWishlistsPage_ValidPageNumberAndSize_ReturnsPage() + { + var tokensModel = await AccessExtention.Login("shopping.assistant.team@gmail.com", "Yuiop12345", _httpClient); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); + var user = await UserExtention.GetCurrentUser(_httpClient); + + var query = new + { + query = "query personalWishlistsPage($pageNumber: Int!, $pageSize: Int!) { personalWishlistsPage(pageNumber: $pageNumber, pageSize: $pageSize) { items { createdById, id, name, type } } }", + variables = new + { + pageNumber = 3, + pageSize = 1 + } + }; + + var jsonPayload = JsonConvert.SerializeObject(query); + var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + + using var response = await _httpClient.PostAsync("graphql", content); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var responseString = await response.Content.ReadAsStringAsync(); + var document = JsonConvert.DeserializeObject(responseString); + + var personalWishlistsPageItems = Enumerable.ToList(document.data.personalWishlistsPage.items); + var personalWishlistCreatedById = (string) personalWishlistsPageItems[0].createdById; + + Assert.NotEmpty(personalWishlistsPageItems); + Assert.Equal(user.Id, personalWishlistCreatedById); + } + + [Fact] + public async Task AddMessageToPersonalWishlist_ValidMessageModel_ReturnsNewMessageModel() + { + var tokensModel = await AccessExtention.Login("shopping.assistant.team@gmail.com", "Yuiop12345", _httpClient); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); + var user = await UserExtention.GetCurrentUser(_httpClient); + + // Get personal wishlist + + var query = new + { + query = "query personalWishlistsPage($pageNumber: Int!, $pageSize: Int!) { personalWishlistsPage(pageNumber: $pageNumber, pageSize: $pageSize) { items { createdById, id, name, type } } }", + variables = new + { + pageNumber = 3, + pageSize = 1 + } + }; + + var jsonPayload = JsonConvert.SerializeObject(query); + var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + + using var personalWishlistPageResponse = await _httpClient.PostAsync("graphql", content); + personalWishlistPageResponse.EnsureSuccessStatusCode(); + Assert.Equal(HttpStatusCode.OK, personalWishlistPageResponse.StatusCode); + + var responseString = await personalWishlistPageResponse.Content.ReadAsStringAsync(); + var document = JsonConvert.DeserializeObject(responseString); + + var personalWishlistsPageItems = Enumerable.ToList(document.data.personalWishlistsPage.items); + var personalWishlistId = (string) personalWishlistsPageItems[0].id; + + Assert.NotNull(personalWishlistId); + + // Add message to personal wishlist const string MESSAGE_TEXT = "Second Message"; - var addMessageToPersonalWishlistMutation = new + var mutation = new { query = "mutation addMessageToPersonalWishlist($wishlistId: String!, $dto: MessageCreateDtoInput!) { addMessageToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { role, text, createdById } }", variables = new { - wishlistId = wishlistId, + wishlistId = personalWishlistId, dto = new { text = MESSAGE_TEXT, @@ -74,7 +144,7 @@ public class WishlistsTests : IClassFixture> } }; - jsonPayload = JsonConvert.SerializeObject(addMessageToPersonalWishlistMutation); + jsonPayload = JsonConvert.SerializeObject(mutation); content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); using var addMessageToPersonalWishlistResponse = await _httpClient.PostAsync("graphql", content); @@ -87,9 +157,65 @@ public class WishlistsTests : IClassFixture> var messageRole = (string) document.data.addMessageToPersonalWishlist.role; var messageText = (string) document.data.addMessageToPersonalWishlist.text; var messageCreatedById = (string) document.data.addMessageToPersonalWishlist.createdById; - + Assert.Equal(MessageRoles.User.ToString(), messageRole); Assert.Equal(MESSAGE_TEXT, messageText); Assert.Equal(user.Id, messageCreatedById); } + + [Fact] + public async Task StartPersonalWishlistAsync_InvalidWishlistModel_ReturnsInternalServerError() + { + var tokensModel = await AccessExtention.Login("shopping.assistant.team@gmail.com", "Yuiop12345", _httpClient); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); + var user = await UserExtention.GetCurrentUser(_httpClient); + + var mutation = new + { + query = "mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { startPersonalWishlist (dto: $dto) { id, name, type, createdById } }", + variables = new + { + dto = new + { + firstMessageText = "First message", + type = "Invalid type" // Invalid Wishlist Type + } + } + }; + + var jsonPayload = JsonConvert.SerializeObject(mutation); + var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + + using var response = await _httpClient.PostAsync("graphql", content); + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + } + + [Fact] + public async Task AddMessageToPersonalWishlist_InvalidMessageModel_ReturnsInternalServerError() + { + var tokensModel = await AccessExtention.Login("shopping.assistant.team@gmail.com", "Yuiop12345", _httpClient); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); + var user = await UserExtention.GetCurrentUser(_httpClient); + + const string MESSAGE_TEXT = "Second Message"; + + var mutation = new + { + query = "mutation addMessageToPersonalWishlist($wishlistId: String!, $dto: MessageCreateDtoInput!) { addMessageToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { role, text, createdById } }", + variables = new + { + wishlistId = "8125jad7g12", // Invalid wishlistId + dto = new + { + text = MESSAGE_TEXT, + } + } + }; + + var jsonPayload = JsonConvert.SerializeObject(mutation); + var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + + using var response = await _httpClient.PostAsync("graphql", content); + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + } }