Add Order and OrderBook domains with test suites
This commit is contained in:
+18
@@ -0,0 +1,18 @@
|
|||||||
|
# .NET build output
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
|
||||||
|
# Test results
|
||||||
|
TestResults/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vs/
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# dotnet CLI local data (when the CLI home points inside the repo)
|
||||||
|
.local/
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Show, Don't Tell — C# exercise workspace
|
||||||
|
|
||||||
|
Workspace for the "Show, Don't Tell — Using Examples with Coding Agents" exercise (C# / .NET 10 / NUnit).
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- .NET 10 SDK (version pinned in `global.json`)
|
||||||
|
- If you use [mise](https://mise.jdx.dev): `mise install` installs the SDK listed in `mise.toml`
|
||||||
|
|
||||||
|
## Build and run the tests
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dotnet build
|
||||||
|
dotnet test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
| Directory | Contents |
|
||||||
|
| --- | --- |
|
||||||
|
| `src/OrderDomain`, `tests/OrderTests` | Order domain with discount behaviour, and its tests |
|
||||||
|
| `src/OrderBookDomain`, `tests/OrderBookTests` | OrderBook state machine, and its integration tests |
|
||||||
|
|
||||||
|
Open `ShowDontTell.sln` in your IDE (Rider, Visual Studio, VS Code with the C# extension).
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31903.59
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderDomain", "src/OrderDomain/OrderDomain.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderTests", "tests/OrderTests/OrderTests.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderBookDomain", "src/OrderBookDomain/OrderBookDomain.csproj", "{C3D4E5F6-A7B8-9012-CDEF-123456789012}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderBookTests", "tests/OrderBookTests/OrderBookTests.csproj", "{D4E5F6A7-B8C9-0123-DEFA-234567890123}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Exercise2", "Exercise2", "{83A6B86A-4CD0-44BE-9FE0-408AC31E69F8}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Exercise1", "Exercise1", "{55470487-3CE4-4FD7-BADF-35F7D1473CA5}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
|
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
|
{D4E5F6A7-B8C9-0123-DEFA-234567890123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D4E5F6A7-B8C9-0123-DEFA-234567890123}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D4E5F6A7-B8C9-0123-DEFA-234567890123}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
|
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{C3D4E5F6-A7B8-9012-CDEF-123456789012} = {83A6B86A-4CD0-44BE-9FE0-408AC31E69F8}
|
||||||
|
{D4E5F6A7-B8C9-0123-DEFA-234567890123} = {83A6B86A-4CD0-44BE-9FE0-408AC31E69F8}
|
||||||
|
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {55470487-3CE4-4FD7-BADF-35F7D1473CA5}
|
||||||
|
{B2C3D4E5-F6A7-8901-BCDE-F12345678901} = {55470487-3CE4-4FD7-BADF-35F7D1473CA5}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "10.0.400",
|
||||||
|
"rollForward": "latestFeature"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[tools]
|
||||||
|
dotnet = "10"
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
namespace OrderBookDomain;
|
||||||
|
|
||||||
|
public enum OrderStatus
|
||||||
|
{
|
||||||
|
Pending,
|
||||||
|
WaitingForPayment,
|
||||||
|
Fulfilling,
|
||||||
|
Delivered,
|
||||||
|
Cancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Order
|
||||||
|
{
|
||||||
|
public int Id { get; }
|
||||||
|
public string Customer { get; }
|
||||||
|
public OrderStatus Status { get; internal set; }
|
||||||
|
public DateTimeOffset? FinalizedAt { get; internal set; }
|
||||||
|
|
||||||
|
internal Order(int id, string customer)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Customer = customer;
|
||||||
|
Status = OrderStatus.Pending;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
namespace OrderBookDomain;
|
||||||
|
|
||||||
|
public class OrderBook
|
||||||
|
{
|
||||||
|
private readonly TimeSpan _paymentWindow;
|
||||||
|
private readonly Dictionary<int, Order> _ordersById = new();
|
||||||
|
private int _nextId = 1;
|
||||||
|
|
||||||
|
public OrderBook(TimeSpan? paymentWindow = null)
|
||||||
|
=> _paymentWindow = paymentWindow ?? TimeSpan.FromDays(14);
|
||||||
|
|
||||||
|
public IReadOnlyList<Order> Orders => _ordersById.Values.ToList();
|
||||||
|
|
||||||
|
public Order OpenOrder(string customer)
|
||||||
|
{
|
||||||
|
var order = new Order(_nextId++, customer);
|
||||||
|
_ordersById[order.Id] = order;
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Order GetOrder(int orderId)
|
||||||
|
=> _ordersById.TryGetValue(orderId, out var order)
|
||||||
|
? order
|
||||||
|
: throw new InvalidOperationException($"Unknown order id {orderId}.");
|
||||||
|
|
||||||
|
public void FinalizeOrder(int orderId, DateTimeOffset finalizedAt)
|
||||||
|
{
|
||||||
|
var order = GetOrder(orderId);
|
||||||
|
RequireStatus(order, OrderStatus.Pending, "finalize");
|
||||||
|
order.Status = OrderStatus.WaitingForPayment;
|
||||||
|
order.FinalizedAt = finalizedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PayOrder(int orderId, DateTimeOffset paidAt)
|
||||||
|
{
|
||||||
|
var order = GetOrder(orderId);
|
||||||
|
RequireStatus(order, OrderStatus.WaitingForPayment, "pay");
|
||||||
|
if (paidAt - order.FinalizedAt > _paymentWindow)
|
||||||
|
{
|
||||||
|
order.Status = OrderStatus.Cancelled;
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Payment for order {orderId} arrived after the payment window; the order was cancelled.");
|
||||||
|
}
|
||||||
|
order.Status = OrderStatus.Fulfilling;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShipOrder(int orderId)
|
||||||
|
{
|
||||||
|
var order = GetOrder(orderId);
|
||||||
|
RequireStatus(order, OrderStatus.Fulfilling, "ship");
|
||||||
|
order.Status = OrderStatus.Delivered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelOrder(int orderId)
|
||||||
|
{
|
||||||
|
var order = GetOrder(orderId);
|
||||||
|
if (order.Status is not (OrderStatus.Pending or OrderStatus.WaitingForPayment))
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Cannot cancel order {orderId} in status {order.Status}.");
|
||||||
|
order.Status = OrderStatus.Cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RequireStatus(Order order, OrderStatus expected, string action)
|
||||||
|
{
|
||||||
|
if (order.Status != expected)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Cannot {action} order {order.Id} in status {order.Status} (expected {expected}).");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace OrderDomain;
|
||||||
|
|
||||||
|
public class Order
|
||||||
|
{
|
||||||
|
public List<OrderLine> Lines { get; } = new();
|
||||||
|
|
||||||
|
public decimal Total => Lines.Sum(l => l.Value);
|
||||||
|
|
||||||
|
public bool IsCompleted { get; private set; }
|
||||||
|
|
||||||
|
public void AddLine(decimal unitPrice, int quantity)
|
||||||
|
=> Lines.Add(new OrderLine(unitPrice, quantity));
|
||||||
|
|
||||||
|
public void ApplyDiscount(int lineIndex)
|
||||||
|
=> Lines[lineIndex].ApplyDiscount();
|
||||||
|
|
||||||
|
public void Complete() => IsCompleted = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace OrderDomain;
|
||||||
|
|
||||||
|
public class OrderLine
|
||||||
|
{
|
||||||
|
public decimal UnitPrice { get; }
|
||||||
|
public int Quantity { get; }
|
||||||
|
public bool HasDiscount { get; private set; }
|
||||||
|
|
||||||
|
public decimal Value =>
|
||||||
|
HasDiscount ? UnitPrice * Quantity * 0.8m : UnitPrice * Quantity;
|
||||||
|
|
||||||
|
public OrderLine(decimal unitPrice, int quantity)
|
||||||
|
{
|
||||||
|
UnitPrice = unitPrice;
|
||||||
|
Quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyDiscount() => HasDiscount = true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
using NUnit.Framework;
|
||||||
|
using OrderBookDomain;
|
||||||
|
|
||||||
|
namespace OrderBookTests;
|
||||||
|
|
||||||
|
public class OrderBookLifecycleTests
|
||||||
|
{
|
||||||
|
private static readonly DateTimeOffset Day0 = new(2025, 6, 1, 0, 0, 0, TimeSpan.Zero);
|
||||||
|
|
||||||
|
private static DateTimeOffset At(int day) => Day0.AddDays(day);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void FinalizeThenPay_MovesOrderThroughTwoStates()
|
||||||
|
{
|
||||||
|
var book = new OrderBook();
|
||||||
|
var order = book.OpenOrder("anna");
|
||||||
|
|
||||||
|
book.FinalizeOrder(order.Id, At(1));
|
||||||
|
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.WaitingForPayment));
|
||||||
|
|
||||||
|
book.PayOrder(order.Id, At(2));
|
||||||
|
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Fulfilling));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void PayThenShip_DeliversTheOrder()
|
||||||
|
{
|
||||||
|
var book = new OrderBook();
|
||||||
|
var order = book.OpenOrder("anna");
|
||||||
|
|
||||||
|
book.FinalizeOrder(order.Id, At(1));
|
||||||
|
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.WaitingForPayment));
|
||||||
|
|
||||||
|
book.PayOrder(order.Id, At(2));
|
||||||
|
book.ShipOrder(order.Id);
|
||||||
|
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Delivered));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void PayWithinWindow_ThenLatePayOnSecondOrder_CancelsOnlyThatOrder()
|
||||||
|
{
|
||||||
|
var book = new OrderBook();
|
||||||
|
var first = book.OpenOrder("anna");
|
||||||
|
var second = book.OpenOrder("bram");
|
||||||
|
|
||||||
|
book.FinalizeOrder(first.Id, At(1));
|
||||||
|
book.FinalizeOrder(second.Id, At(1));
|
||||||
|
Assert.That(book.Orders, Has.Count.EqualTo(2));
|
||||||
|
|
||||||
|
book.PayOrder(first.Id, At(5));
|
||||||
|
Assert.That(book.GetOrder(first.Id).Status, Is.EqualTo(OrderStatus.Fulfilling));
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() => book.PayOrder(second.Id, At(30)));
|
||||||
|
Assert.That(book.GetOrder(second.Id).Status, Is.EqualTo(OrderStatus.Cancelled));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void PayBeforeFinalize_Throws_ThenFinalizeStillWorks()
|
||||||
|
{
|
||||||
|
var book = new OrderBook();
|
||||||
|
var order = book.OpenOrder("anna");
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() => book.PayOrder(order.Id, At(1)));
|
||||||
|
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Pending));
|
||||||
|
|
||||||
|
book.FinalizeOrder(order.Id, At(2));
|
||||||
|
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.WaitingForPayment));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void CancelPendingOrder_ThenPayAfterCancel_ThrowsAgain()
|
||||||
|
{
|
||||||
|
var book = new OrderBook();
|
||||||
|
var order = book.OpenOrder("anna");
|
||||||
|
|
||||||
|
book.CancelOrder(order.Id);
|
||||||
|
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Cancelled));
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() => book.PayOrder(order.Id, At(1)));
|
||||||
|
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Cancelled));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ShippedOrder_CannotBeCancelled()
|
||||||
|
{
|
||||||
|
var book = new OrderBook();
|
||||||
|
var order = book.OpenOrder("anna");
|
||||||
|
|
||||||
|
book.FinalizeOrder(order.Id, At(1));
|
||||||
|
book.PayOrder(order.Id, At(2));
|
||||||
|
book.ShipOrder(order.Id);
|
||||||
|
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Delivered));
|
||||||
|
|
||||||
|
Assert.Throws<InvalidOperationException>(() => book.CancelOrder(order.Id));
|
||||||
|
Assert.That(book.GetOrder(order.Id).Status, Is.EqualTo(OrderStatus.Delivered));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TwoCustomers_InterleaveTransitions_Independently()
|
||||||
|
{
|
||||||
|
var book = new OrderBook();
|
||||||
|
var annas = book.OpenOrder("anna");
|
||||||
|
var brams = book.OpenOrder("bram");
|
||||||
|
|
||||||
|
book.FinalizeOrder(brams.Id, At(1));
|
||||||
|
Assert.That(book.GetOrder(annas.Id).Status, Is.EqualTo(OrderStatus.Pending));
|
||||||
|
Assert.That(book.GetOrder(brams.Id).Status, Is.EqualTo(OrderStatus.WaitingForPayment));
|
||||||
|
|
||||||
|
book.PayOrder(brams.Id, At(2));
|
||||||
|
book.CancelOrder(annas.Id);
|
||||||
|
Assert.That(book.GetOrder(annas.Id).Status, Is.EqualTo(OrderStatus.Cancelled));
|
||||||
|
Assert.That(book.GetOrder(brams.Id).Status, Is.EqualTo(OrderStatus.Fulfilling));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="NUnit" Version="4.6.1" />
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="6.3.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\OrderBookDomain\OrderBookDomain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using NUnit.Framework;
|
||||||
|
using OrderDomain;
|
||||||
|
|
||||||
|
namespace OrderTests;
|
||||||
|
|
||||||
|
public class OrderCompletionTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void CompletedOrder_TotalIsCorrect()
|
||||||
|
{
|
||||||
|
var order = new Order();
|
||||||
|
order.AddLine(100m, 1);
|
||||||
|
order.Complete();
|
||||||
|
|
||||||
|
Assert.That(order.Total, Is.EqualTo(100m));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsTestProject>true</IsTestProject>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||||
|
<PackageReference Include="NUnit" Version="4.6.1" />
|
||||||
|
<PackageReference Include="NUnit3TestAdapter" Version="6.3.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\OrderDomain\OrderDomain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using NUnit.Framework;
|
||||||
|
using OrderDomain;
|
||||||
|
|
||||||
|
namespace OrderTests;
|
||||||
|
|
||||||
|
public class OrderTotalTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void DiscountOnFirstLine_TotalAndLineValues()
|
||||||
|
{
|
||||||
|
var order = new Order();
|
||||||
|
order.AddLine(100m, 2); // line 0: 200 → 160 with discount
|
||||||
|
order.AddLine(50m, 1); // line 1: 50
|
||||||
|
|
||||||
|
order.ApplyDiscount(0);
|
||||||
|
|
||||||
|
Assert.That(order.Total, Is.EqualTo(210m));
|
||||||
|
Assert.That(order.Lines[0].Value, Is.EqualTo(160m));
|
||||||
|
Assert.That(order.Lines[1].Value, Is.EqualTo(50m));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void DiscountOnSecondLine_TotalAndLineValues()
|
||||||
|
{
|
||||||
|
var order = new Order();
|
||||||
|
order.AddLine(100m, 2); // line 0: 200
|
||||||
|
order.AddLine(50m, 1); // line 1: 50 → 40 with discount
|
||||||
|
|
||||||
|
order.ApplyDiscount(1);
|
||||||
|
|
||||||
|
Assert.That(order.Total, Is.EqualTo(240m));
|
||||||
|
Assert.That(order.Lines[0].Value, Is.EqualTo(200m));
|
||||||
|
Assert.That(order.Lines[1].Value, Is.EqualTo(40m));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void DiscountOnBothLines_TotalAndLineValues()
|
||||||
|
{
|
||||||
|
var order = new Order();
|
||||||
|
order.AddLine(100m, 2); // line 0: 200 → 160
|
||||||
|
order.AddLine(50m, 1); // line 1: 50 → 40
|
||||||
|
|
||||||
|
order.ApplyDiscount(0);
|
||||||
|
order.ApplyDiscount(1);
|
||||||
|
|
||||||
|
Assert.That(order.Total, Is.EqualTo(200m));
|
||||||
|
Assert.That(order.Lines[0].Value, Is.EqualTo(160m));
|
||||||
|
Assert.That(order.Lines[1].Value, Is.EqualTo(40m));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void NoDiscount_TotalAndLineValues()
|
||||||
|
{
|
||||||
|
var order = new Order();
|
||||||
|
order.AddLine(100m, 2); // line 0: 200
|
||||||
|
order.AddLine(50m, 1); // line 1: 50
|
||||||
|
|
||||||
|
Assert.That(order.Total, Is.EqualTo(250m));
|
||||||
|
Assert.That(order.Lines[0].Value, Is.EqualTo(200m));
|
||||||
|
Assert.That(order.Lines[1].Value, Is.EqualTo(50m));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SingleLineWithDiscount_TotalAndValue()
|
||||||
|
{
|
||||||
|
var order = new Order();
|
||||||
|
order.AddLine(75m, 4); // line 0: 300 → 240
|
||||||
|
|
||||||
|
order.ApplyDiscount(0);
|
||||||
|
|
||||||
|
Assert.That(order.Total, Is.EqualTo(240m));
|
||||||
|
Assert.That(order.Lines[0].Value, Is.EqualTo(240m));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ThreeLinesOneDiscount_TotalAndLineValues()
|
||||||
|
{
|
||||||
|
var order = new Order();
|
||||||
|
order.AddLine(20m, 1); // line 0: 20
|
||||||
|
order.AddLine(30m, 2); // line 1: 60 → 48
|
||||||
|
order.AddLine(10m, 3); // line 2: 30
|
||||||
|
|
||||||
|
order.ApplyDiscount(1);
|
||||||
|
|
||||||
|
Assert.That(order.Total, Is.EqualTo(98m));
|
||||||
|
Assert.That(order.Lines[0].Value, Is.EqualTo(20m));
|
||||||
|
Assert.That(order.Lines[1].Value, Is.EqualTo(48m));
|
||||||
|
Assert.That(order.Lines[2].Value, Is.EqualTo(30m));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user