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
This commit is contained in:
committed by
GitHub
parent
aa6305eab1
commit
a72dd775ee
@@ -3,10 +3,12 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>Microsoft.eShopWeb.ApplicationCore</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ardalis.GuardClauses" Version="4.0.1" />
|
||||
<PackageReference Include="Ardalis.Result" Version="4.1.0" />
|
||||
<PackageReference Include="Ardalis.Specification" Version="6.1.0" />
|
||||
<PackageReference Include="MediatR" Version="10.0.1" />
|
||||
<PackageReference Include="System.Security.Claims" Version="4.3.0" />
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
|
||||
public class CatalogSettings
|
||||
{
|
||||
public string CatalogBaseUrl { get; set; }
|
||||
public string? CatalogBaseUrl { get; set; }
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,8 @@ public class Buyer : BaseEntity, IAggregateRoot
|
||||
|
||||
public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods.AsReadOnly();
|
||||
|
||||
private Buyer()
|
||||
{
|
||||
// required by EF
|
||||
}
|
||||
#pragma warning disable CS8618 // Required by Entity Framework
|
||||
private Buyer() { }
|
||||
|
||||
public Buyer(string identity) : this()
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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<OrderItem> items)
|
||||
{
|
||||
Guard.Against.NullOrEmpty(buyerId, nameof(buyerId));
|
||||
Guard.Against.Null(shipToAddress, nameof(shipToAddress));
|
||||
Guard.Against.Null(items, nameof(items));
|
||||
|
||||
BuyerId = buyerId;
|
||||
ShipToAddress = shipToAddress;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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<BasketItem> basketItems)
|
||||
{
|
||||
if (!basketItems.Any())
|
||||
|
||||
@@ -9,7 +9,7 @@ public static class JsonExtensions
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public static T FromJson<T>(this string json) =>
|
||||
public static T? FromJson<T>(this string json) =>
|
||||
JsonSerializer.Deserialize<T>(json, _jsonOptions);
|
||||
|
||||
public static string ToJson<T>(this T obj) =>
|
||||
|
||||
@@ -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<Basket> AddItemToBasket(string username, int catalogItemId, decimal price, int quantity = 1);
|
||||
Task<Basket> SetQuantities(int basketId, Dictionary<string, int> quantities);
|
||||
Task<Result<Basket>> SetQuantities(int basketId, Dictionary<string, int> quantities);
|
||||
Task DeleteBasketAsync(int basketId);
|
||||
}
|
||||
|
||||
@@ -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<Basket> 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<Basket> SetQuantities(int basketId, Dictionary<string, int> quantities)
|
||||
public async Task<Result<Basket>> SetQuantities(int basketId, Dictionary<string, int> 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<Basket>.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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -8,9 +8,8 @@ namespace Microsoft.eShopWeb.Infrastructure.Data;
|
||||
|
||||
public class CatalogContext : DbContext
|
||||
{
|
||||
public CatalogContext(DbContextOptions<CatalogContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
#pragma warning disable CS8618 // Required by Entity Framework
|
||||
public CatalogContext(DbContextOptions<CatalogContext> options) : base(options) {}
|
||||
|
||||
public DbSet<Basket> Baskets { get; set; }
|
||||
public DbSet<CatalogItem> CatalogItems { get; set; }
|
||||
|
||||
@@ -9,7 +9,7 @@ public class BasketConfiguration : IEntityTypeConfiguration<Basket>
|
||||
public void Configure(EntityTypeBuilder<Basket> builder)
|
||||
{
|
||||
var navigation = builder.Metadata.FindNavigation(nameof(Basket.Items));
|
||||
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
|
||||
navigation?.SetPropertyAccessMode(PropertyAccessMode.Field);
|
||||
|
||||
builder.Property(b => b.BuyerId)
|
||||
.IsRequired()
|
||||
|
||||
@@ -10,7 +10,7 @@ public class OrderConfiguration : IEntityTypeConfiguration<Order>
|
||||
{
|
||||
var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems));
|
||||
|
||||
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
|
||||
navigation?.SetPropertyAccessMode(PropertyAccessMode.Field);
|
||||
|
||||
builder.Property(b => b.BuyerId)
|
||||
.IsRequired()
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>Microsoft.eShopWeb.Infrastructure</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -42,7 +42,8 @@ public class UpdateCatalogItemEndpoint : IEndpoint<IResult, UpdateCatalogItemReq
|
||||
|
||||
var existingItem = await _itemRepository.GetByIdAsync(request.Id);
|
||||
|
||||
existingItem.UpdateDetails(request.Name, request.Description, request.Price);
|
||||
CatalogItem.CatalogItemDetails details = new(request.Name, request.Description, request.Price);
|
||||
existingItem.UpdateDetails(details);
|
||||
existingItem.UpdateBrand(request.CatalogBrandId);
|
||||
existingItem.UpdateType(request.CatalogTypeId);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<UserSecretsId>5b662463-1efd-4bae-bde4-befe0be3e8ff</UserSecretsId>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<DockerfileContext>..\..</DockerfileContext>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
|
||||
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -21,6 +22,7 @@ public class OrderController : Controller
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> 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<IActionResult> 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)
|
||||
|
||||
@@ -28,7 +28,7 @@ public class UserController : ControllerBase
|
||||
|
||||
private async Task<UserInfo> CreateUserInfo(ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
if (!claimsPrincipal.Identity.IsAuthenticated)
|
||||
if (claimsPrincipal.Identity == null || claimsPrincipal.Identity.Name == null || !claimsPrincipal.Identity.IsAuthenticated)
|
||||
{
|
||||
return UserInfo.Anonymous;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -28,7 +28,7 @@ public class GetMyOrdersHandler : IRequestHandler<GetMyOrders, IEnumerable<Order
|
||||
return orders.Select(o => 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,
|
||||
|
||||
@@ -9,7 +9,7 @@ using Microsoft.eShopWeb.Web.ViewModels;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Features.OrderDetails;
|
||||
|
||||
public class GetOrderDetailsHandler : IRequestHandler<GetOrderDetails, OrderViewModel>
|
||||
public class GetOrderDetailsHandler : IRequestHandler<GetOrderDetails, OrderViewModel?>
|
||||
{
|
||||
private readonly IReadRepository<Order> _orderRepository;
|
||||
|
||||
@@ -18,11 +18,11 @@ public class GetOrderDetailsHandler : IRequestHandler<GetOrderDetails, OrderView
|
||||
_orderRepository = orderRepository;
|
||||
}
|
||||
|
||||
public async Task<OrderViewModel> Handle(GetOrderDetails request,
|
||||
public async Task<OrderViewModel?> 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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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!);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -28,7 +28,7 @@ public class BasketViewModelService : IBasketViewModelService
|
||||
public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName)
|
||||
{
|
||||
var basketSpec = new BasketWithItemsSpecification(userName);
|
||||
var basket = (await _basketRepository.GetBySpecAsync(basketSpec));
|
||||
var basket = (await _basketRepository.FirstOrDefaultAsync(basketSpec));
|
||||
|
||||
if (basket == null)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<ArgumentException>(() => _testItem.UpdateDetails(newValue, _validDescription, _validPrice));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsArgumentExceptionGivenEmptyDescription()
|
||||
{
|
||||
string newValue = "";
|
||||
Assert.Throws<ArgumentException>(() => _testItem.UpdateDetails(_validName, newValue, _validPrice));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsArgumentNullExceptionGivenNullName()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => _testItem.UpdateDetails(null, _validDescription, _validPrice));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ThrowsArgumentNullExceptionGivenNullDescription()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => _testItem.UpdateDetails(_validName, null, _validPrice));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(-1.23)]
|
||||
public void ThrowsArgumentExceptionGivenNonPositivePrice(decimal newPrice)
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => _testItem.UpdateDetails(_validName, _validDescription, newPrice));
|
||||
}
|
||||
}
|
||||
@@ -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<TestParent>
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
public string? Name { get; set; }
|
||||
|
||||
public IEnumerable<TestChild> Children { get; set; }
|
||||
public IEnumerable<TestChild>? 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,19 +12,20 @@ public class AddItemToBasket
|
||||
{
|
||||
private readonly string _buyerId = "Test buyerId";
|
||||
private readonly Mock<IRepository<Basket>> _mockBasketRepo = new();
|
||||
private readonly Mock<IAppLogger<BasketService>> _mockLogger = new();
|
||||
|
||||
[Fact]
|
||||
public async Task InvokesBasketRepositoryGetBySpecAsyncOnce()
|
||||
{
|
||||
var basket = new Basket(_buyerId);
|
||||
basket.AddItem(1, It.IsAny<decimal>(), It.IsAny<int>());
|
||||
_mockBasketRepo.Setup(x => x.GetBySpecAsync(It.IsAny<BasketWithItemsSpecification>(), default)).ReturnsAsync(basket);
|
||||
_mockBasketRepo.Setup(x => x.FirstOrDefaultAsync(It.IsAny<BasketWithItemsSpecification>(), 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<BasketWithItemsSpecification>(), default), Times.Once);
|
||||
_mockBasketRepo.Verify(x => x.FirstOrDefaultAsync(It.IsAny<BasketWithItemsSpecification>(), default), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -32,9 +33,9 @@ public class AddItemToBasket
|
||||
{
|
||||
var basket = new Basket(_buyerId);
|
||||
basket.AddItem(1, It.IsAny<decimal>(), It.IsAny<int>());
|
||||
_mockBasketRepo.Setup(x => x.GetBySpecAsync(It.IsAny<BasketWithItemsSpecification>(), default)).ReturnsAsync(basket);
|
||||
_mockBasketRepo.Setup(x => x.FirstOrDefaultAsync(It.IsAny<BasketWithItemsSpecification>(), 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);
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ public class DeleteBasket
|
||||
{
|
||||
private readonly string _buyerId = "Test buyerId";
|
||||
private readonly Mock<IRepository<Basket>> _mockBasketRepo = new();
|
||||
private readonly Mock<IAppLogger<BasketService>> _mockLogger = new();
|
||||
|
||||
[Fact]
|
||||
public async Task ShouldInvokeBasketRepositoryDeleteAsyncOnce()
|
||||
@@ -20,7 +21,7 @@ public class DeleteBasket
|
||||
basket.AddItem(2, It.IsAny<decimal>(), It.IsAny<int>());
|
||||
_mockBasketRepo.Setup(x => x.GetByIdAsync(It.IsAny<int>(), default))
|
||||
.ReturnsAsync(basket);
|
||||
var basketService = new BasketService(_mockBasketRepo.Object, null);
|
||||
var basketService = new BasketService(_mockBasketRepo.Object, _mockLogger.Object);
|
||||
|
||||
await basketService.DeleteBasketAsync(It.IsAny<int>());
|
||||
|
||||
|
||||
@@ -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<IRepository<Basket>> _mockBasketRepo = new();
|
||||
|
||||
[Fact]
|
||||
public async Task ThrowsGivenInvalidBasketId()
|
||||
{
|
||||
var basketService = new BasketService(_mockBasketRepo.Object, null);
|
||||
|
||||
await Assert.ThrowsAsync<BasketNotFoundException>(async () =>
|
||||
await basketService.SetQuantities(_invalidId, new System.Collections.Generic.Dictionary<string, int>()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ThrowsGivenNullQuantities()
|
||||
{
|
||||
var basketService = new BasketService(null, null);
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () =>
|
||||
await basketService.SetQuantities(123, null));
|
||||
}
|
||||
}
|
||||
@@ -16,34 +16,19 @@ public class TransferBasket
|
||||
private readonly string _nonexistentUserBasketBuyerId = "newuser@microsoft.com";
|
||||
private readonly string _existentUserBasketBuyerId = "testuser@microsoft.com";
|
||||
private readonly Mock<IRepository<Basket>> _mockBasketRepo = new();
|
||||
|
||||
[Fact]
|
||||
public async Task ThrowsGivenNullAnonymousId()
|
||||
{
|
||||
var basketService = new BasketService(null, null);
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await basketService.TransferBasketAsync(null, "steve"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ThrowsGivenNullUserId()
|
||||
{
|
||||
var basketService = new BasketService(null, null);
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await basketService.TransferBasketAsync("abcdefg", null));
|
||||
}
|
||||
private readonly Mock<IAppLogger<BasketService>> _mockLogger = new();
|
||||
|
||||
[Fact]
|
||||
public async Task InvokesBasketRepositoryFirstOrDefaultAsyncOnceIfAnonymousBasketNotExists()
|
||||
{
|
||||
var anonymousBasket = null as Basket;
|
||||
var userBasket = new Basket(_existentUserBasketBuyerId);
|
||||
_mockBasketRepo.SetupSequence(x => x.GetBySpecAsync(It.IsAny<BasketWithItemsSpecification>(), default))
|
||||
_mockBasketRepo.SetupSequence(x => x.FirstOrDefaultAsync(It.IsAny<BasketWithItemsSpecification>(), 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<BasketWithItemsSpecification>(), default), Times.Once);
|
||||
_mockBasketRepo.Verify(x => x.FirstOrDefaultAsync(It.IsAny<BasketWithItemsSpecification>(), 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<BasketWithItemsSpecification>(), default))
|
||||
_mockBasketRepo.SetupSequence(x => x.FirstOrDefaultAsync(It.IsAny<BasketWithItemsSpecification>(), 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<BasketWithItemsSpecification>(), default))
|
||||
_mockBasketRepo.SetupSequence(x => x.FirstOrDefaultAsync(It.IsAny<BasketWithItemsSpecification>(), 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<BasketWithItemsSpecification>(), default))
|
||||
_mockBasketRepo.SetupSequence(x => x.FirstOrDefaultAsync(It.IsAny<BasketWithItemsSpecification>(), 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<Basket>(x => x.BuyerId == _nonexistentUserBasketBuyerId), default), Times.Once);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -22,7 +22,7 @@ public class GetOrderDetails
|
||||
Order order = new Order("buyerId", address, new List<OrderItem> { item });
|
||||
|
||||
_mockOrderRepository = new Mock<IReadRepository<Order>>();
|
||||
_mockOrderRepository.Setup(x => x.GetBySpecAsync(It.IsAny<OrderWithItemsByIdSpec>(), default))
|
||||
_mockOrderRepository.Setup(x => x.FirstOrDefaultAsync(It.IsAny<OrderWithItemsByIdSpec>(), default))
|
||||
.ReturnsAsync(order);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user