Skip to content

Commit

Permalink
feat: unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
baotoq committed Dec 10, 2024
1 parent ce75b86 commit c6593ad
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using MassTransit;

namespace MicroCommerce.ApiService.Domain.Events;

public abstract record DomainEventBase : IDomainEvent
public abstract record DomainEventBase : IDomainEvent, CorrelatedBy<Guid>
{
public string EventType => GetType().FullName!;
public Guid EventId { get; } = Guid.CreateVersion7();
public DateTimeOffset CreatedAt { get; } = DateTimeOffset.UtcNow;
public Guid CorrelationId { get; init; } = Guid.CreateVersion7();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Ardalis.GuardClauses;
using Elastic.Clients.Elasticsearch;
using FluentValidation;
using MediatR;
using MicroCommerce.ApiService.Domain.Entities;
Expand All @@ -9,7 +8,6 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using RedLockNet;
using RedLockNet.SERedis;

namespace MicroCommerce.ApiService.Features.Carts;

Expand Down Expand Up @@ -52,8 +50,8 @@ public record Response
public class Handler : IRequestHandler<Command, Response>
{
private readonly ApplicationDbContext _context;
private readonly ILogger<Handler> _logger;
private readonly IDistributedLockFactory _distributedLockFactory;
private readonly ILogger<Handler> _logger;

public Handler(ApplicationDbContext context, ILogger<Handler> logger, IDistributedLockFactory distributedLockFactory)
{
Expand Down Expand Up @@ -108,12 +106,9 @@ await _context.CartItems.AddAsync(new CartItem
}
else
{
var rowAffected = await _context.CartItems
.Where(s => s.CartId == request.CartId && s.ProductId == request.ProductId)
.ExecuteUpdateAsync(setters =>
setters.SetProperty(p => p.ProductQuantity, p => p.ProductQuantity + request.Quantity), cancellationToken);
var ok = await _context.CartItems.IncreaseProductQuantityInCartAsync(cartItem, request.Quantity, cancellationToken);

if (rowAffected == 0)
if (!ok)
{
throw new InvalidValidationException("Update cart item failed!");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
using MediatR;
using MicroCommerce.ApiService.Domain.Entities;
using MicroCommerce.ApiService.Exceptions;
using MicroCommerce.ApiService.Features.DomainEvents;
using MicroCommerce.ApiService.Infrastructure;
using MicroCommerce.ApiService.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using RedLockNet;
using RedLockNet.SERedis;

namespace MicroCommerce.ApiService.Features.Carts;

public class CheckoutCart: IEndpoint
public class PlaceOrder : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder builder)
{
builder.MapPost("/api/carts/{id:guid}/checkout", async (Guid id, [FromBody] Command request, IMediator mediator)
builder.MapPost("/api/carts/{id:guid}/place-order", async (Guid id, [FromBody] Command request, IMediator mediator)
=>
{
request.CartId = id;
Expand Down Expand Up @@ -47,8 +47,8 @@ public record Response
public class Handler : IRequestHandler<Command, Response>
{
private readonly ApplicationDbContext _context;
private readonly ILogger<Handler> _logger;
private readonly IDistributedLockFactory _distributedLockFactory;
private readonly ILogger<Handler> _logger;

public Handler(ApplicationDbContext context, ILogger<Handler> logger, IDistributedLockFactory distributedLockFactory)
{
Expand Down Expand Up @@ -96,6 +96,8 @@ public async Task<Response> Handle(Command request, CancellationToken cancellati
}
}

cart.AddDomainEvent(new OrderCreatedDomainEvent(cart.Id));

await _context.SaveChangesAsync(cancellationToken);
await trans.CommitAsync(cancellationToken);

Expand All @@ -106,4 +108,3 @@ public async Task<Response> Handle(Command request, CancellationToken cancellati
}
}
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Ardalis.GuardClauses;
using Elastic.Clients.Elasticsearch;
using FluentValidation;
using MediatR;
using MicroCommerce.ApiService.Domain.Entities;
Expand All @@ -9,7 +8,6 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using RedLockNet;
using RedLockNet.SERedis;

namespace MicroCommerce.ApiService.Features.Carts;

Expand Down Expand Up @@ -52,8 +50,8 @@ public record Response
public class Handler : IRequestHandler<Command, Response>
{
private readonly ApplicationDbContext _context;
private readonly ILogger<Handler> _logger;
private readonly IDistributedLockFactory _distributedLockFactory;
private readonly ILogger<Handler> _logger;

public Handler(ApplicationDbContext context, ILogger<Handler> logger, IDistributedLockFactory distributedLockFactory)
{
Expand Down Expand Up @@ -97,18 +95,14 @@ public async Task<Response> Handle(Command request, CancellationToken cancellati
};
}

var rowAffected = await _context.CartItems
.Where(s => s.CartId == request.CartId && s.ProductId == request.ProductId)
.Where(s => s.ProductQuantity - request.Quantity >= 0)
.ExecuteUpdateAsync(setters =>
setters.SetProperty(p => p.ProductQuantity, p => p.ProductQuantity - request.Quantity), cancellationToken);
var ok = await _context.CartItems.DecreaseProductQuantityInCartAsync(cartItem, request.Quantity, cancellationToken);

if (rowAffected == 0)
if (!ok)
{
throw new InvalidValidationException("Update cart item failed!");
}

rowAffected = await _context.CartItems
var rowAffected = await _context.CartItems
.Where(s => s.CartId == request.CartId && s.ProductId == request.ProductId)
.Where(s => s.ProductQuantity - request.Quantity <= 0)
.ExecuteDeleteAsync(cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using MassTransit;
using MicroCommerce.ApiService.Domain.Events;
using RedLockNet;

namespace MicroCommerce.ApiService.Features.DomainEvents;

public class OrderCreatedDomainEvent : IDomainEvent
{
public OrderCreatedDomainEvent(Guid cartId)
{
CartId = cartId;
}

public Guid CartId { get; }
}

public class OrderCreatedDomainEventConsumer : IConsumer<OrderCreatedDomainEvent>
{
private readonly IDistributedLockFactory _distributedLockFactory;
private readonly ILogger<OrderCreatedDomainEventConsumer> _logger;

public OrderCreatedDomainEventConsumer(ILogger<OrderCreatedDomainEventConsumer> logger, IDistributedLockFactory distributedLockFactory)
{
_logger = logger;
_distributedLockFactory = distributedLockFactory;
}

public async Task Consume(ConsumeContext<OrderCreatedDomainEvent> context)
{
var @event = context.Message;

_logger.LogInformation("Order created for cart {CartId}", @event.CartId);

await using var lockHandle = await _distributedLockFactory.CreateLockAsync(@event.CartId.ToString(), TimeSpan.FromSeconds(30));

if (!lockHandle.IsAcquired)
{
_logger.LogWarning("Failed to acquire lock for cart {CartId}", @event.CartId);
return;
}

_logger.LogInformation("Lock acquired for cart {CartId}", @event.CartId);

// Process order

_logger.LogInformation("Order processed for cart {CartId}", @event.CartId);
}
}
21 changes: 21 additions & 0 deletions code/src/MicroCommerce.ApiService/Services/ProductExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,27 @@ public static async Task<bool> UseProductRemainingStockAsync(this DbSet<Product>
return rowAffected > 0;
}

public static async Task<bool> DecreaseProductQuantityInCartAsync(this DbSet<CartItem> cartItems, CartItem cartItem, long quantity, CancellationToken cancellationToken)
{
var rowAffected = await cartItems
.Where(s => s.CartId == cartItem.CartId && s.ProductId == cartItem.ProductId)
.Where(s => s.ProductQuantity - quantity >= 0)
.ExecuteUpdateAsync(setters =>
setters.SetProperty(p => p.ProductQuantity, p => p.ProductQuantity - quantity), cancellationToken);

return rowAffected > 0;
}

public static async Task<bool> IncreaseProductQuantityInCartAsync(this DbSet<CartItem> cartItems, CartItem cartItem, long quantity, CancellationToken cancellationToken)
{
var rowAffected = await cartItems
.Where(s => s.CartId == cartItem.CartId && s.ProductId == cartItem.ProductId)
.ExecuteUpdateAsync(setters =>
setters.SetProperty(p => p.ProductQuantity, p => p.ProductQuantity + quantity), cancellationToken);

return rowAffected > 0;
}

public static async Task<int> ChangeProductStockAsync(this DbSet<Product> products, Guid productId, long changeQuantity, CancellationToken cancellationToken)
{
var rowAffected = await products
Expand Down
11 changes: 11 additions & 0 deletions code/src/MicroCommerce.ApiService/Services/QueryableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Linq.Expressions;

namespace MicroCommerce.ApiService.Services;

public static class QueryableExtensions
{
public static IQueryable<T> WhereIf<T>(this IQueryable<T> queryable, bool condition, Expression<Func<T, bool>> predicate)
{
return condition ? queryable.Where(predicate) : queryable;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace MicroCommerce.Tests.Carts;

public class CheckoutCartTests : TestBase
public class PlaceOrderTests : TestBase
{
[Fact]
public async Task Success()
Expand Down Expand Up @@ -37,13 +37,10 @@ public async Task Success()

var distributedLockFactory = TestHelper.CreateAcquiredLock();

var sut = new CheckoutCart.Handler(SeedContext, NullLogger<CheckoutCart.Handler>.Instance, distributedLockFactory);
var sut = new PlaceOrder.Handler(SeedContext, NullLogger<PlaceOrder.Handler>.Instance, distributedLockFactory);

// Act
var act = await sut.Handle(new CheckoutCart.Command
{
CartId = cart.Id
}, default);
var act = await sut.Handle(new PlaceOrder.Command { CartId = cart.Id }, default);

cart = await VerifyContext.Carts
.Include(s => s.CartItems)
Expand Down

0 comments on commit c6593ad

Please sign in to comment.