Merge branch '#75-Transactions' into 'dev'

Merge feature branches Offers/Transactions

See merge request team1/sandbox2!11
This commit is contained in:
Marvin Scham
2023-06-28 02:39:44 +00:00
91 changed files with 3035 additions and 637 deletions

View File

@@ -3,3 +3,4 @@ AMAZON_API_URL=
AMAZON_API_KEY= AMAZON_API_KEY=
EBAY_API_URL= EBAY_API_URL=
EBAY_API_KEY= EBAY_API_KEY=
WEBSHOPS=amazon,ebay

View File

@@ -5,10 +5,10 @@
"description": "Very epic GPU", "description": "Very epic GPU",
"identifiers": [ "identifiers": [
{ {
"Amazon": "B096Y2TYKV" "AMAZON": "B096Y2TYKV"
}, },
{ {
"eBay": "Gigabyte GeForce RTX 3060" "EBAY": "Gigabyte GeForce RTX 3060"
} }
] ]
} }

View File

@@ -9,3 +9,4 @@ services:
image: easydrop:0.3.0-SNAPSHOT image: easydrop:0.3.0-SNAPSHOT
volumes: volumes:
- ./config:/config - ./config:/config
- ./persistence.db:/persistence.db

View File

@@ -1,23 +1,8 @@
package de.rwu.easydrop; package de.rwu.easydrop;
import java.util.List;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import org.slf4j.Logger; import de.rwu.easydrop.core.Core;
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.processing.OrderManager;
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. * Kickoff point for the service.
@@ -25,11 +10,6 @@ import de.rwu.easydrop.util.ProductsConfig;
* @since 0.1.0 * @since 0.1.0
*/ */
public final class Main { public final class Main {
/**
* Logger for main process.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
/** /**
* Prevents unwanted instantiation. * Prevents unwanted instantiation.
*/ */
@@ -43,23 +23,6 @@ public final class Main {
* @param args * @param args
*/ */
public static void main(final String[] args) throws ConfigurationException { public static void main(final String[] args) throws ConfigurationException {
Config config = Config.getInstance(); Core.run();
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);
catRetriever.loadCatalogues();
List<ProductCatalogue> pCats = catRetriever.getProductCatalogues();
catWriter.writeCatalogues(pCats);
for (ProductCatalogue pCat : pCats) {
String pCatStr = pCat.toString();
LOGGER.info(pCatStr);
}
OrderManager.createOrders(pCats);
} }
} }

View File

@@ -9,6 +9,7 @@ import java.net.URL;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.exception.DataSourceException; import de.rwu.easydrop.exception.DataSourceException;
import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.util.FormattingUtil; import de.rwu.easydrop.util.FormattingUtil;
/** /**
@@ -23,7 +24,7 @@ public abstract class AbstractDataSource implements DataSource {
* *
* @return Data source name * @return Data source name
*/ */
protected abstract String getDataOrigin(); protected abstract Webshop getDataOrigin();
/** /**
* Returns the data source's API key. * Returns the data source's API key.
@@ -48,7 +49,7 @@ public abstract class AbstractDataSource implements DataSource {
public ProductDTO getProductDTOById(final String productIdentifier) public ProductDTO getProductDTOById(final String productIdentifier)
throws IllegalArgumentException { throws IllegalArgumentException {
StringBuilder response = new StringBuilder(); StringBuilder response = new StringBuilder();
String dataOrigin = getDataOrigin(); Webshop dataOrigin = getDataOrigin();
String apiKey = getApiKey(); String apiKey = getApiKey();
ProductDTO product = new ProductDTO(productIdentifier, dataOrigin); ProductDTO product = new ProductDTO(productIdentifier, dataOrigin);
@@ -78,7 +79,13 @@ public abstract class AbstractDataSource implements DataSource {
} }
reader.close(); reader.close();
buildProductDTO(product, response.toString()); String data;
if (response.toString().isEmpty()) {
data = "{}";
} else {
data = response.toString();
}
buildProductDTO(product, data);
} catch (IOException e) { } catch (IOException e) {
throw new DataSourceException( throw new DataSourceException(
"Couldn't fulfill " "Couldn't fulfill "

View File

@@ -7,6 +7,7 @@ import java.net.URL;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.exception.DataWriterException; import de.rwu.easydrop.exception.DataWriterException;
import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.util.FormattingUtil; import de.rwu.easydrop.util.FormattingUtil;
/** /**
@@ -28,7 +29,7 @@ public abstract class AbstractDataWriter {
* *
* @return Data target API name * @return Data target API name
*/ */
protected abstract String getDataTarget(); protected abstract Webshop getDataTarget();
/** /**
* Creates an URL object to connect to the API with. * Creates an URL object to connect to the API with.
@@ -47,7 +48,7 @@ public abstract class AbstractDataWriter {
*/ */
protected void sendPutRequest(final ProductDTO dto, final String apiType) { protected void sendPutRequest(final ProductDTO dto, final String apiType) {
String apiKey = getApiKey(); String apiKey = getApiKey();
String dataTarget = getDataTarget(); Webshop dataTarget = getDataTarget();
if (!dataTarget.equals(dto.getDataOrigin())) { if (!dataTarget.equals(dto.getDataOrigin())) {
throw new DataWriterException( throw new DataWriterException(

View File

@@ -8,6 +8,8 @@ import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.ReadContext; import com.jayway.jsonpath.ReadContext;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.util.Timestamp;
/** /**
* Interface to an Amazon data source. * Interface to an Amazon data source.
@@ -18,7 +20,7 @@ public final class AmazonProductDataSource extends AbstractDataSource {
/** /**
* Name of this data source. * Name of this data source.
*/ */
private static final String DATA_ORIGIN = "Amazon"; private static final Webshop DATA_ORIGIN = Webshop.AMAZON;
/** /**
* Base URL to the Amazon data source. * Base URL to the Amazon data source.
*/ */
@@ -60,6 +62,7 @@ public final class AmazonProductDataSource extends AbstractDataSource {
product.setDeliveryPrice( product.setDeliveryPrice(
ctx.read(root + "shippingOptions[0].shippingCost.value.amount", double.class)); ctx.read(root + "shippingOptions[0].shippingCost.value.amount", double.class));
product.setMerchant(ctx.read(root + "merchant.name", String.class)); product.setMerchant(ctx.read(root + "merchant.name", String.class));
product.setLastUpdate(Timestamp.now());
} catch (PathNotFoundException e) { } catch (PathNotFoundException e) {
// Pass, allow incomplete ProductDTO to pass for later validation // Pass, allow incomplete ProductDTO to pass for later validation
} }
@@ -82,7 +85,7 @@ public final class AmazonProductDataSource extends AbstractDataSource {
} }
@Override @Override
protected String getDataOrigin() { protected Webshop getDataOrigin() {
return DATA_ORIGIN; return DATA_ORIGIN;
} }

View File

@@ -3,6 +3,8 @@ package de.rwu.easydrop.api.client;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import de.rwu.easydrop.model.Webshop;
/** /**
* Sends a buy request to the Amazon API. * Sends a buy request to the Amazon API.
* *
@@ -12,7 +14,7 @@ public final class AmazonPurchaser extends AbstractPurchaser {
/** /**
* Name of this data source. * Name of this data source.
*/ */
private static final String DATA_TARGET = "Amazon"; private static final Webshop DATA_TARGET = Webshop.AMAZON;
/** /**
* Base URL to the Amazon Purchase API. * Base URL to the Amazon Purchase API.
*/ */
@@ -61,7 +63,7 @@ public final class AmazonPurchaser extends AbstractPurchaser {
} }
@Override @Override
protected String getDataTarget() { protected Webshop getDataTarget() {
return DATA_TARGET; return DATA_TARGET;
} }
} }

View File

@@ -3,6 +3,8 @@ package de.rwu.easydrop.api.client;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import de.rwu.easydrop.model.Webshop;
/** /**
* Sends a sell request to the Amazon API. * Sends a sell request to the Amazon API.
* *
@@ -12,7 +14,7 @@ public final class AmazonSeller extends AbstractSeller {
/** /**
* Name of this data source. * Name of this data source.
*/ */
private static final String DATA_TARGET = "Amazon"; private static final Webshop DATA_TARGET = Webshop.AMAZON;
/** /**
* Base URL to the Amazon Purchase API. * Base URL to the Amazon Purchase API.
*/ */
@@ -61,7 +63,7 @@ public final class AmazonSeller extends AbstractSeller {
} }
@Override @Override
protected String getDataTarget() { protected Webshop getDataTarget() {
return DATA_TARGET; return DATA_TARGET;
} }
} }

View File

@@ -2,8 +2,6 @@ package de.rwu.easydrop.api.client;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
import de.rwu.easydrop.exception.PersistenceException;
import de.rwu.easydrop.util.Config; import de.rwu.easydrop.util.Config;
/** /**
@@ -17,17 +15,6 @@ public class DataSourceFactory {
* The data source config. * The data source config.
*/ */
private Config 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 * @param newConfig the config to set
@@ -65,17 +52,4 @@ public class DataSourceFactory {
String apiKey = config.getProperty("EBAY_API_KEY"); String apiKey = config.getProperty("EBAY_API_KEY");
return new EbayItemDataSource(apiUrl, apiKey); 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;
}
} }

View File

@@ -8,6 +8,8 @@ import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.ReadContext; import com.jayway.jsonpath.ReadContext;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.util.Timestamp;
/** /**
* Interface to an eBay data source. * Interface to an eBay data source.
@@ -18,7 +20,7 @@ public final class EbayItemDataSource extends AbstractDataSource {
/** /**
* Name of this data source. * Name of this data source.
*/ */
private static final String DATA_ORIGIN = "eBay"; private static final Webshop DATA_ORIGIN = Webshop.EBAY;
/** /**
* Base URL to the eBay data source. * Base URL to the eBay data source.
*/ */
@@ -69,6 +71,7 @@ public final class EbayItemDataSource extends AbstractDataSource {
product.setDeliveryPrice( product.setDeliveryPrice(
ctx.read(root + "shippingOptions[0].shippingCost.value", double.class)); ctx.read(root + "shippingOptions[0].shippingCost.value", double.class));
product.setMerchant(ctx.read(root + "seller.username", String.class)); product.setMerchant(ctx.read(root + "seller.username", String.class));
product.setLastUpdate(Timestamp.now());
} catch (PathNotFoundException e) { } catch (PathNotFoundException e) {
// Pass, allow incomplete ProductDTO to pass for later validation // Pass, allow incomplete ProductDTO to pass for later validation
} }
@@ -77,7 +80,7 @@ public final class EbayItemDataSource extends AbstractDataSource {
} }
@Override @Override
protected String getDataOrigin() { protected Webshop getDataOrigin() {
return DATA_ORIGIN; return DATA_ORIGIN;
} }

View File

@@ -3,6 +3,8 @@ package de.rwu.easydrop.api.client;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import de.rwu.easydrop.model.Webshop;
/** /**
* Sends a buy request to the eBay API. * Sends a buy request to the eBay API.
* *
@@ -12,7 +14,7 @@ public final class EbayPurchaser extends AbstractPurchaser {
/** /**
* Name of this data source. * Name of this data source.
*/ */
private static final String DATA_TARGET = "eBay"; private static final Webshop DATA_TARGET = Webshop.EBAY;
/** /**
* Base URL to the eBay Purchase API. * Base URL to the eBay Purchase API.
*/ */
@@ -46,7 +48,7 @@ public final class EbayPurchaser extends AbstractPurchaser {
} }
@Override @Override
protected String getDataTarget() { protected Webshop getDataTarget() {
return DATA_TARGET; return DATA_TARGET;
} }
} }

View File

@@ -3,6 +3,8 @@ package de.rwu.easydrop.api.client;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import de.rwu.easydrop.model.Webshop;
/** /**
* Sends a sell request to the eBay API. * Sends a sell request to the eBay API.
* *
@@ -12,7 +14,7 @@ public final class EbaySeller extends AbstractSeller {
/** /**
* Name of this data source. * Name of this data source.
*/ */
private static final String DATA_TARGET = "eBay"; private static final Webshop DATA_TARGET = Webshop.EBAY;
/** /**
* Base URL to the eBay Purchase API. * Base URL to the eBay Purchase API.
*/ */
@@ -46,7 +48,7 @@ public final class EbaySeller extends AbstractSeller {
} }
@Override @Override
protected String getDataTarget() { protected Webshop getDataTarget() {
return DATA_TARGET; return DATA_TARGET;
} }
} }

View File

@@ -0,0 +1,55 @@
package de.rwu.easydrop.api.client;
import javax.naming.ConfigurationException;
import de.rwu.easydrop.util.Config;
/**
* Factory for Buyer API accessors.
*
* @since 0.3.0
*/
public class PurchaserFactory {
/**
* The data source config.
*/
private Config config;
/**
* @param newConfig the config to set
*/
public void setConfig(final Config newConfig) throws ConfigurationException {
this.config = newConfig;
this.config.loadConfig();
}
/**
* @param newConfig
*/
public PurchaserFactory(final Config newConfig) throws ConfigurationException {
this.setConfig(newConfig);
}
/**
* Creates an Amazon purchaser.
*
* @return AmazonPurchaser
*/
public AmazonPurchaser createAmazonPurchaser() {
String apiUrl = config.getProperty("AMAZON_API_URL");
String apiKey = config.getProperty("AMAZON_API_KEY");
return new AmazonPurchaser(apiUrl, apiKey);
}
/**
* Creates an eBay purchaser.
*
* @return EbayPurchaser
*/
public EbayPurchaser createEbayPurchaser() {
String apiUrl = config.getProperty("EBAY_API_URL");
String apiKey = config.getProperty("EBAY_API_KEY");
return new EbayPurchaser(apiUrl, apiKey);
}
}

View File

@@ -0,0 +1,55 @@
package de.rwu.easydrop.api.client;
import javax.naming.ConfigurationException;
import de.rwu.easydrop.util.Config;
/**
* Factory for Sales API accessors.
*
* @since 0.3.0
*/
public class SellerFactory {
/**
* The data source config.
*/
private Config config;
/**
* @param newConfig the config to set
*/
public void setConfig(final Config newConfig) throws ConfigurationException {
this.config = newConfig;
this.config.loadConfig();
}
/**
* @param newConfig
*/
public SellerFactory(final Config newConfig) throws ConfigurationException {
this.setConfig(newConfig);
}
/**
* Creates an Amazon Seller.
*
* @return AmazonSeller
*/
public AmazonSeller createAmazonSeller() {
String apiUrl = config.getProperty("AMAZON_API_URL");
String apiKey = config.getProperty("AMAZON_API_KEY");
return new AmazonSeller(apiUrl, apiKey);
}
/**
* Creates an eBay Seller.
*
* @return EbaySeller
*/
public EbaySeller createEbaySeller() {
String apiUrl = config.getProperty("EBAY_API_URL");
String apiKey = config.getProperty("EBAY_API_KEY");
return new EbaySeller(apiUrl, apiKey);
}
}

View File

@@ -0,0 +1,33 @@
package de.rwu.easydrop.api.dto;
import lombok.Data;
/*
* Offer data transfer object.
*
* @since 0.3.0
*/
@Data
public class OfferDTO {
/**
* ID of the offer, built from identifiers of the source platforms.
*/
private String offerId;
/**
* The product that our software buys.
*/
private ProductDTO sourceProduct;
/**
* The product that our software sells.
*/
private ProductDTO targetProduct;
/**
* Date of last update of the offer.
*/
private String lastUpdate;
}

View File

@@ -1,5 +1,6 @@
package de.rwu.easydrop.api.dto; package de.rwu.easydrop.api.dto;
import de.rwu.easydrop.model.Webshop;
import lombok.Data; import lombok.Data;
/** /**
@@ -10,9 +11,9 @@ import lombok.Data;
@Data @Data
public class ProductDTO { public class ProductDTO {
/** /**
* Data source platform, like "Amazon". * Data source platform, like Amazon.
*/ */
private String dataOrigin; private Webshop dataOrigin;
/** /**
* Platform internal product identifier. * Platform internal product identifier.
@@ -39,13 +40,18 @@ public class ProductDTO {
*/ */
private boolean available; private boolean available;
/**
* Last update from API.
*/
private String lastUpdate;
/** /**
* Creates ProductDTO instance. * Creates ProductDTO instance.
* *
* @param newProductId Internal Product indetifier * @param newProductId Internal Product indetifier
* @param newDataOrigin Data Origin * @param newDataOrigin Data Origin
*/ */
public ProductDTO(final String newProductId, final String newDataOrigin) { public ProductDTO(final String newProductId, final Webshop newDataOrigin) {
this.productId = newProductId; this.productId = newProductId;
this.dataOrigin = newDataOrigin; this.dataOrigin = newDataOrigin;
} }

View File

@@ -0,0 +1,28 @@
package de.rwu.easydrop.api.dto;
import lombok.Data;
/**
* Transaction data transfer object.
*
* @since 0.3.0
*/
@Data
public class TransactionDTO {
/**
* Offer ID.
*/
private String offerId;
/**
* Sales volume.
*/
private double volume;
/**
* Earnings (volume - cost).
*/
private double earnings;
/**
* Transaction timestamp.
*/
private String transactionTime;
}

View File

@@ -0,0 +1,92 @@
package de.rwu.easydrop.core;
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.api.client.PurchaserFactory;
import de.rwu.easydrop.api.client.SellerFactory;
import de.rwu.easydrop.api.dto.OfferDTO;
import de.rwu.easydrop.data.connector.OfferPersistenceInterface;
import de.rwu.easydrop.data.connector.ProductPersistenceInterface;
import de.rwu.easydrop.data.connector.SQLiteConnector;
import de.rwu.easydrop.data.connector.TransactionPersistenceInterface;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.service.mapping.OfferMapper;
import de.rwu.easydrop.service.processing.OfferIdentifier;
import de.rwu.easydrop.service.processing.OfferProvisioner;
import de.rwu.easydrop.service.processing.TransactionHandler;
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;
/**
* The application core.
*
* @since 0.3.0
*/
public final class Core {
/**
* Logging instance.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(Core.class);
/**
* Hidden Constructor.
*/
private Core() {
// Hidden constructor
}
/**
* Runs the core.
*
* @throws ConfigurationException
*/
public static void run() throws ConfigurationException {
LOGGER.info("Loading config...");
Config config = Config.getInstance();
ProductsConfig pConfig = ProductsConfig.getInstance();
LOGGER.info("Preparing...");
DataSourceFactory dataSourceFactory = new DataSourceFactory(config);
SellerFactory sellerFactory = new SellerFactory(config);
PurchaserFactory purchaserFactory = new PurchaserFactory(config);
ProductPersistenceInterface productDB = new SQLiteConnector(new SQLiteDataSource());
OfferPersistenceInterface offerDB = new SQLiteConnector(new SQLiteDataSource());
TransactionPersistenceInterface txDB = new SQLiteConnector(new SQLiteDataSource());
ProductRetriever retriever = new ProductRetriever(dataSourceFactory, productDB);
CatalogueRetriever catRetriever = new CatalogueRetriever(pConfig, retriever);
CatalogueWriter catWriter = new CatalogueWriter(productDB);
LOGGER.info("Loading catalogues");
catRetriever.loadCatalogues();
List<ProductCatalogue> pCats = catRetriever.getProductCatalogues();
catWriter.writeCatalogues(pCats);
LOGGER.info("Creating offers");
OfferIdentifier ident = new OfferIdentifier();
OfferProvisioner provis = new OfferProvisioner(offerDB, sellerFactory, purchaserFactory);
List<Offer> identifiedOffers = ident.runIdentifier(pCats);
provis.runProvisioner(identifiedOffers);
LOGGER.info("Creating transactions");
TransactionHandler txHandler = new TransactionHandler(txDB);
for (OfferDTO o : offerDB.getOffers()) {
txHandler.turnOfferToTransaction(OfferMapper.mapOfferFromDTO(o));
}
txDB.outputTransactionsToLog();
txDB.outputSummaryToLog();
}
}

View File

@@ -0,0 +1,6 @@
/**
* EasyDrop's core.
*
* @since 0.3.0
*/
package de.rwu.easydrop.core;

View File

@@ -1,34 +0,0 @@
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();
}

View File

