Port Round 1 to Java (OrderDomain): 7 tests green, START state preserved
This commit is contained in:
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user