diff --git a/.gitignore b/.gitignore index ae3d76c..7d81f52 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ config.properties products-config.json +persistence.db ################################################################################################### ## Visual Studio Code ############################################################################# 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 2a0ff06..5441d96 100644 --- a/src/main/java/de/rwu/easydrop/Main.java +++ b/src/main/java/de/rwu/easydrop/Main.java @@ -1,14 +1,20 @@ 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; @@ -41,9 +47,14 @@ public final class Main { 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); catRetriever.loadCatalogues(); - for (ProductCatalogue pCat : catRetriever.getProductCatalogues()) { + 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/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/retriever/CatalogueRetriever.java b/src/main/java/de/rwu/easydrop/service/retriever/CatalogueRetriever.java index e800938..64bb8c0 100644 --- a/src/main/java/de/rwu/easydrop/service/retriever/CatalogueRetriever.java +++ b/src/main/java/de/rwu/easydrop/service/retriever/CatalogueRetriever.java @@ -5,6 +5,7 @@ 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; @@ -60,10 +61,12 @@ public class CatalogueRetriever { newProduct.setDataOrigin(product.getDataOrigin()); newProduct.setProductId(product.getProductId()); - if (product.getDataOrigin().equals("Amazon")) { + if (newProduct.getDataOrigin().equals("Amazon")) { newProduct = productRetriever.getProductFromAmazon(product.getProductId()); - } else if (product.getDataOrigin().equals("eBay")) { + } else if (newProduct.getDataOrigin().equals("eBay")) { newProduct = productRetriever.getProductFromEbay(product.getProductId()); + } else { + throw new InvalidProductException("Product data origin is invalid"); } newProductCatalogue.addProduct(newProduct); 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/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/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 index a0721e9..acaca80 100644 --- a/src/test/java/de/rwu/easydrop/service/retriever/CatalogueRetrieverTest.java +++ b/src/test/java/de/rwu/easydrop/service/retriever/CatalogueRetrieverTest.java @@ -1,6 +1,7 @@ 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; @@ -13,6 +14,7 @@ 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; @@ -40,11 +42,11 @@ class CatalogueRetrieverTest { 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 @@ -72,4 +74,30 @@ class CatalogueRetrieverTest { 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/ProductsConfigIntegrationTest.java b/src/test/java/de/rwu/easydrop/util/ProductsConfigIntegrationTest.java index b848b17..a415933 100644 --- a/src/test/java/de/rwu/easydrop/util/ProductsConfigIntegrationTest.java +++ b/src/test/java/de/rwu/easydrop/util/ProductsConfigIntegrationTest.java @@ -1,14 +1,19 @@ 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"; @@ -65,4 +70,20 @@ class ProductsConfigIntegrationTest { 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"); + } }