Added utility structures + tests

This commit is contained in:
Marvin Scham
2023-06-07 05:46:03 +02:00
parent b9fb6cbc89
commit 3ba366684c
19 changed files with 624 additions and 11 deletions

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@
################################################################################################### ###################################################################################################
config.properties config.properties
products-config.json
################################################################################################### ###################################################################################################
## Visual Studio Code ############################################################################# ## Visual Studio Code #############################################################################

View File

@@ -6,8 +6,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import de.rwu.easydrop.api.client.DataSourceFactory; import de.rwu.easydrop.api.client.DataSourceFactory;
import de.rwu.easydrop.model.ProductCatalogue;
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.util.Config; import de.rwu.easydrop.util.Config;
import de.rwu.easydrop.util.ProductsConfig;
/** /**
* Kickoff point for the service. * Kickoff point for the service.
@@ -34,12 +37,15 @@ public final class Main {
*/ */
public static void main(final String[] args) throws ConfigurationException { public static void main(final String[] args) throws ConfigurationException {
Config config = Config.getInstance(); Config config = Config.getInstance();
ProductsConfig pConfig = ProductsConfig.getInstance();
DataSourceFactory dataSourceFactory = new DataSourceFactory(config); DataSourceFactory dataSourceFactory = new DataSourceFactory(config);
ProductRetriever retriever = new ProductRetriever(dataSourceFactory); ProductRetriever retriever = new ProductRetriever(dataSourceFactory);
CatalogueRetriever catRetriever = new CatalogueRetriever(pConfig, retriever);
String amznProduct = retriever.getProductFromAmazon("B096Y2TYKV").toString(); catRetriever.loadCatalogues();
LOGGER.info(amznProduct); for (ProductCatalogue pCat : catRetriever.getProductCatalogues()) {
String ebayProduct = retriever.getProductFromEbay("Gigabyte GeForce RTX 3060").toString(); String pCatStr = pCat.toString();
LOGGER.info(ebayProduct); LOGGER.info(pCatStr);
}
} }
} }

View File

