Port Round 1 to Java (OrderDomain): 7 tests green, START state preserved

This commit is contained in:
2026-10-01 13:35:33 +01:00
parent d9f78162e7
commit 83e1c79ab7
5 changed files with 264 additions and 0 deletions
@@ -0,0 +1,46 @@
package orderdomain;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
public class Order {
private final List<OrderLine> lines = new ArrayList<>();
private boolean completed;
public List<OrderLine> getLines() {
return lines;
}
public BigDecimal getTotal() {
BigDecimal total = BigDecimal.ZERO;
for (OrderLine line : lines) {
total = total.add(line.getValue());
}
return total;
}
public boolean isCompleted() {
return completed;
}
public void addLine(BigDecimal unitPrice, int quantity) {
lines.add(new OrderLine(unitPrice, quantity));
}
public void applyDiscount(int lineIndex) {
lines.get(lineIndex).applyDiscount();
}
public void complete() {
completed = true;
}
// C# decimal == is numeric (scale-insensitive); BigDecimal.equals is
// scale-sensitive, so compare numerically with compareTo.
public boolean isFullyPaid(BigDecimal expectedTotal) {
return completed && getTotal().compareTo(expectedTotal) == 0;
}
}
@@ -0,0 +1,36 @@
package orderdomain;
import java.math.BigDecimal;
public class OrderLine {
private final BigDecimal unitPrice;
private final int quantity;
private boolean hasDiscount;
public OrderLine(BigDecimal unitPrice, int quantity) {
this.unitPrice = unitPrice;
this.quantity = quantity;
}
public BigDecimal getUnitPrice() {
return unitPrice;
}
public int getQuantity() {
return quantity;
}
public boolean hasDiscount() {
return hasDiscount;
}
public BigDecimal getValue() {
BigDecimal value = unitPrice.multiply(BigDecimal.valueOf(quantity));
return hasDiscount ? value.multiply(new BigDecimal("0.8")) : value;
}
public void applyDiscount() {
hasDiscount = true;
}
}
@@ -0,0 +1,21 @@
package ordertests;
import static org.assertj.core.api.Assertions.assertThat;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
import orderdomain.Order;
class OrderCompletionTests {
@Test
void completedOrder_totalIsCorrect() {
Order order = new Order();
order.addLine(new BigDecimal("100"), 1);
order.complete();
assertThat(order.isFullyPaid(new BigDecimal("100"))).isTrue();
}
}
@@ -0,0 +1,67 @@
package ordertests;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.assertj.core.api.AbstractAssert;
import orderdomain.Order;
import orderdomain.OrderLine;
/**
* Custom AssertJ matcher that bundles multiple order assertions into one.
* <p>
* Participants: you do not need to understand how this works —
* just recognize the pattern and use it as an example for the agent.
*/
public class OrderStateAssertion extends AbstractAssert<OrderStateAssertion, Order> {
private OrderStateAssertion(Order actual) {
super(actual, OrderStateAssertion.class);
}
public static OrderStateAssertion assertThat(Order actual) {
return new OrderStateAssertion(actual);
}
public OrderStateAssertion hasState(BigDecimal expectedTotal, List<BigDecimal> expectedLineValues) {
isNotNull();
List<String> details = new ArrayList<>();
BigDecimal actualTotal = actual.getTotal();
if (actualTotal.compareTo(expectedTotal) != 0) {
details.add("Total: expected " + expectedTotal + ", but was " + actualTotal);
}
List<BigDecimal> actualLineValues = new ArrayList<>();
for (OrderLine line : actual.getLines()) {
actualLineValues.add(line.getValue());
}
if (!lineValuesMatch(actualLineValues, expectedLineValues)) {
details.add("Line values: expected " + expectedLineValues
+ ", but was " + actualLineValues);
}
if (!details.isEmpty()) {
failWithMessage("Expected order with total " + expectedTotal
+ " and line values " + expectedLineValues + " but was:\n"
+ String.join("\n", details));
}
return this;
}
private static boolean lineValuesMatch(List<BigDecimal> actual, List<BigDecimal> expected) {
if (actual.size() != expected.size()) {
return false;
}
for (int i = 0; i < expected.size(); i++) {
if (actual.get(i).compareTo(expected.get(i)) != 0) {
return false;
}
}
return true;
}
}
@@ -0,0 +1,94 @@
package ordertests;
import static ordertests.OrderStateAssertion.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
import java.math.BigDecimal;
import java.util.List;
import org.junit.jupiter.api.Test;
import orderdomain.Order;
class OrderTotalTests {
// This test was refactored — notice the custom matcher.
// The other tests still need the same treatment.
@Test
void discountOnFirstLine_totalAndLineValues() {
Order order = new Order();
order.addLine(new BigDecimal("100"), 2); // line 0: 200 → 160 with discount
order.addLine(new BigDecimal("50"), 1); // line 1: 50
order.applyDiscount(0);
assertThat(order).hasState(
new BigDecimal("210"),
List.of(new BigDecimal("160"), new BigDecimal("50")));
}
@Test
void discountOnSecondLine_totalAndLineValues() {
Order order = new Order();
order.addLine(new BigDecimal("100"), 2); // line 0: 200
order.addLine(new BigDecimal("50"), 1); // line 1: 50 → 40 with discount
order.applyDiscount(1);
assertThat(order.getTotal()).isEqualByComparingTo("240");
assertThat(order.getLines().get(0).getValue()).isEqualByComparingTo("200");
assertThat(order.getLines().get(1).getValue()).isEqualByComparingTo("40");
}
@Test
void discountOnBothLines_totalAndLineValues() {
Order order = new Order();
order.addLine(new BigDecimal("100"), 2); // line 0: 200 → 160
order.addLine(new BigDecimal("50"), 1); // line 1: 50 → 40
order.applyDiscount(0);
order.applyDiscount(1);
assertThat(order.getTotal()).isEqualByComparingTo("200");
assertThat(order.getLines().get(0).getValue()).isEqualByComparingTo("160");
assertThat(order.getLines().get(1).getValue()).isEqualByComparingTo("40");
}
@Test
void noDiscount_totalAndLineValues() {
Order order = new Order();
order.addLine(new BigDecimal("100"), 2); // line 0: 200
order.addLine(new BigDecimal("50"), 1); // line 1: 50
assertThat(order.getTotal()).isEqualByComparingTo("250");
assertThat(order.getLines().get(0).getValue()).isEqualByComparingTo("200");
assertThat(order.getLines().get(1).getValue()).isEqualByComparingTo("50");
}
@Test
void singleLineWithDiscount_totalAndValue() {
Order order = new Order();
order.addLine(new BigDecimal("75"), 4); // line 0: 300 → 240
order.applyDiscount(0);
assertThat(order.getTotal()).isEqualByComparingTo("240");
assertThat(order.getLines().get(0).getValue()).isEqualByComparingTo("240");
}
@Test
void threeLinesOneDiscount_totalAndLineValues() {
Order order = new Order();
order.addLine(new BigDecimal("20"), 1); // line 0: 20
order.addLine(new BigDecimal("30"), 2); // line 1: 60 → 48
order.addLine(new BigDecimal("10"), 3); // line 2: 30
order.applyDiscount(1);
assertThat(order.getTotal()).isEqualByComparingTo("98");
assertThat(order.getLines().get(0).getValue()).isEqualByComparingTo("20");
assertThat(order.getLines().get(1).getValue()).isEqualByComparingTo("48");
assertThat(order.getLines().get(2).getValue()).isEqualByComparingTo("30");
}
}