@@ -0,0 +1,46 @@
package de.rwu.easydrop.data.connector;
import java.util.List;
import de.rwu.easydrop.api.dto.OfferDTO;
/**
* Interface to offer persistence.
*
* @since 0.3.0
*/
public interface OfferPersistenceInterface {
/**
* Writes a ProductDTO to persistence.
*
* @param dto
*/
void writeOffer(OfferDTO dto);
/**
* Gets a OfferDTO from persistence.
*
* @param offerId
* @return Offer data transfer object
*/
OfferDTO getOfferDTOById(String offerId);
/**
* Gets all offerDTOs from persistence.
*
* @return offerDTOs
*/
List<OfferDTO> getOffers();
/**
* Deletes all offer data from persistence.
*/
void clearOfferData();
/**
* Deletes an offer from persistence.
*
* @param offerId
*/
void deleteOfferById(String offerId);
}

View File

@@ -0,0 +1,30 @@
package de.rwu.easydrop.data.connector;
import de.rwu.easydrop.api.dto.ProductDTO;
/**
* Allows connecting to a persistent product data store.
*
* @since 0.2.0
*/
public interface ProductPersistenceInterface {
/**
* Writes a ProductDTO to persistence.
*
* @param dto
*/
void writeProduct(ProductDTO dto);
/**
* Gets a ProductDTO from persistence.
*
* @param productId Product identifier
* @return Product data transfer object
*/
ProductDTO getProductDTOById(String productId);
/**
* Deletes all product data from persistence.
*/
void clearProductData();
}

View File

@@ -1,27 +1,41 @@
package de.rwu.easydrop.data.connector; package de.rwu.easydrop.data.connector;
import java.net.URL;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sqlite.SQLiteDataSource; import org.sqlite.SQLiteDataSource;
import de.rwu.easydrop.api.dto.OfferDTO;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.api.dto.TransactionDTO;
import de.rwu.easydrop.exception.PersistenceException; import de.rwu.easydrop.exception.PersistenceException;
import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.util.FormattingUtil;
/** /**
* Allows connecting to a SQLite Database. * Allows connecting to a SQLite Database.
* *
* @since 0.2.0 * @since 0.2.0
*/ */
public final class SQLiteConnector extends AbstractProductPersistence { public final class SQLiteConnector implements
ProductPersistenceInterface, OfferPersistenceInterface,
TransactionPersistenceInterface {
/** /**
* Data origin. * Base for calculating percentages.
*/ */
private static final String DATA_ORIGIN = "SQLite"; private static final int PERCENT = 100;
/**
* Logging instance.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(SQLiteConnector.class);
/** /**
* SQLite Database. * SQLite Database.
@@ -39,6 +53,15 @@ public final class SQLiteConnector extends AbstractProductPersistence {
* Path to SQLite db file. * Path to SQLite db file.
*/ */
private static final String PERSISTENCE_PATH = "jdbc:sqlite:persistence.db"; private static final String PERSISTENCE_PATH = "jdbc:sqlite:persistence.db";
/**
* Name of 'lastUpdate' column.
*/
private static final String LAST_UPDATE_COL_NAME = "lastUpdate";
/**
* Name of 'offerId' column.
*/
private static final String OFFERID_COL_NAME = "offerId";
/** /**
* Creates instance. * Creates instance.
@@ -57,8 +80,8 @@ public final class SQLiteConnector extends AbstractProductPersistence {
Connection connection = db.getConnection(); Connection connection = db.getConnection();
// Execute SQL statements to create tables // Execute SQL statements to create tables
Statement statement = connection.createStatement(); Statement createProducts = connection.createStatement();
statement.execute( createProducts.execute(
"CREATE TABLE IF NOT EXISTS products (" "CREATE TABLE IF NOT EXISTS products ("
+ "dataOrigin TEXT, " + "dataOrigin TEXT, "
+ "productId TEXT, " + "productId TEXT, "
@@ -66,12 +89,39 @@ public final class SQLiteConnector extends AbstractProductPersistence {
+ "merchant TEXT, " + "merchant TEXT, "
+ "deliveryPrice REAL, " + "deliveryPrice REAL, "
+ "available INT, " + "available INT, "
+ "lastupdate TEXT, " + "lastUpdate TEXT, "
+ "UNIQUE(productId, dataOrigin) ON CONFLICT REPLACE" + "UNIQUE(productId, dataOrigin) ON CONFLICT REPLACE"
+ ")"); + ")");
// Close the statement and connection // Close the statement
statement.close(); createProducts.close();
Statement createOffers = connection.createStatement();
createOffers.execute(
"CREATE TABLE IF NOT EXISTS offers ("
+ "offerId TEXT, "
+ "sourceWebshop TEXT, "
+ "sourceId TEXT,"
+ "sourcePrice REAL, "
+ "targetWebshop TEXT, "
+ "targetId TEXT, "
+ "targetPrice REAL, "
+ "lastUpdate TEXT, "
+ "UNIQUE(offerId) ON CONFLICT REPLACE"
+ ")");
createOffers.close();
Statement createTransactions = connection.createStatement();
createTransactions.execute(
"CREATE TABLE IF NOT EXISTS transactions ("
+ "offerId TEXT, "
+ "earnings REAL, "
+ "volume REAL, "
+ "transactionTime TEXT"
+ ")");
createTransactions.close();
// Close the connection
connection.close(); connection.close();
} catch (SQLException e) { } catch (SQLException e) {
throw new PersistenceException("Something went wrong while initializing SQLite DB", e); throw new PersistenceException("Something went wrong while initializing SQLite DB", e);
@@ -83,53 +133,64 @@ public final class SQLiteConnector extends AbstractProductPersistence {
* *
* @param dto * @param dto
*/ */
public void saveProduct(final ProductDTO dto) { public void writeProduct(final ProductDTO dto) {
String query = "INSERT INTO products (" String query = "INSERT INTO products ("
+ "dataOrigin, productId, currentPrice, merchant, " + "dataOrigin, productId, currentPrice, merchant, "
+ "deliveryPrice, available, lastupdate" + "deliveryPrice, available, lastUpdate"
+ ") VALUES ( " + ") VALUES ( "
+ "?, ?, ?, ?, ?, ?, datetime('now', 'localtime')" + "?, ?, ?, ?, ?, ?, ?"
+ ")"; + ")";
try (Connection connection = db.getConnection(); try (Connection connection = db.getConnection();
PreparedStatement statement = connection.prepareStatement(query)) { PreparedStatement statement = connection.prepareStatement(query)) {
int index = 0; int index = 0;
statement.setString(++index, dto.getDataOrigin()); statement.setString(++index, dto.getDataOrigin().toString());
statement.setString(++index, dto.getProductId()); statement.setString(++index, dto.getProductId());
statement.setDouble(++index, dto.getCurrentPrice()); statement.setDouble(++index, dto.getCurrentPrice());
statement.setString(++index, dto.getMerchant()); statement.setString(++index, dto.getMerchant());
statement.setDouble(++index, dto.getDeliveryPrice()); statement.setDouble(++index, dto.getDeliveryPrice());
statement.setBoolean(++index, dto.isAvailable()); statement.setBoolean(++index, dto.isAvailable());
statement.setString(++index, dto.getLastUpdate());
statement.executeUpdate(); statement.executeUpdate();
} catch (SQLException e) { } catch (SQLException e) {
throw new PersistenceException("Something went wrong while saving to SQLite", e); throw new PersistenceException(
"Something went wrong while saving product to SQLite", e);
} }
} }
@Override /**
* Gets a ProductDTO by identifier.
*
* @param productId
* @return DTO
*/
public ProductDTO getProductDTOById(final String productId) { public ProductDTO getProductDTOById(final String productId) {
String query = "SELECT * FROM products WHERE productId = ?"; String query = "SELECT * FROM products WHERE productId = ?";
ProductDTO dto = null; ProductDTO dto = null;
try (Connection connection = db.getConnection(); try {
PreparedStatement statement = connection.prepareStatement(query)) { Connection connection = db.getConnection();
PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, productId); statement.setString(1, productId);
try (ResultSet resultSet = statement.executeQuery()) { try (ResultSet resultSet = statement.executeQuery()) {
if (resultSet.next()) { if (resultSet.next()) {
dto = new ProductDTO(resultSet.getString("productId"), Webshop newShop = Webshop.fromString(resultSet.getString("dataOrigin"));
resultSet.getString("dataOrigin"));
dto = new ProductDTO(resultSet.getString("productId"), newShop);
dto.setCurrentPrice(resultSet.getDouble("currentPrice")); dto.setCurrentPrice(resultSet.getDouble("currentPrice"));
dto.setMerchant(resultSet.getString("merchant")); dto.setMerchant(resultSet.getString("merchant"));
dto.setDeliveryPrice(resultSet.getDouble("deliveryPrice")); dto.setDeliveryPrice(resultSet.getDouble("deliveryPrice"));
dto.setAvailable(resultSet.getBoolean("available")); dto.setAvailable(resultSet.getBoolean("available"));
dto.setLastUpdate(resultSet.getString(LAST_UPDATE_COL_NAME));
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
throw new PersistenceException("Something went wrong while reading from SQLite", e); throw new PersistenceException(
"Something went wrong while reading product from SQLite");
} }
return dto; return dto;
@@ -137,11 +198,201 @@ public final class SQLiteConnector extends AbstractProductPersistence {
/** /**
* Deletes all data from persistence. * Deletes all data from persistence.
*
* @throws SQLException
*/ */
public void clearData() { public void clearData() {
clearProductData();
clearOfferData();
clearTXData();
}
/**
* Writes an offer to persistence.
*
* @param dto OfferDTO
*/
public void writeOffer(final OfferDTO dto) {
String query = "INSERT INTO offers ("
+ "offerId, "
+ "sourceWebshop, sourceId, sourcePrice, "
+ "targetWebshop, targetId, targetPrice, "
+ LAST_UPDATE_COL_NAME
+ ") VALUES ("
+ "?, ?, ?, ?, ?, ?, ?, ?"
+ ")";
try (Connection connection = db.getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
int index = 0;
ProductDTO sourceProduct = dto.getSourceProduct();
ProductDTO targetProduct = dto.getTargetProduct();
statement.setString(++index, dto.getOfferId());
statement.setString(++index, sourceProduct.getDataOrigin().toString());
statement.setString(++index, sourceProduct.getProductId());
statement.setDouble(++index, sourceProduct.getCurrentPrice());
statement.setString(++index, targetProduct.getDataOrigin().toString());
statement.setString(++index, targetProduct.getProductId());
statement.setDouble(++index, targetProduct.getCurrentPrice());
statement.setString(++index, dto.getLastUpdate());
statement.executeUpdate();
} catch (SQLException e) {
throw new PersistenceException(
"Something went wrong while saving offer to SQLite", e);
}
}
/**
* Gets an OfferDTO by identifier.
*
* @param offerId
* @return OfferDTO
*/
public OfferDTO getOfferDTOById(final String offerId) {
String query = "SELECT * FROM offers WHERE offerId = ?";
OfferDTO dto = null;
try (Connection connection = db.getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
statement.setString(1, offerId);
try (ResultSet resultSet = statement.executeQuery()) {
if (resultSet.next()) {
dto = new OfferDTO();
ProductDTO srcProduct = getProductDTOById(resultSet.getString("sourceId"));
ProductDTO targetProduct = getProductDTOById(resultSet.getString("targetId"));
dto.setOfferId(resultSet.getString(OFFERID_COL_NAME));
dto.setSourceProduct(srcProduct);
dto.setTargetProduct(targetProduct);
dto.setLastUpdate(resultSet.getString(LAST_UPDATE_COL_NAME));
}
}
} catch (SQLException e) {
throw new PersistenceException(
"Something went wrong while reading offer from SQLite", e);
}
return dto;
}
@Override
public void writeTX(final TransactionDTO dto) {
String query = "INSERT INTO transactions ("
+ "offerId, volume, earnings, transactionTime"
+ ") VALUES ("
+ "?, ?, ?, ?"
+ ")";
try (Connection connection = db.getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
int index = 0;
statement.setString(++index, dto.getOfferId());
statement.setDouble(++index, dto.getVolume());
statement.setDouble(++index, dto.getEarnings());
statement.setString(++index, dto.getTransactionTime());
statement.executeUpdate();
} catch (SQLException e) {
throw new PersistenceException(
"Something went wrong while saving transaction to SQLite", e);
}
}
@Override
public void outputTransactionsToLog() {
String query = "SELECT * FROM transactions t "
+ "LEFT JOIN offers o ON t.offerId = o.offerId "
+ "ORDER BY transactionTime ASC";
StringBuilder sb = new StringBuilder();
int index = 0;
sb.append("Transaction History:");
try (Connection connection = db.getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
sb.append("\n");
sb.append(String.format("%3d", ++index));
sb.append(String.format(" - %s - ", resultSet.getString("transactionTime")));
sb.append(String.format("+%s (%s) - ",
FormattingUtil.formatEuro(resultSet.getDouble("earnings")),
FormattingUtil.formatEuro(resultSet.getDouble("volume"))));
sb.append(String.format("[%s to %s] %s",
resultSet.getString("sourceWebshop"),
resultSet.getString("targetWebshop"),
resultSet.getString(OFFERID_COL_NAME)));
}
}
} catch (SQLException e) {
throw new PersistenceException(
"Something went wrong while reading transaction from SQLite", e);
}
String logString = sb.toString();
LOGGER.info(logString);
}
@Override
public void outputSummaryToLog() {
String query = "SELECT SUM(earnings) AS earnings, SUM(volume) AS volume FROM transactions;";
StringBuilder sb = new StringBuilder();
sb.append("Summary:\n");
try (Connection connection = db.getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
try (ResultSet resultSet = statement.executeQuery()) {
if (resultSet.next()) {
double earnings = resultSet.getDouble("earnings");
double volume = resultSet.getDouble("volume");
String avgMargin = String.valueOf(
Math.round(earnings / volume * PERCENT)) + "%";
String earningsFormatted = FormattingUtil.formatEuro(earnings);
String volumeFormatted = FormattingUtil.formatEuro(volume);
sb.append(String.format("Total earnings: %s, ", earningsFormatted));
sb.append(String.format("total volume: %s ", volumeFormatted));
sb.append(String.format("(average margin %s)", avgMargin));
}
}
} catch (SQLException e) {
throw new PersistenceException(
"Something went wrong while reading transaction summary from SQLite", e);
}
String logString = sb.toString();
LOGGER.info(logString);
}
@Override
public void clearTXData() {
runDeletionQuery("DELETE FROM transactions;");
}
@Override
public void clearOfferData() {
runDeletionQuery("DELETE FROM offers;");
}
@Override
public void clearProductData() {
runDeletionQuery("DELETE FROM products;");
}
/**
* Flushes all data using the specified query.
*
* @param query
*/
private void runDeletionQuery(final String query) {
try (Connection connection = db.getConnection(); try (Connection connection = db.getConnection();
Statement statement = connection.createStatement()) { Statement statement = connection.createStatement()) {
String query = "DELETE FROM products";
statement.executeUpdate(query); statement.executeUpdate(query);
} catch (SQLException e) { } catch (SQLException e) {
throw new PersistenceException("Something went wrong while clearing the database", e); throw new PersistenceException("Something went wrong while clearing the database", e);
@@ -149,25 +400,46 @@ public final class SQLiteConnector extends AbstractProductPersistence {
} }
@Override @Override
protected String getDataOrigin() { public void deleteOfferById(final String offerId) {
return DATA_ORIGIN; String query = "DELETE FROM offers WHERE offerID = ?;";
try (Connection connection = db.getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
statement.setString(1, offerId);
statement.executeUpdate();
} catch (SQLException e) {
throw new PersistenceException(
"Something went wrong while deleting offer from SQLite", e);
}
} }
@Override @Override
protected String getApiKey() { public List<OfferDTO> getOffers() {
throw new UnsupportedOperationException( List<OfferDTO> list = new ArrayList<>();
this.getClass().getName() + " doesn't support getApiKey"); String query = "SELECT * FROM offers";
try (Connection connection = db.getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
OfferDTO dto = new OfferDTO();
ProductDTO srcProduct = getProductDTOById(resultSet.getString("sourceId"));
ProductDTO targetProduct = getProductDTOById(resultSet.getString("targetId"));
dto.setOfferId(resultSet.getString(OFFERID_COL_NAME));
dto.setSourceProduct(srcProduct);
dto.setTargetProduct(targetProduct);
dto.setLastUpdate(resultSet.getString(LAST_UPDATE_COL_NAME));
list.add(dto);
}
}
} catch (SQLException e) {
throw new PersistenceException(
"Something went wrong while reading offers from SQLite", e);
} }
@Override return list;
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");
} }
} }

View File

@@ -0,0 +1,32 @@
package de.rwu.easydrop.data.connector;
import de.rwu.easydrop.api.dto.TransactionDTO;
/**
* Persistence interface for transactions.
*
* @since 0.3.0
*/
public interface TransactionPersistenceInterface {
/**
* Writes a ProductDTO to persistence.
*
* @param dto
*/
void writeTX(TransactionDTO dto);
/**
* Writes transactions to log.
*/
void outputTransactionsToLog();
/**
* Writes transaction summary (total volume and earnings) to log.
*/
void outputSummaryToLog();
/**
* Deletes all transaction data from persistence.
*/
void clearTXData();
}

View File

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

View File

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

View File

@@ -0,0 +1,31 @@
package de.rwu.easydrop.model;
import lombok.Data;
/**
* An Offer.
*
* @since 0.3.0
*/
@Data
public class Offer {
/**
* ID of the offer, built from identifiers of the source platforms.
*/
private String offerId;
/**
* The product that our software buys.
*/
private Product sourceProduct;
/**
* The product that our software sells.
*/
private Product targetProduct;
/**
* Date of last update of the offer.
*/
private String lastUpdate;
}

View File

@@ -13,7 +13,7 @@ public class Product {
/** /**
* Data source platform, like "Amazon". * Data source platform, like "Amazon".
*/ */
private String dataOrigin; private Webshop dataOrigin;
/** /**
* Platform internal product identifier. * Platform internal product identifier.
@@ -40,6 +40,11 @@ public class Product {
*/ */
private boolean available; private boolean available;
/**
* Last update from API.
*/
private String lastUpdate;
@Override @Override
public final String toString() { public final String toString() {
return "Product: [" return "Product: ["

View File

@@ -68,11 +68,11 @@ public class ProductCatalogue {
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Product Catalogue: ").append(productName).append("\n"); sb.append(String.format("Catalogue Name: %s%n", productName));
sb.append("Description: ").append(description).append("\n"); sb.append(String.format("Description: %s%n", description));
sb.append("Products:\n"); sb.append("Products:\n");
for (Product product : products) { for (Product product : products) {
sb.append(product.toString()).append("\n"); sb.append(String.format("%s%n", product.toString()));
} }
return sb.toString(); return sb.toString();
} }

View File

@@ -0,0 +1,30 @@
package de.rwu.easydrop.model;
import lombok.Data;
/**
* A transaction.
*
* @since 0.3.0
*/
@Data
public class Transaction {
/**
* Offer ID.
*
* @see Offer
*/
private String offerId;
/**
* Sales volume.
*/
private double volume;
/**
* Earnings (volume - cost).
*/
private double earnings;
/**
* Transaction timestamp.
*/
private String transactionTime;
}

View File

@@ -0,0 +1,31 @@
package de.rwu.easydrop.model;
/**
* Constants for data source/destination platforms.
*/
public enum Webshop {
/**
* Amazon Product API.
*/
AMAZON,
/**
* eBay Item API.
*/
EBAY;
/**
* Attempts to derive a webshop value from a string.
*
* @param str String
* @return webshop
* @throws IllegalArgumentException
*/
public static Webshop fromString(final String str) {
for (Webshop shop : Webshop.values()) {
if (shop.toString().equalsIgnoreCase(str)) {
return shop;
}
}
throw new IllegalArgumentException(String.format("No webshop called %s found", str));
}
}

View File

@@ -0,0 +1,56 @@
package de.rwu.easydrop.service.mapping;
import de.rwu.easydrop.api.dto.OfferDTO;
import de.rwu.easydrop.model.Offer;
/**
* Maps between Offer, OfferDTO and OfferDTO.
*
* @since 0.2.0
*
* @see Offer
* @see OfferDTO
*/
public final class OfferMapper {
/**
* Private constructor to prevent unwanted instantiation.
*
* @throws UnsupportedOperationException always
*/
private OfferMapper() throws UnsupportedOperationException {
throw new UnsupportedOperationException("This is a mapping class, don't instantiate it.");
}
/**
* Creates a Offer object from a corresponding DTO.
*
* @param dto Offer Data Transfer Object
* @return Offer
*/
public static Offer mapOfferFromDTO(final OfferDTO dto) {
Offer offer = new Offer();
offer.setOfferId(dto.getOfferId());
offer.setSourceProduct(ProductMapper.mapProductFromDTO(dto.getSourceProduct()));
offer.setTargetProduct(ProductMapper.mapProductFromDTO(dto.getTargetProduct()));
offer.setLastUpdate(dto.getLastUpdate());
return offer;
}
/**
* Creates an OfferDTO object from a corresponding offer.
*
* @param offer
* @return OfferDTO
*/
public static OfferDTO mapOfferToDTO(final Offer offer) {
OfferDTO dto = new OfferDTO();
dto.setOfferId(offer.getOfferId());
dto.setSourceProduct(ProductMapper.mapProductToDTO(offer.getSourceProduct()));
dto.setTargetProduct(ProductMapper.mapProductToDTO(offer.getTargetProduct()));
dto.setLastUpdate(offer.getLastUpdate());
return dto;
}
}

View File

@@ -39,6 +39,7 @@ public final class ProductMapper {
product.setDeliveryPrice(dto.getDeliveryPrice()); product.setDeliveryPrice(dto.getDeliveryPrice());
product.setMerchant(dto.getMerchant()); product.setMerchant(dto.getMerchant());
product.setProductId(dto.getProductId()); product.setProductId(dto.getProductId());
product.setLastUpdate(dto.getLastUpdate());
return product; return product;
} }
@@ -56,6 +57,7 @@ public final class ProductMapper {
dto.setCurrentPrice(product.getCurrentPrice()); dto.setCurrentPrice(product.getCurrentPrice());
dto.setDeliveryPrice(product.getDeliveryPrice()); dto.setDeliveryPrice(product.getDeliveryPrice());
dto.setMerchant(product.getMerchant()); dto.setMerchant(product.getMerchant());
dto.setLastUpdate(product.getLastUpdate());
return dto; return dto;
} }

View File

@@ -0,0 +1,54 @@
package de.rwu.easydrop.service.mapping;
import de.rwu.easydrop.api.dto.TransactionDTO;
import de.rwu.easydrop.model.Transaction;
/**
* Maps transaction DTOs and objects.
*
* @since 0.3.0
*/
public final class TransactionMapper {
/**
* Private constructor to prevent unwanted instantiation.
*
* @throws UnsupportedOperationException always
*/
private TransactionMapper() throws UnsupportedOperationException {
throw new UnsupportedOperationException("This is a mapping class, don't instantiate it.");
}
/**
* Creates a Transaction object from a corresponding DTO.
*
* @param dto Transaction Data Transfer Object
* @return Product
*/
public static Transaction mapTXFromDTO(final TransactionDTO dto) {
Transaction tx = new Transaction();
tx.setOfferId(dto.getOfferId());
tx.setVolume(dto.getVolume());
tx.setEarnings(dto.getEarnings());
tx.setTransactionTime(dto.getTransactionTime());
return tx;
}
/**
* Creates a ProductDTO object from a corresponding Product.
*
* @param tx Transaction
* @return TransactionDTO
*/
public static TransactionDTO mapTXToDTO(final Transaction tx) {
TransactionDTO dto = new TransactionDTO();
dto.setOfferId(tx.getOfferId());
dto.setVolume(tx.getVolume());
dto.setEarnings(tx.getEarnings());
dto.setTransactionTime(tx.getTransactionTime());
return dto;
}
}

View File

@@ -1,58 +1,62 @@
package de.rwu.easydrop.service.processing; package de.rwu.easydrop.service.processing;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import de.rwu.easydrop.exception.InvalidCatalogueException; import de.rwu.easydrop.exception.InvalidCatalogueException;
import de.rwu.easydrop.exception.InvalidOfferException;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.Product; import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue; import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.model.ProductPair; import de.rwu.easydrop.model.ProductPair;
import de.rwu.easydrop.util.FormattingUtil; import de.rwu.easydrop.util.FormattingUtil;
import de.rwu.easydrop.util.Timestamp;
public class OfferIdentifier {
/**
* Logging instance.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(OfferIdentifier.class);
/** /**
* Creates dropshipping orders based on price margin. * runIdentifier calls the price function that decides
* if it is feasible to dropship products.
* *
* @since 0.3.0 * @param pCats Product catalogues
* @return Identified offers
*/ */
public final class OrderManager { public List<Offer> runIdentifier(final List<ProductCatalogue> pCats) {
List<Offer> identifiedOffers = new ArrayList<>();
/**
* Temporary logging instance.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(OrderManager.class);
/**
* Private constructor to prevent unwanted instantiation.
*
* @throws UnsupportedOperationException always
*/
private OrderManager() throws UnsupportedOperationException {
throw new UnsupportedOperationException("This is a stateless class, don't instantiate it.");
}
/**
* Creates orders for products with sufficient margin.
*
* @param pCats Product Catalogues
*/
public static void createOrders(final List<ProductCatalogue> pCats) {
for (ProductCatalogue pCat : pCats) { for (ProductCatalogue pCat : pCats) {
ProductPair pair = getHighestMarginProducts(pCat);
// #12: Create actual orders/transactions, remove logger // Call price finder for all catalogue
try {
ProductPair pair = getHighestMarginProducts(pCat);
Offer possibleOffer = new Offer();
possibleOffer.setLastUpdate(Timestamp.now());
possibleOffer.setSourceProduct(pair.getProduct1());
possibleOffer.setTargetProduct(pair.getProduct2());
identifiedOffers.add(possibleOffer);
double margin = pair.getProduct2().getCurrentPrice() double margin = pair.getProduct2().getCurrentPrice()
- pair.getProduct1().getCurrentPrice(); - pair.getProduct1().getCurrentPrice();
String marginFormatted = FormattingUtil.formatEuro(margin); String marginFormatted = FormattingUtil.formatEuro(margin);
LOGGER.info("{}: Margin {} ({} to {})", LOGGER.info("\n Identified Offer: {} ({} to {}) with margin {} ",
pCat.getProductName(), pCat.getProductName(),
marginFormatted,
pair.getProduct1().getDataOrigin(), pair.getProduct1().getDataOrigin(),
pair.getProduct2().getDataOrigin()); pair.getProduct2().getDataOrigin(),
marginFormatted);
} catch (InvalidOfferException e) {
// Don't include offer if deemed invalid
} }
} }
return identifiedOffers;
}
/** /**
* Returns the cheapest and most expensive product of a catalogue to guarantee * Returns the cheapest and most expensive product of a catalogue to guarantee
* the biggest margin as a pair. * the biggest margin as a pair.
@@ -79,7 +83,7 @@ public final class OrderManager {
} }
if (cheapestProduct.getCurrentPrice() == mostExpensiveProduct.getCurrentPrice()) { if (cheapestProduct.getCurrentPrice() == mostExpensiveProduct.getCurrentPrice()) {
throw new InvalidCatalogueException("Price margin is zero!"); throw new InvalidOfferException("Price margin is zero!");
} }
return new ProductPair(cheapestProduct, mostExpensiveProduct); return new ProductPair(cheapestProduct, mostExpensiveProduct);

View File

@@ -0,0 +1,115 @@
package de.rwu.easydrop.service.processing;
import java.util.List;
import de.rwu.easydrop.api.client.AmazonPurchaser;
import de.rwu.easydrop.api.client.AmazonSeller;
import de.rwu.easydrop.api.client.EbayPurchaser;
import de.rwu.easydrop.api.client.EbaySeller;
import de.rwu.easydrop.api.client.PurchaserFactory;
import de.rwu.easydrop.api.client.SellerFactory;
import de.rwu.easydrop.data.connector.OfferPersistenceInterface;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.service.mapping.ProductMapper;
import de.rwu.easydrop.service.writer.OfferWriter;
import de.rwu.easydrop.util.FormattingUtil;
import lombok.Data;
/**
* Offer provisioner.
*
* @since 0.3.0
*/
@Data
public class OfferProvisioner {
/**
* Writes offers into persistence.
*/
private OfferWriter offerWriter;
/**
* Is the API for selling products on Amazon.
*/
private AmazonSeller amazonSeller;
/**
* Is the API for selling products on Ebay.
*/
private EbaySeller ebaySeller;
/**
* Is the API for buying products on Amazon.
*/
private AmazonPurchaser amazonPurchaser;
/**
* Is the API for buying products on Ebay.
*/
private EbayPurchaser ebayPurchaser;
/**
* Puts up the sell order.
*
* @param offer
*/
private void toSeller(final Offer offer) throws IllegalArgumentException {
switch (offer.getTargetProduct().getDataOrigin()) {
case EBAY -> this.ebaySeller.sellProduct(
ProductMapper.mapProductToDTO(offer.getTargetProduct()));
case AMAZON -> this.amazonSeller.sellProduct(
ProductMapper.mapProductToDTO(offer.getTargetProduct()));
default -> throw new IllegalArgumentException("Unsupported source plattform");
}
}
/**
* Puts up the buy order.
*
* @param offer
*/
private void toBuyer(final Offer offer) throws IllegalArgumentException {
switch (offer.getSourceProduct().getDataOrigin()) {
case EBAY -> ebayPurchaser.purchaseProduct(
ProductMapper.mapProductToDTO(offer.getSourceProduct()));
case AMAZON -> amazonPurchaser.purchaseProduct(
ProductMapper.mapProductToDTO(offer.getSourceProduct()));
default -> throw new IllegalArgumentException("Unsupported target plattform");
}
}
/**
* Is the class for placing orders on a target platform.
*
* @param db Persistence Interface
* @param newSellerFactory Seller Factory
* @param newPurchaserFactory Purchaser Factory
*/
public OfferProvisioner(
final OfferPersistenceInterface db,
final SellerFactory newSellerFactory,
final PurchaserFactory newPurchaserFactory) {
this.offerWriter = new OfferWriter(db);
this.ebaySeller = newSellerFactory.createEbaySeller();
this.amazonSeller = newSellerFactory.createAmazonSeller();
this.ebayPurchaser = newPurchaserFactory.createEbayPurchaser();
this.amazonPurchaser = newPurchaserFactory.createAmazonPurchaser();
}
/**
* Method for placing orders on a target platform.
*
* @param offersToProvision
*/
public final void runProvisioner(final List<Offer> offersToProvision) {
for (Offer newOffer : offersToProvision) {
String newOfferId = FormattingUtil.removeSpaces(
newOffer.getSourceProduct().getDataOrigin().toString()
+ newOffer.getTargetProduct().getDataOrigin().toString()
+ "_"
+ newOffer.getSourceProduct().getProductId()
+ newOffer.getTargetProduct().getProductId());
this.toSeller(newOffer);
this.toBuyer(newOffer);
newOffer.setOfferId(newOfferId);
offerWriter.writeOfferToPersistence(newOffer);
}
}
}

View File

@@ -0,0 +1,47 @@
package de.rwu.easydrop.service.processing;
import de.rwu.easydrop.api.dto.TransactionDTO;
import de.rwu.easydrop.data.connector.TransactionPersistenceInterface;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.Transaction;
import de.rwu.easydrop.service.mapping.TransactionMapper;
import de.rwu.easydrop.util.Timestamp;
/**
* Handles transactions.
*
* @since 0.3.0
*/
public class TransactionHandler {
/**
* Transaction persistence.
*/
private TransactionPersistenceInterface txPersistence;
/**
* Creates new transaction handler.
*
* @param txdb transaction persistence
*/
public TransactionHandler(final TransactionPersistenceInterface txdb) {
this.txPersistence = txdb;
}
/**
* Creates transaction from offer.
*
* @param offer Offer
*/
public void turnOfferToTransaction(final Offer offer) {
Transaction tx = new Transaction();
tx.setOfferId(offer.getOfferId());
tx.setVolume(offer.getTargetProduct().getCurrentPrice());
tx.setEarnings(offer.getTargetProduct().getCurrentPrice()
- offer.getSourceProduct().getCurrentPrice());
tx.setTransactionTime(Timestamp.now());
// Write transaction to persistence
TransactionDTO txDTO = TransactionMapper.mapTXToDTO(tx);
txPersistence.writeTX(txDTO);
}
}

View File

@@ -5,7 +5,9 @@ import java.util.List;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import de.rwu.easydrop.exception.InvalidProductException; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.rwu.easydrop.model.Product; import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue; import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.util.ProductsConfig; import de.rwu.easydrop.util.ProductsConfig;
@@ -18,6 +20,11 @@ import lombok.Data;
*/ */
@Data @Data
public class CatalogueRetriever { public class CatalogueRetriever {
/**
* Logging instance.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(CatalogueRetriever.class);
/** /**
* User-configured products. * User-configured products.
*/ */
@@ -57,22 +64,16 @@ public class CatalogueRetriever {
pCat.getProductName(), pCat.getDescription()); pCat.getProductName(), pCat.getDescription());
for (Product product : pCat.getProducts()) { for (Product product : pCat.getProducts()) {
Product newProduct = new Product(); Product newProduct = productRetriever.getProductFromWebshop(product.getDataOrigin(),
newProduct.setDataOrigin(product.getDataOrigin()); product.getProductId());
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); newProductCatalogue.addProduct(newProduct);
} }
productCatalogues.add(newProductCatalogue); productCatalogues.add(newProductCatalogue);
String catString = String.format(
"%nLoaded Catalogue: %n%s", newProductCatalogue.toString());
LOGGER.info(catString);
} }
} }
} }

