package com.bookshop.behavioral.chainofresponsibility;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.bookshop.domain.Book;
import com.bookshop.domain.Customer;
import java.math.BigDecimal;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@DisplayName("Chain of Responsibility -- order checks as a configurable pipeline")
class OrderValidationChainTest {
private final Book inStockBook = new Book("978-0132350884", "Clean Code", "Robert C. Martin",
new BigDecimal("32.50"));
private final Book rareBook = new Book("978-0000000001", "Out of Print Rarity", "Unknown",
new BigDecimal("950.00"));
private final Customer loyalAlice = new Customer("c-1", "Alice", "alice@example.com", 4);
private final Customer newBob = new Customer("c-2", "Bob", "bob@example.com", 0);
private final OrderCheck chain = OrderCheck.chainOf(List.of(
new StockCheck(Set.of("978-0132350884")),
new AddressCheck(),
new FraudCheck()));
@Test
@DisplayName("a clean order passes every check in the chain")
void validOrderPasses() {
OrderRequest request = new OrderRequest(loyalAlice, List.of(inStockBook), "1 High Street");
assertTrue(chain.validate(request).valid());
}
@Test
@DisplayName("a failing check stops the chain and names itself -- later checks never run")
void rejectionShortCircuits() {
OrderRequest request = new OrderRequest(newBob, List.of(rareBook), "");
ValidationResult result = chain.validate(request);
assertFalse(result.valid());
assertEquals("stock", result.rejectedBy());
}
@Test
@DisplayName("the pipeline is configuration -- reordering the list reorders the checks")
void chainOrderIsConfigurable() {
OrderCheck addressFirst = OrderCheck.chainOf(List.of(new AddressCheck(), new FraudCheck()));
OrderRequest request = new OrderRequest(newBob, List.of(rareBook), "");
assertEquals("address", addressFirst.validate(request).rejectedBy());
}
@Test
@DisplayName("relinking the same checks into a shorter chain drops the old tail")
void relinkedShorterChainHasNoStaleTail() {
StockCheck stock = new StockCheck(Set.of(rareBook.isbn()));
OrderCheck.chainOf(List.of(stock, new FraudCheck()));
OrderRequest fraudWouldReject = new OrderRequest(newBob, List.of(rareBook), "1 High Street");
OrderCheck stockOnly = OrderCheck.chainOf(List.of(stock));
assertTrue(stockOnly.validate(fraudWouldReject).valid());
}
@Test
@DisplayName("each check is a small unit, testable entirely on its own")
void checksAreIndependentlyTestable() {
ValidationResult result = new FraudCheck()
.validate(new OrderRequest(newBob, List.of(rareBook), "1 High Street"));
assertEquals("fraud", result.rejectedBy());
assertEquals("large first order needs manual review", result.reason());
}
}