Merge branch 'dev' into 'main'

Release 0.1.0

Closes #39

See merge request team1/sandbox2!2
This commit is contained in:
Marvin Scham
2023-05-25 11:26:57 +00:00
42 changed files with 1852 additions and 46 deletions

6
.gitignore vendored
View File

@@ -1,3 +1,9 @@
###################################################################################################
## Project Internal ###############################################################################
###################################################################################################
config.properties
###################################################################################################
## Visual Studio Code #############################################################################
###################################################################################################

View File

@@ -1,3 +1,10 @@
# Changelog
## Use me!
## 0.1.0
### Added
- (DataSource) → AmazonProductDataSource as first external data source (#39)
- ProductDTO as data holder structure for data from external sources (~ #39)
- Config for credential, base URLs etc. (~ #39)
- FormattingUtil for formatting price strings (~ #39)

View File

@@ -0,0 +1,544 @@
{
"/products/2020-08-26/products/{productId}/offers": {
"get": {
"produces": ["application/json"],
"parameters": [
{
"description": "The Amazon Standard Item Identifier (ASIN) for the product.",
"in": "path",
"name": "productId",
"required": true,
"type": "string"
},
{
"description": "The region where the customer wants to purchase the product.",
"enum": ["DE", "FR", "UK", "IT", "ES", "US", "CA", "JP"],
"x-docgen-enum-table-extension": [
{
"value": "DE",
"description": "Germany"
},
{
"value": "FR",
"description": "France"
},
{
"value": "UK",
"description": "United Kingdom"
},
{
"value": "IT",
"description": "Italy"
},
{
"value": "ES",
"description": "Spain"
},
{
"value": "US",
"description": "United States of America"
},
{
"value": "CA",
"description": "Canada"
},
{
"value": "JP",
"description": "Japan"
}
],
"in": "query",
"name": "productRegion",
"required": true,
"type": "string"
},
{
"description": "This field determines the region where to ship the product based on the value in the shippingPostalCode.",
"in": "query",
"name": "shippingRegion",
"required": false,
"type": "string"
},
{
"description": "The locale of the request, in the form of an IETF language tag. Each ProductRegion supports one or more locales. This value should comply with java.util.Locale.",
"pattern": "^[-_]+$",
"in": "query",
"name": "locale",
"required": true,
"type": "string"
},
{
"description": "The shipping postal or zip code for customer's request. This parameter doesn't hold geocode.",
"in": "query",
"name": "shippingPostalCode",
"required": false,
"type": "string"
},
{
"description": "The zero-based number of the page being requested. If not specified, a default value of 0 will be used. When passed, the value must be equal or greater than zero, and less than the number of pages returned in the response.",
"in": "query",
"name": "pageNumber",
"required": false,
"type": "integer"
},
{
"description": "The number of items desired for each page in the response. If not specified, a default value of 24 will be used. Maximum items that can be fetched in single request is 24.",
"in": "query",
"name": "pageSize",
"required": false,
"type": "integer"
},
{
"description": "A value that identifies the group within the business account that a customer belongs to. The customer can set this in Amazon Business account information. GroupTag is necessary only if the customer account belongs to more than one group.",
"in": "query",
"name": "groupTag",
"required": false,
"type": "string"
},
{
"collectionFormat": "csv",
"description": "A list of filter ids for use to query results.",
"in": "query",
"name": "filterIds",
"required": false,
"type": "array",
"items": {
"type": "string"
}
},
{
"description": "The number of units the customer intends to purchase. This helps Amazon Business determines quantity-based discounts if an eligible offer is present. Defaults to 1.",
"in": "query",
"name": "quantity",
"required": false,
"type": "integer",
"default": 1
},
{
"description": "The email address of the customer requesting this resource",
"in": "header",
"name": "x-amz-user-email",
"required": true,
"type": "string"
},
{
"collectionFormat": "csv",
"description": "A list specifying the offer fields for inclusion in the response object. Excluding this query parameter results in the response including all inclusions.",
"enum": [
"ALL",
"availability",
"buyingGuidance",
"buyingRestrictions",
"condition",
"fulfillmentType",
"merchant",
"price",
"productCondition",
"productConditionNote",
"quantityLimits",
"quantityPrice",
"taxExclusivePrice",
"deliveryInformation",
"listPrice",
"shippingOptions"
],
"in": "query",
"name": "inclusionsForOffers",
"required": false,
"type": "array",
"items": {
"type": "string"
},
"x-example": "/path?inclusionsForOffers=FOO,BAR"
}
],
"responses": {
"200": {
"description": "Search success",
"schema": {
"$ref": "#/definitions/OffersResult"
},
"headers": {
"x-amzn-RateLimit-Limit": {
"description": "The rate limit (requests per second) for this operation.",
"type": "string"
},
"x-amzn-requestid": {
"description": "A unique request reference identifier.",
"type": "string"
}
},
"x-amazon-spds-sandbox-behaviors": [
{
"request": {
"parameters": {
"productRegion": {
"value": "DE"
},
"locale": {
"value": "es_US"
},
"productId": {
"value": "B07VDJ5RVF"
},
"pageNumber": {
"value": "1"
},
"pageSize": {
"value": "12"
},
"filterIds": {
"value": ["NEW", "PRIME"]
}
}
},
"response": {
"offerCount": 2,
"numberOfPages": 1,
"featuredOffer": {
"availability": "[availability]",
"buyingGuidance": "NONE",
"deliveryInformation": "[deliveryInformation]",
"fulfillmentType": "AMAZON_FULFILLMENT",
"offerId": "[offerId]",
"merchant": {
"name": "[merchantName]",
"merchantId": "[merchantId]",
"meanFeedbackRating": 5.0,
"totalFeedbackCount": 1
},
"price": {
"value": {
"amount": 1.0,
"currencyCode": "USD"
},
"formattedPrice": "[formattedPrice]",
"priceType": "NEW"
},
"listPrice": {
"value": {
"amount": 2.0,
"currencyCode": "USD"
},
"formattedPrice": "[formattedPrice]",
"priceType": "NEW"
},
"taxExclusivePrice": {
"taxExclusiveAmount": {
"amount": 1.0,
"currencyCode": "USD"
},
"formattedPrice": "[formattedPrice]",
"displayString": "[taxExclusivePriceDisplayString]",
"label": "[taxExclusivePriceLabel]"
},
"quantityLimits": {
"minQuantity": 1,
"maxQuantity": 6
},
"quantityPrice": {
"quantityPriceTiers": [
{
"quantityDisplay": "string",
"unitPrice": {
"amount": 0,
"currencyCode": "USD"
},
"minQuantity": 0,
"price": {
"value": {
"amount": 0,
"currencyCode": "USD"
},
"formattedPrice": "[formattedPrice]",
"priceType": "NEW"
},
"savingMessage": "string",
"taxExclusivePrice": {
"taxExclusiveAmount": {
"amount": 0,
"currencyCode": "USD"
},
"displayString": "[taxExclusivePriceDisplayString]",
"formattedPrice": "[formattedPrice]",
"label": "[taxExclusivePriceLabel]"
}
}
]
},
"productCondition": "NEW",
"productConditionNote": "[productConditionNote]",
"condition": {
"conditionValue": "NEW",
"conditionNote": "string",
"subCondition": "ACCEPTABLE"
},
"buyingRestrictions": [],
"shippingOptions": [
{
"shippingCost": {
"value": {
"amount": 9.0,
"currencyCode": "USD"
}
},
"deliveryRange": {
"max": "2022-01-01T00:00:01.000Z",
"min": "2022-01-01T00:00:01.000Z"
}
}
]
},
"offers": [
{
"offerId": "[offerId]",
"merchant": {
"name": "[merchantName]",
"merchantId": "[merchantId]",
"meanFeedbackRating": 5.0,
"totalFeedbackCount": 1
},
"deliveryInformation": "[deliveryInformation]",
"price": {
"value": {
"amount": 1.0,
"currencyCode": "USD"
},
"formattedPrice": "[formattedPrice]",
"priceType": "NEW"
},
"listPrice": {
"value": {
"amount": 2.0,
"currencyCode": "USD"
},
"formattedPrice": "[formattedPrice]",
"priceType": "NEW"
},
"taxExclusivePrice": {
"taxExclusiveAmount": {
"amount": 1.0,
"currencyCode": "USD"
},
"formattedPrice": "[formattedPrice]",
"displayString": "[taxExclusivePriceDisplayString]",
"label": "[taxExclusivePriceLabel]"
},
"quantityLimits": {
"minQuantity": 1,
"maxQuantity": 6
},
"quantityPrice": {
"quantityPriceTiers": [
{
"quantityDisplay": "string",
"unitPrice": {
"amount": 0,
"currencyCode": "USD"
},
"minQuantity": 0,
"price": {
"value": {
"amount": 0,
"currencyCode": "USD"
},
"formattedPrice": "[formattedPrice]",
"priceType": "NEW"
},
"savingMessage": "string",
"taxExclusivePrice": {
"taxExclusiveAmount": {
"amount": 0,
"currencyCode": "USD"
},
"displayString": "[taxExclusivePriceDisplayString]",
"formattedPrice": "[formattedPrice]",
"label": "[taxExclusivePriceLabel]"
}
}
]
},
"productCondition": "NEW",
"productConditionNote": "[productConditionNote]",
"condition": {
"conditionValue": "NEW",
"conditionNote": "string",
"subCondition": "ACCEPTABLE"
},
"availability": "[availability]",
"fulfillmentType": "THIRD_PARTY",
"buyingGuidance": "NONE",
"buyingRestrictions": [],
"shippingOptions": [
{
"shippingCost": {
"value": {
"amount": 9.0,
"currencyCode": "USD"
}
},
"deliveryRange": {
"max": "2022-01-01T00:00:01.000Z",
"min": "2022-01-01T00:00:01.000Z"
}
}
]
}
],
"filterGroups": [
{
"displayName": "[displayName]",
"items": [
{
"displayName": "[itemDisplayName]",
"id": "[itemId]"
}
]
}
]
}
}
]
},
"400": {
"description": "The service was unable to process the request. Reasons for the error are described in an error response object.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
},
"headers": {
"x-amzn-RateLimit-Limit": {
"description": "The rate limit (requests per second) for this operation.",
"type": "string"
},
"x-amzn-requestid": {
"description": "A unique request reference identifier.",
"type": "string"
}
},
"x-amazon-spds-sandbox-behaviors": [
{
"request": {
"parameters": {
"productRegion": {
"value": "US"
},
"productId": {
"value": "B07VDJ5RVF"
}
}
},
"response": {
"errors": [
{
"code": "INVALID_REQUEST_PARAMETER",
"message": "Missing required request parameters: [locale]"
}
]
}
}
]
},
"401": {
"description": "The request's authorization header isn't formatted correctly or doesn't contain a valid authorization input.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
},
"headers": {
"x-amzn-RateLimit-Limit": {
"description": "The rate limit (requests per second) for this operation.",
"type": "string"
},
"x-amzn-requestid": {
"description": "A unique request reference identifier.",
"type": "string"
}
}
},
"403": {
"description": "Indicates that access to the resource is forbidden. Possible reasons include Access Denied, Unauthorized, Expired Token, or Invalid Signature.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
},
"headers": {
"x-amzn-RateLimit-Limit": {
"description": "The rate limit (requests per second) for this operation.",
"type": "string"
},
"x-amzn-requestid": {
"description": "A unique request reference identifier.",
"type": "string"
}
}
},
"404": {
"description": "The resource specified doesn't exist.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
},
"headers": {
"x-amzn-RateLimit-Limit": {
"description": "The rate limit (requests per second) for this operation.",
"type": "string"
},
"x-amzn-requestid": {
"description": "A unique request reference identifier.",
"type": "string"
}
}
},
"429": {
"description": "The frequency of requests was greater than allowed.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
},
"headers": {
"x-amzn-RateLimit-Limit": {
"description": "The rate limit (requests per second) for this operation.",
"type": "string"
},
"x-amzn-requestid": {
"description": "A unique request reference identifier.",
"type": "string"
}
}
},
"500": {
"description": "Encountered an unexpected condition which prevented the server from fulfilling the request.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
},
"headers": {
"x-amzn-RateLimit-Limit": {
"description": "The rate limit (requests per second) for this operation.",
"type": "string"
},
"x-amzn-requestid": {
"description": "A unique request reference identifier.",
"type": "string"
}
}
},
"503": {
"description": "Temporary overloading or maintenance of the server.",
"schema": {
"$ref": "#/definitions/ErrorResponse"
},
"headers": {
"x-amzn-RateLimit-Limit": {
"description": "The rate limit (requests per second) for this operation.",
"type": "string"
},
"x-amzn-requestid": {
"description": "A unique request reference identifier.",
"type": "string"
}
}
}
},
"tags": ["search"],
"description": "Search for offers of a specific product.",
"operationId": "searchOffersRequest",
"summary": "Paginated and filtered search for offers of a specific product."
}
}
}