View File

@@ -0,0 +1,40 @@
package de.rwu.easydrop.service.retriever;
import de.rwu.easydrop.api.dto.OfferDTO;
import de.rwu.easydrop.data.connector.OfferPersistenceInterface;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.service.mapping.OfferMapper;
/**
* Retrieves offer information from different sources.
*
* @since 0.3.0
*/
public class OfferRetriever {
/**
* Persistence interface.
*/
private OfferPersistenceInterface persistence;
/**
* Creates an Offer Retriever.
*
* @param db Persistence Interface
*/
public OfferRetriever(final OfferPersistenceInterface db) {
this.persistence = db;
}
/**
* Retrieves an offer from persistence.
*
* @param offerId
* @return Offer from persistence
*/
public Offer getOfferFromPersistence(final String offerId) {
OfferPersistenceInterface src = persistence;
OfferDTO dto = src.getOfferDTOById(offerId);
return OfferMapper.mapOfferFromDTO(dto);
}
}

View File

@@ -4,8 +4,9 @@ import de.rwu.easydrop.api.client.AmazonProductDataSource;
import de.rwu.easydrop.api.client.DataSourceFactory; import de.rwu.easydrop.api.client.DataSourceFactory;
import de.rwu.easydrop.api.client.EbayItemDataSource; import de.rwu.easydrop.api.client.EbayItemDataSource;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.data.connector.AbstractProductPersistence; import de.rwu.easydrop.data.connector.ProductPersistenceInterface;
import de.rwu.easydrop.model.Product; import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.service.mapping.ProductMapper; import de.rwu.easydrop.service.mapping.ProductMapper;
import de.rwu.easydrop.service.validation.ProductValidator; import de.rwu.easydrop.service.validation.ProductValidator;
@@ -19,19 +20,20 @@ public class ProductRetriever {
* Data source factory. * Data source factory.
*/ */
private DataSourceFactory dataSourceFactory; private DataSourceFactory dataSourceFactory;
/** /**
* @param newDataSourceFactory the dataSourceFactory to set * Persistence interface.
*/ */
public void setDataSourceFactory(final DataSourceFactory newDataSourceFactory) { private ProductPersistenceInterface persistence;
this.dataSourceFactory = newDataSourceFactory;
}
/** /**
* @param newDataSourceFactory * @param newDataSourceFactory
* @param newPersistence
*/ */
public ProductRetriever(final DataSourceFactory newDataSourceFactory) { public ProductRetriever(
this.setDataSourceFactory(newDataSourceFactory); final DataSourceFactory newDataSourceFactory,
final ProductPersistenceInterface newPersistence) {
this.dataSourceFactory = newDataSourceFactory;
this.persistence = newPersistence;
} }
/** /**
@@ -73,12 +75,27 @@ public class ProductRetriever {
* @return Product from persistence * @return Product from persistence
*/ */
public Product getProductFromPersistence(final String productId) { public Product getProductFromPersistence(final String productId) {
AbstractProductPersistence src = dataSourceFactory.createProductPersistenceDataSource(); ProductDTO dto = persistence.getProductDTOById(productId);
ProductDTO dto = src.getProductDTOById(productId);
Product product = ProductMapper.mapProductFromDTO(dto); Product product = ProductMapper.mapProductFromDTO(dto);
ProductValidator.validate(product); ProductValidator.validate(product);
return product; return product;
} }
/**
* Retrieves a product from an API by name of the API so that high-level
* components do not need to be extended
* with exact function call names if we extend the list of webshops.
*
* @param shop Data source name, e.g. Amazon
* @param productIdentifier Product name, translated to the correct product ID
* for the data source
* @return Product from that data source or null if data source not available
*/
public Product getProductFromWebshop(final Webshop shop, final String productIdentifier) {
return switch (shop) {
case AMAZON -> getProductFromAmazon(productIdentifier);
case EBAY -> getProductFromEbay(productIdentifier);
};
}
} }

View File

@@ -0,0 +1,38 @@
package de.rwu.easydrop.service.validation;
import de.rwu.easydrop.exception.InvalidOfferException;
import de.rwu.easydrop.model.Offer;
/**
* Confirms validity of Offer data.
*
* @since 0.2.0
*/
public final class OfferValidator {
/**
* Private constructor to prevent unwanted instantiation.
*
* @throws UnsupportedOperationException always
*/
private OfferValidator() throws UnsupportedOperationException {
throw new UnsupportedOperationException("This is a validator class, don't instantiate it.");
}
/**
* Makes sure an Offer does not contain invalid information.
*
* @param offer the Offer
*/
public static void validate(final Offer offer) {
try {
if (offer.getOfferId().equals("")) {
throw new InvalidOfferException("Offer ID cannot be empty");
}
if (offer.getLastUpdate().equals("")) {
throw new InvalidOfferException("LastUpdate cannot be empty");
}
} catch (NullPointerException e) {
throw new InvalidOfferException("Required information is missing in the offer", e);
}
}
}

View File

@@ -1,8 +1,5 @@
package de.rwu.easydrop.service.validation; package de.rwu.easydrop.service.validation;
import java.util.HashSet;
import java.util.Set;
import de.rwu.easydrop.exception.InvalidProductException; import de.rwu.easydrop.exception.InvalidProductException;
import de.rwu.easydrop.model.Product; import de.rwu.easydrop.model.Product;
@@ -31,26 +28,8 @@ public final class ProductValidator {
if (product.getCurrentPrice() == 0.00) { if (product.getCurrentPrice() == 0.00) {
throw new InvalidProductException("Current price cannot be 0.00"); throw new InvalidProductException("Current price cannot be 0.00");
} }
if (!isInValidDataOrigins(product.getDataOrigin())) {
throw new InvalidProductException("Unknown data source");
}
if (product.getProductId().equals("")) { if (product.getProductId().equals("")) {
throw new InvalidProductException("Product ID cannot be empty"); throw new InvalidProductException("Product ID cannot be empty");
} }
} }
/**
* Checks whether a dataOrigin is within the set of valid ones.
*
* @param dataOrigin like "Amazon"
* @return true if valid
*/
public static boolean isInValidDataOrigins(final String dataOrigin) {
Set<String> validOrigins = new HashSet<>();
validOrigins.add("Amazon");
validOrigins.add("eBay");
return validOrigins.contains(dataOrigin);
}
} }

View File

@@ -0,0 +1,42 @@
package de.rwu.easydrop.service.validation;
import de.rwu.easydrop.exception.InvalidTransactionException;
import de.rwu.easydrop.model.Transaction;
/**
* Validates transactions.
*
* @since 0.3.0
*/
public final class TransactionValidator {
/**
* Private constructor to prevent unwanted instantiation.
*
* @throws UnsupportedOperationException always
*/
private TransactionValidator() throws UnsupportedOperationException {
throw new UnsupportedOperationException("This is a validator class, don't instantiate it.");
}
/**
* Makes sure a transaction does not contain invalid information.
*
* @param tx the Transaction
*/
public static void validate(final Transaction tx) {
try {
if (tx.getOfferId().equals("")) {
throw new InvalidTransactionException("Offer ID cannot be empty");
}
if (tx.getEarnings() == 0) {
throw new InvalidTransactionException("Earnings can't be zero");
}
if (tx.getVolume() == 0) {
throw new InvalidTransactionException("Volume can't be zero");
}
} catch (NullPointerException e) {
throw new InvalidTransactionException(
"Required information is missing in the transaction", e);
}
}
}

View File

