diff --git a/ShoppingAssistantApi.Api/ApiExtentions/GraphQlExtention.cs b/ShoppingAssistantApi.Api/ApiExtentions/GraphQlExtention.cs index 1d67391..c534a56 100644 --- a/ShoppingAssistantApi.Api/ApiExtentions/GraphQlExtention.cs +++ b/ShoppingAssistantApi.Api/ApiExtentions/GraphQlExtention.cs @@ -12,10 +12,12 @@ public static class GraphQlExtention .AddQueryType() .AddTypeExtension() .AddTypeExtension() + .AddTypeExtension() .AddMutationType() .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.Api/Queries/WishlistsQuery.cs b/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs new file mode 100644 index 0000000..389864f --- /dev/null +++ b/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs @@ -0,0 +1,20 @@ +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); + + [Authorize] + public Task GetPersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken, + [Service] IWishlistsService wishlistsService) + => wishlistsService.GetPersonalWishlistAsync(wishlistId, cancellationToken); +} 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..b8c6abf --- /dev/null +++ b/ShoppingAssistantApi.Application/IRepositories/IWishlistRepository.cs @@ -0,0 +1,9 @@ +using System.Linq.Expressions; +using ShoppingAssistantApi.Domain.Entities; + +namespace ShoppingAssistantApi.Application.IRepositories; + +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 new file mode 100644 index 0000000..5bfae6a --- /dev/null +++ b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs @@ -0,0 +1,16 @@ +using ShoppingAssistantApi.Application.Models.CreateDtos; +using ShoppingAssistantApi.Application.Models.Dtos; +using ShoppingAssistantApi.Application.Paging; + +namespace ShoppingAssistantApi.Application.IServices; + +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); + + Task GetPersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken); +} 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..7a5f7a6 --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/CreateDtos/WishlistCreateDto.cs @@ -0,0 +1,8 @@ +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..a33c92e --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/Dtos/MessageDto.cs @@ -0,0 +1,12 @@ +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..9398c26 --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/Dtos/WishlistDto.cs @@ -0,0 +1,12 @@ +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..8a50457 --- /dev/null +++ b/ShoppingAssistantApi.Domain/Entities/Message.cs @@ -0,0 +1,13 @@ +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; } +} diff --git a/ShoppingAssistantApi.Domain/Entities/Wishlist.cs b/ShoppingAssistantApi.Domain/Entities/Wishlist.cs new file mode 100644 index 0000000..11fe978 --- /dev/null +++ b/ShoppingAssistantApi.Domain/Entities/Wishlist.cs @@ -0,0 +1,12 @@ +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; } +} diff --git a/ShoppingAssistantApi.Domain/Enums/MessageRoles.cs b/ShoppingAssistantApi.Domain/Enums/MessageRoles.cs new file mode 100644 index 0000000..8e8cd5d --- /dev/null +++ b/ShoppingAssistantApi.Domain/Enums/MessageRoles.cs @@ -0,0 +1,7 @@ +namespace ShoppingAssistantApi.Domain.Enums; + +public enum MessageRoles +{ + User = 0, + Application = 1 +} 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 +} 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..7e1fc40 --- /dev/null +++ b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs @@ -0,0 +1,107 @@ +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); + + 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"; + + 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 relatedWishlist = await _wishlistsRepository.GetWishlistAsync(x => x.Id == wishlistObjectId && x.CreatedById == GlobalUser.Id, cancellationToken); + + if (relatedWishlist == null) + { + throw new UnAuthorizedException(); + } + + var createdMessage = await _messagesRepository.AddAsync(newMessage, cancellationToken); + + 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); + } + + 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 0753d58..a63c8f7 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -1,11 +1,15 @@ 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; @@ -19,23 +23,27 @@ public class DbInitialaizer private readonly ITokensService _tokensService; + private readonly IMongoCollection _userCollection; + + 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"); + _userCollection = serviceProvider.GetService().Db.GetCollection("Users"); } - 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 +81,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 +131,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 +166,51 @@ InitialaizeDb(CancellationToken cancellationToken) var dto2 = await _rolesService.AddRoleAsync(role2, cancellationToken); var dto3 = await _rolesService.AddRoleAsync(role3, cancellationToken); } + + public async Task AddWishlistsWithMessages(CancellationToken cancellationToken) + { + 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[] + { + new Wishlist + { + Id = ObjectId.Parse("ab79cde6f69abcd3efab65cd"), + Name = "Gaming PC", + Type = WishlistTypes.Product.ToString(), + CreatedById = user1.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("ab6c2c2d9edf39abcd1ef9ab"), + Name = "Generic Wishlist Name", + Type = WishlistTypes.Product.ToString(), + CreatedById = user2.Id, + Messages = new Message[] + { + new Message + { + Text = "Prompt", + Role = MessageRoles.User.ToString(), + } + } + } + }; + + await _wishlistCollection.InsertManyAsync(wishlists); + } } 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..04aca96 --- /dev/null +++ b/ShoppingAssistantApi.Persistance/Repositories/WishlistsRepository.cs @@ -0,0 +1,17 @@ +using System.Linq.Expressions; +using MongoDB.Driver; +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") { } + + 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 new file mode 100644 index 0000000..d153a33 --- /dev/null +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -0,0 +1,281 @@ +using ShoppingAssistantApi.Tests.TestExtentions; +using System.Net.Http.Headers; +using System.Net; +using System.Text; +using Xunit; +using Newtonsoft.Json; +using ShoppingAssistantApi.Domain.Enums; + +namespace ShoppingAssistantApi.Tests.Tests; + +[Collection("Tests")] +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(); + factory.InitialaizeData().GetAwaiter().GetResult(); + } + + [Fact] + public async Task StartPersonalWishlistAsync_ValidWishlistModel_ReturnsNewWishlistModels() + { + 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 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(mutation); + 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 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(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 = 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 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() + { + 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 = TESTING_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); + response.EnsureSuccessStatusCode(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + 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; + 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(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 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 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() + { + 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 = "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); + } +}