diff --git a/.gitignore b/.gitignore
index 24affe0..7d81f52 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@
###################################################################################################
config.properties
+products-config.json
+persistence.db
###################################################################################################
## Visual Studio Code #############################################################################
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d62761a..71620ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,13 +5,14 @@
### Added
- `EbayItemDataSource` (#37)
+- Product data persistence via SQLite integration (#53)
- Mutation testing capabilities via PITest (#66)
- `Product` class
- Mapping between Product and its corresponding DTO
- Dependencies: Lombok for easier notation, JUnit 5 Params for repetitive tests
-- Product Validation
-- Product Retrieval
-- Unit Tests
+- Product validation, retrieval and persistence writing
+- Product catalogue retrieval and persistence writing
+- More unit tests
### Changed
diff --git a/Script/SonarQube_Local.sh b/Script/SonarQube_Local.sh
index 18b0b37..14d2602 100644
--- a/Script/SonarQube_Local.sh
+++ b/Script/SonarQube_Local.sh
@@ -2,4 +2,6 @@ mvn clean verify sonar:sonar -Pcoverage \
-Dsonar.projectKey=EasyDrop \
-Dsonar.projectName='EasyDrop' \
-Dsonar.host.url=http://localhost:9000 \
- -Dsonar.token=sqp_82d35689c620c15fd1064549375e17a2a5b0b931
\ No newline at end of file
+ -Dsonar.token=sqp_82d35689c620c15fd1064549375e17a2a5b0b931
+
+start http://localhost:9000/dashboard?id=EasyDrop
\ No newline at end of file
diff --git a/config/demo.products-config.json b/config/demo.products-config.json
new file mode 100644
index 0000000..2aa9058
--- /dev/null
+++ b/config/demo.products-config.json
@@ -0,0 +1,16 @@
+{
+ "products": [
+ {
+ "name": "Gigabyte GeForce RTX 3060",
+ "description": "Very epic GPU",
+ "identifiers": [
+ {
+ "Amazon": "B096Y2TYKV"
+ },
+ {
+ "eBay": "Gigabyte GeForce RTX 3060"
+ }
+ ]
+ }
+ ]
+}
diff --git a/pom.xml b/pom.xml
index d45d187..58603a6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,6 +76,12 @@
logback-classic
1.4.7
+
+
+ org.xerial
+ sqlite-jdbc
+ 3.42.0.0
+
diff --git a/src/main/java/de/rwu/easydrop/Main.java b/src/main/java/de/rwu/easydrop/Main.java
index 74b6aec..5441d96 100644
--- a/src/main/java/de/rwu/easydrop/Main.java
+++ b/src/main/java/de/rwu/easydrop/Main.java
@@ -1,13 +1,22 @@
package de.rwu.easydrop;
+import java.util.List;
+
import javax.naming.ConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.sqlite.SQLiteDataSource;
import de.rwu.easydrop.api.client.DataSourceFactory;
+import de.rwu.easydrop.data.connector.AbstractProductPersistence;
+import de.rwu.easydrop.data.connector.SQLiteConnector;
+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.writer.CatalogueWriter;
import de.rwu.easydrop.util.Config;
+import de.rwu.easydrop.util.ProductsConfig;
/**
* Kickoff point for the service.
@@ -34,12 +43,20 @@ public final class Main {
*/
public static void main(final String[] args) throws ConfigurationException {
Config config = Config.getInstance();
+ ProductsConfig pConfig = ProductsConfig.getInstance();
DataSourceFactory dataSourceFactory = new DataSourceFactory(config);
ProductRetriever retriever = new ProductRetriever(dataSourceFactory);
+ CatalogueRetriever catRetriever = new CatalogueRetriever(pConfig, retriever);
+ AbstractProductPersistence db = new SQLiteConnector(new SQLiteDataSource());
+ CatalogueWriter catWriter = new CatalogueWriter(db);
- String amznProduct = retriever.getProductFromAmazon("B096Y2TYKV").toString();
- LOGGER.info(amznProduct);
- String ebayProduct = retriever.getProductFromEbay("Gigabyte GeForce RTX 3060").toString();
- LOGGER.info(ebayProduct);
+ catRetriever.loadCatalogues();
+ List pCats = catRetriever.getProductCatalogues();
+ catWriter.writeCatalogues(pCats);
+
+ for (ProductCatalogue pCat : pCats) {
+ String pCatStr = pCat.toString();
+ LOGGER.info(pCatStr);
+ }
}
}
diff --git a/src/main/java/de/rwu/easydrop/api/client/DataSourceFactory.java b/src/main/java/de/rwu/easydrop/api/client/DataSourceFactory.java
index ba79ab5..490466c 100644
--- a/src/main/java/de/rwu/easydrop/api/client/DataSourceFactory.java
+++ b/src/main/java/de/rwu/easydrop/api/client/DataSourceFactory.java
@@ -2,6 +2,8 @@ package de.rwu.easydrop.api.client;
import javax.naming.ConfigurationException;
+import de.rwu.easydrop.data.connector.AbstractProductPersistence;
+import de.rwu.easydrop.exception.PersistenceException;
import de.rwu.easydrop.util.Config;
/**
@@ -15,6 +17,17 @@ public class DataSourceFactory {
* The data source config.
*/
private Config config;
+ /**
+ * Persistence interface.
+ */
+ private AbstractProductPersistence persistence = null;
+
+ /**
+ * @param newPersistence the persistence to set
+ */
+ public void setPersistence(final AbstractProductPersistence newPersistence) {
+ this.persistence = newPersistence;
+ }
/**
* @param newConfig the config to set
@@ -52,4 +65,17 @@ public class DataSourceFactory {
String apiKey = config.getProperty("EBAY_API_KEY");
return new EbayItemDataSource(apiUrl, apiKey);
}
+
+ /**
+ * Creates a persistence data source.
+ *
+ * @return ProductPersistenceInterface
+ */
+ public AbstractProductPersistence createProductPersistenceDataSource() {
+ if (persistence == null) {
+ throw new PersistenceException("Persistence is not set");
+ }
+
+ return persistence;
+ }
}
diff --git a/src/main/java/de/rwu/easydrop/data/connector/AbstractProductPersistence.java b/src/main/java/de/rwu/easydrop/data/connector/AbstractProductPersistence.java
new file mode 100644
index 0000000..6a302dc
--- /dev/null
+++ b/src/main/java/de/rwu/easydrop/data/connector/AbstractProductPersistence.java
@@ -0,0 +1,34 @@
+package de.rwu.easydrop.data.connector;
+
+import de.rwu.easydrop.api.client.AbstractDataSource;
+import de.rwu.easydrop.api.dto.ProductDTO;
+
+/**
+ * Allows connecting to a persistent product data store.
+ *
+ * @since 0.2.0
+ */
+public abstract class AbstractProductPersistence extends AbstractDataSource {
+ /**
+ * Data origin.
+ */
+ public static final String DATA_ORIGIN = "Persistence";
+
+ /**
+ * Writes a ProductDTO to persistence.
+ *
+ * @param dto
+ */
+ public abstract void saveProduct(ProductDTO dto);
+
+ /**
+ * Gets a ProductDTO from persistence.
+ */
+ @Override
+ public abstract ProductDTO getProductDTOById(String productId);
+
+ /**
+ * Deletes all data from persistence.
+ */
+ public abstract void clearData();
+}
diff --git a/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java b/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java
deleted file mode 100644
index ce6360a..0000000
--- a/src/main/java/de/rwu/easydrop/data/connector/DatabaseConnector.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package de.rwu.easydrop.data.connector;
-
-/**
- * Allows connecting to a SQLite Database.
- *
- * TODO implement
- */
-public class DatabaseConnector {
-
-}
diff --git a/src/main/java/de/rwu/easydrop/data/connector/SQLiteConnector.java b/src/main/java/de/rwu/easydrop/data/connector/SQLiteConnector.java
new file mode 100644
index 0000000..6947c5d
--- /dev/null
+++ b/src/main/java/de/rwu/easydrop/data/connector/SQLiteConnector.java
@@ -0,0 +1,173 @@
+package de.rwu.easydrop.data.connector;
+
+import java.net.URL;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.sqlite.SQLiteDataSource;
+
+import de.rwu.easydrop.api.dto.ProductDTO;
+import de.rwu.easydrop.exception.PersistenceException;
+
+/**
+ * Allows connecting to a SQLite Database.
+ *
+ * @since 0.2.0
+ */
+public final class SQLiteConnector extends AbstractProductPersistence {
+ /**
+ * Data origin.
+ */
+ private static final String DATA_ORIGIN = "SQLite";
+
+ /**
+ * SQLite Database.
+ */
+ private SQLiteDataSource db;
+
+ /**
+ * @param src the db to set
+ */
+ public void setDb(final SQLiteDataSource src) {
+ this.db = src;
+ }
+
+ /**
+ * Path to SQLite db file.
+ */
+ private static final String PERSISTENCE_PATH = "jdbc:sqlite:persistence.db";
+
+ /**
+ * Creates instance.
+ *
+ * @param src SQLite Data Source
+ */
+ public SQLiteConnector(final SQLiteDataSource src) {
+ db = src;
+ db.setUrl(PERSISTENCE_PATH);
+ initializeDatabase();
+ }
+
+ private void initializeDatabase() {
+ try {
+ // Create a new database connection
+ Connection connection = db.getConnection();
+
+ // Execute SQL statements to create tables
+ Statement statement = connection.createStatement();
+ statement.execute(
+ "CREATE TABLE IF NOT EXISTS products ("
+ + "dataOrigin TEXT, "
+ + "productId TEXT, "
+ + "currentPrice REAL, "
+ + "merchant TEXT, "
+ + "deliveryPrice REAL, "
+ + "available INT, "
+ + "lastupdate TEXT, "
+ + "UNIQUE(productId, dataOrigin) ON CONFLICT REPLACE"
+ + ")");
+
+ // Close the statement and connection
+ statement.close();
+ connection.close();
+ } catch (SQLException e) {
+ throw new PersistenceException("Something went wrong while initializing SQLite DB", e);
+ }
+ }
+
+ /**
+ * Writes a ProductDTO to persistence.
+ *
+ * @param dto
+ */
+ public void saveProduct(final ProductDTO dto) {
+ String query = "INSERT INTO products ("
+ + "dataOrigin, productId, currentPrice, merchant, "
+ + "deliveryPrice, available, lastupdate"
+ + ") VALUES ("
+ + "?, ?, ?, ?, ?, ?, datetime('now', 'localtime')"
+ + ")";
+
+ try (Connection connection = db.getConnection();
+ PreparedStatement statement = connection.prepareStatement(query)) {
+ int index = 0;
+
+ statement.setString(++index, dto.getDataOrigin());
+ statement.setString(++index, dto.getProductId());
+ statement.setDouble(++index, dto.getCurrentPrice());
+ statement.setString(++index, dto.getMerchant());
+ statement.setDouble(++index, dto.getDeliveryPrice());
+ statement.setBoolean(++index, dto.isAvailable());
+
+ statement.executeUpdate();
+ } catch (SQLException e) {
+ throw new PersistenceException("Something went wrong while saving to SQLite", e);
+ }
+ }
+
+ @Override
+ public ProductDTO getProductDTOById(final String productId) {
+ String query = "SELECT * FROM products WHERE productId = ?";
+ ProductDTO dto = null;
+
+ try (Connection connection = db.getConnection();
+ PreparedStatement statement = connection.prepareStatement(query)) {
+
+ statement.setString(1, productId);
+
+ try (ResultSet resultSet = statement.executeQuery()) {
+ if (resultSet.next()) {
+ dto = new ProductDTO(resultSet.getString("productId"),
+ resultSet.getString("dataOrigin"));
+ dto.setCurrentPrice(resultSet.getDouble("currentPrice"));
+ dto.setMerchant(resultSet.getString("merchant"));
+ dto.setDeliveryPrice(resultSet.getDouble("deliveryPrice"));
+ dto.setAvailable(resultSet.getBoolean("available"));
+ }
+ }
+ } catch (SQLException e) {
+ throw new PersistenceException("Something went wrong while reading from SQLite", e);
+ }
+
+ return dto;
+ }
+
+ /**
+ * Deletes all data from persistence.
+ */
+ public void clearData() {
+ try (Connection connection = db.getConnection();
+ Statement statement = connection.createStatement()) {
+ String query = "DELETE FROM products";
+ statement.executeUpdate(query);
+ } catch (SQLException e) {
+ throw new PersistenceException("Something went wrong while clearing the database", e);
+ }
+ }
+
+ @Override
+ protected String getDataOrigin() {
+ return DATA_ORIGIN;
+ }
+
+ @Override
+ protected String getApiKey() {
+ throw new UnsupportedOperationException(
+ this.getClass().getName() + " doesn't support getApiKey");
+ }
+
+ @Override
+ protected ProductDTO buildProductDTO(final ProductDTO product, final String json) {
+ throw new UnsupportedOperationException(
+ this.getClass().getName() + " doesn't support buildProductDTO");
+ }
+
+ @Override
+ protected URL createApiUrl(final String productIdentifier) {
+ throw new UnsupportedOperationException(
+ this.getClass().getName() + " doesn't support createApiUrl");
+ }
+}
diff --git a/src/main/java/de/rwu/easydrop/data/connector/package-info.java b/src/main/java/de/rwu/easydrop/data/connector/package-info.java
index f5b2dc7..77ea22e 100644
--- a/src/main/java/de/rwu/easydrop/data/connector/package-info.java
+++ b/src/main/java/de/rwu/easydrop/data/connector/package-info.java
@@ -1,6 +1,6 @@
/**
* Connectors for databases.
*
- * TODO implement
+ * @since 0.2.0
*/
package de.rwu.easydrop.data.connector;
diff --git a/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java b/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java
deleted file mode 100644
index f787da9..0000000
--- a/src/main/java/de/rwu/easydrop/data/dao/ProductDAO.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package de.rwu.easydrop.data.dao;
-
-/**
- * Product data access object.
- *
- * TODO implement
- */
-public class ProductDAO {
-
-}
diff --git a/src/main/java/de/rwu/easydrop/data/dao/package-info.java b/src/main/java/de/rwu/easydrop/data/dao/package-info.java
deleted file mode 100644
index 2affd23..0000000
--- a/src/main/java/de/rwu/easydrop/data/dao/package-info.java
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Data access objects for business objects created from persistence.
- *
- * TODO implement
- */
-package de.rwu.easydrop.data.dao;
diff --git a/src/main/java/de/rwu/easydrop/data/package-info.java b/src/main/java/de/rwu/easydrop/data/package-info.java
index a161322..dfbf299 100644
--- a/src/main/java/de/rwu/easydrop/data/package-info.java
+++ b/src/main/java/de/rwu/easydrop/data/package-info.java
@@ -1,6 +1,6 @@
/**
* Structure for business objects and persisting their info.
*
- * TODO implement
+ * @since 0.2.0
*/
package de.rwu.easydrop.data;
diff --git a/src/main/java/de/rwu/easydrop/exception/PersistenceException.java b/src/main/java/de/rwu/easydrop/exception/PersistenceException.java
new file mode 100644
index 0000000..0e32528
--- /dev/null
+++ b/src/main/java/de/rwu/easydrop/exception/PersistenceException.java
@@ -0,0 +1,27 @@
+package de.rwu.easydrop.exception;
+
+/**
+ * Exception that signifies a problem with data persistence.
+ *
+ * @since 0.2.0
+ */
+public class PersistenceException extends RuntimeException {
+ /**
+ * Throws an exception that signifies the data of a Product are invalid.
+ *
+ * @param message
+ */
+ public PersistenceException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Throws an exception that signifies the data of a Product are invalid.
+ *
+ * @param message
+ * @param cause
+ */
+ public PersistenceException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/de/rwu/easydrop/model/ProductCatalogue.java b/src/main/java/de/rwu/easydrop/model/ProductCatalogue.java
new file mode 100644
index 0000000..97f31dc
--- /dev/null
+++ b/src/main/java/de/rwu/easydrop/model/ProductCatalogue.java
@@ -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 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();
+ }
+}
diff --git a/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java b/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java
index 1b37a11..123080a 100644
--- a/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java
+++ b/src/main/java/de/rwu/easydrop/service/mapping/ProductMapper.java
@@ -1,16 +1,17 @@
package de.rwu.easydrop.service.mapping;
import de.rwu.easydrop.api.dto.ProductDTO;
+
import de.rwu.easydrop.model.Product;
/**
- * Maps between Product, ProductDAO and ProductDTO.
+ * Maps between Product, ProductDTO and ProductDTO.
*
* @since 0.2.0
*
* @see Product
* @see ProductDTO
- * @see ProductDAO
+ * @see ProductDTO
*/
public final class ProductMapper {
@@ -41,4 +42,21 @@ public final class ProductMapper {
return product;
}
+
+ /**
+ * Creates a ProductDTO object from a corresponding Product.
+ *
+ * @param product Product
+ * @return ProductDTO
+ */
+ public static ProductDTO mapProductToDTO(final Product product) {
+ ProductDTO dto = new ProductDTO(product.getProductId(), product.getDataOrigin());
+
+ dto.setAvailable(product.isAvailable());
+ dto.setCurrentPrice(product.getCurrentPrice());
+ dto.setDeliveryPrice(product.getDeliveryPrice());
+ dto.setMerchant(product.getMerchant());
+
+ return dto;
+ }
}
diff --git a/src/main/java/de/rwu/easydrop/service/package-info.java b/src/main/java/de/rwu/easydrop/service/package-info.java
index ff59e0c..18ad0d7 100644
--- a/src/main/java/de/rwu/easydrop/service/package-info.java
+++ b/src/main/java/de/rwu/easydrop/service/package-info.java
@@ -1,6 +1,6 @@
/**
* Packages for supporting business logic.
*
- * TODO implement
+ * @since 0.2.0
*/
package de.rwu.easydrop.service;
diff --git a/src/main/java/de/rwu/easydrop/service/processing/package-info.java b/src/main/java/de/rwu/easydrop/service/processing/package-info.java
index db7cb9e..c373207 100644
--- a/src/main/java/de/rwu/easydrop/service/processing/package-info.java
+++ b/src/main/java/de/rwu/easydrop/service/processing/package-info.java
@@ -1,6 +1,6 @@
/**
* Supports diverse business processes and enforces business rules.
*
- * TODO implement
+ * @since 0.2.0
*/
package de.rwu.easydrop.service.processing;
diff --git a/src/main/java/de/rwu/easydrop/service/retriever/CatalogueRetriever.java b/src/main/java/de/rwu/easydrop/service/retriever/CatalogueRetriever.java
new file mode 100644
index 0000000..64bb8c0
--- /dev/null
+++ b/src/main/java/de/rwu/easydrop/service/retriever/CatalogueRetriever.java
@@ -0,0 +1,78 @@
+package de.rwu.easydrop.service.retriever;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.naming.ConfigurationException;
+
+import de.rwu.easydrop.exception.InvalidProductException;
+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 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 (newProduct.getDataOrigin().equals("Amazon")) {
+ newProduct = productRetriever.getProductFromAmazon(product.getProductId());
+ } else if (newProduct.getDataOrigin().equals("eBay")) {
+ newProduct = productRetriever.getProductFromEbay(product.getProductId());
+ } else {
+ throw new InvalidProductException("Product data origin is invalid");
+ }
+
+ newProductCatalogue.addProduct(newProduct);
+ }
+
+ productCatalogues.add(newProductCatalogue);
+ }
+ }
+}
diff --git a/src/main/java/de/rwu/easydrop/service/retriever/ProductRetriever.java b/src/main/java/de/rwu/easydrop/service/retriever/ProductRetriever.java
index bce5d10..da842c3 100644
--- a/src/main/java/de/rwu/easydrop/service/retriever/ProductRetriever.java
+++ b/src/main/java/de/rwu/easydrop/service/retriever/ProductRetriever.java
@@ -4,6 +4,7 @@ import de.rwu.easydrop.api.client.AmazonProductDataSource;
import de.rwu.easydrop.api.client.DataSourceFactory;
import de.rwu.easydrop.api.client.EbayItemDataSource;
import de.rwu.easydrop.api.dto.ProductDTO;
+import de.rwu.easydrop.data.connector.AbstractProductPersistence;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.service.mapping.ProductMapper;
import de.rwu.easydrop.service.validation.ProductValidator;
@@ -64,4 +65,20 @@ public class ProductRetriever {
return product;
}
+
+ /**
+ * Retrieves a product from persistence.
+ *
+ * @param productId
+ * @return Product from persistence
+ */
+ public Product getProductFromPersistence(final String productId) {
+ AbstractProductPersistence src = dataSourceFactory.createProductPersistenceDataSource();
+
+ ProductDTO dto = src.getProductDTOById(productId);
+ Product product = ProductMapper.mapProductFromDTO(dto);
+ ProductValidator.validate(product);
+
+ return product;
+ }
}
diff --git a/src/main/java/de/rwu/easydrop/service/writer/CatalogueWriter.java b/src/main/java/de/rwu/easydrop/service/writer/CatalogueWriter.java
new file mode 100644
index 0000000..a1cf486
--- /dev/null
+++ b/src/main/java/de/rwu/easydrop/service/writer/CatalogueWriter.java
@@ -0,0 +1,47 @@
+package de.rwu.easydrop.service.writer;
+
+import java.util.List;
+
+import de.rwu.easydrop.api.dto.ProductDTO;
+import de.rwu.easydrop.data.connector.AbstractProductPersistence;
+import de.rwu.easydrop.model.Product;
+import de.rwu.easydrop.model.ProductCatalogue;
+import de.rwu.easydrop.service.mapping.ProductMapper;
+import de.rwu.easydrop.service.validation.ProductValidator;
+
+/**
+ * Writes data for all products of multiple catalogues to persistence.
+ *
+ * @since 0.2.0
+ */
+public final class CatalogueWriter {
+ /**
+ * Holds a persistence reference.
+ */
+ private AbstractProductPersistence persistence;
+
+ /**
+ * Creates new instance.
+ *
+ * @param newPersistence
+ */
+ public CatalogueWriter(final AbstractProductPersistence newPersistence) {
+ persistence = newPersistence;
+ }
+
+ /**
+ * Writes all products of specified catalogues to persistence.
+ *
+ * @param catalogues
+ */
+ public void writeCatalogues(final List catalogues) {
+ for (ProductCatalogue pCat : catalogues) {
+ for (Product product : pCat.getProducts()) {
+ ProductValidator.validate(product);
+ ProductDTO dto = ProductMapper.mapProductToDTO(product);
+
+ persistence.saveProduct(dto);
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/rwu/easydrop/service/writer/ProductWriter.java b/src/main/java/de/rwu/easydrop/service/writer/ProductWriter.java
new file mode 100644
index 0000000..d1be850
--- /dev/null
+++ b/src/main/java/de/rwu/easydrop/service/writer/ProductWriter.java
@@ -0,0 +1,38 @@
+package de.rwu.easydrop.service.writer;
+
+import de.rwu.easydrop.api.dto.ProductDTO;
+import de.rwu.easydrop.data.connector.AbstractProductPersistence;
+import de.rwu.easydrop.model.Product;
+import de.rwu.easydrop.service.mapping.ProductMapper;
+import de.rwu.easydrop.service.validation.ProductValidator;
+
+/**
+ * Wrapper for writing product info to persistence.
+ *
+ * @since 0.2.0
+ */
+public class ProductWriter {
+ /**
+ * Persistence.
+ */
+ private AbstractProductPersistence persistence;
+
+ /**
+ * @param newPersistence the persistence to set
+ */
+ public void setPersistence(final AbstractProductPersistence newPersistence) {
+ this.persistence = newPersistence;
+ }
+
+ /**
+ * Validates and saves product to persistence.
+ *
+ * @param product
+ */
+ public void writeProductToPersistence(final Product product) {
+ ProductValidator.validate(product);
+ ProductDTO dto = ProductMapper.mapProductToDTO(product);
+
+ persistence.saveProduct(dto);
+ }
+}
diff --git a/src/main/java/de/rwu/easydrop/service/writer/package-info.java b/src/main/java/de/rwu/easydrop/service/writer/package-info.java
new file mode 100644
index 0000000..09c58f8
--- /dev/null
+++ b/src/main/java/de/rwu/easydrop/service/writer/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * Writes Objects to a data store.
+ *
+ * @since 0.2.0
+ */
+package de.rwu.easydrop.service.writer;
diff --git a/src/main/java/de/rwu/easydrop/util/Config.java b/src/main/java/de/rwu/easydrop/util/Config.java
index 32cce6a..e40b3d4 100644
--- a/src/main/java/de/rwu/easydrop/util/Config.java
+++ b/src/main/java/de/rwu/easydrop/util/Config.java
@@ -52,7 +52,6 @@ public final class Config {
* Returns current config instance.
*
* @return Config instance
- * @throws ConfigurationException
*/
public static Config getInstance() {
if (instance == null) {
diff --git a/src/main/java/de/rwu/easydrop/util/ProductsConfig.java b/src/main/java/de/rwu/easydrop/util/ProductsConfig.java
new file mode 100644
index 0000000..b69777f
--- /dev/null
+++ b/src/main/java/de/rwu/easydrop/util/ProductsConfig.java
@@ -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 productCatalogues;
+
+ /**
+ * @return the product catalogues
+ */
+ public List 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> 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> jsonCatalogues) {
+ for (HashMap 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> identifiers = JsonPath.read(jsonIdentifiers, "$");
+
+ for (HashMap 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<>();
+ }
+}
diff --git a/src/main/java/de/rwu/easydrop/util/package-info.java b/src/main/java/de/rwu/easydrop/util/package-info.java
index 40789f2..ed8e5eb 100644
--- a/src/main/java/de/rwu/easydrop/util/package-info.java
+++ b/src/main/java/de/rwu/easydrop/util/package-info.java
@@ -1,6 +1,6 @@
/**
* General utility such as formatting helpers.
*
- * TODO implement
+ * @since 0.1.0
*/
package de.rwu.easydrop.util;
diff --git a/src/test/java/de/rwu/easydrop/api/client/DataSourceFactoryTest.java b/src/test/java/de/rwu/easydrop/api/client/DataSourceFactoryTest.java
index c84df2a..f1c85b0 100644
--- a/src/test/java/de/rwu/easydrop/api/client/DataSourceFactoryTest.java
+++ b/src/test/java/de/rwu/easydrop/api/client/DataSourceFactoryTest.java
@@ -1,6 +1,8 @@
package de.rwu.easydrop.api.client;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
import javax.naming.ConfigurationException;
@@ -9,7 +11,10 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.sqlite.SQLiteDataSource;
+import de.rwu.easydrop.data.connector.SQLiteConnector;
+import de.rwu.easydrop.exception.PersistenceException;
import de.rwu.easydrop.util.Config;
class DataSourceFactoryTest {
@@ -45,4 +50,20 @@ class DataSourceFactoryTest {
// Assert
assertEquals("ebay-api-key", dataSource.getApiKey());
}
+
+ @Test
+ void createProductPersistenceDataSource_NullPersistence() {
+ PersistenceException exception = assertThrows(PersistenceException.class, () -> {
+ dataSourceFactory.createProductPersistenceDataSource();
+ });
+
+ assertEquals("Persistence is not set", exception.getMessage());
+ }
+
+ @Test
+ void createProductPersistenceDataSource_WorkingPersistence() {
+ dataSourceFactory.setPersistence(new SQLiteConnector(new SQLiteDataSource()));
+
+ assertDoesNotThrow(() -> dataSourceFactory.createProductPersistenceDataSource());
+ }
}
diff --git a/src/test/java/de/rwu/easydrop/data/connector/SQLiteConnectorTest.java b/src/test/java/de/rwu/easydrop/data/connector/SQLiteConnectorTest.java
new file mode 100644
index 0000000..2e54299
--- /dev/null
+++ b/src/test/java/de/rwu/easydrop/data/connector/SQLiteConnectorTest.java
@@ -0,0 +1,195 @@
+package de.rwu.easydrop.data.connector;
+
+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.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.Mockito.doThrow;
+
+import java.sql.SQLException;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sqlite.SQLiteDataSource;
+
+import de.rwu.easydrop.api.dto.ProductDTO;
+import de.rwu.easydrop.exception.PersistenceException;
+
+@TestInstance(Lifecycle.PER_CLASS)
+class SQLiteConnectorTest {
+ private static final String TEST_PRODUCT_ID = "12345";
+ private SQLiteConnector sqliteConnector;
+
+ @Mock
+ private SQLiteDataSource mockDataSource;
+
+ @BeforeAll
+ public void setup() {
+ sqliteConnector = new SQLiteConnector(new SQLiteDataSource());
+ }
+
+ @BeforeEach
+ public void clearDatabase() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ void saveProduct_ValidProduct_SuccessfullySaved() {
+ // Arrange
+ sqliteConnector.clearData();
+ ProductDTO ProductDTO = new ProductDTO(TEST_PRODUCT_ID, "Amazon");
+ ProductDTO.setDataOrigin("Amazon");
+ ProductDTO.setProductId(TEST_PRODUCT_ID);
+ ProductDTO.setCurrentPrice(9.99);
+ ProductDTO.setMerchant("Sample Merchant");
+ ProductDTO.setDeliveryPrice(2.50);
+ ProductDTO.setAvailable(true);
+
+ // Act
+ assertDoesNotThrow(() -> sqliteConnector.saveProduct(ProductDTO));
+
+ // Assert
+ ProductDTO savedProductDTO = sqliteConnector.getProductDTOById(TEST_PRODUCT_ID);
+ assertNotNull(savedProductDTO);
+ assertEquals("Amazon", savedProductDTO.getDataOrigin());
+ assertEquals(TEST_PRODUCT_ID, savedProductDTO.getProductId());
+ assertEquals(9.99, savedProductDTO.getCurrentPrice());
+ assertEquals("Sample Merchant", savedProductDTO.getMerchant());
+ assertEquals(2.50, savedProductDTO.getDeliveryPrice());
+ assertTrue(savedProductDTO.isAvailable());
+ }
+
+ @Test
+ void getProductDTOById_ProductExists_ReturnsProductDTO() {
+ // Arrange
+ sqliteConnector.clearData();
+ insertSampleProduct();
+
+ // Act
+ ProductDTO ProductDTO = sqliteConnector.getProductDTOById(TEST_PRODUCT_ID);
+
+ // Assert
+ assertNotNull(ProductDTO);
+ assertEquals("Amazon", ProductDTO.getDataOrigin());
+ assertEquals(TEST_PRODUCT_ID, ProductDTO.getProductId());
+ assertEquals(9.99, ProductDTO.getCurrentPrice());
+ assertEquals("Sample Merchant", ProductDTO.getMerchant());
+ assertEquals(2.50, ProductDTO.getDeliveryPrice());
+ assertTrue(ProductDTO.isAvailable());
+ }
+
+ @Test
+ void constructor_ThrowsPersistenceException_OnSQLException() {
+ try {
+ doThrow(SQLException.class).when(mockDataSource).getConnection();
+
+ PersistenceException exception = assertThrows(PersistenceException.class, () -> {
+ new SQLiteConnector(mockDataSource);
+ });
+
+ assertEquals("Something went wrong while initializing SQLite DB", exception.getMessage());
+ } catch (SQLException e) {
+ fail("No SQLException should be thrown");
+ }
+ }
+
+ @Test
+ void getProductDTOById_ProductDoesNotExist_ReturnsNull() {
+ // Act
+ ProductDTO ProductDTO = sqliteConnector.getProductDTOById("FAKE_ID");
+
+ // Assert
+ assertNull(ProductDTO);
+ }
+
+ @Test
+ void saveProduct_ThrowsPersistenceException_OnSQLException() throws SQLException {
+ // Arrange
+ ProductDTO ProductDTO = new ProductDTO(TEST_PRODUCT_ID, "Amazon");
+ sqliteConnector.setDb(mockDataSource);
+ doThrow(SQLException.class).when(mockDataSource).getConnection();
+
+ // Act and Assert
+ assertThrows(PersistenceException.class, () -> sqliteConnector.saveProduct(ProductDTO));
+ }
+
+ @Test
+ void getProductDTOById_ThrowsPersistenceException_OnSQLException() throws SQLException {
+ // Arrange
+ String productId = "12345";
+ sqliteConnector.setDb(mockDataSource);
+ doThrow(SQLException.class).when(mockDataSource).getConnection();
+
+ // Act and Assert
+ assertThrows(PersistenceException.class, () -> sqliteConnector.getProductDTOById(productId));
+ }
+
+ @Test
+ void clearData_ThrowsPersistenceException_OnSQLException() throws SQLException {
+ // Arrange
+ sqliteConnector.setDb(mockDataSource);
+ doThrow(SQLException.class).when(mockDataSource).getConnection();
+
+ // Act and Assert
+ assertThrows(PersistenceException.class, () -> sqliteConnector.clearData());
+ }
+
+ private void insertSampleProduct() {
+ ProductDTO ProductDTO = new ProductDTO(TEST_PRODUCT_ID, "Amazon");
+ ProductDTO.setCurrentPrice(9.99);
+ ProductDTO.setMerchant("Sample Merchant");
+ ProductDTO.setDeliveryPrice(2.50);
+ ProductDTO.setAvailable(true);
+ sqliteConnector.saveProduct(ProductDTO);
+ }
+
+ @Test
+ void getDataOrigin_ReturnsCorrectDataOrigin() {
+ // Arrange
+ SQLiteConnector connector = new SQLiteConnector(new SQLiteDataSource());
+
+ // Act
+ String dataOrigin = connector.getDataOrigin();
+
+ // Assert
+ assertEquals("SQLite", dataOrigin);
+ }
+
+ @Test
+ void getApiKey_UnsupportedOperationExceptionThrown() {
+ // Arrange
+ SQLiteConnector connector = new SQLiteConnector(new SQLiteDataSource());
+
+ // Act and Assert
+ assertThrows(UnsupportedOperationException.class, connector::getApiKey);
+ }
+
+ @Test
+ void buildProductDTO_UnsupportedOperationExceptionThrown() {
+ // Arrange
+ SQLiteConnector connector = new SQLiteConnector(new SQLiteDataSource());
+ ProductDTO product = new ProductDTO("ASIN123", "Amazon");
+ String json = "{\"productId\":\"ASIN123\",\"dataOrigin\":\"Amazon\"}";
+
+ // Act and Assert
+ assertThrows(UnsupportedOperationException.class, () -> connector.buildProductDTO(product, json));
+ }
+
+ @Test
+ void createApiUrl_UnsupportedOperationExceptionThrown() {
+ // Arrange
+ SQLiteConnector connector = new SQLiteConnector(new SQLiteDataSource());
+ String productIdentifier = "ASIN123";
+
+ // Act and Assert
+ assertThrows(UnsupportedOperationException.class, () -> connector.createApiUrl(productIdentifier));
+ }
+}
diff --git a/src/test/java/de/rwu/easydrop/exception/PersistenceExceptionTest.java b/src/test/java/de/rwu/easydrop/exception/PersistenceExceptionTest.java
new file mode 100644
index 0000000..b5d2ae2
--- /dev/null
+++ b/src/test/java/de/rwu/easydrop/exception/PersistenceExceptionTest.java
@@ -0,0 +1,28 @@
+package de.rwu.easydrop.exception;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+class PersistenceExceptionTest {
+
+ @Test
+ void testPersistenceExceptionWithMessage() {
+ String errorMessage = "Error occurred during data persistence.";
+ PersistenceException exception = new PersistenceException(errorMessage);
+
+ assertEquals(errorMessage, exception.getMessage());
+ assertNull(exception.getCause());
+ }
+
+ @Test
+ void testPersistenceExceptionWithMessageAndCause() {
+ String errorMessage = "Error occurred during data persistence.";
+ Throwable cause = new IllegalArgumentException("Invalid argument.");
+ PersistenceException exception = new PersistenceException(errorMessage, cause);
+
+ assertEquals(errorMessage, exception.getMessage());
+ assertEquals(cause, exception.getCause());
+ }
+}
diff --git a/src/test/java/de/rwu/easydrop/model/ProductCatalogueTest.java b/src/test/java/de/rwu/easydrop/model/ProductCatalogueTest.java
new file mode 100644
index 0000000..f070e2d
--- /dev/null
+++ b/src/test/java/de/rwu/easydrop/model/ProductCatalogueTest.java
@@ -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 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 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 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());
+ }
+}
diff --git a/src/test/java/de/rwu/easydrop/service/mapping/ProductMapperTest.java b/src/test/java/de/rwu/easydrop/service/mapping/ProductMapperTest.java
index 6dbf741..6239dbf 100644
--- a/src/test/java/de/rwu/easydrop/service/mapping/ProductMapperTest.java
+++ b/src/test/java/de/rwu/easydrop/service/mapping/ProductMapperTest.java
@@ -11,6 +11,7 @@ import java.lang.reflect.Modifier;
import org.junit.jupiter.api.Test;
import de.rwu.easydrop.api.dto.ProductDTO;
+
import de.rwu.easydrop.model.Product;
class ProductMapperTest {
@@ -53,4 +54,27 @@ class ProductMapperTest {
dto.setMerchant("Example Merchant");
return dto;
}
+
+ @Test
+ void mapProductToDTO() {
+ // Arrange
+ Product product = new Product();
+ product.setProductId("12345");
+ product.setDataOrigin("Amazon");
+ product.setAvailable(true);
+ product.setCurrentPrice(9.99);
+ product.setDeliveryPrice(2.50);
+ product.setMerchant("Seller1");
+
+ // Act
+ ProductDTO dto = ProductMapper.mapProductToDTO(product);
+
+ // Assert
+ assertEquals("12345", dto.getProductId());
+ assertEquals("Amazon", dto.getDataOrigin());
+ assertTrue(dto.isAvailable());
+ assertEquals(9.99, dto.getCurrentPrice());
+ assertEquals(2.50, dto.getDeliveryPrice());
+ assertEquals("Seller1", dto.getMerchant());
+ }
}
diff --git a/src/test/java/de/rwu/easydrop/service/retriever/CatalogueRetrieverTest.java b/src/test/java/de/rwu/easydrop/service/retriever/CatalogueRetrieverTest.java
new file mode 100644
index 0000000..acaca80
--- /dev/null
+++ b/src/test/java/de/rwu/easydrop/service/retriever/CatalogueRetrieverTest.java
@@ -0,0 +1,103 @@
+package de.rwu.easydrop.service.retriever;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+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.exception.InvalidProductException;
+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 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 loadedCatalogues = catalogueRetriever.getProductCatalogues();
+ assertEquals(1, loadedCatalogues.size());
+
+ ProductCatalogue loadedCatalogue = loadedCatalogues.get(0);
+ assertEquals("Catalogue 1", loadedCatalogue.getProductName());
+ assertEquals("Sample catalogue", loadedCatalogue.getDescription());
+ List 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");
+ }
+
+ @Test
+ void loadCatalogues_ValidConfig_CataloguesLoaded_InvalidProduct() throws ConfigurationException {
+ // Arrange
+ List productCatalogues = new ArrayList<>();
+
+ // Create a sample product catalogue
+ ProductCatalogue productCatalogue = new ProductCatalogue("Catalogue 1", "Sample catalogue");
+
+ Product product = new Product();
+ product.setDataOrigin("");
+ product.setProductId("ProductID1");
+ productCatalogue.addProduct(product);
+ productCatalogues.add(productCatalogue);
+
+ // Mock the methods
+ when(productsConfig.getProductCatalogues()).thenReturn(productCatalogues);
+ when(productRetriever.getProductFromAmazon("ProductID1")).thenReturn(product);
+
+ // Act and Assert
+ InvalidProductException exception = assertThrows(InvalidProductException.class, () -> {
+ catalogueRetriever.loadCatalogues();
+ });
+
+ assertEquals("Product data origin is invalid", exception.getMessage());
+ }
+}
diff --git a/src/test/java/de/rwu/easydrop/service/retriever/ProductRetrieverTest.java b/src/test/java/de/rwu/easydrop/service/retriever/ProductRetrieverTest.java
index 36dff9b..32afecc 100644
--- a/src/test/java/de/rwu/easydrop/service/retriever/ProductRetrieverTest.java
+++ b/src/test/java/de/rwu/easydrop/service/retriever/ProductRetrieverTest.java
@@ -17,6 +17,7 @@ import de.rwu.easydrop.api.client.AmazonProductDataSource;
import de.rwu.easydrop.api.client.DataSourceFactory;
import de.rwu.easydrop.api.client.EbayItemDataSource;
import de.rwu.easydrop.api.dto.ProductDTO;
+import de.rwu.easydrop.data.connector.AbstractProductPersistence;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.util.Config;
@@ -33,6 +34,8 @@ class ProductRetrieverTest {
private ProductDTO productDTO;
@Mock
private Product product;
+ @Mock
+ private AbstractProductPersistence persistence;
private ProductRetriever productRetriever;
@@ -84,4 +87,25 @@ class ProductRetrieverTest {
assertEquals(9.99, result.getCurrentPrice());
verify(ebayDataSource, times(1)).getProductDTOById(productQuery);
}
+
+ @Test
+ void getProductFromPersistence_ValidProductId_ReturnsProduct() {
+ // Arrange
+ String productId = "123";
+ when(dataSourceFactory.createProductPersistenceDataSource()).thenReturn(persistence);
+ when(persistence.getProductDTOById(productId)).thenReturn(productDTO);
+ when(productDTO.getProductId()).thenReturn(productId);
+ when(productDTO.getCurrentPrice()).thenReturn(9.99);
+ when(productDTO.getDataOrigin()).thenReturn("Amazon");
+
+ // Act
+ Product result = productRetriever.getProductFromPersistence(productId);
+
+ // Assert
+ assertEquals(productId, result.getProductId());
+ assertEquals(9.99, result.getCurrentPrice());
+
+ // Verify the interactions
+ verify(persistence, times(1)).getProductDTOById(productId);
+ }
}
diff --git a/src/test/java/de/rwu/easydrop/service/writer/CatalogueWriterTest.java b/src/test/java/de/rwu/easydrop/service/writer/CatalogueWriterTest.java
new file mode 100644
index 0000000..71fdff5
--- /dev/null
+++ b/src/test/java/de/rwu/easydrop/service/writer/CatalogueWriterTest.java
@@ -0,0 +1,69 @@
+package de.rwu.easydrop.service.writer;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import de.rwu.easydrop.api.dto.ProductDTO;
+import de.rwu.easydrop.data.connector.AbstractProductPersistence;
+import de.rwu.easydrop.model.Product;
+import de.rwu.easydrop.model.ProductCatalogue;
+
+class CatalogueWriterTest {
+
+ @Mock
+ private AbstractProductPersistence persistenceMock;
+
+ private CatalogueWriter catalogueWriter;
+
+ @BeforeEach
+ void setup() {
+ MockitoAnnotations.openMocks(this);
+ catalogueWriter = new CatalogueWriter(persistenceMock);
+ }
+
+ @Test
+ void writeCatalogues_ValidCatalogues_ProductsWrittenToPersistence() {
+ // Arrange
+ List catalogues = createSampleCatalogues();
+
+ // Act
+ catalogueWriter.writeCatalogues(catalogues);
+
+ // Assert
+ verify(persistenceMock, times(4)).saveProduct(any(ProductDTO.class));
+ }
+
+ private List createSampleCatalogues() {
+ List catalogues = new ArrayList<>();
+
+ ProductCatalogue catalogue1 = new ProductCatalogue("Catalogue 1", "Sample catalogue 1");
+ catalogue1.addProduct(createSampleProduct("Amazon", "ID 1"));
+ catalogue1.addProduct(createSampleProduct("eBay", "ID 2"));
+
+ ProductCatalogue catalogue2 = new ProductCatalogue("Catalogue 2", "Sample catalogue 2");
+ catalogue2.addProduct(createSampleProduct("Amazon", "ID 3"));
+ catalogue2.addProduct(createSampleProduct("eBay", "ID 4"));
+
+ catalogues.add(catalogue1);
+ catalogues.add(catalogue2);
+
+ return catalogues;
+ }
+
+ private Product createSampleProduct(String dataOrigin, String productId) {
+ Product product = new Product();
+ product.setDataOrigin(dataOrigin);
+ product.setProductId(productId);
+ product.setCurrentPrice(9999.99);
+ return product;
+ }
+}
diff --git a/src/test/java/de/rwu/easydrop/service/writer/ProductWriterTest.java b/src/test/java/de/rwu/easydrop/service/writer/ProductWriterTest.java
new file mode 100644
index 0000000..aab0cdd
--- /dev/null
+++ b/src/test/java/de/rwu/easydrop/service/writer/ProductWriterTest.java
@@ -0,0 +1,55 @@
+package de.rwu.easydrop.service.writer;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import de.rwu.easydrop.api.dto.ProductDTO;
+import de.rwu.easydrop.data.connector.AbstractProductPersistence;
+import de.rwu.easydrop.model.Product;
+
+class ProductWriterTest {
+
+ @Mock
+ private AbstractProductPersistence persistence;
+
+ private ProductWriter productWriter;
+
+ @BeforeEach
+ public void setup() {
+ MockitoAnnotations.openMocks(this);
+ productWriter = new ProductWriter();
+ productWriter.setPersistence(persistence);
+ }
+
+ @Test
+ void writeProductToPersistence_ValidProduct_CallsSaveProduct() {
+ // Arrange
+ Product product = new Product();
+ product.setProductId("12345");
+ product.setDataOrigin("Amazon");
+ product.setCurrentPrice(9.99);
+
+ // Act
+ productWriter.writeProductToPersistence(product);
+
+ // Assert
+ Mockito.verify(persistence).saveProduct(any(ProductDTO.class));
+ }
+
+ @Test
+ void writeProductToPersistence_InvalidProduct_ThrowsException() {
+ // Arrange
+ Product product = new Product();
+ product.setProductId("");
+ product.setDataOrigin("Amazon");
+
+ // Act and Assert
+ assertThrows(Exception.class, () -> productWriter.writeProductToPersistence(product));
+ }
+}
diff --git a/src/test/java/de/rwu/easydrop/util/ConfigImplTest.java b/src/test/java/de/rwu/easydrop/util/ConfigIntegrationTest.java
similarity index 93%
rename from src/test/java/de/rwu/easydrop/util/ConfigImplTest.java
rename to src/test/java/de/rwu/easydrop/util/ConfigIntegrationTest.java
index 59cece7..2df710d 100644
--- a/src/test/java/de/rwu/easydrop/util/ConfigImplTest.java
+++ b/src/test/java/de/rwu/easydrop/util/ConfigIntegrationTest.java
@@ -12,9 +12,9 @@ import javax.naming.ConfigurationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-class ConfigImplTest {
+class ConfigIntegrationTest {
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_VAL = "keyIsHere";
@@ -77,7 +77,7 @@ class ConfigImplTest {
@Test
void testLoadConfigSuccessfully() {
try {
- config.setConfigLocation("src/test/resources/testdata.properties");
+ config.setConfigLocation("src/test/resources/test.config.properties");
config.loadConfig();
assertEquals(TESTDATA_VAL, config.getProperty(TESTDATA_KEY));
} catch (ConfigurationException e) {
@@ -98,7 +98,7 @@ class ConfigImplTest {
@Test
void testReset() throws ConfigurationException {
- config.setConfigLocation("src/test/resources/testdata.properties");
+ config.setConfigLocation("src/test/resources/test.config.properties");
config.loadConfig();
assertNotNull(config.getProperty(TESTDATA_KEY));
diff --git a/src/test/java/de/rwu/easydrop/util/ProductsConfigIntegrationTest.java b/src/test/java/de/rwu/easydrop/util/ProductsConfigIntegrationTest.java
new file mode 100644
index 0000000..a415933
--- /dev/null
+++ b/src/test/java/de/rwu/easydrop/util/ProductsConfigIntegrationTest.java
@@ -0,0 +1,89 @@
+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.assertThrows;
+import static org.junit.jupiter.api.Assertions.fail;
+
+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.ProductCatalogue;
+
+class ProductsConfigIntegrationTest {
+ 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());
+ }
+
+ @Test
+ void testReset() {
+ try {
+ config.setConfigLocation(TESTDATA_PATH);
+ config.loadConfig();
+ } catch (ConfigurationException e) {
+ fail("ConfigurationException should not be thrown");
+ }
+
+ assertNotNull(config.getProductCatalogues());
+ config.reset();
+ List pCats = config.getProductCatalogues();
+
+ assertEquals(0, pCats.size(), "Catalogues list should be empty");
+ }
+}
diff --git a/src/test/java/de/rwu/easydrop/util/ProductsConfigTest.java b/src/test/java/de/rwu/easydrop/util/ProductsConfigTest.java
new file mode 100644
index 0000000..b791d2a
--- /dev/null
+++ b/src/test/java/de/rwu/easydrop/util/ProductsConfigTest.java
@@ -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());
+ }
+}
diff --git a/src/test/resources/testdata.properties b/src/test/resources/test.config.properties
similarity index 100%
rename from src/test/resources/testdata.properties
rename to src/test/resources/test.config.properties
diff --git a/src/test/resources/test.empty.products-config.json b/src/test/resources/test.empty.products-config.json
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/resources/empty.properties b/src/test/resources/test.empty.properties
similarity index 100%
rename from src/test/resources/empty.properties
rename to src/test/resources/test.empty.properties
diff --git a/src/test/resources/test.malformed.products-config.json b/src/test/resources/test.malformed.products-config.json
new file mode 100644
index 0000000..00d0092
--- /dev/null
+++ b/src/test/resources/test.malformed.products-config.json
@@ -0,0 +1,15 @@
+{
+ "products": [
+ {
+ "name": "Demo Product",
+ description: "Integration Testing Product",
+ "identifiers": [
+ {
+ "Amazon": "DEMO-AMAZON-001"
+ },
+ {
+ "eBay": "DEMO-EBAY-001"
+ }
+ ]
+ ]
+}
diff --git a/src/test/resources/test.products-config.json b/src/test/resources/test.products-config.json
new file mode 100644
index 0000000..44262c8
--- /dev/null
+++ b/src/test/resources/test.products-config.json
@@ -0,0 +1,16 @@
+{
+ "products": [
+ {
+ "name": "Demo Product",
+ "description": "Integration Testing Product",
+ "identifiers": [
+ {
+ "Amazon": "DEMO-AMAZON-001"
+ },
+ {
+ "eBay": "DEMO-EBAY-001"
+ }
+ ]
+ }
+ ]
+}