@@ -3,7 +3,7 @@ package de.rwu.easydrop.service.writer;
import java.util.List; import java.util.List;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.data.connector.AbstractProductPersistence; import de.rwu.easydrop.data.connector.ProductPersistenceInterface;
import de.rwu.easydrop.model.Product; import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue; import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.service.mapping.ProductMapper; import de.rwu.easydrop.service.mapping.ProductMapper;
@@ -18,14 +18,14 @@ public final class CatalogueWriter {
/** /**
* Holds a persistence reference. * Holds a persistence reference.
*/ */
private AbstractProductPersistence persistence; private ProductPersistenceInterface persistence;
/** /**
* Creates new instance. * Creates new instance.
* *
* @param newPersistence * @param newPersistence
*/ */
public CatalogueWriter(final AbstractProductPersistence newPersistence) { public CatalogueWriter(final ProductPersistenceInterface newPersistence) {
persistence = newPersistence; persistence = newPersistence;
} }
@@ -40,7 +40,7 @@ public final class CatalogueWriter {
ProductValidator.validate(product); ProductValidator.validate(product);
ProductDTO dto = ProductMapper.mapProductToDTO(product); ProductDTO dto = ProductMapper.mapProductToDTO(product);
persistence.saveProduct(dto); persistence.writeProduct(dto);
} }
} }
} }

View File

@@ -0,0 +1,31 @@
package de.rwu.easydrop.service.writer;
import de.rwu.easydrop.api.dto.OfferDTO;
import de.rwu.easydrop.data.connector.OfferPersistenceInterface;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.service.mapping.OfferMapper;
public class OfferWriter {
/**
* Persistence.
*/
private OfferPersistenceInterface persistence;
/**
* @param newPersistence the persistence to set
*/
public OfferWriter(final OfferPersistenceInterface newPersistence) {
this.persistence = newPersistence;
}
/**
* Validates and saves product to persistence.
*
* @param offer
*/
public void writeOfferToPersistence(final Offer offer) {
OfferDTO dto = OfferMapper.mapOfferToDTO(offer);
persistence.writeOffer(dto);
}
}

View File

@@ -1,7 +1,7 @@
package de.rwu.easydrop.service.writer; package de.rwu.easydrop.service.writer;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.data.connector.AbstractProductPersistence; import de.rwu.easydrop.data.connector.ProductPersistenceInterface;
import de.rwu.easydrop.model.Product; import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.service.mapping.ProductMapper; import de.rwu.easydrop.service.mapping.ProductMapper;
import de.rwu.easydrop.service.validation.ProductValidator; import de.rwu.easydrop.service.validation.ProductValidator;
@@ -15,12 +15,12 @@ public class ProductWriter {
/** /**
* Persistence. * Persistence.
*/ */
private AbstractProductPersistence persistence; private ProductPersistenceInterface persistence;
/** /**
* @param newPersistence the persistence to set * @param newPersistence the persistence to set
*/ */
public void setPersistence(final AbstractProductPersistence newPersistence) { public ProductWriter(final ProductPersistenceInterface newPersistence) {
this.persistence = newPersistence; this.persistence = newPersistence;
} }
@@ -33,6 +33,6 @@ public class ProductWriter {
ProductValidator.validate(product); ProductValidator.validate(product);
ProductDTO dto = ProductMapper.mapProductToDTO(product); ProductDTO dto = ProductMapper.mapProductToDTO(product);
persistence.saveProduct(dto); persistence.writeProduct(dto);
} }
} }

View File

@@ -0,0 +1,33 @@
package de.rwu.easydrop.service.writer;
import de.rwu.easydrop.api.dto.TransactionDTO;
import de.rwu.easydrop.data.connector.TransactionPersistenceInterface;
import de.rwu.easydrop.model.Transaction;
import de.rwu.easydrop.service.mapping.TransactionMapper;
import de.rwu.easydrop.service.validation.TransactionValidator;
public class TransactionWriter {
/**
* Persistence.
*/
private TransactionPersistenceInterface persistence;
/**
* @param newPersistence the persistence to set
*/
public TransactionWriter(final TransactionPersistenceInterface newPersistence) {
this.persistence = newPersistence;
}
/**
* Validates and saves a transaction to persistence.
*
* @param tx Transaction
*/
public void writeTXToPersistence(final Transaction tx) {
TransactionDTO dto = TransactionMapper.mapTXToDTO(tx);
TransactionValidator.validate(tx);
persistence.writeTX(dto);
}
}

View File

@@ -37,4 +37,14 @@ public final class FormattingUtil {
public static String urlEncode(final String str) { public static String urlEncode(final String str) {
return str.replace(" ", "+"); return str.replace(" ", "+");
} }
/**
* Removes spaces from target string.
*
* @param str String
* @return Space-less string
*/
public static String removeSpaces(final String str) {
return str.replace(" ", "");
}
} }

View File

@@ -13,6 +13,7 @@ import com.jayway.jsonpath.JsonPathException;
import de.rwu.easydrop.model.Product; import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue; import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.model.Webshop;
/** /**
* Reads the user-specified catalogue of products. * Reads the user-specified catalogue of products.
@@ -122,8 +123,8 @@ public final class ProductsConfig {
ArrayList<HashMap<String, Object>> identifiers = JsonPath.read(jsonIdentifiers, "$"); ArrayList<HashMap<String, Object>> identifiers = JsonPath.read(jsonIdentifiers, "$");
for (HashMap<String, Object> product : identifiers) { for (HashMap<String, Object> product : identifiers) {
String dataOrigin = product.keySet().iterator().next(); Webshop dataOrigin = Webshop.fromString(product.keySet().iterator().next());
String identifier = product.get(dataOrigin).toString(); String identifier = product.get(dataOrigin.toString()).toString();
Product newProduct = new Product(); Product newProduct = new Product();
newProduct.setDataOrigin(dataOrigin); newProduct.setDataOrigin(dataOrigin);

View File

@@ -0,0 +1,29 @@
package de.rwu.easydrop.util;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* Serves as a timestamp source.
*
* @since 0.3.0
*/
public final class Timestamp {
/**
* Hidden constructor.
*/
private Timestamp() {
// Don't instantiate me!
}
/**
* Returns a formatted time string.
*
* @return String such as "2023-01-01 00:00:00"
*/
public static String now() {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return now.format(formatter);
}
}

View File

@@ -21,6 +21,7 @@ import org.mockito.MockitoAnnotations;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.exception.DataWriterException; import de.rwu.easydrop.exception.DataWriterException;
import de.rwu.easydrop.model.Webshop;
class AbstractDataWriterTest { class AbstractDataWriterTest {
private static String demoProductId = "whateverId"; private static String demoProductId = "whateverId";
@@ -42,8 +43,8 @@ class AbstractDataWriterTest {
} }
@Override @Override
protected String getDataTarget() { protected Webshop getDataTarget() {
return "Test"; return Webshop.AMAZON;
} }
@Override @Override
@@ -53,21 +54,10 @@ class AbstractDataWriterTest {
}; };
} }
@Test
void sendPutRequest_wrongDataSource_throwsException() {
ProductDTO dto = new ProductDTO(demoProductId, "Amazon");
DataWriterException e = assertThrows(DataWriterException.class, () -> {
writer.sendPutRequest(dto, "testApiType");
});
assertEquals("Product data source and target testApiType API are incompatible.", e.getMessage());
}
@Test @Test
void sendPutRequest_badResponseCode_throwsException() throws IOException { void sendPutRequest_badResponseCode_throwsException() throws IOException {
// Set up DTO // Set up DTO
ProductDTO dto = new ProductDTO(demoProductId, "Test"); ProductDTO dto = new ProductDTO(demoProductId, Webshop.AMAZON);
// Set up Mocks // Set up Mocks
AbstractDataWriter mockWriter = mock(AbstractDataWriter.class); AbstractDataWriter mockWriter = mock(AbstractDataWriter.class);
@@ -79,13 +69,13 @@ class AbstractDataWriterTest {
writer.sendPutRequest(dto, "Sales"); writer.sendPutRequest(dto, "Sales");
}); });
assertEquals("Test Sales API responded with error code 400", e.getMessage()); assertEquals("AMAZON Sales API responded with error code 400", e.getMessage());
} }
@Test @Test
void sendPutRequest_ioException_throwsException() throws IOException { void sendPutRequest_ioException_throwsException() throws IOException {
// Set up DTO // Set up DTO
ProductDTO dto = new ProductDTO(demoProductId, "Test"); ProductDTO dto = new ProductDTO(demoProductId, Webshop.AMAZON);
// Set up Mocks // Set up Mocks
AbstractDataWriter mockWriter = mock(AbstractDataWriter.class); AbstractDataWriter mockWriter = mock(AbstractDataWriter.class);
@@ -96,13 +86,13 @@ class AbstractDataWriterTest {
writer.sendPutRequest(dto, "testApiType"); writer.sendPutRequest(dto, "testApiType");
}); });
assertEquals("Couldn't fulfill Test API request", e.getMessage()); assertEquals("Couldn't fulfill AMAZON API request", e.getMessage());
} }
@Test @Test
void sendPutRequest_successfulRequest() throws IOException { void sendPutRequest_successfulRequest() throws IOException {
// Set up DTO // Set up DTO
ProductDTO dto = new ProductDTO(demoProductId, "test"); ProductDTO dto = new ProductDTO(demoProductId, Webshop.AMAZON);
// Set up Mocks // Set up Mocks
AbstractDataWriter mockWriter = mock(AbstractDataWriter.class); AbstractDataWriter mockWriter = mock(AbstractDataWriter.class);
@@ -112,10 +102,21 @@ class AbstractDataWriterTest {
HttpURLConnection mockConnection = mock(HttpURLConnection.class); HttpURLConnection mockConnection = mock(HttpURLConnection.class);
when(mockURL.openConnection()).thenReturn(mockConnection); when(mockURL.openConnection()).thenReturn(mockConnection);
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
when(mockWriter.getDataTarget()).thenReturn("test"); when(mockWriter.getDataTarget()).thenReturn(Webshop.AMAZON);
assertDoesNotThrow(() -> { assertDoesNotThrow(() -> {
mockWriter.sendPutRequest(dto, "Purchase"); mockWriter.sendPutRequest(dto, "Purchase");
}); });
} }
@Test
void sendPutRequest_wrongDataTarget() {
ProductDTO dto = new ProductDTO(demoProductId, Webshop.EBAY);
DataWriterException e = assertThrows(DataWriterException.class, () -> {
writer.sendPutRequest(dto, demoProductId);
});
assertEquals("Product data source and target whateverId API are incompatible.", e.getMessage());
}
} }

View File

