diff --git a/class-model-core.png b/class-model-core.png new file mode 100644 index 000000000..aa792e704 Binary files /dev/null and b/class-model-core.png differ diff --git a/class-model-extension.png b/class-model-extension.png new file mode 100644 index 000000000..05e439483 Binary files /dev/null and b/class-model-extension.png differ diff --git a/domain-model-core.md b/domain-model-core.md new file mode 100644 index 000000000..47845d002 --- /dev/null +++ b/domain-model-core.md @@ -0,0 +1,90 @@ +# Bob's bagels +### Dorota WlazÅ‚o, Jan Rusak + +``` +1. +As a member of the public, +So I can order a bagel before work, +I'd like to add a specific type of bagel to my basket. +``` +``` +2. +As a member of the public, +So I can change my order, +I'd like to remove a bagel from my basket. +``` +``` +3. +As a member of the public, +So that I can not overfill my small bagel basket +I'd like to know when my basket is full when I try adding an item beyond my basket capacity. +``` +``` +4. +As a Bob's Bagels manager, +So that I can expand my business, +I’d like to change the capacity of baskets. +``` +``` +5. +As a member of the public +So that I can maintain my sanity +I'd like to know if I try to remove an item that doesn't exist in my basket. +``` +``` +6. +As a customer, +So I know how much money I need, +I'd like to know the total cost of items in my basket. +``` +``` +7. +As a customer, +So I know what the damage will be, +I'd like to know the cost of a bagel before I add it to my basket. +``` +``` +8. +As a customer, +So I can shake things up a bit, +I'd like to be able to choose fillings for my bagel. +``` +``` +9. +As a customer, +So I don't over-spend, +I'd like to know the cost of each filling before I add it to my bagel order. +``` +``` +10. +As the manager, +So we don't get any weird requests, +I want customers to only be able to order things that we stock in our inventory. +``` + +| Class | Field | Method | Condition | Output | +|--------|----------------------------------------|---------------------------------------------------|-----------------------------------------------------------------------------------------------|----------------------------------| +| Basket | Hashmap productsCount | boolean add(String productVariant, int amount) | if there are less products in total than capacity | true | +| | | | if there are more products in total than capacity or the productVariant is N/A or amount <= 0 | false | +| | int capacity | | | | +| | int currentAmountOfProducts | boolean remove(String productVariant, int amount) | if the amount of products of sku in basket is >= amount | true | +| | | | if the amount of products of sku in basket is < amount or amount <= 0 | false | +| | | boolean changeCapacity(int newCapacity) | if newCapacity >= currentAmount | true | +| | | | if newCapacity < currentAmount | false | +| | | double totalCost() | always | total cost of products in basket | +| | | double checkCostOfProduct(String productVariant) | always | cost of product by sku | + +| Class | Field | Method | Condition | Output | +|---------|----------------|--------|-----------|--------| +| Product | String sku | | | | +| | double price | | | | +| | String name | | | | +| | String variant | | | | + + +| Class | Field | Method | Condition | Output | +|-----------|-----------------------------------|-------------------------------------------|-----------------------------------|-----------------| +| Inventory | Hashmap products | Product getProduct(String productVariant) | if product exist in the inventory | Product product | +| | | | if product doesn't exists | null | + + diff --git a/domain-model-extension.md b/domain-model-extension.md new file mode 100644 index 000000000..e10133de9 --- /dev/null +++ b/domain-model-extension.md @@ -0,0 +1,70 @@ +| Class | Field | Method | Condition | Output | +|---------|--------------|-------------------|-----------|---------| +| Bargain | int packSize | bargain6Bagels() | | Bargain | +| | int packCost | bargain12Bagels() | | Bargain | + + +| Class | Field | Method | Condition | Output | +|--------|----------------------------------------|-----------------------------------------------|-------------------------------------------------------------------------------------------|--------| +| Basket | Hashmap productsCount | boolean add(String productSKU, int amount) | if there are less products in total than capacity | true | +| | int capacity | | if there are more products in total than capacity or the productSKU is N/A or amount <= 0 | false | +| | int currentAmountOfProducts | boolean remove(String productSKU, int amount) | if the amount of products of sku in basket is >= amount | true | +| | | | if the amount of products of sku in basket is < amount or amount <= 0 | false | +| | | boolean changeCapacity(int newCapacity) | if newCapacity >= currentAmount | true | +| | | | if newCapacity < currentAmount | false | +| | | void clearBasket() | | void | + + +| Class | Field | Method | Condition | Output | +|----------|------------------------------------|--------------------------------------------------------------|-----------|-------------------------------------| +| Checkout | HashMap costs | static int getProductsCost(String productSKU, int quantity) | | products cost without discounts | +| | HashMap amounts | static double countBasketCostWithoutDiscounts(Basket basket) | | basket total cost without discounts | +| | HashMap discounts | static double countBasketCostWithDiscounts(Basket basket) | | basket total cost with discounts | +| | double totalCost | Receipt getReceipt() | | receipt object | +| | double totalDiscount | | | | +| | Basket basket | | | | + + +| Class | Field | Method | Condition | Output | +|--------------------|---------------------------|---------------------------------------------------|-----------|--------------------------| +| CombinationBargain | String name | static List coffeePlusBagel() | | List | +| | List productsSKUs | | | | +| | int price | | | | + + +| Class | Field | Method | Condition | Output | +|-----------|-----------------------------------------------------|---------------------------------------------------------|-----------------------------|-----------------------------------| +| Inventory | static Hashmap products | static List getBargains(String productSKU) | if product not on any sale | empty List | +| | static List combinationBargains | | if product on sale | List | +| | static HashMap> bargains | static boolean productNotInInventory(String productSKU) | if product not in inventory | true | +| | | | if product in inventory | false | +| | | static int checkCostOfTheProduct(String productSKU) | if product in inventory | product price | +| | | | if product not in inventory | 0 | +| | | static Product getProduct(String productSKU) | if product in inventory | Product | +| | | | if product not in inventory | null | +| | | static String getProductDescription(String productSKU) | if product in inventory | variant + name, np. "Onion Bagel" | +| | | | if product not in inventory | productSKU | + + +| Class | Field | Method | Condition | Output | +|---------|----------------|--------|-----------|--------| +| Product | String sku | | | | +| | double price | | | | +| | String name | | | | +| | String variant | | | | + + +| Class | Field | Method | Condition | Output | +|---------|------------------------------------|---------------------------------------|----------------------------------|----------| +| Receipt | HashMap costs | double getDiscount(String productSKU) | if product discount in discounts | discount | +| | HashMap amounts | | if no discount for product | 0 | +| | HashMap discounts | double getCost(String productSKU) | if product cost in costs | cost | +| | double totalCost | | if product cost not in costs | 0 | +| | double totalDiscount | int getQuantity(String productSKU) | if product amount in amounts | quantity | +| | LocalDateTime creationDate | | if product amount not in amounts | 0 | + + +| Class | Field | Method | Condition | Output | +|------------------|-------|-----------------------------------------|-----------|-------------------| +| ReceiptGenerator | | String generateReceipt(Receipt receipt) | | formatted receipt | +| | | | | | diff --git a/src/main/java/com/booleanuk/core/.gitkeep b/src/main/java/com/booleanuk/core/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/main/java/com/booleanuk/core/Basket.java b/src/main/java/com/booleanuk/core/Basket.java new file mode 100644 index 000000000..a1c3643bb --- /dev/null +++ b/src/main/java/com/booleanuk/core/Basket.java @@ -0,0 +1,69 @@ +package com.booleanuk.core; + +import java.util.HashMap; + +public class Basket { + private final HashMap productsCount; + private int capacity; + private int currentAmountOfProducts; + + public Basket(int capacity) { + productsCount = new HashMap<>(); + this.capacity = capacity; + currentAmountOfProducts = 0; + } + + public boolean add(String productSKu, int amount){ + if (amount <= 0 || amount + currentAmountOfProducts > capacity || !Inventory.getProducts().containsKey(productSKu)) + return false; + + if (productsCount.containsKey(productSKu)) + productsCount.put(productSKu, productsCount.get(productSKu) + amount); + else + productsCount.put(productSKu, amount); + + currentAmountOfProducts += amount; + return true; + } + + public boolean remove(String productSKu, int amount){ + if (amount <= 0 || !productsCount.containsKey(productSKu) || productsCount.get(productSKu) < amount) + return false; + + productsCount.put(productSKu, productsCount.get(productSKu) - amount); + + if (productsCount.get(productSKu) == 0) + productsCount.remove(productSKu); + + currentAmountOfProducts -= amount; + return true; + } + + public boolean changeCapacity(int newCapacity){ + if (newCapacity < currentAmountOfProducts || newCapacity == 0) + return false; + + capacity = newCapacity; + return true; + } + + public double totalCost(){ + double cost = 0; + for (String productSKu : productsCount.keySet()) { + cost += productsCount.get(productSKu) * Inventory.getProducts() + .get(productSKu) + .getPrice(); + } + return cost; + } + + public double checkCostOfProduct(String productSKu){ + var products = Inventory.getProducts(); + + if (!products.containsKey(productSKu)) + return 0.0d; + + return products.get(productSKu) + .getPrice(); + } +} diff --git a/src/main/java/com/booleanuk/core/Inventory.java b/src/main/java/com/booleanuk/core/Inventory.java new file mode 100644 index 000000000..e3b02dc00 --- /dev/null +++ b/src/main/java/com/booleanuk/core/Inventory.java @@ -0,0 +1,28 @@ +package com.booleanuk.core; + +import java.util.HashMap; + +public class Inventory { + private static final HashMap products = new HashMap<>() {{ + put("BGLO", new Product("BGLO", 0.49d, "Bagle", "Onion")); + put("BGLP", new Product("BGLP", 0.39d, "Bagle", "Plain")); + put("BGLE", new Product("BGLE", 0.49d, "Bagle", "Everything")); + put("BGLS", new Product("BGLS", 0.49d, "Bagle", "Sesame")); + + put("COFB", new Product("COFB", 0.99d, "Coffee", "Black")); + put("COFW", new Product("COFW", 1.19d, "Coffee", "White")); + put("COFC", new Product("COFC", 1.29d, "Coffee", "Capuccino")); + put("COFL", new Product("COFL", 1.29d, "Coffee", "Latte")); + + put("FILB", new Product("FILB", 0.12d, "Filling", "Bacon")); + put("FILE", new Product("FILE", 0.12d, "Filling", "Egg")); + put("FILC", new Product("FILC", 0.12d, "Filling", "Cheese")); + put("FILX", new Product("FILX", 0.12d, "Filling", "Cream Cheese")); + put("FILS", new Product("FILS", 0.12d, "Filling", "Smoked Salmon")); + put("FILH", new Product("FILH", 0.12d, "Filling", "Ham")); + }}; + + public static HashMap getProducts() { + return products; + } +} diff --git a/src/main/java/com/booleanuk/core/Product.java b/src/main/java/com/booleanuk/core/Product.java new file mode 100644 index 000000000..470ea104a --- /dev/null +++ b/src/main/java/com/booleanuk/core/Product.java @@ -0,0 +1,31 @@ +package com.booleanuk.core; + +public class Product { + private String sku; + private double price; + private String name; + private String variant; + + public Product(String sku, double price, String name, String variant) { + this.sku = sku; + this.price = price; + this.name = name; + this.variant = variant; + } + + public String getSku() { + return sku; + } + + public double getPrice() { + return price; + } + + public String getName() { + return name; + } + + public String getVariant() { + return variant; + } +} diff --git a/src/main/java/com/booleanuk/extension/.gitkeep b/src/main/java/com/booleanuk/extension/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/main/java/com/booleanuk/extension/Bargain.java b/src/main/java/com/booleanuk/extension/Bargain.java new file mode 100644 index 000000000..ac7700c0d --- /dev/null +++ b/src/main/java/com/booleanuk/extension/Bargain.java @@ -0,0 +1,12 @@ +package com.booleanuk.extension; + +public record Bargain(int packSize, int packCost) { + + public static Bargain bargain6Bagels() { + return new Bargain(6, 249); + } + + public static Bargain bargain12Bagels() { + return new Bargain(12, 399); + } +} diff --git a/src/main/java/com/booleanuk/extension/Basket.java b/src/main/java/com/booleanuk/extension/Basket.java new file mode 100644 index 000000000..4c1bdf8fa --- /dev/null +++ b/src/main/java/com/booleanuk/extension/Basket.java @@ -0,0 +1,62 @@ +package com.booleanuk.extension; + +import java.util.HashMap; + +public class Basket { + + private final HashMap productsCount; + private int capacity; + private int currentAmountOfProducts; + + public Basket(int capacity) { + productsCount = new HashMap<>(); + this.capacity = capacity; + currentAmountOfProducts = 0; + } + + public boolean add(String productSKU, int amount){ + if (amount <= 0 || amount + currentAmountOfProducts > capacity || Inventory.productNotInInventory(productSKU)) + return false; + if (productsCount.containsKey(productSKU)) + productsCount.put(productSKU, productsCount.get(productSKU) + amount); + else + productsCount.put(productSKU, amount); + currentAmountOfProducts += amount; + return true; + } + + public boolean remove(String productSKU, int amount){ + if (amount <= 0 || !productsCount.containsKey(productSKU) || productsCount.get(productSKU) < amount) + return false; + productsCount.put(productSKU, productsCount.get(productSKU) - amount); + if (productsCount.get(productSKU) == 0) + productsCount.remove(productSKU); + currentAmountOfProducts -= amount; + return true; + } + + public boolean changeCapacity(int newCapacity){ + if (newCapacity < currentAmountOfProducts || newCapacity == 0) + return false; + capacity = newCapacity; + return true; + } + + public HashMap getProductsCount() { + return productsCount; + } + + public int getCapacity() { + return capacity; + } + + public int getCurrentAmountOfProducts() { + return currentAmountOfProducts; + } + + public void clearBasket() { + productsCount.clear(); + currentAmountOfProducts = 0; + } +} + diff --git a/src/main/java/com/booleanuk/extension/Checkout.java b/src/main/java/com/booleanuk/extension/Checkout.java new file mode 100644 index 000000000..c2bfef641 --- /dev/null +++ b/src/main/java/com/booleanuk/extension/Checkout.java @@ -0,0 +1,247 @@ +package com.booleanuk.extension; + +import java.util.*; + +public class Checkout { + private final HashMap costs; + private final HashMap amounts; + private final HashMap discounts; + private final double totalCost; + private final double totalDiscount; + private final Basket basket; + + public Checkout(Basket basket) { + this.basket = basket; + this.costs = new HashMap<>(); + this.amounts = new HashMap<>(); + this.discounts = new HashMap<>(); + + this.totalCost = countBasketCostWithDiscounts(); + this.totalDiscount = countBasketCostWithoutDiscounts(basket) - this.totalCost; + + } + + public static int getProductsCost(String productSKU, int quantity) { + return Inventory.checkCostOfTheProduct(productSKU) * quantity; + } + + private static int getMultiPricedProductsCost(String productSKU, HashMap basketProducts) { + var bargains = Inventory.getBargains(productSKU); + + if (bargains.isEmpty()) + return 0; + + bargains.sort(Comparator.comparingInt(Bargain::packSize)); + Collections.reverse(bargains); + + int cost = 0; + for (Bargain b: bargains) { + int packs = basketProducts.get(productSKU) / b.packSize(); + basketProducts.computeIfPresent(productSKU, (k, v) -> v - packs * b.packSize()); + cost += packs * b.packCost(); + } + + return cost; + } + + public static double countBasketCostWithoutDiscounts(Basket basket) { + return (double) basket.getProductsCount() + .entrySet() + .stream() + .map(s -> getProductsCost(s.getKey(), s.getValue())) + .reduce(Integer::sum) + .orElse(0) / 100; + } + + public static double countBasketCostWithDiscounts(Basket basket) { + int cost = 0; + HashMap productsInBasket = new HashMap<>(basket.getProductsCount()); + + for (String productSKU : productsInBasket.keySet()) + cost += getMultiPricedProductsCost(productSKU, productsInBasket); + + cost += getProductsCombinationsCost(productsInBasket); + + for (String productSKU : productsInBasket.keySet()) + cost += getProductsCost(productSKU, productsInBasket.get(productSKU)); + + return (double) cost / 100; + } + + private double countBasketCostWithDiscounts() { + int cost = 0; + HashMap productsInBasket = new HashMap<>(basket.getProductsCount()); + + for (String productSKU : productsInBasket.keySet()) { + int prevAmount = productsInBasket.get(productSKU); + int c = getMultiPricedProductsCost(productSKU, productsInBasket); + int actualAmount = productsInBasket.get(productSKU); + + updateProductsTrackMaps(productSKU, c, prevAmount - actualAmount); + cost += c; + } + + cost += getAndTrackProductsCombinationsCost(productsInBasket); + + for (String productSKU : productsInBasket.keySet()) { + int quantity = productsInBasket.get(productSKU); + int c = getProductsCost(productSKU, quantity); + updateProductsTrackMaps(productSKU, c, quantity); + cost += c; + } + + updateDiscountsTrackMap(); + + return (double) cost / 100; + } + + private void updateProductsTrackMaps(String productSKU, int cost, int quantity) { + if (cost == 0 || quantity == 0) + return; + + this.costs.computeIfPresent(productSKU, (k, v) -> v + cost); + this.costs.putIfAbsent(productSKU, cost); + + this.amounts.computeIfPresent(productSKU, (k, v) -> v + quantity); + this.amounts.putIfAbsent(productSKU, quantity); + } + + private void updateDiscountsTrackMap() { + for (String productSKU: costs.keySet()) { + int discount; + + if (Inventory.productNotInInventory(productSKU)) { + discount = getCombinationBargainProductsRawCost(productSKU) + - this.costs.get(productSKU); + } else + discount = getProductsCost(productSKU, this.amounts.get(productSKU)) + - this.costs.get(productSKU); + + if (discount != 0) + this.discounts.put(productSKU, discount); + } + } + + private int getCombinationBargainProductsRawCost(String productSKU) { + int cost = 0; + + var combinationBargain = Inventory.getCombinationBargains() + .stream() + .filter(b -> b.name().equals(productSKU)) + .findAny().orElse(null); + + if (combinationBargain != null) + cost = combinationBargain.productsSKUs() + .stream() + .map(s -> getProductsCost(s, this.amounts.get(productSKU))) + .reduce(Integer::sum) + .orElse(0); + + return cost; + } + + private static int getProductsCombinationsCost(HashMap basketProducts){ + int cost = 0; + + var combinationBargains = new ArrayList<>( + Inventory.getCombinationBargains() + ); + + sortCombinationBargainsByLeastProfitable(combinationBargains); + + for (CombinationBargain cb: combinationBargains) { + var combinationProductSKUs = cb.productsSKUs(); + + if (!basketProducts.keySet().containsAll(combinationProductSKUs)) + continue; + + while (productsInBasketProducts(combinationProductSKUs, basketProducts)) + { + for (String combinationProductSKU : combinationProductSKUs) + basketProducts.computeIfPresent(combinationProductSKU, (k, v) -> v - 1); + cost += cb.price(); + } + } + + return cost; + } + + private int getAndTrackProductsCombinationsCost(HashMap basketProducts){ + int cost = 0; + + var combinationBargains = new ArrayList<>( + Inventory.getCombinationBargains() + ); + + sortCombinationBargainsByLeastProfitable(combinationBargains); + + for (CombinationBargain cb: combinationBargains) { + var combinationProductSKUs = cb.productsSKUs(); + + if (!basketProducts.keySet().containsAll(combinationProductSKUs)) + continue; + + int quantity = 0; + int combinationCost = 0; + while (productsInBasketProducts(combinationProductSKUs, basketProducts)) + { + quantity++; + for (String combinationProductSKU : combinationProductSKUs) + basketProducts.computeIfPresent(combinationProductSKU, (k, v) -> v - 1); + + combinationCost += cb.price(); + } + + updateProductsTrackMaps(cb.name(), combinationCost, quantity); + cost += combinationCost; + } + + return cost; + } + + private static boolean productsInBasketProducts(List productSKUs, HashMap basketProducts) { + return basketProducts.entrySet() + .stream() + .filter(s -> productSKUs.contains(s.getKey())) + .allMatch((s -> s.getValue() > 0)); + } + + private static void sortCombinationBargainsByLeastProfitable(List combinationBargains) { + combinationBargains.sort(Comparator.comparing( + k -> k.productsSKUs() + .stream() + .map(Inventory::checkCostOfTheProduct) + .reduce(Integer::sum) + .orElse(0))); + } + + public HashMap getCosts() { + return costs; + } + + public HashMap getAmounts() { + return amounts; + } + + public HashMap getDiscounts() { + return discounts; + } + + public double getTotalCost() { + return totalCost; + } + + public double getTotalDiscount() { + return totalDiscount; + } + + public Receipt getReceipt() { + return new Receipt( + this.costs, + this.amounts, + this.discounts, + this.totalCost, + this.totalDiscount + ); + } +} diff --git a/src/main/java/com/booleanuk/extension/CombinationBargain.java b/src/main/java/com/booleanuk/extension/CombinationBargain.java new file mode 100644 index 000000000..b9c890b41 --- /dev/null +++ b/src/main/java/com/booleanuk/extension/CombinationBargain.java @@ -0,0 +1,13 @@ +package com.booleanuk.extension; + +import java.util.List; + +public record CombinationBargain(String name, List productsSKUs, int price) { + + public static List coffeePlusBagel() { + return List.of(new CombinationBargain("CoffeePlusOBagel", List.of("COFB", "BGLO"), 125), + new CombinationBargain("CoffeePlusPBagel", List.of("COFB", "BGLP"), 125), + new CombinationBargain("CoffeePlusEBagel", List.of("COFB", "BGLE"), 125), + new CombinationBargain("CoffeePlusSBagel", List.of("COFB", "BGLS"), 125)); + } +} diff --git a/src/main/java/com/booleanuk/extension/Inventory.java b/src/main/java/com/booleanuk/extension/Inventory.java new file mode 100644 index 000000000..f8cf10d78 --- /dev/null +++ b/src/main/java/com/booleanuk/extension/Inventory.java @@ -0,0 +1,83 @@ +package com.booleanuk.extension; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class Inventory { + private static final HashMap products = new HashMap<>() {{ + put("BGLO", new Product("BGLO", 49, "Bagel", "Onion")); + put("BGLP", new Product("BGLP", 39, "Bagel", "Plain")); + put("BGLE", new Product("BGLE", 49, "Bagel", "Everything")); + put("BGLS", new Product("BGLS", 49, "Bagel", "Sesame")); + + put("COFB", new Product("COFB", 99, "Coffee", "Black")); + put("COFW", new Product("COFW", 119, "Coffee", "White")); + put("COFC", new Product("COFC", 129, "Coffee", "Capuccino")); + put("COFL", new Product("COFL", 129, "Coffee", "Latte")); + + put("FILB", new Product("FILB", 12, "Filling", "Bacon")); + put("FILE", new Product("FILE", 12, "Filling", "Egg")); + put("FILC", new Product("FILC", 12, "Filling", "Cheese")); + put("FILX", new Product("FILX", 12, "Filling", "Cream Cheese")); + put("FILS", new Product("FILS", 12, "Filling", "Smoked Salmon")); + put("FILH", new Product("FILH", 12, "Filling", "Ham")); + }}; + + private static final List combinationBargains = CombinationBargain.coffeePlusBagel(); + + private static final HashMap> bargains = new HashMap<>() {{ + put("BGLO", new ArrayList<>() {{ + add(Bargain.bargain6Bagels()); + add(Bargain.bargain12Bagels()); + }}); + put("BGLP", new ArrayList<>() {{ + add(Bargain.bargain6Bagels()); + add(Bargain.bargain12Bagels()); + }}); + put("BGLE", new ArrayList<>() {{ + add(Bargain.bargain6Bagels()); + add(Bargain.bargain12Bagels()); + }}); + put("BGLS", new ArrayList<>() {{ + add(Bargain.bargain6Bagels()); + add(Bargain.bargain12Bagels()); + }}); + }}; + + public static HashMap getProducts() { + return products; + } + + public static List getBargains(String productSKU) { + return bargains.getOrDefault(productSKU, List.of()); + } + + public static List getCombinationBargains() { + return combinationBargains; + } + + public static boolean productNotInInventory(String productSKU) { + return !products.containsKey(productSKU); + } + + public static int checkCostOfTheProduct(String productSKU){ + if (productNotInInventory(productSKU)) + return 0; + + return products.get(productSKU) + .price(); + } + + public static Product getProduct(String productSKU) { + return products.getOrDefault(productSKU, null); + } + + public static String getProductDescription(String productSKU) { + if (productNotInInventory(productSKU)) + return productSKU; + + Product product = getProduct(productSKU); + return product.variant() + " " + product.name(); + } +} diff --git a/src/main/java/com/booleanuk/extension/Product.java b/src/main/java/com/booleanuk/extension/Product.java new file mode 100644 index 000000000..9e362e4bc --- /dev/null +++ b/src/main/java/com/booleanuk/extension/Product.java @@ -0,0 +1,4 @@ +package com.booleanuk.extension; + +public record Product(String sku, int price, String name, String variant) { +} diff --git a/src/main/java/com/booleanuk/extension/Receipt.java b/src/main/java/com/booleanuk/extension/Receipt.java new file mode 100644 index 000000000..329cbf5db --- /dev/null +++ b/src/main/java/com/booleanuk/extension/Receipt.java @@ -0,0 +1,64 @@ +package com.booleanuk.extension; + +import java.time.LocalDateTime; +import java.util.HashMap; + +public class Receipt { + private final HashMap costs; + private final HashMap amounts; + private final HashMap discounts; + private final double totalCost; + private final double totalDiscount; + private final LocalDateTime creationDate; + + public Receipt( + HashMap costs, + HashMap amounts, + HashMap discounts, + double totalCost, + double totalDiscount + ) { + this.costs = costs; + this.amounts = amounts; + this.discounts = discounts; + this.totalCost = totalCost; + this.totalDiscount = totalDiscount; + this.creationDate = LocalDateTime.now(); + } + + public HashMap getCosts() { + return costs; + } + + public HashMap getAmounts() { + return amounts; + } + + public HashMap getDiscounts() { + return discounts; + } + + public double getTotalCost() { + return totalCost; + } + + public double getTotalDiscount() { + return totalDiscount; + } + + public LocalDateTime getCreationDate() { + return creationDate; + } + + public double getDiscount(String productSKU) { + return (double) discounts.getOrDefault(productSKU, 0) / 100; + } + + public double getCost(String productSKU) { + return (double) costs.getOrDefault(productSKU, 0) / 100; + } + + public int getQuantity(String productSKU) { + return amounts.getOrDefault(productSKU, 0); + } +} diff --git a/src/main/java/com/booleanuk/extension/ReceiptGenerator.java b/src/main/java/com/booleanuk/extension/ReceiptGenerator.java new file mode 100644 index 000000000..8e98ce513 --- /dev/null +++ b/src/main/java/com/booleanuk/extension/ReceiptGenerator.java @@ -0,0 +1,219 @@ +package com.booleanuk.extension; + +import java.time.format.DateTimeFormatter; +import java.util.Comparator; + +public class ReceiptGenerator { + + public String generateReceipt(Receipt receipt) { + + String initialLine = "~".repeat(3) + " Bob's Bagels " + "~".repeat(3); + String emptyLine = "\n\n"; + String dataLine = getDateLine(receipt); + String totalSavingsLine = "You saved a total of £%.2f".formatted(receipt.getTotalDiscount()); + + int lineLength = getLineLength(receipt, totalSavingsLine.length(), 6); + + String totalLine = getTotalLine(receipt, lineLength); + String dashLine = "-".repeat(lineLength); + + StringBuilder receiptBuilder = new StringBuilder(); + + generateTopOfTheReceipt( + receiptBuilder, + initialLine, + emptyLine, + dataLine, + dashLine, + lineLength + ); + + generateProductCostsAndDiscountsLines(receiptBuilder, receipt, lineLength); + + generateBottomOfTheReceipt( + receiptBuilder, + emptyLine, + dashLine, + totalLine, + totalSavingsLine, + lineLength + ); + + return receiptBuilder.toString(); + } + + private void generateTopOfTheReceipt( + StringBuilder receiptBuilder, + String initialLine, + String emptyLine, + String dataLine, + String dashLine, + int lineLength + ) { + receiptBuilder.append(centerText(initialLine, lineLength)) + .append(emptyLine) + .append(centerText(dataLine, lineLength)) + .append(emptyLine) + .append(dashLine) + .append(emptyLine); + } + + private void generateBottomOfTheReceipt( + StringBuilder receiptBuilder, + String emptyLine, + String dashLine, + String totalLine, + String totalSavingsLine, + int lineLength + ) { + receiptBuilder.append('\n') + .append(dashLine) + .append('\n') + .append(totalLine) + .append(emptyLine) + .append(centerText(totalSavingsLine, lineLength)) + .append('\n') + .append(centerText("on this shop", lineLength)) + .append(emptyLine) + .append(centerText("Thank you", lineLength)) + .append('\n') + .append(centerText("for your order!", lineLength)); + } + + private String getDateLine(Receipt receipt) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd hh:mm:ss"); + return receipt.getCreationDate().format(formatter); + } + + private String getTotalLine(Receipt receipt, int lineLength) { + String totalCostFormatted = "£%.2f".formatted(receipt.getTotalCost()); + int totalAlign = lineLength - totalCostFormatted.length(); + String totalLineFormat = "Total %" + totalAlign + "s"; + return totalLineFormat.formatted(totalCostFormatted); + } + + private void generateProductCostsAndDiscountsLines( + StringBuilder receiptBuilder, + Receipt receipt, + int lineLength + ) { + for (String productSKU: receipt.getCosts().keySet()) { + generateProductCostLine(receiptBuilder, receipt, productSKU, lineLength); + generateDiscountLine(receiptBuilder, receipt, productSKU, lineLength); + } + } + + private void generateProductCostLine( + StringBuilder receiptBuilder, + Receipt receipt, + String productSKU, + int lineLength + ) { + receiptBuilder.append(getProductCostLine(receipt, productSKU, lineLength)); + } + + private String getProductCostLine( + Receipt receipt, + String productSKU, + int lineLength + ) { + String productDescriptionFormat = + "%-" + + getLengthOfTheLongestProductDescription(receipt) + + "s"; + String productQuantityFormat = + "%-" + + getLengthOfTheLongestProductQuantityString(receipt) + + "s"; + + int space = lineLength - getLengthOfTheLongestProductLine(receipt); + String firstPartFormat = + productDescriptionFormat + + " ".repeat(space / 2) + + productQuantityFormat; + + String firstPart = firstPartFormat.formatted( + Inventory.getProductDescription(productSKU), + receipt.getQuantity(productSKU) + ); + + int costAlign = lineLength - firstPart.length(); + String productCostFormat = "%" + costAlign + "s\n"; + + String costFormatted = "£%.2f".formatted(receipt.getCost(productSKU)); + + return firstPart + productCostFormat.formatted(costFormatted); + } + + private int getLengthOfTheLongestProductQuantityString(Receipt receipt) { + return receipt.getAmounts() + .values() + .stream() + .map(Object::toString) + .map(String::length) + .max(Comparator.comparing(Integer::intValue)) + .orElse(0); + } + private int getLengthOfTheLongestProductDescription(Receipt receipt) { + return receipt.getCosts() + .keySet() + .stream() + .map(Inventory::getProductDescription) + .map(String::length) + .max(Comparator.comparing(Integer::intValue)) + .orElse(0); + } + + private void generateDiscountLine( + StringBuilder receiptBuilder, + Receipt receipt, + String productSKU, + int lineLength + ) { + if (!receipt.getDiscounts().containsKey(productSKU)) + return; + + receiptBuilder.append(getDiscountLine(receipt, productSKU, lineLength)); + } + + private String getDiscountLine( + Receipt receipt, + String productSKU, + int lineLength + ) { + String discountFormatted = "(-£%.2f)".formatted(receipt.getDiscount(productSKU)); + String discountLineFormat = "%" + (lineLength + 1) + "s\n"; + return discountLineFormat.formatted(discountFormatted); + } + + private String centerText(String text, int lineLength) { + int spaceSize = lineLength - text.length(); + int prefixSize = spaceSize / 2; + int suffixSize = (spaceSize + 1) / 2; + + return lineLength > text.length() + ? " ".repeat(prefixSize) + text + " ".repeat(suffixSize) + : text; + } + + private int getLineLength(Receipt receipt, int longestDefaultLine, int additionalLength) { + return Integer.max(getLengthOfTheLongestProductLine(receipt), longestDefaultLine) + additionalLength; + } + + private int getLengthOfTheLongestProductLine(Receipt receipt) { + int longestLineLength = 0; + + for (String productSKU: receipt.getCosts().keySet()) { + int lineLength = 0; + + lineLength += Inventory.getProductDescription(productSKU).length(); + + lineLength += receipt.getAmounts().get(productSKU).toString().length(); + lineLength += receipt.getCosts().get(productSKU).toString().length(); + + longestLineLength = Integer.max(longestLineLength, lineLength); + } + + return longestLineLength; + } +} diff --git a/src/test/java/com/booleanuk/core/.gitkeep b/src/test/java/com/booleanuk/core/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/java/com/booleanuk/core/BasketTest.java b/src/test/java/com/booleanuk/core/BasketTest.java new file mode 100644 index 000000000..f2fba8a87 --- /dev/null +++ b/src/test/java/com/booleanuk/core/BasketTest.java @@ -0,0 +1,56 @@ +package com.booleanuk.core; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class BasketTest { + + @Test + public void addTest() { + Basket basket = new Basket(5); + Assertions.assertTrue(basket.add("BGLP", 1)); + Assertions.assertFalse(basket.add("BGLE", 8)); + Assertions.assertFalse(basket.add("Plastic", 3)); + Assertions.assertFalse(basket.add("BGLP", 0)); + Assertions.assertTrue(basket.add("FILE", 1)); + } + + @Test + public void removeTest() { + Basket basket = new Basket(10); + basket.add("BGLP", 6); + basket.add("BGLE", 2); + basket.add("BGLS", 2); + Assertions.assertTrue(basket.remove("BGLP", 4)); + Assertions.assertFalse(basket.remove("BGLE", 4)); + Assertions.assertFalse(basket.remove("Plastic", 2)); + Assertions.assertFalse(basket.remove("BGLP", 0)); + } + + @Test + public void changeCapacityTest() { + Basket basket = new Basket(10); + basket.add("BGLP", 6); + basket.add("BGLE", 2); + basket.add("BGLS", 2); + Assertions.assertTrue(basket.changeCapacity(15)); + Assertions.assertFalse(basket.changeCapacity(8)); + } + + @Test + public void totalCostTest() { + Basket basket = new Basket(10); + basket.add("BGLP", 6); + basket.add("BGLE", 2); + basket.add("BGLS", 2); + Assertions.assertEquals(4.3, basket.totalCost()); + } + + @Test + public void checkCostOfProducts(){ + Basket basket = new Basket(10); + Assertions.assertEquals(0.39, basket.checkCostOfProduct("BGLP")); + Assertions.assertEquals(0.12, basket.checkCostOfProduct("FILE")); + Assertions.assertEquals(0.0, basket.checkCostOfProduct("Pizza")); + } + +} diff --git a/src/test/java/com/booleanuk/extension/.gitkeep b/src/test/java/com/booleanuk/extension/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/test/java/com/booleanuk/extension/BargainTest.java b/src/test/java/com/booleanuk/extension/BargainTest.java new file mode 100644 index 000000000..57a750bcf --- /dev/null +++ b/src/test/java/com/booleanuk/extension/BargainTest.java @@ -0,0 +1,24 @@ +package com.booleanuk.extension; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BargainTest { + + @Test + public void bargain6BagelsTest() { + Bargain bargain = Bargain.bargain6Bagels(); + + assertEquals(6, bargain.packSize()); + assertEquals(249, bargain.packCost()); + } + + @Test + public void bargain12BagelsTest() { + Bargain bargain = Bargain.bargain12Bagels(); + + assertEquals(12, bargain.packSize()); + assertEquals(399, bargain.packCost()); + } +} diff --git a/src/test/java/com/booleanuk/extension/BasketTest.java b/src/test/java/com/booleanuk/extension/BasketTest.java new file mode 100644 index 000000000..b5009b54c --- /dev/null +++ b/src/test/java/com/booleanuk/extension/BasketTest.java @@ -0,0 +1,181 @@ +package com.booleanuk.extension; + +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class BasketTest { + + @Test + public void createBasketTest() { + Basket basket = new Basket(5); + + assertEquals(5, basket.getCapacity()); + assertEquals(0, basket.getCurrentAmountOfProducts()); + assertTrue(basket.getProductsCount().isEmpty()); + } + + @Nested + public class AddTest { + + private static Basket BASKET; + private static String PLAIN_BAGEL; + private static String EVERYTHING_BAGEL; + private static String EGG_FILLING; + + @BeforeAll + public static void createBasket() { + BASKET = new Basket(5); + PLAIN_BAGEL = "BGLP"; + EVERYTHING_BAGEL = "BGLE"; + EGG_FILLING = "FILE"; + } + + @AfterEach + public void clearBasket() { + BASKET.clearBasket(); + } + + @Test + public void addToBasketEnoughSpaceTest() { + + assertTrue(BASKET.add(PLAIN_BAGEL, 1)); + assertEquals(1, BASKET.getCurrentAmountOfProducts()); + + assertTrue(BASKET.add(PLAIN_BAGEL, 1)); + assertEquals(2, BASKET.getCurrentAmountOfProducts()); + assertEquals(2, BASKET.getProductsCount().get(PLAIN_BAGEL)); + + + + assertTrue(BASKET.add(EGG_FILLING, 3)); + assertEquals(5, BASKET.getCurrentAmountOfProducts()); + + assertEquals(2, BASKET.getProductsCount().size()); + assertEquals(2, BASKET.getProductsCount().get(PLAIN_BAGEL)); + assertEquals(3, BASKET.getProductsCount().get(EGG_FILLING)); + } + + @Test + public void addToBasketNotEnoughSpace() { + assertFalse(BASKET.add(EVERYTHING_BAGEL, 8)); + assertEquals(0, BASKET.getCurrentAmountOfProducts()); + assertTrue(BASKET.getProductsCount().isEmpty()); + } + + @Test + public void addToBasketAmountLessOrEqualZero() { + assertFalse(BASKET.add(PLAIN_BAGEL, 0)); + assertFalse(BASKET.add(EGG_FILLING, -2)); + + assertEquals(0, BASKET.getCurrentAmountOfProducts()); + assertTrue(BASKET.getProductsCount().isEmpty()); + } + } + + @Nested + public class RemoveTest { + private static Basket BASKET; + private static String PLAIN_BAGEL; + private static String EVERYTHING_BAGEL; + private static String SESAME_BAGEL; + + + @BeforeAll + public static void createBasket() { + BASKET = new Basket(10); + PLAIN_BAGEL = "BGLP"; + EVERYTHING_BAGEL = "BGLE"; + SESAME_BAGEL = "BGLS"; + } + + @BeforeEach + public void addProducts() { + BASKET.add(PLAIN_BAGEL, 6); + BASKET.add(EVERYTHING_BAGEL, 2); + BASKET.add(SESAME_BAGEL, 2); + } + + @AfterEach + public void clearBasket() { + BASKET.clearBasket(); + } + + @Test + public void removeProductFromBasketTest() { + assertTrue(BASKET.remove(PLAIN_BAGEL, 4)); + + assertEquals(2, BASKET.getProductsCount().get(PLAIN_BAGEL)); + assertEquals(6, BASKET.getCurrentAmountOfProducts()); + assertEquals(3, BASKET.getProductsCount().size()); + + assertTrue(BASKET.remove(PLAIN_BAGEL, 2)); + assertFalse(BASKET.getProductsCount().containsKey(PLAIN_BAGEL)); + assertEquals(4, BASKET.getCurrentAmountOfProducts()); + assertEquals(2, BASKET.getProductsCount().size()); + } + + @Test + public void removeTooManyProductsFromBasketTest() { + assertFalse(BASKET.remove(EVERYTHING_BAGEL, 4)); + + assertEquals(2, BASKET.getProductsCount().get(EVERYTHING_BAGEL)); + assertEquals(10, BASKET.getCurrentAmountOfProducts()); + assertEquals(3, BASKET.getProductsCount().size()); + } + + @Test + public void removeNonPositiveNumberOfProductsTest() { + assertFalse(BASKET.remove(EVERYTHING_BAGEL, 0)); + assertFalse(BASKET.remove(EVERYTHING_BAGEL, -4)); + + assertEquals(2, BASKET.getProductsCount().get(EVERYTHING_BAGEL)); + assertEquals(10, BASKET.getCurrentAmountOfProducts()); + assertEquals(3, BASKET.getProductsCount().size()); + } + + @Test + public void removeProductThatIsNotStoredInInventoryTest() { + assertFalse(BASKET.remove("Plastic", 4)); + + assertEquals(2, BASKET.getProductsCount().get(EVERYTHING_BAGEL)); + assertEquals(6, BASKET.getProductsCount().get(PLAIN_BAGEL)); + assertEquals(2, BASKET.getProductsCount().get(SESAME_BAGEL)); + assertEquals(10, BASKET.getCurrentAmountOfProducts()); + assertEquals(3, BASKET.getProductsCount().size()); + } + } + + @Nested + public class ChangeCapacity { + + private static Basket BASKET; + + @BeforeAll + public static void createBasket() { + BASKET = new Basket(10); + String PLAIN_BAGEL = "BGLP"; + String EVERYTHING_BAGEL = "BGLE"; + String SESAME_BAGEL = "BGLS"; + BASKET.add(PLAIN_BAGEL, 6); + BASKET.add(EVERYTHING_BAGEL, 2); + BASKET.add(SESAME_BAGEL, 2); + } + + @Test + public void changeCapacityToNumberGreaterThanCurrentAmountOfProductsTest() { + assertTrue(BASKET.changeCapacity(15)); + } + + @Test + public void changeCapacityToNumberLessThanCurrentAmountOfProductsTest() { + assertFalse(BASKET.changeCapacity(8)); + } + + @Test + public void changeCapacityToNonPositiveNumberTest() { + assertFalse(BASKET.changeCapacity(0)); + assertFalse(BASKET.changeCapacity(-4)); + } + } +} diff --git a/src/test/java/com/booleanuk/extension/CheckoutTest.java b/src/test/java/com/booleanuk/extension/CheckoutTest.java new file mode 100644 index 000000000..292ae4a89 --- /dev/null +++ b/src/test/java/com/booleanuk/extension/CheckoutTest.java @@ -0,0 +1,122 @@ +package com.booleanuk.extension; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class CheckoutTest { + + private static Checkout CHECKOUT; + @BeforeAll + public static void initializeCheckout() { + Basket basket = new Basket(30); + basket.add("BGLO", 2); + basket.add("BGLP", 14); + basket.add("BGLE", 6); + basket.add("FILC", 2); + basket.add("COFB", 2); + + CHECKOUT = new Checkout(basket); + } + + @Test + public void getCostsTest() { + assertEquals( + 5, + CHECKOUT.getCosts().size() + ); + } + + @Test + public void getAmountsTest() { + assertEquals( + 5, + CHECKOUT.getAmounts().size() + ); + } + + @Test + public void getDiscountsTest() { + assertEquals( + 3, + CHECKOUT.getDiscounts().size() + ); + } + + @Test + public void getTotalCostTest() { + assertEquals( + 10.2, + CHECKOUT.getTotalCost() + ); + } + + @Test + public void getTotalDiscountTest() { + assertTrue(Math.abs(CHECKOUT.getTotalDiscount() - 1.40d) < 0.01); + } + + @Test + public void getReceiptTest() { + assertNotNull(CHECKOUT.getReceipt()); + } + + @Nested + class CheckoutStaticMethods { + + private static Basket BASKET; + private static String PLAIN_BAGEL; + + @BeforeAll + public static void createBasket() { + PLAIN_BAGEL = "BGLP"; + + BASKET = new Basket(30); + BASKET.add("BGLO", 2); + BASKET.add(PLAIN_BAGEL, 14); + BASKET.add("BGLE", 6); + BASKET.add("FILC", 2); + BASKET.add("COFB", 2); + } + + @Test + public void getCostOfProductsFromInventoryTest() { + assertEquals( + 117, + Checkout.getProductsCost( + PLAIN_BAGEL, + 3 + ) + ); + } + + @Test + public void getCostOfUnknownProductsTest() { + assertEquals( + 0, + Checkout.getProductsCost( + "Plastic", + 3 + ) + ); + } + + @Test + public void countBasketCostWithoutDiscountsTest() { + assertEquals( + 11.6, + Checkout.countBasketCostWithoutDiscounts(BASKET) + ); + } + + @Test + public void countBasketCostWithDiscountsTest() { + assertEquals( + 10.2, + Checkout.countBasketCostWithDiscounts(BASKET) + ); + } + } +} diff --git a/src/test/java/com/booleanuk/extension/CombinationBargainTest.java b/src/test/java/com/booleanuk/extension/CombinationBargainTest.java new file mode 100644 index 000000000..6e0714a75 --- /dev/null +++ b/src/test/java/com/booleanuk/extension/CombinationBargainTest.java @@ -0,0 +1,18 @@ +package com.booleanuk.extension; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class CombinationBargainTest { + + @Test + public void getCoffeePlusBagelCombinationBargainsTest() { + List combinationBargains = + CombinationBargain.coffeePlusBagel(); + + assertEquals(4, combinationBargains.size()); + } +} diff --git a/src/test/java/com/booleanuk/extension/InventoryTest.java b/src/test/java/com/booleanuk/extension/InventoryTest.java new file mode 100644 index 000000000..13ccfbfdf --- /dev/null +++ b/src/test/java/com/booleanuk/extension/InventoryTest.java @@ -0,0 +1,149 @@ +package com.booleanuk.extension; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class InventoryTest { + + @Test + public void getProductsTest() { + var products = Inventory.getProducts(); + assertEquals(14, products.size()); + } + + @Test + public void getCombinationBargainsTest() { + var combinationBargains = Inventory.getCombinationBargains(); + assertEquals(4, combinationBargains.size()); + } + + @Nested + public class ProductNotInInventory { + private static String PLAIN_BAGEL; + + @BeforeAll + public static void initializeProducts() { + PLAIN_BAGEL = "BGLP"; + } + + @Test + public void productsNotInInventoryTest() { + assertTrue(Inventory.productNotInInventory("Plastic")); + } + + @Test + public void productInInventoryTest() { + assertFalse(Inventory.productNotInInventory(PLAIN_BAGEL)); + } + } + + @Nested + public class GetBargains { + private static String PLAIN_BAGEL; + private static String EGG_FILLING; + + @BeforeAll + public static void initializeProducts() { + PLAIN_BAGEL = "BGLP"; + EGG_FILLING = "FILE"; + } + + @Test + public void getBargainsForProductOnSale() { + var bargains = Inventory.getBargains(PLAIN_BAGEL); + + assertEquals(2, bargains.size()); + } + + @Test + public void getBargainsForProductWhichIsNotOnSale() { + var bargains = Inventory.getBargains(EGG_FILLING); + + assertTrue(bargains.isEmpty()); + } + } + + @Nested + public class CheckCostOfTheProduct { + private static String PLAIN_BAGEL; + + @BeforeAll + public static void initializeProducts() { + PLAIN_BAGEL = "BGLP"; + } + + @Test + public void checkCostOfTheProductFromInventoryTest() { + int cost = Inventory.checkCostOfTheProduct(PLAIN_BAGEL); + assertEquals(39, cost); + } + + @Test + public void checkCostOfTheProductNotFromInventoryTest() { + int cost = Inventory.checkCostOfTheProduct("Plastic"); + assertEquals(0, cost); + } + } + + @Nested + public class GetProduct { + private static String PLAIN_BAGEL; + + @BeforeAll + public static void initializeProducts() { + PLAIN_BAGEL = "BGLP"; + } + + @Test + public void getProductFromInventoryTest() { + Product product = Inventory.getProduct(PLAIN_BAGEL); + + assertEquals( + "Bagel", + product.name() + ); + assertEquals( + "Plain", + product.variant() + ); + assertEquals( + 39, + product.price() + ); + } + + @Test + public void getProductNotFromInventoryTest() { + assertNull(Inventory.getProduct("Plasic")); + } + } + + @Nested + public class GetProductDescription { + private static String PLAIN_BAGEL; + + @BeforeAll + public static void initializeProductsTest() { + PLAIN_BAGEL = "BGLP"; + } + + @Test + public void getDescriptionOfTheProductFromInventoryTest() { + assertEquals( + "Plain Bagel", + Inventory.getProductDescription(PLAIN_BAGEL) + ); + } + + @Test + public void getDescriptionOfTheProductNotFromInventoryTest() { + assertEquals( + "CoffeePlusBagel", + Inventory.getProductDescription("CoffeePlusBagel") + ); + } + } +} diff --git a/src/test/java/com/booleanuk/extension/ProductTest.java b/src/test/java/com/booleanuk/extension/ProductTest.java new file mode 100644 index 000000000..e6ef7c72b --- /dev/null +++ b/src/test/java/com/booleanuk/extension/ProductTest.java @@ -0,0 +1,18 @@ +package com.booleanuk.extension; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ProductTest { + + @Test + public void createProductTest() { + Product product = new Product("BGLP", 39, "Bagel", "Plain"); + + assertEquals("BGLP", product.sku()); + assertEquals(39, product.price()); + assertEquals("Bagel", product.name()); + assertEquals("Plain", product.variant()); + } +} diff --git a/src/test/java/com/booleanuk/extension/ReceiptGeneratorTest.java b/src/test/java/com/booleanuk/extension/ReceiptGeneratorTest.java new file mode 100644 index 000000000..d17da7d41 --- /dev/null +++ b/src/test/java/com/booleanuk/extension/ReceiptGeneratorTest.java @@ -0,0 +1,66 @@ +package com.booleanuk.extension; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ReceiptGeneratorTest { + + private static Receipt RECEIPT; + private static ReceiptGenerator RECEIPT_GENERATOR; + @BeforeAll + public static void createReceiptAndReceiptGenerator() { + Basket basket = new Basket(30); + basket.add("BGLO", 2); + basket.add("BGLP", 14); + basket.add("BGLE", 6); + basket.add("FILC", 2); + basket.add("COFB", 2); + + Checkout checkout = new Checkout(basket); + + RECEIPT = checkout.getReceipt(); + RECEIPT_GENERATOR = new ReceiptGenerator(); + } + + @Test + public void generateReceiptTest() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd hh:mm:ss"); + String date = LocalDateTime.now().format(formatter); + + String expectedReceipt = """ + ~~~ Bob's Bagels ~~~ \s + + %s \s + + -------------------------------- + + Everything Bagel 6 £2.49 + (-£0.45) + Plain Bagel 12 £3.99 + (-£0.69) + Onion Bagel 2 £0.98 + CoffeePlusPBagel 2 £2.50 + (-£0.26) + Cheese Filling 2 £0.24 + + -------------------------------- + Total £10.20 + + You saved a total of £1.40 \s + on this shop \s + + Thank you \s + for your order! \s"""; + + + assertEquals( + expectedReceipt.formatted(date), + RECEIPT_GENERATOR.generateReceipt(RECEIPT) + ); + } +} diff --git a/src/test/java/com/booleanuk/extension/ReceiptTest.java b/src/test/java/com/booleanuk/extension/ReceiptTest.java new file mode 100644 index 000000000..112b93f3e --- /dev/null +++ b/src/test/java/com/booleanuk/extension/ReceiptTest.java @@ -0,0 +1,86 @@ +package com.booleanuk.extension; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ReceiptTest { + + private static Receipt RECEIPT; + private static LocalDateTime CREATION_DATE; + + @BeforeAll + public static void initializeReceipt() { + Basket basket = new Basket(30); + basket.add("BGLO", 2); + basket.add("BGLP", 14); + basket.add("BGLE", 6); + basket.add("FILC", 2); + basket.add("COFB", 2); + + Checkout checkout = new Checkout(basket); + RECEIPT = checkout.getReceipt(); + CREATION_DATE = LocalDateTime.now(); + } + + @Test + public void createReceiptTest() { + assertEquals(5, RECEIPT.getCosts().size()); + assertEquals(5, RECEIPT.getAmounts().size()); + assertEquals(3, RECEIPT.getDiscounts().size()); + assertEquals(10.2d, RECEIPT.getTotalCost()); + assertTrue(Math.abs(RECEIPT.getTotalDiscount() - 1.40d) < 0.01); + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd hh:mm:ss"); + assertEquals( + CREATION_DATE.format(formatter), + RECEIPT.getCreationDate().format(formatter)); + } + + @Nested + class SpecificGetters { + + private static Receipt RECEIPT; + private static String PLAIN_BAGEL; + @BeforeAll + public static void initializeReceipt() { + PLAIN_BAGEL = "BGLP"; + + Basket basket = new Basket(30); + basket.add(PLAIN_BAGEL, 14); + + Checkout checkout = new Checkout(basket); + RECEIPT = checkout.getReceipt(); + } + + @Test + public void getCostTest() { + assertEquals( + 4.77, + RECEIPT.getCost(PLAIN_BAGEL) + ); + } + + @Test + public void getDiscountTest() { + assertEquals( + 0.69, + RECEIPT.getDiscount(PLAIN_BAGEL) + ); + } + + @Test + public void getQuantityTest() { + assertEquals( + 14, + RECEIPT.getQuantity(PLAIN_BAGEL) + ); + } + } +}