From 547c23f7c0f53fbb8d3ed13817fcb929102b11e2 Mon Sep 17 00:00:00 2001 From: shchoholiev-opflo Date: Sat, 14 Oct 2023 19:53:12 +0000 Subject: [PATCH 1/6] 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 2/6] 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 3/6] 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 39bed12f30077ded23b537ea347fd7d471793631 Mon Sep 17 00:00:00 2001 From: shchoholiev Date: Mon, 16 Oct 2023 03:22:49 +0000 Subject: [PATCH 4/6] 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 5/6] 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 6/6] 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 +}