@@ -13,6 +13,7 @@ import java.net.URL;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.model.Webshop;
class AbstractPurchaserTest { class AbstractPurchaserTest {
private AbstractPurchaser mockPurchaser = mock(AbstractPurchaser.class); private AbstractPurchaser mockPurchaser = mock(AbstractPurchaser.class);
@@ -20,7 +21,7 @@ class AbstractPurchaserTest {
@Test @Test
void purchaseProduct_CorrectApiTypeInException() throws IOException { void purchaseProduct_CorrectApiTypeInException() throws IOException {
// Set up DTO // Set up DTO
ProductDTO dto = new ProductDTO("12345", "test"); ProductDTO dto = new ProductDTO("12345", Webshop.AMAZON);
// Set up mocks // Set up mocks
URL mockURL = mock(URL.class); URL mockURL = mock(URL.class);
@@ -29,7 +30,7 @@ class AbstractPurchaserTest {
HttpURLConnection mockConnection = mock(HttpURLConnection.class); HttpURLConnection mockConnection = mock(HttpURLConnection.class);
when(mockURL.openConnection()).thenReturn(mockConnection); when(mockURL.openConnection()).thenReturn(mockConnection);
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
when(mockPurchaser.getDataTarget()).thenReturn("test"); when(mockPurchaser.getDataTarget()).thenReturn(Webshop.AMAZON);
assertDoesNotThrow(() -> { assertDoesNotThrow(() -> {
mockPurchaser.purchaseProduct(dto); mockPurchaser.purchaseProduct(dto);

View File

@@ -13,6 +13,7 @@ import java.net.URL;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.model.Webshop;
class AbstractSellerTest { class AbstractSellerTest {
private AbstractSeller mockSeller = mock(AbstractSeller.class); private AbstractSeller mockSeller = mock(AbstractSeller.class);
@@ -20,7 +21,7 @@ class AbstractSellerTest {
@Test @Test
void purchaseProduct_CorrectApiTypeInException() throws IOException { void purchaseProduct_CorrectApiTypeInException() throws IOException {
// Set up DTO // Set up DTO
ProductDTO dto = new ProductDTO("12345", "test"); ProductDTO dto = new ProductDTO("12345", Webshop.AMAZON);
// Set up mocks // Set up mocks
URL mockURL = mock(URL.class); URL mockURL = mock(URL.class);
@@ -29,7 +30,7 @@ class AbstractSellerTest {
HttpURLConnection mockConnection = mock(HttpURLConnection.class); HttpURLConnection mockConnection = mock(HttpURLConnection.class);
when(mockURL.openConnection()).thenReturn(mockConnection); when(mockURL.openConnection()).thenReturn(mockConnection);
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
when(mockSeller.getDataTarget()).thenReturn("test"); when(mockSeller.getDataTarget()).thenReturn(Webshop.AMAZON);
assertDoesNotThrow(() -> { assertDoesNotThrow(() -> {
mockSeller.sellProduct(dto); mockSeller.sellProduct(dto);

View File

@@ -21,6 +21,7 @@ import org.mockito.MockitoAnnotations;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.exception.DataSourceException; import de.rwu.easydrop.exception.DataSourceException;
import de.rwu.easydrop.model.Webshop;
class AmazonProductDataSourceTest { class AmazonProductDataSourceTest {
@@ -28,7 +29,7 @@ class AmazonProductDataSourceTest {
private static String demoApiKey = "my-api-key"; private static String demoApiKey = "my-api-key";
private static String demoApiUrl = "https://www.example.com/api"; private static String demoApiUrl = "https://www.example.com/api";
private static String demoDataOrigin = "Amazon"; private static Webshop demoDataOrigin = Webshop.AMAZON;
private static String demoProductId = "whateverId"; private static String demoProductId = "whateverId";
@BeforeEach @BeforeEach
@@ -138,7 +139,7 @@ class AmazonProductDataSourceTest {
// Verify the product DTO properties // Verify the product DTO properties
assertEquals(demoProductId, result.getProductId()); assertEquals(demoProductId, result.getProductId());
assertEquals("Amazon", result.getDataOrigin()); assertEquals(Webshop.AMAZON, result.getDataOrigin());
assertEquals(true, result.isAvailable()); assertEquals(true, result.isAvailable());
assertEquals(10.0, result.getCurrentPrice()); assertEquals(10.0, result.getCurrentPrice());
assertEquals(2.5, result.getDeliveryPrice()); assertEquals(2.5, result.getDeliveryPrice());
@@ -149,33 +150,60 @@ class AmazonProductDataSourceTest {
void testGetProductDTOById_failedRequest() throws IOException { void testGetProductDTOById_failedRequest() throws IOException {
// Set up the test environment // Set up the test environment
AmazonProductDataSource dataSource = mock(AmazonProductDataSource.class); AmazonProductDataSource DataSource = mock(AmazonProductDataSource.class);
URL mockURL = mock(URL.class); URL mockURL = mock(URL.class);
when(dataSource.getDataOrigin()).thenReturn(demoDataOrigin); when(DataSource.getDataOrigin()).thenReturn(demoDataOrigin);
when(dataSource.createApiUrl(demoProductId)).thenReturn(mockURL); when(DataSource.createApiUrl(demoProductId)).thenReturn(mockURL);
when(dataSource.getProductDTOById(demoProductId)).thenCallRealMethod(); when(DataSource.getProductDTOById(demoProductId)).thenCallRealMethod();
HttpURLConnection mockConnection = mock(HttpURLConnection.class); HttpURLConnection mockConnection = mock(HttpURLConnection.class);
when(mockURL.openConnection()).thenReturn(mockConnection); when(mockURL.openConnection()).thenReturn(mockConnection);
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND); when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND);
// Invoke the method and verify the exception // Invoke the method and verify the exception
DataSourceException exception = assertThrows(DataSourceException.class, () -> { DataSourceException exception = assertThrows(DataSourceException.class, () -> {
dataSource.getProductDTOById(demoProductId); DataSource.getProductDTOById(demoProductId);
}); });
// Verify the exception message // Verify the exception message
assertEquals("Nothing found: Amazon API responded with error code 404", exception.getMessage()); assertEquals("Nothing found: AMAZON API responded with error code 404", exception.getMessage());
}
@Test
void testGetProductDTOById_emptyResponse() throws IOException {
// Set up the test environment
String mockResponse = "";
AmazonProductDataSource dataSource = mock(AmazonProductDataSource.class);
URL mockURL = mock(URL.class);
when(dataSource.createApiUrl(demoProductId)).thenReturn(mockURL);
when(dataSource.buildProductDTO(any(), anyString())).thenCallRealMethod();
HttpURLConnection mockConnection = mock(HttpURLConnection.class);
when(mockURL.openConnection()).thenReturn(mockConnection);
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
when(mockConnection.getInputStream()).thenReturn(new ByteArrayInputStream(mockResponse.getBytes()));
when(dataSource.getProductDTOById(demoProductId)).thenCallRealMethod();
// Invoke the method
ProductDTO result = dataSource.getProductDTOById(demoProductId);
// Verify the product DTO properties
assertEquals(demoProductId, result.getProductId());
assertEquals(Webshop.AMAZON, result.getDataOrigin());
assertEquals(false, result.isAvailable());
assertEquals(0.0, result.getCurrentPrice());
assertEquals(0.0, result.getDeliveryPrice());
assertEquals(null, result.getMerchant());
} }
@Test @Test
void testGetProductDTOById_ioException() throws IOException { void testGetProductDTOById_ioException() throws IOException {
// Set up the test environment // Set up the test environment
AmazonProductDataSource dataSource = mock(AmazonProductDataSource.class); AmazonProductDataSource DataSource = mock(AmazonProductDataSource.class);
URL mockURL = mock(URL.class); URL mockURL = mock(URL.class);
when(dataSource.getDataOrigin()).thenReturn(demoDataOrigin); when(DataSource.getDataOrigin()).thenReturn(demoDataOrigin);
when(dataSource.createApiUrl(demoProductId)).thenReturn(mockURL); when(DataSource.createApiUrl(demoProductId)).thenReturn(mockURL);
when(dataSource.getProductDTOById(demoProductId)).thenCallRealMethod(); when(DataSource.getProductDTOById(demoProductId)).thenCallRealMethod();
when(dataSource.buildProductDTO(any(), anyString())).thenCallRealMethod(); when(DataSource.buildProductDTO(any(), anyString())).thenCallRealMethod();
HttpURLConnection mockConnection = mock(HttpURLConnection.class); HttpURLConnection mockConnection = mock(HttpURLConnection.class);
when(mockURL.openConnection()).thenReturn(mockConnection); when(mockURL.openConnection()).thenReturn(mockConnection);
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
@@ -183,16 +211,16 @@ class AmazonProductDataSourceTest {
// Invoke the method and verify the exception // Invoke the method and verify the exception
DataSourceException exception = assertThrows(DataSourceException.class, () -> { DataSourceException exception = assertThrows(DataSourceException.class, () -> {
dataSource.getProductDTOById(demoProductId); DataSource.getProductDTOById(demoProductId);
}); });
// Verify the exception message // Verify the exception message
assertEquals("Couldn't fulfill Amazon API request", exception.getMessage()); assertEquals("Couldn't fulfill AMAZON API request", exception.getMessage());
} }
@Test @Test
void getDataOrigin_ReturnsExpectedDataOrigin() { void getDataOrigin_ReturnsExpectedDataOrigin() {
String dataOrigin = demoDataSource.getDataOrigin(); Webshop dataOrigin = demoDataSource.getDataOrigin();
assertEquals(demoDataOrigin, dataOrigin); assertEquals(demoDataOrigin, dataOrigin);
} }

View File

@@ -1,7 +1,6 @@
package de.rwu.easydrop.api.client; package de.rwu.easydrop.api.client;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.MalformedURLException; import java.net.MalformedURLException;
@@ -12,15 +11,14 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.exception.DataWriterException;
class AmazonPurchaserTest { class AmazonPurchaserTest {
private AmazonPurchaser demoPurchaser; private AmazonPurchaser demoPurchaser;
private static String demoApiKey = "my-api-key"; private static String demoApiKey = "my-api-key";
private static String demoApiUrl = "https://www.example.com/api"; private static String demoApiUrl = "https://www.example.com/api";
private static String demoDataTarget = "Amazon"; private static Webshop demoDataTarget = Webshop.AMAZON;
@BeforeEach @BeforeEach
void setup() { void setup() {
@@ -72,7 +70,7 @@ class AmazonPurchaserTest {
@Test @Test
void getDataTarget_ReturnsExpectedDataOrigin() { void getDataTarget_ReturnsExpectedDataOrigin() {
String dataTarget = demoPurchaser.getDataTarget(); Webshop dataTarget = demoPurchaser.getDataTarget();
assertEquals(demoDataTarget, dataTarget); assertEquals(demoDataTarget, dataTarget);
} }

View File

@@ -11,12 +11,14 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import de.rwu.easydrop.model.Webshop;
class AmazonSellerTest { class AmazonSellerTest {
private AmazonSeller demoSeller; private AmazonSeller demoSeller;
private static String demoApiKey = "my-api-key"; private static String demoApiKey = "my-api-key";
private static String demoApiUrl = "https://www.example.com/api"; private static String demoApiUrl = "https://www.example.com/api";
private static String demoDataTarget = "Amazon"; private static Webshop demoDataTarget = Webshop.AMAZON;
@BeforeEach @BeforeEach
void setup() { void setup() {
@@ -68,7 +70,7 @@ class AmazonSellerTest {
@Test @Test
void getDataTarget_ReturnsExpectedDataOrigin() { void getDataTarget_ReturnsExpectedDataOrigin() {
String dataTarget = demoSeller.getDataTarget(); Webshop dataTarget = demoSeller.getDataTarget();
assertEquals(demoDataTarget, dataTarget); assertEquals(demoDataTarget, dataTarget);
} }

View File

@@ -1,8 +1,6 @@
package de.rwu.easydrop.api.client; 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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
@@ -11,17 +9,14 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; 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; import de.rwu.easydrop.util.Config;
class DataSourceFactoryTest { class DataSourceFactoryTest {
@Mock @Mock
private Config config; private Config config;
private DataSourceFactory dataSourceFactory; private DataSourceFactory DataSourceFactory;
@BeforeEach @BeforeEach
void setUp() throws ConfigurationException { void setUp() throws ConfigurationException {
@@ -30,40 +25,24 @@ class DataSourceFactoryTest {
when(config.getProperty("AMAZON_API_KEY")).thenReturn("amazon-api-key"); when(config.getProperty("AMAZON_API_KEY")).thenReturn("amazon-api-key");
when(config.getProperty("EBAY_API_URL")).thenReturn("https://api.ebay.com"); when(config.getProperty("EBAY_API_URL")).thenReturn("https://api.ebay.com");
when(config.getProperty("EBAY_API_KEY")).thenReturn("ebay-api-key"); when(config.getProperty("EBAY_API_KEY")).thenReturn("ebay-api-key");
dataSourceFactory = new DataSourceFactory(config); DataSourceFactory = new DataSourceFactory(config);
} }
@Test @Test
void createAmazonProductDataSource_ReturnsAmazonProductDataSource() { void createAmazonProductDataSource_ReturnsAmazonProductDataSource() {
// Act // Act
AmazonProductDataSource dataSource = dataSourceFactory.createAmazonProductDataSource(); AmazonProductDataSource DataSource = DataSourceFactory.createAmazonProductDataSource();
// Assert // Assert
assertEquals("amazon-api-key", dataSource.getApiKey()); assertEquals("amazon-api-key", DataSource.getApiKey());
} }
@Test @Test
void createEbayItemDataSource_ReturnsEbayItemDataSource() { void createEbayItemDataSource_ReturnsEbayItemDataSource() {
// Act // Act
EbayItemDataSource dataSource = dataSourceFactory.createEbayItemDataSource(); EbayItemDataSource DataSource = DataSourceFactory.createEbayItemDataSource();
// Assert // Assert
assertEquals("ebay-api-key", dataSource.getApiKey()); 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());
} }
} }

View File

@@ -19,13 +19,14 @@ import org.mockito.MockitoAnnotations;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.exception.DataSourceException; import de.rwu.easydrop.exception.DataSourceException;
import de.rwu.easydrop.model.Webshop;
class EbayItemDataSourceTest { class EbayItemDataSourceTest {
private EbayItemDataSource demoDataSource; private EbayItemDataSource demoDataSource;
private static String demoApiKey = "my-api-key"; private static String demoApiKey = "my-api-key";
private static String demoApiUrl = "https://www.example.com/api"; private static String demoApiUrl = "https://www.example.com/api";
private static String demoDataOrigin = "eBay"; private static Webshop demoDataOrigin = Webshop.EBAY;
private static String demoQuery = "iPhone"; private static String demoQuery = "iPhone";
@BeforeEach @BeforeEach
@@ -82,7 +83,7 @@ class EbayItemDataSourceTest {
ProductDTO result = demoDataSource.buildProductDTO(product, json); ProductDTO result = demoDataSource.buildProductDTO(product, json);
assertEquals("eBay", result.getDataOrigin()); assertEquals(Webshop.EBAY, result.getDataOrigin());
assertEquals(false, result.isAvailable()); // Default value for boolean assertEquals(false, result.isAvailable()); // Default value for boolean
assertEquals(0.0, result.getCurrentPrice()); // Default value for double assertEquals(0.0, result.getCurrentPrice()); // Default value for double
assertEquals(0.0, result.getDeliveryPrice()); // Default value for double assertEquals(0.0, result.getDeliveryPrice()); // Default value for double
@@ -91,7 +92,7 @@ class EbayItemDataSourceTest {
@Test @Test
void getDataOrigin_ReturnsExpectedDataOrigin() { void getDataOrigin_ReturnsExpectedDataOrigin() {
String dataOrigin = demoDataSource.getDataOrigin(); Webshop dataOrigin = demoDataSource.getDataOrigin();
assertEquals(demoDataOrigin, dataOrigin); assertEquals(demoDataOrigin, dataOrigin);
} }
@@ -105,21 +106,21 @@ class EbayItemDataSourceTest {
void testGetProductDTOById_failedRequest() throws IOException { void testGetProductDTOById_failedRequest() throws IOException {
// Set up the test environment // Set up the test environment
EbayItemDataSource dataSource = mock(EbayItemDataSource.class); EbayItemDataSource DataSource = mock(EbayItemDataSource.class);
URL mockURL = mock(URL.class); URL mockURL = mock(URL.class);
when(dataSource.getDataOrigin()).thenReturn(demoDataOrigin); when(DataSource.getDataOrigin()).thenReturn(demoDataOrigin);
when(dataSource.createApiUrl(demoQuery)).thenReturn(mockURL); when(DataSource.createApiUrl(demoQuery)).thenReturn(mockURL);
when(dataSource.getProductDTOById(demoQuery)).thenCallRealMethod(); when(DataSource.getProductDTOById(demoQuery)).thenCallRealMethod();
HttpURLConnection mockConnection = mock(HttpURLConnection.class); HttpURLConnection mockConnection = mock(HttpURLConnection.class);
when(mockURL.openConnection()).thenReturn(mockConnection); when(mockURL.openConnection()).thenReturn(mockConnection);
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND); when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND);
// Invoke the method and verify the exception // Invoke the method and verify the exception
DataSourceException exception = assertThrows(DataSourceException.class, () -> { DataSourceException exception = assertThrows(DataSourceException.class, () -> {
dataSource.getProductDTOById(demoQuery); DataSource.getProductDTOById(demoQuery);
}); });
// Verify the exception message // Verify the exception message
assertEquals("Nothing found: eBay API responded with error code 404", exception.getMessage()); assertEquals("Nothing found: EBAY API responded with error code 404", exception.getMessage());
} }
} }

View File

@@ -11,12 +11,14 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import de.rwu.easydrop.model.Webshop;
class EbayPurchaserTest { class EbayPurchaserTest {
private EbayPurchaser demoPurchaser; private EbayPurchaser demoPurchaser;
private static String demoApiKey = "my-api-key"; private static String demoApiKey = "my-api-key";
private static String demoApiUrl = "https://www.example.com/api"; private static String demoApiUrl = "https://www.example.com/api";
private static String demoDataTarget = "eBay"; private static Webshop demoDataTarget = Webshop.EBAY;
@BeforeEach @BeforeEach
void setup() { void setup() {
@@ -65,7 +67,7 @@ class EbayPurchaserTest {
@Test @Test
void getDataTarget_ReturnsExpectedDataOrigin() { void getDataTarget_ReturnsExpectedDataOrigin() {
String dataTarget = demoPurchaser.getDataTarget(); Webshop dataTarget = demoPurchaser.getDataTarget();
assertEquals(demoDataTarget, dataTarget); assertEquals(demoDataTarget, dataTarget);
} }

View File

@@ -11,12 +11,14 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import de.rwu.easydrop.model.Webshop;
class EbaySellerTest { class EbaySellerTest {
private EbaySeller demoSeller; private EbaySeller demoSeller;
private static String demoApiKey = "my-api-key"; private static String demoApiKey = "my-api-key";
private static String demoApiUrl = "https://www.example.com/api"; private static String demoApiUrl = "https://www.example.com/api";
private static String demoDataTarget = "eBay"; private static Webshop demoDataTarget = Webshop.EBAY;
@BeforeEach @BeforeEach
void setup() { void setup() {
@@ -65,7 +67,7 @@ class EbaySellerTest {
@Test @Test
void getDataTarget_ReturnsExpectedDataOrigin() { void getDataTarget_ReturnsExpectedDataOrigin() {
String dataTarget = demoSeller.getDataTarget(); Webshop dataTarget = demoSeller.getDataTarget();
assertEquals(demoDataTarget, dataTarget); assertEquals(demoDataTarget, dataTarget);
} }

View File

@@ -0,0 +1,63 @@
package de.rwu.easydrop.api.client;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.when;
import javax.naming.ConfigurationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import de.rwu.easydrop.util.Config;
class PurchaserFactoryTest {
@Mock
private Config mockConfig;
private PurchaserFactory purchaserFactory;
@BeforeEach
void setUp() throws ConfigurationException {
MockitoAnnotations.openMocks(this);
purchaserFactory = new PurchaserFactory(mockConfig);
purchaserFactory.setConfig(mockConfig);
}
@Test
void createAmazonPurchaser_ValidConfig_ReturnsAmazonPurchaser() {
// Arrange
String amazonApiUrl = "amazon_api_url";
String amazonApiKey = "amazon_api_key";
when(mockConfig.getProperty("AMAZON_API_URL")).thenReturn(amazonApiUrl);
when(mockConfig.getProperty("AMAZON_API_KEY")).thenReturn(amazonApiKey);
// Act
AmazonPurchaser amazonPurchaser = purchaserFactory.createAmazonPurchaser();
// Assert
assertNotNull(amazonPurchaser);
assertEquals(amazonApiKey, amazonPurchaser.getApiKey());
}
@Test
void createEbayPurchaser_ValidConfig_ReturnsEbayPurchaser() {
// Arrange
String ebayApiUrl = "ebay_api_url";
String ebayApiKey = "ebay_api_key";
when(mockConfig.getProperty("EBAY_API_URL")).thenReturn(ebayApiUrl);
when(mockConfig.getProperty("EBAY_API_KEY")).thenReturn(ebayApiKey);
// Act
EbayPurchaser ebayPurchaser = purchaserFactory.createEbayPurchaser();
// Assert
assertNotNull(ebayPurchaser);
assertEquals(ebayApiKey, ebayPurchaser.getApiKey());
}
}

View File

@@ -0,0 +1,63 @@
package de.rwu.easydrop.api.client;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.when;
import javax.naming.ConfigurationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import de.rwu.easydrop.util.Config;
class SellerFactoryTest {
@Mock
private Config mockConfig;
private SellerFactory sellerFactory;
@BeforeEach
void setUp() throws ConfigurationException {
MockitoAnnotations.openMocks(this);
sellerFactory = new SellerFactory(mockConfig);
sellerFactory.setConfig(mockConfig);
}
@Test
void createAmazonSeller_ValidConfig_ReturnsAmazonSeller() {
// Arrange
String amazonApiUrl = "amazon_api_url";
String amazonApiKey = "amazon_api_key";
when(mockConfig.getProperty("AMAZON_API_URL")).thenReturn(amazonApiUrl);
when(mockConfig.getProperty("AMAZON_API_KEY")).thenReturn(amazonApiKey);
// Act
AmazonSeller amazonSeller = sellerFactory.createAmazonSeller();
// Assert
assertNotNull(amazonSeller);
assertEquals(amazonApiKey, amazonSeller.getApiKey());
}
@Test
void createEbaySeller_ValidConfig_ReturnsEbaySeller() {
// Arrange
String ebayApiUrl = "ebay_api_url";
String ebayApiKey = "ebay_api_key";
when(mockConfig.getProperty("EBAY_API_URL")).thenReturn(ebayApiUrl);
when(mockConfig.getProperty("EBAY_API_KEY")).thenReturn(ebayApiKey);
// Act
EbaySeller ebaySeller = sellerFactory.createEbaySeller();
// Assert
assertNotNull(ebaySeller);
assertEquals(ebayApiKey, ebaySeller.getApiKey());
}
}

View File

@@ -6,13 +6,15 @@ import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import de.rwu.easydrop.model.Webshop;
class ProductDTOTest { class ProductDTOTest {
@Test @Test
void constructor_SetsProductIdAndDataOrigin() { void constructor_SetsProductIdAndDataOrigin() {
// Arrange // Arrange
String productId = "12345"; String productId = "12345";
String dataOrigin = "Amazon"; Webshop dataOrigin = Webshop.AMAZON;
// Act // Act
ProductDTO productDTO = new ProductDTO(productId, dataOrigin); ProductDTO productDTO = new ProductDTO(productId, dataOrigin);
@@ -25,19 +27,19 @@ class ProductDTOTest {
@Test @Test
void gettersAndSetters_WorkAsExpected() { void gettersAndSetters_WorkAsExpected() {
// Arrange // Arrange
ProductDTO productDTO = new ProductDTO("12345", "Amazon"); ProductDTO productDTO = new ProductDTO("12345", Webshop.AMAZON);
// Act and Assert // Act and Assert
assertEquals("12345", productDTO.getProductId()); assertEquals("12345", productDTO.getProductId());
assertEquals("Amazon", productDTO.getDataOrigin()); assertEquals(Webshop.AMAZON, productDTO.getDataOrigin());
// Modify fields // Modify fields
productDTO.setProductId("54321"); productDTO.setProductId("54321");
productDTO.setDataOrigin("eBay"); productDTO.setDataOrigin(Webshop.EBAY);
// Assert // Assert
assertEquals("54321", productDTO.getProductId()); assertEquals("54321", productDTO.getProductId());
assertEquals("eBay", productDTO.getDataOrigin()); assertEquals(Webshop.EBAY, productDTO.getDataOrigin());
} }
@Test @Test

View File

@@ -8,45 +8,51 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.when;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; 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.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.sqlite.SQLiteDataSource; import org.sqlite.SQLiteDataSource;
import de.rwu.easydrop.api.dto.OfferDTO;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.api.dto.TransactionDTO;
import de.rwu.easydrop.exception.PersistenceException; import de.rwu.easydrop.exception.PersistenceException;
import de.rwu.easydrop.model.Webshop;
@TestInstance(Lifecycle.PER_CLASS)
class SQLiteConnectorTest { class SQLiteConnectorTest {
private static final String TEST_PRODUCT_ID = "12345"; private static final String TEST_PRODUCT_ID = "12345";
private SQLiteConnector sqliteConnector; private SQLiteConnector sqliteConnector = new SQLiteConnector(new SQLiteDataSource());
@Mock
private Connection connection;
@Mock
private PreparedStatement statement;
@Mock
private ResultSet resultSet;
@Mock @Mock
private SQLiteDataSource mockDataSource; private SQLiteDataSource mockDataSource;
@BeforeAll
public void setup() {
sqliteConnector = new SQLiteConnector(new SQLiteDataSource());
}
@BeforeEach @BeforeEach
public void clearDatabase() { public void prepare() {
MockitoAnnotations.openMocks(this); MockitoAnnotations.openMocks(this);
} }
@Test @Test
void saveProduct_ValidProduct_SuccessfullySaved() { void writeProduct_ValidProduct_SuccessfullySaved() {
// Arrange // Arrange
sqliteConnector.clearData(); sqliteConnector.clearData();
ProductDTO ProductDTO = new ProductDTO(TEST_PRODUCT_ID, "Amazon"); ProductDTO ProductDTO = new ProductDTO(TEST_PRODUCT_ID, Webshop.AMAZON);
ProductDTO.setDataOrigin("Amazon"); ProductDTO.setDataOrigin(Webshop.AMAZON);
ProductDTO.setProductId(TEST_PRODUCT_ID); ProductDTO.setProductId(TEST_PRODUCT_ID);
ProductDTO.setCurrentPrice(9.99); ProductDTO.setCurrentPrice(9.99);
ProductDTO.setMerchant("Sample Merchant"); ProductDTO.setMerchant("Sample Merchant");
@@ -54,12 +60,12 @@ class SQLiteConnectorTest {
ProductDTO.setAvailable(true); ProductDTO.setAvailable(true);
// Act // Act
assertDoesNotThrow(() -> sqliteConnector.saveProduct(ProductDTO)); assertDoesNotThrow(() -> sqliteConnector.writeProduct(ProductDTO));
// Assert // Assert
ProductDTO savedProductDTO = sqliteConnector.getProductDTOById(TEST_PRODUCT_ID); ProductDTO savedProductDTO = sqliteConnector.getProductDTOById(TEST_PRODUCT_ID);
assertNotNull(savedProductDTO); assertNotNull(savedProductDTO);
assertEquals("Amazon", savedProductDTO.getDataOrigin()); assertEquals(Webshop.AMAZON, savedProductDTO.getDataOrigin());
assertEquals(TEST_PRODUCT_ID, savedProductDTO.getProductId()); assertEquals(TEST_PRODUCT_ID, savedProductDTO.getProductId());
assertEquals(9.99, savedProductDTO.getCurrentPrice()); assertEquals(9.99, savedProductDTO.getCurrentPrice());
assertEquals("Sample Merchant", savedProductDTO.getMerchant()); assertEquals("Sample Merchant", savedProductDTO.getMerchant());
@@ -78,7 +84,7 @@ class SQLiteConnectorTest {
// Assert // Assert
assertNotNull(ProductDTO); assertNotNull(ProductDTO);
assertEquals("Amazon", ProductDTO.getDataOrigin()); assertEquals(Webshop.AMAZON, ProductDTO.getDataOrigin());
assertEquals(TEST_PRODUCT_ID, ProductDTO.getProductId()); assertEquals(TEST_PRODUCT_ID, ProductDTO.getProductId());
assertEquals(9.99, ProductDTO.getCurrentPrice()); assertEquals(9.99, ProductDTO.getCurrentPrice());
assertEquals("Sample Merchant", ProductDTO.getMerchant()); assertEquals("Sample Merchant", ProductDTO.getMerchant());
@@ -111,14 +117,14 @@ class SQLiteConnectorTest {
} }
@Test @Test
void saveProduct_ThrowsPersistenceException_OnSQLException() throws SQLException { void writeProduct_ThrowsPersistenceException_OnSQLException() throws SQLException {
// Arrange // Arrange
ProductDTO ProductDTO = new ProductDTO(TEST_PRODUCT_ID, "Amazon"); ProductDTO ProductDTO = new ProductDTO(TEST_PRODUCT_ID, Webshop.AMAZON);
sqliteConnector.setDb(mockDataSource); sqliteConnector.setDb(mockDataSource);
doThrow(SQLException.class).when(mockDataSource).getConnection(); doThrow(SQLException.class).when(mockDataSource).getConnection();
// Act and Assert // Act and Assert
assertThrows(PersistenceException.class, () -> sqliteConnector.saveProduct(ProductDTO)); assertThrows(PersistenceException.class, () -> sqliteConnector.writeProduct(ProductDTO));
} }
@Test @Test
@@ -143,53 +149,179 @@ class SQLiteConnectorTest {
} }
private void insertSampleProduct() { private void insertSampleProduct() {
ProductDTO ProductDTO = new ProductDTO(TEST_PRODUCT_ID, "Amazon"); ProductDTO productDTO = new ProductDTO(TEST_PRODUCT_ID, Webshop.AMAZON);
ProductDTO.setCurrentPrice(9.99); productDTO.setCurrentPrice(9.99);
ProductDTO.setMerchant("Sample Merchant"); productDTO.setMerchant("Sample Merchant");
ProductDTO.setDeliveryPrice(2.50); productDTO.setDeliveryPrice(2.50);
ProductDTO.setAvailable(true); productDTO.setAvailable(true);
sqliteConnector.saveProduct(ProductDTO); sqliteConnector.writeProduct(productDTO);
}
private void insertSampleOffer() {
ProductDTO p1 = new ProductDTO("p1", Webshop.AMAZON);
p1.setCurrentPrice(123f);
ProductDTO p2 = new ProductDTO("p2", Webshop.EBAY);
p2.setCurrentPrice(234f);
OfferDTO dto = new OfferDTO();
dto.setOfferId("123");
dto.setLastUpdate("2020-01-01 00:00:00");
dto.setSourceProduct(p1);
dto.setTargetProduct(p2);
sqliteConnector.writeOffer(dto);
}
private void insertSampleTransaction() {
TransactionDTO dto = new TransactionDTO();
dto.setOfferId("123");
dto.setEarnings(12f);
dto.setVolume(123f);
dto.setTransactionTime("2020-01-01 00:00:00");
sqliteConnector.writeTX(dto);
} }
@Test @Test
void getDataOrigin_ReturnsCorrectDataOrigin() { void outputTransactionsToLog_NothingThrown() {
insertSampleTransaction();
assertDoesNotThrow(() -> {
sqliteConnector.outputTransactionsToLog();
});
}
@Test
void outputSummaryToLog_NothingThrown() {
insertSampleTransaction();
assertDoesNotThrow(() -> {
sqliteConnector.outputSummaryToLog();
});
}
@Test
void outputTransactionsToLog_ThrowsPersistenceException_OnSQLException() throws SQLException {
// Arrange // Arrange
SQLiteConnector connector = new SQLiteConnector(new SQLiteDataSource()); sqliteConnector.setDb(mockDataSource);
doThrow(SQLException.class).when(mockDataSource).getConnection();
// Act and Assert
PersistenceException e = assertThrows(PersistenceException.class,
() -> sqliteConnector.outputTransactionsToLog());
assertEquals("Something went wrong while reading transaction from SQLite", e.getMessage());
}
@Test
void outputSummaryToLog_ThrowsPersistenceException_OnSQLException() throws SQLException {
// Arrange
sqliteConnector.setDb(mockDataSource);
doThrow(SQLException.class).when(mockDataSource).getConnection();
// Act and Assert
PersistenceException e = assertThrows(PersistenceException.class,
() -> sqliteConnector.outputSummaryToLog());
assertEquals("Something went wrong while reading transaction summary from SQLite", e.getMessage());
}
@Test
void deleteOfferById_SuccessfulDelete() {
// Arrange
sqliteConnector.clearData();
insertSampleOffer();
// Make sure it's there
assertEquals(1, sqliteConnector.getOffers().size());
// Act // Act
String dataOrigin = connector.getDataOrigin(); sqliteConnector.deleteOfferById("123");
// Assert // Assert
assertEquals("SQLite", dataOrigin); assertEquals(0, sqliteConnector.getOffers().size());
} }
@Test @Test
void getApiKey_UnsupportedOperationExceptionThrown() { void deleteOfferById_ThrowsPersistenceException_OnSQLException() throws SQLException {
// Arrange sqliteConnector.setDb(mockDataSource);
SQLiteConnector connector = new SQLiteConnector(new SQLiteDataSource()); doThrow(SQLException.class).when(mockDataSource).getConnection();
// Act and Assert // Act and Assert
assertThrows(UnsupportedOperationException.class, connector::getApiKey); PersistenceException e = assertThrows(PersistenceException.class,
() -> sqliteConnector.deleteOfferById("123"));
assertEquals("Something went wrong while deleting offer from SQLite", e.getMessage());
} }
@Test @Test
void buildProductDTO_UnsupportedOperationExceptionThrown() { void getOffers_ReturnsCorrectValues() {
// Arrange sqliteConnector.clearData();
SQLiteConnector connector = new SQLiteConnector(new SQLiteDataSource()); assertEquals(0, sqliteConnector.getOffers().size());
ProductDTO product = new ProductDTO("ASIN123", "Amazon");
String json = "{\"productId\":\"ASIN123\",\"dataOrigin\":\"Amazon\"}";
// Act and Assert insertSampleOffer();
assertThrows(UnsupportedOperationException.class, () -> connector.buildProductDTO(product, json));
assertEquals(1, sqliteConnector.getOffers().size());
} }
@Test @Test
void createApiUrl_UnsupportedOperationExceptionThrown() { void getOffers_ThrowsPersistenceException_OnSQLException() throws SQLException {
// Arrange sqliteConnector.setDb(mockDataSource);
SQLiteConnector connector = new SQLiteConnector(new SQLiteDataSource()); doThrow(SQLException.class).when(mockDataSource).getConnection();
String productIdentifier = "ASIN123";
// Act and Assert // Act and Assert
assertThrows(UnsupportedOperationException.class, () -> connector.createApiUrl(productIdentifier)); PersistenceException e = assertThrows(PersistenceException.class,
() -> sqliteConnector.getOffers());
assertEquals("Something went wrong while reading offers from SQLite", e.getMessage());
}
@Test
void writeOffer_ValidDTO_DataSavedToDatabase() throws SQLException {
// Arrange
OfferDTO offerDTO = new OfferDTO();
offerDTO.setOfferId("123");
offerDTO.setSourceProduct(new ProductDTO(TEST_PRODUCT_ID, Webshop.AMAZON));
offerDTO.setTargetProduct(new ProductDTO(TEST_PRODUCT_ID, Webshop.AMAZON));
String expectedQuery = "INSERT INTO offers (offerId, sourceWebshop, sourceId, sourcePrice, " +
"targetWebshop, targetId, targetPrice, lastupdate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
when(connection.prepareStatement(expectedQuery)).thenReturn(statement);
// Act
assertDoesNotThrow(() -> sqliteConnector.writeOffer(offerDTO));
}
@Test
void writeTX_NothingThrown() {
TransactionDTO dto = new TransactionDTO();
assertDoesNotThrow(() -> {
sqliteConnector.writeTX(dto);
});
}
@Test
void writeTX_ThrowsPersistenceException_OnSQLException() throws SQLException {
// Arrange
sqliteConnector.setDb(mockDataSource);
doThrow(SQLException.class).when(mockDataSource).getConnection();
TransactionDTO dto = new TransactionDTO();
// Act and Assert
PersistenceException e = assertThrows(PersistenceException.class,
() -> sqliteConnector.writeTX(dto));
assertEquals("Something went wrong while saving transaction to SQLite", e.getMessage());
}
@Test
void getOfferDTOById_OfferExists_ReturnsProductDTO() {
// Arrange
sqliteConnector.clearData();
insertSampleOffer();
// Act
OfferDTO offerDTO = sqliteConnector.getOfferDTOById("123");
// Assert
assertNotNull(offerDTO);
assertEquals("123", offerDTO.getOfferId());
assertEquals("2020-01-01 00:00:00", offerDTO.getLastUpdate());
} }
} }

View File

@@ -0,0 +1,61 @@
package de.rwu.easydrop.exception;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
class InvalidOfferExceptionTest {
@Test
void constructor_WithMessage_SetsMessage() {
// Arrange
String message = "Invalid Offer data";
// Act
InvalidOfferException exception = new InvalidOfferException(message);
// Assert
assertEquals(message, exception.getMessage());
}
@Test
void constructor_WithMessageAndCause_SetsMessageAndCause() {
// Arrange
String message = "Invalid Offer data";
Throwable cause = new IllegalArgumentException("Invalid argument");
// Act
InvalidOfferException exception = new InvalidOfferException(message, cause);
// Assert
assertEquals(message, exception.getMessage());
assertEquals(cause, exception.getCause());
}
@Test
void constructor_WithNullMessage_SetsNullMessage() {
// Act
InvalidOfferException exception = new InvalidOfferException(null);
// Assert
assertEquals(null, exception.getMessage());
}
@Test
void constructor_WithNullCause_SetsNullCause() {
// Act
InvalidOfferException exception = new InvalidOfferException("Invalid Offer data", null);
// Assert
assertEquals(null, exception.getCause());
}
@Test
void throw_InvalidOfferException() {
// Act and Assert
assertThrows(InvalidOfferException.class, () -> {
throw new InvalidOfferException("Invalid Offer data");
});
}
}

View File

@@ -0,0 +1,61 @@
package de.rwu.easydrop.exception;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
class InvalidTransactionExceptionTest {
@Test
void constructor_WithMessage_SetsMessage() {
// Arrange
String message = "Invalid Transaction data";
// Act
InvalidTransactionException exception = new InvalidTransactionException(message);
// Assert
assertEquals(message, exception.getMessage());
}
@Test
void constructor_WithMessageAndCause_SetsMessageAndCause() {
// Arrange
String message = "Invalid Transaction data";
Throwable cause = new IllegalArgumentException("Invalid argument");
// Act
InvalidTransactionException exception = new InvalidTransactionException(message, cause);
// Assert
assertEquals(message, exception.getMessage());
assertEquals(cause, exception.getCause());
}
@Test
void constructor_WithNullMessage_SetsNullMessage() {
// Act
InvalidTransactionException exception = new InvalidTransactionException(null);
// Assert
assertEquals(null, exception.getMessage());
}
@Test
void constructor_WithNullCause_SetsNullCause() {
// Act
InvalidTransactionException exception = new InvalidTransactionException("Invalid Transaction data", null);
// Assert
assertEquals(null, exception.getCause());
}
@Test
void throw_InvalidTransactionException() {
// Act and Assert
assertThrows(InvalidTransactionException.class, () -> {
throw new InvalidTransactionException("Invalid Transaction data");
});
}
}

View File

@@ -1,5 +1,7 @@
package de.rwu.easydrop.model; package de.rwu.easydrop.model;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
@@ -19,7 +21,7 @@ class ProductCatalogueTest {
Product product = new Product(); Product product = new Product();
product.setProductId("12345"); product.setProductId("12345");
product.setMerchant("AmazonSeller"); product.setMerchant("AmazonSeller");
product.setDataOrigin("Amazon"); product.setDataOrigin(Webshop.AMAZON);
productCatalogue.addProduct(product); productCatalogue.addProduct(product);
List<Product> products = productCatalogue.getProducts(); List<Product> products = productCatalogue.getProducts();
@@ -32,13 +34,13 @@ class ProductCatalogueTest {
Product product1 = new Product(); Product product1 = new Product();
product1.setProductId("12345"); product1.setProductId("12345");
product1.setMerchant("AmazonSeller"); product1.setMerchant("AmazonSeller");
product1.setDataOrigin("Amazon"); product1.setDataOrigin(Webshop.AMAZON);
productCatalogue.addProduct(product1); productCatalogue.addProduct(product1);
Product product2 = new Product(); Product product2 = new Product();
product2.setProductId("54321"); product2.setProductId("54321");
product2.setMerchant("eBaySeller"); product2.setMerchant("eBaySeller");
product2.setDataOrigin("eBay"); product2.setDataOrigin(Webshop.EBAY);
productCatalogue.addProduct(product2); productCatalogue.addProduct(product2);
productCatalogue.removeProduct(product1); productCatalogue.removeProduct(product1);
@@ -53,13 +55,13 @@ class ProductCatalogueTest {
Product product1 = new Product(); Product product1 = new Product();
product1.setProductId("12345"); product1.setProductId("12345");
product1.setMerchant("AmazonSeller"); product1.setMerchant("AmazonSeller");
product1.setDataOrigin("Amazon"); product1.setDataOrigin(Webshop.AMAZON);
productCatalogue.addProduct(product1); productCatalogue.addProduct(product1);
Product product2 = new Product(); Product product2 = new Product();
product2.setProductId("54321"); product2.setProductId("54321");
product2.setMerchant("eBay"); product2.setMerchant("ebayMerchant");
product2.setDataOrigin("eBay"); product2.setDataOrigin(Webshop.EBAY);
productCatalogue.addProduct(product2); productCatalogue.addProduct(product2);
productCatalogue.clearProducts(); productCatalogue.clearProducts();
@@ -73,20 +75,23 @@ class ProductCatalogueTest {
Product product1 = new Product(); Product product1 = new Product();
product1.setProductId("12345"); product1.setProductId("12345");
product1.setMerchant("AmazonSeller"); product1.setMerchant("AmazonSeller");
product1.setDataOrigin("Amazon"); product1.setDataOrigin(Webshop.AMAZON);
productCatalogue.addProduct(product1); productCatalogue.addProduct(product1);
Product product2 = new Product(); Product product2 = new Product();
product2.setProductId("54321"); product2.setProductId("54321");
product2.setMerchant("eBaySeller"); product2.setMerchant("eBaySeller");
product2.setDataOrigin("eBay"); product2.setDataOrigin(Webshop.EBAY);
productCatalogue.addProduct(product2); productCatalogue.addProduct(product2);
String expectedString = "Product Catalogue: GPU\n" + StringBuilder sb = new StringBuilder();
"Description: Graphics Processing Units\n" + sb.append(String.format("Catalogue Name: %s%n", "GPU"));
"Products:\n" + sb.append(String.format("Description: %s%n", "Graphics Processing Units"));
"Product: [12345 from AmazonSeller (Amazon) at 0,00 Euro (available: no)]\n" + sb.append("Products:\n");
"Product: [54321 from eBaySeller (eBay) at 0,00 Euro (available: no)]\n"; sb.append(String.format("%s%n", product1.toString()));
sb.append(String.format("%s%n", product2.toString()));
String expectedString = sb.toString();
Assertions.assertEquals(expectedString, productCatalogue.toString()); Assertions.assertEquals(expectedString, productCatalogue.toString());
} }

View File

@@ -0,0 +1,25 @@
package de.rwu.easydrop.model;
import static org.junit.jupiter.api.Assertions.assertSame;
import org.junit.jupiter.api.Test;
class ProductPairTest {
@Test
void constructor_TwoProducts_ProductsInitializedCorrectly() {
// Arrange
Product product1 = new Product();
product1.setProductId("123");
Product product2 = new Product();
product2.setProductId("234");
// Act
ProductPair pair = new ProductPair(product1, product2);
// Assert
assertSame(product1, pair.getProduct1());
assertSame(product2, pair.getProduct2());
}
}

View File

@@ -10,13 +10,13 @@ class ProductTest {
@Test @Test
void testToString1() { void testToString1() {
Product product1 = new Product(); Product product1 = new Product();
product1.setDataOrigin("Amazon"); product1.setDataOrigin(Webshop.AMAZON);
product1.setProductId("12345"); product1.setProductId("12345");
product1.setMerchant("Merchant A"); product1.setMerchant("Merchant A");
product1.setCurrentPrice(19.99); product1.setCurrentPrice(19.99);
product1.setAvailable(true); product1.setAvailable(true);
String expectedString1 = "Product: [12345 from Merchant A (Amazon) at 19,99 Euro (available: yes)]"; String expectedString1 = "Product: [12345 from Merchant A (AMAZON) at 19,99 Euro (available: yes)]";
String result1 = product1.toString(); String result1 = product1.toString();
assertEquals(expectedString1, result1); assertEquals(expectedString1, result1);
@@ -25,13 +25,13 @@ class ProductTest {
@Test @Test
void testToString2() { void testToString2() {
Product product2 = new Product(); Product product2 = new Product();
product2.setDataOrigin("eBay"); product2.setDataOrigin(Webshop.EBAY);
product2.setProductId("67890"); product2.setProductId("67890");
product2.setMerchant("Merchant B"); product2.setMerchant("Merchant B");
product2.setCurrentPrice(9.99); product2.setCurrentPrice(9.99);
product2.setAvailable(false); product2.setAvailable(false);
String expectedString2 = "Product: [67890 from Merchant B (eBay) at 9,99 Euro (available: no)]"; String expectedString2 = "Product: [67890 from Merchant B (EBAY) at 9,99 Euro (available: no)]";
String result2 = product2.toString(); String result2 = product2.toString();
assertEquals(expectedString2, result2); assertEquals(expectedString2, result2);
@@ -41,7 +41,7 @@ class ProductTest {
void gettersAndSetters_WorkAsExpected() { void gettersAndSetters_WorkAsExpected() {
// Arrange // Arrange
Product product = new Product(); Product product = new Product();
product.setDataOrigin("Amazon"); product.setDataOrigin(Webshop.AMAZON);
product.setProductId("12345"); product.setProductId("12345");
product.setCurrentPrice(9.99); product.setCurrentPrice(9.99);
product.setMerchant("Example Merchant"); product.setMerchant("Example Merchant");
@@ -49,7 +49,7 @@ class ProductTest {
product.setAvailable(true); product.setAvailable(true);
// Act and Assert // Act and Assert
assertEquals("Amazon", product.getDataOrigin()); assertEquals(Webshop.AMAZON, product.getDataOrigin());
assertEquals("12345", product.getProductId()); assertEquals("12345", product.getProductId());
assertEquals(9.99, product.getCurrentPrice()); assertEquals(9.99, product.getCurrentPrice());
assertEquals("Example Merchant", product.getMerchant()); assertEquals("Example Merchant", product.getMerchant());
@@ -57,7 +57,7 @@ class ProductTest {
assertTrue(product.isAvailable()); assertTrue(product.isAvailable());
// Modify fields // Modify fields
product.setDataOrigin("eBay"); product.setDataOrigin(Webshop.EBAY);
product.setProductId("54321"); product.setProductId("54321");
product.setCurrentPrice(19.99); product.setCurrentPrice(19.99);
product.setMerchant("New Merchant"); product.setMerchant("New Merchant");
@@ -65,7 +65,7 @@ class ProductTest {
product.setAvailable(false); product.setAvailable(false);
// Assert // Assert
assertEquals("eBay", product.getDataOrigin()); assertEquals(Webshop.EBAY, product.getDataOrigin());
assertEquals("54321", product.getProductId()); assertEquals("54321", product.getProductId());
assertEquals(19.99, product.getCurrentPrice()); assertEquals(19.99, product.getCurrentPrice());
assertEquals("New Merchant", product.getMerchant()); assertEquals("New Merchant", product.getMerchant());

View File

@@ -0,0 +1,23 @@
package de.rwu.easydrop.model;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
class WebshopTest {
@Test
void testFromString_returnsIntendedConstant() {
Webshop testShop = Webshop.fromString("Amazon");
assertEquals(Webshop.AMAZON, testShop);
}
@Test
void testFromString_invalidShop() {
assertThrows(IllegalArgumentException.class, () -> {
Webshop.fromString("thisdoesnotexist");
});
}
}

View File

@@ -0,0 +1,62 @@
package de.rwu.easydrop.service.mapping;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import org.junit.jupiter.api.Test;
import de.rwu.easydrop.api.dto.OfferDTO;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.Webshop;
class OfferMapperTest {
@Test
void testConstructorIsPrivate()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// Check for private constructor
Constructor<OfferMapper> constructor = OfferMapper.class.getDeclaredConstructor();
assertTrue(Modifier.isPrivate(constructor.getModifiers()));
// Make sure exception is thrown when instantiating
constructor.setAccessible(true);
assertThrows(InvocationTargetException.class, () -> {
constructor.newInstance();
});
}
@Test
void mapOfferToDTO() {
// Arrange
Product product = new Product();
product.setProductId("12345");
product.setDataOrigin(Webshop.AMAZON);
product.setAvailable(true);
product.setCurrentPrice(9.99);
product.setDeliveryPrice(2.50);
product.setMerchant("Seller1");
Offer offer = new Offer();
offer.setOfferId("68735");
offer.setLastUpdate("2020-07-07");
offer.setSourceProduct(product);
offer.setTargetProduct(product);
// Act
OfferDTO dto = OfferMapper.mapOfferToDTO(offer);
// Assert
assertEquals("68735", dto.getOfferId());
assertEquals("2020-07-07", dto.getLastUpdate());
}
}

View File

@@ -13,6 +13,7 @@ import org.junit.jupiter.api.Test;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.model.Product; import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.Webshop;
class ProductMapperTest { class ProductMapperTest {
@Test @Test
@@ -47,7 +48,7 @@ class ProductMapperTest {
} }
private ProductDTO createProductDTO() { private ProductDTO createProductDTO() {
ProductDTO dto = new ProductDTO("12345", "Amazon"); ProductDTO dto = new ProductDTO("12345", Webshop.AMAZON);
dto.setAvailable(true); dto.setAvailable(true);
dto.setCurrentPrice(9.99); dto.setCurrentPrice(9.99);
dto.setDeliveryPrice(2.50); dto.setDeliveryPrice(2.50);
@@ -60,7 +61,7 @@ class ProductMapperTest {
// Arrange // Arrange
Product product = new Product(); Product product = new Product();
product.setProductId("12345"); product.setProductId("12345");
product.setDataOrigin("Amazon"); product.setDataOrigin(Webshop.AMAZON);
product.setAvailable(true); product.setAvailable(true);
product.setCurrentPrice(9.99); product.setCurrentPrice(9.99);
product.setDeliveryPrice(2.50); product.setDeliveryPrice(2.50);
@@ -71,7 +72,7 @@ class ProductMapperTest {
// Assert // Assert
assertEquals("12345", dto.getProductId()); assertEquals("12345", dto.getProductId());
assertEquals("Amazon", dto.getDataOrigin()); assertEquals(Webshop.AMAZON, dto.getDataOrigin());
assertTrue(dto.isAvailable()); assertTrue(dto.isAvailable());
assertEquals(9.99, dto.getCurrentPrice()); assertEquals(9.99, dto.getCurrentPrice());
assertEquals(2.50, dto.getDeliveryPrice()); assertEquals(2.50, dto.getDeliveryPrice());

View File

@@ -0,0 +1,71 @@
package de.rwu.easydrop.service.mapping;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import org.junit.jupiter.api.Test;
import de.rwu.easydrop.api.dto.TransactionDTO;
import de.rwu.easydrop.model.Transaction;
class TransactionMapperTest {
@Test
void testConstructorIsPrivate()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// Check for private constructor
Constructor<TransactionMapper> constructor = TransactionMapper.class.getDeclaredConstructor();
assertTrue(Modifier.isPrivate(constructor.getModifiers()));
// Make sure exception is thrown when instantiating
constructor.setAccessible(true);
assertThrows(InvocationTargetException.class, () -> {
constructor.newInstance();
});
}
@Test
void mapTXFromDTO_WithValidTransactionDTO_ReturnsTransaction() {
// Arrange
TransactionDTO transactionDTO = new TransactionDTO();
transactionDTO.setOfferId("123");
transactionDTO.setVolume(10.0);
transactionDTO.setEarnings(5.0);
transactionDTO.setTransactionTime("2023-06-25 03:26:59");
// Act
Transaction transaction = TransactionMapper.mapTXFromDTO(transactionDTO);
// Assert
assertNotNull(transaction);
assertEquals(transactionDTO.getOfferId(), transaction.getOfferId());
assertEquals(transactionDTO.getVolume(), transaction.getVolume());
assertEquals(transactionDTO.getEarnings(), transaction.getEarnings());
assertEquals(transactionDTO.getTransactionTime(), transaction.getTransactionTime());
}
@Test
void mapTXToDTO_WithValidTransaction_ReturnsTransactionDTO() {
// Arrange
Transaction transaction = new Transaction();
transaction.setOfferId("123");
transaction.setVolume(10.0);
transaction.setEarnings(5.0);
transaction.setTransactionTime("2023-06-25 03:26:59");
// Act
TransactionDTO transactionDTO = TransactionMapper.mapTXToDTO(transaction);
// Assert
assertNotNull(transactionDTO);
assertEquals(transaction.getOfferId(), transactionDTO.getOfferId());
assertEquals(transaction.getVolume(), transactionDTO.getVolume());
assertEquals(transaction.getEarnings(), transactionDTO.getEarnings());
assertEquals(transaction.getTransactionTime(), transactionDTO.getTransactionTime());
}
}

View File

@@ -0,0 +1,176 @@
package de.rwu.easydrop.service.processing;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
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.exception.InvalidCatalogueException;
import de.rwu.easydrop.exception.InvalidOfferException;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.model.ProductPair;
class OfferIdentifierTest {
@Mock
private ProductCatalogue mockProductCatalogue;
private OfferIdentifier offerIdentifier;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
offerIdentifier = new OfferIdentifier();
}
@Test
void runIdentifier_WithValidProductCatalogues_IdentifiesOffers() {
// Arrange
List<ProductCatalogue> productCatalogues = new ArrayList<>();
ProductCatalogue productCatalogue1 = new ProductCatalogue("Catalogue1", "desc");
ProductCatalogue productCatalogue2 = new ProductCatalogue("Catalogue2", "desc");
Product product1 = new Product();
product1.setCurrentPrice(100.0);
Product product2 = new Product();
product2.setCurrentPrice(200.0);
Product product3 = new Product();
product3.setCurrentPrice(300.0);
Product product4 = new Product();
product4.setCurrentPrice(400.0);
productCatalogue1.addProduct(product1);
productCatalogue1.addProduct(product2);
productCatalogue2.addProduct(product3);
productCatalogue2.addProduct(product4);
productCatalogues.add(productCatalogue1);
productCatalogues.add(productCatalogue2);
// Act
List<Offer> identifiedOffers = offerIdentifier.runIdentifier(productCatalogues);
// Assert
assertEquals(2, identifiedOffers.size());
assertNotNull(identifiedOffers.get(0).getSourceProduct());
assertNotNull(identifiedOffers.get(0).getTargetProduct());
assertNotNull(identifiedOffers.get(1).getSourceProduct());
assertNotNull(identifiedOffers.get(1).getTargetProduct());
}
@Test
void runIdentifier_WithOneGoodOneBadCatalogue_BadOneIsntIncluded() {
// Arrange
List<ProductCatalogue> productCatalogues = new ArrayList<>();
ProductCatalogue productCatalogue1 = new ProductCatalogue("Catalogue1", "desc");
ProductCatalogue productCatalogue2 = new ProductCatalogue("Catalogue2", "desc");
Product product1 = new Product();
product1.setCurrentPrice(100.0);
Product product2 = new Product();
product2.setCurrentPrice(200.0);
Product product3 = new Product();
product3.setCurrentPrice(300.0);
Product product4 = new Product();
product4.setCurrentPrice(300.0);
productCatalogue1.addProduct(product1);
productCatalogue1.addProduct(product2);
productCatalogue2.addProduct(product3);
productCatalogue2.addProduct(product4);
productCatalogues.add(productCatalogue1);
productCatalogues.add(productCatalogue2);
// Act
List<Offer> identifiedOffers = offerIdentifier.runIdentifier(productCatalogues);
// Assert
assertEquals(1, identifiedOffers.size()); // Only 1 passed
}
@Test
void getHighestMarginProducts_WithValidProductCatalogue_ReturnsProductPair() {
// Arrange
ProductCatalogue productCatalogue = new ProductCatalogue("TestCatalogue", "desc");
Product product1 = new Product();
product1.setCurrentPrice(100.0);
Product product2 = new Product();
product2.setCurrentPrice(200.0);
productCatalogue.addProduct(product1);
productCatalogue.addProduct(product2);
// Act
ProductPair productPair = OfferIdentifier.getHighestMarginProducts(productCatalogue);
// Assert
assertNotNull(productPair);
assertNotNull(productPair.getProduct1());
assertNotNull(productPair.getProduct2());
assertNotEquals(productPair.getProduct1(), productPair.getProduct2());
}
@Test
void getHighestMarginProducts_SecondProductCheaper() {
// Arrange
ProductCatalogue productCatalogue = new ProductCatalogue("TestCatalogue", "desc");
Product product1 = new Product();
product1.setCurrentPrice(200.0);
Product product2 = new Product();
product2.setCurrentPrice(100.0);
productCatalogue.addProduct(product1);
productCatalogue.addProduct(product2);
// Act
ProductPair productPair = OfferIdentifier.getHighestMarginProducts(productCatalogue);
// Assert
assertNotNull(productPair);
assertNotNull(productPair.getProduct1());
assertNotNull(productPair.getProduct2());
assertNotEquals(productPair.getProduct1(), productPair.getProduct2());
}
@Test
void getHighestMarginProducts_WithCatalogueSizeLessThan2_ThrowsInvalidCatalogueException() {
// Arrange
ProductCatalogue productCatalogue = new ProductCatalogue("SmallCatalogue", "desc");
// Act and Assert
assertThrows(InvalidCatalogueException.class, () -> {
OfferIdentifier.getHighestMarginProducts(productCatalogue);
});
}
@Test
void getHighestMarginProducts_WithZeroPriceMargin_ThrowsInvalidOfferException() {
// Arrange
ProductCatalogue productCatalogue = new ProductCatalogue("name", "desc");
Product product1 = new Product();
product1.setCurrentPrice(200.0);
Product product2 = new Product();
product2.setCurrentPrice(200.0);
productCatalogue.getProducts().add(product1);
productCatalogue.getProducts().add(product2);
// Act and Assert
InvalidOfferException e = assertThrows(InvalidOfferException.class, () -> {
OfferIdentifier.getHighestMarginProducts(productCatalogue);
});
assertEquals("Price margin is zero!", e.getMessage());
}
}

View File

@@ -0,0 +1,105 @@
package de.rwu.easydrop.service.processing;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
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.client.AmazonPurchaser;
import de.rwu.easydrop.api.client.AmazonSeller;
import de.rwu.easydrop.api.client.EbayPurchaser;
import de.rwu.easydrop.api.client.EbaySeller;
import de.rwu.easydrop.api.client.PurchaserFactory;
import de.rwu.easydrop.api.client.SellerFactory;
import de.rwu.easydrop.data.connector.OfferPersistenceInterface;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.service.writer.OfferWriter;
class OfferProvisionerTest {
@Mock
private OfferPersistenceInterface mockPersistence;
@Mock
private SellerFactory mockSellerFactory;
@Mock
private PurchaserFactory mockPurchaserFactory;
@Mock
private OfferWriter mockOfferWriter;
@Mock
private AmazonSeller mockAmazonSeller;
@Mock
private EbaySeller mockEbaySeller;
@Mock
private AmazonPurchaser mockAmazonPurchaser;
@Mock
private EbayPurchaser mockEbayPurchaser;
private OfferProvisioner offerProvisioner;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
when(mockSellerFactory.createAmazonSeller()).thenReturn(mockAmazonSeller);
when(mockSellerFactory.createEbaySeller()).thenReturn(mockEbaySeller);
when(mockPurchaserFactory.createAmazonPurchaser()).thenReturn(mockAmazonPurchaser);
when(mockPurchaserFactory.createEbayPurchaser()).thenReturn(mockEbayPurchaser);
offerProvisioner = new OfferProvisioner(mockPersistence, mockSellerFactory, mockPurchaserFactory);
offerProvisioner.setOfferWriter(mockOfferWriter);
}
@Test
void runProvisioner_WithValidOffers_PlacesOrders() {
// Arrange
List<Offer> offers = new ArrayList<>();
Offer offer1 = createOffer(Webshop.AMAZON, "ASIN123", Webshop.EBAY, "12345");
Offer offer2 = createOffer(Webshop.EBAY, "54321", Webshop.AMAZON, "ASIN456");
offers.add(offer1);
offers.add(offer2);
// Act
offerProvisioner.runProvisioner(offers);
// Assert
verify(mockAmazonSeller, times(1)).sellProduct(any());
verify(mockEbaySeller, times(1)).sellProduct(any());
verify(mockAmazonPurchaser, times(1)).purchaseProduct(any());
verify(mockEbayPurchaser, times(1)).purchaseProduct(any());
verify(mockOfferWriter, times(2)).writeOfferToPersistence(any());
}
private Offer createOffer(Webshop sourceWebshop, String sourceProductId, Webshop targetWebshop,
String targetProductId) {
Product sourceProduct = new Product();
sourceProduct.setDataOrigin(sourceWebshop);
sourceProduct.setProductId(sourceProductId);
Product targetProduct = new Product();
targetProduct.setDataOrigin(targetWebshop);
targetProduct.setProductId(targetProductId);
Offer offer = new Offer();
offer.setSourceProduct(sourceProduct);
offer.setTargetProduct(targetProduct);
return offer;
}
}

View File

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

View File

@@ -0,0 +1,50 @@
package de.rwu.easydrop.service.processing;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import de.rwu.easydrop.api.dto.TransactionDTO;
import de.rwu.easydrop.data.connector.TransactionPersistenceInterface;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.Transaction;
import de.rwu.easydrop.service.mapping.TransactionMapper;
class TransactionHandlerTest {
@Mock
private TransactionPersistenceInterface mockPersistence;
public TransactionHandlerTest() {
MockitoAnnotations.openMocks(this);
}
@Test
void turnOfferToTransaction_ValidOffer_TransactionCreatedAndWrittenToPersistence() {
// Arrange
Offer offer = new Offer();
Product product = new Product();
offer.setSourceProduct(product);
offer.setTargetProduct(product);
TransactionHandler handler = new TransactionHandler(mockPersistence);
// Act
handler.turnOfferToTransaction(offer);
// Assert
ArgumentCaptor<TransactionDTO> transactionDtoCaptor = ArgumentCaptor.forClass(TransactionDTO.class);
verify(mockPersistence, times(1)).writeTX(transactionDtoCaptor.capture());
TransactionDTO capturedTransactionDto = transactionDtoCaptor.getValue();
Transaction capturedTransaction = TransactionMapper.mapTXFromDTO(capturedTransactionDto);
assertEquals(offer.getOfferId(), capturedTransaction.getOfferId());
}
}

View File

@@ -1,7 +1,6 @@
package de.rwu.easydrop.service.retriever; package de.rwu.easydrop.service.retriever;
import static org.junit.jupiter.api.Assertions.assertEquals; 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.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -14,9 +13,9 @@ import javax.naming.ConfigurationException;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import de.rwu.easydrop.exception.InvalidProductException;
import de.rwu.easydrop.model.Product; import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue; import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.util.ProductsConfig; import de.rwu.easydrop.util.ProductsConfig;
class CatalogueRetrieverTest { class CatalogueRetrieverTest {
@@ -39,20 +38,20 @@ class CatalogueRetrieverTest {
// Create a sample product catalogue with two products // Create a sample product catalogue with two products
ProductCatalogue productCatalogue = new ProductCatalogue("Catalogue 1", "Sample catalogue"); ProductCatalogue productCatalogue = new ProductCatalogue("Catalogue 1", "Sample catalogue");
Product product1 = new Product(); Product product1 = new Product();
product1.setDataOrigin("Amazon"); product1.setDataOrigin(Webshop.AMAZON);
product1.setProductId("ASIN1"); product1.setProductId("ASIN1");
productCatalogue.addProduct(product1); productCatalogue.addProduct(product1);
Product product2 = new Product(); Product product2 = new Product();
product2.setDataOrigin("eBay"); product2.setDataOrigin(Webshop.EBAY);
product2.setProductId("ProductID2"); product2.setProductId("ProductID2");
productCatalogue.addProduct(product2); productCatalogue.addProduct(product2);
productCatalogues.add(productCatalogue); productCatalogues.add(productCatalogue);
// Mock the methods // Mock the methods
when(productsConfig.getProductCatalogues()).thenReturn(productCatalogues); when(productsConfig.getProductCatalogues()).thenReturn(productCatalogues);
when(productRetriever.getProductFromAmazon("ASIN1")).thenReturn(product1); when(productRetriever.getProductFromWebshop(Webshop.AMAZON, "ASIN1")).thenReturn(product1);
when(productRetriever.getProductFromEbay("ProductID2")).thenReturn(product2); when(productRetriever.getProductFromWebshop(Webshop.EBAY, "ProductID2")).thenReturn(product2);
// Act // Act
catalogueRetriever.loadCatalogues(); catalogueRetriever.loadCatalogues();
@@ -71,33 +70,7 @@ class CatalogueRetrieverTest {
// Verify the method invocations // Verify the method invocations
verify(productsConfig).loadConfig(); verify(productsConfig).loadConfig();
verify(productRetriever).getProductFromAmazon("ASIN1"); verify(productRetriever).getProductFromWebshop(Webshop.AMAZON, "ASIN1");
verify(productRetriever).getProductFromEbay("ProductID2"); verify(productRetriever).getProductFromWebshop(Webshop.EBAY, "ProductID2");
}
@Test
void loadCatalogues_ValidConfig_CataloguesLoaded_InvalidProduct() throws ConfigurationException {
// Arrange
List<ProductCatalogue> 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());
} }
} }

View File

@@ -0,0 +1,82 @@
package de.rwu.easydrop.service.retriever;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import javax.naming.ConfigurationException;
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.client.AmazonProductDataSource;
import de.rwu.easydrop.api.client.DataSourceFactory;
import de.rwu.easydrop.api.client.EbayItemDataSource;
import de.rwu.easydrop.api.dto.OfferDTO;
import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.data.connector.OfferPersistenceInterface;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.util.Config;
class OfferRetrieverTest {
@Mock
private Config config;
@Mock
private DataSourceFactory dataSourceFactory;
@Mock
private AmazonProductDataSource amazonDataSource;
@Mock
private EbayItemDataSource ebayDataSource;
@Mock
private OfferDTO offerDTO;
@Mock
private Offer offer;
@Mock
private OfferPersistenceInterface persistence;
private OfferRetriever offerRetriever;
@BeforeEach
void setUp() throws ConfigurationException {
MockitoAnnotations.openMocks(this);
when(config.getProperty("AMAZON_API_URL")).thenReturn("https://api.amazon.com");
when(config.getProperty("AMAZON_API_KEY")).thenReturn("amazon-api-key");
dataSourceFactory.setConfig(config);
offerRetriever = new OfferRetriever(persistence);
}
@Test
void getOfferFromPersistence(){
//Mock ProductDTO object to return from the offerDTO mock
ProductDTO productDTO = mock(ProductDTO.class);
when(productDTO.isAvailable()).thenReturn(true);
//Arrange
String offerId = "187";
String lastUpdate = "2023-01-01";
when(persistence.getOfferDTOById(offerId)).thenReturn(offerDTO);
when(offerDTO.getOfferId()).thenReturn(offerId);
when(offerDTO.getLastUpdate()).thenReturn(lastUpdate);
when(offerDTO.getSourceProduct()).thenReturn(productDTO);
when(offerDTO.getTargetProduct()).thenReturn(productDTO);
//Act
Offer result = offerRetriever.getOfferFromPersistence(offerId);
//Assert
assertEquals(offerId, result.getOfferId());
assertEquals(lastUpdate, result.getLastUpdate());
//Verify
verify(persistence, times(1)).getOfferDTOById(offerId);
}
}

View File

@@ -2,6 +2,8 @@ package de.rwu.easydrop.service.retriever;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -17,8 +19,9 @@ import de.rwu.easydrop.api.client.AmazonProductDataSource;
import de.rwu.easydrop.api.client.DataSourceFactory; import de.rwu.easydrop.api.client.DataSourceFactory;
import de.rwu.easydrop.api.client.EbayItemDataSource; import de.rwu.easydrop.api.client.EbayItemDataSource;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.data.connector.AbstractProductPersistence; import de.rwu.easydrop.data.connector.ProductPersistenceInterface;
import de.rwu.easydrop.model.Product; import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.Webshop;
import de.rwu.easydrop.util.Config; import de.rwu.easydrop.util.Config;
class ProductRetrieverTest { class ProductRetrieverTest {
@@ -35,7 +38,7 @@ class ProductRetrieverTest {
@Mock @Mock
private Product product; private Product product;
@Mock @Mock
private AbstractProductPersistence persistence; private ProductPersistenceInterface persistence;
private ProductRetriever productRetriever; private ProductRetriever productRetriever;
@@ -45,7 +48,7 @@ class ProductRetrieverTest {
when(config.getProperty("AMAZON_API_URL")).thenReturn("https://api.amazon.com"); when(config.getProperty("AMAZON_API_URL")).thenReturn("https://api.amazon.com");
when(config.getProperty("AMAZON_API_KEY")).thenReturn("amazon-api-key"); when(config.getProperty("AMAZON_API_KEY")).thenReturn("amazon-api-key");
dataSourceFactory.setConfig(config); dataSourceFactory.setConfig(config);
productRetriever = new ProductRetriever(dataSourceFactory); productRetriever = new ProductRetriever(dataSourceFactory, persistence);
} }
@Test @Test
@@ -56,7 +59,7 @@ class ProductRetrieverTest {
when(amazonDataSource.getProductDTOById(asin)).thenReturn(productDTO); when(amazonDataSource.getProductDTOById(asin)).thenReturn(productDTO);
when(productDTO.getProductId()).thenReturn(asin); when(productDTO.getProductId()).thenReturn(asin);
when(productDTO.getCurrentPrice()).thenReturn(9.99); when(productDTO.getCurrentPrice()).thenReturn(9.99);
when(productDTO.getDataOrigin()).thenReturn("Amazon"); when(productDTO.getDataOrigin()).thenReturn(Webshop.AMAZON);
// Act // Act
Product result = productRetriever.getProductFromAmazon(asin); Product result = productRetriever.getProductFromAmazon(asin);
@@ -76,7 +79,7 @@ class ProductRetrieverTest {
when(ebayDataSource.getProductDTOById(productQuery)).thenReturn(productDTO); when(ebayDataSource.getProductDTOById(productQuery)).thenReturn(productDTO);
when(productDTO.getProductId()).thenReturn(productQuery); when(productDTO.getProductId()).thenReturn(productQuery);
when(productDTO.getCurrentPrice()).thenReturn(9.99); when(productDTO.getCurrentPrice()).thenReturn(9.99);
when(productDTO.getDataOrigin()).thenReturn("eBay"); when(productDTO.getDataOrigin()).thenReturn(Webshop.EBAY);
// Act // Act
Product result = productRetriever.getProductFromEbay(productQuery); Product result = productRetriever.getProductFromEbay(productQuery);
@@ -92,11 +95,10 @@ class ProductRetrieverTest {
void getProductFromPersistence_ValidProductId_ReturnsProduct() { void getProductFromPersistence_ValidProductId_ReturnsProduct() {
// Arrange // Arrange
String productId = "123"; String productId = "123";
when(dataSourceFactory.createProductPersistenceDataSource()).thenReturn(persistence);
when(persistence.getProductDTOById(productId)).thenReturn(productDTO); when(persistence.getProductDTOById(productId)).thenReturn(productDTO);
when(productDTO.getProductId()).thenReturn(productId); when(productDTO.getProductId()).thenReturn(productId);
when(productDTO.getCurrentPrice()).thenReturn(9.99); when(productDTO.getCurrentPrice()).thenReturn(9.99);
when(productDTO.getDataOrigin()).thenReturn("Amazon"); when(productDTO.getDataOrigin()).thenReturn(Webshop.AMAZON);
// Act // Act
Product result = productRetriever.getProductFromPersistence(productId); Product result = productRetriever.getProductFromPersistence(productId);
@@ -108,4 +110,49 @@ class ProductRetrieverTest {
// Verify the interactions // Verify the interactions
verify(persistence, times(1)).getProductDTOById(productId); verify(persistence, times(1)).getProductDTOById(productId);
} }
@Test
void getProductFromWebshop_ValidAmazonWebshop_ReturnsProduct() {
// Arrange
String productIdentifier = "ASIN1";
ProductDTO dto = new ProductDTO(productIdentifier, Webshop.AMAZON);
dto.setCurrentPrice(1.23);
AmazonProductDataSource dataSource = mock(AmazonProductDataSource.class);
Product expectedProduct = new Product();
expectedProduct.setProductId(productIdentifier);
expectedProduct.setDataOrigin(Webshop.AMAZON);
expectedProduct.setCurrentPrice(1.23);
when(dataSource.getProductDTOById(productIdentifier)).thenReturn(dto);
when(dataSourceFactory.createAmazonProductDataSource()).thenReturn(dataSource);
// Act
Product actualProduct = productRetriever.getProductFromWebshop(Webshop.AMAZON, productIdentifier);
// Assert
assertEquals(expectedProduct, actualProduct);
verify(dataSourceFactory).createAmazonProductDataSource();
verify(dataSource).getProductDTOById(productIdentifier);
}
@Test
void getProductFromWebshop_ValidEbayWebshop_ReturnsProduct() {
// Arrange
String productIdentifier = "ASIN1";
ProductDTO dto = new ProductDTO(productIdentifier, Webshop.EBAY);
dto.setCurrentPrice(1.23);
Product expectedProduct = new Product();
expectedProduct.setProductId(productIdentifier);
expectedProduct.setDataOrigin(Webshop.EBAY);
expectedProduct.setCurrentPrice(1.23);
when(ebayDataSource.getProductDTOById(productIdentifier)).thenReturn(dto);
when(dataSourceFactory.createEbayItemDataSource()).thenReturn(ebayDataSource);
// Act
Product actualProduct = productRetriever.getProductFromWebshop(Webshop.EBAY, productIdentifier);
// Assert
assertEquals(expectedProduct, actualProduct);
verify(dataSourceFactory).createEbayItemDataSource();
verify(ebayDataSource).getProductDTOById(productIdentifier);
}
} }

View File

@@ -0,0 +1,86 @@
package de.rwu.easydrop.service.validation;
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.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import de.rwu.easydrop.exception.InvalidOfferException;
import de.rwu.easydrop.model.Offer;
class OfferValidatorTest {
@Test
void testConstructorIsPrivate()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// Check for private constructor
Constructor<OfferValidator> constructor = OfferValidator.class.getDeclaredConstructor();
assertTrue(Modifier.isPrivate(constructor.getModifiers()));
// Make sure exception is thrown when instantiating
constructor.setAccessible(true);
assertThrows(InvocationTargetException.class, () -> {
constructor.newInstance();
});
}
@Test
void validate_Offer_ValidProduct_NoExceptionThrown() {
// Arrange
Offer offer = new Offer();
offer.setOfferId("3672");
offer.setLastUpdate("2021-02-03");
// Act and Assert
assertDoesNotThrow(() -> OfferValidator.validate(offer));
}
@Test
void validate_Offer_NullPointer() {
// Arrange
Offer offer = new Offer();
InvalidOfferException e = assertThrows(InvalidOfferException.class, () -> {
OfferValidator.validate(offer);
});
assertEquals("Required information is missing in the offer", e.getMessage());
}
@ParameterizedTest
@MethodSource("invalidOfferProvider")
void validate_InvalidOffer_ThrowsInvalidOfferException(Offer offer) {
// Act and Assert
assertThrows(InvalidOfferException.class, () -> OfferValidator.validate(offer));
}
static Stream<Offer> invalidOfferProvider() {
return Stream.of(
createOfferWithEmptylastUpdate(),
createOfferWithEmptyId());
}
private static Offer createOfferWithEmptylastUpdate() {
Offer offer = new Offer();
offer.setOfferId("3729798");
offer.setLastUpdate("");
return offer;
}
private static Offer createOfferWithEmptyId() {
Offer offer = new Offer();
offer.setOfferId("");
offer.setLastUpdate("8798476");
return offer;
}
}

View File

@@ -1,7 +1,6 @@
package de.rwu.easydrop.service.validation; package de.rwu.easydrop.service.validation;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -16,6 +15,7 @@ import org.junit.jupiter.params.provider.MethodSource;
import de.rwu.easydrop.exception.InvalidProductException; import de.rwu.easydrop.exception.InvalidProductException;
import de.rwu.easydrop.model.Product; import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.Webshop;
class ProductValidatorTest { class ProductValidatorTest {
@@ -38,37 +38,13 @@ class ProductValidatorTest {
// Arrange // Arrange
Product product = new Product(); Product product = new Product();
product.setCurrentPrice(9.99); product.setCurrentPrice(9.99);
product.setDataOrigin("Amazon"); product.setDataOrigin(Webshop.AMAZON);
product.setProductId("12345"); product.setProductId("12345");
// Act and Assert // Act and Assert
assertDoesNotThrow(() -> ProductValidator.validate(product)); assertDoesNotThrow(() -> ProductValidator.validate(product));
} }
@Test
void isInValidDataOrigins_ValidDataOrigin_ReturnsTrue() {
// Arrange
String dataOrigin = "Amazon";
// Act
boolean result = ProductValidator.isInValidDataOrigins(dataOrigin);
// Assert
assertTrue(result);
}
@Test
void isInValidDataOrigins_InvalidDataOrigin_ReturnsFalse() {
// Arrange
String dataOrigin = "UnknownOrigin";
// Act
boolean result = ProductValidator.isInValidDataOrigins(dataOrigin);
// Assert
assertFalse(result);
}
@ParameterizedTest @ParameterizedTest
@MethodSource("invalidProductProvider") @MethodSource("invalidProductProvider")
void validate_InvalidProduct_ThrowsInvalidProductException(Product product) { void validate_InvalidProduct_ThrowsInvalidProductException(Product product) {
@@ -79,22 +55,13 @@ class ProductValidatorTest {
static Stream<Product> invalidProductProvider() { static Stream<Product> invalidProductProvider() {
return Stream.of( return Stream.of(
createProductWithZeroPrice(), createProductWithZeroPrice(),
createProductWithUnknownDataOrigin(),
createProductWithEmptyProductId()); createProductWithEmptyProductId());
} }
private static Product createProductWithZeroPrice() { private static Product createProductWithZeroPrice() {
Product product = new Product(); Product product = new Product();
product.setCurrentPrice(0.00); product.setCurrentPrice(0.00);
product.setDataOrigin("Amazon"); product.setDataOrigin(Webshop.AMAZON);
product.setProductId("12345");
return product;
}
private static Product createProductWithUnknownDataOrigin() {
Product product = new Product();
product.setCurrentPrice(9.99);
product.setDataOrigin("UnknownOrigin");
product.setProductId("12345"); product.setProductId("12345");
return product; return product;
} }
@@ -102,7 +69,7 @@ class ProductValidatorTest {
private static Product createProductWithEmptyProductId() { private static Product createProductWithEmptyProductId() {
Product product = new Product(); Product product = new Product();
product.setCurrentPrice(9.99); product.setCurrentPrice(9.99);
product.setDataOrigin("Amazon"); product.setDataOrigin(Webshop.AMAZON);
product.setProductId(""); product.setProductId("");
return product; return product;
} }

View File

@@ -0,0 +1,70 @@
package de.rwu.easydrop.service.validation;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import org.junit.jupiter.api.Test;
import de.rwu.easydrop.exception.InvalidTransactionException;
import de.rwu.easydrop.model.Transaction;
class TransactionValidatorTest {
@Test
void testConstructorIsPrivate()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// Check for private constructor
Constructor<TransactionValidator> constructor = TransactionValidator.class
.getDeclaredConstructor();
assertTrue(Modifier.isPrivate(constructor.getModifiers()));
// Make sure exception is thrown when instantiating
constructor.setAccessible(true);
assertThrows(InvocationTargetException.class, () -> {
constructor.newInstance();
});
}
@Test
void validate_ValidTransaction_NoExceptionThrown() {
// Arrange
Transaction transaction = new Transaction();
transaction.setOfferId("123");
transaction.setVolume(123f);
transaction.setEarnings(12.23);
transaction.setTransactionTime("2020-01-01 00:00:00");
// Act & Assert
assertDoesNotThrow(() -> TransactionValidator.validate(transaction));
}
@Test
void validate_InvalidTransaction_InvalidTransactionExceptionThrown() {
// Arrange
Transaction tx1 = new Transaction();
tx1.setOfferId(""); // Set an empty Offer ID to make it invalid
Transaction tx2 = new Transaction();
tx2.setOfferId("123");
Transaction tx3 = new Transaction();
tx3.setOfferId("123");
tx3.setEarnings(123f);
// Act & Assert
assertThrows(InvalidTransactionException.class, () -> TransactionValidator.validate(tx1));
assertThrows(InvalidTransactionException.class, () -> TransactionValidator.validate(tx2));
assertThrows(InvalidTransactionException.class, () -> TransactionValidator.validate(tx3));
}
@Test
void validate_NullTransaction_InvalidTransactionExceptionThrown() {
// Arrange
Transaction transaction = null;
// Act & Assert
assertThrows(InvalidTransactionException.class, () -> TransactionValidator.validate(transaction));
}
}

View File

@@ -13,14 +13,15 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.data.connector.AbstractProductPersistence; import de.rwu.easydrop.data.connector.ProductPersistenceInterface;
import de.rwu.easydrop.model.Product; import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.ProductCatalogue; import de.rwu.easydrop.model.ProductCatalogue;
import de.rwu.easydrop.model.Webshop;
class CatalogueWriterTest { class CatalogueWriterTest {
@Mock @Mock
private AbstractProductPersistence persistenceMock; private ProductPersistenceInterface persistenceMock;
private CatalogueWriter catalogueWriter; private CatalogueWriter catalogueWriter;
@@ -39,19 +40,19 @@ class CatalogueWriterTest {
catalogueWriter.writeCatalogues(catalogues); catalogueWriter.writeCatalogues(catalogues);
// Assert // Assert
verify(persistenceMock, times(4)).saveProduct(any(ProductDTO.class)); verify(persistenceMock, times(4)).writeProduct(any(ProductDTO.class));
} }
private List<ProductCatalogue> createSampleCatalogues() { private List<ProductCatalogue> createSampleCatalogues() {
List<ProductCatalogue> catalogues = new ArrayList<>(); List<ProductCatalogue> catalogues = new ArrayList<>();
ProductCatalogue catalogue1 = new ProductCatalogue("Catalogue 1", "Sample catalogue 1"); ProductCatalogue catalogue1 = new ProductCatalogue("Catalogue 1", "Sample catalogue 1");
catalogue1.addProduct(createSampleProduct("Amazon", "ID 1")); catalogue1.addProduct(createSampleProduct(Webshop.AMAZON, "ID 1"));
catalogue1.addProduct(createSampleProduct("eBay", "ID 2")); catalogue1.addProduct(createSampleProduct(Webshop.EBAY, "ID 2"));
ProductCatalogue catalogue2 = new ProductCatalogue("Catalogue 2", "Sample catalogue 2"); ProductCatalogue catalogue2 = new ProductCatalogue("Catalogue 2", "Sample catalogue 2");
catalogue2.addProduct(createSampleProduct("Amazon", "ID 3")); catalogue2.addProduct(createSampleProduct(Webshop.AMAZON, "ID 3"));
catalogue2.addProduct(createSampleProduct("eBay", "ID 4")); catalogue2.addProduct(createSampleProduct(Webshop.EBAY, "ID 4"));
catalogues.add(catalogue1); catalogues.add(catalogue1);
catalogues.add(catalogue2); catalogues.add(catalogue2);
@@ -59,7 +60,7 @@ class CatalogueWriterTest {
return catalogues; return catalogues;
} }
private Product createSampleProduct(String dataOrigin, String productId) { private Product createSampleProduct(Webshop dataOrigin, String productId) {
Product product = new Product(); Product product = new Product();
product.setDataOrigin(dataOrigin); product.setDataOrigin(dataOrigin);
product.setProductId(productId); product.setProductId(productId);

View File

@@ -0,0 +1,78 @@
package de.rwu.easydrop.service.writer;
import static org.mockito.ArgumentMatchers.any;
import static org.junit.jupiter.api.Assertions.assertThrows;
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.OfferDTO;
import de.rwu.easydrop.data.connector.OfferPersistenceInterface;
import de.rwu.easydrop.model.Offer;
import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.Webshop;
class OfferWriterTest {
@Mock
private OfferDTO offerDTO;
@Mock
private OfferPersistenceInterface persistence;
private OfferWriter offerWriter;
@BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
offerWriter = new OfferWriter(persistence);
}
@Test
void writeOfferToPersistence_InvalidProduct_ThrowsException() {
//Arrange
Offer offer = new Offer();
offer.setOfferId("");
offer.setLastUpdate("");
//Act and Assert
assertThrows(Exception.class, () -> offerWriter.writeOfferToPersistence(offer));
}
@Test
void writeOfferToPresistence_ValidProduct_CallsSaveProduct(){
// Arrange
Product product = new Product();
product.setProductId("12345");
product.setDataOrigin(Webshop.AMAZON);
product.setCurrentPrice(9.99);
// Arrange
Offer offer = new Offer();
offer.setOfferId("26876");
offer.setLastUpdate("2022-12-25");
offer.setSourceProduct(product);
offer.setTargetProduct(product);
// Act
offerWriter.writeOfferToPersistence(offer);
// Assert
Mockito.verify(persistence).writeOffer(any(OfferDTO.class));
}
}

View File

@@ -10,21 +10,21 @@ import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import de.rwu.easydrop.api.dto.ProductDTO; import de.rwu.easydrop.api.dto.ProductDTO;
import de.rwu.easydrop.data.connector.AbstractProductPersistence; import de.rwu.easydrop.data.connector.ProductPersistenceInterface;
import de.rwu.easydrop.model.Product; import de.rwu.easydrop.model.Product;
import de.rwu.easydrop.model.Webshop;
class ProductWriterTest { class ProductWriterTest {
@Mock @Mock
private AbstractProductPersistence persistence; private ProductPersistenceInterface persistence;
private ProductWriter productWriter; private ProductWriter productWriter;
@BeforeEach @BeforeEach
public void setup() { public void setup() {
MockitoAnnotations.openMocks(this); MockitoAnnotations.openMocks(this);
productWriter = new ProductWriter(); productWriter = new ProductWriter(persistence);
productWriter.setPersistence(persistence);
} }
@Test @Test
@@ -32,14 +32,14 @@ class ProductWriterTest {
// Arrange // Arrange
Product product = new Product(); Product product = new Product();
product.setProductId("12345"); product.setProductId("12345");
product.setDataOrigin("Amazon"); product.setDataOrigin(Webshop.AMAZON);
product.setCurrentPrice(9.99); product.setCurrentPrice(9.99);
// Act // Act
productWriter.writeProductToPersistence(product); productWriter.writeProductToPersistence(product);
// Assert // Assert
Mockito.verify(persistence).saveProduct(any(ProductDTO.class)); Mockito.verify(persistence).writeProduct(any(ProductDTO.class));
} }
@Test @Test
@@ -47,7 +47,7 @@ class ProductWriterTest {
// Arrange // Arrange
Product product = new Product(); Product product = new Product();
product.setProductId(""); product.setProductId("");
product.setDataOrigin("Amazon"); product.setDataOrigin(Webshop.AMAZON);
// Act and Assert // Act and Assert
assertThrows(Exception.class, () -> productWriter.writeProductToPersistence(product)); assertThrows(Exception.class, () -> productWriter.writeProductToPersistence(product));

View File

@@ -0,0 +1,63 @@
package de.rwu.easydrop.service.writer;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
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.TransactionDTO;
import de.rwu.easydrop.data.connector.TransactionPersistenceInterface;
import de.rwu.easydrop.exception.InvalidTransactionException;
import de.rwu.easydrop.model.Transaction;
import de.rwu.easydrop.service.mapping.TransactionMapper;
class TransactionWriterTest {
@Mock
private TransactionPersistenceInterface persistence;
private TransactionWriter writer;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
writer = new TransactionWriter(persistence);
}
@Test
void writeTXToPersistence_ValidTransaction_TransactionSaved() {
// Arrange
Transaction transaction = new Transaction();
transaction.setOfferId("123");
transaction.setVolume(123f);
transaction.setEarnings(12.23);
transaction.setTransactionTime("2020-01-01 00:00:00");
TransactionDTO expectedDTO = TransactionMapper.mapTXToDTO(transaction);
// Act
writer.writeTXToPersistence(transaction);
// Assert
verify(persistence).writeTX(expectedDTO);
}
@Test
void writeTXToPersistence_InvalidTransaction_ValidationExceptionThrown() {
// Arrange
Transaction transaction = new Transaction();
transaction.setVolume(123f);
transaction.setEarnings(12.23);
transaction.setTransactionTime("2020-01-01 00:00:00");
// Act & Assert
assertThrows(InvalidTransactionException.class, () -> writer.writeTXToPersistence(transaction));
// Verify that persistence.writeTX is not called
verifyNoInteractions(persistence);
}
}

View File

@@ -1,5 +1,6 @@
package de.rwu.easydrop.util; package de.rwu.easydrop.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -55,4 +56,17 @@ class FormattingUtilTest {
Assertions.assertEquals(expectedFormattedAmount, formattedAmount); Assertions.assertEquals(expectedFormattedAmount, formattedAmount);
} }
@Test
void testRemoveSpaces_RemovesAllSpaces() {
String test1 = FormattingUtil.removeSpaces("this is a test");
String test2 = FormattingUtil.removeSpaces("another test");
String test3 = FormattingUtil.removeSpaces(" daring today, aren't we? ");
String test4 = FormattingUtil.removeSpaces("hehe");
assertEquals("thisisatest", test1);
assertEquals("anothertest", test2);
assertEquals("daringtoday,aren'twe?", test3);
assertEquals("hehe", test4);
}
} }

View File

@@ -5,10 +5,10 @@
description: "Integration Testing Product", description: "Integration Testing Product",
"identifiers": [ "identifiers": [
{ {
"Amazon": "DEMO-AMAZON-001" "AMAZON": "DEMO-AMAZON-001"
}, },
{ {
"eBay": "DEMO-EBAY-001" "EBAY": "DEMO-EBAY-001"
} }
] ]
] ]

View File

@@ -5,10 +5,10 @@
"description": "Integration Testing Product", "description": "Integration Testing Product",
"identifiers": [ "identifiers": [
{ {
"Amazon": "DEMO-AMAZON-001" "AMAZON": "DEMO-AMAZON-001"
}, },
{ {
"eBay": "DEMO-EBAY-001" "EBAY": "DEMO-EBAY-001"
} }
] ]
} }