From 8b78292ce75c8ecb44d1a81ade97f2b6be660564 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 8 Oct 2023 20:20:13 +0300 Subject: [PATCH 1/6] add database models, data transfer objects and mapper profiles --- .../MappingProfiles/MessageProfile.cs | 15 +++++++++++++++ .../MappingProfiles/WishlistProfile.cs | 15 +++++++++++++++ .../Models/CreateDtos/MessageCreateDto.cs | 6 ++++++ .../Models/CreateDtos/WishlistCreateDto.cs | 7 +++++++ .../Models/Dtos/MessageDto.cs | 11 +++++++++++ .../Models/Dtos/WishlistDto.cs | 11 +++++++++++ ShoppingAssistantApi.Domain/Entities/Message.cs | 12 ++++++++++++ ShoppingAssistantApi.Domain/Entities/Wishlist.cs | 13 +++++++++++++ ShoppingAssistantApi.Domain/Enums/MessageRoles.cs | 7 +++++++ .../Enums/WishlistTypes.cs | 7 +++++++ 10 files changed, 104 insertions(+) create mode 100644 ShoppingAssistantApi.Application/MappingProfiles/MessageProfile.cs create mode 100644 ShoppingAssistantApi.Application/MappingProfiles/WishlistProfile.cs create mode 100644 ShoppingAssistantApi.Application/Models/CreateDtos/MessageCreateDto.cs create mode 100644 ShoppingAssistantApi.Application/Models/CreateDtos/WishlistCreateDto.cs create mode 100644 ShoppingAssistantApi.Application/Models/Dtos/MessageDto.cs create mode 100644 ShoppingAssistantApi.Application/Models/Dtos/WishlistDto.cs create mode 100644 ShoppingAssistantApi.Domain/Entities/Message.cs create mode 100644 ShoppingAssistantApi.Domain/Entities/Wishlist.cs create mode 100644 ShoppingAssistantApi.Domain/Enums/MessageRoles.cs create mode 100644 ShoppingAssistantApi.Domain/Enums/WishlistTypes.cs diff --git a/ShoppingAssistantApi.Application/MappingProfiles/MessageProfile.cs b/ShoppingAssistantApi.Application/MappingProfiles/MessageProfile.cs new file mode 100644 index 0000000..e06161f --- /dev/null +++ b/ShoppingAssistantApi.Application/MappingProfiles/MessageProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using ShoppingAssistantApi.Application.Models.CreateDtos; +using ShoppingAssistantApi.Application.Models.Dtos; +using ShoppingAssistantApi.Domain.Entities; + +namespace ShoppingAssistantApi.Application.MappingProfiles; +public class MessageProfile : Profile +{ + public MessageProfile() + { + CreateMap().ReverseMap(); + + CreateMap().ReverseMap(); + } +} diff --git a/ShoppingAssistantApi.Application/MappingProfiles/WishlistProfile.cs b/ShoppingAssistantApi.Application/MappingProfiles/WishlistProfile.cs new file mode 100644 index 0000000..9b65411 --- /dev/null +++ b/ShoppingAssistantApi.Application/MappingProfiles/WishlistProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using ShoppingAssistantApi.Application.Models.CreateDtos; +using ShoppingAssistantApi.Application.Models.Dtos; +using ShoppingAssistantApi.Domain.Entities; + +namespace ShoppingAssistantApi.Application.MappingProfiles; +public class WishlistProfile : Profile +{ + public WishlistProfile() + { + CreateMap().ReverseMap(); + + CreateMap().ReverseMap(); + } +} diff --git a/ShoppingAssistantApi.Application/Models/CreateDtos/MessageCreateDto.cs b/ShoppingAssistantApi.Application/Models/CreateDtos/MessageCreateDto.cs new file mode 100644 index 0000000..b124a4f --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/CreateDtos/MessageCreateDto.cs @@ -0,0 +1,6 @@ +namespace ShoppingAssistantApi.Application.Models.CreateDtos; + +public class MessageCreateDto +{ + public required string Text { get; set; } +} diff --git a/ShoppingAssistantApi.Application/Models/CreateDtos/WishlistCreateDto.cs b/ShoppingAssistantApi.Application/Models/CreateDtos/WishlistCreateDto.cs new file mode 100644 index 0000000..375f70d --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/CreateDtos/WishlistCreateDto.cs @@ -0,0 +1,7 @@ +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 new file mode 100644 index 0000000..d6882a1 --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/Dtos/MessageDto.cs @@ -0,0 +1,11 @@ +namespace ShoppingAssistantApi.Application.Models.Dtos; + +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 new file mode 100644 index 0000000..6e21f8e --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/Dtos/WishlistDto.cs @@ -0,0 +1,11 @@ +namespace ShoppingAssistantApi.Application.Models.Dtos; + +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 new file mode 100644 index 0000000..a2621bf --- /dev/null +++ b/ShoppingAssistantApi.Domain/Entities/Message.cs @@ -0,0 +1,12 @@ +using MongoDB.Bson; +using ShoppingAssistantApi.Domain.Common; + +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; +} diff --git a/ShoppingAssistantApi.Domain/Entities/Wishlist.cs b/ShoppingAssistantApi.Domain/Entities/Wishlist.cs new file mode 100644 index 0000000..0f3d9e6 --- /dev/null +++ b/ShoppingAssistantApi.Domain/Entities/Wishlist.cs @@ -0,0 +1,13 @@ +using MongoDB.Bson; +using ShoppingAssistantApi.Domain.Common; + +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; } +} diff --git a/ShoppingAssistantApi.Domain/Enums/MessageRoles.cs b/ShoppingAssistantApi.Domain/Enums/MessageRoles.cs new file mode 100644 index 0000000..c33ee4b --- /dev/null +++ b/ShoppingAssistantApi.Domain/Enums/MessageRoles.cs @@ -0,0 +1,7 @@ +namespace ShoppingAssistantApi.Domain.Enums; + +public enum MessageRoles +{ + User = 0, + Application = 0 +} diff --git a/ShoppingAssistantApi.Domain/Enums/WishlistTypes.cs b/ShoppingAssistantApi.Domain/Enums/WishlistTypes.cs new file mode 100644 index 0000000..f846c98 --- /dev/null +++ b/ShoppingAssistantApi.Domain/Enums/WishlistTypes.cs @@ -0,0 +1,7 @@ +namespace ShoppingAssistantApi.Domain.Enums; + +public enum WishlistTypes +{ + Product = 0, + Gift = 1 +} From 7c6d36122dd3559c72b5d151446d738f65207314 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 8 Oct 2023 20:22:53 +0300 Subject: [PATCH 2/6] add wishlist and message repositories and wishlist service --- .../Exceptions/UnAuthorizedException.cs | 11 +++ .../IRepositories/IMessagerepository.cs | 5 ++ .../IRepositories/IWishlistRepository.cs | 5 ++ .../IServices/IWishlistService.cs | 11 +++ .../ServicesExtention.cs | 1 + .../Services/WishlistsService.cs | 74 +++++++++++++++++++ .../RepositoriesExtention.cs | 2 + .../Repositories/MessagesRepository.cs | 10 +++ .../Repositories/WishlistsRepository.cs | 10 +++ 9 files changed, 129 insertions(+) create mode 100644 ShoppingAssistantApi.Application/Exceptions/UnAuthorizedException.cs create mode 100644 ShoppingAssistantApi.Application/IRepositories/IMessagerepository.cs create mode 100644 ShoppingAssistantApi.Application/IRepositories/IWishlistRepository.cs create mode 100644 ShoppingAssistantApi.Application/IServices/IWishlistService.cs create mode 100644 ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs create mode 100644 ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs create mode 100644 ShoppingAssistantApi.Persistance/Repositories/WishlistsRepository.cs diff --git a/ShoppingAssistantApi.Application/Exceptions/UnAuthorizedException.cs b/ShoppingAssistantApi.Application/Exceptions/UnAuthorizedException.cs new file mode 100644 index 0000000..da8a0fa --- /dev/null +++ b/ShoppingAssistantApi.Application/Exceptions/UnAuthorizedException.cs @@ -0,0 +1,11 @@ +using MongoDB.Bson; +using ShoppingAssistantApi.Domain.Common; + +namespace ShoppingAssistantApi.Application.Exceptions; + +public class UnAuthorizedException : Exception where TEntity : EntityBase +{ + public UnAuthorizedException() { } + + public UnAuthorizedException(ObjectId id) : base(String.Format($"Access to object {id} of type {typeof(TEntity).Name} denied.")) { } +} diff --git a/ShoppingAssistantApi.Application/IRepositories/IMessagerepository.cs b/ShoppingAssistantApi.Application/IRepositories/IMessagerepository.cs new file mode 100644 index 0000000..5387549 --- /dev/null +++ b/ShoppingAssistantApi.Application/IRepositories/IMessagerepository.cs @@ -0,0 +1,5 @@ +using ShoppingAssistantApi.Domain.Entities; + +namespace ShoppingAssistantApi.Application.IRepositories; + +public interface IMessagesRepository : IBaseRepository { } diff --git a/ShoppingAssistantApi.Application/IRepositories/IWishlistRepository.cs b/ShoppingAssistantApi.Application/IRepositories/IWishlistRepository.cs new file mode 100644 index 0000000..f5789e5 --- /dev/null +++ b/ShoppingAssistantApi.Application/IRepositories/IWishlistRepository.cs @@ -0,0 +1,5 @@ +using ShoppingAssistantApi.Domain.Entities; + +namespace ShoppingAssistantApi.Application.IRepositories; + +public interface IWishlistsRepository : IBaseRepository { } diff --git a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs new file mode 100644 index 0000000..5ba8be9 --- /dev/null +++ b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs @@ -0,0 +1,11 @@ +using ShoppingAssistantApi.Application.Models.CreateDtos; +using ShoppingAssistantApi.Application.Models.Dtos; + +namespace ShoppingAssistantApi.Application.IServices; + +public interface IWishlistsService +{ + Task StartPersonalWishlistAsync(WishlistCreateDto dto, CancellationToken cancellationToken); + + Task AddMessageToPersonalWishlistAsync(string wishlistId, MessageCreateDto dto, CancellationToken cancellationToken); +} diff --git a/ShoppingAssistantApi.Infrastructure/InfrastructureExtentions/ServicesExtention.cs b/ShoppingAssistantApi.Infrastructure/InfrastructureExtentions/ServicesExtention.cs index a462de4..0db9d03 100644 --- a/ShoppingAssistantApi.Infrastructure/InfrastructureExtentions/ServicesExtention.cs +++ b/ShoppingAssistantApi.Infrastructure/InfrastructureExtentions/ServicesExtention.cs @@ -14,6 +14,7 @@ public static class ServicesExtention services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; } diff --git a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs new file mode 100644 index 0000000..f667126 --- /dev/null +++ b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs @@ -0,0 +1,74 @@ +using AutoMapper; +using MongoDB.Bson; +using ShoppingAssistantApi.Application.Exceptions; +using ShoppingAssistantApi.Application.GlobalInstances; +using ShoppingAssistantApi.Application.IRepositories; +using ShoppingAssistantApi.Application.IServices; +using ShoppingAssistantApi.Application.Models.CreateDtos; +using ShoppingAssistantApi.Application.Models.Dtos; +using ShoppingAssistantApi.Application.Paging; +using ShoppingAssistantApi.Domain.Entities; +using ShoppingAssistantApi.Domain.Enums; + +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) + { + _wishlistsRepository = wishlistRepository; + _messagesRepository = messageRepository; + _mapper = mapper; + } + + public async Task StartPersonalWishlistAsync(WishlistCreateDto dto, CancellationToken cancellationToken) + { + var newWishlist = _mapper.Map(dto); + + newWishlist.CreatedById = (ObjectId) GlobalUser.Id; + newWishlist.CreatedDateUtc = DateTime.UtcNow; + newWishlist.Name = $"{newWishlist.Type} Search"; + + var createdWishlist = await _wishlistsRepository.AddAsync(newWishlist, cancellationToken); + + var newMessage = new Message + { + Text = dto.FirstMessageText, + Role = MessageRoles.User.ToString(), + WishlistId = createdWishlist.Id + }; + var createdMessage = await _messagesRepository.AddAsync(newMessage, cancellationToken); + + return _mapper.Map(createdWishlist); + } + + public async Task AddMessageToPersonalWishlistAsync(string wishlistId, MessageCreateDto dto, CancellationToken cancellationToken) + { + var newMessage = _mapper.Map(dto); + + if (!ObjectId.TryParse(wishlistId, out var wishlistObjectId)) + { + throw new InvalidDataException("Provided id is invalid."); + } + newMessage.WishlistId = wishlistObjectId; + newMessage.Role = MessageRoles.User.ToString(); + 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(); + + if (relatedWishlist == null) + { + throw new UnAuthorizedException(); + } + + var createdMessage = await _messagesRepository.AddAsync(newMessage, cancellationToken); + + return _mapper.Map(createdMessage); + } +} diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/RepositoriesExtention.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/RepositoriesExtention.cs index 1ff1855..e48ae73 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/RepositoriesExtention.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/RepositoriesExtention.cs @@ -13,6 +13,8 @@ public static class RepositoriesExtention services.AddScoped(); services.AddScoped(); + services.AddScoped(); + services.AddScoped(); return services; } diff --git a/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs new file mode 100644 index 0000000..06481f6 --- /dev/null +++ b/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs @@ -0,0 +1,10 @@ +using ShoppingAssistantApi.Application.IRepositories; +using ShoppingAssistantApi.Domain.Entities; +using ShoppingAssistantApi.Persistance.Database; + +namespace ShoppingAssistantApi.Persistance.Repositories; + +public class MessagesRepository : BaseRepository, IMessagesRepository +{ + public MessagesRepository(MongoDbContext db) : base(db, "Messages") { } +} diff --git a/ShoppingAssistantApi.Persistance/Repositories/WishlistsRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/WishlistsRepository.cs new file mode 100644 index 0000000..da92066 --- /dev/null +++ b/ShoppingAssistantApi.Persistance/Repositories/WishlistsRepository.cs @@ -0,0 +1,10 @@ +using ShoppingAssistantApi.Application.IRepositories; +using ShoppingAssistantApi.Domain.Entities; +using ShoppingAssistantApi.Persistance.Database; + +namespace ShoppingAssistantApi.Persistance.Repositories; + +public class WishlistsRepository : BaseRepository, IWishlistsRepository +{ + public WishlistsRepository(MongoDbContext db) : base(db, "Wishlists") { } +} From 60bc38ee372e8a2edc741ccaa44947adcc490a6e Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 8 Oct 2023 20:24:59 +0300 Subject: [PATCH 3/6] add wishlist graphql mutations, data seeding and integrational tests --- .../ApiExtentions/GraphQlExtention.cs | 1 + .../Mutations/WishlistsMutation.cs | 17 ++++ .../PersistanceExtentions/DbInitialaizer.cs | 81 +++++++++++++--- .../Tests/WishlistsTests.cs | 95 +++++++++++++++++++ 4 files changed, 179 insertions(+), 15 deletions(-) create mode 100644 ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs create mode 100644 ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs diff --git a/ShoppingAssistantApi.Api/ApiExtentions/GraphQlExtention.cs b/ShoppingAssistantApi.Api/ApiExtentions/GraphQlExtention.cs index 1d67391..9a214e5 100644 --- a/ShoppingAssistantApi.Api/ApiExtentions/GraphQlExtention.cs +++ b/ShoppingAssistantApi.Api/ApiExtentions/GraphQlExtention.cs @@ -16,6 +16,7 @@ public static class GraphQlExtention .AddTypeExtension() .AddTypeExtension() .AddTypeExtension() + .AddTypeExtension() .AddAuthorization() .InitializeOnStartup(keepWarm: true); diff --git a/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs b/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs new file mode 100644 index 0000000..9c195fb --- /dev/null +++ b/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs @@ -0,0 +1,17 @@ +using ShoppingAssistantApi.Application.IServices; +using ShoppingAssistantApi.Application.Models.CreateDtos; +using ShoppingAssistantApi.Application.Models.Dtos; + +namespace ShoppingAssistantApi.Api.Mutations; + +[ExtendObjectType(OperationTypeNames.Mutation)] +public class WishlistsMutation +{ + public Task StartPersonalWishlist(WishlistCreateDto dto, CancellationToken cancellationToken, + [Service] IWishlistsService wishlistsService) + => wishlistsService.StartPersonalWishlistAsync(dto, cancellationToken); + + public Task AddMessageToPersonalWishlist(string wishlistId, MessageCreateDto dto, CancellationToken cancellationToken, + [Service] IWishlistsService wishlistsService) + => wishlistsService.AddMessageToPersonalWishlistAsync(wishlistId, dto, cancellationToken); +} diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index 0753d58..5832f7a 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -1,41 +1,42 @@ using Microsoft.Extensions.DependencyInjection; using MongoDB.Bson; +using MongoDB.Driver; using ShoppingAssistantApi.Application.GlobalInstances; using ShoppingAssistantApi.Application.IServices; using ShoppingAssistantApi.Application.IServices.Identity; using ShoppingAssistantApi.Application.Models.CreateDtos; using ShoppingAssistantApi.Application.Models.Dtos; using ShoppingAssistantApi.Application.Models.Identity; +using ShoppingAssistantApi.Domain.Entities; +using ShoppingAssistantApi.Domain.Enums; +using ShoppingAssistantApi.Persistance.Database; 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; } public DbInitialaizer(IServiceProvider serviceProvider) { - this._usersService = serviceProvider.GetService(); - this._rolesService = serviceProvider.GetService(); - this._userManager = serviceProvider.GetService(); - this._tokensService = serviceProvider.GetService(); + _usersService = serviceProvider.GetService(); + _rolesService = serviceProvider.GetService(); + _userManager = serviceProvider.GetService(); + _tokensService = serviceProvider.GetService(); + _wishlistCollection = serviceProvider.GetService().Db.GetCollection("Wishlists"); } - public async - Task -InitialaizeDb(CancellationToken cancellationToken) + public async Task InitialaizeDb(CancellationToken cancellationToken) { - await this.AddRoles(cancellationToken); - await this.AddUsers(cancellationToken); + await AddRoles(cancellationToken); + await AddUsers(cancellationToken); + await AddWishlistsWithMessages(cancellationToken); } public async Task AddUsers(CancellationToken cancellationToken) @@ -73,7 +74,7 @@ InitialaizeDb(CancellationToken cancellationToken) _userManager.AccessGuestAsync(guestModel5, cancellationToken) ); - var guests = await this._usersService.GetUsersPageAsync(1, 4, cancellationToken); + var guests = await _usersService.GetUsersPageAsync(1, 4, cancellationToken); var guestsResult = guests.Items.ToList(); var user1 = new UserDto @@ -123,7 +124,7 @@ InitialaizeDb(CancellationToken cancellationToken) RefreshToken = _tokensService.GenerateRefreshToken(), RefreshTokenExpiryDate = DateTime.Now.AddDays(7), }; - + GlobalUser.Id = ObjectId.Parse(user1.Id); await _userManager.UpdateAsync(user1, cancellationToken); @@ -158,4 +159,54 @@ InitialaizeDb(CancellationToken cancellationToken) var dto2 = await _rolesService.AddRoleAsync(role2, cancellationToken); var dto3 = await _rolesService.AddRoleAsync(role3, cancellationToken); } + + public async Task AddWishlistsWithMessages(CancellationToken cancellationToken) + { + var usersPage = await _usersService.GetUsersPageAsync(1, 2, cancellationToken); + var userList = usersPage.Items.ToList(); + + var wishlists = new Wishlist[] + { + new Wishlist + { + Name = "Grandma's Birthday Gift", + Type = WishlistTypes.Gift.ToString(), + UserId = ObjectId.Parse(userList[0].Id), + Messages = new Message[] + { + new Message + { + Text = "Prompt", + Role = MessageRoles.User.ToString(), + }, + new Message + { + Text = "Answer", + Role = MessageRoles.Application.ToString(), + }, + } + }, + new Wishlist + { + Name = "Gaming PC", + Type = WishlistTypes.Product.ToString(), + UserId = ObjectId.Parse(userList[1].Id), + Messages = new Message[] + { + new Message + { + Text = "Prompt", + Role = MessageRoles.User.ToString(), + }, + new Message + { + Text = "Answer", + Role = MessageRoles.Application.ToString(), + }, + } + } + }; + + await _wishlistCollection.InsertManyAsync(wishlists); + } } diff --git a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs new file mode 100644 index 0000000..e513dc5 --- /dev/null +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -0,0 +1,95 @@ +using ShoppingAssistantApi.Tests.TestExtentions; +using System.Net.Http.Headers; +using System.Net; +using System.Text; +using Xunit; +using Newtonsoft.Json; +using ShoppingAssistantApi.Application.Models.Dtos; +using ShoppingAssistantApi.Domain.Enums; + +namespace ShoppingAssistantApi.Tests.Tests; + +[Collection("Tests")] +public class WishlistsTests : IClassFixture> +{ + private readonly HttpClient _httpClient; + + public WishlistsTests(TestingFactory factory) + { + _httpClient = factory.CreateClient(); + factory.InitialaizeData().GetAwaiter().GetResult(); + } + + [Fact] + public async Task StartPersonalWishlistAndAddMessageAsync_ValidWishlistAndMessageModels_ReturnsNewWishlistAndMessageModels() + { + var tokensModel = await AccessExtention.CreateGuest(new Guid().ToString(), _httpClient); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); + var user = await UserExtention.GetCurrentUser(_httpClient); + + var startPersonalWishlistMutation = new + { + query = "mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { startPersonalWishlist (dto: $dto) { id, name, type, createdById } }", + variables = new + { + dto = new + { + firstMessageText = "First message", + type = WishlistTypes.Product.ToString() + } + } + }; + + var jsonPayload = JsonConvert.SerializeObject(startPersonalWishlistMutation); + 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); + + var responseString = await startPersonalWishlistResponse.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); + + const string MESSAGE_TEXT = "Second Message"; + + var addMessageToPersonalWishlistMutation = new + { + query = "mutation addMessageToPersonalWishlist($wishlistId: String!, $dto: MessageCreateDtoInput!) { addMessageToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { role, text, createdById } }", + variables = new + { + wishlistId = wishlistId, + dto = new + { + text = MESSAGE_TEXT, + } + } + }; + + jsonPayload = JsonConvert.SerializeObject(addMessageToPersonalWishlistMutation); + content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + + using var addMessageToPersonalWishlistResponse = await _httpClient.PostAsync("graphql", content); + addMessageToPersonalWishlistResponse.EnsureSuccessStatusCode(); + Assert.Equal(HttpStatusCode.OK, addMessageToPersonalWishlistResponse.StatusCode); + + responseString = await addMessageToPersonalWishlistResponse.Content.ReadAsStringAsync(); + document = JsonConvert.DeserializeObject(responseString); + + 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); + } +} From 5c30fc21e000cbbb42c5ddde474cb65aa73ca1be Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Wed, 11 Oct 2023 14:27:05 +0300 Subject: [PATCH 4/6] 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); + } } From 4e04a86d6a6e8091098556260ad8794028b6ef09 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Wed, 11 Oct 2023 16:53:46 +0300 Subject: [PATCH 5/6] remove http data retrieval in WishlistsTests --- .../PersistanceExtentions/DbInitialaizer.cs | 28 ++------ .../Tests/WishlistsTests.cs | 68 +++++++------------ 2 files changed, 30 insertions(+), 66 deletions(-) diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index a9068c6..5b66554 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -23,6 +23,8 @@ public class DbInitialaizer private readonly ITokensService _tokensService; + private readonly IMongoCollection _userCollection; + private readonly IMongoCollection _wishlistCollection; public IEnumerable Roles { get; set; } @@ -34,6 +36,7 @@ public class DbInitialaizer _userManager = serviceProvider.GetService(); _tokensService = serviceProvider.GetService(); _wishlistCollection = serviceProvider.GetService().Db.GetCollection("Wishlists"); + _userCollection = serviceProvider.GetService().Db.GetCollection("Users"); } public async Task InitialaizeDb(CancellationToken cancellationToken) @@ -166,35 +169,16 @@ public class DbInitialaizer public async Task AddWishlistsWithMessages(CancellationToken cancellationToken) { - var usersPage = await _usersService.GetUsersPageAsync(1, 2, cancellationToken); - var userList = usersPage.Items.ToList(); + var user = await (await _userCollection.FindAsync(x => x.Email.Equals("shopping.assistant.team@gmail.com"))).FirstAsync(); var wishlists = new Wishlist[] { new Wishlist { - Name = "Grandma's Birthday Gift", - Type = WishlistTypes.Gift.ToString(), - CreatedById = ObjectId.Parse(userList[0].Id), - Messages = new Message[] - { - new Message - { - Text = "Prompt", - Role = MessageRoles.User.ToString(), - }, - new Message - { - Text = "Answer", - Role = MessageRoles.Application.ToString(), - }, - } - }, - new Wishlist - { + Id = ObjectId.Parse("ab79cde6f69abcd3efab65cd"), Name = "Gaming PC", Type = WishlistTypes.Product.ToString(), - CreatedById = ObjectId.Parse(userList[1].Id), + CreatedById = user.Id, Messages = new Message[] { new Message diff --git a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs index b67ad6a..4dbdcf0 100644 --- a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -13,6 +13,12 @@ public class WishlistsTests : IClassFixture> { private readonly HttpClient _httpClient; + private const string WISHLIST_TESTING_USER_EMAIL = "shopping.assistant.team@gmail.com"; + + private const string WISHLIST_TESTING_USER_PASSWORD = "Yuiop12345"; + + private const string TESTING_WISHLIST_ID = "ab79cde6f69abcd3efab65cd"; + public WishlistsTests(TestingFactory factory) { _httpClient = factory.CreateClient(); @@ -22,7 +28,7 @@ public class WishlistsTests : IClassFixture> [Fact] public async Task StartPersonalWishlistAsync_ValidWishlistModel_ReturnsNewWishlistModels() { - var tokensModel = await AccessExtention.Login("shopping.assistant.team@gmail.com", "Yuiop12345", _httpClient); + var tokensModel = await AccessExtention.Login(WISHLIST_TESTING_USER_EMAIL, WISHLIST_TESTING_USER_PASSWORD, _httpClient); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); var user = await UserExtention.GetCurrentUser(_httpClient); @@ -62,7 +68,7 @@ public class WishlistsTests : IClassFixture> [Fact] public async Task GetPersonalWishlistsPage_ValidPageNumberAndSize_ReturnsPage() { - var tokensModel = await AccessExtention.Login("shopping.assistant.team@gmail.com", "Yuiop12345", _httpClient); + var tokensModel = await AccessExtention.Login(WISHLIST_TESTING_USER_EMAIL, WISHLIST_TESTING_USER_PASSWORD, _httpClient); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); var user = await UserExtention.GetCurrentUser(_httpClient); @@ -71,7 +77,7 @@ public class WishlistsTests : IClassFixture> query = "query personalWishlistsPage($pageNumber: Int!, $pageSize: Int!) { personalWishlistsPage(pageNumber: $pageNumber, pageSize: $pageSize) { items { createdById, id, name, type } } }", variables = new { - pageNumber = 3, + pageNumber = 1, pageSize = 1 } }; @@ -88,6 +94,9 @@ public class WishlistsTests : IClassFixture> var personalWishlistsPageItems = Enumerable.ToList(document.data.personalWishlistsPage.items); var personalWishlistCreatedById = (string) personalWishlistsPageItems[0].createdById; + Console.WriteLine(personalWishlistsPageItems[0].id); + Console.WriteLine(personalWishlistsPageItems[0].name); + Console.WriteLine(personalWishlistsPageItems[0].type); Assert.NotEmpty(personalWishlistsPageItems); Assert.Equal(user.Id, personalWishlistCreatedById); @@ -96,39 +105,10 @@ public class WishlistsTests : IClassFixture> [Fact] public async Task AddMessageToPersonalWishlist_ValidMessageModel_ReturnsNewMessageModel() { - var tokensModel = await AccessExtention.Login("shopping.assistant.team@gmail.com", "Yuiop12345", _httpClient); + var tokensModel = await AccessExtention.Login(WISHLIST_TESTING_USER_EMAIL, WISHLIST_TESTING_USER_PASSWORD, _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 mutation = new @@ -136,23 +116,23 @@ public class WishlistsTests : IClassFixture> query = "mutation addMessageToPersonalWishlist($wishlistId: String!, $dto: MessageCreateDtoInput!) { addMessageToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { role, text, createdById } }", variables = new { - wishlistId = personalWishlistId, + wishlistId = TESTING_WISHLIST_ID, dto = new { - text = MESSAGE_TEXT, + text = MESSAGE_TEXT } } }; - jsonPayload = JsonConvert.SerializeObject(mutation); - content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonPayload = JsonConvert.SerializeObject(mutation); + var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); - using var addMessageToPersonalWishlistResponse = await _httpClient.PostAsync("graphql", content); - addMessageToPersonalWishlistResponse.EnsureSuccessStatusCode(); - Assert.Equal(HttpStatusCode.OK, addMessageToPersonalWishlistResponse.StatusCode); + using var response = await _httpClient.PostAsync("graphql", content); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); - responseString = await addMessageToPersonalWishlistResponse.Content.ReadAsStringAsync(); - document = JsonConvert.DeserializeObject(responseString); + var responseString = await response.Content.ReadAsStringAsync(); + var document = JsonConvert.DeserializeObject(responseString); var messageRole = (string) document.data.addMessageToPersonalWishlist.role; var messageText = (string) document.data.addMessageToPersonalWishlist.text; @@ -166,7 +146,7 @@ public class WishlistsTests : IClassFixture> [Fact] public async Task StartPersonalWishlistAsync_InvalidWishlistModel_ReturnsInternalServerError() { - var tokensModel = await AccessExtention.Login("shopping.assistant.team@gmail.com", "Yuiop12345", _httpClient); + var tokensModel = await AccessExtention.Login(WISHLIST_TESTING_USER_EMAIL, WISHLIST_TESTING_USER_PASSWORD, _httpClient); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); var user = await UserExtention.GetCurrentUser(_httpClient); @@ -193,7 +173,7 @@ public class WishlistsTests : IClassFixture> [Fact] public async Task AddMessageToPersonalWishlist_InvalidMessageModel_ReturnsInternalServerError() { - var tokensModel = await AccessExtention.Login("shopping.assistant.team@gmail.com", "Yuiop12345", _httpClient); + var tokensModel = await AccessExtention.Login(WISHLIST_TESTING_USER_EMAIL, WISHLIST_TESTING_USER_PASSWORD, _httpClient); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); var user = await UserExtention.GetCurrentUser(_httpClient); From 5127290365712ee0149256fd149c09b58151b79e Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Wed, 11 Oct 2023 19:57:33 +0300 Subject: [PATCH 6/6] add GetPersonalWishlist GraphQL query covered by integrational tests --- .../Queries/WishlistsQuery.cs | 5 ++ .../IServices/IWishlistService.cs | 2 + .../Services/WishlistsService.cs | 18 ++++ .../PersistanceExtentions/DbInitialaizer.cs | 20 ++++- .../Tests/WishlistsTests.cs | 86 ++++++++++++++++++- 5 files changed, 126 insertions(+), 5 deletions(-) diff --git a/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs b/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs index 896ccd4..389864f 100644 --- a/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs +++ b/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs @@ -12,4 +12,9 @@ public class WishlistsQuery public Task> GetPersonalWishlistsPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken, [Service] IWishlistsService wishlistsService) => wishlistsService.GetPersonalWishlistsPageAsync(pageNumber, pageSize, cancellationToken); + + [Authorize] + public Task GetPersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken, + [Service] IWishlistsService wishlistsService) + => wishlistsService.GetPersonalWishlistAsync(wishlistId, cancellationToken); } diff --git a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs index 696909e..5bfae6a 100644 --- a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs +++ b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs @@ -11,4 +11,6 @@ public interface IWishlistsService Task AddMessageToPersonalWishlistAsync(string wishlistId, MessageCreateDto dto, CancellationToken cancellationToken); Task> GetPersonalWishlistsPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken); + + Task GetPersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken); } diff --git a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs index f066848..7e1fc40 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs @@ -86,4 +86,22 @@ public class WishlistsService : IWishlistsService var count = await _wishlistsRepository.GetTotalCountAsync(); return new PagedList(dtos, pageNumber, pageSize, count); } + + public async Task GetPersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken) + { + if (!ObjectId.TryParse(wishlistId, out var wishlistObjectId)) + { + throw new InvalidDataException("Provided id is invalid."); + } + var entity = await _wishlistsRepository.GetWishlistAsync(x => x.Id == wishlistObjectId && x.CreatedById == GlobalUser.Id, cancellationToken); + + Console.WriteLine(" WISHLIST: " + entity.CreatedById + " " + GlobalUser.Id); + + if (entity == null) + { + throw new UnAuthorizedException(); + } + + return _mapper.Map(entity); + } } diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index 5b66554..a63c8f7 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -169,7 +169,8 @@ public class DbInitialaizer public async Task AddWishlistsWithMessages(CancellationToken cancellationToken) { - var user = await (await _userCollection.FindAsync(x => x.Email.Equals("shopping.assistant.team@gmail.com"))).FirstAsync(); + var user1 = await (await _userCollection.FindAsync(x => x.Email.Equals("shopping.assistant.team@gmail.com"))).FirstAsync(); + var user2 = await (await _userCollection.FindAsync(x => x.Email.Equals("mykhailo.bilodid@nure.ua"))).FirstAsync(); var wishlists = new Wishlist[] { @@ -178,7 +179,7 @@ public class DbInitialaizer Id = ObjectId.Parse("ab79cde6f69abcd3efab65cd"), Name = "Gaming PC", Type = WishlistTypes.Product.ToString(), - CreatedById = user.Id, + CreatedById = user1.Id, Messages = new Message[] { new Message @@ -192,6 +193,21 @@ public class DbInitialaizer Role = MessageRoles.Application.ToString(), }, } + }, + new Wishlist + { + Id = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), + Name = "Generic Wishlist Name", + Type = WishlistTypes.Product.ToString(), + CreatedById = user2.Id, + Messages = new Message[] + { + new Message + { + Text = "Prompt", + Role = MessageRoles.User.ToString(), + } + } } }; diff --git a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs index 4dbdcf0..d153a33 100644 --- a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -94,14 +94,48 @@ public class WishlistsTests : IClassFixture> var personalWishlistsPageItems = Enumerable.ToList(document.data.personalWishlistsPage.items); var personalWishlistCreatedById = (string) personalWishlistsPageItems[0].createdById; - Console.WriteLine(personalWishlistsPageItems[0].id); - Console.WriteLine(personalWishlistsPageItems[0].name); - Console.WriteLine(personalWishlistsPageItems[0].type); Assert.NotEmpty(personalWishlistsPageItems); Assert.Equal(user.Id, personalWishlistCreatedById); } + [Fact] + public async Task GetPersonalWishlist_ValidWishlistIdOrAuthorizedAccess_ReturnsWishlistDto() + { + var tokensModel = await AccessExtention.Login(WISHLIST_TESTING_USER_EMAIL, WISHLIST_TESTING_USER_PASSWORD, _httpClient); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); + var user = await UserExtention.GetCurrentUser(_httpClient); + + var query = new + { + query = "query personalWishlist($wishlistId: String!) { personalWishlist(wishlistId: $wishlistId) { createdById, id, name, type } }", + variables = new + { + wishlistId = TESTING_WISHLIST_ID + } + }; + + 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 personalWishlistId = (string) document.data.personalWishlist.id; + var personalWishlistName = (string) document.data.personalWishlist.name; + var personalWishlistType = (string) document.data.personalWishlist.type; + var personalWishlistCreatedById = (string) document.data.personalWishlist.createdById; + + Assert.Equal(TESTING_WISHLIST_ID, personalWishlistId); + Assert.Equal("Gaming PC", personalWishlistName); + Assert.Equal(WishlistTypes.Product.ToString(), personalWishlistType); + Assert.Equal(user.Id, personalWishlistCreatedById); + } + [Fact] public async Task AddMessageToPersonalWishlist_ValidMessageModel_ReturnsNewMessageModel() { @@ -170,6 +204,52 @@ public class WishlistsTests : IClassFixture> Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); } + [Fact] + public async Task GetPersonalWishlist_InvalidWishlistId_ReturnsInternalServerError() + { + var tokensModel = await AccessExtention.Login(WISHLIST_TESTING_USER_EMAIL, WISHLIST_TESTING_USER_PASSWORD, _httpClient); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); + var user = await UserExtention.GetCurrentUser(_httpClient); + + var query = new + { + query = "query personalWishlist($wishlistId: String!) { personalWishlist(wishlistId: $wishlistId) { createdById, id, name, type } }", + variables = new + { + wishlistId = "1234567890abcdef12345678" // Invalid wishlistId + } + }; + + var jsonPayload = JsonConvert.SerializeObject(query); + 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 GetPersonalWishlist_UnAuthorizedAccess_ReturnsInternalServerError() + { + var tokensModel = await AccessExtention.Login(WISHLIST_TESTING_USER_EMAIL, WISHLIST_TESTING_USER_PASSWORD, _httpClient); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); + var user = await UserExtention.GetCurrentUser(_httpClient); + + var query = new + { + query = "query personalWishlist($wishlistId: String!) { personalWishlist(wishlistId: $wishlistId) { createdById, id, name, type } }", + variables = new + { + wishlistId = "ab6c2c2d9edf39abcd1ef9ab" // Other user's wishlist + } + }; + + var jsonPayload = JsonConvert.SerializeObject(query); + 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() {