View File

@@ -0,0 +1,200 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Modified version of sun coding conventions
Checkstyle configuration that checks the sun coding conventions from:
- the Java Language Specification at
https://docs.oracle.com/javase/specs/jls/se11/html/index.html
- the Sun Code Conventions at
https://www.oracle.com/java/technologies/javase/codeconventions-contents.html
- the Javadoc guidelines at
https://www.oracle.com/technical-resources/articles/java/javadoc-tool.html
- the JDK Api documentation https://docs.oracle.com/en/java/javase/11/
- some best practices
Checkstyle is very configurable. Be sure to read the documentation at
https://checkstyle.org (or in your downloaded distribution).
Most Checks are configurable, be sure to consult the documentation.
To completely disable a check, just comment it out or delete it from the file.
To suppress certain violations please review suppression filters.
Finally, it is worth reading the documentation.
-->
<module name="Checker">
<!--
If you set the basedir property below, then all reported file
names will be relative to the specified directory. See
https://checkstyle.org/config.html#Checker
<property name="basedir" value="${basedir}"/>
-->
<property name="severity" value="error" />
<property name="fileExtensions" value="java, properties, xml" />
<!-- Excludes all 'module-info.java' files -->
<!-- See https://checkstyle.org/config_filefilters.html -->
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value="module\-info\.java$" />
</module>
<!-- https://checkstyle.org/config_filters.html#SuppressionFilter -->
<module name="SuppressionFilter">
<property name="file" value="${org.checkstyle.sun.suppressionfilter.config}"
default="checkstyle-suppressions.xml" />
<property name="optional" value="true" />
</module>
<!-- Checks that a package-info.java file exists for each package. -->
<!-- See https://checkstyle.org/config_javadoc.html#JavadocPackage -->
<module name="JavadocPackage" />
<!-- Checks whether files end with a new line. -->
<!-- See https://checkstyle.org/config_misc.html#NewlineAtEndOfFile -->
<module name="NewlineAtEndOfFile" />
<!-- Checks that property files contain the same keys. -->
<!-- See https://checkstyle.org/config_misc.html#Translation -->
<module name="Translation" />
<!-- Checks for Size Violations. -->
<!-- See https://checkstyle.org/config_sizes.html -->
<module name="FileLength" />
<module name="LineLength">
<property name="max" value="100" />
<property name="fileExtensions" value="java" />
</module>
<!-- Checks for whitespace -->
<!-- See https://checkstyle.org/config_whitespace.html -->
<module name="FileTabCharacter" />
<!-- Miscellaneous other checks. -->
<!-- See https://checkstyle.org/config_misc.html -->
<module name="RegexpSingleline">
<property name="format" value="\s+$" />
<property name="minimum" value="0" />
<property name="maximum" value="0" />
<property name="message" value="Line has trailing spaces." />
</module>
<!-- Checks for Headers -->
<!-- See https://checkstyle.org/config_header.html -->
<!-- <module name="Header"> -->
<!-- <property name="headerFile" value="${checkstyle.header.file}"/> -->
<!-- <property name="fileExtensions" value="java"/> -->
<!-- </module> -->
<module name="TreeWalker">
<!-- Checks for Javadoc comments. -->
<!-- See https://checkstyle.org/config_javadoc.html -->
<module name="InvalidJavadocPosition" />
<module name="JavadocMethod" />
<module name="JavadocType" />
<module name="JavadocVariable" />
<module name="JavadocStyle" />
<module name="MissingJavadocMethod" />
<!-- Checks for Naming Conventions. -->
<!-- See https://checkstyle.org/config_naming.html -->
<module name="ConstantName" />
<module name="LocalFinalVariableName" />
<module name="LocalVariableName" />
<module name="MemberName" />
<module name="MethodName" />
<module name="PackageName" />
<module name="ParameterName" />
<module name="StaticVariableName" />
<module name="TypeName" />
<!-- Checks for imports -->
<!-- See https://checkstyle.org/config_imports.html -->
<module name="AvoidStarImport" />
<module name="IllegalImport" /> <!-- defaults to sun.* packages -->
<module name="RedundantImport" />
<module name="UnusedImports">
<property name="processJavadoc" value="false" />
</module>
<!-- Checks for Size Violations. -->
<!-- See https://checkstyle.org/config_sizes.html -->
<module name="MethodLength" />
<module name="ParameterNumber" />
<!-- Checks for whitespace -->
<!-- See https://checkstyle.org/config_whitespace.html -->
<module name="EmptyForIteratorPad" />
<module name="GenericWhitespace" />
<module name="MethodParamPad" />
<module name="NoWhitespaceAfter" />
<module name="NoWhitespaceBefore" />
<module name="OperatorWrap" />
<module name="ParenPad" />
<module name="TypecastParenPad" />
<module name="WhitespaceAfter" />
<module name="WhitespaceAround" />
<!-- Modifier Checks -->
<!-- See https://checkstyle.org/config_modifier.html -->
<module name="ModifierOrder" />
<module name="RedundantModifier" />
<!-- Checks for blocks. You know, those {}'s -->
<!-- See https://checkstyle.org/config_blocks.html -->
<module name="AvoidNestedBlocks" />
<module name="EmptyBlock" />
<module name="LeftCurly" />
<module name="NeedBraces" />
<module name="RightCurly" />
<!-- Checks for common coding problems -->
<!-- See https://checkstyle.org/config_coding.html -->
<module name="EmptyStatement" />
<module name="EqualsHashCode" />
<module name="HiddenField" />
<module name="IllegalInstantiation" />
<module name="InnerAssignment" />
<module name="MagicNumber" />
<module name="MissingSwitchDefault" />
<module name="MultipleVariableDeclarations" />
<module name="SimplifyBooleanExpression" />
<module name="SimplifyBooleanReturn" />
<!-- Checks for class design -->
<!-- See https://checkstyle.org/config_design.html -->
<module name="DesignForExtension" />
<module name="FinalClass" />
<module name="HideUtilityClassConstructor" />
<module name="InterfaceIsType" />
<module name="VisibilityModifier" />
<!-- Miscellaneous other checks. -->
<!-- See https://checkstyle.org/config_misc.html -->
<module name="ArrayTypeStyle" />
<module name="FinalParameters" />
<module name="TodoComment" />
<module name="UpperEll" />
<!-- https://checkstyle.org/config_filters.html#SuppressionXpathFilter -->
<module name="SuppressionXpathFilter">
<property name="file" value="${org.checkstyle.sun.suppressionxpathfilter.config}"
default="checkstyle-xpath-suppressions.xml" />
<property name="optional" value="true" />
</module>
</module>
</module>