@@ -0,0 +1,79 @@
package de.rwu.easydrop.model;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
/**
* Holds Product instances for one product from different data sources.
*
* @since 0.2.0
*/
@Data
public class ProductCatalogue {
/**
* Product name.
*/
private String productName;
/**
* Product description.
*/
private String description;
/**
* Product collection.
*/
private List<Product> products;
/**
* Creates new Product Catalogue.
*
* @param newProductName
* @param newDescription
*/
public ProductCatalogue(final String newProductName, final String newDescription) {
this.productName = newProductName;
this.description = newDescription;
this.products = new ArrayList<>();
}
/**
* Adds a product to the catalogue.
*
* @param product
*/
public void addProduct(final Product product) {
products.add(product);
}
/**
* Removes a product from the catalogue.
*
* @param product
*/
public void removeProduct(final Product product) {
products.remove(product);
}
/**
* Removes all products from the catalogue.
*/
public void clearProducts() {
products = new ArrayList<>();
}
/**
* Outputs the catalogue as a string.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Product Catalogue: ").append(productName).append("\n");
sb.append("Description: ").append(description).append("\n");
sb.append("Products:\n");
for (Product product : products) {
sb.append(product.toString()).append("\n");
}
return sb.toString();
}
}

View File

@@ -1,6 +1,6 @@
/** /**
* Packages for supporting business logic. * Packages for supporting business logic.
* *
* TODO implement * @since 0.2.0
*/ */
package de.rwu.easydrop.service; package de.rwu.easydrop.service;

View File

@@ -1,6 +1,6 @@
/** /**
* Supports diverse business processes and enforces business rules. * Supports diverse business processes and enforces business rules.
* *
* TODO implement * @since 0.2.0
*/ */
package de.rwu.easydrop.service.processing; package de.rwu.easydrop.service.processing;

View File

@@ -0,0 +1,75 @@
package de.rwu.easydrop.service.retriever;
import java.util.ArrayList;
import java.util.List;
import javax.naming.ConfigurationException;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.util.ProductsConfig;
import lombok.Data;
/**
* Retrieves data for all products of multiple catalogues.
*
* @since 0.2.0
*/
@Data
public class CatalogueRetriever {
/**
* User-configured products.
*/
private ProductsConfig productsConfig;
/**
* Product Retriever.
*/
private ProductRetriever productRetriever;
/**
* Product catalogue.
*/
private List<ProductCatalogue> productCatalogues;
/**
* Creates a new instance.
*
* @param newProductsConfig
* @param newProductRetriever
*/
public CatalogueRetriever(
final ProductsConfig newProductsConfig, final ProductRetriever newProductRetriever) {
productRetriever = newProductRetriever;
productsConfig = newProductsConfig;
productCatalogues = new ArrayList<>();
}
/**
* Loads catalogues as configured by the user.
*
* @throws ConfigurationException
*/
public void loadCatalogues() throws ConfigurationException {
productsConfig.loadConfig();
for (ProductCatalogue pCat : productsConfig.getProductCatalogues()) {
ProductCatalogue newProductCatalogue = new ProductCatalogue(
pCat.getProductName(), pCat.getDescription());
for (Product product : pCat.getProducts()) {
Product newProduct = new Product();
newProduct.setDataOrigin(product.getDataOrigin());
newProduct.setProductId(product.getProductId());
if (product.getDataOrigin().equals("Amazon")) {
newProduct = productRetriever.getProductFromAmazon(product.getProductId());
} else if (product.getDataOrigin().equals("eBay")) {
newProduct = productRetriever.getProductFromEbay(product.getProductId());
}
newProductCatalogue.addProduct(newProduct);
}
productCatalogues.add(newProductCatalogue);
}
}
}

View File

@@ -52,7 +52,6 @@ public final class Config {
* Returns current config instance. * Returns current config instance.
* *
* @return Config instance * @return Config instance
* @throws ConfigurationException
*/ */
public static Config getInstance() { public static Config getInstance() {
if (instance == null) { if (instance == null) {

View File

@@ -0,0 +1,142 @@
package de.rwu.easydrop.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.naming.ConfigurationException;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.JsonPathException;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue;
/**
* Reads the user-specified catalogue of products.
*
* @since 0.2.0
*/
public final class ProductsConfig {
/**
* Products config location.
*/
private String configLocation = "config/products-config.json";
/**
* @return the configLocation
*/
public String getConfigLocation() {
return configLocation;
}
/**
* @param newConfigLocation the configLocation to set
*/
public void setConfigLocation(final String newConfigLocation) {
this.configLocation = newConfigLocation;
}
/**
* Holds the configured products.
*/
private List<ProductCatalogue> productCatalogues;
/**
* @return the product catalogues
*/
public List<ProductCatalogue> getProductCatalogues() {
return productCatalogues;
}
/**
* Singleton instance.
*/
private static ProductsConfig instance = null;
/**
* Private constructor to prevent external instantiation.
*/
private ProductsConfig() {
productCatalogues = new ArrayList<>();
}
/**
* Returns current products config instance.
*
* @return Products Config instance
*/
public static ProductsConfig getInstance() {
if (instance == null) {
instance = new ProductsConfig();
}
return instance;
}
/**
* Loads user-specified configuration into productCatalogues attribute.
*
* @throws ConfigurationException
*/
public void loadConfig() throws ConfigurationException {
try {
File jsonFile = new File(configLocation).getAbsoluteFile();
ArrayList<HashMap<String, Object>> jsonProducts = JsonPath.read(jsonFile, "$.products");
setProductCatalogues(jsonProducts);
} catch (IOException e) {
throw new ConfigurationException("Couldn't load required products config file");
} catch (JsonPathException e) {
throw new ConfigurationException("Products config is empty or malformed");
}
}
/**
* Loads catalogues from JSON.
*
* @param jsonCatalogues
*/
private void setProductCatalogues(final ArrayList<HashMap<String, Object>> jsonCatalogues) {
for (HashMap<String, Object> productCatalogue : jsonCatalogues) {
String name = productCatalogue.get("name").toString();
String desc = productCatalogue.get("description").toString();
String identifiers = productCatalogue.get("identifiers").toString();
ProductCatalogue pCat = new ProductCatalogue(name, desc);
addProductsToCatalogue(identifiers, pCat);
productCatalogues.add(pCat);
}
}
/**
* Loads products from JSON.
*
* @param jsonIdentifiers
* @param pCat
*/
private void addProductsToCatalogue(final String jsonIdentifiers, final ProductCatalogue pCat) {
ArrayList<HashMap<String, Object>> identifiers = JsonPath.read(jsonIdentifiers, "$");
for (HashMap<String, Object> product : identifiers) {
String dataOrigin = product.keySet().iterator().next();
String identifier = product.get(dataOrigin).toString();
Product newProduct = new Product();
newProduct.setDataOrigin(dataOrigin);
newProduct.setProductId(identifier);
pCat.addProduct(newProduct);
}
}
/**
* Resets the contained product catalogues.
*/
public void reset() {
productCatalogues = new ArrayList<>();
}
}

View File

@@ -1,6 +1,6 @@
/** /**
* General utility such as formatting helpers. * General utility such as formatting helpers.
* *
* TODO implement * @since 0.1.0
*/ */
package de.rwu.easydrop.util; package de.rwu.easydrop.util;

View File

@@ -0,0 +1,93 @@
package de.rwu.easydrop.model;
import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class ProductCatalogueTest {
private ProductCatalogue productCatalogue;
@BeforeEach
public void setup() {
productCatalogue = new ProductCatalogue("GPU", "Graphics Processing Units");
}
@Test
void testAddProduct() {
Product product = new Product();
product.setProductId("12345");
product.setMerchant("AmazonSeller");
product.setDataOrigin("Amazon");
productCatalogue.addProduct(product);
List<Product> products = productCatalogue.getProducts();
Assertions.assertEquals(1, products.size());
Assertions.assertEquals(product, products.get(0));
}
@Test
void testRemoveProduct() {
Product product1 = new Product();
product1.setProductId("12345");
product1.setMerchant("AmazonSeller");
product1.setDataOrigin("Amazon");
productCatalogue.addProduct(product1);
Product product2 = new Product();
product2.setProductId("54321");
product2.setMerchant("eBaySeller");
product2.setDataOrigin("eBay");
productCatalogue.addProduct(product2);
productCatalogue.removeProduct(product1);
List<Product> products = productCatalogue.getProducts();
Assertions.assertEquals(1, products.size());
Assertions.assertEquals(product2, products.get(0));
}
@Test
void testClearProducts() {
Product product1 = new Product();
product1.setProductId("12345");
product1.setMerchant("AmazonSeller");
product1.setDataOrigin("Amazon");
productCatalogue.addProduct(product1);
Product product2 = new Product();
product2.setProductId("54321");
product2.setMerchant("eBay");
product2.setDataOrigin("eBay");
productCatalogue.addProduct(product2);
productCatalogue.clearProducts();
List<Product> products = productCatalogue.getProducts();
Assertions.assertTrue(products.isEmpty());
}
@Test
void testToString() {
Product product1 = new Product();
product1.setProductId("12345");
product1.setMerchant("AmazonSeller");
product1.setDataOrigin("Amazon");
productCatalogue.addProduct(product1);
Product product2 = new Product();
product2.setProductId("54321");
product2.setMerchant("eBaySeller");
product2.setDataOrigin("eBay");
productCatalogue.addProduct(product2);
String expectedString = "Product Catalogue: GPU\n" +
"Description: Graphics Processing Units\n" +
"Products:\n" +
"Product: [12345 from AmazonSeller (Amazon) at 0,00 € (available: no)]\n" +
"Product: [54321 from eBaySeller (eBay) at 0,00 € (available: no)]\n";
Assertions.assertEquals(expectedString, productCatalogue.toString());
}
}

View File

@@ -0,0 +1,75 @@
package de.rwu.easydrop.service.retriever;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import javax.naming.ConfigurationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.util.ProductsConfig;
class CatalogueRetrieverTest {
private ProductsConfig productsConfig;
private ProductRetriever productRetriever;
private CatalogueRetriever catalogueRetriever;
@BeforeEach
public void setup() {
productsConfig = mock(ProductsConfig.class);
productRetriever = mock(ProductRetriever.class);
catalogueRetriever = new CatalogueRetriever(productsConfig, productRetriever);
}
@Test
void loadCatalogues_ValidConfig_CataloguesLoaded() throws ConfigurationException {
// Arrange
List<ProductCatalogue> productCatalogues = new ArrayList<>();
// Create a sample product catalogue with two products
ProductCatalogue productCatalogue = new ProductCatalogue("Catalogue 1", "Sample catalogue");
Product product1 = new Product();
product1.setDataOrigin("Amazon");
product1.setProductId("ASIN1");
productCatalogue.addProduct(product1);
Product product2 = new Product();
product2.setDataOrigin("eBay");
product2.setProductId("ProductID2");
productCatalogue.addProduct(product2);
productCatalogues.add(productCatalogue);
// Mock the methods
when(productsConfig.getProductCatalogues()).thenReturn(productCatalogues);
when(productRetriever.getProductFromAmazon("ASIN1")).thenReturn(product1);
when(productRetriever.getProductFromEbay("ProductID2")).thenReturn(product2);
// Act
catalogueRetriever.loadCatalogues();
// Assert
List<ProductCatalogue> loadedCatalogues = catalogueRetriever.getProductCatalogues();
assertEquals(1, loadedCatalogues.size());
ProductCatalogue loadedCatalogue = loadedCatalogues.get(0);
assertEquals("Catalogue 1", loadedCatalogue.getProductName());
assertEquals("Sample catalogue", loadedCatalogue.getDescription());
List<Product> loadedProducts = loadedCatalogue.getProducts();
assertEquals(2, loadedProducts.size());
assertEquals(product1, loadedProducts.get(0));
assertEquals(product2, loadedProducts.get(1));
// Verify the method invocations
verify(productsConfig).loadConfig();
verify(productRetriever).getProductFromAmazon("ASIN1");
verify(productRetriever).getProductFromEbay("ProductID2");
}
}

View File

@@ -14,7 +14,7 @@ import org.junit.jupiter.api.Test;
class ConfigImplTest { class ConfigImplTest {
private Config config; private Config config;
private final static String TESTDATA_PATH = "src/test/resources/testdata.properties"; private final static String TESTDATA_PATH = "src/test/resources/test.config.properties";
private final static String TESTDATA_KEY = "API_KEY"; private final static String TESTDATA_KEY = "API_KEY";
private final static String TESTDATA_VAL = "keyIsHere"; private final static String TESTDATA_VAL = "keyIsHere";
@@ -77,7 +77,7 @@ class ConfigImplTest {
@Test @Test
void testLoadConfigSuccessfully() { void testLoadConfigSuccessfully() {
try { try {
config.setConfigLocation("src/test/resources/testdata.properties"); config.setConfigLocation("src/test/resources/test.config.properties");
config.loadConfig(); config.loadConfig();
assertEquals(TESTDATA_VAL, config.getProperty(TESTDATA_KEY)); assertEquals(TESTDATA_VAL, config.getProperty(TESTDATA_KEY));
} catch (ConfigurationException e) { } catch (ConfigurationException e) {
@@ -98,7 +98,7 @@ class ConfigImplTest {
@Test @Test
void testReset() throws ConfigurationException { void testReset() throws ConfigurationException {
config.setConfigLocation("src/test/resources/testdata.properties"); config.setConfigLocation("src/test/resources/test.config.properties");
config.loadConfig(); config.loadConfig();
assertNotNull(config.getProperty(TESTDATA_KEY)); assertNotNull(config.getProperty(TESTDATA_KEY));

View File

@@ -0,0 +1,68 @@
package de.rwu.easydrop.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import javax.naming.ConfigurationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class ProductsConfigImplTest {
private ProductsConfig config;
private final static String TESTDATA_PATH = "src/test/resources/test.products-config.json";
private final static String TESTDATA_EMPTY_PATH = "src/test/resources/test.empty.products-config.json";
private final static String TESTDATA_MALFORMED_PATH = "src/test/resources/test.malformed.products-config.json";
@BeforeEach
void setUp() {
config = ProductsConfig.getInstance();
}
@Test
void testLoadConfigSuccessfully() {
try {
config.setConfigLocation(TESTDATA_PATH);
config.loadConfig();
String value = config.getProductCatalogues().get(0).getProductName();
assertEquals("Demo Product", value);
} catch (ConfigurationException e) {
fail("ConfigurationException should not be thrown");
}
}
@Test
void testLoadConfigMissingFile() {
config.setConfigLocation("path/that/doesnt/exist/products-config.json");
ConfigurationException exception = assertThrows(ConfigurationException.class, () -> {
config.loadConfig();
});
assertEquals("Couldn't load required products config file", exception.getMessage());
}
@Test
void testLoadConfigEmptyFile() {
config.setConfigLocation(TESTDATA_EMPTY_PATH);
ConfigurationException exception = assertThrows(ConfigurationException.class, () -> {
config.loadConfig();
});
assertEquals("Products config is empty or malformed", exception.getMessage());
}
@Test
void testLoadConfigMalformedFile() {
config.setConfigLocation(TESTDATA_MALFORMED_PATH);
ConfigurationException exception = assertThrows(ConfigurationException.class, () -> {
config.loadConfig();
});
assertEquals("Products config is empty or malformed", exception.getMessage());
}
}

View File

@@ -0,0 +1,44 @@
package de.rwu.easydrop.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.spy;
import javax.naming.ConfigurationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
class ProductsConfigTest {
private ProductsConfig productsConfig;
@BeforeEach
void setUp() throws ConfigurationException {
MockitoAnnotations.openMocks(this);
productsConfig = spy(ProductsConfig.getInstance());
}
@Test
void testGetInstance() {
ProductsConfig newConfig = ProductsConfig.getInstance();
assertNotNull(newConfig);
}
@Test
void testGetInstanceEquality() {
// Create "two" instances to check validity of Singleton pattern
ProductsConfig instance1 = ProductsConfig.getInstance();
ProductsConfig instance2 = ProductsConfig.getInstance();
assertSame(instance1, instance2, "Instances should be equal");
}
@Test
void testSetConfigLocation() {
String newPath = "new/location/config.properties";
productsConfig.setConfigLocation(newPath);
assertEquals(newPath, productsConfig.getConfigLocation());
}
}

View File

@@ -0,0 +1,15 @@
{
"products": [
{
"name": "Demo Product",
description: "Integration Testing Product",
"identifiers": [
{
"Amazon": "DEMO-AMAZON-001"
},
{
"eBay": "DEMO-EBAY-001"
}
]
]
}

View File

@@ -0,0 +1,16 @@
{
"products": [
{
"name": "Demo Product",
"description": "Integration Testing Product",
"identifiers": [
{
"Amazon": "DEMO-AMAZON-001"
},
{
"eBay": "DEMO-EBAY-001"
}
]
}
]
}