Skip to content

Commit

Permalink
feat: add endpoint to update order status #76
Browse files Browse the repository at this point in the history
- fix mysql tables
- add endpoint to update order status
- fix tracking code model
  • Loading branch information
italopessoa committed Jun 12, 2024
1 parent 59d715f commit 0732ce7
Show file tree
Hide file tree
Showing 15 changed files with 144 additions and 16 deletions.
2 changes: 1 addition & 1 deletion database/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ create table IF NOT EXISTS Orders
Status int not null,
Created datetime null,
Updated datetime null,
Code varchar(7) null
TrackingCode varchar(7) null
-- constraint Order_Customer_Id_fk
-- foreign key (CustomerId) references Customer (Id) null
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ public class OrdersController(IOrderService orderService, ILogger<OrdersControll
var order = await orderService.CreateAsync(newOrder.Cpf, orderItems.ToList());

logger.LogInformation("Order created with ID: {OrderId}", order.Id);
return CreatedAtAction(nameof(Get), new { id = order.Id }, new NewOrderDto(order.Id, order.TrackingCode.Value));
return CreatedAtAction(nameof(Get), new { id = order.Id },
new NewOrderDto(order.Id, order.TrackingCode.Value));
}

/// <summary>
Expand All @@ -51,7 +52,8 @@ public class OrdersController(IOrderService orderService, ILogger<OrdersControll
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Orders list</returns>
[HttpGet]
public async Task<ActionResult<ReadOnlyCollection<OrderDto>>> Get(bool listAll, CancellationToken cancellationToken)
public async Task<ActionResult<ReadOnlyCollection<OrderDto>>> Get(bool listAll,
CancellationToken cancellationToken)
{
logger.LogInformation("Getting all orders");
var orders = await orderService.GetAllAsync(listAll);
Expand Down Expand Up @@ -100,5 +102,27 @@ public async Task<ActionResult<OrderDto>> Get(Guid id, CancellationToken cancell
await orderService.CheckoutAsync(command.Id);
return Ok();
}

/// <summary>
/// Checkout order
/// </summary>
/// <param name="id"></param>
/// <param name="command">Checkout order command.</param>
/// <param name="cancellationToken">Cancellation token</param>
[Route("{id:guid}/status")]
[HttpPut]
public async Task<ActionResult<OrderDto>> UpdateStatus(Guid id,
[FromBody] UpdateOrderStatusCommandDto command,
CancellationToken cancellationToken)
{
logger.LogInformation("Updating order status: {Id}", id);
if (await orderService.UpdateStatusAsync(id, (OrderStatus)command.Status))
{
logger.LogInformation("Order status updated.");
return NoContent();
}

return BadRequest("Status not updated.");
}
}
}
4 changes: 2 additions & 2 deletions src/FIAP.TechChallenge.ByteMeBurger.Api/Model/OrderDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public OrderDto(Order order)
Id = order.Id;
TrackingCode = order.TrackingCode.Value;
Total = order.Total;
Status = order.Status;
Status = (OrderStatusDto)order.Status;
CreationDate = order.Created;
LastUpdate = order.LastUpdate;
OrderItems = order.OrderItems.Select(o => new OrderItemDto(o)).ToList();
Expand All @@ -37,7 +37,7 @@ public OrderDto(Order order)

public decimal Total { get; set; }

public OrderStatus Status { get; set; }
public OrderStatusDto Status { get; set; }

public DateTime CreationDate { get; set; }

Expand Down
20 changes: 20 additions & 0 deletions src/FIAP.TechChallenge.ByteMeBurger.Api/Model/OrderStatusDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) 2024, Italo Pessoa (https://github.com/italopessoa)
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.

namespace FIAP.TechChallenge.ByteMeBurger.Api.Model;

/// <summary>
/// Orders statuses
/// </summary>
public enum OrderStatusDto
{
PaymentPending = 0,
PaymentConfirmed = 1,
Received = 2,
InPreparation = 3,
Ready = 4,
Completed = 5
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) 2024, Italo Pessoa (https://github.com/italopessoa)
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.

using Microsoft.Build.Framework;

namespace FIAP.TechChallenge.ByteMeBurger.Api.Model;

/// <summary>
/// Command to update an order status.
/// </summary>
/// <param name="Status"></param>
public class UpdateOrderStatusCommandDto
{
[Required]
public OrderStatusDto Status { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public class OrderService(
IGetOrderDetailsUseCase getOrderDetailsUseCase,
IOrderGetAllUseCase orderGetAllUseCase,
ICheckoutOrderUseCase checkoutOrderUseCase,
IOrderRepository orderRepository)
IOrderRepository orderRepository,
IUpdateOrderStatusUseCase updateOrderStatusUseCase)
: IOrderService
{
public async Task<Order> CreateAsync(string? customerCpf, List<SelectedProduct> selectedProducts)
Expand All @@ -44,4 +45,10 @@ public async Task CheckoutAsync(Guid id)
{
await checkoutOrderUseCase.Execute(id);
}

public async Task<bool> UpdateStatusAsync(Guid orderId, OrderStatus newStatus)
{
var order = await updateOrderStatusUseCase.Execute(orderId, newStatus);
return await orderRepository.UpdateOrderStatusAsync(order);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public void FinishPreparing()
public void DeliverOrder()
{
if (Status != OrderStatus.Ready)
throw new DomainException("Cannot Deliver order if it's not Completed yet.");
throw new DomainException("Cannot Deliver order if it's not Ready yet.");

Status = OrderStatus.Completed;
Update();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,12 @@ public interface IOrderService
/// <param name="id">Order Id</param>
[Obsolete("This method will be removed in the future.")]
Task CheckoutAsync(Guid id);

/// <summary>
/// Update Order status
/// </summary>
/// <param name="orderId">Order Id.</param>
/// <param name="newStatus">New Status</param>
/// <returns></returns>
Task<bool> UpdateStatusAsync(Guid orderId, OrderStatus newStatus);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ public OrderTrackingCode(string value)

Value = value;
}

public static implicit operator OrderTrackingCode(string code) => new (code);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class OrderListDto
public int Status { get; set; }
public DateTime Created { get; set; }
public DateTime? Updated { get; set; }
public string? Code { get; set; }
public string TrackingCode { get; set; }
public Customer? Customer { get; set; }
public Guid ProductId { get; set; }
public string ProductName { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ public async Task<Order> CreateAsync(Order order)
try
{
await dbConnection.ExecuteAsync(
"insert into Orders (Id, CustomerId, Status, Created, Code) values (@Id, @CustomerId, @Status, @Created, @Code);",
"insert into Orders (Id, CustomerId, Status, Created, TrackingCode) values (@Id, @CustomerId, @Status, @Created, @TrackingCode);",
new
{
Id = order.Id,
CustomerId = order.Customer?.Id,
Status = (int)order.Status,
Created = order.Created,
Code = order.TrackingCode.Value
TrackingCode = order.TrackingCode.Value
});
await dbConnection.ExecuteAsync(
"insert into OrderItems (OrderId, ProductId, ProductName, UnitPrice, Quantity) " +
Expand Down Expand Up @@ -69,7 +69,7 @@ public async Task<ReadOnlyCollection<Order>> GetAllAsync()
o.Status,
o.Created,
o.Updated,
o.Code,
o.TrackingCode,
oi.OrderId,
oi.ProductId,
oi.ProductName,
Expand All @@ -92,7 +92,7 @@ public async Task<ReadOnlyCollection<Order>> GetAllAsync()
else
{
order = new Order(orderListDto.Id, customerDto, (OrderStatus)orderListDto.Status,
new OrderTrackingCode(orderListDto.Code), orderListDto.Created, orderListDto.Updated);
new OrderTrackingCode(orderListDto.TrackingCode), orderListDto.Created, orderListDto.Updated);
order.LoadItems(orderListDto.ProductId, orderListDto.ProductName, orderListDto.UnitPrice,
orderListDto.Quantity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,4 +248,28 @@ public async void CheckoutOrder_Success()
_serviceMock.VerifyAll();
}
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async void UpdateOrderStatusAsync(bool success)
{
// Arrange
_serviceMock.Setup(s => s.UpdateStatusAsync(It.IsAny<Guid>(), It.IsAny<OrderStatus>()))
.ReturnsAsync(success)
.Verifiable();

// Act
var response = await _target.UpdateStatus(Guid.NewGuid(),
new UpdateOrderStatusCommandDto() { Status = OrderStatusDto.Ready }, CancellationToken.None);

// Assert
using (new AssertionScope())
{
if (success)
response.Result.Should().BeOfType<NoContentResult>();
else
response.Result.Should().BeOfType<BadRequestObjectResult>();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class OrderServiceTest
private readonly Mock<IGetOrderDetailsUseCase> _mockGetOrderDetailsUseCase;
private readonly Mock<IOrderGetAllUseCase> _mockOrderGetAllUseCase;
private readonly Mock<ICheckoutOrderUseCase> _mockCheckoutOrderUseCase;
private readonly Mock<IUpdateOrderStatusUseCase> _mockUpdateOrderStatusUseCase;
private readonly Mock<IOrderRepository> _mockOrderRepository;


Expand All @@ -31,10 +32,11 @@ public OrderServiceTest()
_mockGetOrderDetailsUseCase = new Mock<IGetOrderDetailsUseCase>();
_mockOrderGetAllUseCase = new Mock<IOrderGetAllUseCase>();
_mockCheckoutOrderUseCase = new Mock<ICheckoutOrderUseCase>();
_mockUpdateOrderStatusUseCase = new Mock<IUpdateOrderStatusUseCase>();
_mockOrderRepository = new Mock<IOrderRepository>();

_target = new OrderService(_mockCreateOrderUseCase.Object, _mockGetOrderDetailsUseCase.Object,
_mockOrderGetAllUseCase.Object, _mockCheckoutOrderUseCase.Object, _mockOrderRepository.Object);
_mockOrderGetAllUseCase.Object, _mockCheckoutOrderUseCase.Object, _mockOrderRepository.Object, _mockUpdateOrderStatusUseCase.Object);
}

[Fact]
Expand Down Expand Up @@ -105,7 +107,6 @@ public async Task Create_Success(List<SelectedProduct> selectedProducts)
_mockOrderRepository.Verify(m => m.CreateAsync(
It.Is<Order>(o => o.Created != DateTime.MinValue
&& o.Status == OrderStatus.PaymentPending)), Times.Once);

}
}

Expand Down Expand Up @@ -165,4 +166,27 @@ public async Task Checkout_Success()
_mockCheckoutOrderUseCase.VerifyAll();
}
}

[Fact]
public async Task UpdateStatusAsync_Success()
{
// Arrange
var order = new Fixture().Create<Order>();
_mockUpdateOrderStatusUseCase.Setup(r => r.Execute(It.IsAny<Guid>(), It.IsAny<OrderStatus>()))
.ReturnsAsync(order)
.Verifiable();

_mockOrderRepository.Setup(r => r.UpdateOrderStatusAsync(It.Is<Order>(o => o.Equals(order))))
.ReturnsAsync(true)
.Verifiable();

// Act
var updated = await _target.UpdateStatusAsync(Guid.NewGuid(), OrderStatus.Ready);

// Assert
using (new AssertionScope())
{
updated.Should().BeTrue();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ public void Order_Deliver_NotFinished_ThrowsError()
using (new AssertionScope())
{
func.Should().ThrowExactly<DomainException>().And.Message.Should()
.Be("Cannot Deliver order if it's not Completed yet.");
.Be("Cannot Deliver order if it's not Ready yet.");
order.TrackingCode.Should().NotBeNull();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public async Task Create_Success()

_mockConnection.SetupDapperAsync(c =>
c.ExecuteAsync(
"insert into Orders (Id, CustomerId, Status, Created, Code) values (@Id, @CustomerId, @Status, @Created, @Code);",
"insert into Orders (Id, CustomerId, Status, Created, TrackingCode) values (@Id, @CustomerId, @Status, @Created, @TrackingCode);",
null, null, null, null))
.ReturnsAsync(1);
_mockConnection.SetupDapperAsync(c =>
Expand Down

0 comments on commit 0732ce7

Please sign in to comment.