From a72dd775eebe9898d2ef5acc97f7667c2cca0ed7 Mon Sep 17 00:00:00 2001 From: Philippe Vaillancourt Date: Tue, 4 Oct 2022 16:57:40 +0100 Subject: [PATCH] Refactor to introduce nullable types and reduce compiler warnings (#801) * Introduce nullable types to Core, Infrastructure and PublicApi projects * Refactor unit tests * Fixed failing tests * Introduce Parameter object for CatalogItem update method * Refactor CataloItemDetails param object * Refactor following PR comments --- src/ApplicationCore/ApplicationCore.csproj | 2 + src/ApplicationCore/CatalogSettings.cs | 2 +- .../Entities/BasketAggregate/Basket.cs | 3 +- .../Entities/BuyerAggregate/Buyer.cs | 6 +- .../Entities/BuyerAggregate/PaymentMethod.cs | 6 +- src/ApplicationCore/Entities/CatalogItem.cs | 32 ++++++++--- .../Entities/OrderAggregate/Address.cs | 1 + .../OrderAggregate/CatalogItemOrdered.cs | 6 +- .../Entities/OrderAggregate/Order.cs | 8 +-- .../Entities/OrderAggregate/OrderItem.cs | 6 +- .../Extensions/GuardExtensions.cs | 6 -- .../Extensions/JsonExtensions.cs | 2 +- .../Interfaces/IBasketService.cs | 3 +- src/ApplicationCore/Services/BasketService.cs | 17 +++--- src/ApplicationCore/Services/OrderService.cs | 4 +- src/Infrastructure/Data/CatalogContext.cs | 5 +- .../Data/Config/BasketConfiguration.cs | 2 +- .../Data/Config/OrderConfiguration.cs | 2 +- src/Infrastructure/Data/FileItem.cs | 10 ++-- src/Infrastructure/Infrastructure.csproj | 1 + .../UpdateCatalogItemEndpoint.cs | 5 +- src/PublicApi/PublicApi.csproj | 1 + .../Identity/Pages/Account/Login.cshtml.cs | 8 ++- .../Identity/Pages/Account/Register.cshtml.cs | 8 ++- .../RevokeAuthenticationEvents.cs | 6 +- src/Web/Controllers/ManageController.cs | 4 +- src/Web/Controllers/OrderController.cs | 7 ++- src/Web/Controllers/UserController.cs | 2 +- src/Web/Extensions/UrlHelperExtensions.cs | 2 +- .../Features/MyOrders/GetMyOrdersHandler.cs | 2 +- .../OrderDetails/GetOrderDetailsHandler.cs | 6 +- src/Web/HealthChecks/HomePageHealthCheck.cs | 4 +- src/Web/Pages/Basket/Checkout.cshtml.cs | 6 +- src/Web/Pages/Basket/Index.cshtml.cs | 7 ++- .../Components/BasketComponent/Basket.cs | 6 +- src/Web/Services/BasketViewModelService.cs | 2 +- .../Services/CatalogItemViewModelService.cs | 8 ++- src/Web/SlugifyParameterTransformer.cs | 6 +- .../Web/Pages/HomePageOnGet.cs | 4 +- .../GetByIdWithItemsAsync.cs | 2 +- .../CatalogItemTests/UpdateDetails.cs | 55 ------------------- .../ApplicationCore/Extensions/TestParent.cs | 27 +++++---- .../BasketServiceTests/AddItemToBasket.cs | 11 ++-- .../BasketServiceTests/DeleteBasket.cs | 3 +- .../BasketServiceTests/SetQuantities.cs | 34 ------------ .../BasketServiceTests/TransferBasket.cs | 35 ++++-------- .../CatalogFilterPaginatedSpecification.cs | 12 +--- .../CatalogFilterSpecification.cs | 4 +- .../CatalogItemsSpecification.cs | 8 +-- .../CustomerOrdersWithItemsSpecification.cs | 13 ++--- .../OrdersTests/GetOrderDetails.cs | 2 +- 51 files changed, 168 insertions(+), 256 deletions(-) delete mode 100644 tests/UnitTests/ApplicationCore/Entities/CatalogItemTests/UpdateDetails.cs delete mode 100644 tests/UnitTests/ApplicationCore/Services/BasketServiceTests/SetQuantities.cs diff --git a/src/ApplicationCore/ApplicationCore.csproj b/src/ApplicationCore/ApplicationCore.csproj index 9a54122..2d2c5cd 100644 --- a/src/ApplicationCore/ApplicationCore.csproj +++ b/src/ApplicationCore/ApplicationCore.csproj @@ -3,10 +3,12 @@ net6.0 Microsoft.eShopWeb.ApplicationCore + enable + diff --git a/src/ApplicationCore/CatalogSettings.cs b/src/ApplicationCore/CatalogSettings.cs index 5ea8bd2..bd5a0b6 100644 --- a/src/ApplicationCore/CatalogSettings.cs +++ b/src/ApplicationCore/CatalogSettings.cs @@ -2,5 +2,5 @@ public class CatalogSettings { - public string CatalogBaseUrl { get; set; } + public string? CatalogBaseUrl { get; set; } } diff --git a/src/ApplicationCore/Entities/BasketAggregate/Basket.cs b/src/ApplicationCore/Entities/BasketAggregate/Basket.cs index 71ab4ee..c49bae4 100644 --- a/src/ApplicationCore/Entities/BasketAggregate/Basket.cs +++ b/src/ApplicationCore/Entities/BasketAggregate/Basket.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Ardalis.GuardClauses; using Microsoft.eShopWeb.ApplicationCore.Interfaces; namespace Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; @@ -25,7 +26,7 @@ public class Basket : BaseEntity, IAggregateRoot _items.Add(new BasketItem(catalogItemId, quantity, unitPrice)); return; } - var existingItem = Items.FirstOrDefault(i => i.CatalogItemId == catalogItemId); + var existingItem = Items.First(i => i.CatalogItemId == catalogItemId); existingItem.AddQuantity(quantity); } diff --git a/src/ApplicationCore/Entities/BuyerAggregate/Buyer.cs b/src/ApplicationCore/Entities/BuyerAggregate/Buyer.cs index 72f7f1a..8a553f1 100644 --- a/src/ApplicationCore/Entities/BuyerAggregate/Buyer.cs +++ b/src/ApplicationCore/Entities/BuyerAggregate/Buyer.cs @@ -12,10 +12,8 @@ public class Buyer : BaseEntity, IAggregateRoot public IEnumerable PaymentMethods => _paymentMethods.AsReadOnly(); - private Buyer() - { - // required by EF - } + #pragma warning disable CS8618 // Required by Entity Framework + private Buyer() { } public Buyer(string identity) : this() { diff --git a/src/ApplicationCore/Entities/BuyerAggregate/PaymentMethod.cs b/src/ApplicationCore/Entities/BuyerAggregate/PaymentMethod.cs index f4d2e54..97ed93d 100644 --- a/src/ApplicationCore/Entities/BuyerAggregate/PaymentMethod.cs +++ b/src/ApplicationCore/Entities/BuyerAggregate/PaymentMethod.cs @@ -2,7 +2,7 @@ public class PaymentMethod : BaseEntity { - public string Alias { get; private set; } - public string CardId { get; private set; } // actual card data must be stored in a PCI compliant system, like Stripe - public string Last4 { get; private set; } + public string? Alias { get; private set; } + public string? CardId { get; private set; } // actual card data must be stored in a PCI compliant system, like Stripe + public string? Last4 { get; private set; } } diff --git a/src/ApplicationCore/Entities/CatalogItem.cs b/src/ApplicationCore/Entities/CatalogItem.cs index abf5a02..1b9534d 100644 --- a/src/ApplicationCore/Entities/CatalogItem.cs +++ b/src/ApplicationCore/Entities/CatalogItem.cs @@ -11,9 +11,9 @@ public class CatalogItem : BaseEntity, IAggregateRoot public decimal Price { get; private set; } public string PictureUri { get; private set; } public int CatalogTypeId { get; private set; } - public CatalogType CatalogType { get; private set; } + public CatalogType? CatalogType { get; private set; } public int CatalogBrandId { get; private set; } - public CatalogBrand CatalogBrand { get; private set; } + public CatalogBrand? CatalogBrand { get; private set; } public CatalogItem(int catalogTypeId, int catalogBrandId, @@ -30,15 +30,15 @@ public class CatalogItem : BaseEntity, IAggregateRoot PictureUri = pictureUri; } - public void UpdateDetails(string name, string description, decimal price) + public void UpdateDetails(CatalogItemDetails details) { - Guard.Against.NullOrEmpty(name, nameof(name)); - Guard.Against.NullOrEmpty(description, nameof(description)); - Guard.Against.NegativeOrZero(price, nameof(price)); + Guard.Against.NullOrEmpty(details.Name, nameof(details.Name)); + Guard.Against.NullOrEmpty(details.Description, nameof(details.Description)); + Guard.Against.NegativeOrZero(details.Price, nameof(details.Price)); - Name = name; - Description = description; - Price = price; + Name = details.Name; + Description = details.Description; + Price = details.Price; } public void UpdateBrand(int catalogBrandId) @@ -62,4 +62,18 @@ public class CatalogItem : BaseEntity, IAggregateRoot } PictureUri = $"images\\products\\{pictureName}?{new DateTime().Ticks}"; } + + public readonly record struct CatalogItemDetails + { + public string? Name { get; } + public string? Description { get; } + public decimal Price { get; } + + public CatalogItemDetails(string? name, string? description, decimal price) + { + Name = name; + Description = description; + Price = price; + } + } } diff --git a/src/ApplicationCore/Entities/OrderAggregate/Address.cs b/src/ApplicationCore/Entities/OrderAggregate/Address.cs index 65bd261..8b651cd 100644 --- a/src/ApplicationCore/Entities/OrderAggregate/Address.cs +++ b/src/ApplicationCore/Entities/OrderAggregate/Address.cs @@ -12,6 +12,7 @@ public class Address // ValueObject public string ZipCode { get; private set; } + #pragma warning disable CS8618 // Required by Entity Framework private Address() { } public Address(string street, string city, string state, string country, string zipcode) diff --git a/src/ApplicationCore/Entities/OrderAggregate/CatalogItemOrdered.cs b/src/ApplicationCore/Entities/OrderAggregate/CatalogItemOrdered.cs index 98cce22..59d1adb 100644 --- a/src/ApplicationCore/Entities/OrderAggregate/CatalogItemOrdered.cs +++ b/src/ApplicationCore/Entities/OrderAggregate/CatalogItemOrdered.cs @@ -19,10 +19,8 @@ public class CatalogItemOrdered // ValueObject PictureUri = pictureUri; } - private CatalogItemOrdered() - { - // required by EF - } + #pragma warning disable CS8618 // Required by Entity Framework + private CatalogItemOrdered() {} public int CatalogItemId { get; private set; } public string ProductName { get; private set; } diff --git a/src/ApplicationCore/Entities/OrderAggregate/Order.cs b/src/ApplicationCore/Entities/OrderAggregate/Order.cs index 53c587e..ca9a86e 100644 --- a/src/ApplicationCore/Entities/OrderAggregate/Order.cs +++ b/src/ApplicationCore/Entities/OrderAggregate/Order.cs @@ -7,16 +7,12 @@ namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate; public class Order : BaseEntity, IAggregateRoot { - private Order() - { - // required by EF - } + #pragma warning disable CS8618 // Required by Entity Framework + private Order() {} public Order(string buyerId, Address shipToAddress, List items) { Guard.Against.NullOrEmpty(buyerId, nameof(buyerId)); - Guard.Against.Null(shipToAddress, nameof(shipToAddress)); - Guard.Against.Null(items, nameof(items)); BuyerId = buyerId; ShipToAddress = shipToAddress; diff --git a/src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs b/src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs index fec2582..43054d1 100644 --- a/src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs +++ b/src/ApplicationCore/Entities/OrderAggregate/OrderItem.cs @@ -6,10 +6,8 @@ public class OrderItem : BaseEntity public decimal UnitPrice { get; private set; } public int Units { get; private set; } - private OrderItem() - { - // required by EF - } + #pragma warning disable CS8618 // Required by Entity Framework + private OrderItem() {} public OrderItem(CatalogItemOrdered itemOrdered, decimal unitPrice, int units) { diff --git a/src/ApplicationCore/Extensions/GuardExtensions.cs b/src/ApplicationCore/Extensions/GuardExtensions.cs index 06a781a..138602f 100644 --- a/src/ApplicationCore/Extensions/GuardExtensions.cs +++ b/src/ApplicationCore/Extensions/GuardExtensions.cs @@ -7,12 +7,6 @@ namespace Ardalis.GuardClauses; public static class BasketGuards { - public static void NullBasket(this IGuardClause guardClause, int basketId, Basket basket) - { - if (basket == null) - throw new BasketNotFoundException(basketId); - } - public static void EmptyBasketOnCheckout(this IGuardClause guardClause, IReadOnlyCollection basketItems) { if (!basketItems.Any()) diff --git a/src/ApplicationCore/Extensions/JsonExtensions.cs b/src/ApplicationCore/Extensions/JsonExtensions.cs index 1a2b8e0..622bcc9 100644 --- a/src/ApplicationCore/Extensions/JsonExtensions.cs +++ b/src/ApplicationCore/Extensions/JsonExtensions.cs @@ -9,7 +9,7 @@ public static class JsonExtensions PropertyNameCaseInsensitive = true }; - public static T FromJson(this string json) => + public static T? FromJson(this string json) => JsonSerializer.Deserialize(json, _jsonOptions); public static string ToJson(this T obj) => diff --git a/src/ApplicationCore/Interfaces/IBasketService.cs b/src/ApplicationCore/Interfaces/IBasketService.cs index 4dbdf9f..204f5f1 100644 --- a/src/ApplicationCore/Interfaces/IBasketService.cs +++ b/src/ApplicationCore/Interfaces/IBasketService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Ardalis.Result; using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; namespace Microsoft.eShopWeb.ApplicationCore.Interfaces; @@ -8,6 +9,6 @@ public interface IBasketService { Task TransferBasketAsync(string anonymousId, string userName); Task AddItemToBasket(string username, int catalogItemId, decimal price, int quantity = 1); - Task SetQuantities(int basketId, Dictionary quantities); + Task> SetQuantities(int basketId, Dictionary quantities); Task DeleteBasketAsync(int basketId); } diff --git a/src/ApplicationCore/Services/BasketService.cs b/src/ApplicationCore/Services/BasketService.cs index 167c1cb..ec810f3 100644 --- a/src/ApplicationCore/Services/BasketService.cs +++ b/src/ApplicationCore/Services/BasketService.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Ardalis.GuardClauses; +using Ardalis.Result; using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; using Microsoft.eShopWeb.ApplicationCore.Interfaces; using Microsoft.eShopWeb.ApplicationCore.Specifications; @@ -22,7 +23,7 @@ public class BasketService : IBasketService public async Task AddItemToBasket(string username, int catalogItemId, decimal price, int quantity = 1) { var basketSpec = new BasketWithItemsSpecification(username); - var basket = await _basketRepository.GetBySpecAsync(basketSpec); + var basket = await _basketRepository.FirstOrDefaultAsync(basketSpec); if (basket == null) { @@ -39,15 +40,15 @@ public class BasketService : IBasketService public async Task DeleteBasketAsync(int basketId) { var basket = await _basketRepository.GetByIdAsync(basketId); + Guard.Against.Null(basket, nameof(basket)); await _basketRepository.DeleteAsync(basket); } - public async Task SetQuantities(int basketId, Dictionary quantities) + public async Task> SetQuantities(int basketId, Dictionary quantities) { - Guard.Against.Null(quantities, nameof(quantities)); var basketSpec = new BasketWithItemsSpecification(basketId); - var basket = await _basketRepository.GetBySpecAsync(basketSpec); - Guard.Against.NullBasket(basketId, basket); + var basket = await _basketRepository.FirstOrDefaultAsync(basketSpec); + if (basket == null) return Result.NotFound(); foreach (var item in basket.Items) { @@ -64,13 +65,11 @@ public class BasketService : IBasketService public async Task TransferBasketAsync(string anonymousId, string userName) { - Guard.Against.NullOrEmpty(anonymousId, nameof(anonymousId)); - Guard.Against.NullOrEmpty(userName, nameof(userName)); var anonymousBasketSpec = new BasketWithItemsSpecification(anonymousId); - var anonymousBasket = await _basketRepository.GetBySpecAsync(anonymousBasketSpec); + var anonymousBasket = await _basketRepository.FirstOrDefaultAsync(anonymousBasketSpec); if (anonymousBasket == null) return; var userBasketSpec = new BasketWithItemsSpecification(userName); - var userBasket = await _basketRepository.GetBySpecAsync(userBasketSpec); + var userBasket = await _basketRepository.FirstOrDefaultAsync(userBasketSpec); if (userBasket == null) { userBasket = new Basket(userName); diff --git a/src/ApplicationCore/Services/OrderService.cs b/src/ApplicationCore/Services/OrderService.cs index 1f38523..3bc88f1 100644 --- a/src/ApplicationCore/Services/OrderService.cs +++ b/src/ApplicationCore/Services/OrderService.cs @@ -30,9 +30,9 @@ public class OrderService : IOrderService public async Task CreateOrderAsync(int basketId, Address shippingAddress) { var basketSpec = new BasketWithItemsSpecification(basketId); - var basket = await _basketRepository.GetBySpecAsync(basketSpec); + var basket = await _basketRepository.FirstOrDefaultAsync(basketSpec); - Guard.Against.NullBasket(basketId, basket); + Guard.Against.Null(basket, nameof(basket)); Guard.Against.EmptyBasketOnCheckout(basket.Items); var catalogItemsSpecification = new CatalogItemsSpecification(basket.Items.Select(item => item.CatalogItemId).ToArray()); diff --git a/src/Infrastructure/Data/CatalogContext.cs b/src/Infrastructure/Data/CatalogContext.cs index f9f340a..fc2e4b6 100644 --- a/src/Infrastructure/Data/CatalogContext.cs +++ b/src/Infrastructure/Data/CatalogContext.cs @@ -8,9 +8,8 @@ namespace Microsoft.eShopWeb.Infrastructure.Data; public class CatalogContext : DbContext { - public CatalogContext(DbContextOptions options) : base(options) - { - } + #pragma warning disable CS8618 // Required by Entity Framework + public CatalogContext(DbContextOptions options) : base(options) {} public DbSet Baskets { get; set; } public DbSet CatalogItems { get; set; } diff --git a/src/Infrastructure/Data/Config/BasketConfiguration.cs b/src/Infrastructure/Data/Config/BasketConfiguration.cs index d66fcf4..96c3e07 100644 --- a/src/Infrastructure/Data/Config/BasketConfiguration.cs +++ b/src/Infrastructure/Data/Config/BasketConfiguration.cs @@ -9,7 +9,7 @@ public class BasketConfiguration : IEntityTypeConfiguration public void Configure(EntityTypeBuilder builder) { var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + navigation?.SetPropertyAccessMode(PropertyAccessMode.Field); builder.Property(b => b.BuyerId) .IsRequired() diff --git a/src/Infrastructure/Data/Config/OrderConfiguration.cs b/src/Infrastructure/Data/Config/OrderConfiguration.cs index 3b5a8c3..b169237 100644 --- a/src/Infrastructure/Data/Config/OrderConfiguration.cs +++ b/src/Infrastructure/Data/Config/OrderConfiguration.cs @@ -10,7 +10,7 @@ public class OrderConfiguration : IEntityTypeConfiguration { var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems)); - navigation.SetPropertyAccessMode(PropertyAccessMode.Field); + navigation?.SetPropertyAccessMode(PropertyAccessMode.Field); builder.Property(b => b.BuyerId) .IsRequired() diff --git a/src/Infrastructure/Data/FileItem.cs b/src/Infrastructure/Data/FileItem.cs index 0229b88..329c041 100644 --- a/src/Infrastructure/Data/FileItem.cs +++ b/src/Infrastructure/Data/FileItem.cs @@ -2,10 +2,10 @@ public class FileItem { - public string FileName { get; set; } - public string Url { get; set; } + public string? FileName { get; set; } + public string? Url { get; set; } public long Size { get; set; } - public string Ext { get; set; } - public string Type { get; set; } - public string DataBase64 { get; set; } + public string? Ext { get; set; } + public string? Type { get; set; } + public string? DataBase64 { get; set; } } diff --git a/src/Infrastructure/Infrastructure.csproj b/src/Infrastructure/Infrastructure.csproj index 4672210..a0be8bb 100644 --- a/src/Infrastructure/Infrastructure.csproj +++ b/src/Infrastructure/Infrastructure.csproj @@ -3,6 +3,7 @@ net6.0 Microsoft.eShopWeb.Infrastructure + enable diff --git a/src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.cs b/src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.cs index 7bc715b..2ce0e6c 100644 --- a/src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.cs +++ b/src/PublicApi/CatalogItemEndpoints/UpdateCatalogItemEndpoint.cs @@ -41,8 +41,9 @@ public class UpdateCatalogItemEndpoint : IEndpoint5b662463-1efd-4bae-bde4-befe0be3e8ff Linux ..\.. + enable diff --git a/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs b/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs index 87f7962..fdf4b1f 100644 --- a/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs +++ b/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Ardalis.GuardClauses; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -73,17 +74,17 @@ public class LoginModel : PageModel // This doesn't count login failures towards account lockout // To enable password failures to trigger account lockout, set lockoutOnFailure: true //var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true); - var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, false, true); + var result = await _signInManager.PasswordSignInAsync(Input?.Email, Input?.Password, false, true); if (result.Succeeded) { _logger.LogInformation("User logged in."); - await TransferAnonymousBasketToUserAsync(Input.Email); + await TransferAnonymousBasketToUserAsync(Input?.Email); return LocalRedirect(returnUrl); } if (result.RequiresTwoFactor) { - return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe }); + return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input?.RememberMe }); } if (result.IsLockedOut) { @@ -108,6 +109,7 @@ public class LoginModel : PageModel var anonymousId = Request.Cookies[Constants.BASKET_COOKIENAME]; if (Guid.TryParse(anonymousId, out var _)) { + Guard.Against.NullOrEmpty(userName, nameof(userName)); await _basketService.TransferBasketAsync(anonymousId, userName); } Response.Cookies.Delete(Constants.BASKET_COOKIENAME); diff --git a/src/Web/Areas/Identity/Pages/Account/Register.cshtml.cs b/src/Web/Areas/Identity/Pages/Account/Register.cshtml.cs index 5991039..ce66e9e 100644 --- a/src/Web/Areas/Identity/Pages/Account/Register.cshtml.cs +++ b/src/Web/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Text.Encodings.Web; using System.Threading.Tasks; +using Ardalis.GuardClauses; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI.Services; @@ -67,8 +68,8 @@ public class RegisterModel : PageModel returnUrl = returnUrl ?? Url.Content("~/"); if (ModelState.IsValid) { - var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email }; - var result = await _userManager.CreateAsync(user, Input.Password); + var user = new ApplicationUser { UserName = Input?.Email, Email = Input?.Email }; + var result = await _userManager.CreateAsync(user, Input?.Password); if (result.Succeeded) { _logger.LogInformation("User created a new account with password."); @@ -80,7 +81,8 @@ public class RegisterModel : PageModel values: new { userId = user.Id, code = code }, protocol: Request.Scheme); - await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", + Guard.Against.Null(callbackUrl, nameof(callbackUrl)); + await _emailSender.SendEmailAsync(Input?.Email, "Confirm your email", $"Please confirm your account by clicking here."); await _signInManager.SignInAsync(user, isPersistent: false); diff --git a/src/Web/Configuration/RevokeAuthenticationEvents.cs b/src/Web/Configuration/RevokeAuthenticationEvents.cs index 0e29163..09ba74a 100644 --- a/src/Web/Configuration/RevokeAuthenticationEvents.cs +++ b/src/Web/Configuration/RevokeAuthenticationEvents.cs @@ -22,12 +22,12 @@ public class RevokeAuthenticationEvents : CookieAuthenticationEvents public override async Task ValidatePrincipal(CookieValidatePrincipalContext context) { - var userId = context.Principal.Claims.First(c => c.Type == ClaimTypes.Name); + var userId = context.Principal?.Claims.First(c => c.Type == ClaimTypes.Name); var identityKey = context.Request.Cookies[ConfigureCookieSettings.IdentifierCookieName]; - if (_cache.TryGetValue($"{userId.Value}:{identityKey}", out var revokeKeys)) + if (_cache.TryGetValue($"{userId?.Value}:{identityKey}", out var revokeKeys)) { - _logger.LogDebug($"Access has been revoked for: {userId.Value}."); + _logger.LogDebug($"Access has been revoked for: {userId?.Value}."); context.RejectPrincipal(); await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); } diff --git a/src/Web/Controllers/ManageController.cs b/src/Web/Controllers/ManageController.cs index 8bf6d28..779fa36 100644 --- a/src/Web/Controllers/ManageController.cs +++ b/src/Web/Controllers/ManageController.cs @@ -1,5 +1,6 @@ using System.Text; using System.Text.Encodings.Web; +using Ardalis.GuardClauses; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; @@ -119,6 +120,7 @@ public class ManageController : Controller var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme); + Guard.Against.Null(callbackUrl, nameof(callbackUrl)); var email = user.Email; await _emailSender.SendEmailConfirmationAsync(email, callbackUrl); @@ -405,7 +407,7 @@ public class ManageController : Controller } // Strip spaces and hypens - var verificationCode = model.Code.Replace(" ", string.Empty).Replace("-", string.Empty); + var verificationCode = model.Code?.Replace(" ", string.Empty).Replace("-", string.Empty); var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync( user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); diff --git a/src/Web/Controllers/OrderController.cs b/src/Web/Controllers/OrderController.cs index f8e4093..5a2e1e1 100644 --- a/src/Web/Controllers/OrderController.cs +++ b/src/Web/Controllers/OrderController.cs @@ -1,4 +1,5 @@ -using MediatR; +using Ardalis.GuardClauses; +using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.eShopWeb.Web.Features.MyOrders; @@ -20,7 +21,8 @@ public class OrderController : Controller [HttpGet] public async Task MyOrders() - { + { + Guard.Against.Null(User?.Identity?.Name, nameof(User.Identity.Name)); var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name)); return View(viewModel); @@ -29,6 +31,7 @@ public class OrderController : Controller [HttpGet("{orderId}")] public async Task Detail(int orderId) { + Guard.Against.Null(User?.Identity?.Name, nameof(User.Identity.Name)); var viewModel = await _mediator.Send(new GetOrderDetails(User.Identity.Name, orderId)); if (viewModel == null) diff --git a/src/Web/Controllers/UserController.cs b/src/Web/Controllers/UserController.cs index 6ce1818..05d55c2 100644 --- a/src/Web/Controllers/UserController.cs +++ b/src/Web/Controllers/UserController.cs @@ -28,7 +28,7 @@ public class UserController : ControllerBase private async Task CreateUserInfo(ClaimsPrincipal claimsPrincipal) { - if (!claimsPrincipal.Identity.IsAuthenticated) + if (claimsPrincipal.Identity == null || claimsPrincipal.Identity.Name == null || !claimsPrincipal.Identity.IsAuthenticated) { return UserInfo.Anonymous; } diff --git a/src/Web/Extensions/UrlHelperExtensions.cs b/src/Web/Extensions/UrlHelperExtensions.cs index 75ea598..e643e69 100644 --- a/src/Web/Extensions/UrlHelperExtensions.cs +++ b/src/Web/Extensions/UrlHelperExtensions.cs @@ -2,7 +2,7 @@ public static class UrlHelperExtensions { - public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme) + public static string? EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme) { return urlHelper.Action( action: "GET", diff --git a/src/Web/Features/MyOrders/GetMyOrdersHandler.cs b/src/Web/Features/MyOrders/GetMyOrdersHandler.cs index a22961f..6cd64af 100644 --- a/src/Web/Features/MyOrders/GetMyOrdersHandler.cs +++ b/src/Web/Features/MyOrders/GetMyOrdersHandler.cs @@ -28,7 +28,7 @@ public class GetMyOrdersHandler : IRequestHandler new OrderViewModel { OrderDate = o.OrderDate, - OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel() + OrderItems = o.OrderItems.Select(oi => new OrderItemViewModel() { PictureUrl = oi.ItemOrdered.PictureUri, ProductId = oi.ItemOrdered.CatalogItemId, diff --git a/src/Web/Features/OrderDetails/GetOrderDetailsHandler.cs b/src/Web/Features/OrderDetails/GetOrderDetailsHandler.cs index 4fa7e54..dab5050 100644 --- a/src/Web/Features/OrderDetails/GetOrderDetailsHandler.cs +++ b/src/Web/Features/OrderDetails/GetOrderDetailsHandler.cs @@ -9,7 +9,7 @@ using Microsoft.eShopWeb.Web.ViewModels; namespace Microsoft.eShopWeb.Web.Features.OrderDetails; -public class GetOrderDetailsHandler : IRequestHandler +public class GetOrderDetailsHandler : IRequestHandler { private readonly IReadRepository _orderRepository; @@ -18,11 +18,11 @@ public class GetOrderDetailsHandler : IRequestHandler Handle(GetOrderDetails request, + public async Task Handle(GetOrderDetails request, CancellationToken cancellationToken) { var spec = new OrderWithItemsByIdSpec(request.OrderId); - var order = await _orderRepository.GetBySpecAsync(spec, cancellationToken); + var order = await _orderRepository.FirstOrDefaultAsync(spec, cancellationToken); if (order == null) { diff --git a/src/Web/HealthChecks/HomePageHealthCheck.cs b/src/Web/HealthChecks/HomePageHealthCheck.cs index 0579dd7..0896954 100644 --- a/src/Web/HealthChecks/HomePageHealthCheck.cs +++ b/src/Web/HealthChecks/HomePageHealthCheck.cs @@ -19,8 +19,8 @@ public class HomePageHealthCheck : IHealthCheck HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken)) { - var request = _httpContextAccessor.HttpContext.Request; - string myUrl = request.Scheme + "://" + request.Host.ToString(); + var request = _httpContextAccessor.HttpContext?.Request; + string myUrl = request?.Scheme + "://" + request?.Host.ToString(); var client = new HttpClient(); var response = await client.GetAsync(myUrl); diff --git a/src/Web/Pages/Basket/Checkout.cshtml.cs b/src/Web/Pages/Basket/Checkout.cshtml.cs index 37d00a7..ee54592 100644 --- a/src/Web/Pages/Basket/Checkout.cshtml.cs +++ b/src/Web/Pages/Basket/Checkout.cshtml.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Authorization; +using Ardalis.GuardClauses; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; @@ -68,6 +69,7 @@ public class CheckoutModel : PageModel private async Task SetBasketModelAsync() { + Guard.Against.Null(User?.Identity?.Name, nameof(User.Identity.Name)); if (_signInManager.IsSignedIn(HttpContext.User)) { BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(User.Identity.Name); @@ -75,7 +77,7 @@ public class CheckoutModel : PageModel else { GetOrSetBasketCookieAndUserName(); - BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(_username); + BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(_username!); } } diff --git a/src/Web/Pages/Basket/Index.cshtml.cs b/src/Web/Pages/Basket/Index.cshtml.cs index 4eea893..11e7a69 100644 --- a/src/Web/Pages/Basket/Index.cshtml.cs +++ b/src/Web/Pages/Basket/Index.cshtml.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using Ardalis.GuardClauses; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.eShopWeb.ApplicationCore.Entities; using Microsoft.eShopWeb.ApplicationCore.Interfaces; @@ -66,11 +67,13 @@ public class IndexModel : PageModel private string GetOrSetBasketCookieAndUserName() { + Guard.Against.Null(Request.HttpContext.User.Identity, nameof(Request.HttpContext.User.Identity)); string? userName = null; if (Request.HttpContext.User.Identity.IsAuthenticated) { - return Request.HttpContext.User.Identity.Name; + Guard.Against.Null(Request.HttpContext.User.Identity.Name, nameof(Request.HttpContext.User.Identity.Name)); + return Request.HttpContext.User.Identity.Name!; } if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME)) diff --git a/src/Web/Pages/Shared/Components/BasketComponent/Basket.cs b/src/Web/Pages/Shared/Components/BasketComponent/Basket.cs index 519a335..986c7b8 100644 --- a/src/Web/Pages/Shared/Components/BasketComponent/Basket.cs +++ b/src/Web/Pages/Shared/Components/BasketComponent/Basket.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Ardalis.GuardClauses; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.eShopWeb.Infrastructure.Identity; @@ -33,17 +34,18 @@ public class Basket : ViewComponent { if (_signInManager.IsSignedIn(HttpContext.User)) { + Guard.Against.Null(User?.Identity?.Name, nameof(User.Identity.Name)); return await _basketService.CountTotalBasketItems(User.Identity.Name); } - string anonymousId = GetAnnonymousIdFromCookie(); + string? anonymousId = GetAnnonymousIdFromCookie(); if (anonymousId == null) return 0; return await _basketService.CountTotalBasketItems(anonymousId); } - private string GetAnnonymousIdFromCookie() + private string? GetAnnonymousIdFromCookie() { if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME)) { diff --git a/src/Web/Services/BasketViewModelService.cs b/src/Web/Services/BasketViewModelService.cs index e07ca61..658d571 100644 --- a/src/Web/Services/BasketViewModelService.cs +++ b/src/Web/Services/BasketViewModelService.cs @@ -28,7 +28,7 @@ public class BasketViewModelService : IBasketViewModelService public async Task GetOrCreateBasketForUser(string userName) { var basketSpec = new BasketWithItemsSpecification(userName); - var basket = (await _basketRepository.GetBySpecAsync(basketSpec)); + var basket = (await _basketRepository.FirstOrDefaultAsync(basketSpec)); if (basket == null) { diff --git a/src/Web/Services/CatalogItemViewModelService.cs b/src/Web/Services/CatalogItemViewModelService.cs index 7c66d61..4b63ad0 100644 --- a/src/Web/Services/CatalogItemViewModelService.cs +++ b/src/Web/Services/CatalogItemViewModelService.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using Ardalis.GuardClauses; using Microsoft.eShopWeb.ApplicationCore.Entities; using Microsoft.eShopWeb.ApplicationCore.Interfaces; using Microsoft.eShopWeb.Web.Interfaces; @@ -18,7 +18,11 @@ public class CatalogItemViewModelService : ICatalogItemViewModelService public async Task UpdateCatalogItem(CatalogItemViewModel viewModel) { var existingCatalogItem = await _catalogItemRepository.GetByIdAsync(viewModel.Id); - existingCatalogItem.UpdateDetails(viewModel.Name, existingCatalogItem.Description, viewModel.Price); + + Guard.Against.Null(existingCatalogItem, nameof(existingCatalogItem)); + + CatalogItem.CatalogItemDetails details = new(viewModel.Name, existingCatalogItem.Description, viewModel.Price); + existingCatalogItem.UpdateDetails(details); await _catalogItemRepository.UpdateAsync(existingCatalogItem); } } diff --git a/src/Web/SlugifyParameterTransformer.cs b/src/Web/SlugifyParameterTransformer.cs index 3946970..d2d1c56 100644 --- a/src/Web/SlugifyParameterTransformer.cs +++ b/src/Web/SlugifyParameterTransformer.cs @@ -5,11 +5,13 @@ namespace Microsoft.eShopWeb.Web; public class SlugifyParameterTransformer : IOutboundParameterTransformer { - public string? TransformOutbound(object value) + public string? TransformOutbound(object? value) { if (value == null) { return null; } + string? str = value.ToString(); + if (string.IsNullOrEmpty(str)) { return null; } // Slugify value - return Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2").ToLower(); + return Regex.Replace(str, "([a-z])([A-Z])", "$1-$2").ToLower(); } } diff --git a/tests/FunctionalTests/Web/Pages/HomePageOnGet.cs b/tests/FunctionalTests/Web/Pages/HomePageOnGet.cs index 76cc135..635d87b 100644 --- a/tests/FunctionalTests/Web/Pages/HomePageOnGet.cs +++ b/tests/FunctionalTests/Web/Pages/HomePageOnGet.cs @@ -1,6 +1,4 @@ -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.eShopWeb.FunctionalTests.Web; +using Microsoft.eShopWeb.FunctionalTests.Web; using Xunit; namespace Microsoft.eShopWeb.FunctionalTests.WebRazorPages; diff --git a/tests/IntegrationTests/Repositories/OrderRepositoryTests/GetByIdWithItemsAsync.cs b/tests/IntegrationTests/Repositories/OrderRepositoryTests/GetByIdWithItemsAsync.cs index a7d3e56..77e9190 100644 --- a/tests/IntegrationTests/Repositories/OrderRepositoryTests/GetByIdWithItemsAsync.cs +++ b/tests/IntegrationTests/Repositories/OrderRepositoryTests/GetByIdWithItemsAsync.cs @@ -49,7 +49,7 @@ public class GetByIdWithItemsAsync //Act var spec = new OrderWithItemsByIdSpec(secondOrderId); - var orderFromRepo = await _orderRepository.GetBySpecAsync(spec); + var orderFromRepo = await _orderRepository.FirstOrDefaultAsync(spec); //Assert Assert.Equal(secondOrderId, orderFromRepo.Id); diff --git a/tests/UnitTests/ApplicationCore/Entities/CatalogItemTests/UpdateDetails.cs b/tests/UnitTests/ApplicationCore/Entities/CatalogItemTests/UpdateDetails.cs deleted file mode 100644 index edc6f2b..0000000 --- a/tests/UnitTests/ApplicationCore/Entities/CatalogItemTests/UpdateDetails.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Xunit; - -namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Entities.CatalogItemTests; - -public class UpdateDetails -{ - private CatalogItem _testItem; - private int _validTypeId = 1; - private int _validBrandId = 2; - private string _validDescription = "test description"; - private string _validName = "test name"; - private decimal _validPrice = 1.23m; - private string _validUri = "/123"; - - public UpdateDetails() - { - _testItem = new CatalogItem(_validTypeId, _validBrandId, _validDescription, _validName, _validPrice, _validUri); - } - - [Fact] - public void ThrowsArgumentExceptionGivenEmptyName() - { - string newValue = ""; - Assert.Throws(() => _testItem.UpdateDetails(newValue, _validDescription, _validPrice)); - } - - [Fact] - public void ThrowsArgumentExceptionGivenEmptyDescription() - { - string newValue = ""; - Assert.Throws(() => _testItem.UpdateDetails(_validName, newValue, _validPrice)); - } - - [Fact] - public void ThrowsArgumentNullExceptionGivenNullName() - { - Assert.Throws(() => _testItem.UpdateDetails(null, _validDescription, _validPrice)); - } - - [Fact] - public void ThrowsArgumentNullExceptionGivenNullDescription() - { - Assert.Throws(() => _testItem.UpdateDetails(_validName, null, _validPrice)); - } - - [Theory] - [InlineData(0)] - [InlineData(-1.23)] - public void ThrowsArgumentExceptionGivenNonPositivePrice(decimal newPrice) - { - Assert.Throws(() => _testItem.UpdateDetails(_validName, _validDescription, newPrice)); - } -} diff --git a/tests/UnitTests/ApplicationCore/Extensions/TestParent.cs b/tests/UnitTests/ApplicationCore/Extensions/TestParent.cs index bd20e36..ec791b4 100644 --- a/tests/UnitTests/ApplicationCore/Extensions/TestParent.cs +++ b/tests/UnitTests/ApplicationCore/Extensions/TestParent.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Extensions; @@ -9,12 +6,22 @@ public class TestParent : IEquatable { public int Id { get; set; } - public string Name { get; set; } + public string? Name { get; set; } - public IEnumerable Children { get; set; } + public IEnumerable? Children { get; set; } - public bool Equals([AllowNull] TestParent other) => - other?.Id == Id && other?.Name == Name && - (other?.Children is null && Children is null || - (other?.Children?.Zip(Children)?.All(t => t.First?.Equals(t.Second) ?? false) ?? false)); + public bool Equals([AllowNull] TestParent other) + { + if (other?.Id == Id && other?.Name == Name) + { + if (Children is null) + { + return other?.Children is null; + } + + return other?.Children?.Zip(Children).All(t => t.First?.Equals(t.Second) ?? false) ?? false; + } + + return false; + } } diff --git a/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/AddItemToBasket.cs b/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/AddItemToBasket.cs index 7937d08..9f11114 100644 --- a/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/AddItemToBasket.cs +++ b/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/AddItemToBasket.cs @@ -12,19 +12,20 @@ public class AddItemToBasket { private readonly string _buyerId = "Test buyerId"; private readonly Mock> _mockBasketRepo = new(); + private readonly Mock> _mockLogger = new(); [Fact] public async Task InvokesBasketRepositoryGetBySpecAsyncOnce() { var basket = new Basket(_buyerId); basket.AddItem(1, It.IsAny(), It.IsAny()); - _mockBasketRepo.Setup(x => x.GetBySpecAsync(It.IsAny(), default)).ReturnsAsync(basket); + _mockBasketRepo.Setup(x => x.FirstOrDefaultAsync(It.IsAny(), default)).ReturnsAsync(basket); - var basketService = new BasketService(_mockBasketRepo.Object, null); + var basketService = new BasketService(_mockBasketRepo.Object, _mockLogger.Object); await basketService.AddItemToBasket(basket.BuyerId, 1, 1.50m); - _mockBasketRepo.Verify(x => x.GetBySpecAsync(It.IsAny(), default), Times.Once); + _mockBasketRepo.Verify(x => x.FirstOrDefaultAsync(It.IsAny(), default), Times.Once); } [Fact] @@ -32,9 +33,9 @@ public class AddItemToBasket { var basket = new Basket(_buyerId); basket.AddItem(1, It.IsAny(), It.IsAny()); - _mockBasketRepo.Setup(x => x.GetBySpecAsync(It.IsAny(), default)).ReturnsAsync(basket); + _mockBasketRepo.Setup(x => x.FirstOrDefaultAsync(It.IsAny(), default)).ReturnsAsync(basket); - var basketService = new BasketService(_mockBasketRepo.Object, null); + var basketService = new BasketService(_mockBasketRepo.Object, _mockLogger.Object); await basketService.AddItemToBasket(basket.BuyerId, 1, 1.50m); diff --git a/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/DeleteBasket.cs b/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/DeleteBasket.cs index 01eff04..cd94c3a 100644 --- a/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/DeleteBasket.cs +++ b/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/DeleteBasket.cs @@ -11,6 +11,7 @@ public class DeleteBasket { private readonly string _buyerId = "Test buyerId"; private readonly Mock> _mockBasketRepo = new(); + private readonly Mock> _mockLogger = new(); [Fact] public async Task ShouldInvokeBasketRepositoryDeleteAsyncOnce() @@ -20,7 +21,7 @@ public class DeleteBasket basket.AddItem(2, It.IsAny(), It.IsAny()); _mockBasketRepo.Setup(x => x.GetByIdAsync(It.IsAny(), default)) .ReturnsAsync(basket); - var basketService = new BasketService(_mockBasketRepo.Object, null); + var basketService = new BasketService(_mockBasketRepo.Object, _mockLogger.Object); await basketService.DeleteBasketAsync(It.IsAny()); diff --git a/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/SetQuantities.cs b/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/SetQuantities.cs deleted file mode 100644 index 57d4d6c..0000000 --- a/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/SetQuantities.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; -using Microsoft.eShopWeb.ApplicationCore.Exceptions; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.eShopWeb.ApplicationCore.Services; -using Moq; -using Xunit; - -namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Services.BasketServiceTests; - -public class SetQuantities -{ - private readonly int _invalidId = -1; - private readonly Mock> _mockBasketRepo = new(); - - [Fact] - public async Task ThrowsGivenInvalidBasketId() - { - var basketService = new BasketService(_mockBasketRepo.Object, null); - - await Assert.ThrowsAsync(async () => - await basketService.SetQuantities(_invalidId, new System.Collections.Generic.Dictionary())); - } - - [Fact] - public async Task ThrowsGivenNullQuantities() - { - var basketService = new BasketService(null, null); - - await Assert.ThrowsAsync(async () => - await basketService.SetQuantities(123, null)); - } -} diff --git a/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/TransferBasket.cs b/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/TransferBasket.cs index 390e9eb..c82c18d 100644 --- a/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/TransferBasket.cs +++ b/tests/UnitTests/ApplicationCore/Services/BasketServiceTests/TransferBasket.cs @@ -16,34 +16,19 @@ public class TransferBasket private readonly string _nonexistentUserBasketBuyerId = "newuser@microsoft.com"; private readonly string _existentUserBasketBuyerId = "testuser@microsoft.com"; private readonly Mock> _mockBasketRepo = new(); - - [Fact] - public async Task ThrowsGivenNullAnonymousId() - { - var basketService = new BasketService(null, null); - - await Assert.ThrowsAsync(async () => await basketService.TransferBasketAsync(null, "steve")); - } - - [Fact] - public async Task ThrowsGivenNullUserId() - { - var basketService = new BasketService(null, null); - - await Assert.ThrowsAsync(async () => await basketService.TransferBasketAsync("abcdefg", null)); - } + private readonly Mock> _mockLogger = new(); [Fact] public async Task InvokesBasketRepositoryFirstOrDefaultAsyncOnceIfAnonymousBasketNotExists() { var anonymousBasket = null as Basket; var userBasket = new Basket(_existentUserBasketBuyerId); - _mockBasketRepo.SetupSequence(x => x.GetBySpecAsync(It.IsAny(), default)) + _mockBasketRepo.SetupSequence(x => x.FirstOrDefaultAsync(It.IsAny(), default)) .ReturnsAsync(anonymousBasket) .ReturnsAsync(userBasket); - var basketService = new BasketService(_mockBasketRepo.Object, null); + var basketService = new BasketService(_mockBasketRepo.Object, _mockLogger.Object); await basketService.TransferBasketAsync(_nonexistentAnonymousBasketBuyerId, _existentUserBasketBuyerId); - _mockBasketRepo.Verify(x => x.GetBySpecAsync(It.IsAny(), default), Times.Once); + _mockBasketRepo.Verify(x => x.FirstOrDefaultAsync(It.IsAny(), default), Times.Once); } [Fact] @@ -55,10 +40,10 @@ public class TransferBasket var userBasket = new Basket(_existentUserBasketBuyerId); userBasket.AddItem(1, 10, 4); userBasket.AddItem(2, 99, 3); - _mockBasketRepo.SetupSequence(x => x.GetBySpecAsync(It.IsAny(), default)) + _mockBasketRepo.SetupSequence(x => x.FirstOrDefaultAsync(It.IsAny(), default)) .ReturnsAsync(anonymousBasket) .ReturnsAsync(userBasket); - var basketService = new BasketService(_mockBasketRepo.Object, null); + var basketService = new BasketService(_mockBasketRepo.Object, _mockLogger.Object); await basketService.TransferBasketAsync(_nonexistentAnonymousBasketBuyerId, _existentUserBasketBuyerId); _mockBasketRepo.Verify(x => x.UpdateAsync(userBasket, default), Times.Once); Assert.Equal(3, userBasket.Items.Count); @@ -72,10 +57,10 @@ public class TransferBasket { var anonymousBasket = new Basket(_existentAnonymousBasketBuyerId); var userBasket = new Basket(_existentUserBasketBuyerId); - _mockBasketRepo.SetupSequence(x => x.GetBySpecAsync(It.IsAny(), default)) + _mockBasketRepo.SetupSequence(x => x.FirstOrDefaultAsync(It.IsAny(), default)) .ReturnsAsync(anonymousBasket) .ReturnsAsync(userBasket); - var basketService = new BasketService(_mockBasketRepo.Object, null); + var basketService = new BasketService(_mockBasketRepo.Object, _mockLogger.Object); await basketService.TransferBasketAsync(_nonexistentAnonymousBasketBuyerId, _existentUserBasketBuyerId); _mockBasketRepo.Verify(x => x.UpdateAsync(userBasket, default), Times.Once); _mockBasketRepo.Verify(x => x.DeleteAsync(anonymousBasket, default), Times.Once); @@ -86,10 +71,10 @@ public class TransferBasket { var anonymousBasket = new Basket(_existentAnonymousBasketBuyerId); var userBasket = null as Basket; - _mockBasketRepo.SetupSequence(x => x.GetBySpecAsync(It.IsAny(), default)) + _mockBasketRepo.SetupSequence(x => x.FirstOrDefaultAsync(It.IsAny(), default)) .ReturnsAsync(anonymousBasket) .ReturnsAsync(userBasket); - var basketService = new BasketService(_mockBasketRepo.Object, null); + var basketService = new BasketService(_mockBasketRepo.Object, _mockLogger.Object); await basketService.TransferBasketAsync(_existentAnonymousBasketBuyerId, _nonexistentUserBasketBuyerId); _mockBasketRepo.Verify(x => x.AddAsync(It.Is(x => x.BuyerId == _nonexistentUserBasketBuyerId), default), Times.Once); } diff --git a/tests/UnitTests/ApplicationCore/Specifications/CatalogFilterPaginatedSpecification.cs b/tests/UnitTests/ApplicationCore/Specifications/CatalogFilterPaginatedSpecification.cs index c3d3828..5ac89a2 100644 --- a/tests/UnitTests/ApplicationCore/Specifications/CatalogFilterPaginatedSpecification.cs +++ b/tests/UnitTests/ApplicationCore/Specifications/CatalogFilterPaginatedSpecification.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using Microsoft.eShopWeb.ApplicationCore.Entities; +using Microsoft.eShopWeb.ApplicationCore.Entities; using Xunit; namespace Microsoft.eShopWeb.UnitTests.ApplicationCore.Specifications; @@ -12,9 +10,7 @@ public class CatalogFilterPaginatedSpecification { var spec = new eShopWeb.ApplicationCore.Specifications.CatalogFilterPaginatedSpecification(0, 10, null, null); - var result = GetTestCollection() - .AsQueryable() - .Where(spec.WhereExpressions.FirstOrDefault().Filter); + var result = spec.Evaluate(GetTestCollection()); Assert.NotNull(result); Assert.Equal(4, result.ToList().Count); @@ -25,9 +21,7 @@ public class CatalogFilterPaginatedSpecification { var spec = new eShopWeb.ApplicationCore.Specifications.CatalogFilterPaginatedSpecification(0, 10, 1, 1); - var result = GetTestCollection() - .AsQueryable() - .Where(spec.WhereExpressions.FirstOrDefault().Filter); + var result = spec.Evaluate(GetTestCollection()).ToList(); Assert.NotNull(result); Assert.Equal(2, result.ToList().Count); diff --git a/tests/UnitTests/ApplicationCore/Specifications/CatalogFilterSpecification.cs b/tests/UnitTests/ApplicationCore/Specifications/CatalogFilterSpecification.cs index 56b1e0d..cb065df 100644 --- a/tests/UnitTests/ApplicationCore/Specifications/CatalogFilterSpecification.cs +++ b/tests/UnitTests/ApplicationCore/Specifications/CatalogFilterSpecification.cs @@ -19,9 +19,7 @@ public class CatalogFilterSpecification { var spec = new eShopWeb.ApplicationCore.Specifications.CatalogFilterSpecification(brandId, typeId); - var result = GetTestItemCollection() - .AsQueryable() - .Where(spec.WhereExpressions.FirstOrDefault().Filter); + var result = spec.Evaluate(GetTestItemCollection()).ToList(); Assert.Equal(expectedCount, result.Count()); } diff --git a/tests/UnitTests/ApplicationCore/Specifications/CatalogItemsSpecification.cs b/tests/UnitTests/ApplicationCore/Specifications/CatalogItemsSpecification.cs index 0085ca7..3258322 100644 --- a/tests/UnitTests/ApplicationCore/Specifications/CatalogItemsSpecification.cs +++ b/tests/UnitTests/ApplicationCore/Specifications/CatalogItemsSpecification.cs @@ -14,9 +14,7 @@ public class CatalogItemsSpecification var catalogItemIds = new int[] { 1 }; var spec = new eShopWeb.ApplicationCore.Specifications.CatalogItemsSpecification(catalogItemIds); - var result = GetTestCollection() - .AsQueryable() - .Where(spec.WhereExpressions.FirstOrDefault().Filter); + var result = spec.Evaluate(GetTestCollection()).ToList(); Assert.NotNull(result); Assert.Single(result.ToList()); @@ -28,9 +26,7 @@ public class CatalogItemsSpecification var catalogItemIds = new int[] { 1, 3 }; var spec = new eShopWeb.ApplicationCore.Specifications.CatalogItemsSpecification(catalogItemIds); - var result = GetTestCollection() - .AsQueryable() - .Where(spec.WhereExpressions.FirstOrDefault().Filter); + var result = spec.Evaluate(GetTestCollection()).ToList(); Assert.NotNull(result); Assert.Equal(2, result.ToList().Count); diff --git a/tests/UnitTests/ApplicationCore/Specifications/CustomerOrdersWithItemsSpecification.cs b/tests/UnitTests/ApplicationCore/Specifications/CustomerOrdersWithItemsSpecification.cs index fe3281e..0a066d0 100644 --- a/tests/UnitTests/ApplicationCore/Specifications/CustomerOrdersWithItemsSpecification.cs +++ b/tests/UnitTests/ApplicationCore/Specifications/CustomerOrdersWithItemsSpecification.cs @@ -15,14 +15,12 @@ public class CustomerOrdersWithItemsSpecification { var spec = new eShopWeb.ApplicationCore.Specifications.CustomerOrdersWithItemsSpecification(_buyerId); - var result = GetTestCollection() - .AsQueryable() - .FirstOrDefault(spec.WhereExpressions.FirstOrDefault().Filter); + var result = spec.Evaluate(GetTestCollection()).FirstOrDefault(); Assert.NotNull(result); Assert.NotNull(result.OrderItems); Assert.Equal(1, result.OrderItems.Count); - Assert.NotNull(result.OrderItems.FirstOrDefault().ItemOrdered); + Assert.NotNull(result.OrderItems.FirstOrDefault()?.ItemOrdered); } [Fact] @@ -30,15 +28,12 @@ public class CustomerOrdersWithItemsSpecification { var spec = new eShopWeb.ApplicationCore.Specifications.CustomerOrdersWithItemsSpecification(_buyerId); - var result = GetTestCollection() - .AsQueryable() - .Where(spec.WhereExpressions.FirstOrDefault().Filter) - .ToList(); + var result = spec.Evaluate(GetTestCollection()).ToList(); Assert.NotNull(result); Assert.Equal(2, result.Count); Assert.Equal(1, result[0].OrderItems.Count); - Assert.NotNull(result[0].OrderItems.FirstOrDefault().ItemOrdered); + Assert.NotNull(result[0].OrderItems.FirstOrDefault()?.ItemOrdered); Assert.Equal(2, result[1].OrderItems.Count); Assert.NotNull(result[1].OrderItems.ToList()[0].ItemOrdered); Assert.NotNull(result[1].OrderItems.ToList()[1].ItemOrdered); diff --git a/tests/UnitTests/MediatorHandlers/OrdersTests/GetOrderDetails.cs b/tests/UnitTests/MediatorHandlers/OrdersTests/GetOrderDetails.cs index ec4cadd..aa19b83 100644 --- a/tests/UnitTests/MediatorHandlers/OrdersTests/GetOrderDetails.cs +++ b/tests/UnitTests/MediatorHandlers/OrdersTests/GetOrderDetails.cs @@ -22,7 +22,7 @@ public class GetOrderDetails Order order = new Order("buyerId", address, new List { item }); _mockOrderRepository = new Mock>(); - _mockOrderRepository.Setup(x => x.GetBySpecAsync(It.IsAny(), default)) + _mockOrderRepository.Setup(x => x.FirstOrDefaultAsync(It.IsAny(), default)) .ReturnsAsync(order); }