diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0e6d68c..3075303 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -20,7 +20,9 @@ "kreativ-software.csharpextensions", "ms-dotnettools.csharp", "patcx.vscode-nuget-gallery", - "mhutchie.git-graph" + "mhutchie.git-graph", + "fernandoescolar.vscode-solution-explorer", + "formulahendry.dotnet-test-explorer" ] } } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cf3a569 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnType": true +} \ No newline at end of file diff --git a/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs b/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs index 9c195fb..f3ff806 100644 --- a/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs +++ b/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs @@ -7,11 +7,19 @@ namespace ShoppingAssistantApi.Api.Mutations; [ExtendObjectType(OperationTypeNames.Mutation)] public class WishlistsMutation { - public Task StartPersonalWishlist(WishlistCreateDto dto, CancellationToken cancellationToken, - [Service] IWishlistsService wishlistsService) + public Task StartPersonalWishlistAsync(WishlistCreateDto dto, CancellationToken cancellationToken, + [Service] IWishlistsService wishlistsService) => wishlistsService.StartPersonalWishlistAsync(dto, cancellationToken); - public Task AddMessageToPersonalWishlist(string wishlistId, MessageCreateDto dto, CancellationToken cancellationToken, - [Service] IWishlistsService wishlistsService) + public Task AddMessageToPersonalWishlistAsync(string wishlistId, MessageCreateDto dto, CancellationToken cancellationToken, + [Service] IWishlistsService wishlistsService) => wishlistsService.AddMessageToPersonalWishlistAsync(wishlistId, dto, cancellationToken); + + public Task AddProductToPersonalWishlistAsync(string wishlistId, ProductCreateDto dto, CancellationToken cancellationToken, + [Service] IWishlistsService wishlistsService) + => wishlistsService.AddProductToPersonalWishlistAsync(wishlistId, dto, cancellationToken); + + public Task DeletePersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken, + [Service] IWishlistsService wishlistsService) + => wishlistsService.DeletePersonalWishlistAsync(wishlistId, cancellationToken); } diff --git a/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs b/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs index 389864f..d3d36be 100644 --- a/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs +++ b/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs @@ -9,12 +9,22 @@ namespace ShoppingAssistantApi.Api.Queries; public class WishlistsQuery { [Authorize] - public Task> GetPersonalWishlistsPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken, - [Service] IWishlistsService wishlistsService) - => wishlistsService.GetPersonalWishlistsPageAsync(pageNumber, pageSize, cancellationToken); + 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); + [Service] IWishlistsService wishlistsService) + => wishlistsService.GetPersonalWishlistAsync(wishlistId, cancellationToken); + + [Authorize] + public Task> GetMessagesPageFromPersonalWishlistAsync(string wishlistId, int pageNumber, int pageSize, + CancellationToken cancellationToken, [Service] IWishlistsService wishlistsService) + => wishlistsService.GetMessagesPageFromPersonalWishlistAsync(wishlistId, pageNumber, pageSize, cancellationToken); + + [Authorize] + public Task> GetProductsPageFromPersonalWishlistAsync(string wishlistId, int pageNumber, int pageSize, + CancellationToken cancellationToken, [Service] IWishlistsService wishlistsService) + => wishlistsService.GetProductsPageFromPersonalWishlistAsync(wishlistId, pageNumber, pageSize, cancellationToken); } diff --git a/ShoppingAssistantApi.Application/IRepositories/IMessagerepository.cs b/ShoppingAssistantApi.Application/IRepositories/IMessagerepository.cs index 5387549..3d0483e 100644 --- a/ShoppingAssistantApi.Application/IRepositories/IMessagerepository.cs +++ b/ShoppingAssistantApi.Application/IRepositories/IMessagerepository.cs @@ -1,5 +1,9 @@ +using System.Linq.Expressions; using ShoppingAssistantApi.Domain.Entities; namespace ShoppingAssistantApi.Application.IRepositories; -public interface IMessagesRepository : IBaseRepository { } +public interface IMessagesRepository : IBaseRepository +{ + Task> GetPageStartingFromEndAsync(int pageNumber, int pageSize, Expression> predicate, CancellationToken cancellationToken); +} diff --git a/ShoppingAssistantApi.Application/IRepositories/IProductsRepository.cs b/ShoppingAssistantApi.Application/IRepositories/IProductsRepository.cs new file mode 100644 index 0000000..d5c6c02 --- /dev/null +++ b/ShoppingAssistantApi.Application/IRepositories/IProductsRepository.cs @@ -0,0 +1,5 @@ +using ShoppingAssistantApi.Domain.Entities; + +namespace ShoppingAssistantApi.Application.IRepositories; + +public interface IProductsRepository : IBaseRepository { } diff --git a/ShoppingAssistantApi.Application/IServices/IOpenAiService.cs b/ShoppingAssistantApi.Application/IServices/IOpenAiService.cs new file mode 100644 index 0000000..df56a10 --- /dev/null +++ b/ShoppingAssistantApi.Application/IServices/IOpenAiService.cs @@ -0,0 +1,13 @@ +using ShoppingAssistantApi.Application.Models.OpenAi; + +namespace ShoppingAssistantApi.Application.IServices; + +public interface IOpenAiService +{ + Task GetChatCompletion(ChatCompletionRequest chat, CancellationToken cancellationToken); + + /// + /// Retrieves a stream of tokens (pieces of words) based on provided chat. + /// + IAsyncEnumerable GetChatCompletionStream(ChatCompletionRequest chat, CancellationToken cancellationToken); +} diff --git a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs index 5bfae6a..d95a874 100644 --- a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs +++ b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs @@ -13,4 +13,12 @@ public interface IWishlistsService Task> GetPersonalWishlistsPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken); Task GetPersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken); + + Task> GetMessagesPageFromPersonalWishlistAsync(string wishlistId, int pageNumber, int pageSize, CancellationToken cancellationToken); + + Task AddProductToPersonalWishlistAsync(string wishlistId, ProductCreateDto dto, CancellationToken cancellationToken); + + Task> GetProductsPageFromPersonalWishlistAsync(string wishlistId, int pageNumber, int pageSize, CancellationToken cancellationToken); + + Task DeletePersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken); } diff --git a/ShoppingAssistantApi.Application/MappingProfiles/ProductProfile.cs b/ShoppingAssistantApi.Application/MappingProfiles/ProductProfile.cs new file mode 100644 index 0000000..92ab66f --- /dev/null +++ b/ShoppingAssistantApi.Application/MappingProfiles/ProductProfile.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 ProductProfile : Profile +{ + public ProductProfile() + { + CreateMap().ReverseMap(); + + CreateMap().ReverseMap(); + } +} diff --git a/ShoppingAssistantApi.Application/Models/CreateDtos/ProductCreateDto.cs b/ShoppingAssistantApi.Application/Models/CreateDtos/ProductCreateDto.cs new file mode 100644 index 0000000..015706c --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/CreateDtos/ProductCreateDto.cs @@ -0,0 +1,16 @@ +namespace ShoppingAssistantApi.Application.Models.CreateDtos; + +public class ProductCreateDto +{ + public required string Url { get; set; } + + public required string Name { get; set; } + + public required string Description { get; set; } + + public required double Rating { get; set; } + + public required string[] ImagesUrls { get; set; } + + public required bool WasOpened { get; set; } +} diff --git a/ShoppingAssistantApi.Application/Models/Dtos/MessageDto.cs b/ShoppingAssistantApi.Application/Models/Dtos/MessageDto.cs index a33c92e..9225d00 100644 --- a/ShoppingAssistantApi.Application/Models/Dtos/MessageDto.cs +++ b/ShoppingAssistantApi.Application/Models/Dtos/MessageDto.cs @@ -8,5 +8,5 @@ public class MessageDto public required string Role { get; set; } - public string? CreatedById { get; set; } = null; + public required string CreatedById { get; set; } } diff --git a/ShoppingAssistantApi.Application/Models/Dtos/ProductDto.cs b/ShoppingAssistantApi.Application/Models/Dtos/ProductDto.cs new file mode 100644 index 0000000..1697cd6 --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/Dtos/ProductDto.cs @@ -0,0 +1,20 @@ +namespace ShoppingAssistantApi.Application.Models.Dtos; + +public class ProductDto +{ + public required string Id { get; set; } + + public required string Url { get; set; } + + public required string Name { get; set; } + + public required string Description { get; set; } + + public required double Rating { get; set; } + + public required string[] ImagesUrls { get; set; } + + public required bool WasOpened { get; set; } + + public required string WishlistId { get; set; } +} diff --git a/ShoppingAssistantApi.Application/Models/Dtos/WishlistDto.cs b/ShoppingAssistantApi.Application/Models/Dtos/WishlistDto.cs index 9398c26..cbb2cf6 100644 --- a/ShoppingAssistantApi.Application/Models/Dtos/WishlistDto.cs +++ b/ShoppingAssistantApi.Application/Models/Dtos/WishlistDto.cs @@ -8,5 +8,5 @@ public class WishlistDto public required string Type { get; set; } - public string CreatedById { get; set; } = null!; + public required string CreatedById { get; set; } } diff --git a/ShoppingAssistantApi.Application/Models/OpenAi/ChatCompletionRequest.cs b/ShoppingAssistantApi.Application/Models/OpenAi/ChatCompletionRequest.cs new file mode 100644 index 0000000..d2ad66b --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/OpenAi/ChatCompletionRequest.cs @@ -0,0 +1,14 @@ +namespace ShoppingAssistantApi.Application.Models.OpenAi; + +public class ChatCompletionRequest +{ + public string Model { get; set; } = "gpt-3.5-turbo"; + + public List Messages { get; set; } + + public double Temperature { get; set; } = 0.7; + + public int MaxTokens { get; set; } = 256; + + public bool Stream { get; set; } = false; +} diff --git a/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiMessage.cs b/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiMessage.cs new file mode 100644 index 0000000..91bd757 --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/OpenAi/OpenAiMessage.cs @@ -0,0 +1,10 @@ +using ShoppingAssistantApi.Domain.Enums; + +namespace ShoppingAssistantApi.Application.Models.OpenAi; + +public class OpenAiMessage +{ + public OpenAiRole Role { get; set; } + + public string Content { get; set; } +} diff --git a/ShoppingAssistantApi.Domain/Entities/Message.cs b/ShoppingAssistantApi.Domain/Entities/Message.cs index 8a50457..77f4004 100644 --- a/ShoppingAssistantApi.Domain/Entities/Message.cs +++ b/ShoppingAssistantApi.Domain/Entities/Message.cs @@ -9,5 +9,5 @@ public class Message : EntityBase public required string Role { get; set; } - public ObjectId WishlistId { get; set; } + public required ObjectId WishlistId { get; set; } } diff --git a/ShoppingAssistantApi.Domain/Entities/Product.cs b/ShoppingAssistantApi.Domain/Entities/Product.cs index f3b2ccd..ce0cfcb 100644 --- a/ShoppingAssistantApi.Domain/Entities/Product.cs +++ b/ShoppingAssistantApi.Domain/Entities/Product.cs @@ -1,21 +1,22 @@ -using MongoDB.Bson; +using MongoDB.Bson; using ShoppingAssistantApi.Domain.Common; namespace ShoppingAssistantApi.Domain.Entities; public class Product : EntityBase { - public ObjectId WishlistId { get; set; } - - public string? Url { get; set; } - - public string? Name { get; set; } - - public string? Description { get; set; } - - public double Rating { get; set; } - - public string[]? ImagesUrls { get; set; } - - public bool WasOpened { get; set; } -} \ No newline at end of file + + public required string Url { get; set; } + + public required string Name { get; set; } + + public required string Description { get; set; } + + public required double Rating { get; set; } + + public required string[] ImagesUrls { get; set; } + + public required bool WasOpened { get; set; } + + public required ObjectId WishlistId { get; set; } +} diff --git a/ShoppingAssistantApi.Domain/Enums/OpenAiRole.cs b/ShoppingAssistantApi.Domain/Enums/OpenAiRole.cs new file mode 100644 index 0000000..54d2c0a --- /dev/null +++ b/ShoppingAssistantApi.Domain/Enums/OpenAiRole.cs @@ -0,0 +1,8 @@ +namespace ShoppingAssistantApi.Domain.Enums; + +public enum OpenAiRole +{ + System, + User, + Assistant +} diff --git a/ShoppingAssistantApi.Infrastructure/Services/OpenAiService.cs b/ShoppingAssistantApi.Infrastructure/Services/OpenAiService.cs new file mode 100644 index 0000000..9ed750a --- /dev/null +++ b/ShoppingAssistantApi.Infrastructure/Services/OpenAiService.cs @@ -0,0 +1,24 @@ +using ShoppingAssistantApi.Application.IServices; +using ShoppingAssistantApi.Application.Models.OpenAi; + +namespace ShoppingAssistantApi.Infrastructure.Services; + +public class OpenAiService : IOpenAiService +{ + private readonly HttpClient _httpClient; + + public OpenAiService(HttpClient client) + { + _httpClient = client; + } + + public Task GetChatCompletion(ChatCompletionRequest chat, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public IAsyncEnumerable GetChatCompletionStream(ChatCompletionRequest chat, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } +} diff --git a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs index 7e1fc40..5dd2ae7 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs @@ -18,12 +18,15 @@ public class WishlistsService : IWishlistsService private readonly IMessagesRepository _messagesRepository; + private readonly IProductsRepository _productsRepository; + private readonly IMapper _mapper; - public WishlistsService(IWishlistsRepository wishlistRepository, IMessagesRepository messageRepository, IMapper mapper) + public WishlistsService(IWishlistsRepository wishlistRepository, IMessagesRepository messageRepository, IProductsRepository productRepository, IMapper mapper) { _wishlistsRepository = wishlistRepository; _messagesRepository = messageRepository; + _productsRepository = productRepository; _mapper = mapper; } @@ -47,6 +50,8 @@ public class WishlistsService : IWishlistsService { Text = dto.FirstMessageText, Role = MessageRoles.User.ToString(), + CreatedById = (ObjectId) GlobalUser.Id, + CreatedDateUtc = DateTime.UtcNow, WishlistId = createdWishlist.Id }; var createdMessage = await _messagesRepository.AddAsync(newMessage, cancellationToken); @@ -62,17 +67,13 @@ public class WishlistsService : IWishlistsService { throw new InvalidDataException("Provided id is invalid."); } - newMessage.WishlistId = wishlistObjectId; + newMessage.Role = MessageRoles.User.ToString(); newMessage.CreatedById = (ObjectId) GlobalUser.Id; newMessage.CreatedDateUtc = DateTime.UtcNow; + newMessage.WishlistId = wishlistObjectId; - var relatedWishlist = await _wishlistsRepository.GetWishlistAsync(x => x.Id == wishlistObjectId && x.CreatedById == GlobalUser.Id, cancellationToken); - - if (relatedWishlist == null) - { - throw new UnAuthorizedException(); - } + await TryGetPersonalWishlist(wishlistObjectId, cancellationToken); var createdMessage = await _messagesRepository.AddAsync(newMessage, cancellationToken); @@ -81,7 +82,7 @@ public class WishlistsService : IWishlistsService public async Task> GetPersonalWishlistsPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken) { - var entities = await _wishlistsRepository.GetPageAsync(pageNumber, pageSize, cancellationToken); + var entities = await _wishlistsRepository.GetPageAsync(pageNumber, pageSize, x => x.CreatedById == GlobalUser.Id, cancellationToken); var dtos = _mapper.Map>(entities); var count = await _wishlistsRepository.GetTotalCountAsync(); return new PagedList(dtos, pageNumber, pageSize, count); @@ -93,15 +94,95 @@ public class WishlistsService : IWishlistsService { 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(); - } + var entity = await TryGetPersonalWishlist(wishlistObjectId, cancellationToken); return _mapper.Map(entity); } + + public async Task> GetMessagesPageFromPersonalWishlistAsync(string wishlistId, int pageNumber, int pageSize, CancellationToken cancellationToken) + { + if (!ObjectId.TryParse(wishlistId, out var wishlistObjectId)) + { + throw new InvalidDataException("Provided id is invalid."); + } + + await TryGetPersonalWishlist(wishlistObjectId, cancellationToken); + + var entities = await _messagesRepository.GetPageStartingFromEndAsync(pageNumber, pageSize, x => x.WishlistId == wishlistObjectId, cancellationToken); + + var dtos = _mapper.Map>(entities); + var count = await _messagesRepository.GetCountAsync(x => x.WishlistId == wishlistObjectId, cancellationToken); + return new PagedList(dtos, pageNumber, pageSize, count); + } + + public async Task AddProductToPersonalWishlistAsync(string wishlistId, ProductCreateDto dto, CancellationToken cancellationToken) + { + var newProduct = _mapper.Map(dto); + + if (!ObjectId.TryParse(wishlistId, out var wishlistObjectId)) + { + throw new InvalidDataException("Provided id is invalid."); + } + + await TryGetPersonalWishlist(wishlistObjectId, cancellationToken); + + newProduct.CreatedById = (ObjectId) GlobalUser.Id; + newProduct.CreatedDateUtc = DateTime.UtcNow; + newProduct.WishlistId = wishlistObjectId; + + var createdProduct = await _productsRepository.AddAsync(newProduct, cancellationToken); + + return _mapper.Map(createdProduct); + } + + public async Task> GetProductsPageFromPersonalWishlistAsync(string wishlistId, int pageNumber, int pageSize, CancellationToken cancellationToken) + { + if (!ObjectId.TryParse(wishlistId, out var wishlistObjectId)) + { + throw new InvalidDataException("Provided id is invalid."); + } + + await TryGetPersonalWishlist(wishlistObjectId, cancellationToken); + + var entities = await _productsRepository.GetPageAsync(pageNumber, pageSize, x => x.WishlistId == wishlistObjectId, cancellationToken); + + var dtos = _mapper.Map>(entities); + var count = await _productsRepository.GetCountAsync(x => x.WishlistId == wishlistObjectId, cancellationToken); + return new PagedList(dtos, pageNumber, pageSize, count); + } + + public async Task DeletePersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken) + { + if (!ObjectId.TryParse(wishlistId, out var wishlistObjectId)) + { + throw new InvalidDataException("Provided id is invalid."); + } + + var entity = await TryGetPersonalWishlist(wishlistObjectId, cancellationToken); + + entity.LastModifiedById = GlobalUser.Id; + entity.LastModifiedDateUtc = DateTime.UtcNow; + + await _wishlistsRepository.DeleteAsync(entity, cancellationToken); + + return _mapper.Map(entity); + } + + private async Task TryGetPersonalWishlist(ObjectId wishlistId, CancellationToken cancellationToken) + { + var entity = await _wishlistsRepository.GetWishlistAsync(x => x.Id == wishlistId, cancellationToken); + + if (entity.CreatedById != GlobalUser.Id) + { + throw new UnAuthorizedException(); + } + + if (entity == null) + { + throw new EntityNotFoundException(); + } + + return entity; + } } diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index b64f784..dbba926 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -50,7 +50,6 @@ public class DbInitialaizer await AddRoles(cancellationToken); await AddUsers(cancellationToken); await AddWishlistsWithMessages(cancellationToken); - await AddProducts(cancellationToken); } public async Task AddUsers(CancellationToken cancellationToken) diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/RepositoriesExtention.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/RepositoriesExtention.cs index e48ae73..2fbfa72 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/RepositoriesExtention.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/RepositoriesExtention.cs @@ -15,6 +15,7 @@ public static class RepositoriesExtention services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; } diff --git a/ShoppingAssistantApi.Persistance/Repositories/BaseRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/BaseRepository.cs index 565112e..319942d 100644 --- a/ShoppingAssistantApi.Persistance/Repositories/BaseRepository.cs +++ b/ShoppingAssistantApi.Persistance/Repositories/BaseRepository.cs @@ -69,4 +69,4 @@ public abstract class BaseRepository where TEntity : EntityBase return await this._collection.FindOneAndUpdateAsync( Builders.Filter.Eq(e => e.Id, entity.Id), updateDefinition, options, cancellationToken); } -} \ No newline at end of file +} diff --git a/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs index 06481f6..55734c9 100644 --- a/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs +++ b/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.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,13 @@ namespace ShoppingAssistantApi.Persistance.Repositories; public class MessagesRepository : BaseRepository, IMessagesRepository { public MessagesRepository(MongoDbContext db) : base(db, "Messages") { } + + public async Task> GetPageStartingFromEndAsync(int pageNumber, int pageSize, Expression> predicate, CancellationToken cancellationToken) + { + return await _collection.Find(predicate) + .SortByDescending(x => x.CreatedDateUtc) + .Skip((pageNumber - 1) * pageSize) + .Limit(pageSize) + .ToListAsync(cancellationToken); + } } diff --git a/ShoppingAssistantApi.Persistance/Repositories/ProductRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/ProductRepository.cs new file mode 100644 index 0000000..3e0863a --- /dev/null +++ b/ShoppingAssistantApi.Persistance/Repositories/ProductRepository.cs @@ -0,0 +1,10 @@ +using ShoppingAssistantApi.Application.IRepositories; +using ShoppingAssistantApi.Domain.Entities; +using ShoppingAssistantApi.Persistance.Database; + +namespace ShoppingAssistantApi.Persistance.Repositories; + +public class ProductsRepository : BaseRepository, IProductsRepository +{ + public ProductsRepository(MongoDbContext db) : base(db, "Products") { } +} diff --git a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs index d153a33..fa884c9 100644 --- a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -17,7 +17,15 @@ public class WishlistsTests : IClassFixture> private const string WISHLIST_TESTING_USER_PASSWORD = "Yuiop12345"; - private const string TESTING_WISHLIST_ID = "ab79cde6f69abcd3efab65cd"; + private const string WISHLIST_TESTING_VALID_WISHLIST_ID = "ab79cde6f69abcd3efab65cd"; + + private const string WISHLIST_TESTING_VALID_WISHLIST_NAME = "Gaming PC"; + + private const WishlistTypes WISHLIST_TESTING_VALID_WISHLIST_TYPE = WishlistTypes.Product; + + private const string WISHLIST_TESTING_INVALID_WISHLIST_ID = "1234567890abcdef12345678"; + + private const string WISHLIST_TESTING_OTHER_USER_WISHLIST_ID = "ab6c2c2d9edf39abcd1ef9ab"; public WishlistsTests(TestingFactory factory) { @@ -78,7 +86,7 @@ public class WishlistsTests : IClassFixture> variables = new { pageNumber = 1, - pageSize = 1 + pageSize = 5 } }; @@ -111,7 +119,7 @@ public class WishlistsTests : IClassFixture> query = "query personalWishlist($wishlistId: String!) { personalWishlist(wishlistId: $wishlistId) { createdById, id, name, type } }", variables = new { - wishlistId = TESTING_WISHLIST_ID + wishlistId = WISHLIST_TESTING_VALID_WISHLIST_ID } }; @@ -130,9 +138,9 @@ public class WishlistsTests : IClassFixture> 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(WISHLIST_TESTING_VALID_WISHLIST_ID, personalWishlistId); + Assert.Equal(WISHLIST_TESTING_VALID_WISHLIST_NAME, personalWishlistName); + Assert.Equal(WISHLIST_TESTING_VALID_WISHLIST_TYPE.ToString(), personalWishlistType); Assert.Equal(user.Id, personalWishlistCreatedById); } @@ -150,7 +158,7 @@ public class WishlistsTests : IClassFixture> query = "mutation addMessageToPersonalWishlist($wishlistId: String!, $dto: MessageCreateDtoInput!) { addMessageToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { role, text, createdById } }", variables = new { - wishlistId = TESTING_WISHLIST_ID, + wishlistId = WISHLIST_TESTING_VALID_WISHLIST_ID, dto = new { text = MESSAGE_TEXT @@ -177,6 +185,158 @@ public class WishlistsTests : IClassFixture> Assert.Equal(user.Id, messageCreatedById); } + [Fact] + public async Task GetMessagesPageFromPersonalWishlist_ValidPageNumberAndSizeValidWishlistIdOrAuthorizedAccess_ReturnsPage() + { + 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 mutation = new + { + query = "query messagesPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) { messagesPageFromPersonalWishlist (wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize) { hasNextPage, hasPreviousPage, items { id, text, role, createdById }, pageNumber, pageSize, totalItems, totalPages } }", + variables = new + { + wishlistId = WISHLIST_TESTING_VALID_WISHLIST_ID, + pageNumber = 1, + pageSize = 2 + } + }; + + 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.OK, response.StatusCode); + + var responseString = await response.Content.ReadAsStringAsync(); + var document = JsonConvert.DeserializeObject(responseString); + + Console.WriteLine(document); + + var messagesPageFromPersonalWishlist = Enumerable.ToList(document.data.messagesPageFromPersonalWishlist.items); + var firstMessageInPage = messagesPageFromPersonalWishlist[0]; + var secondMessageInPage = messagesPageFromPersonalWishlist[1]; + + Assert.Equal("Message 6", (string) firstMessageInPage.text); + Assert.Equal(MessageRoles.Application.ToString(), (string) firstMessageInPage.role); + } + + [Fact] + public async Task AddProductToPersonalWishlist_ValidMessageModel_ReturnsNewProductModel() + { + 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 mutation = new + { + query = "mutation addProductToPersonalWishlist($wishlistId: String!, $dto: ProductCreateDtoInput!) { addProductToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { url, name, description, rating, imagesUrls, wasOpened } }", + variables = new + { + wishlistId = WISHLIST_TESTING_VALID_WISHLIST_ID, + dto = new + { + url = "https://www.amazon.com/url", + name = "Generic name", + description = "Generic description", + rating = 4.8, + imagesUrls = new string[] + { + "https://www.amazon.com/image-url-1", + "https://www.amazon.com/image-url-2" + }, + wasOpened = false + } + } + }; + + 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.OK, response.StatusCode); + + var responseString = await response.Content.ReadAsStringAsync(); + var document = JsonConvert.DeserializeObject(responseString); + + Assert.Equal("https://www.amazon.com/url", (string) document.data.addProductToPersonalWishlist.url); + Assert.Equal("Generic name", (string) document.data.addProductToPersonalWishlist.name); + Assert.Equal("Generic description", (string) document.data.addProductToPersonalWishlist.description); + Assert.Equal(4.8, (double) document.data.addProductToPersonalWishlist.rating); + Assert.Equal("https://www.amazon.com/image-url-1", (string) document.data.addProductToPersonalWishlist.imagesUrls[0]); + } + + [Fact] + public async Task GetProductsPageFromPersonalWishlist_ValidPageNumberAndSizeValidWishlistIdOrAuthorizedAccess_ReturnsPage() + { + 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 mutation = new + { + query = "query productsPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) { productsPageFromPersonalWishlist (wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize) { hasNextPage, hasPreviousPage, items { id, url, name, description, rating, imagesUrls, wasOpened, wishlistId }, pageNumber, pageSize, totalItems, totalPages } }", + variables = new + { + wishlistId = WISHLIST_TESTING_VALID_WISHLIST_ID, + pageNumber = 1, + pageSize = 2 + } + }; + + 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.OK, response.StatusCode); + + var responseString = await response.Content.ReadAsStringAsync(); + var document = JsonConvert.DeserializeObject(responseString); + + var productsPageFromPersonalWishlist = Enumerable.ToList(document.data.productsPageFromPersonalWishlist.items); + var secondProductInPage = productsPageFromPersonalWishlist[1]; + + Assert.Equal("Samsung 970 EVO Plus SSD 2TB NVMe M.2 Internal Solid State Hard Drive, V-NAND Technology, Storage and Memory Expansion for Gaming, Graphics w/ Heat Control, Max Speed, MZ-V7S2T0B/AM ", (string) secondProductInPage.name); + Assert.Equal(WISHLIST_TESTING_VALID_WISHLIST_ID, (string) secondProductInPage.wishlistId); + } + + [Fact] + public async Task DeletePersonalWishlist_ValidWishlistIdOrAuthorizedAccess_ReturnsWishlistModel() + { + 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 mutation = new + { + query = "mutation deletePersonalWishlist($wishlistId: String!) { deletePersonalWishlist (wishlistId: $wishlistId) { createdById, id, name, type } }", + variables = new + { + wishlistId = WISHLIST_TESTING_VALID_WISHLIST_ID + } + }; + + 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.OK, response.StatusCode); + + var responseString = await response.Content.ReadAsStringAsync(); + var document = JsonConvert.DeserializeObject(responseString); + + var personalWishlistId = (string) document.data.deletePersonalWishlist.id; + var personalWishlistName = (string) document.data.deletePersonalWishlist.name; + var personalWishlistType = (string) document.data.deletePersonalWishlist.type; + var personalWishlistCreatedById = (string) document.data.deletePersonalWishlist.createdById; + + Assert.Equal(WISHLIST_TESTING_VALID_WISHLIST_ID, personalWishlistId); + Assert.Equal(WISHLIST_TESTING_VALID_WISHLIST_NAME, personalWishlistName); + Assert.Equal(WISHLIST_TESTING_VALID_WISHLIST_TYPE.ToString(), personalWishlistType); + Assert.Equal(user.Id, personalWishlistCreatedById); + } + [Fact] public async Task StartPersonalWishlistAsync_InvalidWishlistModel_ReturnsInternalServerError() { @@ -204,6 +364,76 @@ public class WishlistsTests : IClassFixture> Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); } + [Fact] + public async Task GetPersonalWishlistsPage_InValidPageNumber_ReturnsEmptyList() + { + 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 personalWishlistsPage($pageNumber: Int!, $pageSize: Int!) { personalWishlistsPage(pageNumber: $pageNumber, pageSize: $pageSize) { items { createdById, id, name, type } } }", + variables = new + { + pageNumber = 100, + pageSize = 1 + } + }; + + 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.OK, response.StatusCode); + + var responseString = await response.Content.ReadAsStringAsync(); + var document = JsonConvert.DeserializeObject(responseString); + + Console.WriteLine(document); + + var personalWishlistsPageItems = Enumerable.ToList(document.data.personalWishlistsPage.items); + + Assert.Empty(personalWishlistsPageItems); + } + + [Fact] + public async Task GetPersonalWishlistsPage_InValidPageSize_ReturnsPage() + { + 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 personalWishlistsPage($pageNumber: Int!, $pageSize: Int!) { personalWishlistsPage(pageNumber: $pageNumber, pageSize: $pageSize) { items { createdById, id, name, type } } }", + variables = new + { + pageNumber = 1, + pageSize = 100 + } + }; + + 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); + + Console.WriteLine(document); + + + 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 GetPersonalWishlist_InvalidWishlistId_ReturnsInternalServerError() { @@ -216,7 +446,7 @@ public class WishlistsTests : IClassFixture> query = "query personalWishlist($wishlistId: String!) { personalWishlist(wishlistId: $wishlistId) { createdById, id, name, type } }", variables = new { - wishlistId = "1234567890abcdef12345678" // Invalid wishlistId + wishlistId = WISHLIST_TESTING_INVALID_WISHLIST_ID } }; @@ -239,7 +469,7 @@ public class WishlistsTests : IClassFixture> query = "query personalWishlist($wishlistId: String!) { personalWishlist(wishlistId: $wishlistId) { createdById, id, name, type } }", variables = new { - wishlistId = "ab6c2c2d9edf39abcd1ef9ab" // Other user's wishlist + wishlistId = WISHLIST_TESTING_OTHER_USER_WISHLIST_ID } }; @@ -251,7 +481,7 @@ public class WishlistsTests : IClassFixture> } [Fact] - public async Task AddMessageToPersonalWishlist_InvalidMessageModel_ReturnsInternalServerError() + public async Task AddMessageToPersonalWishlist_InvalidWishlistId_ReturnsInternalServerError() { var tokensModel = await AccessExtention.Login(WISHLIST_TESTING_USER_EMAIL, WISHLIST_TESTING_USER_PASSWORD, _httpClient); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); @@ -264,7 +494,7 @@ public class WishlistsTests : IClassFixture> query = "mutation addMessageToPersonalWishlist($wishlistId: String!, $dto: MessageCreateDtoInput!) { addMessageToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { role, text, createdById } }", variables = new { - wishlistId = "8125jad7g12", // Invalid wishlistId + wishlistId = WISHLIST_TESTING_INVALID_WISHLIST_ID, dto = new { text = MESSAGE_TEXT, @@ -278,4 +508,383 @@ public class WishlistsTests : IClassFixture> using var response = await _httpClient.PostAsync("graphql", content); Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); } + + [Fact] + public async Task AddMessageToPersonalWishlist_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); + + 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 = WISHLIST_TESTING_OTHER_USER_WISHLIST_ID, + 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); + } + + [Fact] + public async Task GetMessagesPageFromPersonalWishlist_InValidPageNumber_ReturnsEmptyList() + { + 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 mutation = new + { + query = "query messagesPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) { messagesPageFromPersonalWishlist (wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize) { hasNextPage, hasPreviousPage, items { id, text, role, createdById }, pageNumber, pageSize, totalItems, totalPages } }", + variables = new + { + wishlistId = WISHLIST_TESTING_VALID_WISHLIST_ID, + pageNumber = 4, + pageSize = 2 + } + }; + + 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.OK, response.StatusCode); + + var responseString = await response.Content.ReadAsStringAsync(); + var document = JsonConvert.DeserializeObject(responseString); + + Console.WriteLine(document); + + + var messagesPageFromPersonalWishlistItems = Enumerable.ToList(document.data.messagesPageFromPersonalWishlist.items); + + Assert.Empty(messagesPageFromPersonalWishlistItems); + } + + [Fact] + public async Task GetMessagesPageFromPersonalWishlist_InValidPageSize_ReturnsPage() + { + 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 mutation = new + { + query = "query messagesPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) { messagesPageFromPersonalWishlist (wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize) { hasNextPage, hasPreviousPage, items { id, text, role, createdById }, pageNumber, pageSize, totalItems, totalPages } }", + variables = new + { + wishlistId = WISHLIST_TESTING_VALID_WISHLIST_ID, + pageNumber = 1, + pageSize = 10 + } + }; + + 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.OK, response.StatusCode); + + var responseString = await response.Content.ReadAsStringAsync(); + var document = JsonConvert.DeserializeObject(responseString); + + Console.WriteLine(document); + + + var messagesPageFromPersonalWishlist = Enumerable.ToList(document.data.messagesPageFromPersonalWishlist.items); + var firstMessageInPage = messagesPageFromPersonalWishlist[0]; + var secondMessageInPage = messagesPageFromPersonalWishlist[1]; + + Assert.Equal("Message 6", (string) firstMessageInPage.text); + Assert.Equal(MessageRoles.Application.ToString(), (string) firstMessageInPage.role); + } + + [Fact] + public async Task GetMessagesPageFromPersonalWishlist_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 mutation = new + { + query = "query messagesPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) { messagesPageFromPersonalWishlist (wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize) { hasNextPage, hasPreviousPage, items { id, text, role, createdById }, pageNumber, pageSize, totalItems, totalPages } }", + variables = new + { + wishlistId = WISHLIST_TESTING_INVALID_WISHLIST_ID, + pageNumber = 1, + pageSize = 2 + } + }; + + 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 GetMessagesPageFromPersonalWishlist_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 mutation = new + { + query = "query messagesPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) { messagesPageFromPersonalWishlist (wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize) { hasNextPage, hasPreviousPage, items { id, text, role, createdById }, pageNumber, pageSize, totalItems, totalPages } }", + variables = new + { + wishlistId = WISHLIST_TESTING_OTHER_USER_WISHLIST_ID, + pageNumber = 1, + pageSize = 2 + } + }; + + 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 AddProductToPersonalWishlist_InValidWishlistId_RturnsInternalServerError() + { + 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 mutation = new + { + query = "mutation addProductToPersonalWishlist($wishlistId: String!, $dto: ProductCreateDtoInput!) { addProductToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { url, name, description, rating, imagesUrls, wasOpened } }", + variables = new + { + wishlistId = WISHLIST_TESTING_INVALID_WISHLIST_ID, + dto = new + { + url = "https://www.amazon.com/url", + name = "Generic name", + description = "Generic description", + rating = 4.8, + imagesUrls = new string[] + { + "https://www.amazon.com/image-url-1", + "https://www.amazon.com/image-url-2" + }, + wasOpened = false + } + } + }; + + 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 AddProductToPersonalWishlist_UnAuthorizedAccess_RturnsInternalServerError() + { + 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 mutation = new + { + query = "mutation addProductToPersonalWishlist($wishlistId: String!, $dto: ProductCreateDtoInput!) { addProductToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { url, name, description, rating, imagesUrls, wasOpened } }", + variables = new + { + wishlistId = WISHLIST_TESTING_OTHER_USER_WISHLIST_ID, + dto = new + { + url = "https://www.amazon.com/url", + name = "Generic name", + description = "Generic description", + rating = 4.8, + imagesUrls = new string[] + { + "https://www.amazon.com/image-url-1", + "https://www.amazon.com/image-url-2" + }, + wasOpened = false + } + } + }; + + 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 GetProductsPageFromPersonalWishlist_InValidPageNumber_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 mutation = new + { + query = "query productsPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) { productsPageFromPersonalWishlist (wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize) { hasNextPage, hasPreviousPage, items { id, url, name, description, rating, imagesUrls, wasOpened, wishlistId }, pageNumber, pageSize, totalItems, totalPages } }", + variables = new + { + wishlistId = WISHLIST_TESTING_VALID_WISHLIST_ID, + pageNumber = 0, + pageSize = 2 + } + }; + + 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 GetProductsPageFromPersonalWishlist_InValidPageSize_ReturnsPage() + { + 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 mutation = new + { + query = "query productsPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) { productsPageFromPersonalWishlist (wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize) { hasNextPage, hasPreviousPage, items { id, url, name, description, rating, imagesUrls, wasOpened, wishlistId }, pageNumber, pageSize, totalItems, totalPages } }", + variables = new + { + wishlistId = WISHLIST_TESTING_VALID_WISHLIST_ID, + pageNumber = 1, + pageSize = 100 + } + }; + + 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.OK, response.StatusCode); + + var responseString = await response.Content.ReadAsStringAsync(); + var document = JsonConvert.DeserializeObject(responseString); + + var productsPageFromPersonalWishlist = Enumerable.ToList(document.data.productsPageFromPersonalWishlist.items); + var secondProductInPage = productsPageFromPersonalWishlist[1]; + + Assert.Equal("Samsung 970 EVO Plus SSD 2TB NVMe M.2 Internal Solid State Hard Drive, V-NAND Technology, Storage and Memory Expansion for Gaming, Graphics w/ Heat Control, Max Speed, MZ-V7S2T0B/AM ", (string) secondProductInPage.name); + Assert.Equal(WISHLIST_TESTING_VALID_WISHLIST_ID, (string) secondProductInPage.wishlistId); + } + + [Fact] + public async Task GetProductsPageFromPersonalWishlist_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 mutation = new + { + query = "query productsPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) { productsPageFromPersonalWishlist (wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize) { hasNextPage, hasPreviousPage, items { id, url, name, description, rating, imagesUrls, wasOpened, wishlistId }, pageNumber, pageSize, totalItems, totalPages } }", + variables = new + { + wishlistId = WISHLIST_TESTING_INVALID_WISHLIST_ID, + pageNumber = 0, + pageSize = 2 + } + }; + + 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 GetProductsPageFromPersonalWishlist_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 mutation = new + { + query = "query productsPageFromPersonalWishlist($wishlistId: String!, $pageNumber: Int!, $pageSize: Int!) { productsPageFromPersonalWishlist (wishlistId: $wishlistId, pageNumber: $pageNumber, pageSize: $pageSize) { hasNextPage, hasPreviousPage, items { id, url, name, description, rating, imagesUrls, wasOpened, wishlistId }, pageNumber, pageSize, totalItems, totalPages } }", + variables = new + { + wishlistId = WISHLIST_TESTING_OTHER_USER_WISHLIST_ID, + pageNumber = 0, + pageSize = 2 + } + }; + + 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 DeletePersonalWishlist_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 mutation = new + { + query = "mutation deletePersonalWishlist($wishlistId: String!) { deletePersonalWishlist (wishlistId: $wishlistId) { createdById, id, name, type } }", + variables = new + { + wishlistId = WISHLIST_TESTING_INVALID_WISHLIST_ID + } + }; + + 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 DeletePersonalWishlist_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 mutation = new + { + query = "mutation deletePersonalWishlist($wishlistId: String!) { deletePersonalWishlist (wishlistId: $wishlistId) { createdById, id, name, type } }", + variables = new + { + wishlistId = WISHLIST_TESTING_OTHER_USER_WISHLIST_ID + } + }; + + 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); + } } diff --git a/ShoppingAssistantApi.UnitTests/GlobalUsings.cs b/ShoppingAssistantApi.UnitTests/GlobalUsings.cs new file mode 100644 index 0000000..8c927eb --- /dev/null +++ b/ShoppingAssistantApi.UnitTests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/ShoppingAssistantApi.UnitTests/ShoppingAssistantApi.UnitTests.csproj b/ShoppingAssistantApi.UnitTests/ShoppingAssistantApi.UnitTests.csproj new file mode 100644 index 0000000..9274a65 --- /dev/null +++ b/ShoppingAssistantApi.UnitTests/ShoppingAssistantApi.UnitTests.csproj @@ -0,0 +1,31 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/ShoppingAssistantApi.sln b/ShoppingAssistantApi.sln index fb54417..f2f2788 100644 --- a/ShoppingAssistantApi.sln +++ b/ShoppingAssistantApi.sln @@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ShoppingAssistantApi.Api", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShoppingAssistantApi.Tests", "ShoppingAssistantApi.Tests\ShoppingAssistantApi.Tests.csproj", "{297B5378-79D7-406C-80A5-151C6B3EA147}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ShoppingAssistantApi.UnitTests", "ShoppingAssistantApi.UnitTests\ShoppingAssistantApi.UnitTests.csproj", "{B4EFE8F1-89F5-44E4-BD0A-4F63D09C8E6F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,6 +47,10 @@ Global {297B5378-79D7-406C-80A5-151C6B3EA147}.Debug|Any CPU.Build.0 = Debug|Any CPU {297B5378-79D7-406C-80A5-151C6B3EA147}.Release|Any CPU.ActiveCfg = Release|Any CPU {297B5378-79D7-406C-80A5-151C6B3EA147}.Release|Any CPU.Build.0 = Release|Any CPU + {B4EFE8F1-89F5-44E4-BD0A-4F63D09C8E6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4EFE8F1-89F5-44E4-BD0A-4F63D09C8E6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4EFE8F1-89F5-44E4-BD0A-4F63D09C8E6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4EFE8F1-89F5-44E4-BD0A-4F63D09C8E6F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE