Skip to content

Instantly share code, notes, and snippets.

@fnmssteam
Created February 12, 2025 10:06
Show Gist options
  • Select an option

  • Save fnmssteam/4d46603caeff3e52cf0cbea28fc3e733 to your computer and use it in GitHub Desktop.

Select an option

Save fnmssteam/4d46603caeff3e52cf0cbea28fc3e733 to your computer and use it in GitHub Desktop.
DeepSeek 2nd xUnit Challenge Solution
using DeepSeekChallenge.Contracts;
using DeepSeekChallenge.Entities;
using DeepSeekChallenge.Exceptions;
namespace DeepSeekChallenge.Services;
public class OrderProcessor
{
private readonly IInventoryService _inventory;
private readonly IPaymentGateway _paymentGateway;
private readonly INotificationService _notification;
public OrderProcessor(IInventoryService inventory, IPaymentGateway paymentGateway, INotificationService notification)
{
_inventory = inventory;
_paymentGateway = paymentGateway;
_notification = notification;
}
public async Task<OrderResult> ProcessOrder(string userId, string itemId, decimal amount)
{
var orderResult = new OrderResult();
if (!_inventory.IsInStock(itemId))
{
orderResult.Success = false;
orderResult.Message = $"Item {itemId} is out of stock.";
return orderResult;
}
int retries = 3;
do
{
try
{
await _paymentGateway.ProcessPaymentAsync(amount);
_inventory.UpdateStock(itemId, -1);
orderResult.Success = true;
orderResult.Message = "Order successfully processed";
await _notification.SendNotification(userId, "Order Succeeded");
break;
}
catch (TransientPaymentException e)
{
retries--;
if (retries == 0)
{
orderResult.Success = false;
orderResult.Message = e.Message;
await _notification.SendNotification(userId, "Order failed: Payment error");
}
}
catch (Exception e) when (e is PaymentFailedException || e is Exception)
{
orderResult.Success = false;
orderResult.Message = e.Message;
await _notification.SendNotification(userId, "Order failed: Payment error");
break;
}
} while (retries > 0);
return orderResult;
}
}
using DeepSeekChallenge.Contracts;
using DeepSeekChallenge.Exceptions;
using DeepSeekChallenge.Services;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using NSubstitute.ReceivedExtensions;
namespace DeepSeekChallengeTests.Services;
public class OrderProcessorTests
{
private string _userId;
private string _itemId;
private decimal _amount;
public OrderProcessorTests()
{
_userId = "1";
_itemId = "1D";
_amount = 2.99m;
}
[Fact]
public async void ProcessOrder_PaymentSucceeds_ShouldReturnSuccessfulOrderResult()
{
// Arrange
var inventoryService = Substitute.For<IInventoryService>();
inventoryService.IsInStock(_itemId).Returns(true);
var paymentGateway = Substitute.For<IPaymentGateway>();
paymentGateway.ProcessPaymentAsync(_amount).Returns(Task.CompletedTask);
var notificationService = Substitute.For<INotificationService>();
notificationService.SendNotification(_userId, "Order Succeeded").Returns(Task.CompletedTask);
var orderProcessor = new OrderProcessor(inventoryService, paymentGateway, notificationService);
// Act
var orderResult = await orderProcessor.ProcessOrder(_userId, _itemId, _amount);
// Assert
Assert.True(orderResult.Success);
Assert.Contains("successfully processed", orderResult.Message, StringComparison.OrdinalIgnoreCase);
inventoryService.Received(1).UpdateStock(_itemId, -1);
await paymentGateway.Received(1).ProcessPaymentAsync(_amount);
await notificationService.Received(1).SendNotification(_userId, "Order Succeeded");
Received.InOrder(async () =>
{
inventoryService.UpdateStock(_itemId, -1);
await notificationService.SendNotification(_userId, "Order Succeeded");
});
}
[Fact]
public async void ProcessOrder_PaymentTwiceRetriedThenSucceeds_ShouldReturnSuccessfulOrderResult()
{
// Arrange
var inventoryService = Substitute.For<IInventoryService>();
inventoryService.IsInStock(_itemId).Returns(true);
var paymentGateway = Substitute.For<IPaymentGateway>();
paymentGateway
.ProcessPaymentAsync(_amount)
.Returns(
x => { throw new TransientPaymentException(); },
x => { throw new TransientPaymentException(); },
x => Task.CompletedTask
);
var notificationService = Substitute.For<INotificationService>();
notificationService.SendNotification(_userId, "Order Succeeded").Returns(Task.CompletedTask);
var orderProcessor = new OrderProcessor(inventoryService, paymentGateway, notificationService);
// Act
var orderResult = await orderProcessor.ProcessOrder(_userId, _itemId, _amount);
// Assert
Assert.True(orderResult.Success);
Assert.Contains("successfully processed", orderResult.Message, StringComparison.OrdinalIgnoreCase);
// assert ProcessPayment() was tried 3 times
await paymentGateway.Received(3).ProcessPaymentAsync(_amount);
await notificationService.Received(1).SendNotification(_userId, "Order Succeeded");
// ensure the inventory was decremented only ONCE
inventoryService.Received(1).UpdateStock(_itemId, -1);
Received.InOrder(async () =>
{
inventoryService.UpdateStock(_itemId, -1);
await notificationService.SendNotification(_userId, "Order Succeeded");
});
}
[Fact]
public async void ProcessOrder_InsufficientInventory_ShouldReturnFailedOrderResult()
{
// Arrange
var inventoryService = Substitute.For<IInventoryService>();
inventoryService.IsInStock(_itemId).Returns(false);
var paymentGateway = Substitute.For<IPaymentGateway>();
var notificationService = Substitute.For<INotificationService>();
var orderProcessor = new OrderProcessor(inventoryService, paymentGateway, notificationService);
// Act
var orderResult = await orderProcessor.ProcessOrder(_userId, _itemId, _amount);
// Assert
Assert.False(orderResult.Success);
Assert.Contains("out of stock", orderResult.Message, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async void ProcessOrder_PaymentFailedThrice_ShouldReturnFailedOrderResult()
{
// Arrange
var inventoryService = Substitute.For<IInventoryService>();
inventoryService.IsInStock(_itemId).Returns(true);
var paymentGateway = Substitute.For<IPaymentGateway>();
paymentGateway
.ProcessPaymentAsync(_amount)
.Returns(
x => { throw new TransientPaymentException(); },
x => { throw new TransientPaymentException(); },
x => { throw new TransientPaymentException(); }
);
var notificationService = Substitute.For<INotificationService>();
notificationService.SendNotification(_userId, "Order failed: Payment Error").Returns(Task.CompletedTask);
var orderProcessor = new OrderProcessor(inventoryService, paymentGateway, notificationService);
// Act
var orderResult = await orderProcessor.ProcessOrder(_userId, _itemId, _amount);
// Assert
Assert.False(orderResult.Success);
await paymentGateway.Received(3).ProcessPaymentAsync(_amount);
await notificationService.Received(1).SendNotification(_userId, "Order failed: Payment error");
inventoryService.DidNotReceive().UpdateStock(_itemId, -1);
}
[Fact]
public async void ProcessOrder_PaymentFailedPermanently_ShouldReturnFailedOrderResult()
{
// Arrange
var inventoryService = Substitute.For<IInventoryService>();
inventoryService.IsInStock(_itemId).Returns(true);
var paymentGateway = Substitute.For<IPaymentGateway>();
paymentGateway
.ProcessPaymentAsync(_amount)
.Throws<PaymentFailedException>();
var notificationService = Substitute.For<INotificationService>();
notificationService.SendNotification(_userId, "Order failed: Payment Error").Returns(Task.CompletedTask);
var orderProcessor = new OrderProcessor(inventoryService, paymentGateway, notificationService);
// Act
var orderResult = await orderProcessor.ProcessOrder(_userId, _itemId, _amount);
// Assert
Assert.False(orderResult.Success);
await paymentGateway.Received(1).ProcessPaymentAsync(_amount);
await notificationService.Received(1).SendNotification(_userId, "Order failed: Payment error");
inventoryService.DidNotReceive().UpdateStock(_itemId, -1);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment