From 6d440a47ffc81cdd506f254c2d913d6bd7c0e4a4 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Tue, 10 Sep 2019 23:26:17 +0100 Subject: [PATCH 01/27] Initial commit --- .gitignore | 5 +++++ pom.xml | 12 ++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2f3445d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea/ +build/ +out/ +*.iml +*.class diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..da9ffb22 --- /dev/null +++ b/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + com.ford.henrysgroceries + henrys-groceries + 1.0-SNAPSHOT + + + \ No newline at end of file From f8ef8da3fe0077ca8f1e231019aaef05eeccf8f6 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Tue, 10 Sep 2019 23:49:18 +0100 Subject: [PATCH 02/27] Update .gitignore to ignore target directory --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 2f3445d8..ee14693d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .idea/ -build/ -out/ +target/ *.iml *.class From 7a4e229fd65dd0de5c1414dd6078faa82f551b67 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Wed, 11 Sep 2019 00:22:44 +0100 Subject: [PATCH 03/27] Calculate basket total --- pom.xml | 27 +++++++ src/test/java/com/ford/Basket.java | 26 +++++++ src/test/java/com/ford/BasketTest.java | 68 ++++++++++++++++++ src/test/java/com/ford/products/Product.java | 12 ++++ .../java/com/ford/products/ProductHelper.java | 70 +++++++++++++++++++ 5 files changed, 203 insertions(+) create mode 100644 src/test/java/com/ford/Basket.java create mode 100644 src/test/java/com/ford/BasketTest.java create mode 100644 src/test/java/com/ford/products/Product.java create mode 100644 src/test/java/com/ford/products/ProductHelper.java diff --git a/pom.xml b/pom.xml index da9ffb22..b04a4384 100644 --- a/pom.xml +++ b/pom.xml @@ -7,6 +7,33 @@ com.ford.henrysgroceries henrys-groceries 1.0-SNAPSHOT + + 1.8 + 1.8 + + 4.11 + + + + + junit + junit + ${junit.version} + test + + + org.hamcrest + hamcrest-core + + + + + org.hamcrest + hamcrest-all + 1.3 + test + + \ No newline at end of file diff --git a/src/test/java/com/ford/Basket.java b/src/test/java/com/ford/Basket.java new file mode 100644 index 00000000..23642409 --- /dev/null +++ b/src/test/java/com/ford/Basket.java @@ -0,0 +1,26 @@ +package com.ford; + +import com.ford.products.Product; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +public class Basket { + + private List products; + + public Basket() { + this.products = new ArrayList<>(); + } + + public void addProduct(Product product) { + products.add(product); + } + + public BigDecimal calculateTotal() { + return products.stream() + .map(Product::getPrice) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } +} diff --git a/src/test/java/com/ford/BasketTest.java b/src/test/java/com/ford/BasketTest.java new file mode 100644 index 00000000..8e31600b --- /dev/null +++ b/src/test/java/com/ford/BasketTest.java @@ -0,0 +1,68 @@ +package com.ford; + +import com.ford.products.Product; +import org.junit.Test; + +import java.math.BigDecimal; +import java.util.Arrays; + +import static com.ford.products.ProductHelper.*; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public class BasketTest { + private Basket basket; + + @Test + public void emptyBasketTotalIsZero() { + basket = new Basket(); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(BigDecimal.ZERO), is(0)); + } + + @Test + public void givesCorrectTotalForSingleProducts() { + for (Product product : Arrays.asList(soup(), bread(), milk(), apples())) { + givenBasketHasProduct(product); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(product.getPrice()), is(0)); + } + } + + @Test + public void givesCorrectTotalForMilkAndBread() { + givenBasketHasProducts(milk(), bread()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("2.10")), is(0)); + } + + @Test + public void givesCorrectTotalForMoreThanOneProductOfSameType() { + givenBasketHasProducts( + apples(), apples(), + soup(), soup() + ); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("1.50")), is(0)); + } + + private void givenBasketHasProduct(Product product) { + givenBasketHasProducts(product); + } + + private void givenBasketHasProducts(Product... products) { + basket = new Basket(); + + for (Product product : products) { + basket.addProduct(product); + } + } +} diff --git a/src/test/java/com/ford/products/Product.java b/src/test/java/com/ford/products/Product.java new file mode 100644 index 00000000..be50cc31 --- /dev/null +++ b/src/test/java/com/ford/products/Product.java @@ -0,0 +1,12 @@ +package com.ford.products; + +import java.math.BigDecimal; + +public interface Product { + + String getProduct(); + + String getUnit(); + + BigDecimal getPrice(); +} diff --git a/src/test/java/com/ford/products/ProductHelper.java b/src/test/java/com/ford/products/ProductHelper.java new file mode 100644 index 00000000..768f38d1 --- /dev/null +++ b/src/test/java/com/ford/products/ProductHelper.java @@ -0,0 +1,70 @@ +package com.ford.products; + +import java.math.BigDecimal; + +public class ProductHelper { + + public static Product soup() { + return new Product() { + public String getProduct() { + return "soup"; + } + + public String getUnit() { + return "tin"; + } + + public BigDecimal getPrice() { + return new BigDecimal("0.65"); + } + }; + } + + public static Product bread() { + return new Product() { + public String getProduct() { + return "bread"; + } + + public String getUnit() { + return "loaf"; + } + + public BigDecimal getPrice() { + return new BigDecimal("0.80"); + } + }; + } + + public static Product milk() { + return new Product() { + public String getProduct() { + return "milk"; + } + + public String getUnit() { + return "bottle"; + } + + public BigDecimal getPrice() { + return new BigDecimal("1.30"); + } + }; + } + + public static Product apples() { + return new Product() { + public String getProduct() { + return "apples"; + } + + public String getUnit() { + return "single"; + } + + public BigDecimal getPrice() { + return new BigDecimal("0.10"); + } + }; + } +} \ No newline at end of file From cecd1d1277bd2f98bb948d1b6f5a0447c30a8275 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Thu, 12 Sep 2019 23:22:05 +0100 Subject: [PATCH 04/27] Move classes into src folder --- .../ford => main/java/com/ford/henrysgroceries}/Basket.java | 4 ++-- .../java/com/ford/henrysgroceries}/products/Product.java | 2 +- .../com/ford/henrysgroceries}/products/ProductHelper.java | 2 +- src/test/java/com/ford/BasketTest.java | 5 +++-- 4 files changed, 7 insertions(+), 6 deletions(-) rename src/{test/java/com/ford => main/java/com/ford/henrysgroceries}/Basket.java (85%) rename src/{test/java/com/ford => main/java/com/ford/henrysgroceries}/products/Product.java (76%) rename src/{test/java/com/ford => main/java/com/ford/henrysgroceries}/products/ProductHelper.java (97%) diff --git a/src/test/java/com/ford/Basket.java b/src/main/java/com/ford/henrysgroceries/Basket.java similarity index 85% rename from src/test/java/com/ford/Basket.java rename to src/main/java/com/ford/henrysgroceries/Basket.java index 23642409..f4ec9ea4 100644 --- a/src/test/java/com/ford/Basket.java +++ b/src/main/java/com/ford/henrysgroceries/Basket.java @@ -1,6 +1,6 @@ -package com.ford; +package com.ford.henrysgroceries; -import com.ford.products.Product; +import com.ford.henrysgroceries.products.Product; import java.math.BigDecimal; import java.util.ArrayList; diff --git a/src/test/java/com/ford/products/Product.java b/src/main/java/com/ford/henrysgroceries/products/Product.java similarity index 76% rename from src/test/java/com/ford/products/Product.java rename to src/main/java/com/ford/henrysgroceries/products/Product.java index be50cc31..9366ea13 100644 --- a/src/test/java/com/ford/products/Product.java +++ b/src/main/java/com/ford/henrysgroceries/products/Product.java @@ -1,4 +1,4 @@ -package com.ford.products; +package com.ford.henrysgroceries.products; import java.math.BigDecimal; diff --git a/src/test/java/com/ford/products/ProductHelper.java b/src/main/java/com/ford/henrysgroceries/products/ProductHelper.java similarity index 97% rename from src/test/java/com/ford/products/ProductHelper.java rename to src/main/java/com/ford/henrysgroceries/products/ProductHelper.java index 768f38d1..58f5ac69 100644 --- a/src/test/java/com/ford/products/ProductHelper.java +++ b/src/main/java/com/ford/henrysgroceries/products/ProductHelper.java @@ -1,4 +1,4 @@ -package com.ford.products; +package com.ford.henrysgroceries.products; import java.math.BigDecimal; diff --git a/src/test/java/com/ford/BasketTest.java b/src/test/java/com/ford/BasketTest.java index 8e31600b..5a41ede6 100644 --- a/src/test/java/com/ford/BasketTest.java +++ b/src/test/java/com/ford/BasketTest.java @@ -1,12 +1,13 @@ package com.ford; -import com.ford.products.Product; +import com.ford.henrysgroceries.Basket; +import com.ford.henrysgroceries.products.Product; import org.junit.Test; import java.math.BigDecimal; import java.util.Arrays; -import static com.ford.products.ProductHelper.*; +import static com.ford.henrysgroceries.products.ProductHelper.*; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; From 55a2497ae76023298a09396a6d5fd931a2f5b315 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Thu, 12 Sep 2019 23:44:43 +0100 Subject: [PATCH 05/27] Convert Product into a class --- .../henrysgroceries/products/Product.java | 26 +++++++-- .../products/ProductHelper.java | 58 ++----------------- 2 files changed, 27 insertions(+), 57 deletions(-) diff --git a/src/main/java/com/ford/henrysgroceries/products/Product.java b/src/main/java/com/ford/henrysgroceries/products/Product.java index 9366ea13..b0a7dc87 100644 --- a/src/main/java/com/ford/henrysgroceries/products/Product.java +++ b/src/main/java/com/ford/henrysgroceries/products/Product.java @@ -2,11 +2,29 @@ import java.math.BigDecimal; -public interface Product { +public class Product { - String getProduct(); + private final String name; - String getUnit(); + private final String unit; - BigDecimal getPrice(); + private final BigDecimal price; + + public Product(String name, String unit, BigDecimal price) { + this.name = name; + this.unit = unit; + this.price = price; + } + + public String getName() { + return name; + } + + public String getUnit() { + return unit; + } + + public BigDecimal getPrice() { + return price; + } } diff --git a/src/main/java/com/ford/henrysgroceries/products/ProductHelper.java b/src/main/java/com/ford/henrysgroceries/products/ProductHelper.java index 58f5ac69..40d1abc7 100644 --- a/src/main/java/com/ford/henrysgroceries/products/ProductHelper.java +++ b/src/main/java/com/ford/henrysgroceries/products/ProductHelper.java @@ -5,66 +5,18 @@ public class ProductHelper { public static Product soup() { - return new Product() { - public String getProduct() { - return "soup"; - } - - public String getUnit() { - return "tin"; - } - - public BigDecimal getPrice() { - return new BigDecimal("0.65"); - } - }; + return new Product("Soup", "tin", new BigDecimal("0.65")); } public static Product bread() { - return new Product() { - public String getProduct() { - return "bread"; - } - - public String getUnit() { - return "loaf"; - } - - public BigDecimal getPrice() { - return new BigDecimal("0.80"); - } - }; + return new Product("Bread", "loaf", new BigDecimal("0.80")); } public static Product milk() { - return new Product() { - public String getProduct() { - return "milk"; - } - - public String getUnit() { - return "bottle"; - } - - public BigDecimal getPrice() { - return new BigDecimal("1.30"); - } - }; + return new Product("Milk", "bottle", new BigDecimal("1.30")); } public static Product apples() { - return new Product() { - public String getProduct() { - return "apples"; - } - - public String getUnit() { - return "single"; - } - - public BigDecimal getPrice() { - return new BigDecimal("0.10"); - } - }; + return new Product("Apples", "single", new BigDecimal("0.10")); } -} \ No newline at end of file +} From e71b446321c6821cf63234dd4772e9874cae3ccd Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Fri, 13 Sep 2019 01:28:54 +0100 Subject: [PATCH 06/27] Moved BasketTest into correct package --- src/test/java/com/ford/{ => henrysgroceries}/BasketTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename src/test/java/com/ford/{ => henrysgroceries}/BasketTest.java (96%) diff --git a/src/test/java/com/ford/BasketTest.java b/src/test/java/com/ford/henrysgroceries/BasketTest.java similarity index 96% rename from src/test/java/com/ford/BasketTest.java rename to src/test/java/com/ford/henrysgroceries/BasketTest.java index 5a41ede6..b87774fb 100644 --- a/src/test/java/com/ford/BasketTest.java +++ b/src/test/java/com/ford/henrysgroceries/BasketTest.java @@ -1,6 +1,5 @@ -package com.ford; +package com.ford.henrysgroceries; -import com.ford.henrysgroceries.Basket; import com.ford.henrysgroceries.products.Product; import org.junit.Test; From f735725bda9fe4f1aae867bb41c52f5035f38259 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Fri, 13 Sep 2019 01:30:16 +0100 Subject: [PATCH 07/27] Add console output --- pom.xml | 6 ++ .../java/com/ford/henrysgroceries/Basket.java | 12 ++++ .../ford/henrysgroceries/BasketRunner.java | 66 +++++++++++++++++++ .../henrysgroceries/BasketRunnerTest.java | 65 ++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 src/main/java/com/ford/henrysgroceries/BasketRunner.java create mode 100644 src/test/java/com/ford/henrysgroceries/BasketRunnerTest.java diff --git a/pom.xml b/pom.xml index b04a4384..71fb7825 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,12 @@ 1.3 test + + org.mockito + mockito-core + 2.20.1 + test + diff --git a/src/main/java/com/ford/henrysgroceries/Basket.java b/src/main/java/com/ford/henrysgroceries/Basket.java index f4ec9ea4..3aae38ea 100644 --- a/src/main/java/com/ford/henrysgroceries/Basket.java +++ b/src/main/java/com/ford/henrysgroceries/Basket.java @@ -23,4 +23,16 @@ public BigDecimal calculateTotal() { .map(Product::getPrice) .reduce(BigDecimal.ZERO, BigDecimal::add); } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Basket:\n"); + products.forEach(product -> display(sb, product)); + sb.append("Total: £").append(calculateTotal()).append("\n"); + return sb.toString(); + } + + private StringBuilder display(StringBuilder sb, Product product) { + return sb.append(product.getName()).append(" ").append(product.getPrice()).append("\n"); + } } diff --git a/src/main/java/com/ford/henrysgroceries/BasketRunner.java b/src/main/java/com/ford/henrysgroceries/BasketRunner.java new file mode 100644 index 00000000..a0606b8b --- /dev/null +++ b/src/main/java/com/ford/henrysgroceries/BasketRunner.java @@ -0,0 +1,66 @@ +package com.ford.henrysgroceries; + +import java.io.PrintStream; +import java.util.Scanner; + +import static com.ford.henrysgroceries.products.ProductHelper.*; + +public class BasketRunner { + private final Basket basket; + + private final Scanner scanner; + + private final PrintStream printStream; + + public BasketRunner(Basket basket, Scanner scanner, PrintStream printStream) { + this.basket = basket; + this.scanner = scanner; + this.printStream = printStream; + } + + public static void main(String[] args) { + BasketRunner basketRunner = new BasketRunner(new Basket(), new Scanner(System.in), System.out); + basketRunner.run(); + } + + void run() { + boolean flag = true; + + do { + printStream.print("Please enter the first letter of the product you wish to add to your basket: [S]oup, [B]read, [M]ilk, [A]pples or [Q]uit: "); + String input = scanner.nextLine().toUpperCase(); + + switch (input) { + case "S": + printStream.print("You added: Soup\n"); + basket.addProduct(soup()); + break; + + case "B": + printStream.print("You added: Bread\n"); + basket.addProduct(bread()); + break; + + case "M": + printStream.print("You added: Milk\n"); + basket.addProduct(milk()); + break; + + case "A": + printStream.print("You added: Apples\n"); + basket.addProduct(apples()); + break; + + case "Q": + printStream.print("\n"); + flag = false; + break; + + default: + printStream.print("Product not recognised\n"); + } + + printStream.println(basket); + } while (flag); + } +} diff --git a/src/test/java/com/ford/henrysgroceries/BasketRunnerTest.java b/src/test/java/com/ford/henrysgroceries/BasketRunnerTest.java new file mode 100644 index 00000000..c0c030e5 --- /dev/null +++ b/src/test/java/com/ford/henrysgroceries/BasketRunnerTest.java @@ -0,0 +1,65 @@ +package com.ford.henrysgroceries; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.math.BigDecimal; +import java.util.Scanner; + +import static com.ford.henrysgroceries.products.ProductHelper.milk; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class BasketRunnerTest { + private static final String INSTRUCTIONS = "Please enter the first letter of the product you wish to add to your basket: [S]oup, [B]read, [M]ilk, [A]pples or [Q]uit: "; + + @Mock + private Basket basket; + + private ByteArrayOutputStream output; + + @Before + public void setUp() { + output = new ByteArrayOutputStream(); + } + + @Test + public void addNothingToBasketThenQuit() { + when(basket.toString()).thenReturn("Basket:\nTotal: £0.00\n"); + BasketRunner basketRunner = new BasketRunner(basket, new Scanner("Q\n"), new PrintStream(output)); + + basketRunner.run(); + + assertThat(output.toString(), is( + INSTRUCTIONS + "\n" + + "Basket:\n" + + "Total: £0.00\n\r\n")); + } + + @Test + public void addMilkToBasketThenQuit() { + BigDecimal price = milk().getPrice(); + when(basket.toString()).thenReturn("Basket:\nMilk £" + price + "\nTotal: £" + price + "\n"); + BasketRunner basketRunner = new BasketRunner(basket, new Scanner("M\nQ\n"), new PrintStream(output)); + + basketRunner.run(); + + assertThat(output.toString(), is( + INSTRUCTIONS + "You added: Milk\n" + + "Basket:\n" + + "Milk £1.30\n" + + "Total: £1.30\n\r" + + "\n" + + INSTRUCTIONS + "\n" + + "Basket:\n" + + "Milk £1.30\n" + + "Total: £1.30\n\r\n")); + } +} From 34d4b4026dd2bdf5d0b4e4331dfcd1c5cfdc0584 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Fri, 13 Sep 2019 02:14:45 +0100 Subject: [PATCH 08/27] Create percentage discount offer --- .../java/com/ford/henrysgroceries/Basket.java | 25 ++++++-- .../ford/henrysgroceries/offers/Offer.java | 8 +++ .../offers/PercentageDiscount.java | 34 +++++++++++ .../henrysgroceries/products/Product.java | 15 +++++ .../offers/PercentageDiscountTest.java | 59 +++++++++++++++++++ .../henrysgroceries/products/ProductTest.java | 24 ++++++++ 6 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/ford/henrysgroceries/offers/Offer.java create mode 100644 src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java create mode 100644 src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java create mode 100644 src/test/java/com/ford/henrysgroceries/products/ProductTest.java diff --git a/src/main/java/com/ford/henrysgroceries/Basket.java b/src/main/java/com/ford/henrysgroceries/Basket.java index 3aae38ea..a5dae402 100644 --- a/src/main/java/com/ford/henrysgroceries/Basket.java +++ b/src/main/java/com/ford/henrysgroceries/Basket.java @@ -1,29 +1,46 @@ package com.ford.henrysgroceries; +import com.ford.henrysgroceries.offers.Offer; import com.ford.henrysgroceries.products.Product; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; public class Basket { private List products; + private List offers; + public Basket() { - this.products = new ArrayList<>(); + products = new ArrayList<>(); + offers = new ArrayList<>(); } - public void addProduct(Product product) { - products.add(product); + public Basket(List offers, Product... products) { + this.products = Arrays.asList(products); + this.offers = offers; } public BigDecimal calculateTotal() { + offers.forEach(offer -> offer.apply(this)); + return products.stream() - .map(Product::getPrice) + .map(product -> product.hasDiscount() ? product.getDiscountPrice() : product.getPrice()) .reduce(BigDecimal.ZERO, BigDecimal::add); } + public List getProducts() { + return products; + } + + public void addProduct(Product product) { + this.products.add(product); + } + @Override public String toString() { StringBuilder sb = new StringBuilder("Basket:\n"); diff --git a/src/main/java/com/ford/henrysgroceries/offers/Offer.java b/src/main/java/com/ford/henrysgroceries/offers/Offer.java new file mode 100644 index 00000000..2874bc72 --- /dev/null +++ b/src/main/java/com/ford/henrysgroceries/offers/Offer.java @@ -0,0 +1,8 @@ +package com.ford.henrysgroceries.offers; + +import com.ford.henrysgroceries.Basket; + +public interface Offer { + + Basket apply(Basket basket); +} diff --git a/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java b/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java new file mode 100644 index 00000000..8321ca90 --- /dev/null +++ b/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java @@ -0,0 +1,34 @@ +package com.ford.henrysgroceries.offers; + +import com.ford.henrysgroceries.Basket; +import com.ford.henrysgroceries.products.Product; + +import java.math.BigDecimal; + +import static com.ford.henrysgroceries.products.ProductHelper.apples; + +public class PercentageDiscount implements Offer { + + private Product discountedProduct; + + private BigDecimal percentage; + + public PercentageDiscount(Product discountedProduct, int percentage) { + this.discountedProduct = discountedProduct; + this.percentage = new BigDecimal(percentage); + } + + @Override + public Basket apply(Basket basket) { + basket.getProducts().stream() + .filter(product -> product.getName().equals(apples().getName())) + .forEach(product -> product.setDiscountPrice(setDiscountPrice(product))); + return basket; + } + + private BigDecimal setDiscountPrice(Product apple) { + BigDecimal price = apple.getPrice(); + BigDecimal discount = price.divide(new BigDecimal("100.00")).multiply(percentage); + return price.subtract(discount); + } +} \ No newline at end of file diff --git a/src/main/java/com/ford/henrysgroceries/products/Product.java b/src/main/java/com/ford/henrysgroceries/products/Product.java index b0a7dc87..e359e1ce 100644 --- a/src/main/java/com/ford/henrysgroceries/products/Product.java +++ b/src/main/java/com/ford/henrysgroceries/products/Product.java @@ -10,10 +10,13 @@ public class Product { private final BigDecimal price; + private BigDecimal discountPrice; + public Product(String name, String unit, BigDecimal price) { this.name = name; this.unit = unit; this.price = price; + discountPrice = BigDecimal.ZERO; } public String getName() { @@ -27,4 +30,16 @@ public String getUnit() { public BigDecimal getPrice() { return price; } + + public BigDecimal getDiscountPrice() { + return discountPrice; + } + + public void setDiscountPrice(BigDecimal discountPrice) { + this.discountPrice = discountPrice; + } + + public boolean hasDiscount() { + return discountPrice.compareTo(BigDecimal.ZERO) != 0; + } } diff --git a/src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java b/src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java new file mode 100644 index 00000000..86fc4cc8 --- /dev/null +++ b/src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java @@ -0,0 +1,59 @@ +package com.ford.henrysgroceries.offers; + +import com.ford.henrysgroceries.Basket; +import com.ford.henrysgroceries.products.Product; +import org.junit.Test; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.ford.henrysgroceries.products.ProductHelper.*; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public class PercentageDiscountTest { + @Test + public void emptyBasketRemainsUnchanged() { + List offers = Collections.singletonList(new PercentageDiscount(apples(), 10)); + Basket basket = new Basket(offers); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(BigDecimal.ZERO), is(0)); + } + + @Test + public void applesAreDiscountedBy10Percent() { + List offers = Collections.singletonList(new PercentageDiscount(apples(), 10)); + Basket basket = new Basket(offers, apples()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("0.09")), is(0)); + } + + @Test + public void manyApplesAreDiscountedBy10Percent() { + List offers = Collections.singletonList(new PercentageDiscount(apples(), 10)); + Basket basket = new Basket(offers, apples(), apples(), apples()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("0.27")), is(0)); + } + + @Test + public void offerNotAppliedToOtherProducts() { + List offers = Collections.singletonList(new PercentageDiscount(apples(), 10)); + + for (Product product : Arrays.asList(soup(), bread(), milk())) { + Basket basket = new Basket(offers, product); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(product.getPrice()), is(0)); + } + } +} diff --git a/src/test/java/com/ford/henrysgroceries/products/ProductTest.java b/src/test/java/com/ford/henrysgroceries/products/ProductTest.java new file mode 100644 index 00000000..08660eae --- /dev/null +++ b/src/test/java/com/ford/henrysgroceries/products/ProductTest.java @@ -0,0 +1,24 @@ +package com.ford.henrysgroceries.products; + +import org.junit.Test; + +import java.math.BigDecimal; + +import static com.ford.henrysgroceries.products.ProductHelper.milk; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public class ProductTest { + @Test + public void hasDiscountReturnsTrueWhenDiscountedPriceExists() { + Product milk = milk(); + milk.setDiscountPrice(new BigDecimal("1.00")); + + assertThat(milk.hasDiscount(), is(true)); + } + + @Test + public void hasDiscountReturnsFalseWhenNoDiscountedPrice() { + assertThat(milk().hasDiscount(), is(false)); + } +} From 66506e178d2e0be7d6aeba2f55351118b9625811 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Fri, 13 Sep 2019 02:30:24 +0100 Subject: [PATCH 09/27] Made PercentageDiscount not be specific to apples --- .../henrysgroceries/offers/PercentageDiscount.java | 7 ++++--- .../offers/PercentageDiscountTest.java | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java b/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java index 8321ca90..3dbc3dcc 100644 --- a/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java +++ b/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java @@ -6,6 +6,7 @@ import java.math.BigDecimal; import static com.ford.henrysgroceries.products.ProductHelper.apples; +import static java.math.BigDecimal.ROUND_HALF_UP; public class PercentageDiscount implements Offer { @@ -21,13 +22,13 @@ public PercentageDiscount(Product discountedProduct, int percentage) { @Override public Basket apply(Basket basket) { basket.getProducts().stream() - .filter(product -> product.getName().equals(apples().getName())) + .filter(product -> product.getName().equals(discountedProduct.getName())) .forEach(product -> product.setDiscountPrice(setDiscountPrice(product))); return basket; } - private BigDecimal setDiscountPrice(Product apple) { - BigDecimal price = apple.getPrice(); + private BigDecimal setDiscountPrice(Product product) { + BigDecimal price = product.getPrice(); BigDecimal discount = price.divide(new BigDecimal("100.00")).multiply(percentage); return price.subtract(discount); } diff --git a/src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java b/src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java index 86fc4cc8..8ad524a0 100644 --- a/src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java +++ b/src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java @@ -34,6 +34,16 @@ public void applesAreDiscountedBy10Percent() { assertThat(total.compareTo(new BigDecimal("0.09")), is(0)); } + @Test + public void breadDiscountedBy50Percent() { + List offers = Collections.singletonList(new PercentageDiscount(bread(), 50)); + Basket basket = new Basket(offers, bread()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("0.40")), is(0)); + } + @Test public void manyApplesAreDiscountedBy10Percent() { List offers = Collections.singletonList(new PercentageDiscount(apples(), 10)); @@ -46,9 +56,9 @@ public void manyApplesAreDiscountedBy10Percent() { @Test public void offerNotAppliedToOtherProducts() { - List offers = Collections.singletonList(new PercentageDiscount(apples(), 10)); + List offers = Collections.singletonList(new PercentageDiscount(milk(), 10)); - for (Product product : Arrays.asList(soup(), bread(), milk())) { + for (Product product : Arrays.asList(soup(), bread(), apples())) { Basket basket = new Basket(offers, product); BigDecimal total = basket.calculateTotal(); From 3b1d833bfc449f47609911ef81f8f07116e41f4b Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Fri, 13 Sep 2019 02:35:32 +0100 Subject: [PATCH 10/27] Remove unused imports --- .../com/ford/henrysgroceries/offers/PercentageDiscount.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java b/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java index 3dbc3dcc..282e92c9 100644 --- a/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java +++ b/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java @@ -5,9 +5,6 @@ import java.math.BigDecimal; -import static com.ford.henrysgroceries.products.ProductHelper.apples; -import static java.math.BigDecimal.ROUND_HALF_UP; - public class PercentageDiscount implements Offer { private Product discountedProduct; From a41a10d6d0b186783665d502aaba94bbd95b6705 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Sun, 15 Sep 2019 22:58:28 +0100 Subject: [PATCH 11/27] Convert PercentageDiscount back to being apple-specific --- .../offers/PercentageDiscount.java | 9 ++++--- .../offers/PercentageDiscountTest.java | 25 +++++++------------ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java b/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java index 282e92c9..4455fe20 100644 --- a/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java +++ b/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java @@ -5,15 +5,16 @@ import java.math.BigDecimal; +import static com.ford.henrysgroceries.products.ProductHelper.apples; + public class PercentageDiscount implements Offer { private Product discountedProduct; - private BigDecimal percentage; - public PercentageDiscount(Product discountedProduct, int percentage) { - this.discountedProduct = discountedProduct; - this.percentage = new BigDecimal(percentage); + public PercentageDiscount() { + this.discountedProduct = apples(); + this.percentage = new BigDecimal(10); } @Override diff --git a/src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java b/src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java index 8ad524a0..db232f78 100644 --- a/src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java +++ b/src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java @@ -14,9 +14,12 @@ import static org.junit.Assert.assertThat; public class PercentageDiscountTest { + + private Offer tenPercentOffApples = new PercentageDiscount(); + @Test public void emptyBasketRemainsUnchanged() { - List offers = Collections.singletonList(new PercentageDiscount(apples(), 10)); + List offers = Collections.singletonList(tenPercentOffApples); Basket basket = new Basket(offers); BigDecimal total = basket.calculateTotal(); @@ -25,8 +28,8 @@ public void emptyBasketRemainsUnchanged() { } @Test - public void applesAreDiscountedBy10Percent() { - List offers = Collections.singletonList(new PercentageDiscount(apples(), 10)); + public void oneAppleISDiscountedBy10Percent() { + List offers = Collections.singletonList(tenPercentOffApples); Basket basket = new Basket(offers, apples()); BigDecimal total = basket.calculateTotal(); @@ -34,19 +37,9 @@ public void applesAreDiscountedBy10Percent() { assertThat(total.compareTo(new BigDecimal("0.09")), is(0)); } - @Test - public void breadDiscountedBy50Percent() { - List offers = Collections.singletonList(new PercentageDiscount(bread(), 50)); - Basket basket = new Basket(offers, bread()); - - BigDecimal total = basket.calculateTotal(); - - assertThat(total.compareTo(new BigDecimal("0.40")), is(0)); - } - @Test public void manyApplesAreDiscountedBy10Percent() { - List offers = Collections.singletonList(new PercentageDiscount(apples(), 10)); + List offers = Collections.singletonList(tenPercentOffApples); Basket basket = new Basket(offers, apples(), apples(), apples()); BigDecimal total = basket.calculateTotal(); @@ -56,9 +49,9 @@ public void manyApplesAreDiscountedBy10Percent() { @Test public void offerNotAppliedToOtherProducts() { - List offers = Collections.singletonList(new PercentageDiscount(milk(), 10)); + List offers = Collections.singletonList(tenPercentOffApples); - for (Product product : Arrays.asList(soup(), bread(), apples())) { + for (Product product : Arrays.asList(soup(), bread(), milk())) { Basket basket = new Basket(offers, product); BigDecimal total = basket.calculateTotal(); From baf7e2e05cdd1aa34492d85845c97fa2d3693c0e Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Sun, 15 Sep 2019 23:00:43 +0100 Subject: [PATCH 12/27] Rename: PercentageDiscount -> TenPercentOffApplesOffer --- ...ercentageDiscount.java => TenPercentOffApplesOffer.java} | 4 ++-- ...eDiscountTest.java => TenPercentOffApplesOfferTest.java} | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/main/java/com/ford/henrysgroceries/offers/{PercentageDiscount.java => TenPercentOffApplesOffer.java} (90%) rename src/test/java/com/ford/henrysgroceries/offers/{PercentageDiscountTest.java => TenPercentOffApplesOfferTest.java} (93%) diff --git a/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java b/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java similarity index 90% rename from src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java rename to src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java index 4455fe20..38706d21 100644 --- a/src/main/java/com/ford/henrysgroceries/offers/PercentageDiscount.java +++ b/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java @@ -7,12 +7,12 @@ import static com.ford.henrysgroceries.products.ProductHelper.apples; -public class PercentageDiscount implements Offer { +public class TenPercentOffApplesOffer implements Offer { private Product discountedProduct; private BigDecimal percentage; - public PercentageDiscount() { + public TenPercentOffApplesOffer() { this.discountedProduct = apples(); this.percentage = new BigDecimal(10); } diff --git a/src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java b/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java similarity index 93% rename from src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java rename to src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java index db232f78..eda5de14 100644 --- a/src/test/java/com/ford/henrysgroceries/offers/PercentageDiscountTest.java +++ b/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java @@ -13,9 +13,9 @@ import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; -public class PercentageDiscountTest { - - private Offer tenPercentOffApples = new PercentageDiscount(); +public class TenPercentOffApplesOfferTest { + + private Offer tenPercentOffApples = new TenPercentOffApplesOffer(); @Test public void emptyBasketRemainsUnchanged() { From 6c25df5ae8d11e5e335e67b40b81a3a59c371b04 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 02:17:15 +0100 Subject: [PATCH 13/27] Add date range to apple offer --- .../java/com/ford/henrysgroceries/Basket.java | 19 ++++- .../ford/henrysgroceries/offers/Offer.java | 4 +- .../offers/TenPercentOffApplesOffer.java | 27 +++++-- .../offers/TenPercentOffApplesOfferTest.java | 80 ++++++++++++++++--- 4 files changed, 106 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/ford/henrysgroceries/Basket.java b/src/main/java/com/ford/henrysgroceries/Basket.java index a5dae402..d3e4691b 100644 --- a/src/main/java/com/ford/henrysgroceries/Basket.java +++ b/src/main/java/com/ford/henrysgroceries/Basket.java @@ -4,35 +4,46 @@ import com.ford.henrysgroceries.products.Product; import java.math.BigDecimal; +import java.time.Clock; +import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; public class Basket { private List products; - private List offers; + private Clock clock; public Basket() { products = new ArrayList<>(); offers = new ArrayList<>(); } - public Basket(List offers, Product... products) { + public Basket(List offers) { + products = new ArrayList<>(); + this.offers = offers; + } + + public Basket(List offers, Clock fixedClock, Product... products) { this.products = Arrays.asList(products); this.offers = offers; + clock = fixedClock; } public BigDecimal calculateTotal() { - offers.forEach(offer -> offer.apply(this)); + offers.forEach(offer -> offer.apply(this, getDate())); return products.stream() .map(product -> product.hasDiscount() ? product.getDiscountPrice() : product.getPrice()) .reduce(BigDecimal.ZERO, BigDecimal::add); } + private LocalDate getDate() { + return clock == null ? LocalDate.now() : LocalDate.now(clock); + } + public List getProducts() { return products; } diff --git a/src/main/java/com/ford/henrysgroceries/offers/Offer.java b/src/main/java/com/ford/henrysgroceries/offers/Offer.java index 2874bc72..feabb858 100644 --- a/src/main/java/com/ford/henrysgroceries/offers/Offer.java +++ b/src/main/java/com/ford/henrysgroceries/offers/Offer.java @@ -2,7 +2,9 @@ import com.ford.henrysgroceries.Basket; +import java.time.LocalDate; + public interface Offer { - Basket apply(Basket basket); + Basket apply(Basket basket, LocalDate date); } diff --git a/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java b/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java index 38706d21..9f2b413f 100644 --- a/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java +++ b/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java @@ -4,30 +4,43 @@ import com.ford.henrysgroceries.products.Product; import java.math.BigDecimal; +import java.time.LocalDate; import static com.ford.henrysgroceries.products.ProductHelper.apples; +import static java.time.temporal.TemporalAdjusters.lastDayOfMonth; public class TenPercentOffApplesOffer implements Offer { - private Product discountedProduct; - private BigDecimal percentage; + private final Product discountedProduct; + private final BigDecimal percentage; + private final LocalDate start; + private final LocalDate end; - public TenPercentOffApplesOffer() { - this.discountedProduct = apples(); - this.percentage = new BigDecimal(10); + public TenPercentOffApplesOffer(LocalDate today) { + discountedProduct = apples(); + percentage = new BigDecimal(10); + start = today.plusDays(3); + end = today.plusMonths(1).with(lastDayOfMonth()); } @Override - public Basket apply(Basket basket) { + public Basket apply(Basket basket, LocalDate date) { + if (notApplicable(date)) + return basket; + basket.getProducts().stream() .filter(product -> product.getName().equals(discountedProduct.getName())) .forEach(product -> product.setDiscountPrice(setDiscountPrice(product))); return basket; } + private boolean notApplicable(LocalDate date) { + return date.isBefore(start) || date.isAfter(end); + } + private BigDecimal setDiscountPrice(Product product) { BigDecimal price = product.getPrice(); BigDecimal discount = price.divide(new BigDecimal("100.00")).multiply(percentage); return price.subtract(discount); } -} \ No newline at end of file +} diff --git a/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java b/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java index eda5de14..cb56f9d4 100644 --- a/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java +++ b/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java @@ -5,22 +5,35 @@ import org.junit.Test; import java.math.BigDecimal; +import java.time.*; import java.util.Arrays; import java.util.Collections; import java.util.List; import static com.ford.henrysgroceries.products.ProductHelper.*; +import static java.time.temporal.ChronoUnit.DAYS; +import static java.time.temporal.TemporalAdjusters.lastDayOfMonth; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; public class TenPercentOffApplesOfferTest { + private Clock fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + private LocalDate today = LocalDate.now(fixedClock); + private List offers = Collections.singletonList(new TenPercentOffApplesOffer(today)); - private Offer tenPercentOffApples = new TenPercentOffApplesOffer(); + @Test + public void emptyBasketNoOffersTotalIsZero() { + List offers = Collections.emptyList(); + Basket basket = new Basket(offers, fixedClock); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(BigDecimal.ZERO), is(0)); + } @Test - public void emptyBasketRemainsUnchanged() { - List offers = Collections.singletonList(tenPercentOffApples); - Basket basket = new Basket(offers); + public void emptyBasketWithOffersTotalIsZero() { + Basket basket = new Basket(offers, fixedClock); BigDecimal total = basket.calculateTotal(); @@ -28,9 +41,49 @@ public void emptyBasketRemainsUnchanged() { } @Test - public void oneAppleISDiscountedBy10Percent() { - List offers = Collections.singletonList(tenPercentOffApples); - Basket basket = new Basket(offers, apples()); + public void offerDoesNotApplyTwoDaysFromNow() { + Clock clock = Clock.offset(fixedClock, Duration.ofDays(2)); + Basket basket = new Basket(offers, clock, apples()); + + BigDecimal total = basket.calculateTotal( ); + + assertThat(total.compareTo(apples().getPrice()), is(0)); + } + + @Test + public void offerStartsThreeDaysFromNow() { + Clock clock = Clock.offset(fixedClock, Duration.ofDays(3)); + Basket basket = new Basket(offers, clock, apples()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("0.09")), is(0)); + } + + @Test + public void offerAppliesTillEndOfFollowingMonth() { + Clock clock = Clock.offset(fixedClock, lastDayOfferApplies()); + Basket basket = new Basket(offers, clock, apples()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("0.09")), is(0)); + } + + @Test + public void offerDoesNotApplyOneDayAfterEndOfFollowingMonth() { + Clock clock = Clock.offset(fixedClock, lastDayOfferApplies().plusDays(1)); + Basket basket = new Basket(offers, clock, apples()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(apples().getPrice()), is(0)); + } + + @Test + public void oneAppleIsDiscountedBy10Percent() { + Clock clock = Clock.offset(fixedClock, Duration.ofDays(10)); + Basket basket = new Basket(offers, clock, apples()); BigDecimal total = basket.calculateTotal(); @@ -39,8 +92,8 @@ public void oneAppleISDiscountedBy10Percent() { @Test public void manyApplesAreDiscountedBy10Percent() { - List offers = Collections.singletonList(tenPercentOffApples); - Basket basket = new Basket(offers, apples(), apples(), apples()); + Clock clock = Clock.offset(fixedClock, Duration.ofDays(10)); + Basket basket = new Basket(offers, clock, apples(), apples(), apples()); BigDecimal total = basket.calculateTotal(); @@ -49,14 +102,17 @@ public void manyApplesAreDiscountedBy10Percent() { @Test public void offerNotAppliedToOtherProducts() { - List offers = Collections.singletonList(tenPercentOffApples); - for (Product product : Arrays.asList(soup(), bread(), milk())) { - Basket basket = new Basket(offers, product); + Basket basket = new Basket(offers, fixedClock, product); BigDecimal total = basket.calculateTotal(); assertThat(total.compareTo(product.getPrice()), is(0)); } } + + private Duration lastDayOfferApplies() { + LocalDate lastDayOfferApplies = today.plusMonths(1).with(lastDayOfMonth()); + return Duration.ofDays(DAYS.between(today, lastDayOfferApplies)); + } } From fb7fc51d544f591f439f9c3c6f6e2a2c7d9d87b6 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 02:36:38 +0100 Subject: [PATCH 14/27] Minor refactors to TenPercentOffApplesOffer --- .../henrysgroceries/offers/TenPercentOffApplesOffer.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java b/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java index 9f2b413f..5148c656 100644 --- a/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java +++ b/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java @@ -11,14 +11,10 @@ public class TenPercentOffApplesOffer implements Offer { - private final Product discountedProduct; - private final BigDecimal percentage; private final LocalDate start; private final LocalDate end; public TenPercentOffApplesOffer(LocalDate today) { - discountedProduct = apples(); - percentage = new BigDecimal(10); start = today.plusDays(3); end = today.plusMonths(1).with(lastDayOfMonth()); } @@ -29,7 +25,7 @@ public Basket apply(Basket basket, LocalDate date) { return basket; basket.getProducts().stream() - .filter(product -> product.getName().equals(discountedProduct.getName())) + .filter(product -> product.getName().equals(apples().getName())) .forEach(product -> product.setDiscountPrice(setDiscountPrice(product))); return basket; } @@ -40,6 +36,7 @@ private boolean notApplicable(LocalDate date) { private BigDecimal setDiscountPrice(Product product) { BigDecimal price = product.getPrice(); + BigDecimal percentage = new BigDecimal(10); BigDecimal discount = price.divide(new BigDecimal("100.00")).multiply(percentage); return price.subtract(discount); } From ebb3b6a0f3346acbb8943f2c46ff1ace261d11ff Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 03:29:43 +0100 Subject: [PATCH 15/27] Create buy two soups get bread half price offer --- .../BuyTwoSoupsGetBreadHalfPriceOffer.java | 50 +++++++ ...BuyTwoSoupsGetBreadHalfPriceOfferTest.java | 135 ++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 src/main/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOffer.java create mode 100644 src/test/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOfferTest.java diff --git a/src/main/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOffer.java b/src/main/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOffer.java new file mode 100644 index 00000000..307552c3 --- /dev/null +++ b/src/main/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOffer.java @@ -0,0 +1,50 @@ +package com.ford.henrysgroceries.offers; + +import com.ford.henrysgroceries.Basket; +import com.ford.henrysgroceries.products.Product; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; + +import static com.ford.henrysgroceries.products.ProductHelper.bread; +import static com.ford.henrysgroceries.products.ProductHelper.soup; + +public class BuyTwoSoupsGetBreadHalfPriceOffer implements Offer { + private final LocalDate start; + private final LocalDate end; + + public BuyTwoSoupsGetBreadHalfPriceOffer(LocalDate today) { + start = today.minusDays(1); + end = start.plusDays(7); + } + + @Override + public Basket apply(Basket basket, LocalDate date) { + if (notApplicable(date)) + return basket; + + List products = basket.getProducts(); + long numberOfSoup = products.stream() + .filter(product -> product.getName().equals(soup().getName())) + .count(); + + if (numberOfSoup < 2) + return basket; + + long numberOfDiscountsToApply = numberOfSoup / 2; + + for (Product product : products) { + if (product.getName().equals(bread().getName()) && numberOfDiscountsToApply > 0) { + product.setDiscountPrice(product.getPrice().divide(new BigDecimal(2))); + numberOfDiscountsToApply--; + } + } + + return basket; + } + + private boolean notApplicable(LocalDate date) { + return date.isBefore(start) || date.isAfter(end); + } +} diff --git a/src/test/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOfferTest.java b/src/test/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOfferTest.java new file mode 100644 index 00000000..32420601 --- /dev/null +++ b/src/test/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOfferTest.java @@ -0,0 +1,135 @@ +package com.ford.henrysgroceries.offers; + +import com.ford.henrysgroceries.Basket; +import com.ford.henrysgroceries.products.Product; +import org.junit.Test; + +import java.math.BigDecimal; +import java.time.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.ford.henrysgroceries.products.ProductHelper.*; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public class BuyTwoSoupsGetBreadHalfPriceOfferTest { + private Clock fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + private LocalDate today = LocalDate.now(fixedClock); + private List offers = Collections.singletonList(new BuyTwoSoupsGetBreadHalfPriceOffer(today)); + + @Test + public void emptyBasketTotalsToZero() { + Basket basket = new Basket(offers, fixedClock); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(BigDecimal.ZERO), is(0)); + } + + @Test + public void offerDoesNotApplyTwoDaysAgo() { + Clock clock = Clock.offset(fixedClock, Duration.ofDays(-2)); + Basket basket = new Basket(offers, clock, soup(), soup(), bread()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("2.10")), is(0)); + } + + @Test + public void offerStartsFromYesterday() { + Clock clock = Clock.offset(fixedClock, Duration.ofDays(-1)); + Basket basket = new Basket(offers, clock, soup(), soup(), bread()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("1.70")), is(0)); + } + + @Test + public void offerAppliesForSevenDays() { + Clock clock = Clock.offset(fixedClock, Duration.ofDays(6)); + Basket basket = new Basket(offers, clock, soup(), soup(), bread()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("1.70")), is(0)); + } + + @Test + public void offerEndsAfterSevenDays() { + Clock clock = Clock.offset(fixedClock, Duration.ofDays(7)); + Basket basket = new Basket(offers, clock, soup(), soup(), bread()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("2.10")), is(0)); + } + + @Test + public void offerAppliesToday() { + Basket basket = new Basket(offers, fixedClock, soup(), soup(), bread()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("1.70")), is(0)); + } + + @Test + public void noDiscountWhenNoSoupBought() { + Basket basket = new Basket(offers, fixedClock, bread()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("0.80")), is(0)); + } + + @Test + public void noDiscountWhenOnlyOneSoupBought() { + Basket basket = new Basket(offers, fixedClock, soup(), bread()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("1.45")), is(0)); + } + + @Test + public void onlyDiscountedAppliedWhenTwoSoupsBought() { + Basket basket = new Basket(offers, fixedClock, soup(), soup(), bread(), bread()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("2.50")), is(0)); + } + + @Test + public void onlyOneDiscountAppliedWhenThreeSoupsBought() { + Basket basket = new Basket(offers, fixedClock, soup(), soup(), soup(), bread(), bread()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("3.15")), is(0)); + } + + @Test + public void twoDiscountsAppliedWhenFourSoupsBought() { + Basket basket = new Basket(offers, fixedClock, soup(), soup(), soup(), soup(), bread(), bread()); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("3.40")), is(0)); + } + + @Test + public void offerNotAppliedToOtherProducts() { + for (Product product : Arrays.asList(milk(), apples())) { + Basket basket = new Basket(offers, fixedClock, product); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(product.getPrice()), is(0)); + } + } +} From e6f3696aa71c9f49489068dc0da79538131d0c61 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 03:30:36 +0100 Subject: [PATCH 16/27] Remove white space --- .../henrysgroceries/offers/TenPercentOffApplesOfferTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java b/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java index cb56f9d4..7f3cb538 100644 --- a/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java +++ b/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java @@ -45,7 +45,7 @@ public void offerDoesNotApplyTwoDaysFromNow() { Clock clock = Clock.offset(fixedClock, Duration.ofDays(2)); Basket basket = new Basket(offers, clock, apples()); - BigDecimal total = basket.calculateTotal( ); + BigDecimal total = basket.calculateTotal(); assertThat(total.compareTo(apples().getPrice()), is(0)); } From f820966479396f690b4614a3039a302d8935376c Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 04:00:25 +0100 Subject: [PATCH 17/27] Push duplicate code into new abstract offer class --- .../ford/henrysgroceries/offers/AbstractOffer.java | 12 ++++++++++++ .../offers/BuyTwoSoupsGetBreadHalfPriceOffer.java | 9 +-------- .../offers/TenPercentOffApplesOffer.java | 9 +-------- 3 files changed, 14 insertions(+), 16 deletions(-) create mode 100644 src/main/java/com/ford/henrysgroceries/offers/AbstractOffer.java diff --git a/src/main/java/com/ford/henrysgroceries/offers/AbstractOffer.java b/src/main/java/com/ford/henrysgroceries/offers/AbstractOffer.java new file mode 100644 index 00000000..398a9555 --- /dev/null +++ b/src/main/java/com/ford/henrysgroceries/offers/AbstractOffer.java @@ -0,0 +1,12 @@ +package com.ford.henrysgroceries.offers; + +import java.time.LocalDate; + +public abstract class AbstractOffer implements Offer { + LocalDate start; + LocalDate end; + + boolean notApplicable(LocalDate date) { + return date.isBefore(start) || date.isAfter(end); + } +} diff --git a/src/main/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOffer.java b/src/main/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOffer.java index 307552c3..d30c484d 100644 --- a/src/main/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOffer.java +++ b/src/main/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOffer.java @@ -10,10 +10,7 @@ import static com.ford.henrysgroceries.products.ProductHelper.bread; import static com.ford.henrysgroceries.products.ProductHelper.soup; -public class BuyTwoSoupsGetBreadHalfPriceOffer implements Offer { - private final LocalDate start; - private final LocalDate end; - +public class BuyTwoSoupsGetBreadHalfPriceOffer extends AbstractOffer { public BuyTwoSoupsGetBreadHalfPriceOffer(LocalDate today) { start = today.minusDays(1); end = start.plusDays(7); @@ -43,8 +40,4 @@ public Basket apply(Basket basket, LocalDate date) { return basket; } - - private boolean notApplicable(LocalDate date) { - return date.isBefore(start) || date.isAfter(end); - } } diff --git a/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java b/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java index 5148c656..2b7604d9 100644 --- a/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java +++ b/src/main/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOffer.java @@ -9,10 +9,7 @@ import static com.ford.henrysgroceries.products.ProductHelper.apples; import static java.time.temporal.TemporalAdjusters.lastDayOfMonth; -public class TenPercentOffApplesOffer implements Offer { - - private final LocalDate start; - private final LocalDate end; +public class TenPercentOffApplesOffer extends AbstractOffer { public TenPercentOffApplesOffer(LocalDate today) { start = today.plusDays(3); @@ -30,10 +27,6 @@ public Basket apply(Basket basket, LocalDate date) { return basket; } - private boolean notApplicable(LocalDate date) { - return date.isBefore(start) || date.isAfter(end); - } - private BigDecimal setDiscountPrice(Product product) { BigDecimal price = product.getPrice(); BigDecimal percentage = new BigDecimal(10); From 9ea42e863b1d701614b885131461f7da541d3ebd Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 04:01:57 +0100 Subject: [PATCH 18/27] Include offers in BasketRunner --- .../java/com/ford/henrysgroceries/BasketRunner.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ford/henrysgroceries/BasketRunner.java b/src/main/java/com/ford/henrysgroceries/BasketRunner.java index a0606b8b..9cc0ef6b 100644 --- a/src/main/java/com/ford/henrysgroceries/BasketRunner.java +++ b/src/main/java/com/ford/henrysgroceries/BasketRunner.java @@ -1,6 +1,14 @@ package com.ford.henrysgroceries; +import com.ford.henrysgroceries.offers.BuyTwoSoupsGetBreadHalfPriceOffer; +import com.ford.henrysgroceries.offers.Offer; +import com.ford.henrysgroceries.offers.TenPercentOffApplesOffer; + import java.io.PrintStream; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Scanner; import static com.ford.henrysgroceries.products.ProductHelper.*; @@ -19,7 +27,10 @@ public BasketRunner(Basket basket, Scanner scanner, PrintStream printStream) { } public static void main(String[] args) { - BasketRunner basketRunner = new BasketRunner(new Basket(), new Scanner(System.in), System.out); + Offer applesOffer = new TenPercentOffApplesOffer(LocalDate.now()); + Offer breadOffer = new BuyTwoSoupsGetBreadHalfPriceOffer(LocalDate.now()); + List offers = Arrays.asList(applesOffer, breadOffer); + BasketRunner basketRunner = new BasketRunner(new Basket(offers), new Scanner(System.in), System.out); basketRunner.run(); } From ea6f02b04c97258073af19ba168d86153437f37d Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 04:34:56 +0100 Subject: [PATCH 19/27] Expand basket tests --- .../com/ford/henrysgroceries/BasketTest.java | 84 +++++++++++++++++-- 1 file changed, 79 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/ford/henrysgroceries/BasketTest.java b/src/test/java/com/ford/henrysgroceries/BasketTest.java index b87774fb..57aa8a17 100644 --- a/src/test/java/com/ford/henrysgroceries/BasketTest.java +++ b/src/test/java/com/ford/henrysgroceries/BasketTest.java @@ -1,20 +1,30 @@ package com.ford.henrysgroceries; +import com.ford.henrysgroceries.offers.BuyTwoSoupsGetBreadHalfPriceOffer; +import com.ford.henrysgroceries.offers.Offer; +import com.ford.henrysgroceries.offers.TenPercentOffApplesOffer; import com.ford.henrysgroceries.products.Product; import org.junit.Test; import java.math.BigDecimal; +import java.time.*; import java.util.Arrays; +import java.util.List; import static com.ford.henrysgroceries.products.ProductHelper.*; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; public class BasketTest { + private Clock fixedClock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + private Basket basket; + private Offer applesOffer = new TenPercentOffApplesOffer(LocalDate.now()); + private Offer breadOffer = new BuyTwoSoupsGetBreadHalfPriceOffer(LocalDate.now()); + private List offers = Arrays.asList(applesOffer, breadOffer); @Test - public void emptyBasketTotalIsZero() { + public void emptyBasketWithNoOffersTotalsToZero() { basket = new Basket(); BigDecimal total = basket.calculateTotal(); @@ -22,6 +32,15 @@ public void emptyBasketTotalIsZero() { assertThat(total.compareTo(BigDecimal.ZERO), is(0)); } + @Test + public void emptyBasketWithOffersTotalsToZero() { + basket = new Basket(offers); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(BigDecimal.ZERO), is(0)); + } + @Test public void givesCorrectTotalForSingleProducts() { for (Product product : Arrays.asList(soup(), bread(), milk(), apples())) { @@ -54,15 +73,70 @@ public void givesCorrectTotalForMoreThanOneProductOfSameType() { assertThat(total.compareTo(new BigDecimal("1.50")), is(0)); } + @Test + public void threeTinsOfSoupAndTwoLoavesOfBreadBoughtTodayCosts3Pounds15Pence() { + givenBasketHasProducts( + soup(), soup(), soup(), + bread(), bread() + ); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("3.15")), is(0)); + } + + @Test + public void sixApplesAndOneBottleOfMilkBoughtTodayCosts1Pound90Pence() { + givenBasketHasProducts( + apples(), apples(), apples(), + apples(), apples(), apples(), + milk() + ); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("1.90")), is(0)); + } + + @Test + public void sixApplesAndOneBottleOfMilkBoughtIn5DaysTimeCosts1Pound84Pence() { + Clock clock = Clock.offset(fixedClock, Duration.ofDays(5)); + givenBasketHasProducts( + clock, + apples(), apples(), apples(), + apples(), apples(), apples(), + milk() + ); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("1.84")), is(0)); + } + + @Test + public void threeApplesAndTwoTinsOfSoupAndAndOneLoafOfBreadBoughtIn5DaysTimeCosts1Pound97Pence() { + Clock clock = Clock.offset(fixedClock, Duration.ofDays(5)); + givenBasketHasProducts( + clock, + apples(), apples(), apples(), + soup(), soup(), + bread() + ); + + BigDecimal total = basket.calculateTotal(); + + assertThat(total.compareTo(new BigDecimal("1.97")), is(0)); + } + private void givenBasketHasProduct(Product product) { givenBasketHasProducts(product); } private void givenBasketHasProducts(Product... products) { - basket = new Basket(); + givenBasketHasProducts(fixedClock, products); + } - for (Product product : products) { - basket.addProduct(product); - } + private void givenBasketHasProducts(Clock clock, Product... products) { + basket = new Basket(offers, clock, products); } } From 5144c35336cd95a4ee6a1e5135de31c518216e58 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 04:51:16 +0100 Subject: [PATCH 20/27] Format prices and display discounts --- src/main/java/com/ford/henrysgroceries/Basket.java | 10 ++++++++-- .../com/ford/henrysgroceries/products/Product.java | 4 ++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ford/henrysgroceries/Basket.java b/src/main/java/com/ford/henrysgroceries/Basket.java index d3e4691b..7ba5a386 100644 --- a/src/main/java/com/ford/henrysgroceries/Basket.java +++ b/src/main/java/com/ford/henrysgroceries/Basket.java @@ -4,6 +4,7 @@ import com.ford.henrysgroceries.products.Product; import java.math.BigDecimal; +import java.text.NumberFormat; import java.time.Clock; import java.time.LocalDate; import java.util.ArrayList; @@ -50,17 +51,22 @@ public List getProducts() { public void addProduct(Product product) { this.products.add(product); + calculateTotal(); } @Override public String toString() { StringBuilder sb = new StringBuilder("Basket:\n"); products.forEach(product -> display(sb, product)); - sb.append("Total: £").append(calculateTotal()).append("\n"); + sb.append("Total: ").append(format(calculateTotal())).append("\n"); return sb.toString(); } private StringBuilder display(StringBuilder sb, Product product) { - return sb.append(product.getName()).append(" ").append(product.getPrice()).append("\n"); + return sb.append(product.getName()).append(" ").append(format(product.getDisplayPrice())).append("\n"); + } + + private String format(BigDecimal price) { + return NumberFormat.getCurrencyInstance().format(price); } } diff --git a/src/main/java/com/ford/henrysgroceries/products/Product.java b/src/main/java/com/ford/henrysgroceries/products/Product.java index e359e1ce..caee2701 100644 --- a/src/main/java/com/ford/henrysgroceries/products/Product.java +++ b/src/main/java/com/ford/henrysgroceries/products/Product.java @@ -35,6 +35,10 @@ public BigDecimal getDiscountPrice() { return discountPrice; } + public BigDecimal getDisplayPrice() { + return hasDiscount() ? discountPrice : price; + } + public void setDiscountPrice(BigDecimal discountPrice) { this.discountPrice = discountPrice; } From 8ef31934220e0ad586d2d53e6e25fc5d0b7173d2 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 04:54:13 +0100 Subject: [PATCH 21/27] Improve loop varible name --- src/main/java/com/ford/henrysgroceries/BasketRunner.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ford/henrysgroceries/BasketRunner.java b/src/main/java/com/ford/henrysgroceries/BasketRunner.java index 9cc0ef6b..6a522481 100644 --- a/src/main/java/com/ford/henrysgroceries/BasketRunner.java +++ b/src/main/java/com/ford/henrysgroceries/BasketRunner.java @@ -35,7 +35,7 @@ public static void main(String[] args) { } void run() { - boolean flag = true; + boolean addMoreToBasket = true; do { printStream.print("Please enter the first letter of the product you wish to add to your basket: [S]oup, [B]read, [M]ilk, [A]pples or [Q]uit: "); @@ -64,7 +64,7 @@ void run() { case "Q": printStream.print("\n"); - flag = false; + addMoreToBasket = false; break; default: @@ -72,6 +72,6 @@ void run() { } printStream.println(basket); - } while (flag); + } while (addMoreToBasket); } } From 4716640d209d191b167f60d74659d689656f7d02 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 04:56:19 +0100 Subject: [PATCH 22/27] Extract duplicated date to variable --- src/main/java/com/ford/henrysgroceries/BasketRunner.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ford/henrysgroceries/BasketRunner.java b/src/main/java/com/ford/henrysgroceries/BasketRunner.java index 6a522481..6dde685b 100644 --- a/src/main/java/com/ford/henrysgroceries/BasketRunner.java +++ b/src/main/java/com/ford/henrysgroceries/BasketRunner.java @@ -27,8 +27,9 @@ public BasketRunner(Basket basket, Scanner scanner, PrintStream printStream) { } public static void main(String[] args) { - Offer applesOffer = new TenPercentOffApplesOffer(LocalDate.now()); - Offer breadOffer = new BuyTwoSoupsGetBreadHalfPriceOffer(LocalDate.now()); + LocalDate today = LocalDate.now(); + Offer applesOffer = new TenPercentOffApplesOffer(today); + Offer breadOffer = new BuyTwoSoupsGetBreadHalfPriceOffer(today); List offers = Arrays.asList(applesOffer, breadOffer); BasketRunner basketRunner = new BasketRunner(new Basket(offers), new Scanner(System.in), System.out); basketRunner.run(); From f289fde720fdb8eeb6ff970413cbe621747a65af Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 04:57:13 +0100 Subject: [PATCH 23/27] Remove ununsed import --- src/main/java/com/ford/henrysgroceries/BasketRunner.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/ford/henrysgroceries/BasketRunner.java b/src/main/java/com/ford/henrysgroceries/BasketRunner.java index 6dde685b..4cd8d774 100644 --- a/src/main/java/com/ford/henrysgroceries/BasketRunner.java +++ b/src/main/java/com/ford/henrysgroceries/BasketRunner.java @@ -7,7 +7,6 @@ import java.io.PrintStream; import java.time.LocalDate; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Scanner; From 5953be1d012f4fce7eb27c65dcecddc45f3dfecd Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 05:04:43 +0100 Subject: [PATCH 24/27] Do not display basket again when quitting --- .../java/com/ford/henrysgroceries/BasketRunner.java | 3 ++- .../com/ford/henrysgroceries/BasketRunnerTest.java | 11 ++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/ford/henrysgroceries/BasketRunner.java b/src/main/java/com/ford/henrysgroceries/BasketRunner.java index 4cd8d774..ed7dbf89 100644 --- a/src/main/java/com/ford/henrysgroceries/BasketRunner.java +++ b/src/main/java/com/ford/henrysgroceries/BasketRunner.java @@ -71,7 +71,8 @@ void run() { printStream.print("Product not recognised\n"); } - printStream.println(basket); + if (addMoreToBasket) + printStream.println(basket); } while (addMoreToBasket); } } diff --git a/src/test/java/com/ford/henrysgroceries/BasketRunnerTest.java b/src/test/java/com/ford/henrysgroceries/BasketRunnerTest.java index c0c030e5..4ac714dc 100644 --- a/src/test/java/com/ford/henrysgroceries/BasketRunnerTest.java +++ b/src/test/java/com/ford/henrysgroceries/BasketRunnerTest.java @@ -32,15 +32,11 @@ public void setUp() { @Test public void addNothingToBasketThenQuit() { - when(basket.toString()).thenReturn("Basket:\nTotal: £0.00\n"); BasketRunner basketRunner = new BasketRunner(basket, new Scanner("Q\n"), new PrintStream(output)); basketRunner.run(); - assertThat(output.toString(), is( - INSTRUCTIONS + "\n" + - "Basket:\n" + - "Total: £0.00\n\r\n")); + assertThat(output.toString(), is(INSTRUCTIONS + "\n")); } @Test @@ -57,9 +53,6 @@ public void addMilkToBasketThenQuit() { "Milk £1.30\n" + "Total: £1.30\n\r" + "\n" + - INSTRUCTIONS + "\n" + - "Basket:\n" + - "Milk £1.30\n" + - "Total: £1.30\n\r\n")); + INSTRUCTIONS + "\n")); } } From 2d80e153dd2e53ae28cd75c7350e52ca7ae4eb26 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 05:59:53 +0100 Subject: [PATCH 25/27] Simplify tests with custom matcher for BigDecimals --- .../com/ford/henrysgroceries/BasketTest.java | 20 +++++------ ...BuyTwoSoupsGetBreadHalfPriceOfferTest.java | 26 +++++++-------- .../offers/TenPercentOffApplesOfferTest.java | 20 +++++------ .../utils/EqualsBigDecimalMatcher.java | 33 +++++++++++++++++++ 4 files changed, 66 insertions(+), 33 deletions(-) create mode 100644 src/test/java/com/ford/henrysgroceries/utils/EqualsBigDecimalMatcher.java diff --git a/src/test/java/com/ford/henrysgroceries/BasketTest.java b/src/test/java/com/ford/henrysgroceries/BasketTest.java index 57aa8a17..717d043e 100644 --- a/src/test/java/com/ford/henrysgroceries/BasketTest.java +++ b/src/test/java/com/ford/henrysgroceries/BasketTest.java @@ -12,7 +12,7 @@ import java.util.List; import static com.ford.henrysgroceries.products.ProductHelper.*; -import static org.hamcrest.Matchers.is; +import static com.ford.henrysgroceries.utils.EqualsBigDecimalMatcher.is; import static org.junit.Assert.assertThat; public class BasketTest { @@ -29,7 +29,7 @@ public void emptyBasketWithNoOffersTotalsToZero() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(BigDecimal.ZERO), is(0)); + assertThat(total, is(BigDecimal.ZERO)); } @Test @@ -38,7 +38,7 @@ public void emptyBasketWithOffersTotalsToZero() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(BigDecimal.ZERO), is(0)); + assertThat(total, is(BigDecimal.ZERO)); } @Test @@ -48,7 +48,7 @@ public void givesCorrectTotalForSingleProducts() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(product.getPrice()), is(0)); + assertThat(total, is(product.getPrice())); } } @@ -58,7 +58,7 @@ public void givesCorrectTotalForMilkAndBread() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("2.10")), is(0)); + assertThat(total, is(new BigDecimal("2.10"))); } @Test @@ -70,7 +70,7 @@ public void givesCorrectTotalForMoreThanOneProductOfSameType() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("1.50")), is(0)); + assertThat(total, is(new BigDecimal("1.50"))); } @Test @@ -82,7 +82,7 @@ public void threeTinsOfSoupAndTwoLoavesOfBreadBoughtTodayCosts3Pounds15Pence() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("3.15")), is(0)); + assertThat(total, is(new BigDecimal("3.15"))); } @Test @@ -95,7 +95,7 @@ public void sixApplesAndOneBottleOfMilkBoughtTodayCosts1Pound90Pence() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("1.90")), is(0)); + assertThat(total, is(new BigDecimal("1.90"))); } @Test @@ -110,7 +110,7 @@ public void sixApplesAndOneBottleOfMilkBoughtIn5DaysTimeCosts1Pound84Pence() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("1.84")), is(0)); + assertThat(total, is(new BigDecimal("1.84"))); } @Test @@ -125,7 +125,7 @@ public void threeApplesAndTwoTinsOfSoupAndAndOneLoafOfBreadBoughtIn5DaysTimeCost BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("1.97")), is(0)); + assertThat(total, is(new BigDecimal("1.97"))); } private void givenBasketHasProduct(Product product) { diff --git a/src/test/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOfferTest.java b/src/test/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOfferTest.java index 32420601..f0de9bf6 100644 --- a/src/test/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOfferTest.java +++ b/src/test/java/com/ford/henrysgroceries/offers/BuyTwoSoupsGetBreadHalfPriceOfferTest.java @@ -11,7 +11,7 @@ import java.util.List; import static com.ford.henrysgroceries.products.ProductHelper.*; -import static org.hamcrest.Matchers.is; +import static com.ford.henrysgroceries.utils.EqualsBigDecimalMatcher.is; import static org.junit.Assert.assertThat; public class BuyTwoSoupsGetBreadHalfPriceOfferTest { @@ -25,7 +25,7 @@ public void emptyBasketTotalsToZero() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(BigDecimal.ZERO), is(0)); + assertThat(total, is(BigDecimal.ZERO)); } @Test @@ -35,7 +35,7 @@ public void offerDoesNotApplyTwoDaysAgo() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("2.10")), is(0)); + assertThat(total, is(new BigDecimal("2.10"))); } @Test @@ -45,7 +45,7 @@ public void offerStartsFromYesterday() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("1.70")), is(0)); + assertThat(total, is(new BigDecimal("1.70"))); } @Test @@ -55,7 +55,7 @@ public void offerAppliesForSevenDays() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("1.70")), is(0)); + assertThat(total, is(new BigDecimal("1.70"))); } @Test @@ -65,7 +65,7 @@ public void offerEndsAfterSevenDays() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("2.10")), is(0)); + assertThat(total, is(new BigDecimal("2.10"))); } @Test @@ -74,7 +74,7 @@ public void offerAppliesToday() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("1.70")), is(0)); + assertThat(total, is(new BigDecimal("1.70"))); } @Test @@ -83,7 +83,7 @@ public void noDiscountWhenNoSoupBought() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("0.80")), is(0)); + assertThat(total, is(new BigDecimal("0.80"))); } @Test @@ -92,7 +92,7 @@ public void noDiscountWhenOnlyOneSoupBought() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("1.45")), is(0)); + assertThat(total, is(new BigDecimal("1.45"))); } @Test @@ -101,7 +101,7 @@ public void onlyDiscountedAppliedWhenTwoSoupsBought() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("2.50")), is(0)); + assertThat(total, is(new BigDecimal("2.50"))); } @Test @@ -110,7 +110,7 @@ public void onlyOneDiscountAppliedWhenThreeSoupsBought() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("3.15")), is(0)); + assertThat(total, is(new BigDecimal("3.15"))); } @Test @@ -119,7 +119,7 @@ public void twoDiscountsAppliedWhenFourSoupsBought() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("3.40")), is(0)); + assertThat(total, is(new BigDecimal("3.40"))); } @Test @@ -129,7 +129,7 @@ public void offerNotAppliedToOtherProducts() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(product.getPrice()), is(0)); + assertThat(total, is(product.getPrice())); } } } diff --git a/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java b/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java index 7f3cb538..d3256ecd 100644 --- a/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java +++ b/src/test/java/com/ford/henrysgroceries/offers/TenPercentOffApplesOfferTest.java @@ -11,9 +11,9 @@ import java.util.List; import static com.ford.henrysgroceries.products.ProductHelper.*; +import static com.ford.henrysgroceries.utils.EqualsBigDecimalMatcher.is; import static java.time.temporal.ChronoUnit.DAYS; import static java.time.temporal.TemporalAdjusters.lastDayOfMonth; -import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; public class TenPercentOffApplesOfferTest { @@ -28,7 +28,7 @@ public void emptyBasketNoOffersTotalIsZero() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(BigDecimal.ZERO), is(0)); + assertThat(total, is(BigDecimal.ZERO)); } @Test @@ -37,7 +37,7 @@ public void emptyBasketWithOffersTotalIsZero() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(BigDecimal.ZERO), is(0)); + assertThat(total, is(BigDecimal.ZERO)); } @Test @@ -47,7 +47,7 @@ public void offerDoesNotApplyTwoDaysFromNow() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(apples().getPrice()), is(0)); + assertThat(total, is(apples().getPrice())); } @Test @@ -57,7 +57,7 @@ public void offerStartsThreeDaysFromNow() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("0.09")), is(0)); + assertThat(total, is(new BigDecimal("0.09"))); } @Test @@ -67,7 +67,7 @@ public void offerAppliesTillEndOfFollowingMonth() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("0.09")), is(0)); + assertThat(total, is(new BigDecimal("0.09"))); } @Test @@ -77,7 +77,7 @@ public void offerDoesNotApplyOneDayAfterEndOfFollowingMonth() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(apples().getPrice()), is(0)); + assertThat(total, is(apples().getPrice())); } @Test @@ -87,7 +87,7 @@ public void oneAppleIsDiscountedBy10Percent() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("0.09")), is(0)); + assertThat(total, is(new BigDecimal("0.09"))); } @Test @@ -97,7 +97,7 @@ public void manyApplesAreDiscountedBy10Percent() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(new BigDecimal("0.27")), is(0)); + assertThat(total, is(new BigDecimal("0.27"))); } @Test @@ -107,7 +107,7 @@ public void offerNotAppliedToOtherProducts() { BigDecimal total = basket.calculateTotal(); - assertThat(total.compareTo(product.getPrice()), is(0)); + assertThat(total, is(product.getPrice())); } } diff --git a/src/test/java/com/ford/henrysgroceries/utils/EqualsBigDecimalMatcher.java b/src/test/java/com/ford/henrysgroceries/utils/EqualsBigDecimalMatcher.java new file mode 100644 index 00000000..5d1004ae --- /dev/null +++ b/src/test/java/com/ford/henrysgroceries/utils/EqualsBigDecimalMatcher.java @@ -0,0 +1,33 @@ +package com.ford.henrysgroceries.utils; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import java.math.BigDecimal; +import java.text.NumberFormat; + +public class EqualsBigDecimalMatcher extends TypeSafeMatcher { + + private BigDecimal expected; + + public static Matcher is(BigDecimal expected) { + return new EqualsBigDecimalMatcher(expected); + } + + private EqualsBigDecimalMatcher(BigDecimal expected) { + this.expected = expected; + } + + @Override + protected boolean matchesSafely(BigDecimal actual) { + return actual.setScale(2).compareTo(expected.setScale(2)) == 0; + } + + @Override + public void describeTo(Description description) { + description.appendText("equals " + NumberFormat.getCurrencyInstance().format(expected)); + } + + +} From a4d173bfc2831200eb9ac933cb2ac894774ab46c Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 06:33:54 +0100 Subject: [PATCH 26/27] Update pom to allow build of runnable jar with dependencies --- pom.xml | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 71fb7825..776291f0 100644 --- a/pom.xml +++ b/pom.xml @@ -41,5 +41,30 @@ - - \ No newline at end of file + + + + maven-assembly-plugin + + + package + + single + + + + + + + true + com.ford.henrysgroceries.BasketRunner + + + + jar-with-dependencies + + + + + + From ee89455b976ff5802625b395ccbb838beabaeaa3 Mon Sep 17 00:00:00 2001 From: Saqib Arif Date: Mon, 16 Sep 2019 06:39:37 +0100 Subject: [PATCH 27/27] Update README with instructions on how to run --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e75f4549..c254212f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # Java Exercise -This is a simple exercise to allow you to demostrate your software engineering skillset. It's completly up to you how long you give yourself, stop when you're happy with the quality of your work, but we don't expect it to take too long. +This is a simple exercise to allow you to demonstrate your software engineering skillset. It's completely up to you how long you give yourself, stop when you're happy with the quality of your work, but we don't expect it to take too long. + +## To Run + 1. First build with `mvn clean install`. + 2. Run with `java -jar /insert/path/to/your/maven/repository/henrys-groceries-1.0-SNAPSHOT-jar-with-dependencies.jar`. + 3. Alternatively, just run `BasketRunner` class in your IDE. ## Instructions 1. Please fork this repository and work on your fork. @@ -18,7 +23,7 @@ This is a simple exercise to allow you to demostrate your software engineering s A local shop, Henry’s Grocery, has asked you to author an IT solution for them to price up a basket of shopping for their customers. -Henry’s Grocery, currently only stocks four items and has two promotions. These are as follows: +Henry’s Grocery currently only stocks four items and has two promotions. These are as follows: ### Stock Items