View File

@@ -0,0 +1,3 @@
# Amazon Credentials
AMAZON_API_URL=
AMAZON_API_KEY=

12
config/logback.xml Normal file
View File

@@ -0,0 +1,12 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>

40
pom.xml
View File

@@ -5,27 +5,58 @@
<groupId>de.rwu</groupId>
<artifactId>easydrop</artifactId>
<packaging>jar</packaging>
<version>0.1-SNAPSHOT</version>
<version>0.1.0</version>
<name>EasyDrop</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20230227</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.7</version>
</dependency>
</dependencies>
<build>
@@ -39,6 +70,7 @@
<id>checkstyle-check</id>
<phase>verify</phase>
<configuration>
<configLocation>config/custom-checkstyle.xml</configLocation>
<failOnViolation>true</failOnViolation>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>

View File

@@ -0,0 +1,49 @@
package de.rwu.easydrop;
import javax.naming.ConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.rwu.easydrop.api.client.AmazonProductDataSource;
import de.rwu.easydrop.util.Config;
/**
* Kickoff point for the service.
*
* @since 0.1.0
*/
public final class Main {
/**
* Logger for main process.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
/**
* Prevents unwanted instantiation.
*/
private Main() throws IllegalAccessException {
throw new IllegalAccessException("Don't instantiate me! >:(");
}
/**
* Application entrypoint.
*
* @param args
*/
public static void main(final String[] args) throws ConfigurationException {
Config config = Config.getInstance();
config.loadConfig();
String amznBaseUrl = config.getProperty("AMAZON_API_URL");
String amznApiKey = config.getProperty("AMAZON_API_KEY");
String testProduct = null;
AmazonProductDataSource amznSrc = new AmazonProductDataSource(amznBaseUrl, amznApiKey);
try {
testProduct = amznSrc.getProductDTOById("B096Y2TYKV").toString();
LOGGER.info(testProduct);
} catch (IllegalArgumentException e) {
LOGGER.error("Something went wrong :(", e);
}
}
}

