Created
February 12, 2025 10:06
-
-
Save fnmssteam/4d46603caeff3e52cf0cbea28fc3e733 to your computer and use it in GitHub Desktop.
DeepSeek 2nd xUnit Challenge Solution
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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