From e9c3b57fc4e95efad1903fb75071e8dca37e6e2e Mon Sep 17 00:00:00 2001 From: stasex Date: Thu, 12 Oct 2023 20:55:34 +0300 Subject: [PATCH 01/22] added product entity and initial data for it --- .../Entities/Product.cs | 19 ++++++++ .../PersistanceExtentions/DbInitialaizer.cs | 45 +++++++++++++++++++ .../Repositories/ProductsRepository.cs | 9 ++++ 3 files changed, 73 insertions(+) create mode 100644 ShoppingAssistantApi.Domain/Entities/Product.cs create mode 100644 ShoppingAssistantApi.Persistance/Repositories/ProductsRepository.cs diff --git a/ShoppingAssistantApi.Domain/Entities/Product.cs b/ShoppingAssistantApi.Domain/Entities/Product.cs new file mode 100644 index 0000000..c581196 --- /dev/null +++ b/ShoppingAssistantApi.Domain/Entities/Product.cs @@ -0,0 +1,19 @@ +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; } +} \ No newline at end of file diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index a63c8f7..651e374 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -23,9 +23,13 @@ public class DbInitialaizer private readonly ITokensService _tokensService; + private readonly IWishlistsService _wishlistsService; + private readonly IMongoCollection _userCollection; private readonly IMongoCollection _wishlistCollection; + + private readonly IMongoCollection _productCollection; public IEnumerable Roles { get; set; } @@ -35,8 +39,10 @@ public class DbInitialaizer _rolesService = serviceProvider.GetService(); _userManager = serviceProvider.GetService(); _tokensService = serviceProvider.GetService(); + _wishlistsService = serviceProvider.GetService(); _wishlistCollection = serviceProvider.GetService().Db.GetCollection("Wishlists"); _userCollection = serviceProvider.GetService().Db.GetCollection("Users"); + _productCollection = serviceProvider.GetService().Db.GetCollection("Product"); } public async Task InitialaizeDb(CancellationToken cancellationToken) @@ -213,4 +219,43 @@ public class DbInitialaizer await _wishlistCollection.InsertManyAsync(wishlists); } + + public async Task AddProducts(CancellationToken cancellationToken) + { + var wishList1 = await _wishlistCollection.FindAsync(w => w.Name == "Gaming PC"); + var wishList2 = await _wishlistCollection.FindAsync(w => w.Name == "Generic Wishlist Name"); + + var products = new Product[] + { + new Product() + { + Id = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), + Url = "url", + Name = "Thermaltake Glacier", + Description = "Something", + Rating = 4.1, + ImagesUrls = new string[] + { + "url1", + "url2" + } + }, + + new Product() + { + Id = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), + Url = "url", + Name = "Mac", + Description = "very very cool laptop", + Rating = 4.9, + ImagesUrls = new string[] + { + "url1", + "url2" + } + } + + }; + + } } diff --git a/ShoppingAssistantApi.Persistance/Repositories/ProductsRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/ProductsRepository.cs new file mode 100644 index 0000000..5de13b9 --- /dev/null +++ b/ShoppingAssistantApi.Persistance/Repositories/ProductsRepository.cs @@ -0,0 +1,9 @@ +using ShoppingAssistantApi.Domain.Entities; +using ShoppingAssistantApi.Persistance.Database; + +namespace ShoppingAssistantApi.Persistance.Repositories; + +public class ProductsRepository : BaseRepository +{ + public ProductsRepository(MongoDbContext db) : base(db, "Products") { } +} \ No newline at end of file From e26177528b38c6386601ecba241672b4ead80677 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sat, 14 Oct 2023 20:05:02 +0300 Subject: [PATCH 02/22] add ability to delete wishlist --- .../Mutations/WishlistsMutation.cs | 4 + .../IServices/IWishlistService.cs | 2 + .../Services/WishlistsService.cs | 49 ++++++++--- .../Tests/WishlistsTests.cs | 85 +++++++++++++++++-- 4 files changed, 118 insertions(+), 22 deletions(-) diff --git a/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs b/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs index 9c195fb..3bd1d7c 100644 --- a/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs +++ b/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs @@ -14,4 +14,8 @@ public class WishlistsMutation public Task AddMessageToPersonalWishlist(string wishlistId, MessageCreateDto dto, CancellationToken cancellationToken, [Service] IWishlistsService wishlistsService) => wishlistsService.AddMessageToPersonalWishlistAsync(wishlistId, dto, cancellationToken); + + public Task DeletePersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken, + [Service] IWishlistsService wishlistsService) + => wishlistsService.DeletePersonalWishlistAsync(wishlistId, cancellationToken); } diff --git a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs index 5bfae6a..41081c6 100644 --- a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs +++ b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs @@ -13,4 +13,6 @@ public interface IWishlistsService Task> GetPersonalWishlistsPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken); Task GetPersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken); + + Task DeletePersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken); } diff --git a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs index 7e1fc40..a5166f2 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs @@ -67,12 +67,7 @@ public class WishlistsService : IWishlistsService 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(); - } + await TryGetPersonalWishlist(wishlistObjectId, cancellationToken); var createdMessage = await _messagesRepository.AddAsync(newMessage, cancellationToken); @@ -93,15 +88,43 @@ 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 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.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs index d153a33..93f37f8 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) { @@ -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,42 @@ public class WishlistsTests : IClassFixture> Assert.Equal(user.Id, messageCreatedById); } + [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() { @@ -216,7 +260,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 +283,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 } }; @@ -264,7 +308,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 +322,27 @@ public class WishlistsTests : IClassFixture> 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); + } } From 547c23f7c0f53fbb8d3ed13817fcb929102b11e2 Mon Sep 17 00:00:00 2001 From: shchoholiev-opflo Date: Sat, 14 Oct 2023 19:53:12 +0000 Subject: [PATCH 03/22] SA-14 added extensions to devcontainer --- .devcontainer/devcontainer.json | 5 ++++- .vscode/settings.json | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0e6d68c..8c8e5e0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -20,7 +20,10 @@ "kreativ-software.csharpextensions", "ms-dotnettools.csharp", "patcx.vscode-nuget-gallery", - "mhutchie.git-graph" + "mhutchie.git-graph", + "fernandoescolar.vscode-solution-explorer", + "formulahendry.dotnet-test-explorer", + "GitHub.copilot" ] } } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d9821aa --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.exclude": { + "**/bin": true + } +} \ No newline at end of file From 5679b2837dffd90316566e4d14eaa034bdb79e8f Mon Sep 17 00:00:00 2001 From: shchoholiev-opflo Date: Sat, 14 Oct 2023 19:53:47 +0000 Subject: [PATCH 04/22] SA-14 expanded base repository --- .../IRepositories/IBaseRepository.cs | 7 ++++++- .../Repositories/BaseRepository.cs | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/ShoppingAssistantApi.Application/IRepositories/IBaseRepository.cs b/ShoppingAssistantApi.Application/IRepositories/IBaseRepository.cs index f2a2ceb..ebcb860 100644 --- a/ShoppingAssistantApi.Application/IRepositories/IBaseRepository.cs +++ b/ShoppingAssistantApi.Application/IRepositories/IBaseRepository.cs @@ -1,4 +1,5 @@ -using ShoppingAssistantApi.Domain.Common; +using MongoDB.Bson; +using ShoppingAssistantApi.Domain.Common; using System.Linq.Expressions; namespace ShoppingAssistantApi.Application.IRepositories; @@ -7,6 +8,10 @@ public interface IBaseRepository where TEntity : EntityBase { Task AddAsync(TEntity entity, CancellationToken cancellationToken); + Task GetOneAsync(ObjectId id, CancellationToken cancellationToken); + + Task GetOneAsync(Expression> predicate, CancellationToken cancellationToken); + Task> GetPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken); Task> GetPageAsync(int pageNumber, int pageSize, Expression> predicate, CancellationToken cancellationToken); diff --git a/ShoppingAssistantApi.Persistance/Repositories/BaseRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/BaseRepository.cs index 565112e..dbfba85 100644 --- a/ShoppingAssistantApi.Persistance/Repositories/BaseRepository.cs +++ b/ShoppingAssistantApi.Persistance/Repositories/BaseRepository.cs @@ -1,11 +1,13 @@ -using MongoDB.Driver; +using MongoDB.Bson; +using MongoDB.Driver; +using ShoppingAssistantApi.Application.IRepositories; using ShoppingAssistantApi.Domain.Common; using ShoppingAssistantApi.Persistance.Database; using System.Linq.Expressions; namespace ShoppingAssistantApi.Persistance.Repositories; -public abstract class BaseRepository where TEntity : EntityBase +public abstract class BaseRepository : IBaseRepository where TEntity : EntityBase { protected MongoDbContext _db; @@ -17,6 +19,16 @@ public abstract class BaseRepository where TEntity : EntityBase this._collection = _db.Db.GetCollection(collectionName); } + public async Task GetOneAsync(ObjectId id, CancellationToken cancellationToken) + { + return await this._collection.Find(x => x.Id == id).FirstOrDefaultAsync(cancellationToken); + } + + public async Task GetOneAsync(Expression> predicate, CancellationToken cancellationToken) + { + return await this._collection.Find(predicate).FirstOrDefaultAsync(cancellationToken); + } + public async Task AddAsync(TEntity entity, CancellationToken cancellationToken) { await this._collection.InsertOneAsync(entity, new InsertOneOptions(), cancellationToken); From 4102312fe96ce85efd97f03c68204827d089113a Mon Sep 17 00:00:00 2001 From: shchoholiev-opflo Date: Sat, 14 Oct 2023 19:55:13 +0000 Subject: [PATCH 05/22] Sa-14 added RefreshTokensRepository --- .../IRepositories/IRefreshTokensRepository.cs | 8 ++++++++ ShoppingAssistantApi.Domain/Entities/RefreshToken.cs | 10 ++++++++++ .../PersistanceExtentions/RepositoriesExtention.cs | 1 + .../Repositories/RefreshTokensRepository.cs | 10 ++++++++++ 4 files changed, 29 insertions(+) create mode 100644 ShoppingAssistantApi.Application/IRepositories/IRefreshTokensRepository.cs create mode 100644 ShoppingAssistantApi.Domain/Entities/RefreshToken.cs create mode 100644 ShoppingAssistantApi.Persistance/Repositories/RefreshTokensRepository.cs diff --git a/ShoppingAssistantApi.Application/IRepositories/IRefreshTokensRepository.cs b/ShoppingAssistantApi.Application/IRepositories/IRefreshTokensRepository.cs new file mode 100644 index 0000000..492e62e --- /dev/null +++ b/ShoppingAssistantApi.Application/IRepositories/IRefreshTokensRepository.cs @@ -0,0 +1,8 @@ +using ShoppingAssistantApi.Domain.Entities; + +namespace ShoppingAssistantApi.Application.IRepositories; + +public interface IRefreshTokensRepository : IBaseRepository +{ + +} diff --git a/ShoppingAssistantApi.Domain/Entities/RefreshToken.cs b/ShoppingAssistantApi.Domain/Entities/RefreshToken.cs new file mode 100644 index 0000000..e36974b --- /dev/null +++ b/ShoppingAssistantApi.Domain/Entities/RefreshToken.cs @@ -0,0 +1,10 @@ +using ShoppingAssistantApi.Domain.Common; + +namespace ShoppingAssistantApi.Domain.Entities; + +public class RefreshToken : EntityBase +{ + public string Token { get; set; } + + public DateTime ExpiryDateUTC { get; set; } +} diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/RepositoriesExtention.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/RepositoriesExtention.cs index e48ae73..3d076e3 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/RepositoriesExtention.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/RepositoriesExtention.cs @@ -13,6 +13,7 @@ public static class RepositoriesExtention services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/ShoppingAssistantApi.Persistance/Repositories/RefreshTokensRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/RefreshTokensRepository.cs new file mode 100644 index 0000000..0228cf9 --- /dev/null +++ b/ShoppingAssistantApi.Persistance/Repositories/RefreshTokensRepository.cs @@ -0,0 +1,10 @@ +using ShoppingAssistantApi.Application.IRepositories; +using ShoppingAssistantApi.Domain.Entities; +using ShoppingAssistantApi.Persistance.Database; + +namespace ShoppingAssistantApi.Persistance.Repositories; + +public class RefreshTokensRepository : BaseRepository, IRefreshTokensRepository +{ + public RefreshTokensRepository(MongoDbContext db) : base(db, "RefreshTokens") { } +} From 4f67175ff7c50b47cc641362816a2f3f3b4ef32c Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 15 Oct 2023 09:44:20 +0300 Subject: [PATCH 06/22] add ability to view personal wishlists (get pages of messages starting from last messages) --- .../Mutations/WishlistsMutation.cs | 4 +- .../Queries/WishlistsQuery.cs | 11 +- .../IRepositories/IMessagerepository.cs | 6 +- .../IServices/IWishlistService.cs | 2 + .../Services/WishlistsService.cs | 21 ++- .../PersistanceExtentions/DbInitialaizer.cs | 85 ++++++--- .../Repositories/MessagesRepository.cs | 12 ++ .../Tests/WishlistsTests.cs | 161 ++++++++++++++++++ 8 files changed, 271 insertions(+), 31 deletions(-) diff --git a/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs b/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs index 3bd1d7c..c4dde4c 100644 --- a/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs +++ b/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs @@ -7,11 +7,11 @@ namespace ShoppingAssistantApi.Api.Mutations; [ExtendObjectType(OperationTypeNames.Mutation)] public class WishlistsMutation { - public Task StartPersonalWishlist(WishlistCreateDto dto, CancellationToken cancellationToken, + public Task StartPersonalWishlistAsync(WishlistCreateDto dto, CancellationToken cancellationToken, [Service] IWishlistsService wishlistsService) => wishlistsService.StartPersonalWishlistAsync(dto, cancellationToken); - public Task AddMessageToPersonalWishlist(string wishlistId, MessageCreateDto dto, CancellationToken cancellationToken, + public Task AddMessageToPersonalWishlistAsync(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 index 389864f..49a229f 100644 --- a/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs +++ b/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs @@ -9,12 +9,17 @@ namespace ShoppingAssistantApi.Api.Queries; public class WishlistsQuery { [Authorize] - public Task> GetPersonalWishlistsPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken, - [Service] IWishlistsService wishlistsService) + 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) + [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); } 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/IServices/IWishlistService.cs b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs index 41081c6..c2cd5f3 100644 --- a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs +++ b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs @@ -14,5 +14,7 @@ public interface IWishlistsService Task GetPersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken); + Task> GetMessagesPageFromPersonalWishlistAsync(string wishlistId, int pageNumber, int pageSize, CancellationToken cancellationToken); + Task DeletePersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken); } diff --git a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs index a5166f2..6ec7a20 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs @@ -47,6 +47,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,10 +64,11 @@ 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; await TryGetPersonalWishlist(wishlistObjectId, cancellationToken); @@ -94,6 +97,22 @@ public class WishlistsService : IWishlistsService 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 DeletePersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken) { if (!ObjectId.TryParse(wishlistId, out var wishlistObjectId)) diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index a63c8f7..2d3af82 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -27,6 +27,8 @@ public class DbInitialaizer private readonly IMongoCollection _wishlistCollection; + private readonly IMongoCollection _messageCollection; + public IEnumerable Roles { get; set; } public DbInitialaizer(IServiceProvider serviceProvider) @@ -35,8 +37,9 @@ public class DbInitialaizer _rolesService = serviceProvider.GetService(); _userManager = serviceProvider.GetService(); _tokensService = serviceProvider.GetService(); - _wishlistCollection = serviceProvider.GetService().Db.GetCollection("Wishlists"); _userCollection = serviceProvider.GetService().Db.GetCollection("Users"); + _wishlistCollection = serviceProvider.GetService().Db.GetCollection("Wishlists"); + _messageCollection = serviceProvider.GetService().Db.GetCollection("Messages"); } public async Task InitialaizeDb(CancellationToken cancellationToken) @@ -172,45 +175,79 @@ public class DbInitialaizer 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 wishlistId1 = ObjectId.Parse("ab79cde6f69abcd3efab65cd"); + var wishlistId2 = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"); + var wishlists = new Wishlist[] { new Wishlist { - Id = ObjectId.Parse("ab79cde6f69abcd3efab65cd"), + Id = wishlistId1, 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"), + Id = wishlistId2, 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); + + var messages = new Message[] + { + new Message + { + Text = "Message 1", + Role = MessageRoles.User.ToString(), + WishlistId = wishlistId1, + CreatedById = user1.Id, + }, + new Message + { + Text = "Message 2", + Role = MessageRoles.Application.ToString(), + WishlistId = wishlistId1, + }, + new Message + { + Text = "Message 3", + Role = MessageRoles.User.ToString(), + WishlistId = wishlistId1, + CreatedById = user1.Id, + }, + new Message + { + Text = "Message 4", + Role = MessageRoles.Application.ToString(), + WishlistId = wishlistId1, + }, + new Message + { + Text = "Message 5", + Role = MessageRoles.User.ToString(), + WishlistId = wishlistId1, + CreatedById = user1.Id, + }, + new Message + { + Text = "Message 6", + Role = MessageRoles.Application.ToString(), + WishlistId = wishlistId1, + }, + new Message + { + Text = "Prompt", + Role = MessageRoles.User.ToString(), + WishlistId = wishlistId2, + CreatedById = user2.Id, + } + }; + + await _messageCollection.InsertManyAsync(messages); } } diff --git a/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs index 06481f6..23f9b8f 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,14 @@ 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) + { + var messageCount = await GetCountAsync(predicate, cancellationToken); + + return await _collection.Find(predicate) + .Skip((messageCount / pageSize - pageNumber) * pageSize) + .Limit(pageSize) + .ToListAsync(cancellationToken); + } } diff --git a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs index 93f37f8..2c64d42 100644 --- a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -185,6 +185,44 @@ public class WishlistsTests : IClassFixture> Assert.Equal(user.Id, messageCreatedById); } + [Fact] + public async Task GetMessagesPageFromPersonalWishlist_ValidPageNumberAndSizeValidWishlistIdOrAuthorizedAccess_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 = "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.data.messagesPageFromPersonalWishlist); + + var messagesPageFromPersonalWishlist = Enumerable.ToList(document.data.messagesPageFromPersonalWishlist.items); + var firstMessageInPage = messagesPageFromPersonalWishlist[0]; + var secondMessageInPage = messagesPageFromPersonalWishlist[1]; + + Assert.Equal("Message 5", (string) firstMessageInPage.text); + Assert.Equal(MessageRoles.User.ToString(), (string) firstMessageInPage.role); + Assert.Equal(user.Id, (string) firstMessageInPage.createdById); + } + [Fact] public async Task DeletePersonalWishlist_ValidWishlistIdOrAuthorizedAccess_ReturnsWishlistModel() { @@ -323,6 +361,106 @@ public class WishlistsTests : IClassFixture> Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); } + [Fact] + public async Task GetMessagesPageFromPersonalWishlist_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 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.InternalServerError, response.StatusCode); + } + + [Fact] + public async Task GetMessagesPageFromPersonalWishlist_InValidPageSize_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_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.InternalServerError, response.StatusCode); + } + + [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 DeletePersonalWishlist_InValidWishlistId_ReturnsInternalServerError() { @@ -345,4 +483,27 @@ public class WishlistsTests : IClassFixture> 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); + } } From 9c9081a44f3fcbd250566dd05cf731682010370c Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 15 Oct 2023 10:55:39 +0300 Subject: [PATCH 07/22] fix unauthorized access to wishlistsPage --- .../Services/WishlistsService.cs | 2 +- .../Repositories/BaseRepository.cs | 2 +- .../Repositories/MessagesRepository.cs | 5 +- .../Tests/WishlistsTests.cs | 86 +++++++++++++++++-- 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs index 6ec7a20..99cbad7 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs @@ -79,7 +79,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); 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 23f9b8f..2181ac5 100644 --- a/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs +++ b/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs @@ -14,8 +14,11 @@ public class MessagesRepository : BaseRepository, IMessagesRepository { var messageCount = await GetCountAsync(predicate, cancellationToken); + pageSize = Math.Clamp(pageSize, 1, messageCount); + var numberOfPages = messageCount / pageSize; + return await _collection.Find(predicate) - .Skip((messageCount / pageSize - pageNumber) * pageSize) + .Skip((numberOfPages - pageNumber) * pageSize) .Limit(pageSize) .ToListAsync(cancellationToken); } diff --git a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs index 2c64d42..2e2a97d 100644 --- a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -86,7 +86,7 @@ public class WishlistsTests : IClassFixture> variables = new { pageNumber = 1, - pageSize = 1 + pageSize = 5 } }; @@ -212,8 +212,6 @@ public class WishlistsTests : IClassFixture> var responseString = await response.Content.ReadAsStringAsync(); var document = JsonConvert.DeserializeObject(responseString); - Console.WriteLine(document.data.messagesPageFromPersonalWishlist); - var messagesPageFromPersonalWishlist = Enumerable.ToList(document.data.messagesPageFromPersonalWishlist.items); var firstMessageInPage = messagesPageFromPersonalWishlist[0]; var secondMessageInPage = messagesPageFromPersonalWishlist[1]; @@ -286,6 +284,71 @@ 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); + + 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); + + 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() { @@ -362,7 +425,7 @@ public class WishlistsTests : IClassFixture> } [Fact] - public async Task GetMessagesPageFromPersonalWishlist_InValidPageNumber_ReturnsInternalServerError() + 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); @@ -387,7 +450,7 @@ public class WishlistsTests : IClassFixture> } [Fact] - public async Task GetMessagesPageFromPersonalWishlist_InValidPageSize_ReturnsInternalServerError() + 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); @@ -408,7 +471,18 @@ public class WishlistsTests : IClassFixture> var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); using var response = await _httpClient.PostAsync("graphql", content); - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var responseString = await response.Content.ReadAsStringAsync(); + var document = JsonConvert.DeserializeObject(responseString); + + var messagesPageFromPersonalWishlist = Enumerable.ToList(document.data.messagesPageFromPersonalWishlist.items); + var firstMessageInPage = messagesPageFromPersonalWishlist[0]; + var secondMessageInPage = messagesPageFromPersonalWishlist[1]; + + Assert.Equal("Message 1", (string) firstMessageInPage.text); + Assert.Equal(MessageRoles.User.ToString(), (string) firstMessageInPage.role); + Assert.Equal(user.Id, (string) firstMessageInPage.createdById); } [Fact] From f24365a3eb088c3f87a4c22cf7e483be5ffdead6 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 15 Oct 2023 12:17:00 +0300 Subject: [PATCH 08/22] fix unpredictable results from GetPage method --- .../Repositories/MessagesRepository.cs | 15 ++++++++++++++ .../Tests/WishlistsTests.cs | 20 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs index 2181ac5..801963e 100644 --- a/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs +++ b/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs @@ -17,6 +17,21 @@ public class MessagesRepository : BaseRepository, IMessagesRepository pageSize = Math.Clamp(pageSize, 1, messageCount); var numberOfPages = messageCount / pageSize; + if (pageNumber > numberOfPages) + { + return new List(); + } + + if (pageNumber < 1) + { + throw new ArgumentOutOfRangeException(); + } + + if (pageSize < 1) + { + throw new ArgumentOutOfRangeException(); + } + return await _collection.Find(predicate) .Skip((numberOfPages - pageNumber) * pageSize) .Limit(pageSize) diff --git a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs index 2e2a97d..90394c5 100644 --- a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -310,6 +310,8 @@ public class WishlistsTests : IClassFixture> 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); @@ -342,6 +344,9 @@ public class WishlistsTests : IClassFixture> 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; @@ -446,7 +451,17 @@ public class WishlistsTests : IClassFixture> var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); using var response = await _httpClient.PostAsync("graphql", content); - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + 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] @@ -476,6 +491,9 @@ public class WishlistsTests : IClassFixture> 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]; From b9a58a963a63f870474e3d374ea3c0b7150b2531 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 15 Oct 2023 13:22:06 +0300 Subject: [PATCH 09/22] add ability to view products in wishlist --- .../Queries/WishlistsQuery.cs | 5 + .../IRepositories/IProductsRepository.cs | 5 + .../IServices/IWishlistService.cs | 2 + .../MappingProfiles/ProductProfile.cs | 15 ++ .../Models/CreateDtos/ProductCreateDto.cs | 18 +++ .../Models/Dtos/MessageDto.cs | 2 +- .../Models/Dtos/ProductDto.cs | 20 +++ .../Models/Dtos/WishlistDto.cs | 2 +- .../Entities/Message.cs | 2 +- .../Entities/Product.cs | 21 +++ .../Services/WishlistsService.cs | 26 +++- .../PersistanceExtentions/DbInitialaizer.cs | 54 ++++++- .../RepositoriesExtention.cs | 1 + .../Repositories/ProductRepository.cs | 10 ++ .../Tests/WishlistsTests.cs | 145 +++++++++++++++++- 15 files changed, 321 insertions(+), 7 deletions(-) create mode 100644 ShoppingAssistantApi.Application/IRepositories/IProductsRepository.cs create mode 100644 ShoppingAssistantApi.Application/MappingProfiles/ProductProfile.cs create mode 100644 ShoppingAssistantApi.Application/Models/CreateDtos/ProductCreateDto.cs create mode 100644 ShoppingAssistantApi.Application/Models/Dtos/ProductDto.cs create mode 100644 ShoppingAssistantApi.Domain/Entities/Product.cs create mode 100644 ShoppingAssistantApi.Persistance/Repositories/ProductRepository.cs diff --git a/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs b/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs index 49a229f..086be2e 100644 --- a/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs +++ b/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs @@ -22,4 +22,9 @@ public class WishlistsQuery 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/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/IWishlistService.cs b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs index c2cd5f3..b34e985 100644 --- a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs +++ b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs @@ -16,5 +16,7 @@ public interface IWishlistsService Task> GetMessagesPageFromPersonalWishlistAsync(string wishlistId, int pageNumber, int pageSize, 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..56687e1 --- /dev/null +++ b/ShoppingAssistantApi.Application/Models/CreateDtos/ProductCreateDto.cs @@ -0,0 +1,18 @@ +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; } + + public required string WishlistId { 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.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 new file mode 100644 index 0000000..40fbf90 --- /dev/null +++ b/ShoppingAssistantApi.Domain/Entities/Product.cs @@ -0,0 +1,21 @@ +using MongoDB.Bson; +using ShoppingAssistantApi.Domain.Common; + +namespace ShoppingAssistantApi.Domain.Entities; + +public class Product : EntityBase +{ + 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.Infrastructure/Services/WishlistsService.cs b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs index 99cbad7..1a91f55 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 _productRepository; + 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; + _productRepository = productRepository; _mapper = mapper; } @@ -113,6 +116,27 @@ public class WishlistsService : IWishlistsService return new PagedList(dtos, pageNumber, pageSize, count); } + 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 _productRepository.GetPageAsync(pageNumber, pageSize, x => x.WishlistId == wishlistObjectId, cancellationToken); + + foreach (var e in entities) + { + Console.WriteLine(e.Name); + } + + var dtos = _mapper.Map>(entities); + var count = await _productRepository.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)) diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index 2d3af82..6233c86 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -29,6 +29,8 @@ public class DbInitialaizer private readonly IMongoCollection _messageCollection; + private readonly IMongoCollection _productCollection; + public IEnumerable Roles { get; set; } public DbInitialaizer(IServiceProvider serviceProvider) @@ -40,13 +42,14 @@ public class DbInitialaizer _userCollection = serviceProvider.GetService().Db.GetCollection("Users"); _wishlistCollection = serviceProvider.GetService().Db.GetCollection("Wishlists"); _messageCollection = serviceProvider.GetService().Db.GetCollection("Messages"); + _productCollection = serviceProvider.GetService().Db.GetCollection("Products"); } public async Task InitialaizeDb(CancellationToken cancellationToken) { await AddRoles(cancellationToken); await AddUsers(cancellationToken); - await AddWishlistsWithMessages(cancellationToken); + await AddWishlistsWithMessagesAndProducts(cancellationToken); } public async Task AddUsers(CancellationToken cancellationToken) @@ -170,7 +173,7 @@ public class DbInitialaizer var dto3 = await _rolesService.AddRoleAsync(role3, cancellationToken); } - public async Task AddWishlistsWithMessages(CancellationToken cancellationToken) + public async Task AddWishlistsWithMessagesAndProducts(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(); @@ -186,6 +189,7 @@ public class DbInitialaizer Name = "Gaming PC", Type = WishlistTypes.Product.ToString(), CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow }, new Wishlist { @@ -193,6 +197,7 @@ public class DbInitialaizer Name = "Generic Wishlist Name", Type = WishlistTypes.Product.ToString(), CreatedById = user2.Id, + CreatedDateUtc = DateTime.UtcNow } }; @@ -206,12 +211,14 @@ public class DbInitialaizer Role = MessageRoles.User.ToString(), WishlistId = wishlistId1, CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow }, new Message { Text = "Message 2", Role = MessageRoles.Application.ToString(), WishlistId = wishlistId1, + CreatedDateUtc = DateTime.UtcNow }, new Message { @@ -219,12 +226,14 @@ public class DbInitialaizer Role = MessageRoles.User.ToString(), WishlistId = wishlistId1, CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow }, new Message { Text = "Message 4", Role = MessageRoles.Application.ToString(), WishlistId = wishlistId1, + CreatedDateUtc = DateTime.UtcNow }, new Message { @@ -232,12 +241,14 @@ public class DbInitialaizer Role = MessageRoles.User.ToString(), WishlistId = wishlistId1, CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow }, new Message { Text = "Message 6", Role = MessageRoles.Application.ToString(), WishlistId = wishlistId1, + CreatedDateUtc = DateTime.UtcNow }, new Message { @@ -245,9 +256,48 @@ public class DbInitialaizer Role = MessageRoles.User.ToString(), WishlistId = wishlistId2, CreatedById = user2.Id, + CreatedDateUtc = DateTime.UtcNow } }; await _messageCollection.InsertManyAsync(messages); + + var products = new Product[] + { + new Product + { + Name = "AMD Ryzen 5 5600G 6-Core 12-Thread Unlocked Desktop Processor with Radeon Graphics", + Description = "Features best-in-class graphics performance in a desktop processor for smooth 1080p gaming, no graphics card required", + Rating = 4.8, + Url = "https://a.co/d/5ceuIrq", + ImagesUrls = new string[] + { + "https://m.media-amazon.com/images/I/51f2hkWjTlL._AC_SL1200_.jpg", + "https://m.media-amazon.com/images/I/51iji7Gel-L._AC_SL1200_.jpg" + }, + WasOpened = false, + WishlistId = wishlistId1, + CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow + }, + new Product + { + Name = "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 ", + Description = "7 Year Limited Warranty: The 970 EVO Plus provides up to 1200 TBW (Terabytes Written) with 5-years of protection for exceptional endurance powered by the latest V-NAND technology and Samsung's reputation for quality ", + Rating = 4.8, + Url = "https://a.co/d/gxnuqs1", + ImagesUrls = new string[] + { + "https://m.media-amazon.com/images/I/51Brl+iYtvL._AC_SL1001_.jpg", + "https://m.media-amazon.com/images/I/51GOfLlVwoL._AC_SL1001_.jpg" + }, + WasOpened = false, + WishlistId = wishlistId1, + CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow + }, + }; + + await _productCollection.InsertManyAsync(products); } } 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/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 90394c5..6f22fe7 100644 --- a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -186,7 +186,7 @@ public class WishlistsTests : IClassFixture> } [Fact] - public async Task GetMessagesPageFromPersonalWishlist_ValidPageNumberAndSizeValidWishlistIdOrAuthorizedAccess_ReturnsWishlistModel() + 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); @@ -221,6 +221,40 @@ public class WishlistsTests : IClassFixture> Assert.Equal(user.Id, (string) firstMessageInPage.createdById); } + [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() { @@ -553,6 +587,115 @@ public class WishlistsTests : IClassFixture> 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() { From 1412c07fa279774837f188ba153e98f098d49445 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 15 Oct 2023 13:32:35 +0300 Subject: [PATCH 10/22] fix: return oredered wishlist messages Before that there was a possibility of returning pages with incorrectly ordered messages --- .../PersistanceExtentions/DbInitialaizer.cs | 10 +++++----- .../Repositories/MessagesRepository.cs | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index 6233c86..cd43f63 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -218,7 +218,7 @@ public class DbInitialaizer Text = "Message 2", Role = MessageRoles.Application.ToString(), WishlistId = wishlistId1, - CreatedDateUtc = DateTime.UtcNow + CreatedDateUtc = DateTime.UtcNow.AddSeconds(5) }, new Message { @@ -226,14 +226,14 @@ public class DbInitialaizer Role = MessageRoles.User.ToString(), WishlistId = wishlistId1, CreatedById = user1.Id, - CreatedDateUtc = DateTime.UtcNow + CreatedDateUtc = DateTime.UtcNow.AddSeconds(20) }, new Message { Text = "Message 4", Role = MessageRoles.Application.ToString(), WishlistId = wishlistId1, - CreatedDateUtc = DateTime.UtcNow + CreatedDateUtc = DateTime.UtcNow.AddSeconds(25) }, new Message { @@ -241,14 +241,14 @@ public class DbInitialaizer Role = MessageRoles.User.ToString(), WishlistId = wishlistId1, CreatedById = user1.Id, - CreatedDateUtc = DateTime.UtcNow + CreatedDateUtc = DateTime.UtcNow.AddSeconds(45) }, new Message { Text = "Message 6", Role = MessageRoles.Application.ToString(), WishlistId = wishlistId1, - CreatedDateUtc = DateTime.UtcNow + CreatedDateUtc = DateTime.UtcNow.AddSeconds(50) }, new Message { diff --git a/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs index 801963e..02d309d 100644 --- a/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs +++ b/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs @@ -33,6 +33,7 @@ public class MessagesRepository : BaseRepository, IMessagesRepository } return await _collection.Find(predicate) + .SortBy(x => x.CreatedDateUtc) .Skip((numberOfPages - pageNumber) * pageSize) .Limit(pageSize) .ToListAsync(cancellationToken); From 2096df2c34c6e138d5a4852f7d8d35cbc283f62d Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 15 Oct 2023 14:04:57 +0300 Subject: [PATCH 11/22] add ability to add product to wishlist --- .../Mutations/WishlistsMutation.cs | 10 +- .../Queries/WishlistsQuery.cs | 8 +- .../IServices/IWishlistService.cs | 2 + .../Models/CreateDtos/ProductCreateDto.cs | 2 - .../Services/WishlistsService.cs | 33 ++-- .../Tests/WishlistsTests.cs | 148 +++++++++++++++++- 6 files changed, 184 insertions(+), 19 deletions(-) diff --git a/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs b/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs index c4dde4c..f3ff806 100644 --- a/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs +++ b/ShoppingAssistantApi.Api/Mutations/WishlistsMutation.cs @@ -8,14 +8,18 @@ namespace ShoppingAssistantApi.Api.Mutations; public class WishlistsMutation { public Task StartPersonalWishlistAsync(WishlistCreateDto dto, CancellationToken cancellationToken, - [Service] IWishlistsService wishlistsService) + [Service] IWishlistsService wishlistsService) => wishlistsService.StartPersonalWishlistAsync(dto, cancellationToken); public Task AddMessageToPersonalWishlistAsync(string wishlistId, MessageCreateDto dto, CancellationToken cancellationToken, - [Service] IWishlistsService wishlistsService) + [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) + [Service] IWishlistsService wishlistsService) => wishlistsService.DeletePersonalWishlistAsync(wishlistId, cancellationToken); } diff --git a/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs b/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs index 086be2e..d3d36be 100644 --- a/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs +++ b/ShoppingAssistantApi.Api/Queries/WishlistsQuery.cs @@ -11,20 +11,20 @@ public class WishlistsQuery [Authorize] public Task> GetPersonalWishlistsPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken, [Service] IWishlistsService wishlistsService) - => wishlistsService.GetPersonalWishlistsPageAsync(pageNumber, pageSize, cancellationToken); + => wishlistsService.GetPersonalWishlistsPageAsync(pageNumber, pageSize, cancellationToken); [Authorize] public Task GetPersonalWishlistAsync(string wishlistId, CancellationToken cancellationToken, [Service] IWishlistsService wishlistsService) - => wishlistsService.GetPersonalWishlistAsync(wishlistId, cancellationToken); + => 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); + => 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); + => wishlistsService.GetProductsPageFromPersonalWishlistAsync(wishlistId, pageNumber, pageSize, cancellationToken); } diff --git a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs index b34e985..d95a874 100644 --- a/ShoppingAssistantApi.Application/IServices/IWishlistService.cs +++ b/ShoppingAssistantApi.Application/IServices/IWishlistService.cs @@ -16,6 +16,8 @@ public interface IWishlistsService 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/Models/CreateDtos/ProductCreateDto.cs b/ShoppingAssistantApi.Application/Models/CreateDtos/ProductCreateDto.cs index 56687e1..015706c 100644 --- a/ShoppingAssistantApi.Application/Models/CreateDtos/ProductCreateDto.cs +++ b/ShoppingAssistantApi.Application/Models/CreateDtos/ProductCreateDto.cs @@ -13,6 +13,4 @@ public class ProductCreateDto public required string[] ImagesUrls { get; set; } public required bool WasOpened { get; set; } - - public required string WishlistId { get; set; } } diff --git a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs index 1a91f55..5dd2ae7 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/WishlistsService.cs @@ -18,7 +18,7 @@ public class WishlistsService : IWishlistsService private readonly IMessagesRepository _messagesRepository; - private readonly IProductsRepository _productRepository; + private readonly IProductsRepository _productsRepository; private readonly IMapper _mapper; @@ -26,7 +26,7 @@ public class WishlistsService : IWishlistsService { _wishlistsRepository = wishlistRepository; _messagesRepository = messageRepository; - _productRepository = productRepository; + _productsRepository = productRepository; _mapper = mapper; } @@ -116,6 +116,26 @@ public class WishlistsService : IWishlistsService 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)) @@ -125,15 +145,10 @@ public class WishlistsService : IWishlistsService await TryGetPersonalWishlist(wishlistObjectId, cancellationToken); - var entities = await _productRepository.GetPageAsync(pageNumber, pageSize, x => x.WishlistId == wishlistObjectId, cancellationToken); - - foreach (var e in entities) - { - Console.WriteLine(e.Name); - } + var entities = await _productsRepository.GetPageAsync(pageNumber, pageSize, x => x.WishlistId == wishlistObjectId, cancellationToken); var dtos = _mapper.Map>(entities); - var count = await _productRepository.GetCountAsync(x => x.WishlistId == wishlistObjectId, cancellationToken); + var count = await _productsRepository.GetCountAsync(x => x.WishlistId == wishlistObjectId, cancellationToken); return new PagedList(dtos, pageNumber, pageSize, count); } diff --git a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs index 6f22fe7..eab15fa 100644 --- a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -221,6 +221,51 @@ public class WishlistsTests : IClassFixture> Assert.Equal(user.Id, (string) firstMessageInPage.createdById); } + [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() { @@ -435,7 +480,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); @@ -463,6 +508,35 @@ public class WishlistsTests : IClassFixture> 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() { @@ -587,6 +661,78 @@ public class WishlistsTests : IClassFixture> 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() { From 98795719dc57bad764950a4ab90bcf78279b8e05 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 15 Oct 2023 14:10:19 +0300 Subject: [PATCH 12/22] remove unused unit test class --- .../OpenAiServiceTests.cs | 86 ------------------- 1 file changed, 86 deletions(-) delete mode 100644 ShoppingAssistantApi.UnitTests/OpenAiServiceTests.cs diff --git a/ShoppingAssistantApi.UnitTests/OpenAiServiceTests.cs b/ShoppingAssistantApi.UnitTests/OpenAiServiceTests.cs deleted file mode 100644 index 7ec9de8..0000000 --- a/ShoppingAssistantApi.UnitTests/OpenAiServiceTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Net; -using Moq; -using Moq.Protected; -using ShoppingAssistantApi.Application.IServices; -using ShoppingAssistantApi.Application.Models.OpenAi; -using ShoppingAssistantApi.Domain.Enums; -using ShoppingAssistantApi.Infrastructure.Services; - -namespace ShoppingAssistantApi.UnitTests; - -public class OpenAiServiceTests -{ - private readonly IOpenAiService _openAiService; - - private readonly Mock _mockHttpMessageHandler; - - private readonly HttpClient _httpClient; - - public OpenAiServiceTests() - { - // Mock any dependencies - _mockHttpMessageHandler = new Mock(); - _httpClient = new HttpClient(_mockHttpMessageHandler.Object); - _openAiService = new OpenAiService(_httpClient); - } - - [Fact] - public async Task GetChatCompletion_ValidChat_ReturnsNewMessage() - { - // Arrange - _mockHttpMessageHandler - .Protected() - .Setup>( - "SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny() - ) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(@" - { - ""id"": ""chatcmpl-89OMdgTZXOLAXv7bPUJ4SwrPpS5Md"", - ""object"": ""chat.completion"", - ""created"": 1697249299, - ""model"": ""gpt-3.5-turbo-0613"", - ""choices"": [ - { - ""index"": 0, - ""message"": { - ""role"": ""assistant"", - ""content"": ""Hello World!"" - }, - ""finish_reason"": ""stop"" - } - ], - ""usage"": { - ""prompt_tokens"": 10, - ""completion_tokens"": 3, - ""total_tokens"": 13 - } - }"), - }); - - var chat = new ChatCompletionRequest - { - Messages = new List - { - new OpenAiMessage - { - Role = OpenAiRole.User, - Content = "Return Hello World!" - } - } - }; - - // Act - var newMessage = await _openAiService.GetChatCompletion(chat, CancellationToken.None); - - // Assert - Assert.NotNull(newMessage); - Assert.Equal("Hello, World!", newMessage.Content); - } - - // TODO: Add more tests -} \ No newline at end of file From 5abb14201518fbdeea3e1c5cab19aba4b71b1720 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Sun, 15 Oct 2023 16:26:00 +0300 Subject: [PATCH 13/22] fix message pages --- .../Repositories/MessagesRepository.cs | 24 ++----------------- .../Tests/WishlistsTests.cs | 12 +++++----- 2 files changed, 8 insertions(+), 28 deletions(-) diff --git a/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs index 02d309d..55734c9 100644 --- a/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs +++ b/ShoppingAssistantApi.Persistance/Repositories/MessagesRepository.cs @@ -12,29 +12,9 @@ public class MessagesRepository : BaseRepository, IMessagesRepository public async Task> GetPageStartingFromEndAsync(int pageNumber, int pageSize, Expression> predicate, CancellationToken cancellationToken) { - var messageCount = await GetCountAsync(predicate, cancellationToken); - - pageSize = Math.Clamp(pageSize, 1, messageCount); - var numberOfPages = messageCount / pageSize; - - if (pageNumber > numberOfPages) - { - return new List(); - } - - if (pageNumber < 1) - { - throw new ArgumentOutOfRangeException(); - } - - if (pageSize < 1) - { - throw new ArgumentOutOfRangeException(); - } - return await _collection.Find(predicate) - .SortBy(x => x.CreatedDateUtc) - .Skip((numberOfPages - pageNumber) * pageSize) + .SortByDescending(x => x.CreatedDateUtc) + .Skip((pageNumber - 1) * pageSize) .Limit(pageSize) .ToListAsync(cancellationToken); } diff --git a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs index eab15fa..fa884c9 100644 --- a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -212,13 +212,14 @@ public class WishlistsTests : IClassFixture> 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 5", (string) firstMessageInPage.text); - Assert.Equal(MessageRoles.User.ToString(), (string) firstMessageInPage.role); - Assert.Equal(user.Id, (string) firstMessageInPage.createdById); + Assert.Equal("Message 6", (string) firstMessageInPage.text); + Assert.Equal(MessageRoles.Application.ToString(), (string) firstMessageInPage.role); } [Fact] @@ -606,9 +607,8 @@ public class WishlistsTests : IClassFixture> var firstMessageInPage = messagesPageFromPersonalWishlist[0]; var secondMessageInPage = messagesPageFromPersonalWishlist[1]; - Assert.Equal("Message 1", (string) firstMessageInPage.text); - Assert.Equal(MessageRoles.User.ToString(), (string) firstMessageInPage.role); - Assert.Equal(user.Id, (string) firstMessageInPage.createdById); + Assert.Equal("Message 6", (string) firstMessageInPage.text); + Assert.Equal(MessageRoles.Application.ToString(), (string) firstMessageInPage.role); } [Fact] From 80c9bc2b3289ce000ef86733f41b8bde85f72895 Mon Sep 17 00:00:00 2001 From: stasex Date: Sun, 15 Oct 2023 16:27:29 +0300 Subject: [PATCH 14/22] Small changes for DbInitialaizer with products collection --- .../PersistanceExtentions/DbInitialaizer.cs | 74 +++++++++++++------ 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index 651e374..9d88eb1 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -222,40 +222,72 @@ public class DbInitialaizer public async Task AddProducts(CancellationToken cancellationToken) { - var wishList1 = await _wishlistCollection.FindAsync(w => w.Name == "Gaming PC"); - var wishList2 = await _wishlistCollection.FindAsync(w => w.Name == "Generic Wishlist Name"); - var products = new Product[] { new Product() { - Id = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), - Url = "url", - Name = "Thermaltake Glacier", - Description = "Something", - Rating = 4.1, + Name = "Thermaltake Glacier 360 Liquid-Cooled PC", + Description = "Cool PC for any task!", + Rating = 4.3, + Url = "https://www.amazon.com/Thermaltake-Liquid-Cooled-ToughRAM-Computer-S3WT-B550-G36-LCS/dp" + + "/B09FYNM2GW/ref=sr_1_1?crid=391KAS4JFJSFF&keywords=gaming%2Bpc&qid=1697132083&sprefix=gaming%2Bpc%2Caps%2C209&sr=8-1&th=1", ImagesUrls = new string[] { - "url1", - "url2" - } + "https://m.media-amazon.com/images/I/61cXu9yGldL._AC_SL1200_.jpg", + "https://m.media-amazon.com/images/I/615gxSGp42L._AC_SL1200_.jpg" + }, + CreatedDateUtc = DateTime.UtcNow }, new Product() { - Id = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), - Url = "url", - Name = "Mac", - Description = "very very cool laptop", - Rating = 4.9, + Name = "Apple MagSafe Battery Pack", + Description = "Portable Charger with Fast Charging Capability, Power Bank Compatible with iPhone", + Rating = 4.3, + Url = "https://www.amazon.com/Apple-MJWY3AM-A-MagSafe-Battery/dp/" + + "B099BWY7WT/ref=sr_1_1?keywords=apple+power+bank&qid=1697375350&sr=8-1", ImagesUrls = new string[] { - "url1", - "url2" - } - } + "https://m.media-amazon.com/images/I/418SjFMB1wL._AC_SX679_.jpg", + "https://m.media-amazon.com/images/I/51v4pgChtLL._AC_SX679_.jpg", + "https://m.media-amazon.com/images/I/61mJ0z7uYQL._AC_SX679_.jpg" + }, + CreatedDateUtc = DateTime.UtcNow + }, + new Product() + { + Name = "Logitech K400 Plus Wireless Touch With Easy Media Control and Built-in Touchpad", + Description = "Reliable membrane keyboard with touchpad!", + Rating = 4.5, + Url = "https://www.amazon.com/Logitech-Wireless-Keyboard-Touchpad-PC-connected/dp/B014EUQOGK/" + + "ref=sr_1_11?crid=BU2PHZKHKD65&keywords=keyboard+wireless&qid=1697375559&sprefix=keyboard+wir%2Caps%2C195&sr=8-11", + ImagesUrls = new string[] + { + "https://m.media-amazon.com/images/I/51yjnWJ5urL._AC_SX466_.jpg", + "https://m.media-amazon.com/images/I/71al70zP7QL._AC_SX466_.jpg", + "https://m.media-amazon.com/images/I/71+JXDDY01L._AC_SX466_.jpg" + }, + CreatedDateUtc = DateTime.UtcNow + }, + + new Product() + { + Name = "Logitech MX Anywhere 2S Wireless Mouse Use On Any Surface", + Description = "Cross computer control: Game changing capacity to navigate seamlessly on three computers," + + " and copy paste text, images, and files from one to the other using Logitech Flow", + Rating = 4.6, + Url = "https://www.amazon.com/Logitech-Hyper-Fast-Scrolling-Rechargeable-Computers/dp/B08P2JFPQC/ref=sr_1_8?" + + "crid=2BL6Z14W2TPP3&keywords=mouse%2Bwireless&qid=1697375784&sprefix=mousewireless%2Caps%2C197&sr=8-8&th=1", + ImagesUrls = new string[] + { + "https://m.media-amazon.com/images/I/6170mJHIsYL._AC_SX466_.jpg", + "https://m.media-amazon.com/images/I/71a5As76MDL._AC_SX466_.jpg" + }, + CreatedDateUtc = DateTime.UtcNow + } }; - + + await _productCollection.InsertManyAsync(products); } } From 004033f3076cb42c402eda941a289a5cfb59bac3 Mon Sep 17 00:00:00 2001 From: stasex Date: Sun, 15 Oct 2023 16:38:20 +0300 Subject: [PATCH 15/22] added the missing property to the product entity --- ShoppingAssistantApi.Domain/Entities/Product.cs | 2 ++ .../PersistanceExtentions/DbInitialaizer.cs | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ShoppingAssistantApi.Domain/Entities/Product.cs b/ShoppingAssistantApi.Domain/Entities/Product.cs index c581196..f3b2ccd 100644 --- a/ShoppingAssistantApi.Domain/Entities/Product.cs +++ b/ShoppingAssistantApi.Domain/Entities/Product.cs @@ -16,4 +16,6 @@ public class Product : EntityBase public double Rating { get; set; } public string[]? ImagesUrls { get; set; } + + public bool WasOpened { get; set; } } \ No newline at end of file diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index 9d88eb1..dbba926 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -236,7 +236,8 @@ public class DbInitialaizer "https://m.media-amazon.com/images/I/61cXu9yGldL._AC_SL1200_.jpg", "https://m.media-amazon.com/images/I/615gxSGp42L._AC_SL1200_.jpg" }, - CreatedDateUtc = DateTime.UtcNow + CreatedDateUtc = DateTime.UtcNow, + WasOpened = false }, new Product() @@ -252,7 +253,8 @@ public class DbInitialaizer "https://m.media-amazon.com/images/I/51v4pgChtLL._AC_SX679_.jpg", "https://m.media-amazon.com/images/I/61mJ0z7uYQL._AC_SX679_.jpg" }, - CreatedDateUtc = DateTime.UtcNow + CreatedDateUtc = DateTime.UtcNow, + WasOpened = false }, new Product() @@ -268,7 +270,8 @@ public class DbInitialaizer "https://m.media-amazon.com/images/I/71al70zP7QL._AC_SX466_.jpg", "https://m.media-amazon.com/images/I/71+JXDDY01L._AC_SX466_.jpg" }, - CreatedDateUtc = DateTime.UtcNow + CreatedDateUtc = DateTime.UtcNow, + WasOpened = false }, new Product() @@ -284,7 +287,8 @@ public class DbInitialaizer "https://m.media-amazon.com/images/I/6170mJHIsYL._AC_SX466_.jpg", "https://m.media-amazon.com/images/I/71a5As76MDL._AC_SX466_.jpg" }, - CreatedDateUtc = DateTime.UtcNow + CreatedDateUtc = DateTime.UtcNow, + WasOpened = false } }; From ba876b30fe78f2f94614a6fb257394aedc0d2809 Mon Sep 17 00:00:00 2001 From: stasex Date: Sun, 15 Oct 2023 20:08:21 +0300 Subject: [PATCH 16/22] added a method call --- .../PersistanceExtentions/DbInitialaizer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index dbba926..b64f784 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -50,6 +50,7 @@ public class DbInitialaizer await AddRoles(cancellationToken); await AddUsers(cancellationToken); await AddWishlistsWithMessages(cancellationToken); + await AddProducts(cancellationToken); } public async Task AddUsers(CancellationToken cancellationToken) From 250e493fc24a0a782af460deabd9c77b3c49c69f Mon Sep 17 00:00:00 2001 From: stasex Date: Sun, 15 Oct 2023 20:26:34 +0300 Subject: [PATCH 17/22] added a method call --- .../PersistanceExtentions/DbInitialaizer.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index dbba926..f96d491 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -50,6 +50,7 @@ public class DbInitialaizer await AddRoles(cancellationToken); await AddUsers(cancellationToken); await AddWishlistsWithMessages(cancellationToken); + await AddProducts(cancellationToken); } public async Task AddUsers(CancellationToken cancellationToken) @@ -192,11 +193,17 @@ public class DbInitialaizer { Text = "Prompt", Role = MessageRoles.User.ToString(), + WishlistId = ObjectId.Parse("ab79cde6f69abcd3efab65cd"), + CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow }, new Message { Text = "Answer", Role = MessageRoles.Application.ToString(), + WishlistId = ObjectId.Parse("ab79cde6f69abcd3efab65cd"), + CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow }, } }, @@ -212,6 +219,9 @@ public class DbInitialaizer { Text = "Prompt", Role = MessageRoles.User.ToString(), + WishlistId = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), + CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow } } } @@ -237,7 +247,8 @@ public class DbInitialaizer "https://m.media-amazon.com/images/I/615gxSGp42L._AC_SL1200_.jpg" }, CreatedDateUtc = DateTime.UtcNow, - WasOpened = false + WasOpened = false, + WishlistId = ObjectId.Parse("ab79cde6f69abcd3efab65cd") }, new Product() @@ -254,7 +265,8 @@ public class DbInitialaizer "https://m.media-amazon.com/images/I/61mJ0z7uYQL._AC_SX679_.jpg" }, CreatedDateUtc = DateTime.UtcNow, - WasOpened = false + WasOpened = false, + WishlistId = ObjectId.Parse("ab79cde6f69abcd3efab65cd") }, new Product() @@ -271,7 +283,8 @@ public class DbInitialaizer "https://m.media-amazon.com/images/I/71+JXDDY01L._AC_SX466_.jpg" }, CreatedDateUtc = DateTime.UtcNow, - WasOpened = false + WasOpened = false, + WishlistId = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab") }, new Product() @@ -288,7 +301,8 @@ public class DbInitialaizer "https://m.media-amazon.com/images/I/71a5As76MDL._AC_SX466_.jpg" }, CreatedDateUtc = DateTime.UtcNow, - WasOpened = false + WasOpened = false, + WishlistId = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab") } }; From 39bed12f30077ded23b537ea347fd7d471793631 Mon Sep 17 00:00:00 2001 From: shchoholiev Date: Mon, 16 Oct 2023 03:22:49 +0000 Subject: [PATCH 18/22] SA-14 Updated User model structure and Services --- .devcontainer/devcontainer.json | 6 + .../Mutations/AccessMutation.cs | 14 +- .../Mutations/RolesMutation.cs | 12 +- .../Mutations/UsersMutation.cs | 20 +- .../IServices/Identity/ITokensService.cs | 5 +- .../IServices/Identity/IUsersManager.cs | 8 +- ShoppingAssistantApi.Domain/Entities/User.cs | 4 - .../Services/Identity/TokensService.cs | 66 +--- .../Services/Identity/UserManager.cs | 299 +++++++++++------- .../Services/RolesService.cs | 4 +- .../Services/ServiceBase.cs | 15 + .../Repositories/UsersRepository.cs | 4 - 12 files changed, 249 insertions(+), 208 deletions(-) create mode 100644 ShoppingAssistantApi.Infrastructure/Services/ServiceBase.cs diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8c8e5e0..5ad2e4e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,6 +14,12 @@ // "protocol": "https" // } // } + + // Container is not working on M1 Mac + // "runArgs": [ + // "--platform=linux/amd64" + // ], + "customizations": { "vscode": { "extensions": [ diff --git a/ShoppingAssistantApi.Api/Mutations/AccessMutation.cs b/ShoppingAssistantApi.Api/Mutations/AccessMutation.cs index 7abc641..df920c0 100644 --- a/ShoppingAssistantApi.Api/Mutations/AccessMutation.cs +++ b/ShoppingAssistantApi.Api/Mutations/AccessMutation.cs @@ -7,14 +7,14 @@ namespace ShoppingAssistantApi.Api.Mutations; public class AccessMutation { public Task LoginAsync(AccessUserModel login, CancellationToken cancellationToken, - [Service] IUserManager userManager) - => userManager.LoginAsync(login, cancellationToken); + [Service] IUserManager userManager) + => userManager.LoginAsync(login, cancellationToken); public Task AccessGuestAsync(AccessGuestModel guest, CancellationToken cancellationToken, - [Service] IUserManager userManager) - => userManager.AccessGuestAsync(guest, cancellationToken); + [Service] IUserManager userManager) + => userManager.AccessGuestAsync(guest, cancellationToken); - public Task RefreshUserTokenAsync(TokensModel model, CancellationToken cancellationToken, - [Service] ITokensService tokensService) - => tokensService.RefreshUserAsync(model, cancellationToken); + public Task RefreshAccessTokenAsync(TokensModel model, CancellationToken cancellationToken, + [Service] IUserManager userManager) + => userManager.RefreshAccessTokenAsync(model, cancellationToken); } \ No newline at end of file diff --git a/ShoppingAssistantApi.Api/Mutations/RolesMutation.cs b/ShoppingAssistantApi.Api/Mutations/RolesMutation.cs index a4c98bb..e8e2138 100644 --- a/ShoppingAssistantApi.Api/Mutations/RolesMutation.cs +++ b/ShoppingAssistantApi.Api/Mutations/RolesMutation.cs @@ -1,22 +1,14 @@ -using ShoppingAssistantApi.Application.IServices.Identity; +using HotChocolate.Authorization; using ShoppingAssistantApi.Application.IServices; using ShoppingAssistantApi.Application.Models.CreateDtos; using ShoppingAssistantApi.Application.Models.Dtos; -using ShoppingAssistantApi.Application.Models.Identity; namespace ShoppingAssistantApi.Api.Mutations; [ExtendObjectType(OperationTypeNames.Mutation)] public class RolesMutation { - public Task AddToRoleAsync(string roleName, string id, CancellationToken cancellationToken, - [Service] IUserManager userManager) - => userManager.AddToRoleAsync(roleName, id, cancellationToken); - - public Task RemoveFromRoleAsync(string roleName, string id, CancellationToken cancellationToken, - [Service] IUserManager userManager) - => userManager.RemoveFromRoleAsync(roleName, id, cancellationToken); - + [Authorize] public Task AddRole(RoleCreateDto roleDto, CancellationToken cancellationToken, [Service] IRolesService rolesService) => rolesService.AddRoleAsync(roleDto, cancellationToken); diff --git a/ShoppingAssistantApi.Api/Mutations/UsersMutation.cs b/ShoppingAssistantApi.Api/Mutations/UsersMutation.cs index 1185f97..6beaccf 100644 --- a/ShoppingAssistantApi.Api/Mutations/UsersMutation.cs +++ b/ShoppingAssistantApi.Api/Mutations/UsersMutation.cs @@ -10,11 +10,21 @@ public class UsersMutation { [Authorize] public Task UpdateUserAsync(UserDto userDto, CancellationToken cancellationToken, - [Service] IUserManager userManager) - => userManager.UpdateAsync(userDto, cancellationToken); + [Service] IUserManager userManager) + => userManager.UpdateAsync(userDto, cancellationToken); [Authorize] - public Task UpdateUserByAdminAsync(string id, UserDto userDto, CancellationToken cancellationToken, - [Service] IUserManager userManager) - => userManager.UpdateUserByAdminAsync(id, userDto, cancellationToken); + public Task UpdateUserByAdminAsync(string id, UserDto userDto, CancellationToken cancellationToken, + [Service] IUserManager userManager) + => userManager.UpdateUserByAdminAsync(id, userDto, cancellationToken); + + [Authorize] + public Task AddToRoleAsync(string roleName, string userId, CancellationToken cancellationToken, + [Service] IUserManager userManager) + => userManager.AddToRoleAsync(roleName, userId, cancellationToken); + + [Authorize] + public Task RemoveFromRoleAsync(string roleName, string userId, CancellationToken cancellationToken, + [Service] IUserManager userManager) + => userManager.RemoveFromRoleAsync(roleName, userId, cancellationToken); } \ No newline at end of file diff --git a/ShoppingAssistantApi.Application/IServices/Identity/ITokensService.cs b/ShoppingAssistantApi.Application/IServices/Identity/ITokensService.cs index 2dda569..a6f14d7 100644 --- a/ShoppingAssistantApi.Application/IServices/Identity/ITokensService.cs +++ b/ShoppingAssistantApi.Application/IServices/Identity/ITokensService.cs @@ -1,5 +1,4 @@ -using ShoppingAssistantApi.Application.Models.Identity; -using System.Security.Claims; +using System.Security.Claims; namespace ShoppingAssistantApi.Application.IServices.Identity; @@ -9,5 +8,5 @@ public interface ITokensService string GenerateRefreshToken(); - Task RefreshUserAsync(TokensModel tokensModel, CancellationToken cancellationToken); + ClaimsPrincipal GetPrincipalFromExpiredToken(string token); } \ No newline at end of file diff --git a/ShoppingAssistantApi.Application/IServices/Identity/IUsersManager.cs b/ShoppingAssistantApi.Application/IServices/Identity/IUsersManager.cs index 72c84ae..4a07f20 100644 --- a/ShoppingAssistantApi.Application/IServices/Identity/IUsersManager.cs +++ b/ShoppingAssistantApi.Application/IServices/Identity/IUsersManager.cs @@ -10,11 +10,13 @@ public interface IUserManager Task LoginAsync(AccessUserModel login, CancellationToken cancellationToken); - Task AddToRoleAsync(string roleName, string id, CancellationToken cancellationToken); + Task AddToRoleAsync(string roleName, string userId, CancellationToken cancellationToken); - Task RemoveFromRoleAsync(string roleName, string id, CancellationToken cancellationToken); + Task RemoveFromRoleAsync(string roleName, string userId, CancellationToken cancellationToken); Task UpdateAsync(UserDto userDto, CancellationToken cancellationToken); - Task UpdateUserByAdminAsync(string id, UserDto userDto, CancellationToken cancellationToken); + Task UpdateUserByAdminAsync(string id, UserDto userDto, CancellationToken cancellationToken); + + Task RefreshAccessTokenAsync(TokensModel tokensModel, CancellationToken cancellationToken); } \ No newline at end of file diff --git a/ShoppingAssistantApi.Domain/Entities/User.cs b/ShoppingAssistantApi.Domain/Entities/User.cs index 7f5a0b1..27e928b 100644 --- a/ShoppingAssistantApi.Domain/Entities/User.cs +++ b/ShoppingAssistantApi.Domain/Entities/User.cs @@ -13,8 +13,4 @@ public class User : EntityBase public string? Email { get; set; } public string? PasswordHash { get; set; } - - public string RefreshToken { get; set; } - - public DateTime RefreshTokenExpiryDate { get; set; } } diff --git a/ShoppingAssistantApi.Infrastructure/Services/Identity/TokensService.cs b/ShoppingAssistantApi.Infrastructure/Services/Identity/TokensService.cs index 2ea9728..b50f302 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/Identity/TokensService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/Identity/TokensService.cs @@ -5,10 +5,7 @@ using System.Text; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; -using MongoDB.Bson; -using ShoppingAssistantApi.Application.IRepositories; using ShoppingAssistantApi.Application.IServices.Identity; -using ShoppingAssistantApi.Application.Models.Identity; namespace ShoppingAssistantApi.Infrastructure.Services.Identity; @@ -16,50 +13,16 @@ public class TokensService : ITokensService { private readonly IConfiguration _configuration; - private readonly IUsersRepository _usersRepository; - private readonly ILogger _logger; - public TokensService(IConfiguration configuration, IUsersRepository usersRepository, - ILogger logger) + public TokensService( + IConfiguration configuration, + ILogger logger) { this._configuration = configuration; - this._usersRepository = usersRepository; this._logger = logger; } - public async Task RefreshUserAsync(TokensModel tokensModel, CancellationToken cancellationToken) - { - var principal = this.GetPrincipalFromExpiredToken(tokensModel.AccessToken); - - var userId = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value; - if (!ObjectId.TryParse(userId, out var objectId)) - { - throw new InvalidDataException("Provided id is invalid."); - } - - var user = await this._usersRepository.GetUserAsync(objectId, cancellationToken); - if (user == null || user?.RefreshToken != tokensModel.RefreshToken - || user?.RefreshTokenExpiryDate <= DateTime.UtcNow) - { - throw new SecurityTokenExpiredException(); - } - - var newAccessToken = this.GenerateAccessToken(principal.Claims); - var newRefreshToken = this.GenerateRefreshToken(); - user.RefreshToken = newRefreshToken; - user.RefreshTokenExpiryDate = DateTime.UtcNow.AddDays(30); - await this._usersRepository.UpdateUserAsync(user, cancellationToken); - - this._logger.LogInformation($"Refreshed user tokens."); - - return new TokensModel - { - AccessToken = newAccessToken, - RefreshToken = newRefreshToken - }; - } - public string GenerateAccessToken(IEnumerable claims) { var tokenOptions = GetTokenOptions(claims); @@ -73,18 +36,16 @@ public class TokensService : ITokensService public string GenerateRefreshToken() { var randomNumber = new byte[32]; - using (var rng = RandomNumberGenerator.Create()) - { - rng.GetBytes(randomNumber); - var refreshToken = Convert.ToBase64String(randomNumber); + using var rng = RandomNumberGenerator.Create(); + rng.GetBytes(randomNumber); + var refreshToken = Convert.ToBase64String(randomNumber); - this._logger.LogInformation($"Generated new refresh token."); + this._logger.LogInformation($"Generated new refresh token."); - return refreshToken; - } + return refreshToken; } - private ClaimsPrincipal GetPrincipalFromExpiredToken(string token) + public ClaimsPrincipal GetPrincipalFromExpiredToken(string token) { var tokenValidationParameters = new TokenValidationParameters { @@ -96,11 +57,10 @@ public class TokensService : ITokensService ValidateLifetime = false }; var tokenHandler = new JwtSecurityTokenHandler(); - SecurityToken securityToken; - var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken); + var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken); var jwtSecurityToken = securityToken as JwtSecurityToken; - if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, - StringComparison.InvariantCultureIgnoreCase)) + if (jwtSecurityToken == null + || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase)) throw new SecurityTokenException("Invalid token"); this._logger.LogInformation($"Returned data from expired access token."); @@ -117,7 +77,7 @@ public class TokensService : ITokensService var tokenOptions = new JwtSecurityToken( issuer: _configuration.GetValue("JsonWebTokenKeys:ValidIssuer"), audience: _configuration.GetValue("JsonWebTokenKeys:ValidAudience"), - expires: DateTime.UtcNow.AddMinutes(5), + expires: DateTime.UtcNow.AddMinutes(15), claims: claims, signingCredentials: signinCredentials ); diff --git a/ShoppingAssistantApi.Infrastructure/Services/Identity/UserManager.cs b/ShoppingAssistantApi.Infrastructure/Services/Identity/UserManager.cs index 571ad9c..7720477 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/Identity/UserManager.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/Identity/UserManager.cs @@ -1,7 +1,6 @@ using AutoMapper; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; -using Microsoft.Win32; using MongoDB.Bson; using ShoppingAssistantApi.Application.Exceptions; using ShoppingAssistantApi.Application.GlobalInstances; @@ -14,23 +13,32 @@ using ShoppingAssistantApi.Domain.Entities; using System.Security.Claims; using System.Text.RegularExpressions; - namespace ShoppingAssistantApi.Infrastructure.Services.Identity; -public class UserManager : IUserManager + +public class UserManager : ServiceBase, IUserManager { private readonly IUsersRepository _usersRepository; - private readonly ILogger _logger; - private readonly IPasswordHasher _passwordHasher; private readonly ITokensService _tokensService; + private readonly IRolesRepository _rolesRepository; + + private readonly IRefreshTokensRepository _refreshTokensRepository; + private readonly IMapper _mapper; - private readonly IRolesRepository _rolesRepository; + private readonly ILogger _logger; - public UserManager(IUsersRepository usersRepository, ILogger logger, IPasswordHasher passwordHasher, ITokensService tokensService, IMapper mapper, IRolesRepository rolesRepository) + public UserManager( + IUsersRepository usersRepository, + IPasswordHasher passwordHasher, + ITokensService tokensService, + IRolesRepository rolesRepository, + IRefreshTokensRepository refreshTokensRepository, + IMapper mapper, + ILogger logger) { this._usersRepository = usersRepository; this._logger = logger; @@ -38,15 +46,16 @@ public class UserManager : IUserManager this._tokensService = tokensService; this._mapper = mapper; this._rolesRepository = rolesRepository; - + this._refreshTokensRepository = refreshTokensRepository; } public async Task LoginAsync(AccessUserModel login, CancellationToken cancellationToken) { - var user = login.Email != null - ? await this._usersRepository.GetUserAsync(x => x.Email == login.Email, cancellationToken) - : await this._usersRepository.GetUserAsync(x => x.Phone == login.Phone, cancellationToken); + _logger.LogInformation($"Logging in user with email: {login.Email} and phone: {login.Phone}."); + var user = string.IsNullOrEmpty(login.Phone) + ? await this._usersRepository.GetUserAsync(u => u.Email == login.Email, cancellationToken) + : await this._usersRepository.GetUserAsync(u => u.Phone == login.Phone, cancellationToken); if (user == null) { throw new EntityNotFoundException(); @@ -57,197 +66,216 @@ public class UserManager : IUserManager throw new InvalidDataException("Invalid password!"); } - user.RefreshToken = this.GetRefreshToken(); - user.RefreshTokenExpiryDate = DateTime.UtcNow.AddDays(30); - await this._usersRepository.UpdateUserAsync(user, cancellationToken); - var tokens = this.GetUserTokens(user); + var refreshToken = await AddRefreshToken(user.Id, cancellationToken); - this._logger.LogInformation($"Logged in user with email: {login.Email}."); + var tokens = this.GetUserTokens(user, refreshToken); + + this._logger.LogInformation($"Logged in user with email: {login.Email} and phone: {login.Phone}."); return tokens; } public async Task AccessGuestAsync(AccessGuestModel guest, CancellationToken cancellationToken) { + _logger.LogInformation($"Logging in / Registering guest with guest id: {guest.GuestId}."); + var user = await this._usersRepository.GetUserAsync(x => x.GuestId == guest.GuestId, cancellationToken); - if (user != null) + if (user == null) { - user.RefreshToken = this.GetRefreshToken(); - await this._usersRepository.UpdateUserAsync(user, cancellationToken); - var userTokens = this.GetUserTokens(user); + var role = await this._rolesRepository.GetRoleAsync(r => r.Name == "Guest", cancellationToken); + user = new User + { + GuestId = guest.GuestId, + Roles = new List { role }, + CreatedDateUtc = DateTime.UtcNow, + CreatedById = ObjectId.Empty // Default value for all new users + }; - this._logger.LogInformation($"Logged in guest with guest id: {guest.GuestId}."); + await this._usersRepository.AddAsync(user, cancellationToken); - return userTokens; + this._logger.LogInformation($"Created guest with guest id: {guest.GuestId}."); } - var role = await this._rolesRepository.GetRoleAsync(r => r.Name == "Guest", cancellationToken); + var refreshToken = await AddRefreshToken(user.Id, cancellationToken); + var tokens = this.GetUserTokens(user, refreshToken); - var newUser = new User - { - GuestId = guest.GuestId, - Roles = new List { role }, - RefreshToken = this.GetRefreshToken(), - RefreshTokenExpiryDate = DateTime.Now.AddDays(30), - CreatedDateUtc = DateTime.UtcNow, - LastModifiedDateUtc = DateTime.UtcNow - }; - - await this._usersRepository.AddAsync(newUser, cancellationToken); - var tokens = this.GetUserTokens(newUser); - - this._logger.LogInformation($"Created guest with guest id: {guest.GuestId}."); + this._logger.LogInformation($"Logged in guest with guest id: {guest.GuestId}."); return tokens; } - public async Task AddToRoleAsync(string roleName, string id, CancellationToken cancellationToken) + public async Task RefreshAccessTokenAsync(TokensModel tokensModel, CancellationToken cancellationToken) { + _logger.LogInformation($"Refreshing access token."); + + var principal = _tokensService.GetPrincipalFromExpiredToken(tokensModel.AccessToken); + var userId = ParseObjectId(principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value); + + var refreshTokenModel = await this._refreshTokensRepository + .GetOneAsync(r => + r.Token == tokensModel.RefreshToken + && r.CreatedById == userId + && r.IsDeleted == false, cancellationToken); + if (refreshTokenModel == null || refreshTokenModel.ExpiryDateUTC < DateTime.UtcNow) + { + throw new SecurityTokenExpiredException(); + } + + var refreshToken = refreshTokenModel.Token; + + // Update Refresh token if it expires in less than 7 days to keep user constantly logged in if he uses the app + if (refreshTokenModel.ExpiryDateUTC.AddDays(-7) < DateTime.UtcNow) + { + await _refreshTokensRepository.DeleteAsync(refreshTokenModel, cancellationToken); + + var newRefreshToken = await AddRefreshToken(userId, cancellationToken); + refreshToken = newRefreshToken.Token; + } + + var tokens = new TokensModel + { + AccessToken = _tokensService.GenerateAccessToken(principal.Claims), + RefreshToken = refreshToken + }; + + this._logger.LogInformation($"Refreshed access token."); + + return tokens; + } + + public async Task AddToRoleAsync(string roleName, string userId, CancellationToken cancellationToken) + { + _logger.LogInformation($"Adding Role: {roleName} to User with Id: {userId}."); + var role = await this._rolesRepository.GetRoleAsync(r => r.Name == roleName, cancellationToken); if (role == null) { throw new EntityNotFoundException(); } - if (!ObjectId.TryParse(id, out var objectId)) - { - throw new InvalidDataException("Provided id is invalid."); - } - - var user = await this._usersRepository.GetUserAsync(objectId, cancellationToken); + var userObjectId = ParseObjectId(userId); + var user = await this._usersRepository.GetUserAsync(userObjectId, cancellationToken); if (user == null) { throw new EntityNotFoundException(); } user.Roles.Add(role); - await this._usersRepository.UpdateUserAsync(user, cancellationToken); - var tokens = this.GetUserTokens(user); + var updatedUser = await this._usersRepository.UpdateUserAsync(user, cancellationToken); + var userDto = this._mapper.Map(updatedUser); - this._logger.LogInformation($"Added role {roleName} to user with id: {id}."); + this._logger.LogInformation($"Added Role: {roleName} to User with Id: {userId}."); - return tokens; + return userDto; } - public async Task RemoveFromRoleAsync(string roleName, string id, CancellationToken cancellationToken) + public async Task RemoveFromRoleAsync(string roleName, string userId, CancellationToken cancellationToken) { + _logger.LogInformation($"Removing Role: {roleName} from User with Id: {userId}."); + var role = await this._rolesRepository.GetRoleAsync(r => r.Name == roleName, cancellationToken); if (role == null) { throw new EntityNotFoundException(); } - if (!ObjectId.TryParse(id, out var objectId)) - { - throw new InvalidDataException("Provided id is invalid."); - } - - var user = await this._usersRepository.GetUserAsync(objectId, cancellationToken); + var userObjectId = ParseObjectId(userId); + var user = await this._usersRepository.GetUserAsync(userObjectId, cancellationToken); if (user == null) { throw new EntityNotFoundException(); } var deletedRole = user.Roles.Find(x => x.Name == role.Name); - user.Roles.Remove(deletedRole); - await this._usersRepository.UpdateUserAsync(user, cancellationToken); - var tokens = this.GetUserTokens(user); - this._logger.LogInformation($"Added role {roleName} to user with id: {id}."); + var updatedUser = await this._usersRepository.UpdateUserAsync(user, cancellationToken); + var userDto = this._mapper.Map(updatedUser); - return tokens; + this._logger.LogInformation($"Removed Role: {roleName} from User with Id: {userId}."); + + return userDto; } public async Task UpdateAsync(UserDto userDto, CancellationToken cancellationToken) { - if (userDto.Email != null) ValidateEmail(userDto.Email); - if (userDto.Phone != null) ValidateNumber(userDto.Phone); - - if (userDto.Roles.Any(x => x.Name == "Guest") && !userDto.Roles.Any(x => x.Name == "User")) - { - if (userDto.Password != null && (userDto.Email != null || userDto.Phone != null)) - { - var roleEntity = await this._rolesRepository.GetRoleAsync(x => x.Name == "User", cancellationToken); - var roleDto = this._mapper.Map(roleEntity); - userDto.Roles.Add(roleDto); - } - } + _logger.LogInformation($"Updating user with id: {GlobalUser.Id}."); var user = await this._usersRepository.GetUserAsync(x => x.Id == GlobalUser.Id, cancellationToken); - if (user == null) { throw new EntityNotFoundException(); } - if (userDto.Roles.Any(x => x.Name == "User") && userDto.Email != null) - { - if (await this._usersRepository.GetUserAsync(x => x.Email == userDto.Email, cancellationToken) != null) - { - throw new EntityAlreadyExistsException("email", userDto.Email); - } - } - if (userDto.Roles.Any(x => x.Name == "User") && userDto.Phone != null) - { - if (await this._usersRepository.GetUserAsync(x => x.Phone == userDto.Phone, cancellationToken) != null) - { - throw new EntityAlreadyExistsException("phone", userDto.Phone); - } - } + await ValidateUserAsync(userDto, user, cancellationToken); this._mapper.Map(userDto, user); - if (!userDto.Password.IsNullOrEmpty()) + if (!string.IsNullOrEmpty(userDto.Password)) { user.PasswordHash = this._passwordHasher.Hash(userDto.Password); } - user.RefreshToken = this.GetRefreshToken(); - await this._usersRepository.UpdateUserAsync(user, cancellationToken); + await CheckAndUpgradeToUserAsync(user, cancellationToken); - var tokens = this.GetUserTokens(user); + var updatedUser = await this._usersRepository.UpdateUserAsync(user, cancellationToken); - this._logger.LogInformation($"Update user with id: {GlobalUser.Id.ToString()}."); + var refreshToken = await AddRefreshToken(user.Id, cancellationToken); + var tokens = this.GetUserTokens(user, refreshToken); - return new UpdateUserModel() { Tokens = tokens, User = this._mapper.Map(user) }; + var updatedUserDto = this._mapper.Map(updatedUser); + + this._logger.LogInformation($"Update user with id: {GlobalUser.Id}."); + + return new UpdateUserModel() + { + Tokens = tokens, + User = updatedUserDto + }; } - public async Task UpdateUserByAdminAsync(string id, UserDto userDto, CancellationToken cancellationToken) + public async Task UpdateUserByAdminAsync(string id, UserDto userDto, CancellationToken cancellationToken) { - if (!ObjectId.TryParse(id, out var objectId)) - { - throw new InvalidDataException("Provided id is invalid."); - } - - var user = await this._usersRepository.GetUserAsync(objectId, cancellationToken); + _logger.LogInformation($"Admin updating User with Id: {id}."); + var userObjectId = ParseObjectId(id); + var user = await this._usersRepository.GetUserAsync(userObjectId, cancellationToken); if (user == null) { throw new EntityNotFoundException(); } + await ValidateUserAsync(userDto, user, cancellationToken); + this._mapper.Map(userDto, user); + var updatedUser = await this._usersRepository.UpdateUserAsync(user, cancellationToken); - user.RefreshToken = this.GetRefreshToken(); - await this._usersRepository.UpdateUserAsync(user, cancellationToken); + var updatedUserDto = this._mapper.Map(updatedUser); + + this._logger.LogInformation($"Admin updated User with Id: {id}."); - var tokens = this.GetUserTokens(user); - - this._logger.LogInformation($"Update user with id: {id}."); - - return new UpdateUserModel() { Tokens = tokens, User = this._mapper.Map(user) }; + return updatedUserDto; } - private string GetRefreshToken() + private async Task AddRefreshToken(ObjectId userId, CancellationToken cancellationToken) { - var refreshToken = this._tokensService.GenerateRefreshToken(); + _logger.LogInformation($"Adding new refresh token for user with Id : {userId}."); - this._logger.LogInformation($"Returned new refresh token."); + var refreshToken = new RefreshToken + { + Token = _tokensService.GenerateRefreshToken(), + ExpiryDateUTC = DateTime.UtcNow.AddDays(30), + CreatedById = userId, + CreatedDateUtc = DateTime.UtcNow + }; + + await this._refreshTokensRepository.AddAsync(refreshToken, cancellationToken); + + this._logger.LogInformation($"Added new refresh token."); return refreshToken; } - private TokensModel GetUserTokens(User user) + private TokensModel GetUserTokens(User user, RefreshToken refreshToken) { var claims = this.GetClaims(user); var accessToken = this._tokensService.GenerateAccessToken(claims); @@ -257,7 +285,7 @@ public class UserManager : IUserManager return new TokensModel { AccessToken = accessToken, - RefreshToken = user.RefreshToken, + RefreshToken = refreshToken.Token, }; } @@ -265,21 +293,56 @@ public class UserManager : IUserManager { var claims = new List() { - new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), - new Claim(ClaimTypes.Email, user.Email ?? string.Empty), - new Claim(ClaimTypes.MobilePhone, user.Phone ?? string.Empty), + new (ClaimTypes.NameIdentifier, user.Id.ToString()), + new (ClaimTypes.Email, user.Email ?? string.Empty), + new (ClaimTypes.MobilePhone, user.Phone ?? string.Empty), }; foreach (var role in user.Roles) { - claims.Add(new Claim(ClaimTypes.Role, role.Name)); + claims.Add(new (ClaimTypes.Role, role.Name)); } - this._logger.LogInformation($"Returned claims for user with id: {user.Id.ToString()}."); + this._logger.LogInformation($"Returned claims for User with Id: {user.Id}."); return claims; } + private async Task CheckAndUpgradeToUserAsync(User user, CancellationToken cancellationToken) + { + if (user.Roles.Any(x => x.Name == "Guest") && !user.Roles.Any(x => x.Name == "User")) + { + if (!string.IsNullOrEmpty(user.PasswordHash) && (!string.IsNullOrEmpty(user.Email) || !string.IsNullOrEmpty(user.Phone))) + { + var role = await this._rolesRepository.GetRoleAsync(x => x.Name == "User", cancellationToken); + user.Roles.Add(role); + } + } + } + + private async Task ValidateUserAsync(UserDto userDto, User user, CancellationToken cancellationToken) + { + if (!string.IsNullOrEmpty(userDto.Email)) + { + ValidateEmail(userDto.Email); + if (userDto.Email != user.Email + && await this._usersRepository.ExistsAsync(x => x.Email == userDto.Email, cancellationToken)) + { + throw new EntityAlreadyExistsException("email", userDto.Email); + } + } + + if (!string.IsNullOrEmpty(userDto.Phone)) + { + ValidatePhone(userDto.Phone); + if (userDto.Phone != user.Phone + && await this._usersRepository.ExistsAsync(x => x.Phone == userDto.Phone, cancellationToken)) + { + throw new EntityAlreadyExistsException("phone", userDto.Phone); + } + } + } + private void ValidateEmail(string email) { string regex = @"^[^@\s]+@[^@\s]+\.[^@\s]+$"; @@ -290,7 +353,7 @@ public class UserManager : IUserManager } } - private void ValidateNumber(string phone) + private void ValidatePhone(string phone) { string regex = @"^\+[0-9]{1,15}$"; diff --git a/ShoppingAssistantApi.Infrastructure/Services/RolesService.cs b/ShoppingAssistantApi.Infrastructure/Services/RolesService.cs index 7881ec4..d30bc3b 100644 --- a/ShoppingAssistantApi.Infrastructure/Services/RolesService.cs +++ b/ShoppingAssistantApi.Infrastructure/Services/RolesService.cs @@ -33,14 +33,16 @@ public class RolesService : IRolesService entity.CreatedDateUtc = DateTime.UtcNow; entity.LastModifiedDateUtc = DateTime.UtcNow; await this._repository.AddAsync(entity, cancellationToken); + return this._mapper.Map(entity); } public async Task> GetRolesPageAsync(int pageNumber, int pageSize, CancellationToken cancellationToken) { var entities = await this._repository.GetPageAsync(pageNumber, pageSize, cancellationToken); - var dtos = this._mapper.Map>(entities); var count = await this._repository.GetTotalCountAsync(); + var dtos = this._mapper.Map>(entities); + return new PagedList(dtos, pageNumber, pageSize, count); } } diff --git a/ShoppingAssistantApi.Infrastructure/Services/ServiceBase.cs b/ShoppingAssistantApi.Infrastructure/Services/ServiceBase.cs new file mode 100644 index 0000000..3e1381f --- /dev/null +++ b/ShoppingAssistantApi.Infrastructure/Services/ServiceBase.cs @@ -0,0 +1,15 @@ +namespace ShoppingAssistantApi.Infrastructure.Services; +using MongoDB.Bson; + +public abstract class ServiceBase +{ + public ObjectId ParseObjectId(string? id) + { + if (ObjectId.TryParse(id, out ObjectId objectId)) + { + return objectId; + } + + throw new InvalidDataException("Provided id cannot be parsed to a MongoDb ObjectId."); + } +} diff --git a/ShoppingAssistantApi.Persistance/Repositories/UsersRepository.cs b/ShoppingAssistantApi.Persistance/Repositories/UsersRepository.cs index 198bc08..39b4ab5 100644 --- a/ShoppingAssistantApi.Persistance/Repositories/UsersRepository.cs +++ b/ShoppingAssistantApi.Persistance/Repositories/UsersRepository.cs @@ -27,8 +27,6 @@ public class UsersRepository : BaseRepository, IUsersRepository var updateDefinition = Builders.Update .Set(u => u.Email, user.Email) .Set(u => u.Phone, user.Phone) - .Set(u => u.RefreshToken, user.RefreshToken) - .Set(u => u.RefreshTokenExpiryDate, user.RefreshTokenExpiryDate) .Set(u => u.GuestId, user.GuestId) .Set(u => u.Roles, user.Roles) .Set(u => u.PasswordHash, user.PasswordHash) @@ -42,7 +40,5 @@ public class UsersRepository : BaseRepository, IUsersRepository return await this._collection.FindOneAndUpdateAsync( Builders.Filter.Eq(u => u.Id, user.Id), updateDefinition, options, cancellationToken); - } - } From 6b8154e4743c2749da36ae8dfc77161ab6309dba Mon Sep 17 00:00:00 2001 From: shchoholiev Date: Mon, 16 Oct 2023 03:23:07 +0000 Subject: [PATCH 19/22] SA-14 fixed and refactored integration tests --- .../Database/MongoDbContext.cs | 2 + .../ShoppingAssistantApi.Tests.csproj | 1 - .../TestExtentions/AccessExtention.cs | 65 ---- .../TestExtentions/DbInitializer.cs | 140 +++++++ .../TestExtentions/TestingFactory.cs | 54 ++- .../TestExtentions/UserExtention.cs | 50 --- .../Tests/AccessTests.cs | 176 +++++---- .../Tests/RolesTests.cs | 209 ++++------- ShoppingAssistantApi.Tests/Tests/TestsBase.cs | 66 ++++ .../Tests/UsersTests.cs | 349 ++++++++++-------- .../Tests/WishlistsTests.cs | 267 ++++++-------- ShoppingAssistantApi.Tests/Usings.cs | 4 + 12 files changed, 716 insertions(+), 667 deletions(-) delete mode 100644 ShoppingAssistantApi.Tests/TestExtentions/AccessExtention.cs create mode 100644 ShoppingAssistantApi.Tests/TestExtentions/DbInitializer.cs delete mode 100644 ShoppingAssistantApi.Tests/TestExtentions/UserExtention.cs create mode 100644 ShoppingAssistantApi.Tests/Tests/TestsBase.cs create mode 100644 ShoppingAssistantApi.Tests/Usings.cs diff --git a/ShoppingAssistantApi.Persistance/Database/MongoDbContext.cs b/ShoppingAssistantApi.Persistance/Database/MongoDbContext.cs index ea3f3a3..ac3876e 100644 --- a/ShoppingAssistantApi.Persistance/Database/MongoDbContext.cs +++ b/ShoppingAssistantApi.Persistance/Database/MongoDbContext.cs @@ -16,4 +16,6 @@ public class MongoDbContext } public IMongoDatabase Db => this._db; + + public MongoClient Client => this._client; } \ No newline at end of file diff --git a/ShoppingAssistantApi.Tests/ShoppingAssistantApi.Tests.csproj b/ShoppingAssistantApi.Tests/ShoppingAssistantApi.Tests.csproj index a7e4074..c36f3b4 100644 --- a/ShoppingAssistantApi.Tests/ShoppingAssistantApi.Tests.csproj +++ b/ShoppingAssistantApi.Tests/ShoppingAssistantApi.Tests.csproj @@ -10,7 +10,6 @@ - diff --git a/ShoppingAssistantApi.Tests/TestExtentions/AccessExtention.cs b/ShoppingAssistantApi.Tests/TestExtentions/AccessExtention.cs deleted file mode 100644 index 16e0a03..0000000 --- a/ShoppingAssistantApi.Tests/TestExtentions/AccessExtention.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Text; -using Newtonsoft.Json; -using ShoppingAssistantApi.Application.Models.Identity; - -namespace ShoppingAssistantApi.Tests.TestExtentions; - -public static class AccessExtention -{ - public static async Task Login(string email, string password, HttpClient httpClient) - { - var mutation = new - { - query = "mutation Login($login: AccessUserModelInput!) { login(login: $login) { accessToken refreshToken }}", - variables = new - { - login = new - { - email = email, - password = password - } - } - }; - - var jsonPayload = JsonConvert.SerializeObject(mutation); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); - - using var response = await httpClient.PostAsync("graphql", content); - var responseString = await response.Content.ReadAsStringAsync(); - var document = JsonConvert.DeserializeObject(responseString); - - return new TokensModel - { - AccessToken = (string)document.data.login.accessToken, - RefreshToken = (string)document.data.login.refreshToken - }; - } - - public static async Task CreateGuest(string guestId, HttpClient httpClient) - { - var mutation = new - { - query = "mutation AccessGuest($guest: AccessGuestModelInput!) { accessGuest(guest: $guest) { accessToken, refreshToken } }", - variables = new - { - guest = new - { - guestId - } - } - }; - - var jsonPayload = JsonConvert.SerializeObject(mutation); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); - - using var response = await httpClient.PostAsync("graphql", content); - var responseString = await response.Content.ReadAsStringAsync(); - var document = JsonConvert.DeserializeObject(responseString); - - return new TokensModel - { - AccessToken = (string)document.data.accessGuest.accessToken, - RefreshToken = (string)document.data.accessGuest.refreshToken - }; - } -} \ No newline at end of file diff --git a/ShoppingAssistantApi.Tests/TestExtentions/DbInitializer.cs b/ShoppingAssistantApi.Tests/TestExtentions/DbInitializer.cs new file mode 100644 index 0000000..ae2ed20 --- /dev/null +++ b/ShoppingAssistantApi.Tests/TestExtentions/DbInitializer.cs @@ -0,0 +1,140 @@ +using Microsoft.Extensions.Logging; +using MongoDB.Bson; +using ShoppingAssistantApi.Domain.Entities; +using ShoppingAssistantApi.Domain.Enums; +using ShoppingAssistantApi.Infrastructure.Services.Identity; +using ShoppingAssistantApi.Persistance.Database; + +namespace ShoppingAssistantApi.Tests.TestExtentions; + +public class DbInitializer +{ + private readonly MongoDbContext _dbContext; + + public DbInitializer(MongoDbContext dbContext) + { + _dbContext = dbContext; + } + + public void InitializeDb() + { + _dbContext.Client.DropDatabase(_dbContext.Db.DatabaseNamespace.DatabaseName); + + InitializeUsersAsync().Wait(); + InitializeWishlistsAsync().Wait(); + } + + public async Task InitializeUsersAsync() + { + #region Roles + + var rolesCollection = _dbContext.Db.GetCollection("Roles"); + + var questRole = new Role + { + Name = "Guest" + }; + await rolesCollection.InsertOneAsync(questRole); + + var userRole = new Role + { + Name = "User" + }; + await rolesCollection.InsertOneAsync(userRole); + + var adminRole = new Role + { + Name = "Admin" + }; + await rolesCollection.InsertOneAsync(adminRole); + + #endregion + + #region Users + + var passwordHasher = new PasswordHasher(new Logger(new LoggerFactory())); + var usersCollection = _dbContext.Db.GetCollection("Users"); + + var testUser = new User + { + Id = ObjectId.Parse("652c3b89ae02a3135d6409fc"), + Email = "test@gmail.com", + Phone = "+380953326869", + Roles = new List { questRole, userRole }, + PasswordHash = passwordHasher.Hash("Yuiop12345"), + CreatedById = ObjectId.Empty, + CreatedDateUtc = DateTime.UtcNow + }; + await usersCollection.InsertOneAsync(testUser); + + var adminUser = new User + { + Id = ObjectId.Parse("652c3b89ae02a3135d6408fc"), + Email = "admin@gmail.com", + Phone = "+12345678901", + Roles = new List { questRole, userRole, adminRole }, + PasswordHash = passwordHasher.Hash("Yuiop12345"), + CreatedById = ObjectId.Empty, + CreatedDateUtc = DateTime.UtcNow + }; + await usersCollection.InsertOneAsync(adminUser); + + var wishlistsUser = new User + { + Id = ObjectId.Parse("652c3b89ae02a3135d6418fc"), + Email = "wishlists@gmail.com", + Phone = "+12234567890", + Roles = new List { questRole }, + PasswordHash = passwordHasher.Hash("Yuiop12345"), + CreatedById = ObjectId.Empty, + CreatedDateUtc = DateTime.UtcNow + }; + await usersCollection.InsertOneAsync(wishlistsUser); + + #endregion + } + + public async Task InitializeWishlistsAsync() + { + var wishlistsCollection = _dbContext.Db.GetCollection("Wishlists"); + + var gamingPcWishlist = new Wishlist + { + Id = ObjectId.Parse("ab79cde6f69abcd3efab65cd"), + Name = "Gaming PC", + Type = WishlistTypes.Product.ToString(), + CreatedById = ObjectId.Parse("652c3b89ae02a3135d6418fc"), + Messages = new Message[] + { + new Message + { + Text = "Prompt", + Role = MessageRoles.User.ToString(), + }, + new Message + { + Text = "Answer", + Role = MessageRoles.Application.ToString(), + }, + } + }; + await wishlistsCollection.InsertOneAsync(gamingPcWishlist); + + var genericWishlist = new Wishlist + { + Id = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), + Name = "Generic Wishlist Name", + Type = WishlistTypes.Product.ToString(), + CreatedById = ObjectId.Parse("652c3b89ae02a3135d6409fc"), + Messages = new Message[] + { + new Message + { + Text = "Prompt", + Role = MessageRoles.User.ToString(), + } + } + }; + await wishlistsCollection.InsertOneAsync(genericWishlist); + } +} diff --git a/ShoppingAssistantApi.Tests/TestExtentions/TestingFactory.cs b/ShoppingAssistantApi.Tests/TestExtentions/TestingFactory.cs index a8407df..a63a957 100644 --- a/ShoppingAssistantApi.Tests/TestExtentions/TestingFactory.cs +++ b/ShoppingAssistantApi.Tests/TestExtentions/TestingFactory.cs @@ -1,48 +1,64 @@ -using Microsoft.AspNetCore.Hosting; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Mongo2Go; -using ShoppingAssistantApi.Persistance.PersistanceExtentions; +using ShoppingAssistantApi.Persistance.Database; namespace ShoppingAssistantApi.Tests.TestExtentions; public class TestingFactory : WebApplicationFactory where TEntryPoint : Program { - private readonly MongoDbRunner _runner = MongoDbRunner.Start(); + private MongoDbRunner? _runner; private bool _isDataInitialaized = false; protected override void ConfigureWebHost(IWebHostBuilder builder) { + // Mongo2Go is not supported on ARM64 so we need to use a real MongoDB instance + Console.WriteLine($"[ARCH]: {RuntimeInformation.ProcessArchitecture}"); + + var connectionString = string.Empty; + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + connectionString = "mongodb+srv://api:pUe2dLT8llwEgwzq@cluster0.3q6mxmw.mongodb.net/?retryWrites=true&w=majority"; + } + else + { + _runner = MongoDbRunner.Start(); + connectionString = _runner.ConnectionString; + } + builder.ConfigureAppConfiguration((context, config) => { - var dbConfig = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary() - { - { "ConnectionStrings:MongoDb", _runner.ConnectionString } - }) - .Build(); + var dbConfig = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary() + { + { "ConnectionStrings:MongoDb", connectionString } + }) + .Build(); config.AddConfiguration(dbConfig); }); } - public async Task InitialaizeData() + public void InitialaizeDatabase() { - if (!_isDataInitialaized) - { - _isDataInitialaized = true; - using var scope = Services.CreateScope(); - var initialaizer = new DbInitialaizer(scope.ServiceProvider); - using var cancellationTokenSource = new CancellationTokenSource(); - var cancellationToken = cancellationTokenSource.Token; - await initialaizer.InitialaizeDb(cancellationToken); - } + if (_isDataInitialaized) return; + + using var scope = Services.CreateScope(); + var mongodbContext = scope.ServiceProvider.GetRequiredService(); + + var initialaizer = new DbInitializer(mongodbContext); + initialaizer.InitializeDb(); + + _isDataInitialaized = true; } protected override void Dispose(bool disposing) { - _runner.Dispose(); + _runner?.Dispose(); base.Dispose(disposing); } } \ No newline at end of file diff --git a/ShoppingAssistantApi.Tests/TestExtentions/UserExtention.cs b/ShoppingAssistantApi.Tests/TestExtentions/UserExtention.cs deleted file mode 100644 index 269ff8a..0000000 --- a/ShoppingAssistantApi.Tests/TestExtentions/UserExtention.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Net.Http.Headers; -using System.Text; -using Newtonsoft.Json; -using ShoppingAssistantApi.Application.Models.Dtos; - -namespace ShoppingAssistantApi.Tests.TestExtentions; - -public static class UserExtention -{ - public static async Task GetCurrentUser(HttpClient httpClient) - { - var query = new - { - query = "query CurrentUser { currentUser { id, guestId, phone, email, refreshToken, refreshTokenExpiryDate, roles { id, name }}}", - variables = new { } - }; - - var jsonPayload = JsonConvert.SerializeObject(query); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); - - using var response = await httpClient.PostAsync("graphql", content); - var responseString = await response.Content.ReadAsStringAsync(); - var document = JsonConvert.DeserializeObject(responseString); - return JsonConvert.DeserializeObject(document.data.currentUser.ToString()); - } - - public static async Task> GetUsers(int amount, HttpClient httpClient) - { - var accessToken = await AccessExtention.Login("mykhailo.bilodid@nure.ua", "Yuiop12345", httpClient); - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken); - - var query = new - { - query = "query UsersPage($pageNumber: Int!, $pageSize: Int!) { usersPage(pageNumber: $pageNumber, pageSize: $pageSize) { items { id, email, phone }}}", - variables = new - { - pageNumber = 1, - pageSize = amount - } - }; - - var jsonPayload = JsonConvert.SerializeObject(query); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); - - using var response = await httpClient.PostAsync("graphql", content); - var responseString = await response.Content.ReadAsStringAsync(); - var document = JsonConvert.DeserializeObject(responseString); - return JsonConvert.DeserializeObject>(document.data.usersPage.items.ToString()); - } -} \ No newline at end of file diff --git a/ShoppingAssistantApi.Tests/Tests/AccessTests.cs b/ShoppingAssistantApi.Tests/Tests/AccessTests.cs index 65f29f4..74f6cc1 100644 --- a/ShoppingAssistantApi.Tests/Tests/AccessTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/AccessTests.cs @@ -1,26 +1,21 @@ -using System.Net; -using System.Text; -using Xunit; +using Newtonsoft.Json.Linq; +using ShoppingAssistantApi.Application.Models.Identity; using ShoppingAssistantApi.Tests.TestExtentions; -using Newtonsoft.Json; +using Xunit; namespace ShoppingAssistantApi.Tests.Tests; -[Collection("Tests")] - -public class AccessTests : IClassFixture> +// TODO: make errors test more descrptive +public class AccessTests : TestsBase { - private readonly HttpClient _httpClient; - public AccessTests(TestingFactory factory) - { - _httpClient = factory.CreateClient(); - factory.InitialaizeData().GetAwaiter().GetResult(); - } + : base(factory) + { } [Fact] public async Task AccessGuestAsync_ValidGuid_ReturnsTokensModel() { + // Arrange var mutation = new { query = "mutation AccessGuest($guest: AccessGuestModelInput!) { accessGuest(guest: $guest) { accessToken, refreshToken } }", @@ -33,27 +28,20 @@ public class AccessTests : IClassFixture> } }; - var jsonPayload = JsonConvert.SerializeObject(mutation); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + // Act + var jsonObject = await SendGraphQlRequestAsync(mutation); + var tokens = (TokensModel?) jsonObject?.data?.accessGuest?.ToObject(); - 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 accessToken = (string)document.data.accessGuest.accessToken; - var refreshToken = (string)document.data.accessGuest.refreshToken; - - Assert.NotNull(accessToken); - Assert.NotNull(refreshToken); - } + // Assert + Assert.NotNull(tokens); + Assert.NotNull(tokens.AccessToken); + Assert.NotNull(tokens.RefreshToken); + } [Theory] - [InlineData("")] + [InlineData(null)] [InlineData("invalid-guid-format")] - public async Task AccessGuestAsync_InvalidGuid_ReturnsInternalServerError(string guestId) + public async Task AccessGuestAsync_InvalidGuid_ReturnsErrors(string guestId) { var mutation = new { @@ -67,19 +55,19 @@ public class AccessTests : IClassFixture> } }; - var jsonPayload = JsonConvert.SerializeObject(mutation); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; - using var response = await _httpClient.PostAsync("graphql", content); - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.NotNull(errors); + Assert.True(errors.Count > 0); } [Theory] [InlineData("invalid-email-format", null, "Yuiop12345")] + [InlineData(null, "invalid-phone", "Yuiop12345")] + [InlineData("test@gmail.com", null, "random-password")] [InlineData(null, null, "Yuiop12345")] - [InlineData(null, null, "")] - [InlineData("mihail.beloded.work@gmail.com", null, "")] - public async Task LoginAsync_InvalidCredentials_ReturnsInternalServerError(string email, string phone, string password) + public async Task LoginAsync_InvalidCredentials_ReturnsErrors(string email, string phone, string password) { var mutation = new { @@ -95,17 +83,17 @@ public class AccessTests : IClassFixture> } }; - var jsonPayload = JsonConvert.SerializeObject(mutation); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; - using var response = await _httpClient.PostAsync("graphql", content); - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.NotNull(errors); + Assert.True(errors.Count > 0); } - + [Theory] - [InlineData("mykhailo.bilodid@nure.ua", "+380953326869", "Yuiop12345")] - [InlineData(null, "+380953326888", "Yuiop12345")] - [InlineData("mykhailo.bilodid@nure.ua", null, "Yuiop12345")] + [InlineData("test@gmail.com", "+380953326869", "Yuiop12345")] + [InlineData(null, "+380953326869", "Yuiop12345")] + [InlineData("test@gmail.com", null, "Yuiop12345")] public async Task LoginAsync_ValidCredentials_ReturnsTokensModel(string email, string phone, string password) { var mutation = new @@ -121,83 +109,85 @@ public class AccessTests : IClassFixture> } } }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var tokens = (TokensModel?) jsonObject?.data?.login?.ToObject(); - 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 accessToken = (string)document.data.login.accessToken; - var refreshToken = (string)document.data.login.refreshToken; - - Assert.NotNull(accessToken); - Assert.NotNull(refreshToken); + Assert.NotNull(tokens); + Assert.NotNull(tokens.AccessToken); + Assert.NotNull(tokens.RefreshToken); } [Fact] public async Task RefreshUserTokenAsync_ValidTokensModel_ReturnsTokensModel() { - var tokensModel = await AccessExtention.CreateGuest(new Guid().ToString(), _httpClient); - var accessToken = tokensModel.AccessToken; - var refreshToken = tokensModel.RefreshToken; - + var tokensModel = await CreateGuestAsync(); var mutation = new { - query = "mutation RefreshToken($model: TokensModelInput!) { refreshUserToken(model: $model) { accessToken refreshToken }}", + query = "mutation RefreshToken($model: TokensModelInput!) { refreshAccessToken(model: $model) { accessToken refreshToken }}", variables = new { model = new { - accessToken, - refreshToken + accessToken = tokensModel.AccessToken, + refreshToken = tokensModel.RefreshToken } } }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var tokens = (TokensModel?) jsonObject?.data?.refreshAccessToken?.ToObject(); - 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 accessTokenResult = (string)document.data.refreshUserToken.accessToken; - var refreshTokenResult = (string)document.data.refreshUserToken.refreshToken; - - Assert.NotNull(accessTokenResult); - Assert.NotNull(refreshTokenResult); + Assert.NotNull(tokens); + Assert.NotNull(tokens.AccessToken); + Assert.NotNull(tokens.RefreshToken); } - [Theory] - [InlineData(null, null)] - [InlineData("invalid-access-token", "invalid-refresh-token")] - public async Task RefreshUserTokenAsync_InvalidTokensModel_ReturnsInternalServerError(string refreshToken, string accessToken) + [Fact] + public async Task RefreshAccessTokenAsync_NonExistingRefreshToken_ReturnsErrors() { var mutation = new { - query = "mutation RefreshToken($model: TokensModelInput!) { refreshUserToken(model: $model) { accessToken refreshToken }}", + query = "mutation RefreshToken($model: TokensModelInput!) { refreshAccessToken(model: $model) { accessToken refreshToken }}", variables = new { model = new { - accessToken, - refreshToken + accessToken = "random-access-token", + refreshToken = "random-refresh-token" } } }; - var jsonPayload = JsonConvert.SerializeObject(mutation); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; - using var response = await _httpClient.PostAsync("graphql", content); - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + + private async Task CreateGuestAsync() + { + var mutation = new + { + query = @" + mutation AccessGuest($guest: AccessGuestModelInput!) { + accessGuest(guest: $guest) { + accessToken, refreshToken + } + }", + variables = new + { + guest = new + { + guestId = Guid.NewGuid() + } + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var tokens = (TokensModel?) jsonObject?.data?.accessGuest?.ToObject(); + + return tokens; } } \ No newline at end of file diff --git a/ShoppingAssistantApi.Tests/Tests/RolesTests.cs b/ShoppingAssistantApi.Tests/Tests/RolesTests.cs index 29b3774..2e52ef1 100644 --- a/ShoppingAssistantApi.Tests/Tests/RolesTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/RolesTests.cs @@ -1,138 +1,42 @@ -using System.Net; -using System.Text; -using Xunit; -using ShoppingAssistantApi.Tests.TestExtentions; -using System.Net.Http.Headers; -using Newtonsoft.Json; -using GreenDonut; +using ShoppingAssistantApi.Tests.TestExtentions; +using Newtonsoft.Json.Linq; +using ShoppingAssistantApi.Application.Paging; +using ShoppingAssistantApi.Application.Models.Dtos; namespace ShoppingAssistantApi.Tests.Tests; -[Collection("Tests")] -public class RolesTests : IClassFixture> +// TODO: make errors test more descrptive +public class RolesTests : TestsBase { - private readonly HttpClient _httpClient; - public RolesTests(TestingFactory factory) + : base(factory) + { } + + [Fact] + public async Task AddRole_ValidName_ReturnsCreatedRole() { - _httpClient = factory.CreateClient(); - factory.InitialaizeData().GetAwaiter().GetResult(); + await LoginAsync("admin@gmail.com", "Yuiop12345"); + var mutation = new + { + query = "mutation AddRole ($dto: RoleCreateDtoInput!){ addRole (roleDto: $dto) { id, name }} ", + variables = new + { + dto = new + { + name = "NewRole" + } + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var role = jsonObject?.data?.addRole?.ToObject(); + + Assert.NotNull(role); + Assert.Equal("NewRole", role.Name); } [Fact] - public async Task AddToRoleAsync_ValidRoleName_ReturnsTokensModel() - { - var usersPage = await UserExtention.GetUsers(10, _httpClient); - var mutation = new - { - query = "mutation AddToRole($roleName: String!, $id: String!) { addToRole(roleName: $roleName, id: $id) { accessToken, refreshToken }}", - variables = new - { - roleName = "Admin", - id = usersPage[0].Id, - } - }; - - 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 accessToken = (string)document.data.addToRole.accessToken; - var refreshToken = (string)document.data.addToRole.refreshToken; - - Assert.NotNull(accessToken); - Assert.NotNull(refreshToken); - } - - - [Theory] - [InlineData("")] - [InlineData("InvalidRole")] - public async Task AddToRoleAsync_InvalidRoleName_ReturnsInternalServerError(string roleName) - { - var usersPage = await UserExtention.GetUsers(10, _httpClient); - var mutation = new - { - query = "mutation AddToRole($roleName: String!, $id: String!) { addToRole(roleName: $roleName, id: $id) { accessToken, refreshToken }}", - variables = new - { - roleName, - id = usersPage[0].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 RemoveFromRoleAsync_ValidRoleName_ReturnsTokensModel() - { - var usersPage = await UserExtention.GetUsers(10, _httpClient); - var mutation = new - { - query = "mutation RemoveFromRole($roleName: String!, $id: String!) { removeFromRole(roleName: $roleName, id: $id) { accessToken, refreshToken }}", - variables = new - { - roleName = "Admin", - id = usersPage[0].Id, - } - }; - - 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 accessToken = (string)document.data.removeFromRole.accessToken; - var refreshToken = (string)document.data.removeFromRole.refreshToken; - - Assert.NotNull(accessToken); - Assert.NotNull(refreshToken); - } - - [Theory] - [InlineData("")] - [InlineData("InvalidRole")] - public async Task RemoveFromRoleAsync_InvalidRoleName_ReturnsInternalServerError(string roleName) - { - var usersPage = await UserExtention.GetUsers(10, _httpClient); - var mutation = new - { - query = "mutation RemoveFromRole($roleName: String!, $id: String!) { removeFromRole(roleName: $roleName, id: $id) { accessToken, refreshToken }}", - variables = new - { - roleName, - id = usersPage[0].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); - } - - [Theory] - [InlineData("User")] - [InlineData(null)] - public async Task AddRole_InvalidRoleName_ReturnsInternalServerError(string roleName) + public async Task AddRole_Unauthorized_ReturnsErrors() { var mutation = new { @@ -141,23 +45,45 @@ public class RolesTests : IClassFixture> { dto = new { - name = roleName + name = "NewRole" + } + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + + [Fact] + public async Task AddRole_ExistingRoleName_ReturnsErrors() + { + await LoginAsync("admin@gmail.com", "Yuiop12345"); + var mutation = new + { + query = "mutation AddRole ($dto: RoleCreateDtoInput!){ addRole (roleDto: $dto) { id, name }} ", + variables = new + { + dto = new + { + name = "User" } } }; - var jsonPayload = JsonConvert.SerializeObject(mutation); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; - using var response = await _httpClient.PostAsync("graphql", content); - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.NotNull(errors); + Assert.True(errors.Count > 0); } [Fact] public async Task GetRolesPageAsync_ValidPageNumberAndSize_ReturnsRolesPagedList() { - var tokensModel = await AccessExtention.Login("mykhailo.bilodid@nure.ua", "Yuiop12345", _httpClient); - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); + await LoginAsync("admin@gmail.com", "Yuiop12345"); var query = new { query = "query RolesPage($pageNumber: Int!, $pageSize: Int!) { rolesPage(pageNumber: $pageNumber, pageSize: $pageSize) { items { id, name } }}", @@ -167,18 +93,11 @@ public class RolesTests : IClassFixture> pageSize = 3 } }; + + var jsonObject = await SendGraphQlRequestAsync(query); + var pagedList = (PagedList?) jsonObject?.data?.rolesPage?.ToObject>(); - 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 items = document.data.rolesPage.items; - Assert.NotEmpty(items); + Assert.NotNull(pagedList); + Assert.NotEmpty(pagedList.Items); } } \ No newline at end of file diff --git a/ShoppingAssistantApi.Tests/Tests/TestsBase.cs b/ShoppingAssistantApi.Tests/Tests/TestsBase.cs new file mode 100644 index 0000000..d5d7962 --- /dev/null +++ b/ShoppingAssistantApi.Tests/Tests/TestsBase.cs @@ -0,0 +1,66 @@ +using System.Net.Http.Headers; +using System.Text; +using Newtonsoft.Json; +using ShoppingAssistantApi.Application.Models.Dtos; +using ShoppingAssistantApi.Tests.TestExtentions; + +namespace ShoppingAssistantApi.Tests.Tests; + +public class TestsBase: IClassFixture> +{ + private protected HttpClient _httpClient; + + public TestsBase(TestingFactory factory) + { + _httpClient = factory.CreateClient(); + factory.InitialaizeDatabase(); + } + + public async Task LoginAsync(string email, string password) + { + var mutation = new + { + query = "mutation Login($login: AccessUserModelInput!) { login(login: $login) { accessToken refreshToken }}", + variables = new + { + login = new + { + email = email, + password = password + } + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", (string?) jsonObject?.data?.login?.accessToken); + } + + public async Task SendGraphQlRequestAsync(object request) + { + var jsonPayload = JsonConvert.SerializeObject(request); + var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + + using var response = await _httpClient.PostAsync("graphql", content); + var responseString = await response.Content.ReadAsStringAsync(); + Console.WriteLine(responseString); + + var jsonObject = JsonConvert.DeserializeObject(responseString); + + return jsonObject; + } + + public async Task GetCurrentUserAsync() + { + var query = new + { + query = "query CurrentUser { currentUser { id, guestId, phone, email, roles { id, name }}}", + variables = new { } + }; + + var jsonObject = await SendGraphQlRequestAsync(query); + var user = (UserDto?) jsonObject?.data?.currentUser?.ToObject(); + + return user; + } +} \ No newline at end of file diff --git a/ShoppingAssistantApi.Tests/Tests/UsersTests.cs b/ShoppingAssistantApi.Tests/Tests/UsersTests.cs index 8c0e7d1..1d34597 100644 --- a/ShoppingAssistantApi.Tests/Tests/UsersTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/UsersTests.cs @@ -1,222 +1,172 @@ 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.Application.Models.Identity; +using Newtonsoft.Json.Linq; +using ShoppingAssistantApi.Application.Paging; namespace ShoppingAssistantApi.Tests.Tests; -[Collection("Tests")] -public class UsersTests : IClassFixture> +// TODO: make errors test more descrptive +public class UsersTests : TestsBase { - private readonly HttpClient _httpClient; - public UsersTests(TestingFactory factory) - { - _httpClient = factory.CreateClient(); - factory.InitialaizeData().GetAwaiter().GetResult(); - } + : base(factory) + { } [Fact] public async Task UpdateUserAsync_ValidUserModel_ReturnsUpdateUserModel() { - var tokensModel = await AccessExtention.CreateGuest(Guid.NewGuid().ToString(), _httpClient); - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); - - var user = await UserExtention.GetCurrentUser(_httpClient); - - var roles = new object[1]; - - foreach(var role in user.Roles) - { - roles[0] = new - { - id = role.Id, - name = role.Name - }; - } - + await LoginAsync("test@gmail.com", "Yuiop12345"); + var user = await GetCurrentUserAsync(); var mutation = new { - query = "mutation UpdateUser($userDto: UserDtoInput!) { updateUser(userDto: $userDto) { tokens { accessToken, refreshToken }, user { email } }}", + query = @" + mutation UpdateUser($userDto: UserDtoInput!) { + updateUser(userDto: $userDto) { + tokens { accessToken, refreshToken }, + user { email, phone } + } + }", variables = new { userDto = new { id = user.Id, guestId = user.GuestId, - roles = roles, - email = "testing@gmail.com", - password = "Yuiop12345", - refreshTokenExpiryDate = user.RefreshTokenExpiryDate + roles = user.Roles.Select(r => new { id = r.Id, name = r.Name }), + email = user.Email, + phone = "+12345678902", } } }; - var jsonPayload = JsonConvert.SerializeObject(mutation); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(mutation); + var tokens = (TokensModel?) jsonObject?.data?.updateUser?.tokens?.ToObject(); + var updatedUser = (UserDto?) jsonObject?.data?.updateUser?.user?.ToObject(); - 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 accessTokenResult = (string)document.data.updateUser.tokens.accessToken; - var refreshTokenResult = (string)document.data.updateUser.tokens.refreshToken; - var userResult = JsonConvert.DeserializeObject(document.data.updateUser.user.ToString()); - - Assert.NotNull(accessTokenResult); - Assert.NotNull(refreshTokenResult); - Assert.NotNull(userResult.Email); + Assert.NotNull(tokens); + Assert.NotNull(tokens.AccessToken); + Assert.NotNull(tokens.RefreshToken); + Assert.NotNull(updatedUser); + Assert.NotNull(updatedUser.Email); + Assert.Equal(user.Email, updatedUser.Email); + Assert.Equal("+12345678902", updatedUser.Phone); } [Fact] public async Task UpdateUserByAdminAsync_ValidUserModel_ReturnsUpdateUserModel() { - var tokensModel = await AccessExtention.CreateGuest(new Guid().ToString(), _httpClient); - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); - - var user = await UserExtention.GetCurrentUser(_httpClient); - - var roles = new object[1]; - - foreach (var role in user.Roles) - { - roles[0] = new - { - id = role.Id, - name = role.Name, - }; - } - + await LoginAsync("test@gmail.com", "Yuiop12345"); + var user = await GetCurrentUserAsync(); var mutation = new { - query = "mutation UpdateUserByAdmin($id: String!, $userDto: UserDtoInput!) { updateUserByAdmin(id: $id, userDto: $userDto) { tokens { accessToken, refreshToken }, user { guestId } }}", + query = @" + mutation UpdateUserByAdmin($id: String!, $userDto: UserDtoInput!) { + updateUserByAdmin(id: $id, userDto: $userDto) { + email, + phone + } + }", variables = new { id = user.Id, userDto = new { id = user.Id, - guestId = Guid.NewGuid().ToString(), - roles = roles, - refreshTokenExpiryDate = user.RefreshTokenExpiryDate + guestId = user.GuestId, + roles = user.Roles.Select(r => new { id = r.Id, name = r.Name }), + email = user.Email, + phone = "+12345678903", } } }; - var jsonPayload = JsonConvert.SerializeObject(mutation); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(mutation); + var updatedUser = (UserDto?) jsonObject?.data?.updateUserByAdmin?.ToObject(); - 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 accessTokenResult = (string)document.data.updateUserByAdmin.tokens.accessToken; - var refreshToken = (string)document.data.updateUserByAdmin.tokens.refreshToken; - var updatedUserGuestId = (Guid)document.data.updateUserByAdmin.user.guestId; - - Assert.NotNull(accessTokenResult); - Assert.NotNull(refreshToken); - Assert.NotEqual(user.GuestId, updatedUserGuestId); + Assert.NotNull(updatedUser); + Assert.NotNull(updatedUser.Email); + Assert.Equal(user.Email, updatedUser.Email); + Assert.Equal("+12345678903", updatedUser.Phone); } [Fact] public async Task GetUserAsync_ValidUserId_ReturnsUser() { - var tokensModel = await AccessExtention.Login("mykhailo.bilodid@nure.ua", "Yuiop12345", _httpClient); - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); - - var usersPage = await UserExtention.GetUsers(10, _httpClient); + await LoginAsync("admin@gmail.com", "Yuiop12345"); var query = new { - query = "query User($id: String!) { user(id: $id) { id, email, phone }}", + query = @" + query User($id: String!) { + user(id: $id) { + id, + email + } + }", variables = new { - id = usersPage[0].Id, + id = "652c3b89ae02a3135d6409fc", } }; - var jsonPayload = JsonConvert.SerializeObject(query); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(query); + var user = (UserDto?) jsonObject?.data?.user?.ToObject(); - 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 userResult = JsonConvert.DeserializeObject(document.data.user.ToString()); - Assert.Equal(userResult.Id, usersPage[0].Id); + Assert.NotNull(user); + Assert.Equal("652c3b89ae02a3135d6409fc", user.Id); + Assert.Equal("test@gmail.com", user.Email); } [Fact] public async Task GetUserAsync_InvalidUserId_ReturnsInternalServerError() { - var tokensModel = await AccessExtention.Login("mykhailo.bilodid@nure.ua", "Yuiop12345", _httpClient); - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); - + await LoginAsync("admin@gmail.com", "Yuiop12345"); var query = new { - query = "query User($id: String!) { user(id: $id) { id, email, phone }}", + query = "query User($id: String!) { user(id: $id) { id }}", variables = new { - id = "error", + id = "invalid", } }; - var jsonPayload = JsonConvert.SerializeObject(query); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(query); + var errors = (JArray?) jsonObject?.errors; - using var response = await _httpClient.PostAsync("graphql", content); - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.NotNull(errors); + Assert.True(errors.Count > 0); } [Fact] - public async Task GetCurrentUserAsync_ValidCredentials_ReturnsCurrentUser() + public async Task GetCurrentUserAsync_Authorized_ReturnsCurrentUser() { - var tokensModel = await AccessExtention.Login("mykhailo.bilodid@nure.ua", "Yuiop12345", _httpClient); - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); - + await LoginAsync("admin@gmail.com", "Yuiop12345"); var query = new { - query = "query CurrentUser { currentUser { id, email, phone }}", - variables = new { } + query = "query CurrentUser { currentUser { id, email, phone }}" }; - var jsonPayload = JsonConvert.SerializeObject(query); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(query); + var user = (UserDto?) jsonObject?.data?.currentUser?.ToObject(); - 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 user = JsonConvert.DeserializeObject(document.data.currentUser.ToString()); - Assert.NotEmpty(user.Id); - Assert.NotEmpty(user.Email); - Assert.NotEmpty(user.Phone); - Assert.Equal(user.Email, "mykhailo.bilodid@nure.ua"); + Assert.NotNull(user); + Assert.Equal("652c3b89ae02a3135d6408fc", user.Id); + Assert.Equal("admin@gmail.com", user.Email); + Assert.Equal("+12345678901", user.Phone); } [Fact] public async Task GetUsersPageAsync_ValidPageNumberAndSize_ReturnsUsersPage() { - var tokensModel = await AccessExtention.Login("mykhailo.bilodid@nure.ua", "Yuiop12345", _httpClient); - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokensModel.AccessToken); - + await LoginAsync("admin@gmail.com", "Yuiop12345"); var query = new { - query = "query UsersPage($pageNumber: Int!, $pageSize: Int!) { usersPage(pageNumber: $pageNumber, pageSize: $pageSize) { items { id, email, phone }}}", + query = @" + query UsersPage($pageNumber: Int!, $pageSize: Int!) { + usersPage(pageNumber: $pageNumber, pageSize: $pageSize) { + items { id, email, phone } + } + }", variables = new { pageNumber = 1, @@ -224,17 +174,128 @@ public class UsersTests : IClassFixture> } }; - var jsonPayload = JsonConvert.SerializeObject(query); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(query); + var pagedList = (PagedList?) jsonObject?.data?.usersPage?.ToObject>(); - using var response = await _httpClient.PostAsync("graphql", content); - response.EnsureSuccessStatusCode(); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotNull(pagedList); + Assert.NotEmpty(pagedList.Items); + } - var responseString = await response.Content.ReadAsStringAsync(); - var document = JsonConvert.DeserializeObject(responseString); - var items = document.data.usersPage.items; - Assert.NotEmpty(items); + [Fact] + public async Task AddToRoleAsync_ValidRoleName_ReturnsTokensModel() + { + await LoginAsync("admin@gmail.com", "Yuiop12345"); + var mutation = new + { + query = @" + mutation AddToRole($roleName: String!, $userId: String!) { + addToRole(roleName: $roleName, userId: $userId) { + id, email, roles { + name + } + } + }", + variables = new + { + roleName = "Admin", + userId = "652c3b89ae02a3135d6409fc", + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var user = (UserDto?) jsonObject?.data?.addToRole?.ToObject(); + + Assert.NotNull(user); + Assert.Equal("652c3b89ae02a3135d6409fc", user.Id); + Assert.Equal("test@gmail.com", user.Email); + Assert.Contains(user.Roles, r => r.Name == "Admin"); + } + + [Fact] + public async Task AddToRoleAsync_NonExistingRole_ReturnsErrors() + { + await LoginAsync("admin@gmail.com", "Yuiop12345"); + var mutation = new + { + query = @" + mutation AddToRole($roleName: String!, $userId: String!) { + addToRole(roleName: $roleName, userId: $userId) { + id, email, roles { + name + } + } + }", + variables = new + { + roleName = "NonExistingRole", + id = "652c3b89ae02a3135d6409fc", + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + + + [Fact] + public async Task RemoveFromRoleAsync_ValidRoleName_ReturnsTokensModel() + { + await LoginAsync("admin@gmail.com", "Yuiop12345"); + var mutation = new + { + query = @" + mutation RemoveFromRole($roleName: String!, $userId: String!) { + removeFromRole(roleName: $roleName, userId: $userId) { + id, email, roles { + name + } + } + }", + variables = new + { + roleName = "User", + userId = "652c3b89ae02a3135d6409fc", + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var user = (UserDto?) jsonObject?.data?.removeFromRole?.ToObject(); + + Assert.NotNull(user); + Assert.Equal("652c3b89ae02a3135d6409fc", user.Id); + Assert.Equal("test@gmail.com", user.Email); + Assert.DoesNotContain(user.Roles, r => r.Name == "User"); + } + + [Fact] + public async Task RemoveFromRoleAsync_NonExistingRole_ReturnsErrors() + { + await LoginAsync("admin@gmail.com", "Yuiop12345"); + var mutation = new + { + query = @" + mutation RemoveFromRole($roleName: String!, $userId: String!) { + removeFromRole(roleName: $roleName, userId: $userId) { + id, email, roles { + name + } + } + }", + variables = new + { + roleName = "NonExistingRole", + userId = "652c3b89ae02a3135d6409fc", + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); } } diff --git a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs index d153a33..0f5b723 100644 --- a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -1,40 +1,39 @@ using ShoppingAssistantApi.Tests.TestExtentions; -using System.Net.Http.Headers; -using System.Net; -using System.Text; -using Xunit; -using Newtonsoft.Json; using ShoppingAssistantApi.Domain.Enums; +using ShoppingAssistantApi.Application.Models.Dtos; +using ShoppingAssistantApi.Application.Paging; +using Newtonsoft.Json.Linq; namespace ShoppingAssistantApi.Tests.Tests; -[Collection("Tests")] -public class WishlistsTests : IClassFixture> +// TODO: make errors test more descrptive +public class WishlistsTests : TestsBase { - private readonly HttpClient _httpClient; + // From DbInitializer + private const string TestingUserId = "652c3b89ae02a3135d6418fc"; - private const string WISHLIST_TESTING_USER_EMAIL = "shopping.assistant.team@gmail.com"; + private const string TestingUserEmail = "wishlists@gmail.com"; - private const string WISHLIST_TESTING_USER_PASSWORD = "Yuiop12345"; + private const string TestingUserPassword = "Yuiop12345"; - private const string TESTING_WISHLIST_ID = "ab79cde6f69abcd3efab65cd"; + private const string TestingWishlistId = "ab79cde6f69abcd3efab65cd"; public WishlistsTests(TestingFactory factory) - { - _httpClient = factory.CreateClient(); - factory.InitialaizeData().GetAwaiter().GetResult(); - } + : base(factory) + { } [Fact] - public async Task StartPersonalWishlistAsync_ValidWishlistModel_ReturnsNewWishlistModels() + public async Task StartPersonalWishlistAsync_ValidWishlist_ReturnsNewWishlist() { - 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); - + await LoginAsync(TestingUserEmail, TestingUserPassword); var mutation = new { - query = "mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { startPersonalWishlist (dto: $dto) { id, name, type, createdById } }", + query = @" + mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { + startPersonalWishlist (dto: $dto) { + id, name, type, createdById + } + }", variables = new { dto = new @@ -45,36 +44,27 @@ public class WishlistsTests : IClassFixture> } }; - var jsonPayload = JsonConvert.SerializeObject(mutation); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(mutation); + var wishlist = (WishlistDto?) jsonObject?.data?.startPersonalWishlist?.ToObject(); - 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); + Assert.NotNull(wishlist); + Assert.Equal(TestingUserId, wishlist.CreatedById); + Assert.Equal(WishlistTypes.Product.ToString(), wishlist.Type); + Assert.Equal($"{WishlistTypes.Product} Search", wishlist.Name); } [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); - + await LoginAsync(TestingUserEmail, TestingUserPassword); var query = new { - query = "query personalWishlistsPage($pageNumber: Int!, $pageSize: Int!) { personalWishlistsPage(pageNumber: $pageNumber, pageSize: $pageSize) { items { createdById, id, name, type } } }", + query = @" + query personalWishlistsPage($pageNumber: Int!, $pageSize: Int!) { + personalWishlistsPage(pageNumber: $pageNumber, pageSize: $pageSize) { + items { createdById, id, name, type } + } + }", variables = new { pageNumber = 1, @@ -82,111 +72,84 @@ public class WishlistsTests : IClassFixture> } }; - var jsonPayload = JsonConvert.SerializeObject(query); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(query); + var pagedList = (PagedList?) jsonObject?.data?.personalWishlistsPage?.ToObject>(); - 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); + Assert.NotNull(pagedList); + Assert.NotEmpty(pagedList.Items); } [Fact] - public async Task GetPersonalWishlist_ValidWishlistIdOrAuthorizedAccess_ReturnsWishlistDto() + public async Task GetPersonalWishlist_ValidWishlistIdOrAuthorizedAccess_ReturnsWishlist() { - 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); - + await LoginAsync(TestingUserEmail, TestingUserPassword); var query = new { - query = "query personalWishlist($wishlistId: String!) { personalWishlist(wishlistId: $wishlistId) { createdById, id, name, type } }", + query = @" + query personalWishlist($wishlistId: String!) { + personalWishlist(wishlistId: $wishlistId) { + createdById, id, name, type + } + }", variables = new { - wishlistId = TESTING_WISHLIST_ID + wishlistId = TestingWishlistId } }; - var jsonPayload = JsonConvert.SerializeObject(query); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(query); + var wishlist = (WishlistDto?) jsonObject?.data?.personalWishlist?.ToObject(); - 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); + Assert.NotNull(wishlist); + Assert.Equal("Gaming PC", wishlist.Name); + Assert.Equal(WishlistTypes.Product.ToString(), wishlist.Type); + Assert.Equal(TestingUserId, wishlist.CreatedById); } [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"; - + await LoginAsync(TestingUserEmail, TestingUserPassword); + const string MessageText = "Second Message"; var mutation = new { - query = "mutation addMessageToPersonalWishlist($wishlistId: String!, $dto: MessageCreateDtoInput!) { addMessageToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { role, text, createdById } }", + query = @" + mutation addMessageToPersonalWishlist($wishlistId: String!, $dto: MessageCreateDtoInput!) { + addMessageToPersonalWishlist(wishlistId: $wishlistId, dto: $dto) { + role, text, createdById + } + }", variables = new { - wishlistId = TESTING_WISHLIST_ID, + wishlistId = TestingWishlistId, dto = new { - text = MESSAGE_TEXT + text = MessageText } } }; - var jsonPayload = JsonConvert.SerializeObject(mutation); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(mutation); + var message = (MessageDto?) jsonObject?.data?.addMessageToPersonalWishlist?.ToObject(); - 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); + Assert.NotNull(message); + Assert.Equal(MessageRoles.User.ToString(), message.Role); + Assert.Equal(MessageText, message.Text); + Assert.Equal(TestingUserId, message.CreatedById); } [Fact] - public async Task StartPersonalWishlistAsync_InvalidWishlistModel_ReturnsInternalServerError() + public async Task StartPersonalWishlistAsync_InvalidWishlist_ReturnsErrors() { - 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); - + await LoginAsync(TestingUserEmail, TestingUserPassword); var mutation = new { - query = "mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { startPersonalWishlist (dto: $dto) { id, name, type, createdById } }", + query = @" + mutation startPersonalWishlist($dto: WishlistCreateDtoInput!) { + startPersonalWishlist (dto: $dto) { + id, name, type, createdById + } + }", variables = new { dto = new @@ -197,85 +160,89 @@ public class WishlistsTests : IClassFixture> } }; - var jsonPayload = JsonConvert.SerializeObject(mutation); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; - using var response = await _httpClient.PostAsync("graphql", content); - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.NotNull(errors); + Assert.True(errors.Count > 0); } [Fact] - public async Task GetPersonalWishlist_InvalidWishlistId_ReturnsInternalServerError() + public async Task GetPersonalWishlist_InvalidWishlistId_ReturnsErrors() { - 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); - + await LoginAsync(TestingUserEmail, TestingUserPassword); var query = new { - query = "query personalWishlist($wishlistId: String!) { personalWishlist(wishlistId: $wishlistId) { createdById, id, name, type } }", + 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"); + var jsonObject = await SendGraphQlRequestAsync(query); + var errors = (JArray?) jsonObject?.errors; - using var response = await _httpClient.PostAsync("graphql", content); - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.NotNull(errors); + Assert.True(errors.Count > 0); } [Fact] - public async Task GetPersonalWishlist_UnAuthorizedAccess_ReturnsInternalServerError() + public async Task GetPersonalWishlist_UnauthorizedAccess_ReturnsErrors() { - 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); - + await LoginAsync(TestingUserEmail, TestingUserPassword); var query = new { - query = "query personalWishlist($wishlistId: String!) { personalWishlist(wishlistId: $wishlistId) { createdById, id, name, type } }", + 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"); + var jsonObject = await SendGraphQlRequestAsync(query); + var errors = (JArray?) jsonObject?.errors; - using var response = await _httpClient.PostAsync("graphql", content); - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.NotNull(errors); + Assert.True(errors.Count > 0); } [Fact] - public async Task AddMessageToPersonalWishlist_InvalidMessageModel_ReturnsInternalServerError() + public async Task AddMessageToPersonalWishlist_InvalidMessage_ReturnsErrors() { - 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"; - + await LoginAsync(TestingUserEmail, TestingUserPassword); var mutation = new { - query = "mutation addMessageToPersonalWishlist($wishlistId: String!, $dto: MessageCreateDtoInput!) { addMessageToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { role, text, createdById } }", + 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, + text = "random text", } } }; - var jsonPayload = JsonConvert.SerializeObject(mutation); - var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; - using var response = await _httpClient.PostAsync("graphql", content); - Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + Assert.NotNull(errors); + Assert.True(errors.Count > 0); } } diff --git a/ShoppingAssistantApi.Tests/Usings.cs b/ShoppingAssistantApi.Tests/Usings.cs new file mode 100644 index 0000000..4f1bf08 --- /dev/null +++ b/ShoppingAssistantApi.Tests/Usings.cs @@ -0,0 +1,4 @@ +global using Xunit; + +// Parallel running was disabled because it causes errors with the database access +[assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file From 424acaf451a1b0d855b7170dacecc7fa92340383 Mon Sep 17 00:00:00 2001 From: cuqmbr Date: Mon, 16 Oct 2023 13:29:30 +0300 Subject: [PATCH 20/22] add lost wishlist integrational tests --- .../TestExtentions/DbInitializer.cs | 213 +++-- .../Tests/WishlistsTests.cs | 760 +++++++++++++++++- 2 files changed, 881 insertions(+), 92 deletions(-) diff --git a/ShoppingAssistantApi.Tests/TestExtentions/DbInitializer.cs b/ShoppingAssistantApi.Tests/TestExtentions/DbInitializer.cs index 6877b07..811cce6 100644 --- a/ShoppingAssistantApi.Tests/TestExtentions/DbInitializer.cs +++ b/ShoppingAssistantApi.Tests/TestExtentions/DbInitializer.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using MongoDB.Bson; +using MongoDB.Driver; using ShoppingAssistantApi.Domain.Entities; using ShoppingAssistantApi.Domain.Enums; using ShoppingAssistantApi.Infrastructure.Services.Identity; @@ -22,6 +23,8 @@ public class DbInitializer InitializeUsersAsync().Wait(); InitializeWishlistsAsync().Wait(); + InitializeMessagesAsync().Wait(); + InitializeProductsAsync().Wait(); } public async Task InitializeUsersAsync() @@ -97,94 +100,156 @@ public class DbInitializer public async Task InitializeWishlistsAsync() { var wishlistsCollection = _dbContext.Db.GetCollection("Wishlists"); - var messagesCollection = _dbContext.Db.GetCollection("Messages"); + var usersCollection = _dbContext.Db.GetCollection("Users"); - var gamingPcWishlist = new Wishlist - { - Id = ObjectId.Parse("ab79cde6f69abcd3efab65cd"), - Name = "Gaming PC", - Type = WishlistTypes.Product.ToString(), - CreatedById = ObjectId.Parse("652c3b89ae02a3135d6418fc") - }; - await wishlistsCollection.InsertOneAsync(gamingPcWishlist); + var user1 = await (await usersCollection.FindAsync(x => x.Email!.Equals("wishlists@gmail.com"))).FirstAsync(); + var user2 = await (await usersCollection.FindAsync(x => x.Email!.Equals("test@gmail.com"))).FirstAsync(); - await messagesCollection.InsertManyAsync(new Message[] - { - new() { - WishlistId = ObjectId.Parse("ab79cde6f69abcd3efab65cd"), - Text = "Prompt", - Role = MessageRoles.User.ToString(), - CreatedDateUtc = DateTime.UtcNow.AddMinutes(-1), - CreatedById = ObjectId.Parse("652c3b89ae02a3135d6418fc") - }, - new() { - WishlistId = ObjectId.Parse("ab79cde6f69abcd3efab65cd"), - Text = "Answer", - Role = MessageRoles.Application.ToString(), - CreatedDateUtc = DateTime.UtcNow, - CreatedById = ObjectId.Parse("652c3b89ae02a3135d6418fc") - }, - }); + var wishlistId1 = ObjectId.Parse("ab79cde6f69abcd3efab65cd"); + var wishlistId2 = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"); - var genericWishlist = new Wishlist + var wishlists = new Wishlist[] { - Id = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), - Name = "Generic Wishlist Name", - Type = WishlistTypes.Product.ToString(), - CreatedById = ObjectId.Parse("652c3b89ae02a3135d6409fc"), - Messages = new Message[] + new Wishlist { - new Message - { - Text = "One Message", - Role = MessageRoles.User.ToString(), - CreatedDateUtc = DateTime.UtcNow.AddMinutes(-1), - CreatedById = ObjectId.Parse("652c3b89ae02a3135d6409fc") - } + Id = wishlistId1, + Name = "Gaming PC", + Type = WishlistTypes.Product.ToString(), + CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow + }, + new Wishlist + { + Id = wishlistId2, + Name = "Generic Wishlist Name", + Type = WishlistTypes.Product.ToString(), + CreatedById = user2.Id, + CreatedDateUtc = DateTime.UtcNow } }; - await wishlistsCollection.InsertOneAsync(genericWishlist); - await messagesCollection.InsertOneAsync(new Message - { - WishlistId = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"), - Text = "One Message", - Role = MessageRoles.User.ToString(), - CreatedDateUtc = DateTime.UtcNow.AddMinutes(-1), - CreatedById = ObjectId.Parse("652c3b89ae02a3135d6409fc") - }); - - var mouseWishlist = new Wishlist - { - Id = ObjectId.Parse("ab79cde6f69abcd3efab95cd"), - Name = "Mouse", - Type = WishlistTypes.Product.ToString(), - CreatedById = ObjectId.Parse("652c3b89ae02a3135d6418fc"), - }; - await wishlistsCollection.InsertOneAsync(mouseWishlist); - await messagesCollection.InsertManyAsync(new List + await wishlistsCollection.InsertManyAsync(wishlists); + } + + + public async Task InitializeMessagesAsync() + { + var messagesCollection = _dbContext.Db.GetCollection("Messages"); + var usersCollection = _dbContext.Db.GetCollection("Users"); + + var user1 = await (await usersCollection.FindAsync(x => x.Email!.Equals("wishlists@gmail.com"))).FirstAsync(); + var user2 = await (await usersCollection.FindAsync(x => x.Email!.Equals("test@gmail.com"))).FirstAsync(); + + var wishlistId1 = ObjectId.Parse("ab79cde6f69abcd3efab65cd"); + var wishlistId2 = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"); + + var messages = new Message[] { - new() { - WishlistId = ObjectId.Parse("ab79cde6f69abcd3efab95cd"), - Text = "First Message", + new Message + { + Text = "Message 1", Role = MessageRoles.User.ToString(), - CreatedDateUtc = DateTime.UtcNow.AddMinutes(-2), - CreatedById = ObjectId.Parse("652c3b89ae02a3135d6418fc"), + WishlistId = wishlistId1, + CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow }, - new() { - WishlistId = ObjectId.Parse("ab79cde6f69abcd3efab95cd"), - Text = "Second Message", + new Message + { + Text = "Message 2", Role = MessageRoles.Application.ToString(), - CreatedDateUtc = DateTime.UtcNow.AddMinutes(-1), - CreatedById = ObjectId.Parse("652c3b89ae02a3135d6418fc"), + WishlistId = wishlistId1, + CreatedDateUtc = DateTime.UtcNow.AddSeconds(5) }, - new() { - WishlistId = ObjectId.Parse("ab79cde6f69abcd3efab95cd"), - Text = "Third Message", + new Message + { + Text = "Message 3", Role = MessageRoles.User.ToString(), - CreatedDateUtc = DateTime.UtcNow, - CreatedById = ObjectId.Parse("652c3b89ae02a3135d6418fc"), + WishlistId = wishlistId1, + CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow.AddSeconds(20) }, - }); + new Message + { + Text = "Message 4", + Role = MessageRoles.Application.ToString(), + WishlistId = wishlistId1, + CreatedDateUtc = DateTime.UtcNow.AddSeconds(25) + }, + new Message + { + Text = "Message 5", + Role = MessageRoles.User.ToString(), + WishlistId = wishlistId1, + CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow.AddSeconds(45) + }, + new Message + { + Text = "Message 6", + Role = MessageRoles.Application.ToString(), + WishlistId = wishlistId1, + CreatedDateUtc = DateTime.UtcNow.AddSeconds(50) + }, + new Message + { + Text = "Prompt", + Role = MessageRoles.User.ToString(), + WishlistId = wishlistId2, + CreatedById = user2.Id, + CreatedDateUtc = DateTime.UtcNow + } + }; + + await messagesCollection.InsertManyAsync(messages); + } + + public async Task InitializeProductsAsync() + { + var productsCollection = _dbContext.Db.GetCollection("Products"); + var usersCollection = _dbContext.Db.GetCollection("Users"); + + var user1 = await (await usersCollection.FindAsync(x => x.Email!.Equals("wishlists@gmail.com"))).FirstAsync(); + var user2 = await (await usersCollection.FindAsync(x => x.Email!.Equals("test@gmail.com"))).FirstAsync(); + + var wishlistId1 = ObjectId.Parse("ab79cde6f69abcd3efab65cd"); + var wishlistId2 = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab"); + + var products = new Product[] + { + new Product + { + Name = "AMD Ryzen 5 5600G 6-Core 12-Thread Unlocked Desktop Processor with Radeon Graphics", + Description = "Features best-in-class graphics performance in a desktop processor for smooth 1080p gaming, no graphics card required", + Rating = 4.8, + Url = "https://a.co/d/5ceuIrq", + ImagesUrls = new string[] + { + "https://m.media-amazon.com/images/I/51f2hkWjTlL._AC_SL1200_.jpg", + "https://m.media-amazon.com/images/I/51iji7Gel-L._AC_SL1200_.jpg" + }, + WasOpened = false, + WishlistId = wishlistId1, + CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow + }, + new Product + { + Name = "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 ", + Description = "7 Year Limited Warranty: The 970 EVO Plus provides up to 1200 TBW (Terabytes Written) with 5-years of protection for exceptional endurance powered by the latest V-NAND technology and Samsung's reputation for quality ", + Rating = 4.8, + Url = "https://a.co/d/gxnuqs1", + ImagesUrls = new string[] + { + "https://m.media-amazon.com/images/I/51Brl+iYtvL._AC_SL1001_.jpg", + "https://m.media-amazon.com/images/I/51GOfLlVwoL._AC_SL1001_.jpg" + }, + WasOpened = false, + WishlistId = wishlistId1, + CreatedById = user1.Id, + CreatedDateUtc = DateTime.UtcNow + }, + }; + + await productsCollection.InsertManyAsync(products); } } diff --git a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs index ac89a96..5d989b7 100644 --- a/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs +++ b/ShoppingAssistantApi.Tests/Tests/WishlistsTests.cs @@ -6,7 +6,6 @@ using Newtonsoft.Json.Linq; namespace ShoppingAssistantApi.Tests.Tests; -// TODO: make errors test more descrptive public class WishlistsTests : TestsBase { // From DbInitializer @@ -16,14 +15,23 @@ public class WishlistsTests : TestsBase private const string TestingUserPassword = "Yuiop12345"; - private const string TestingWishlistId = "ab79cde6f69abcd3efab65cd"; + private const string TestingNotExistingWishlistId = "1234567890abcdef12345678"; + + private const string TestingValidWishlistName = "Gaming PC"; + + private const WishlistTypes TestingValidWishlistType = WishlistTypes.Product; + + private const string TestingUnauthorizedWishlistId = "ab6c2c2d9edf39abcd1ef9ab"; + + private const string TestingValidWishlistId = "ab79cde6f69abcd3efab65cd"; + public WishlistsTests(TestingFactory factory) : base(factory) { } [Fact] - public async Task StartPersonalWishlistAsync_ValidWishlist_ReturnsNewWishlist() + public async Task StartPersonalWishlist_ValidWishlist_ReturnsNewWishlist() { await LoginAsync(TestingUserEmail, TestingUserPassword); var mutation = new @@ -93,7 +101,7 @@ public class WishlistsTests : TestsBase }", variables = new { - wishlistId = TestingWishlistId + wishlistId = TestingValidWishlistId } }; @@ -101,8 +109,8 @@ public class WishlistsTests : TestsBase var wishlist = (WishlistDto?) jsonObject?.data?.personalWishlist?.ToObject(); Assert.NotNull(wishlist); - Assert.Equal("Gaming PC", wishlist.Name); - Assert.Equal(WishlistTypes.Product.ToString(), wishlist.Type); + Assert.Equal(TestingValidWishlistName, wishlist.Name); + Assert.Equal(TestingValidWishlistType.ToString(), wishlist.Type); Assert.Equal(TestingUserId, wishlist.CreatedById); } @@ -121,7 +129,7 @@ public class WishlistsTests : TestsBase }", variables = new { - wishlistId = TestingWishlistId, + wishlistId = TestingValidWishlistId, dto = new { text = MessageText @@ -139,7 +147,7 @@ public class WishlistsTests : TestsBase } [Fact] - public async Task GetMessagesPageFromPersonalWishlist_ValidPageNumberAndSize_ReturnsPage() + public async Task GetMessagesPageFromPersonalWishlist_ValidPageNumberAndSizeValidWishlistIdOrAuthorizedAccess_ReturnsPage() { await LoginAsync(TestingUserEmail, TestingUserPassword); var mutation = new @@ -158,7 +166,7 @@ public class WishlistsTests : TestsBase }", variables = new { - wishlistId = "ab79cde6f69abcd3efab95cd", // From DbInitializer + wishlistId = TestingValidWishlistId, pageNumber = 1, pageSize = 2 } @@ -169,12 +177,116 @@ public class WishlistsTests : TestsBase Assert.NotNull(pagedList); Assert.NotEmpty(pagedList.Items); - Assert.Equal("Third Message", pagedList.Items.FirstOrDefault()?.Text); - Assert.Equal(MessageRoles.User.ToString(), pagedList.Items.FirstOrDefault()?.Role); + Assert.Equal("Message 6", pagedList.Items.FirstOrDefault()?.Text); + Assert.Equal(MessageRoles.Application.ToString(), pagedList.Items.FirstOrDefault()?.Role); } [Fact] - public async Task StartPersonalWishlistAsync_InvalidWishlist_ReturnsErrors() + public async Task AddProductToPersonalWishlist_ValidProduct_ReturnsNewProduct() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + var mutation = new + { + query = @" + mutation addProductToPersonalWishlist($wishlistId: String!, $dto: ProductCreateDtoInput!) { + addProductToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { + url, name, description, rating, imagesUrls, wasOpened + } + }", + variables = new + { + wishlistId = TestingValidWishlistId, + 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 jsonObject = await SendGraphQlRequestAsync(mutation); + var product = (ProductDto?) jsonObject?.data?.addProductToPersonalWishlist?.ToObject(); + + Assert.NotNull(product); + Assert.Equal("https://www.amazon.com/url", product.Url); + Assert.Equal("Generic name", product.Name); + Assert.Equal("Generic description", product.Description); + Assert.Equal(4.8, product.Rating); + Assert.Equal("https://www.amazon.com/image-url-1", product.ImagesUrls[0]); + } + + [Fact] + public async Task GetProductsPageFromPersonalWishlist_ValidPageNumberAndSizeValidWishlistIdOrAuthorizedAccess_ReturnsPage() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + 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 = TestingValidWishlistId, + pageNumber = 1, + pageSize = 2 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var pagedList = (PagedList?) jsonObject?.data?.productsPageFromPersonalWishlist?.ToObject>(); + + Assert.NotNull(pagedList); + 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 ", pagedList.Items.LastOrDefault()?.Name); + Assert.Equal(TestingValidWishlistId, pagedList.Items.LastOrDefault()?.WishlistId); + } + + [Fact] + public async Task DeletePersonalWishlist_ValidWishlistIdOrAuthorizedAccess_ReturnsWishlist() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + var mutation = new + { + query = @" + mutation deletePersonalWishlist($wishlistId: String!) { + deletePersonalWishlist (wishlistId: $wishlistId) { + createdById, id, name, type + } + }", + variables = new + { + wishlistId = TestingValidWishlistId + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var wishlist = (WishlistDto?) jsonObject?.data?.deletePersonalWishlist?.ToObject(); + + Assert.NotNull(wishlist); + Assert.Equal(TestingValidWishlistId, wishlist.Id); + Assert.Equal(TestingValidWishlistName, wishlist.Name); + Assert.Equal(TestingValidWishlistType.ToString(), wishlist.Type); + Assert.Equal(TestingUserId, wishlist.CreatedById); + } + + [Fact] + public async Task StartPersonalWishlist_InvalidWishlist_ReturnsErrors() { await LoginAsync(TestingUserEmail, TestingUserPassword); var mutation = new @@ -203,7 +315,113 @@ public class WishlistsTests : TestsBase } [Fact] - public async Task GetPersonalWishlist_InvalidWishlistId_ReturnsErrors() + public async Task GetPersonalWishlistsPage_PageNumberGreaterThanAvailablePages_ReturnsEmptyList() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + var mutation = new + { + query = @" + query personalWishlistsPage($pageNumber: Int!, $pageSize: Int!) { + personalWishlistsPage(pageNumber: $pageNumber, pageSize: $pageSize) { + items { createdById, id, name, type } + } + }", + variables = new + { + pageNumber = 100, + pageSize = 2 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var pagedList = (PagedList?) jsonObject?.data?.personalWishlistsPage?.ToObject>(); + + Assert.NotNull(pagedList); + Assert.Empty(pagedList.Items); + } + + [Fact] + public async Task GetPersonalWishlistsPage_PageNumberLessThan1_ReturnsError() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + var mutation = new + { + query = @" + query personalWishlistsPage($pageNumber: Int!, $pageSize: Int!) { + personalWishlistsPage(pageNumber: $pageNumber, pageSize: $pageSize) { + items { createdById, id, name, type } + } + }", + variables = new + { + pageNumber = 0, + pageSize = 1 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + + [Fact] + public async Task GetPersonalWishlistsPage_PageSizeGreaterThanAvailableEntities_ReturnsPage() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + var mutation = 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 jsonObject = await SendGraphQlRequestAsync(mutation); + var pagedList = (PagedList?) jsonObject?.data?.personalWishlistsPage?.ToObject>(); + + Assert.NotNull(pagedList); + Assert.NotEmpty(pagedList.Items); + Assert.Equal(TestingUserId, pagedList.Items.FirstOrDefault()?.CreatedById); + } + + [Fact] + public async Task GetPersonalWishlistsPage_PageSizeLessThan0_ReturnsPage() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + var mutation = 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 jsonObject = await SendGraphQlRequestAsync(mutation); + var pagedList = (PagedList?) jsonObject?.data?.personalWishlistsPage?.ToObject>(); + + Assert.NotNull(pagedList); + Assert.NotEmpty(pagedList.Items); + Assert.Equal(TestingUserId, pagedList.Items.FirstOrDefault()?.CreatedById); + } + + [Fact] + public async Task GetPersonalWishlist_NotExistingWishlistId_ReturnsErrors() { await LoginAsync(TestingUserEmail, TestingUserPassword); var query = new @@ -216,7 +434,7 @@ public class WishlistsTests : TestsBase }", variables = new { - wishlistId = "1234567890abcdef12345678" // Invalid wishlistId + wishlistId = TestingNotExistingWishlistId } }; @@ -228,7 +446,397 @@ public class WishlistsTests : TestsBase } [Fact] - public async Task GetPersonalWishlist_UnauthorizedAccess_ReturnsErrors() + public async Task GetMessagesPageFromPersonalWishlist_PageNumberGreaterThanAvailablePages_ReturnsEmptyList() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + 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 = TestingValidWishlistId, + pageNumber = 100, + pageSize = 2 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var pagedList = (PagedList?) jsonObject?.data?.messagesPageFromPersonalWishlist?.ToObject>(); + + Assert.NotNull(pagedList); + Assert.Empty(pagedList.Items); + } + + [Fact] + public async Task GetMessagesPageFromPersonalWishlist_PageNumberLessThan1_ReturnsError() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + 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 = TestingValidWishlistId, + pageNumber = 0, + pageSize = 2 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + + [Fact] + public async Task GetMessagesPageFromPersonalWishlist_PageSizeGreaterThanAvailableEntities_ReturnsPage() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + 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 = TestingValidWishlistId, + pageNumber = 1, + pageSize = 10 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var pagedList = (PagedList?) jsonObject?.data?.messagesPageFromPersonalWishlist?.ToObject>(); + + Assert.NotNull(pagedList); + Assert.Equal("Message 6", pagedList.Items.FirstOrDefault()?.Text); + Assert.Equal(MessageRoles.Application.ToString(), pagedList.Items.FirstOrDefault()?.Role); + } + + [Fact] + public async Task GetMessagesPageFromPersonalWishlist_PageSizeLessThan0_ReturnsPage() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + 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 = TestingValidWishlistId, + pageNumber = 1, + pageSize = -2 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var pagedList = (PagedList?) jsonObject?.data?.messagesPageFromPersonalWishlist?.ToObject>(); + + Assert.NotNull(pagedList); + Assert.Equal("Message 6", pagedList.Items.FirstOrDefault()?.Text); + Assert.Equal(MessageRoles.Application.ToString(), pagedList.Items.FirstOrDefault()?.Role); + } + + [Fact] + public async Task GetMessagesPageFromPersonalWishlist_NotExistingWishlistId_ReturnsError() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + 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 = TestingNotExistingWishlistId, + pageNumber = 1, + pageSize = 2 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + + [Fact] + public async Task GetMessagesPageFromPersonalWishlist_OtherUserWishlistId_ReturnsError() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + 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 = TestingUnauthorizedWishlistId, + pageNumber = 1, + pageSize = 2 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + + [Fact] + public async Task AddProductToPersonalWishlist_NotExistingWishlistId_RturnsError() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + var mutation = new + { + query = @" + mutation addProductToPersonalWishlist($wishlistId: String!, $dto: ProductCreateDtoInput!) { + addProductToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { + url, name, description, rating, imagesUrls, wasOpened + } + }", + variables = new + { + wishlistId = TestingNotExistingWishlistId, + 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 jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + + [Fact] + public async Task AddProductToPersonalWishlist_OtherUserWishlistId_RturnsError() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + var mutation = new + { + query = @" + mutation addProductToPersonalWishlist($wishlistId: String!, $dto: ProductCreateDtoInput!) { + addProductToPersonalWishlist (wishlistId: $wishlistId, dto: $dto) { + url, name, description, rating, imagesUrls, wasOpened + } + }", + variables = new + { + wishlistId = TestingUnauthorizedWishlistId, + 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 jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + + [Fact] + public async Task GetProductsPageFromPersonalWishlist_PageNumberGreaterThanAvailablePages_ReturnsEmptyList() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + 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 = TestingValidWishlistId, + pageNumber = 100, + pageSize = 2 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var pagedList = (PagedList?) jsonObject?.data?.productsPageFromPersonalWishlist?.ToObject>(); + + Assert.NotNull(pagedList); + Assert.Empty(pagedList.Items); + } + + [Fact] + public async Task GetProductsPageFromPersonalWishlist_PageNumberLessThan1_ReturnsError() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + 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 = TestingValidWishlistId, + pageNumber = 0, + pageSize = 2 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + + [Fact] + public async Task GetProductsPageFromPersonalWishlist_PageSizeGreaterThanAvailableEntities_ReturnsPage() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + 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 = TestingValidWishlistId, + pageNumber = 1, + pageSize = 100 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var pagedList = (PagedList?) jsonObject?.data?.productsPageFromPersonalWishlist?.ToObject>(); + + Assert.NotNull(pagedList); + + 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 ", pagedList.Items.ToList()[1].Name); + Assert.Equal(TestingValidWishlistId, pagedList.Items.ToList()[1].WishlistId); + } + + [Fact] + public async Task GetProductsPageFromPersonalWishlist_PageSizeLessThan0_ReturnsPage() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + 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 = TestingValidWishlistId, + pageNumber = 1, + pageSize = -2 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var pagedList = (PagedList?) jsonObject?.data?.productsPageFromPersonalWishlist?.ToObject>(); + + Assert.NotNull(pagedList); + + 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 ", pagedList.Items.ToList()[1].Name); + Assert.Equal(TestingValidWishlistId, pagedList.Items.ToList()[1].WishlistId); + } + + [Fact] + public async Task GetPersonalWishlist_OtherUserWishlistId_ReturnsErrors() { await LoginAsync(TestingUserEmail, TestingUserPassword); var query = new @@ -241,7 +849,7 @@ public class WishlistsTests : TestsBase }", variables = new { - wishlistId = "ab6c2c2d9edf39abcd1ef9ab" // Other user's wishlist + wishlistId = TestingUnauthorizedWishlistId } }; @@ -252,6 +860,122 @@ public class WishlistsTests : TestsBase Assert.True(errors.Count > 0); } + [Fact] + public async Task GetProductsPageFromPersonalWishlist_NotExistingWishlistId_ReturnsError() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + 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 = TestingNotExistingWishlistId, + pageNumber = 0, + pageSize = 2 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + + [Fact] + public async Task GetProductsPageFromPersonalWishlist_OtherUserWishlistId_ReturnsError() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + 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 = TestingUnauthorizedWishlistId, + pageNumber = 0, + pageSize = 2 + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + + [Fact] + public async Task DeletePersonalWishlist_NotExistingWishlistId_ReturnsError() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + var mutation = new + { + query = @" + mutation deletePersonalWishlist($wishlistId: String!) { + deletePersonalWishlist (wishlistId: $wishlistId) { + createdById, id, name, type + } + }", + variables = new + { + wishlistId = TestingNotExistingWishlistId + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + + [Fact] + public async Task DeletePersonalWishlist_OtherUserWishlistId_ReturnsError() + { + await LoginAsync(TestingUserEmail, TestingUserPassword); + var mutation = new + { + query = @" + mutation deletePersonalWishlist($wishlistId: String!) { + deletePersonalWishlist (wishlistId: $wishlistId) { + createdById, id, name, type + } + }", + variables = new + { + wishlistId = TestingUnauthorizedWishlistId + } + }; + + var jsonObject = await SendGraphQlRequestAsync(mutation); + var errors = (JArray?) jsonObject?.errors; + + Assert.NotNull(errors); + Assert.True(errors.Count > 0); + } + [Fact] public async Task AddMessageToPersonalWishlist_InvalidMessage_ReturnsErrors() { @@ -266,7 +990,7 @@ public class WishlistsTests : TestsBase }", variables = new { - wishlistId = "8125jad7g12", // Invalid wishlistId + wishlistId = TestingNotExistingWishlistId, dto = new { text = "random text", @@ -280,4 +1004,4 @@ public class WishlistsTests : TestsBase Assert.NotNull(errors); Assert.True(errors.Count > 0); } -} \ No newline at end of file +} From 8ae360b7e022258c9e6616b6fb4f0f55b8c00972 Mon Sep 17 00:00:00 2001 From: Mykhailo Bilodid Date: Sat, 21 Oct 2023 16:52:54 +0300 Subject: [PATCH 21/22] SA-116 initialaizer fixed --- .../PersistanceExtentions/DbInitialaizer.cs | 267 +++++++++--------- 1 file changed, 134 insertions(+), 133 deletions(-) diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index f96d491..af50479 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -1,12 +1,7 @@ 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; @@ -15,34 +10,23 @@ 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 IWishlistsService _wishlistsService; - private readonly IMongoCollection _userCollection; + private readonly IMongoCollection _roleCollection; + private readonly IMongoCollection _wishlistCollection; - + private readonly IMongoCollection _productCollection; - public IEnumerable Roles { get; set; } + private readonly IPasswordHasher _passwordHasher; - public DbInitialaizer(IServiceProvider serviceProvider) + public DbInitialaizer(IServiceProvider serviceProvider, IPasswordHasher passwordHasher) { - _usersService = serviceProvider.GetService(); - _rolesService = serviceProvider.GetService(); - _userManager = serviceProvider.GetService(); - _tokensService = serviceProvider.GetService(); - _wishlistsService = serviceProvider.GetService(); _wishlistCollection = serviceProvider.GetService().Db.GetCollection("Wishlists"); _userCollection = serviceProvider.GetService().Db.GetCollection("Users"); + _roleCollection = serviceProvider.GetService().Db.GetCollection("Roles"); _productCollection = serviceProvider.GetService().Db.GetCollection("Product"); + _passwordHasher = passwordHasher; } public async Task InitialaizeDb(CancellationToken cancellationToken) @@ -55,123 +39,140 @@ public class DbInitialaizer public async Task AddUsers(CancellationToken cancellationToken) { - var guestModel1 = new AccessGuestModel + var userRole = await (await _roleCollection.FindAsync(x => x.Name.Equals("User"))).FirstAsync(); + var guestRole = await (await _roleCollection.FindAsync(x => x.Name.Equals("Guest"))).FirstAsync(); + var adminRole = await (await _roleCollection.FindAsync(x => x.Name.Equals("Admin"))).FirstAsync(); + + var users = new User[] { - GuestId = Guid.NewGuid(), + new User + { + Id = ObjectId.Parse("6533bb29c8c22b038c71cf46"), + GuestId = Guid.NewGuid(), + Roles = {guestRole}, + CreatedById = ObjectId.Parse("6533bb29c8c22b038c71cf46"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bb29c8c22b038c71cf46"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false + }, + + new User + { + Id = ObjectId.Parse("6533bde5755745116be42ce7"), + GuestId = Guid.NewGuid(), + Roles = + { + guestRole, + userRole + }, + Phone = "+380953326869", + Email = "mykhailo.bilodid@nure.ua", + PasswordHash = this._passwordHasher.Hash("Yuiop12345"), + CreatedById = ObjectId.Parse("6533bde5755745116be42ce7"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bde5755745116be42ce7"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false + }, + + new User + { + Id = ObjectId.Parse("6533bded80fbc6e96250575b"), + GuestId = Guid.NewGuid(), + Roles = + { + guestRole, + userRole, + adminRole + }, + Phone = "+380953826869", + Email = "shopping.assistant.team@gmail.com", + PasswordHash = this._passwordHasher.Hash("Yuiop12345"), + CreatedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false }, + + new User + { + Id = ObjectId.Parse("6533bdf9efaca5bb0894f992"), + GuestId = Guid.NewGuid(), + Roles = + { + guestRole, + userRole + }, + Phone = "+380983326869", + Email = "vitalii.krasnorutski@nure.ua", + PasswordHash = this._passwordHasher.Hash("Yuiop12345"), + CreatedById = ObjectId.Parse("6533bdf9efaca5bb0894f992"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bdf9efaca5bb0894f992"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false }, + + new User + { + Id = ObjectId.Parse("6533be06d1b78a76c664ddae"), + GuestId = Guid.NewGuid(), + Roles = + { + guestRole, + userRole + }, + Phone = "+380953326888", + Email = "serhii.shchoholiev@nure.ua", + PasswordHash = this._passwordHasher.Hash("Yuiop12345"), + CreatedById = ObjectId.Parse("6533be06d1b78a76c664ddae"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533be06d1b78a76c664ddae"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false } }; - var guestModel2 = new AccessGuestModel - { - GuestId = Guid.NewGuid(), - }; - - var guestModel3 = new AccessGuestModel - { - GuestId = Guid.NewGuid(), - }; - - var guestModel4 = new AccessGuestModel - { - GuestId = Guid.NewGuid(), - }; - - var guestModel5 = new AccessGuestModel - { - GuestId = Guid.NewGuid(), - }; - - Task.WaitAll( - _userManager.AccessGuestAsync(guestModel1, cancellationToken), - _userManager.AccessGuestAsync(guestModel2, cancellationToken), - _userManager.AccessGuestAsync(guestModel3, cancellationToken), - _userManager.AccessGuestAsync(guestModel4, cancellationToken), - _userManager.AccessGuestAsync(guestModel5, cancellationToken) - ); - - var guests = await _usersService.GetUsersPageAsync(1, 4, cancellationToken); - var guestsResult = guests.Items.ToList(); - - var user1 = new UserDto - { - Id = guestsResult[0].Id, - GuestId = guestsResult[0].GuestId, - Roles = guestsResult[0].Roles, - Phone = "+380953326869", - Email = "mykhailo.bilodid@nure.ua", - Password = "Yuiop12345", - RefreshToken = _tokensService.GenerateRefreshToken(), - RefreshTokenExpiryDate = DateTime.Now.AddDays(7), - }; - - var user2 = new UserDto - { - Id = guestsResult[1].Id, - GuestId = guestsResult[1].GuestId, - Roles = guestsResult[1].Roles, - Phone = "+380953326888", - Email = "serhii.shchoholiev@nure.ua", - Password = "Yuiop12345", - RefreshToken = _tokensService.GenerateRefreshToken(), - RefreshTokenExpiryDate = DateTime.Now.AddDays(7), - }; - - var user3 = new UserDto - { - Id = guestsResult[2].Id, - GuestId = guestsResult[2].GuestId, - Roles = guestsResult[2].Roles, - Phone = "+380983326869", - Email = "vitalii.krasnorutski@nure.ua", - Password = "Yuiop12345", - RefreshToken = _tokensService.GenerateRefreshToken(), - RefreshTokenExpiryDate = DateTime.Now.AddDays(7), - }; - - var user4 = new UserDto - { - Id = guestsResult[3].Id, - GuestId = guestsResult[3].GuestId, - Roles = guestsResult[3].Roles, - Phone = "+380953826869", - Email = "shopping.assistant.team@gmail.com", - Password = "Yuiop12345", - RefreshToken = _tokensService.GenerateRefreshToken(), - RefreshTokenExpiryDate = DateTime.Now.AddDays(7), - }; - - GlobalUser.Id = ObjectId.Parse(user1.Id); - await _userManager.UpdateAsync(user1, cancellationToken); - - GlobalUser.Id = ObjectId.Parse(user2.Id); - await _userManager.UpdateAsync(user2, cancellationToken); - - GlobalUser.Id = ObjectId.Parse(user3.Id); - await _userManager.UpdateAsync(user3, cancellationToken); - - GlobalUser.Id = ObjectId.Parse(user4.Id); - await _userManager.UpdateAsync(user4, cancellationToken); + await _userCollection.InsertManyAsync(users); } public async Task AddRoles(CancellationToken cancellationToken) { - var role1 = new RoleCreateDto + var roles = new Role[] { - Name = "User" - }; + new Role + { + Id = ObjectId.Parse("6533b5882e7867b8b21e7b27"), + Name = "User", + CreatedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false + }, - var role2 = new RoleCreateDto - { - Name = "Admin" - }; + new Role + { + Id = ObjectId.Parse("6533b591a7f31776cd2d50fc"), + Name = "Guest", + CreatedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false + }, - var role3 = new RoleCreateDto - { - Name = "Guest" + new Role + { + Id = ObjectId.Parse("6533b59d1b09ab2618af5ff3"), + Name = "Admin", + CreatedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + CreatedDateUtc = DateTime.UtcNow, + LastModifiedById = ObjectId.Parse("6533bded80fbc6e96250575b"), + LastModifiedDateUtc = DateTime.UtcNow, + IsDeleted = false + }, }; - - var dto1 = await _rolesService.AddRoleAsync(role1, cancellationToken); - var dto2 = await _rolesService.AddRoleAsync(role2, cancellationToken); - var dto3 = await _rolesService.AddRoleAsync(role3, cancellationToken); + await _roleCollection.InsertManyAsync(roles); } public async Task AddWishlistsWithMessages(CancellationToken cancellationToken) @@ -249,8 +250,8 @@ public class DbInitialaizer CreatedDateUtc = DateTime.UtcNow, WasOpened = false, WishlistId = ObjectId.Parse("ab79cde6f69abcd3efab65cd") - }, - + }, + new Product() { Name = "Apple MagSafe Battery Pack", @@ -268,7 +269,7 @@ public class DbInitialaizer WasOpened = false, WishlistId = ObjectId.Parse("ab79cde6f69abcd3efab65cd") }, - + new Product() { Name = "Logitech K400 Plus Wireless Touch With Easy Media Control and Built-in Touchpad", @@ -286,7 +287,7 @@ public class DbInitialaizer WasOpened = false, WishlistId = ObjectId.Parse("ab6c2c2d9edf39abcd1ef9ab") }, - + new Product() { Name = "Logitech MX Anywhere 2S Wireless Mouse Use On Any Surface", From bc0501dab598d31e9fd5b506096d1b6259f630c4 Mon Sep 17 00:00:00 2001 From: Mykhailo Bilodid Date: Sat, 21 Oct 2023 22:12:01 +0300 Subject: [PATCH 22/22] SA-116 final changes --- ShoppingAssistantApi.Api/Program.cs | 14 +++++----- .../PersistanceExtentions/DbInitialaizer.cs | 26 ++++++++++--------- .../ShoppingAssistantApi.Persistance.csproj | 1 + 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/ShoppingAssistantApi.Api/Program.cs b/ShoppingAssistantApi.Api/Program.cs index affb266..4a1e95b 100644 --- a/ShoppingAssistantApi.Api/Program.cs +++ b/ShoppingAssistantApi.Api/Program.cs @@ -36,14 +36,12 @@ app.AddGlobalUserMiddleware(); app.MapGraphQL(); app.MapControllers(); -/* -using var scope = app.Services.CreateScope(); -var serviceProvider = scope.ServiceProvider; -using var cancellationTokenSource = new CancellationTokenSource(); -var cancellationToken = cancellationTokenSource.Token; -var initializer = new DbInitialaizer(serviceProvider); -initializer.InitialaizeDb(cancellationToken); -*/ + +// using var scope = app.Services.CreateScope(); +// var serviceProvider = scope.ServiceProvider; +// var initializer = new DbInitialaizer(serviceProvider); +// await initializer.InitialaizeDb(CancellationToken.None); + app.Run(); public partial class Program { } \ No newline at end of file diff --git a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs index af50479..f627b46 100644 --- a/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs +++ b/ShoppingAssistantApi.Persistance/PersistanceExtentions/DbInitialaizer.cs @@ -1,9 +1,11 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; using MongoDB.Bson; using MongoDB.Driver; using ShoppingAssistantApi.Application.IServices.Identity; using ShoppingAssistantApi.Domain.Entities; using ShoppingAssistantApi.Domain.Enums; +using ShoppingAssistantApi.Infrastructure.Services.Identity; using ShoppingAssistantApi.Persistance.Database; namespace ShoppingAssistantApi.Persistance.PersistanceExtentions; @@ -18,15 +20,15 @@ public class DbInitialaizer private readonly IMongoCollection _productCollection; - private readonly IPasswordHasher _passwordHasher; + private readonly PasswordHasher passwordHasher; - public DbInitialaizer(IServiceProvider serviceProvider, IPasswordHasher passwordHasher) + public DbInitialaizer(IServiceProvider serviceProvider) { + passwordHasher = new PasswordHasher(new Logger(new LoggerFactory())); _wishlistCollection = serviceProvider.GetService().Db.GetCollection("Wishlists"); _userCollection = serviceProvider.GetService().Db.GetCollection("Users"); _roleCollection = serviceProvider.GetService().Db.GetCollection("Roles"); _productCollection = serviceProvider.GetService().Db.GetCollection("Product"); - _passwordHasher = passwordHasher; } public async Task InitialaizeDb(CancellationToken cancellationToken) @@ -49,7 +51,7 @@ public class DbInitialaizer { Id = ObjectId.Parse("6533bb29c8c22b038c71cf46"), GuestId = Guid.NewGuid(), - Roles = {guestRole}, + Roles = new List {guestRole}, CreatedById = ObjectId.Parse("6533bb29c8c22b038c71cf46"), CreatedDateUtc = DateTime.UtcNow, LastModifiedById = ObjectId.Parse("6533bb29c8c22b038c71cf46"), @@ -61,14 +63,14 @@ public class DbInitialaizer { Id = ObjectId.Parse("6533bde5755745116be42ce7"), GuestId = Guid.NewGuid(), - Roles = + Roles = new List { guestRole, userRole }, Phone = "+380953326869", Email = "mykhailo.bilodid@nure.ua", - PasswordHash = this._passwordHasher.Hash("Yuiop12345"), + PasswordHash = this.passwordHasher.Hash("Yuiop12345"), CreatedById = ObjectId.Parse("6533bde5755745116be42ce7"), CreatedDateUtc = DateTime.UtcNow, LastModifiedById = ObjectId.Parse("6533bde5755745116be42ce7"), @@ -80,7 +82,7 @@ public class DbInitialaizer { Id = ObjectId.Parse("6533bded80fbc6e96250575b"), GuestId = Guid.NewGuid(), - Roles = + Roles = new List { guestRole, userRole, @@ -88,7 +90,7 @@ public class DbInitialaizer }, Phone = "+380953826869", Email = "shopping.assistant.team@gmail.com", - PasswordHash = this._passwordHasher.Hash("Yuiop12345"), + PasswordHash = this.passwordHasher.Hash("Yuiop12345"), CreatedById = ObjectId.Parse("6533bded80fbc6e96250575b"), CreatedDateUtc = DateTime.UtcNow, LastModifiedById = ObjectId.Parse("6533bded80fbc6e96250575b"), @@ -99,14 +101,14 @@ public class DbInitialaizer { Id = ObjectId.Parse("6533bdf9efaca5bb0894f992"), GuestId = Guid.NewGuid(), - Roles = + Roles = new List { guestRole, userRole }, Phone = "+380983326869", Email = "vitalii.krasnorutski@nure.ua", - PasswordHash = this._passwordHasher.Hash("Yuiop12345"), + PasswordHash = this.passwordHasher.Hash("Yuiop12345"), CreatedById = ObjectId.Parse("6533bdf9efaca5bb0894f992"), CreatedDateUtc = DateTime.UtcNow, LastModifiedById = ObjectId.Parse("6533bdf9efaca5bb0894f992"), @@ -117,14 +119,14 @@ public class DbInitialaizer { Id = ObjectId.Parse("6533be06d1b78a76c664ddae"), GuestId = Guid.NewGuid(), - Roles = + Roles = new List { guestRole, userRole }, Phone = "+380953326888", Email = "serhii.shchoholiev@nure.ua", - PasswordHash = this._passwordHasher.Hash("Yuiop12345"), + PasswordHash = this.passwordHasher.Hash("Yuiop12345"), CreatedById = ObjectId.Parse("6533be06d1b78a76c664ddae"), CreatedDateUtc = DateTime.UtcNow, LastModifiedById = ObjectId.Parse("6533be06d1b78a76c664ddae"), diff --git a/ShoppingAssistantApi.Persistance/ShoppingAssistantApi.Persistance.csproj b/ShoppingAssistantApi.Persistance/ShoppingAssistantApi.Persistance.csproj index 951f76f..743a04f 100644 --- a/ShoppingAssistantApi.Persistance/ShoppingAssistantApi.Persistance.csproj +++ b/ShoppingAssistantApi.Persistance/ShoppingAssistantApi.Persistance.csproj @@ -14,6 +14,7 @@ +