View File

@@ -0,0 +1,125 @@
package de.rwu.easydrop.api.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.ReadContext;
import de.rwu.easydrop.api.dto.ProductDTO;
/**
* Interface to an Amazon data source.
*
* @since 0.1.0
*/
public final class AmazonProductDataSource implements DataSource {
/**
* Name of this data source.
*/
private static final String DATA_ORIGIN = "Amazon";
/**
* Base URL to the Amazon data source.
*/
private String baseUrl;
/**
* Credential key to authorize acccess.
*/
private String apiKey;
/**
* Product region parameter required for data access.
*/
private static final String PRODUCT_REGION = "DE";
/**
* Locale parameter required for data access.
*/
private static final String LOCALE = "de_DE";
/**
* Sets up instance with Base URL and API Key.
*
* @param newBaseUrl
* @param newApiKey
*/
public AmazonProductDataSource(final String newBaseUrl, final String newApiKey) {
this.baseUrl = newBaseUrl;
this.apiKey = newApiKey;
}
@Override
public ProductDTO getProductDTOById(final String productId) throws IllegalArgumentException {
StringBuilder response = new StringBuilder();
ProductDTO product = new ProductDTO(productId, DATA_ORIGIN);
try {
URL apiUrl = createApiUrl(productId);
HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Credential", apiKey);
int responseCode = connection.getResponseCode();
BufferedReader reader;
if (responseCode == HttpURLConnection.HTTP_OK) {
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
} else {
throw new IllegalArgumentException(
"Nothing found: Amazon API responded with error code " + responseCode);
}
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
buildProductDTO(product, response.toString());
} catch (IOException e) {
throw new IllegalArgumentException("Couldn't fulfill Amazon API request");
}
return product;
}
/**
* Enriches a ProductDTO with API-gathered data.
*
* @param product Unfinished ProductDTO
* @param json Product data
* @return Finished ProductDTO
*/
public ProductDTO buildProductDTO(final ProductDTO product, final String json) {
String root = "$.featuredOffer.";
ReadContext ctx = JsonPath.parse(json);
try {
product.setDataOrigin(DATA_ORIGIN);
product.setAvailable(
ctx.read(root + "availability", String.class).equals("available"));
product.setCurrentPrice(ctx.read(root + "price.value.amount", double.class));
product.setDeliveryPrice(
ctx.read(root + "shippingOptions[0].shippingCost.value.amount", double.class));
product.setMerchant(ctx.read(root + "merchant.name", String.class));
} catch (PathNotFoundException e) {
// Pass, allow incomplete ProductDTO to pass for later validation
}
return product;
}
@Override
public URL createApiUrl(final String productId) throws MalformedURLException {
return new URL(baseUrl
+ "/products/2020-08-26/products/"
+ productId
+ "/offers?productRegion="
+ PRODUCT_REGION
+ "&locale="
+ LOCALE);
}
}

