Merge branch 'Funktion-zur-automatischen-Margenermittlung' into dev

This commit is contained in:
Shan Ruhhammer
2023-06-25 03:43:40 +02:00
10 changed files with 315 additions and 14 deletions

View File

@@ -12,6 +12,7 @@ import de.rwu.easydrop.api.client.DataSourceFactory;
import de.rwu.easydrop.data.connector.AbstractProductPersistence; import de.rwu.easydrop.data.connector.AbstractProductPersistence;
import de.rwu.easydrop.data.connector.SQLiteConnector; import de.rwu.easydrop.data.connector.SQLiteConnector;
import de.rwu.easydrop.model.ProductCatalogue; import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.service.processing.OrderManager;
import de.rwu.easydrop.service.retriever.CatalogueRetriever; import de.rwu.easydrop.service.retriever.CatalogueRetriever;
import de.rwu.easydrop.service.retriever.ProductRetriever; import de.rwu.easydrop.service.retriever.ProductRetriever;
import de.rwu.easydrop.service.writer.CatalogueWriter; import de.rwu.easydrop.service.writer.CatalogueWriter;
@@ -58,5 +59,7 @@ public final class Main {
String pCatStr = pCat.toString(); String pCatStr = pCat.toString();
LOGGER.info(pCatStr); LOGGER.info(pCatStr);
} }
OrderManager.createOrders(pCats);
} }
} }

View File

@@ -0,0 +1,27 @@
package de.rwu.easydrop.exception;
/**
* Exception that signifies the data of a Product are invalid.
*
* @since 0.3.0
*/
public class InvalidCatalogueException extends RuntimeException {
/**
* Throws an exception that signifies the data of a Product are invalid.
*
* @param message
*/
public InvalidCatalogueException(final String message) {
super(message);
}
/**
* Throws an exception that signifies the data of a Product are invalid.
*
* @param message
* @param cause
*/
public InvalidCatalogueException(final String message, final Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,31 @@
package de.rwu.easydrop.model;
import lombok.Data;
/**
* Associates two related Products to one another.
*
* @since 0.3.0
*/
@Data
public class ProductPair {
/**
* The first product.
*/
private final Product product1;
/**
* The second product.
*/
private final Product product2;
/**
* Constructs a Product Pair.
*
* @param a First product
* @param b Second product
*/
public ProductPair(final Product a, final Product b) {
this.product1 = a;
this.product2 = b;
}
}

View File

@@ -1,10 +1,87 @@
package de.rwu.easydrop.service.processing; package de.rwu.easydrop.service.processing;
/** import java.util.List;
* Processes dropshipping orders.
*
* TODO implement
*/
public class OrderManager {
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.rwu.easydrop.exception.InvalidCatalogueException;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.model.ProductPair;
import de.rwu.easydrop.util.FormattingUtil;
/**
* Creates dropshipping orders based on price margin.
*
* @since 0.3.0
*/
public final class OrderManager {
/**
* Temporary logging instance.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(OrderManager.class);
/**
* Private constructor to prevent unwanted instantiation.
*
* @throws UnsupportedOperationException always
*/
private OrderManager() throws UnsupportedOperationException {
throw new UnsupportedOperationException("This is a stateless class, don't instantiate it.");
}
/**
* Creates orders for products with sufficient margin.
*
* @param pCats Product Catalogues
*/
public static void createOrders(final List<ProductCatalogue> pCats) {
for (ProductCatalogue pCat : pCats) {
ProductPair pair = getHighestMarginProducts(pCat);
// #12: Create actual orders/transactions, remove logger
double margin = pair.getProduct2().getCurrentPrice()
- pair.getProduct1().getCurrentPrice();
String marginFormatted = FormattingUtil.formatEuro(margin);
LOGGER.info("{}: Margin {} ({} to {})",
pCat.getProductName(),
marginFormatted,
pair.getProduct1().getDataOrigin(),
pair.getProduct2().getDataOrigin());
}
}
/**
* Returns the cheapest and most expensive product of a catalogue to guarantee
* the biggest margin as a pair.
*
* @param pCat Product Catalogue
* @return Cheapest Product, Most Expensive Product as a Product Pair
*/
public static ProductPair getHighestMarginProducts(final ProductCatalogue pCat) {
if (pCat.getProducts().size() < 2) {
throw new InvalidCatalogueException("Product Catalogue holds less than 2 products!");
}
// Initialize indexes
Product cheapestProduct = pCat.getProducts().get(0);
Product mostExpensiveProduct = pCat.getProducts().get(0);
for (Product product : pCat.getProducts()) {
if (product.getCurrentPrice() < cheapestProduct.getCurrentPrice()) {
cheapestProduct = product;
}
if (product.getCurrentPrice() > mostExpensiveProduct.getCurrentPrice()) {
mostExpensiveProduct = product;
}
}
if (cheapestProduct.getCurrentPrice() == mostExpensiveProduct.getCurrentPrice()) {
throw new InvalidCatalogueException("Price margin is zero!");
}
return new ProductPair(cheapestProduct, mostExpensiveProduct);
}
} }

View File

@@ -25,7 +25,7 @@ public final class FormattingUtil {
* @return formatted price * @return formatted price
*/ */
public static String formatEuro(final double amount) { public static String formatEuro(final double amount) {
return String.format(Locale.GERMAN, "%,.2f", amount) + " "; return String.format(Locale.GERMAN, "%,.2f", amount) + " Euro";
} }
/** /**

View File

@@ -0,0 +1,33 @@
package de.rwu.easydrop.exception;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class InvalidCatalogueExceptionTest {
@Test
void testConstructorWithMessage_ExceptionWithMessageCreated() {
// Arrange
String message = "Invalid catalogue";
// Act
InvalidCatalogueException exception = new InvalidCatalogueException(message);
// Assert
Assertions.assertEquals(message, exception.getMessage());
}
@Test
void testConstructorWithMessageAndCause_ExceptionWithMessageAndCauseCreated() {
// Arrange
String message = "Invalid catalogue";
Throwable cause = new RuntimeException("Cause exception");
// Act
InvalidCatalogueException exception = new InvalidCatalogueException(message, cause);
// Assert
Assertions.assertEquals(message, exception.getMessage());
Assertions.assertEquals(cause, exception.getCause());
}
}

View File

@@ -85,8 +85,8 @@ class ProductCatalogueTest {
String expectedString = "Product Catalogue: GPU\n" + String expectedString = "Product Catalogue: GPU\n" +
"Description: Graphics Processing Units\n" + "Description: Graphics Processing Units\n" +
"Products:\n" + "Products:\n" +
"Product: [12345 from AmazonSeller (Amazon) at 0,00 (available: no)]\n" + "Product: [12345 from AmazonSeller (Amazon) at 0,00 Euro (available: no)]\n" +
"Product: [54321 from eBaySeller (eBay) at 0,00 (available: no)]\n"; "Product: [54321 from eBaySeller (eBay) at 0,00 Euro (available: no)]\n";
Assertions.assertEquals(expectedString, productCatalogue.toString()); Assertions.assertEquals(expectedString, productCatalogue.toString());
} }

View File

@@ -16,7 +16,7 @@ class ProductTest {
product1.setCurrentPrice(19.99); product1.setCurrentPrice(19.99);
product1.setAvailable(true); product1.setAvailable(true);
String expectedString1 = "Product: [12345 from Merchant A (Amazon) at 19,99 (available: yes)]"; String expectedString1 = "Product: [12345 from Merchant A (Amazon) at 19,99 Euro (available: yes)]";
String result1 = product1.toString(); String result1 = product1.toString();
assertEquals(expectedString1, result1); assertEquals(expectedString1, result1);
@@ -31,7 +31,7 @@ class ProductTest {
product2.setCurrentPrice(9.99); product2.setCurrentPrice(9.99);
product2.setAvailable(false); product2.setAvailable(false);
String expectedString2 = "Product: [67890 from Merchant B (eBay) at 9,99 (available: no)]"; String expectedString2 = "Product: [67890 from Merchant B (eBay) at 9,99 Euro (available: no)]";
String result2 = product2.toString(); String result2 = product2.toString();
assertEquals(expectedString2, result2); assertEquals(expectedString2, result2);

View File

@@ -0,0 +1,130 @@
package de.rwu.easydrop.service.processing;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import de.rwu.easydrop.exception.InvalidCatalogueException;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.model.ProductPair;
class OrderManagerTest {
@Test
void testConstructorIsPrivate()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// Check for private constructor
Constructor<OrderManager> constructor = OrderManager.class.getDeclaredConstructor();
assertTrue(Modifier.isPrivate(constructor.getModifiers()));
// Make sure exception is thrown when instantiating
constructor.setAccessible(true);
assertThrows(InvocationTargetException.class, () -> {
constructor.newInstance();
});
}
@Test
void testCreateOrders_ValidCatalogues_OrdersCreated() {
// Arrange
List<ProductCatalogue> catalogues = createSampleCatalogues();
// Act
assertDoesNotThrow(() -> {
OrderManager.createOrders(catalogues);
});
}
@Test
void testGetHighestMarginProducts_ValidCatalogue_CheapestAndMostExpensiveProductsReturned() {
// Arrange
ProductCatalogue catalogue = createSampleCatalogue();
// Act
ProductPair pair = OrderManager.getHighestMarginProducts(catalogue);
// Assert
assertNotNull(pair);
assertNotNull(pair.getProduct1());
assertNotNull(pair.getProduct2());
assertEquals("Product2", pair.getProduct1().getProductId());
assertEquals("Product3", pair.getProduct2().getProductId());
}
@Test
void testGetHighestMarginProducts_InvalidCatalogue_ThrowsInvalidCatalogueException() {
// Arrange
ProductCatalogue catalogue = new ProductCatalogue("Catalogue1", "Sample catalogue");
catalogue.addProduct(new Product());
// The catalogue has only one product, which is invalid
// Act & Assert
Exception e = assertThrows(InvalidCatalogueException.class,
() -> OrderManager.getHighestMarginProducts(catalogue));
assertEquals("Product Catalogue holds less than 2 products!", e.getMessage());
}
@Test
void testGetHighestMarginProducts_InvalidCatalogue_NoMargin() {
// Arrange
ProductCatalogue catalogue = new ProductCatalogue("Catalogue1", "Sample catalogue");
catalogue.addProduct(new Product());
catalogue.addProduct(new Product());
// The catalogue has only one product, which is invalid
// Act & Assert
Exception e = assertThrows(InvalidCatalogueException.class,
() -> OrderManager.getHighestMarginProducts(catalogue));
assertEquals("Price margin is zero!", e.getMessage());
}
private List<ProductCatalogue> createSampleCatalogues() {
List<ProductCatalogue> catalogues = new ArrayList<>();
ProductCatalogue catalogue1 = createSampleCatalogue();
catalogues.add(catalogue1);
ProductCatalogue catalogue2 = new ProductCatalogue("Catalogue2", "Sample catalogue 2");
Product product4 = new Product();
product4.setProductId("Product4");
product4.setCurrentPrice(6.78);
catalogue2.addProduct(product4);
Product product5 = new Product();
product5.setProductId("Product5");
product5.setCurrentPrice(7.89);
catalogue2.addProduct(product5);
catalogues.add(catalogue2);
return catalogues;
}
private ProductCatalogue createSampleCatalogue() {
ProductCatalogue catalogue = new ProductCatalogue("Catalogue1", "Sample catalogue");
Product product1 = new Product();
product1.setProductId("Product1");
product1.setCurrentPrice(1.23);
catalogue.addProduct(product1);
Product product2 = new Product();
product2.setProductId("Product2");
product2.setCurrentPrice(0.89);
catalogue.addProduct(product2);
Product product3 = new Product();
product3.setProductId("Product3");
product3.setCurrentPrice(4.56);
catalogue.addProduct(product3);
return catalogue;
}
}

View File

@@ -29,7 +29,7 @@ class FormattingUtilTest {
@Test @Test
void testFormatEuro_positiveAmount() { void testFormatEuro_positiveAmount() {
double amount = 1234.56; double amount = 1234.56;
String expectedFormattedAmount = "1.234,56 "; String expectedFormattedAmount = "1.234,56 Euro";
String formattedAmount = FormattingUtil.formatEuro(amount); String formattedAmount = FormattingUtil.formatEuro(amount);
@@ -39,7 +39,7 @@ class FormattingUtilTest {
@Test @Test
void testFormatEuro_zeroAmount() { void testFormatEuro_zeroAmount() {
double amount = 0.0; double amount = 0.0;
String expectedFormattedAmount = "0,00 "; String expectedFormattedAmount = "0,00 Euro";
String formattedAmount = FormattingUtil.formatEuro(amount); String formattedAmount = FormattingUtil.formatEuro(amount);
@@ -49,7 +49,7 @@ class FormattingUtilTest {
@Test @Test
void testFormatEuro_negativeAmount() { void testFormatEuro_negativeAmount() {
double amount = -789.12; double amount = -789.12;
String expectedFormattedAmount = "-789,12 "; String expectedFormattedAmount = "-789,12 Euro";
String formattedAmount = FormattingUtil.formatEuro(amount); String formattedAmount = FormattingUtil.formatEuro(amount);