View File

@@ -0,0 +1,28 @@
package de.rwu.easydrop.api.client;
import java.net.MalformedURLException;
import java.net.URL;
import de.rwu.easydrop.api.dto.ProductDTO;
/**
* Universal interface to implement concrete APIs on.
*/
public interface DataSource {
/**
* Retrieves product info from the data source.
*
* @param productId ASIN
* @return ProductDTO
*/
ProductDTO getProductDTOById(String productId);
/**
* Creates an URL object to connect to the API with.
*
* @param productId ASIN
* @return URL object
* @throws MalformedURLException
*/
URL createApiUrl(String productId) throws MalformedURLException;
}

View File

@@ -0,0 +1,6 @@
/**
* API client connectors for interaction with external data sources.
*
* @since 0.1.0
*/
package de.rwu.easydrop.api.client;

View File

@@ -0,0 +1,139 @@
package de.rwu.easydrop.api.dto;
import de.rwu.easydrop.util.FormattingUtil;
/**
* Product data transfer object.
*
* @since 0.1.0
*/
public class ProductDTO {
/**
* Data source platform, like "Amazon".
*/
private String dataOrigin;
/**
* @return the dataOrigin
*/
public String getDataOrigin() {
return dataOrigin;
}
/**
* @param newDataOrigin the dataOrigin to set
*/
public void setDataOrigin(final String newDataOrigin) {
this.dataOrigin = newDataOrigin;
}
/**
* Platform internal product identifier.
*/
private String productId;
/**
* @return the productId
*/
public String getProductId() {
return productId;
}
/**
* Current product price per piece in Euro.
*/
private double currentPrice;
/**
* @return the currentPrice
*/
public double getCurrentPrice() {
return currentPrice;
}
/**
* @param newCurrentPrice the currentPrice to set
*/
public void setCurrentPrice(final double newCurrentPrice) {
this.currentPrice = newCurrentPrice;
}
/**
* Name of mercant offering the product on the platform.
*/
private String merchant;
/**
* @return the merchant
*/
public String getMerchant() {
return merchant;
}
/**
* @param newMerchant the merchant to set
*/
public void setMerchant(final String newMerchant) {
this.merchant = newMerchant;
}
/**
* Additional Cost for delivery in Euro.
*/
private double deliveryPrice;
/**
* @return the deliveryPrice
*/
public double getDeliveryPrice() {
return deliveryPrice;
}
/**
* @param newDeliveryPrice the deliveryPrice to set
*/
public void setDeliveryPrice(final double newDeliveryPrice) {
this.deliveryPrice = newDeliveryPrice;
}
/**
* Whether the product can be purchased at this point.
*/
private boolean available;
/**
* @return the available
*/
public boolean isAvailable() {
return available;
}
/**
* @param newAvailable the available to set
*/
public void setAvailable(final boolean newAvailable) {
this.available = newAvailable;
}
/**
* Creates ProductDTO instance.
*
* @param newProductId Interal Product indetifier
* @param newDataOrigin Data Origin
*/
public ProductDTO(final String newProductId, final String newDataOrigin) {
this.productId = newProductId;
this.dataOrigin = newDataOrigin;
}
@Override
public final String toString() {
return "ProductDTO{"
+ productId + " from "
+ merchant + " ("
+ dataOrigin + ")"
+ " at "
+ FormattingUtil.formatEuro(currentPrice) + " (available: "
+ (available ? "yes" : "no") + ")}";
}
}

View File

@@ -0,0 +1,6 @@
/**
* Data transfer objects for data derived from external sources.
*
* @since 0.1.0
*/
package de.rwu.easydrop.api.dto;

View File

@@ -0,0 +1,6 @@
/**
* Interaction with external APIs.
*
* @since 0.1.0
*/
package de.rwu.easydrop.api;

View File

@@ -0,0 +1,10 @@
package de.rwu.easydrop.data.connector;
/**
* Allows connecting to a SQLite Database.
*
* TODO implement
*/
public class DatabaseConnector {
}

View File

@@ -0,0 +1,6 @@
/**
* Connectors for databases.
*
* TODO implement
*/
package de.rwu.easydrop.data.connector;

View File

@@ -0,0 +1,10 @@
package de.rwu.easydrop.data.dao;
/**
* Product data access object.
*
* TODO implement
*/
public class ProductDAO {
}

View File

@@ -0,0 +1,6 @@
/**
* Data access objects for business objects created from persistence.
*
* TODO implement
*/
package de.rwu.easydrop.data.dao;

View File

@@ -0,0 +1,10 @@
package de.rwu.easydrop.data.model;
/**
* A Product.
*
* TODO implement
*/
public class Product {
}

View File

@@ -0,0 +1,6 @@
/**
* Business objects.
*
* TODO implement
*/
package de.rwu.easydrop.data.model;

View File

@@ -0,0 +1,6 @@
/**
* Structure for business objects and persisting their info.
*
* TODO implement
*/
package de.rwu.easydrop.data;

View File

@@ -1,16 +0,0 @@
package de.rwu.easydrop.demo;
/**
* Demo-Class.
*/
public final class DemoClass {
/**
* Demo-Method inverting a boolean.
*
* @param bool Input Boolean
* @return Inverted Boolean
*/
public boolean invertBool(final boolean bool) {
return !bool;
}
}

View File

@@ -1,6 +0,0 @@
/**
* Dieses Paket beinhaltet ist zu Demo-Zwecken.
*
* @since 0.1-SNAPSHOT
*/
package de.rwu.easydrop.demo;

View File

@@ -0,0 +1,6 @@
/**
* One of the Top 2 dropshipping platforms around.
*
* @since 0.1.0
*/
package de.rwu.easydrop;

View File

@@ -0,0 +1,14 @@
package de.rwu.easydrop.service.mapping;
/**
* Maps between Product, ProductDAO and ProductDTO.
*
* TODO implement
*
* @see Product
* @see ProductDTO
* @see ProductDAO
*/
public class ProductMapper {
}

View File

@@ -0,0 +1,6 @@
/**
* Maps different formats of corresponding objects.
*
* TODO implement
*/
package de.rwu.easydrop.service.mapping;

View File

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

View File

@@ -0,0 +1,10 @@
package de.rwu.easydrop.service.processing;
/**
* Processes dropshipping orders.
*
* TODO implement
*/
public class OrderManager {
}

View File

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

View File

@@ -0,0 +1,10 @@
package de.rwu.easydrop.service.validation;
/**
* Confirms validity of Product data.
*
* @since 0.1.0
*/
public class ProductValidator {
}

View File

@@ -0,0 +1,6 @@
/**
* Supports validation processes.
*
* TODO implement
*/
package de.rwu.easydrop.service.validation;

View File

@@ -0,0 +1,112 @@
package de.rwu.easydrop.util;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.Properties;
import javax.naming.ConfigurationException;
/**
* Allows access to the config.
*
* @since 0.1.0
*/
public final class Config {
/**
* Config file location.
*/
private String configLocation = "config/config.properties";
/**
* @return the configLocation
*/
public String getConfigLocation() {
return configLocation;
}
/**
* @param newConfigLocation the configLocation to set
*/
public void setConfigLocation(final String newConfigLocation) {
configLocation = newConfigLocation;
}
/**
* Holds the config values.
*/
private Properties properties = null;
/**
* Singleton instance.
*/
private static Config instance = null;
/**
* Private constructor to prevent external instantiation.
*/
private Config() {
// Do Nothing
}
/**
* Returns current config instance.
*
* @return Config instance
* @throws ConfigurationException
*/
public static Config getInstance() {
if (instance == null) {
return new Config();
}
return instance;
}
/**
* Loads config file values into the instance.
*
* @throws ConfigurationException
*/
public void loadConfig() throws ConfigurationException {
Properties newProps = new Properties();
try (FileInputStream input = new FileInputStream(configLocation)) {
newProps.load(input);
properties = newProps;
} catch (IOException e) {
throw new ConfigurationException("Couldn't load required config file");
}
}
/**
* Returns a config property by specified key.
*
* @param key Config Key, like "API_KEY"
* @return Config value
* @throws NoSuchElementException Required key missing
*/
public String getProperty(final String key) throws NoSuchElementException {
String value = null;
try {
value = properties.getProperty(key);
} catch (NullPointerException e) {
throw new NoSuchElementException("Config has not been loaded");
}
if (value == null) {
throw new NoSuchElementException("Requested config value does not exist");
}
return value;
}
/**
* Overrides a config property loaded from file for current instance.
*
* @param key Config Key
* @param value Property Value
*/
public void setProperty(final String key, final String value) {
properties.setProperty(key, value);
}
}

View File

@@ -0,0 +1,30 @@
package de.rwu.easydrop.util;
import java.util.Locale;
/**
* Helps format a bunch of things.
*
* @since 0.1.0
*/
public final class FormattingUtil {
/**
* Private constructor to prevent unwanted instantiation.
*
* @throws UnsupportedOperationException always
*/
private FormattingUtil() throws UnsupportedOperationException {
throw new UnsupportedOperationException("This is a utility class, don't instantiate it.");
}
/**
* Formats a price to the German Euro format.
*
* @param amount price
* @return formatted price
*/
public static String formatEuro(final double amount) {
return String.format(Locale.GERMAN, "%,.2f", amount) + "";
}
}

View File

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

View File

@@ -0,0 +1,189 @@
package de.rwu.easydrop.api.client;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import de.rwu.easydrop.api.dto.ProductDTO;
class AmazonProductDataSourceTest {
private AmazonProductDataSource demoDataSource;
private static String demoApiKey = "my-api-key";
private static String demoApiUrl = "https://www.example.com/api";
private static String demoDataOrigin = "Amazon";
private static String demoProductId = "whateverId";
@BeforeEach
void setup() {
demoDataSource = new AmazonProductDataSource(
demoApiUrl,
demoApiKey);
MockitoAnnotations.openMocks(this);
}
@Test
void testConstructor() {
// Assert
try {
Field baseUrlField = AmazonProductDataSource.class.getDeclaredField("baseUrl");
baseUrlField.setAccessible(true);
Assertions.assertEquals(demoApiUrl, baseUrlField.get(demoDataSource));
Field apiKeyField = AmazonProductDataSource.class.getDeclaredField("apiKey");
apiKeyField.setAccessible(true);
Assertions.assertEquals(demoApiKey, apiKeyField.get(demoDataSource));
} catch (NoSuchFieldException e) {
Assertions.fail();
} catch (IllegalAccessException e) {
Assertions.fail();
}
}
@Test
void testCreateApiUrl() throws MalformedURLException {
// Test case 1
String productId1 = "12345";
URL expectedUrl1 = new URL(
"https://www.example.com/api/products/2020-08-26/products/12345/offers?productRegion=DE&locale=de_DE");
URL createdUrl1 = demoDataSource.createApiUrl(productId1);
Assertions.assertEquals(expectedUrl1, createdUrl1);
// Test case 2
String productId2 = "67890";
URL expectedUrl2 = new URL(
"https://www.example.com/api/products/2020-08-26/"
+ "products/67890/offers?productRegion=DE&locale=de_DE");
URL createdUrl2 = demoDataSource.createApiUrl(productId2);
Assertions.assertEquals(expectedUrl2, createdUrl2);
}
@Test
void testBuildProductDTO_completeData() {
// Set up the test environment
String json = "{\"featuredOffer\": {\"availability\": \"available\"," +
"\"price\": {\"value\": {\"amount\": 10.0}}," +
"\"shippingOptions\": [{\"shippingCost\": {\"value\": {\"amount\": 2.5}}}], " +
"\"merchant\": {\"name\": \"Merchant A\"}}}";
ProductDTO product = new ProductDTO(demoProductId, demoDataOrigin);
// Invoke the method
ProductDTO result = demoDataSource.buildProductDTO(product, json);
// Verify the product DTO properties
assertEquals(demoDataOrigin, result.getDataOrigin());
assertEquals(true, result.isAvailable());
assertEquals(10.0, result.getCurrentPrice());
assertEquals(2.5, result.getDeliveryPrice());
assertEquals("Merchant A", result.getMerchant());
}
@Test
void testBuildProductDTO_incompleteData() {
// Set up the test environment
String json = "{\"otherOffer\": {\"availability\": \"available\"," +
"\"price\": {\"value\": {\"amount\": 10.0}}," +
"\"shippingOptions\": [{\"shippingCost\": {\"value\": {\"amount\": 2.5}}}], " +
"\"merchant\": {\"name\": \"Merchant A\"}}}";
ProductDTO product = new ProductDTO(demoProductId, demoDataOrigin);
// Invoke the method
ProductDTO result = demoDataSource.buildProductDTO(product, json);
// Verify that the product DTO remains unchanged
assertEquals(demoDataOrigin, result.getDataOrigin());
assertEquals(false, result.isAvailable());
assertEquals(0.0, result.getCurrentPrice());
assertEquals(0.0, result.getDeliveryPrice());
assertEquals(null, result.getMerchant());
}
@Test
void testGetProductDTOById_successfulRequest() throws IOException {
// Set up the test environment
String mockResponse = "{\"featuredOffer\": {\"availability\": \"available\"," +
"\"price\": {\"value\": {\"amount\": 10.0}}," +
"\"shippingOptions\": [{\"shippingCost\": {\"value\": {\"amount\": 2.5}}}], " +
"\"merchant\": {\"name\": \"Merchant A\"}}}";
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("Amazon", result.getDataOrigin());
assertEquals(true, result.isAvailable());
assertEquals(10.0, result.getCurrentPrice());
assertEquals(2.5, result.getDeliveryPrice());
assertEquals("Merchant A", result.getMerchant());
}
@Test
void testGetProductDTOById_failedRequest() throws IOException {
// Set up the test environment
AmazonProductDataSource dataSource = mock(AmazonProductDataSource.class);
URL mockURL = mock(URL.class);
when(dataSource.createApiUrl(demoProductId)).thenReturn(mockURL);
when(dataSource.getProductDTOById(demoProductId)).thenCallRealMethod();
HttpURLConnection mockConnection = mock(HttpURLConnection.class);
when(mockURL.openConnection()).thenReturn(mockConnection);
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND);
// Invoke the method and verify the exception
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
dataSource.getProductDTOById(demoProductId);
});
// Verify the exception message
assertEquals("Nothing found: Amazon API responded with error code 404", exception.getMessage());
}
@Test
void testGetProductDTOById_ioException() throws IOException {
// Set up the test environment
AmazonProductDataSource dataSource = mock(AmazonProductDataSource.class);
URL mockURL = mock(URL.class);
when(dataSource.createApiUrl(demoProductId)).thenReturn(mockURL);
when(dataSource.getProductDTOById(demoProductId)).thenCallRealMethod();
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()).thenThrow(new IOException());
// Invoke the method and verify the exception
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
dataSource.getProductDTOById(demoProductId);
});
// Verify the exception message
assertEquals("Couldn't fulfill Amazon API request", exception.getMessage());
}
}

View File

@@ -0,0 +1,33 @@
package de.rwu.easydrop.api.dto;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class ProductDTOTest {
@Test
void testToString1() {
ProductDTO product1 = new ProductDTO("12345", "Amazon");
product1.setMerchant("Merchant A");
product1.setCurrentPrice(19.99);
product1.setAvailable(true);
String expectedString1 = "ProductDTO{12345 from Merchant A (Amazon) at 19,99 € (available: yes)}";
String result1 = product1.toString();
assertEquals(expectedString1, result1);
}
@Test
void testToString2() {
ProductDTO product2 = new ProductDTO("67890", "eBay");
product2.setMerchant("Merchant B");
product2.setCurrentPrice(9.99);
product2.setAvailable(false);
String expectedString2 = "ProductDTO{67890 from Merchant B (eBay) at 9,99 € (available: no)}";
String result2 = product2.toString();
assertEquals(expectedString2, result2);
}
}

View File

@@ -1,19 +0,0 @@
package de.rwu.easydrop.demo;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class DemoClassTest {
private final DemoClass demoClass = new DemoClass();
@Test
public void testInvertBoolTrue() {
assertEquals("Inverting true should return false", false, demoClass.invertBool(true));
}
@Test
public void testInvertBoolFalse() {
assertEquals("Inverting false should return true", true, demoClass.invertBool(false));
}
}

View File

@@ -0,0 +1,95 @@
package de.rwu.easydrop.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.NoSuchElementException;
import javax.naming.ConfigurationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class ConfigImplTest {
private Config config;
private final static String TESTDATA_PATH = "testResources/testdata.properties";
private final static String TESTDATA_KEY = "API_KEY";
private final static String TESTDATA_VAL = "keyIsHere";
@BeforeEach
void setUp() {
config = Config.getInstance();
}
@Test
void testGetProperty_ExistingKey() throws ConfigurationException {
config.setConfigLocation(TESTDATA_PATH);
config.loadConfig();
config.setProperty(TESTDATA_KEY, TESTDATA_VAL);
String value = config.getProperty(TESTDATA_KEY);
assertEquals(TESTDATA_VAL, value);
}
@Test
void testGetProperty_ConfigNotLoaded() {
NoSuchElementException exception = assertThrows(NoSuchElementException.class, () -> {
config.getProperty(TESTDATA_KEY);
});
assertEquals("Config has not been loaded", exception.getMessage());
}
@Test
void testGetProperty_NonExistingKey() {
try {
config.setConfigLocation(TESTDATA_PATH);
config.loadConfig();
NoSuchElementException exception = assertThrows(NoSuchElementException.class, () -> {
config.getProperty("I_DONT_EXIST");
});
assertEquals("Requested config value does not exist", exception.getMessage());
} catch (ConfigurationException e) {
fail("ConfigurationException should not be thrown");
}
}
@Test
void testSetProperty() {
try {
config.setConfigLocation(TESTDATA_PATH);
config.loadConfig();
config.setProperty(TESTDATA_KEY, "12345");
assertEquals("12345", config.getProperty(TESTDATA_KEY));
} catch (ConfigurationException e) {
fail("ConfigurationException should not be thrown");
}
}
@Test
void testLoadConfigSuccessfully() {
try {
config.setConfigLocation("testResources/testdata.properties");
config.loadConfig();
assertEquals(TESTDATA_VAL, config.getProperty(TESTDATA_KEY));
} catch (ConfigurationException e) {
fail("ConfigurationException should not be thrown");
}
}
@Test
void testLoadConfigMissingFile() {
config.setConfigLocation("path/that/doesnt/exist/config.properties");
ConfigurationException exception = assertThrows(ConfigurationException.class, () -> {
config.loadConfig();
});
assertEquals("Couldn't load required config file", exception.getMessage());
}
}

View File

@@ -0,0 +1,42 @@
package de.rwu.easydrop.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.spy;
import javax.naming.ConfigurationException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
class ConfigTest {
private Config config;
@BeforeEach
void setUp() throws ConfigurationException {
MockitoAnnotations.openMocks(this);
config = spy(Config.getInstance());
}
@Test
void testGetInstanceNull() {
config = null;
Config newConfig = Config.getInstance();
assertNotNull(newConfig);
}
@Test
void testGetInstanceNotNull() {
Config newConfig = Config.getInstance();
assertNotNull(newConfig);
}
@Test
void testSetConfigLocation() {
String newPath = "new/location/config.properties";
config.setConfigLocation(newPath);
assertEquals(newPath, config.getConfigLocation());
}
}

View File

@@ -0,0 +1,58 @@
package de.rwu.easydrop.util;
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.Assertions;
import org.junit.jupiter.api.Test;
class FormattingUtilTest {
@Test
void testConstructorIsPrivate()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// Check for private constructor
Constructor<FormattingUtil> constructor = FormattingUtil.class.getDeclaredConstructor();
assertTrue(Modifier.isPrivate(constructor.getModifiers()));
// Make sure exception is thrown when instantiating
constructor.setAccessible(true);
assertThrows(InvocationTargetException.class, () -> {
constructor.newInstance();
});
}
@Test
void testFormatEuro_positiveAmount() {
double amount = 1234.56;
String expectedFormattedAmount = "1.234,56 €";
String formattedAmount = FormattingUtil.formatEuro(amount);
Assertions.assertEquals(expectedFormattedAmount, formattedAmount);
}
@Test
void testFormatEuro_zeroAmount() {
double amount = 0.0;
String expectedFormattedAmount = "0,00 €";
String formattedAmount = FormattingUtil.formatEuro(amount);
Assertions.assertEquals(expectedFormattedAmount, formattedAmount);
}
@Test
void testFormatEuro_negativeAmount() {
double amount = -789.12;
String expectedFormattedAmount = "-789,12 €";
String formattedAmount = FormattingUtil.formatEuro(amount);
Assertions.assertEquals(expectedFormattedAmount, formattedAmount);
}
}

View File

View File

@@ -0,0 +1 @@
API_KEY=keyIsHere