Merge branch 'dev' into 'main'
Release 0.2.0 See merge request team1/sandbox2!6
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,6 +3,8 @@
|
||||
###################################################################################################
|
||||
|
||||
config.properties
|
||||
products-config.json
|
||||
persistence.db
|
||||
|
||||
###################################################################################################
|
||||
## Visual Studio Code #############################################################################
|
||||
|
||||
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
@@ -1,3 +1,8 @@
|
||||
{
|
||||
"java.configuration.updateBuildConfiguration": "automatic"
|
||||
}
|
||||
"java.configuration.updateBuildConfiguration": "automatic",
|
||||
"sonarlint.connectedMode.project": {
|
||||
"connectionId": "LocalSonarQube",
|
||||
"projectKey": "EasyDrop"
|
||||
},
|
||||
"java.debug.settings.onBuildFailureProceed": true
|
||||
}
|
||||
|
||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,10 +1,36 @@
|
||||
# Changelog
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Added
|
||||
|
||||
- `EbayItemDataSource` (#37)
|
||||
- Product data persistence via SQLite integration (#53)
|
||||
- Mutation testing capabilities via PITest (#66)
|
||||
- `Product` class
|
||||
- Mapping between Product and its corresponding DTO
|
||||
- Dependencies: Lombok for easier notation, JUnit 5 Params for repetitive tests
|
||||
- Product validation, retrieval and persistence writing
|
||||
- Product catalogue retrieval and persistence writing
|
||||
- More unit tests
|
||||
|
||||
### Changed
|
||||
|
||||
- `AbstractDataSource` between DataSource interface and implementations to minimize code duplication
|
||||
- Affects AmazonProductDataSource and related tests (~ #39)
|
||||
- Testing resources moved to more intuitive location
|
||||
- Demo `Main` method now uses proper Product retrieval
|
||||
|
||||
### Fixed
|
||||
|
||||
- Logback configuration is now linked correctly
|
||||
- Config now properly adheres to singleton pattern
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Added
|
||||
|
||||
- (DataSource) → AmazonProductDataSource as first external data source (#39)
|
||||
- ProductDTO as data holder structure for data from external sources (~ #39)
|
||||
- (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)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
Dropshipping
|
||||
|
||||
Amazon vs. ebay
|
||||
|
||||
software schreiben die ermittelt, ob ein produkt bei amazon oder ebay günstiger bei einer der beiden ist und dann automatisch teurer beim anderen einstellen
|
||||
ziel: keine lagerkosten, profit durch marge
|
||||
problem: kann natürlich sein, dass es zwischenzeitlich beim einen händler nicht mehr verfügbar ist wenn der kunde es bei uns kaufen will,
|
||||
sodass der automatische kauf bei der günstigeren plattform nicht mehr ausgelöst werden kann -->angebot muss also automatisch rausgenommen werden,
|
||||
sobald produkt bei der günstigeren plattform nicht mehr verfügbar ist
|
||||
- preise vergleichen automatisiert
|
||||
- verfügbarkeit vergleichen automatisiert
|
||||
- bspw. verkauf des günstigen produktes auf drittplattform (etsy) => Shopify?
|
||||
|
||||
welche anforderungen gibt es an mein produkt
|
||||
- plattform/en: Amazon und eBay
|
||||
- schnittstellen/APIs (zugang, kosten (pro aufruf))
|
||||
- absicherung (fallback falls ebay/amazon nicht liefert -->alibaba)
|
||||
- welche Art der Datenbank
|
||||
- Lagerhaltung? =>eher nein
|
||||
- algorithmus erstellen der soziale Medien nach Hype-Produkten durchsucht =>affiliate links?
|
||||
@@ -1,8 +0,0 @@
|
||||
- Code should be free of faults
|
||||
- Tests should be green by 90%
|
||||
- Code is pushed to Team Repository
|
||||
- Code has only few comments
|
||||
- No commented out Code in Software
|
||||
- Code is documented
|
||||
- Code is executable on server
|
||||
- Project is presented to customer / product owner / professor
|
||||
BIN
Docs/DemoImage.png
Normal file
BIN
Docs/DemoImage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -1,5 +0,0 @@
|
||||
- Everything from code to comment is written in English; documentation may be in German
|
||||
- Every developer has only one story in development. If the developer needs to halt with development in their story due to unforeseen reasons, they may already start with another story.
|
||||
- Every branch works on one story
|
||||
- User stories should be written in a clear way so that every developer can understand in what development status the ticket is
|
||||
- Another rule
|
||||
@@ -1,5 +0,0 @@
|
||||
- Welche Datenbank? AWS ja nein? =>Friedrich fragen (Rocketchat)
|
||||
- Welche Werkzeuge? VSC
|
||||
- Sprache: Java
|
||||
- Kein Frontend
|
||||
-
|
||||
105
README.md
105
README.md
@@ -1,92 +1,43 @@
|
||||
# Sandbox2
|
||||
# EasyDrop
|
||||
|
||||
|
||||
|
||||
## Getting started
|
||||
|
||||
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
||||
|
||||
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
||||
|
||||
## Add your files
|
||||
|
||||
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
||||
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
|
||||
|
||||
```
|
||||
cd existing_repo
|
||||
git remote add origin http://gitlab.fbe-adswen.rwu.de/team1/sandbox2.git
|
||||
git branch -M main
|
||||
git push -uf origin main
|
||||
```
|
||||
|
||||
## Integrate with your tools
|
||||
|
||||
- [ ] [Set up project integrations](http://gitlab.fbe-adswen.rwu.de/team1/sandbox2/-/settings/integrations)
|
||||
|
||||
## Collaborate with your team
|
||||
|
||||
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
||||
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
||||
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
||||
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
||||
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
|
||||
|
||||
## Test and Deploy
|
||||
|
||||
Use the built-in continuous integration in GitLab.
|
||||
|
||||
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
|
||||
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
||||
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
||||
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
||||
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
||||
|
||||
***
|
||||
|
||||
# Editing this README
|
||||
|
||||
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
||||
|
||||
## Suggestions for a good README
|
||||
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
||||
|
||||
## Name
|
||||
Choose a self-explaining name for your project.
|
||||

|
||||

|
||||
[](https://sonar.fbe-adswen.rwu.de/dashboard?id=de.rwu%3Aeasydrop)
|
||||

|
||||
|
||||
## Description
|
||||
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
||||
|
||||
## Badges
|
||||
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
||||
Get rich quick™ with fully automated dropshipping!
|
||||
|
||||
## Visuals
|
||||
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
||||
## Visuals ✨
|
||||
|
||||
## Installation
|
||||
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
||||

|
||||
|
||||
## Usage
|
||||
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
||||
## Installation ⚙
|
||||
|
||||
## Support
|
||||
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
||||
As the application is still in development, development software will be required along its regular execution environment.
|
||||
|
||||
## Roadmap
|
||||
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
||||
- JDK 17
|
||||
- Maven
|
||||
- _[More/context](https://gitlab.fbe-adswen.rwu.de/team1/sandbox2/-/wikis/Projektdokumentation/Development#software)_
|
||||
|
||||
## Contributing
|
||||
State if you are open to contributions and what your requirements are for accepting them.
|
||||
## Usage 🛠
|
||||
|
||||
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
||||
Set up the required configuration files, use the corresponding demo files for orientation.
|
||||
|
||||
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
||||
- `config/config.properties` for API authorization
|
||||
- `products-config.json` to define product catalogues to use for dropshipping
|
||||
|
||||
## Authors and acknowledgment
|
||||
Show your appreciation to those who have contributed to the project.
|
||||
Run the following command to execute the program:
|
||||
|
||||
## License
|
||||
For open source projects, say how it is licensed.
|
||||
```sh
|
||||
mvn compile exec:java -Dexec.mainClass="de.rwu.easydrop.Main"
|
||||
```
|
||||
|
||||
## Project status
|
||||
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
||||
## Roadmap 🏁
|
||||
|
||||
_Future ideas!_
|
||||
|
||||
## Contributing 👷♂️👷♀️
|
||||
|
||||
Contribution guidelines are available in the [project wiki](https://gitlab.fbe-adswen.rwu.de/team1/sandbox2/-/wikis/Richtlinien/Development)
|
||||
|
||||
133
SampleResponses/eBayProductAPISample.json
Normal file
133
SampleResponses/eBayProductAPISample.json
Normal file
@@ -0,0 +1,133 @@
|
||||
{
|
||||
"href": "https://api.ebay.com/buy/browse/v1/item_summary/search?q=drone&limit=1&offset=0",
|
||||
"total": 260202,
|
||||
"next": "https://api.ebay.com/buy/browse/v1/item_summary/search?q=drone&limit=1&offset=1",
|
||||
"limit": 3,
|
||||
"offset": 0,
|
||||
"itemSummaries": [
|
||||
{
|
||||
"itemId": "v1|1**********1|0",
|
||||
"title": "Syma X5SW-V3 Wifi FPV RC Drone Quadcopter 2.4Ghz 6-Axis Gyro with Headless Mode",
|
||||
"leafCategoryIds": ["179697", "182186"],
|
||||
"categories": [
|
||||
{
|
||||
"categoryId": "179697",
|
||||
"categoryName": "Camera Drones"
|
||||
},
|
||||
{
|
||||
"categoryId": "182186",
|
||||
"categoryName": "Other RC Model Vehicles & Kits"
|
||||
},
|
||||
{
|
||||
"categoryId": "2562",
|
||||
"categoryName": "Radio Control & Control Line"
|
||||
},
|
||||
{
|
||||
"categoryId": "220",
|
||||
"categoryName": "Toys & Hobbies"
|
||||
},
|
||||
{
|
||||
"categoryId": "625",
|
||||
"categoryName": "Cameras & Photo"
|
||||
},
|
||||
{
|
||||
"categoryId": "182181",
|
||||
"categoryName": "RC Model Vehicles & Kits"
|
||||
}
|
||||
],
|
||||
"image": {
|
||||
"imageUrl": "https://i.ebayimg.com/thumbs/images/g/n**************a/s-***5.jpg"
|
||||
},
|
||||
"price": {
|
||||
"value": "59.99",
|
||||
"currency": "USD"
|
||||
},
|
||||
"itemHref": "https://api.ebay.com/buy/browse/v1/item/v1******************0",
|
||||
"seller": {
|
||||
"username": "m********e",
|
||||
"feedbackPercentage": "98.6",
|
||||
"feedbackScore": 130000
|
||||
},
|
||||
"marketingPrice": {
|
||||
"originalPrice": {
|
||||
"value": "74.99",
|
||||
"currency": "USD"
|
||||
},
|
||||
"discountPercentage": "20",
|
||||
"discountAmount": {
|
||||
"value": "15.00",
|
||||
"currency": "USD"
|
||||
},
|
||||
"priceTreatment": "LIST_PRICE"
|
||||
},
|
||||
"condition": "New",
|
||||
"conditionId": "1000",
|
||||
"thumbnailImages": [
|
||||
{
|
||||
"imageUrl": "https://i.ebayimg.com/images/g/n**************a/s-l***0.jpg"
|
||||
}
|
||||
],
|
||||
"shippingOptions": [
|
||||
{
|
||||
"shippingCostType": "FIXED",
|
||||
"shippingCost": {
|
||||
"value": "0.00",
|
||||
"currency": "USD"
|
||||
},
|
||||
"minEstimatedDeliveryDate": "2022-11-19T08:00:00.000Z",
|
||||
"maxEstimatedDeliveryDate": "2022-11-21T08:00:00.000Z",
|
||||
"guaranteedDelivery": true
|
||||
}
|
||||
],
|
||||
"buyingOptions": ["FIXED_PRICE", "BEST_OFFER"],
|
||||
"itemAffiliateWebUrl": "https://www.ebay.com/itm/1**********1?hash=i************d*9",
|
||||
"itemWebUrl": "https://www.ebay.com/itm/1**********1?hash=i************d*p",
|
||||
"itemLocation": {
|
||||
"postalCode": "0****",
|
||||
"country": "US"
|
||||
},
|
||||
"additionalImages": [
|
||||
{
|
||||
"imageUrl": "https://origin-galleryplus.ebayimg.com/ws/web/1**********1_2_0_1/225x225.jpg"
|
||||
},
|
||||
{
|
||||
"imageUrl": "https://origin-galleryplus.ebayimg.com/ws/web/1**********1_3_0_1/225x225.jpg"
|
||||
},
|
||||
{
|
||||
"imageUrl": "https://origin-galleryplus.ebayimg.com/ws/web/1**********1_4_0_1/225x225.jpg"
|
||||
},
|
||||
{
|
||||
"imageUrl": "https://origin-galleryplus.ebayimg.com/ws/web/1**********1_5_0_1/225x225.jpg"
|
||||
},
|
||||
{
|
||||
"imageUrl": "https://origin-galleryplus.ebayimg.com/ws/web/1**********1_6_0_1/225x225.jpg"
|
||||
},
|
||||
{
|
||||
"imageUrl": "https://origin-galleryplus.ebayimg.com/ws/web/1**********1_7_0_1/225x225.jpg"
|
||||
},
|
||||
{
|
||||
"imageUrl": "https://origin-galleryplus.ebayimg.com/ws/web/1**********1_8_0_1/225x225.jpg"
|
||||
},
|
||||
{
|
||||
"imageUrl": "https://origin-galleryplus.ebayimg.com/ws/web/1**********1_9_0_1/225x225.jpg"
|
||||
},
|
||||
{
|
||||
"imageUrl": "https://origin-galleryplus.ebayimg.com/ws/web/1**********1_10_0_1/225x225.jpg"
|
||||
},
|
||||
{
|
||||
"imageUrl": "https://origin-galleryplus.ebayimg.com/ws/web/1**********1_11_0_1/225x225.jpg"
|
||||
},
|
||||
{
|
||||
"imageUrl": "https://origin-galleryplus.ebayimg.com/ws/web/1**********1_12_0_1/225x225.jpg"
|
||||
}
|
||||
],
|
||||
"adultOnly": false,
|
||||
"legacyItemId": "1**********1",
|
||||
"availableCoupons": false,
|
||||
"itemCreationDate": "2022-12-25T07:14:44.000Z",
|
||||
"topRatedBuyingExperience": true,
|
||||
"priorityListing": true,
|
||||
"listingMarketplaceId": "EBAY_US"
|
||||
}
|
||||
]
|
||||
}
|
||||
2
Script/MutationTestReport.sh
Normal file
2
Script/MutationTestReport.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
mvn test-compile org.pitest:pitest-maven:mutationCoverage
|
||||
start target/pit-reports/index.html
|
||||
7
Script/SonarQube_Local.sh
Normal file
7
Script/SonarQube_Local.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
mvn clean verify sonar:sonar -Pcoverage \
|
||||
-Dsonar.projectKey=EasyDrop \
|
||||
-Dsonar.projectName='EasyDrop' \
|
||||
-Dsonar.host.url=http://localhost:9000 \
|
||||
-Dsonar.token=sqp_82d35689c620c15fd1064549375e17a2a5b0b931
|
||||
|
||||
start http://localhost:9000/dashboard?id=EasyDrop
|
||||
@@ -1,3 +1,5 @@
|
||||
# Amazon Credentials
|
||||
AMAZON_API_URL=
|
||||
AMAZON_API_KEY=
|
||||
AMAZON_API_KEY=
|
||||
EBAY_API_URL=
|
||||
EBAY_API_KEY=
|
||||
16
config/demo.products-config.json
Normal file
16
config/demo.products-config.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"products": [
|
||||
{
|
||||
"name": "Gigabyte GeForce RTX 3060",
|
||||
"description": "Very epic GPU",
|
||||
"identifiers": [
|
||||
{
|
||||
"Amazon": "B096Y2TYKV"
|
||||
},
|
||||
{
|
||||
"eBay": "Gigabyte GeForce RTX 3060"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
2
lombok.config
Normal file
2
lombok.config
Normal file
@@ -0,0 +1,2 @@
|
||||
config.stopBubbling = true
|
||||
lombok.addLombokGeneratedAnnotation = true
|
||||
37
pom.xml
37
pom.xml
@@ -5,7 +5,7 @@
|
||||
<groupId>de.rwu</groupId>
|
||||
<artifactId>easydrop</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>0.1.0</version>
|
||||
<version>0.2.0</version>
|
||||
<name>EasyDrop</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
@@ -16,16 +16,29 @@
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.28</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.8.0</version>
|
||||
<version>5.9.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>5.8.0</version>
|
||||
<version>5.9.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<version>5.9.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -47,6 +60,12 @@
|
||||
<version>2.8.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.pitest</groupId>
|
||||
<artifactId>pitest-junit5-plugin</artifactId>
|
||||
<version>1.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
@@ -57,6 +76,12 @@
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.4.7</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.xerial</groupId>
|
||||
<artifactId>sqlite-jdbc</artifactId>
|
||||
<version>3.42.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -88,6 +113,12 @@
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.8.10</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.pitest</groupId>
|
||||
<artifactId>pitest-maven</artifactId>
|
||||
<version>1.14.1</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
package de.rwu.easydrop;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.sqlite.SQLiteDataSource;
|
||||
|
||||
import de.rwu.easydrop.api.client.AmazonProductDataSource;
|
||||
import de.rwu.easydrop.api.client.DataSourceFactory;
|
||||
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
|
||||
import de.rwu.easydrop.data.connector.SQLiteConnector;
|
||||
import de.rwu.easydrop.model.ProductCatalogue;
|
||||
import de.rwu.easydrop.service.retriever.CatalogueRetriever;
|
||||
import de.rwu.easydrop.service.retriever.ProductRetriever;
|
||||
import de.rwu.easydrop.service.writer.CatalogueWriter;
|
||||
import de.rwu.easydrop.util.Config;
|
||||
import de.rwu.easydrop.util.ProductsConfig;
|
||||
|
||||
/**
|
||||
* Kickoff point for the service.
|
||||
@@ -33,17 +43,20 @@ public final class Main {
|
||||
*/
|
||||
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;
|
||||
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);
|
||||
|
||||
AmazonProductDataSource amznSrc = new AmazonProductDataSource(amznBaseUrl, amznApiKey);
|
||||
try {
|
||||
testProduct = amznSrc.getProductDTOById("B096Y2TYKV").toString();
|
||||
LOGGER.info(testProduct);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.error("Something went wrong :(", e);
|
||||
catRetriever.loadCatalogues();
|
||||
List<ProductCatalogue> pCats = catRetriever.getProductCatalogues();
|
||||
catWriter.writeCatalogues(pCats);
|
||||
|
||||
for (ProductCatalogue pCat : pCats) {
|
||||
String pCatStr = pCat.toString();
|
||||
LOGGER.info(pCatStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
100
src/main/java/de/rwu/easydrop/api/client/AbstractDataSource.java
Normal file
100
src/main/java/de/rwu/easydrop/api/client/AbstractDataSource.java
Normal file
@@ -0,0 +1,100 @@
|
||||
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 de.rwu.easydrop.api.dto.ProductDTO;
|
||||
import de.rwu.easydrop.exception.DataSourceException;
|
||||
import de.rwu.easydrop.util.FormattingUtil;
|
||||
|
||||
/**
|
||||
* Helper class for shared data and functions between data sources.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public abstract class AbstractDataSource implements DataSource {
|
||||
|
||||
/**
|
||||
* Returns the data origin for the current source.
|
||||
*
|
||||
* @return Data source name
|
||||
*/
|
||||
protected abstract String getDataOrigin();
|
||||
|
||||
/**
|
||||
* Returns the data source's API key.
|
||||
*
|
||||
* @return Data source API key
|
||||
*/
|
||||
protected abstract String getApiKey();
|
||||
|
||||
/**
|
||||
* Enriches a ProductDTO with API-gathered data.
|
||||
*
|
||||
* @param product Unfinished ProductDTO
|
||||
* @param json Product data
|
||||
* @return Finished ProductDTO
|
||||
*/
|
||||
protected abstract ProductDTO buildProductDTO(ProductDTO product, String json);
|
||||
|
||||
/**
|
||||
* Overridable standard implementation.
|
||||
*/
|
||||
@Override
|
||||
public ProductDTO getProductDTOById(final String productIdentifier)
|
||||
throws IllegalArgumentException {
|
||||
StringBuilder response = new StringBuilder();
|
||||
String dataOrigin = getDataOrigin();
|
||||
String apiKey = getApiKey();
|
||||
ProductDTO product = new ProductDTO(productIdentifier, dataOrigin);
|
||||
|
||||
try {
|
||||
String urlReadyIdentifier = FormattingUtil.urlEncode(productIdentifier);
|
||||
URL apiUrl = createApiUrl(urlReadyIdentifier);
|
||||
|
||||
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 DataSourceException(
|
||||
"Nothing found: "
|
||||
+ dataOrigin
|
||||
+ " 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 DataSourceException(
|
||||
"Couldn't fulfill "
|
||||
+ dataOrigin
|
||||
+ " API request");
|
||||
}
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an URL object to connect to the API with.
|
||||
*
|
||||
* @param productIdentifier Product identifier
|
||||
* @return URL object
|
||||
* @throws MalformedURLException
|
||||
*/
|
||||
protected abstract URL createApiUrl(String productIdentifier) throws MalformedURLException;
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
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;
|
||||
|
||||
@@ -18,7 +14,7 @@ import de.rwu.easydrop.api.dto.ProductDTO;
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
public final class AmazonProductDataSource implements DataSource {
|
||||
public final class AmazonProductDataSource extends AbstractDataSource {
|
||||
/**
|
||||
* Name of this data source.
|
||||
*/
|
||||
@@ -28,7 +24,7 @@ public final class AmazonProductDataSource implements DataSource {
|
||||
*/
|
||||
private String baseUrl;
|
||||
/**
|
||||
* Credential key to authorize acccess.
|
||||
* Credential key to authorize access.
|
||||
*/
|
||||
private String apiKey;
|
||||
/**
|
||||
@@ -52,48 +48,7 @@ public final class AmazonProductDataSource implements DataSource {
|
||||
}
|
||||
|
||||
@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) {
|
||||
protected ProductDTO buildProductDTO(final ProductDTO product, final String json) {
|
||||
String root = "$.featuredOffer.";
|
||||
ReadContext ctx = JsonPath.parse(json);
|
||||
|
||||
@@ -112,8 +67,11 @@ public final class AmazonProductDataSource implements DataSource {
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param productId ASIN
|
||||
*/
|
||||
@Override
|
||||
public URL createApiUrl(final String productId) throws MalformedURLException {
|
||||
protected URL createApiUrl(final String productId) throws MalformedURLException {
|
||||
return new URL(baseUrl
|
||||
+ "/products/2020-08-26/products/"
|
||||
+ productId
|
||||
@@ -122,4 +80,14 @@ public final class AmazonProductDataSource implements DataSource {
|
||||
+ "&locale="
|
||||
+ LOCALE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDataOrigin() {
|
||||
return DATA_ORIGIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getApiKey() {
|
||||
return this.apiKey;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package de.rwu.easydrop.api.client;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||
|
||||
/**
|
||||
@@ -12,17 +9,8 @@ public interface DataSource {
|
||||
/**
|
||||
* Retrieves product info from the data source.
|
||||
*
|
||||
* @param productId ASIN
|
||||
* @param productIdentifier Product identifier
|
||||
* @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;
|
||||
ProductDTO getProductDTOById(String productIdentifier);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package de.rwu.easydrop.api.client;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
|
||||
import de.rwu.easydrop.exception.PersistenceException;
|
||||
import de.rwu.easydrop.util.Config;
|
||||
|
||||
/**
|
||||
* Factory for Data Sources.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class DataSourceFactory {
|
||||
|
||||
/**
|
||||
* The data source config.
|
||||
*/
|
||||
private Config config;
|
||||
/**
|
||||
* Persistence interface.
|
||||
*/
|
||||
private AbstractProductPersistence persistence = null;
|
||||
|
||||
/**
|
||||
* @param newPersistence the persistence to set
|
||||
*/
|
||||
public void setPersistence(final AbstractProductPersistence newPersistence) {
|
||||
this.persistence = newPersistence;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newConfig the config to set
|
||||
*/
|
||||
public void setConfig(final Config newConfig) throws ConfigurationException {
|
||||
this.config = newConfig;
|
||||
this.config.loadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newConfig
|
||||
*/
|
||||
public DataSourceFactory(final Config newConfig) throws ConfigurationException {
|
||||
this.setConfig(newConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Amazon Product Data Source.
|
||||
*
|
||||
* @return AmazonProductDataSource
|
||||
*/
|
||||
public AmazonProductDataSource createAmazonProductDataSource() {
|
||||
String apiUrl = config.getProperty("AMAZON_API_URL");
|
||||
String apiKey = config.getProperty("AMAZON_API_KEY");
|
||||
return new AmazonProductDataSource(apiUrl, apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an eBay Item Data Source.
|
||||
*
|
||||
* @return EbayItemDataSource
|
||||
*/
|
||||
public EbayItemDataSource createEbayItemDataSource() {
|
||||
String apiUrl = config.getProperty("EBAY_API_URL");
|
||||
String apiKey = config.getProperty("EBAY_API_KEY");
|
||||
return new EbayItemDataSource(apiUrl, apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a persistence data source.
|
||||
*
|
||||
* @return ProductPersistenceInterface
|
||||
*/
|
||||
public AbstractProductPersistence createProductPersistenceDataSource() {
|
||||
if (persistence == null) {
|
||||
throw new PersistenceException("Persistence is not set");
|
||||
}
|
||||
|
||||
return persistence;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package de.rwu.easydrop.api.client;
|
||||
|
||||
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 eBay data source.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public final class EbayItemDataSource extends AbstractDataSource {
|
||||
/**
|
||||
* Name of this data source.
|
||||
*/
|
||||
private static final String DATA_ORIGIN = "eBay";
|
||||
/**
|
||||
* Base URL to the eBay data source.
|
||||
*/
|
||||
private String baseUrl;
|
||||
/**
|
||||
* Credential key to authorize access.
|
||||
*/
|
||||
private String apiKey;
|
||||
|
||||
/**
|
||||
* Sets up instance with Base URL and API Key.
|
||||
*
|
||||
* @param newBaseUrl
|
||||
* @param newApiKey
|
||||
*/
|
||||
public EbayItemDataSource(final String newBaseUrl, final String newApiKey) {
|
||||
this.baseUrl = newBaseUrl;
|
||||
this.apiKey = newApiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param searchQuery Exact product name or other valid identifier.
|
||||
*/
|
||||
@Override
|
||||
protected URL createApiUrl(final String searchQuery) throws MalformedURLException {
|
||||
return new URL(baseUrl
|
||||
+ "/buy/browse/v1/item_summary/search?q="
|
||||
+ searchQuery
|
||||
+ "&limit=1&offset=0");
|
||||
}
|
||||
|
||||
/**
|
||||
* Enriches a ProductDTO with API-gathered data.
|
||||
*
|
||||
* @param product Unfinished ProductDTO
|
||||
* @param json Product data
|
||||
* @return Finished ProductDTO
|
||||
*/
|
||||
protected ProductDTO buildProductDTO(final ProductDTO product, final String json) {
|
||||
String root = "$.itemSummaries[0].";
|
||||
ReadContext ctx = JsonPath.parse(json);
|
||||
|
||||
try {
|
||||
product.setDataOrigin(DATA_ORIGIN);
|
||||
product.setAvailable(
|
||||
ctx.read(root + "shippingOptions[0].guaranteedDelivery", boolean.class));
|
||||
product.setCurrentPrice(ctx.read(root + "price.value", double.class));
|
||||
product.setDeliveryPrice(
|
||||
ctx.read(root + "shippingOptions[0].shippingCost.value", double.class));
|
||||
product.setMerchant(ctx.read(root + "seller.username", String.class));
|
||||
} catch (PathNotFoundException e) {
|
||||
// Pass, allow incomplete ProductDTO to pass for later validation
|
||||
}
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDataOrigin() {
|
||||
return DATA_ORIGIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getApiKey() {
|
||||
return this.apiKey;
|
||||
}
|
||||
}
|
||||
@@ -1,139 +1,52 @@
|
||||
package de.rwu.easydrop.api.dto;
|
||||
|
||||
import de.rwu.easydrop.util.FormattingUtil;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Product data transfer object.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*/
|
||||
@Data
|
||||
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 newProductId Internal 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") + ")}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package de.rwu.easydrop.data.connector;
|
||||
|
||||
import de.rwu.easydrop.api.client.AbstractDataSource;
|
||||
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||
|
||||
/**
|
||||
* Allows connecting to a persistent product data store.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public abstract class AbstractProductPersistence extends AbstractDataSource {
|
||||
/**
|
||||
* Data origin.
|
||||
*/
|
||||
public static final String DATA_ORIGIN = "Persistence";
|
||||
|
||||
/**
|
||||
* Writes a ProductDTO to persistence.
|
||||
*
|
||||
* @param dto
|
||||
*/
|
||||
public abstract void saveProduct(ProductDTO dto);
|
||||
|
||||
/**
|
||||
* Gets a ProductDTO from persistence.
|
||||
*/
|
||||
@Override
|
||||
public abstract ProductDTO getProductDTOById(String productId);
|
||||
|
||||
/**
|
||||
* Deletes all data from persistence.
|
||||
*/
|
||||
public abstract void clearData();
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package de.rwu.easydrop.data.connector;
|
||||
|
||||
/**
|
||||
* Allows connecting to a SQLite Database.
|
||||
*
|
||||
* TODO implement
|
||||
*/
|
||||
public class DatabaseConnector {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package de.rwu.easydrop.data.connector;
|
||||
|
||||
import java.net.URL;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import org.sqlite.SQLiteDataSource;
|
||||
|
||||
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||
import de.rwu.easydrop.exception.PersistenceException;
|
||||
|
||||
/**
|
||||
* Allows connecting to a SQLite Database.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public final class SQLiteConnector extends AbstractProductPersistence {
|
||||
/**
|
||||
* Data origin.
|
||||
*/
|
||||
private static final String DATA_ORIGIN = "SQLite";
|
||||
|
||||
/**
|
||||
* SQLite Database.
|
||||
*/
|
||||
private SQLiteDataSource db;
|
||||
|
||||
/**
|
||||
* @param src the db to set
|
||||
*/
|
||||
public void setDb(final SQLiteDataSource src) {
|
||||
this.db = src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path to SQLite db file.
|
||||
*/
|
||||
private static final String PERSISTENCE_PATH = "jdbc:sqlite:persistence.db";
|
||||
|
||||
/**
|
||||
* Creates instance.
|
||||
*
|
||||
* @param src SQLite Data Source
|
||||
*/
|
||||
public SQLiteConnector(final SQLiteDataSource src) {
|
||||
db = src;
|
||||
db.setUrl(PERSISTENCE_PATH);
|
||||
initializeDatabase();
|
||||
}
|
||||
|
||||
private void initializeDatabase() {
|
||||
try {
|
||||
// Create a new database connection
|
||||
Connection connection = db.getConnection();
|
||||
|
||||
// Execute SQL statements to create tables
|
||||
Statement statement = connection.createStatement();
|
||||
statement.execute(
|
||||
"CREATE TABLE IF NOT EXISTS products ("
|
||||
+ "dataOrigin TEXT, "
|
||||
+ "productId TEXT, "
|
||||
+ "currentPrice REAL, "
|
||||
+ "merchant TEXT, "
|
||||
+ "deliveryPrice REAL, "
|
||||
+ "available INT, "
|
||||
+ "lastupdate TEXT, "
|
||||
+ "UNIQUE(productId, dataOrigin) ON CONFLICT REPLACE"
|
||||
+ ")");
|
||||
|
||||
// Close the statement and connection
|
||||
statement.close();
|
||||
connection.close();
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException("Something went wrong while initializing SQLite DB", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a ProductDTO to persistence.
|
||||
*
|
||||
* @param dto
|
||||
*/
|
||||
public void saveProduct(final ProductDTO dto) {
|
||||
String query = "INSERT INTO products ("
|
||||
+ "dataOrigin, productId, currentPrice, merchant, "
|
||||
+ "deliveryPrice, available, lastupdate"
|
||||
+ ") VALUES ("
|
||||
+ "?, ?, ?, ?, ?, ?, datetime('now', 'localtime')"
|
||||
+ ")";
|
||||
|
||||
try (Connection connection = db.getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(query)) {
|
||||
int index = 0;
|
||||
|
||||
statement.setString(++index, dto.getDataOrigin());
|
||||
statement.setString(++index, dto.getProductId());
|
||||
statement.setDouble(++index, dto.getCurrentPrice());
|
||||
statement.setString(++index, dto.getMerchant());
|
||||
statement.setDouble(++index, dto.getDeliveryPrice());
|
||||
statement.setBoolean(++index, dto.isAvailable());
|
||||
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException("Something went wrong while saving to SQLite", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProductDTO getProductDTOById(final String productId) {
|
||||
String query = "SELECT * FROM products WHERE productId = ?";
|
||||
ProductDTO dto = null;
|
||||
|
||||
try (Connection connection = db.getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement(query)) {
|
||||
|
||||
statement.setString(1, productId);
|
||||
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
if (resultSet.next()) {
|
||||
dto = new ProductDTO(resultSet.getString("productId"),
|
||||
resultSet.getString("dataOrigin"));
|
||||
dto.setCurrentPrice(resultSet.getDouble("currentPrice"));
|
||||
dto.setMerchant(resultSet.getString("merchant"));
|
||||
dto.setDeliveryPrice(resultSet.getDouble("deliveryPrice"));
|
||||
dto.setAvailable(resultSet.getBoolean("available"));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException("Something went wrong while reading from SQLite", e);
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all data from persistence.
|
||||
*/
|
||||
public void clearData() {
|
||||
try (Connection connection = db.getConnection();
|
||||
Statement statement = connection.createStatement()) {
|
||||
String query = "DELETE FROM products";
|
||||
statement.executeUpdate(query);
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException("Something went wrong while clearing the database", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDataOrigin() {
|
||||
return DATA_ORIGIN;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getApiKey() {
|
||||
throw new UnsupportedOperationException(
|
||||
this.getClass().getName() + " doesn't support getApiKey");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProductDTO buildProductDTO(final ProductDTO product, final String json) {
|
||||
throw new UnsupportedOperationException(
|
||||
this.getClass().getName() + " doesn't support buildProductDTO");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected URL createApiUrl(final String productIdentifier) {
|
||||
throw new UnsupportedOperationException(
|
||||
this.getClass().getName() + " doesn't support createApiUrl");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Connectors for databases.
|
||||
*
|
||||
* TODO implement
|
||||
* @since 0.2.0
|
||||
*/
|
||||
package de.rwu.easydrop.data.connector;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package de.rwu.easydrop.data.dao;
|
||||
|
||||
/**
|
||||
* Product data access object.
|
||||
*
|
||||
* TODO implement
|
||||
*/
|
||||
public class ProductDAO {
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Data access objects for business objects created from persistence.
|
||||
*
|
||||
* TODO implement
|
||||
*/
|
||||
package de.rwu.easydrop.data.dao;
|
||||
@@ -1,10 +0,0 @@
|
||||
package de.rwu.easydrop.data.model;
|
||||
|
||||
/**
|
||||
* A Product.
|
||||
*
|
||||
* TODO implement
|
||||
*/
|
||||
public class Product {
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Business objects.
|
||||
*
|
||||
* TODO implement
|
||||
*/
|
||||
package de.rwu.easydrop.data.model;
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Structure for business objects and persisting their info.
|
||||
*
|
||||
* TODO implement
|
||||
* @since 0.2.0
|
||||
*/
|
||||
package de.rwu.easydrop.data;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package de.rwu.easydrop.exception;
|
||||
|
||||
/**
|
||||
* Exception that signifies the data of a Product are invalid.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class DataSourceException extends RuntimeException {
|
||||
/**
|
||||
* Throws an exception that signifies the data of a Product are invalid.
|
||||
*
|
||||
* @param message
|
||||
*/
|
||||
public DataSourceException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception that signifies the data of a Product are invalid.
|
||||
*
|
||||
* @param message
|
||||
* @param cause
|
||||
*/
|
||||
public DataSourceException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package de.rwu.easydrop.exception;
|
||||
|
||||
/**
|
||||
* Exception that signifies the data of a Product are invalid.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class InvalidProductException extends RuntimeException {
|
||||
/**
|
||||
* Throws an exception that signifies the data of a Product are invalid.
|
||||
*
|
||||
* @param message
|
||||
*/
|
||||
public InvalidProductException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception that signifies the data of a Product are invalid.
|
||||
*
|
||||
* @param message
|
||||
* @param cause
|
||||
*/
|
||||
public InvalidProductException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package de.rwu.easydrop.exception;
|
||||
|
||||
/**
|
||||
* Exception that signifies a problem with data persistence.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class PersistenceException extends RuntimeException {
|
||||
/**
|
||||
* Throws an exception that signifies the data of a Product are invalid.
|
||||
*
|
||||
* @param message
|
||||
*/
|
||||
public PersistenceException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an exception that signifies the data of a Product are invalid.
|
||||
*
|
||||
* @param message
|
||||
* @param cause
|
||||
*/
|
||||
public PersistenceException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Contains EasyDrop-related custom exceptions.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
package de.rwu.easydrop.exception;
|
||||
53
src/main/java/de/rwu/easydrop/model/Product.java
Normal file
53
src/main/java/de/rwu/easydrop/model/Product.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package de.rwu.easydrop.model;
|
||||
|
||||
import de.rwu.easydrop.util.FormattingUtil;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* A Product.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
@Data
|
||||
public class Product {
|
||||
/**
|
||||
* Data source platform, like "Amazon".
|
||||
*/
|
||||
private String dataOrigin;
|
||||
|
||||
/**
|
||||
* Platform internal product identifier.
|
||||
*/
|
||||
private String productId;
|
||||
|
||||
/**
|
||||
* Current product price per piece in Euro.
|
||||
*/
|
||||
private double currentPrice;
|
||||
|
||||
/**
|
||||
* Name of mercant offering the product on the platform.
|
||||
*/
|
||||
private String merchant;
|
||||
|
||||
/**
|
||||
* Additional Cost for delivery in Euro.
|
||||
*/
|
||||
private double deliveryPrice;
|
||||
|
||||
/**
|
||||
* Whether the product can be purchased at this point.
|
||||
*/
|
||||
private boolean available;
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return "Product: ["
|
||||
+ productId + " from "
|
||||
+ merchant + " ("
|
||||
+ dataOrigin + ")"
|
||||
+ " at "
|
||||
+ FormattingUtil.formatEuro(currentPrice) + " (available: "
|
||||
+ (available ? "yes" : "no") + ")]";
|
||||
}
|
||||
}
|
||||
79
src/main/java/de/rwu/easydrop/model/ProductCatalogue.java
Normal file
79
src/main/java/de/rwu/easydrop/model/ProductCatalogue.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package de.rwu.easydrop.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Holds Product instances for one product from different data sources.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
@Data
|
||||
public class ProductCatalogue {
|
||||
/**
|
||||
* Product name.
|
||||
*/
|
||||
private String productName;
|
||||
/**
|
||||
* Product description.
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
* Product collection.
|
||||
*/
|
||||
private List<Product> products;
|
||||
|
||||
/**
|
||||
* Creates new Product Catalogue.
|
||||
*
|
||||
* @param newProductName
|
||||
* @param newDescription
|
||||
*/
|
||||
public ProductCatalogue(final String newProductName, final String newDescription) {
|
||||
this.productName = newProductName;
|
||||
this.description = newDescription;
|
||||
this.products = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a product to the catalogue.
|
||||
*
|
||||
* @param product
|
||||
*/
|
||||
public void addProduct(final Product product) {
|
||||
products.add(product);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a product from the catalogue.
|
||||
*
|
||||
* @param product
|
||||
*/
|
||||
public void removeProduct(final Product product) {
|
||||
products.remove(product);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all products from the catalogue.
|
||||
*/
|
||||
public void clearProducts() {
|
||||
products = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the catalogue as a string.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Product Catalogue: ").append(productName).append("\n");
|
||||
sb.append("Description: ").append(description).append("\n");
|
||||
sb.append("Products:\n");
|
||||
for (Product product : products) {
|
||||
sb.append(product.toString()).append("\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
6
src/main/java/de/rwu/easydrop/model/package-info.java
Normal file
6
src/main/java/de/rwu/easydrop/model/package-info.java
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Business objects.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
package de.rwu.easydrop.model;
|
||||
@@ -1,14 +1,62 @@
|
||||
package de.rwu.easydrop.service.mapping;
|
||||
|
||||
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||
|
||||
import de.rwu.easydrop.model.Product;
|
||||
|
||||
/**
|
||||
* Maps between Product, ProductDAO and ProductDTO.
|
||||
* Maps between Product, ProductDTO and ProductDTO.
|
||||
*
|
||||
* TODO implement
|
||||
* @since 0.2.0
|
||||
*
|
||||
* @see Product
|
||||
* @see ProductDTO
|
||||
* @see ProductDAO
|
||||
* @see ProductDTO
|
||||
*/
|
||||
public class ProductMapper {
|
||||
public final class ProductMapper {
|
||||
|
||||
/**
|
||||
* Private constructor to prevent unwanted instantiation.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
private ProductMapper() throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException("This is a mapping class, don't instantiate it.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Product object from a corresponding DTO.
|
||||
*
|
||||
* @param dto Product Data Transfer Object
|
||||
* @return Product
|
||||
*/
|
||||
public static Product mapProductFromDTO(final ProductDTO dto) {
|
||||
Product product = new Product();
|
||||
|
||||
product.setAvailable(dto.isAvailable());
|
||||
product.setCurrentPrice(dto.getCurrentPrice());
|
||||
product.setDataOrigin(dto.getDataOrigin());
|
||||
product.setDeliveryPrice(dto.getDeliveryPrice());
|
||||
product.setMerchant(dto.getMerchant());
|
||||
product.setProductId(dto.getProductId());
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ProductDTO object from a corresponding Product.
|
||||
*
|
||||
* @param product Product
|
||||
* @return ProductDTO
|
||||
*/
|
||||
public static ProductDTO mapProductToDTO(final Product product) {
|
||||
ProductDTO dto = new ProductDTO(product.getProductId(), product.getDataOrigin());
|
||||
|
||||
dto.setAvailable(product.isAvailable());
|
||||
dto.setCurrentPrice(product.getCurrentPrice());
|
||||
dto.setDeliveryPrice(product.getDeliveryPrice());
|
||||
dto.setMerchant(product.getMerchant());
|
||||
|
||||
return dto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Maps different formats of corresponding objects.
|
||||
*
|
||||
* TODO implement
|
||||
* @since 0.2.0
|
||||
*/
|
||||
package de.rwu.easydrop.service.mapping;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Packages for supporting business logic.
|
||||
*
|
||||
* TODO implement
|
||||
* @since 0.2.0
|
||||
*/
|
||||
package de.rwu.easydrop.service;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Supports diverse business processes and enforces business rules.
|
||||
*
|
||||
* TODO implement
|
||||
* @since 0.2.0
|
||||
*/
|
||||
package de.rwu.easydrop.service.processing;
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package de.rwu.easydrop.service.retriever;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import de.rwu.easydrop.exception.InvalidProductException;
|
||||
import de.rwu.easydrop.model.Product;
|
||||
import de.rwu.easydrop.model.ProductCatalogue;
|
||||
import de.rwu.easydrop.util.ProductsConfig;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Retrieves data for all products of multiple catalogues.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
@Data
|
||||
public class CatalogueRetriever {
|
||||
/**
|
||||
* User-configured products.
|
||||
*/
|
||||
private ProductsConfig productsConfig;
|
||||
/**
|
||||
* Product Retriever.
|
||||
*/
|
||||
private ProductRetriever productRetriever;
|
||||
/**
|
||||
* Product catalogue.
|
||||
*/
|
||||
private List<ProductCatalogue> productCatalogues;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param newProductsConfig
|
||||
* @param newProductRetriever
|
||||
*/
|
||||
public CatalogueRetriever(
|
||||
final ProductsConfig newProductsConfig, final ProductRetriever newProductRetriever) {
|
||||
productRetriever = newProductRetriever;
|
||||
productsConfig = newProductsConfig;
|
||||
productCatalogues = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads catalogues as configured by the user.
|
||||
*
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public void loadCatalogues() throws ConfigurationException {
|
||||
productsConfig.loadConfig();
|
||||
|
||||
for (ProductCatalogue pCat : productsConfig.getProductCatalogues()) {
|
||||
ProductCatalogue newProductCatalogue = new ProductCatalogue(
|
||||
pCat.getProductName(), pCat.getDescription());
|
||||
|
||||
for (Product product : pCat.getProducts()) {
|
||||
Product newProduct = new Product();
|
||||
newProduct.setDataOrigin(product.getDataOrigin());
|
||||
newProduct.setProductId(product.getProductId());
|
||||
|
||||
if (newProduct.getDataOrigin().equals("Amazon")) {
|
||||
newProduct = productRetriever.getProductFromAmazon(product.getProductId());
|
||||
} else if (newProduct.getDataOrigin().equals("eBay")) {
|
||||
newProduct = productRetriever.getProductFromEbay(product.getProductId());
|
||||
} else {
|
||||
throw new InvalidProductException("Product data origin is invalid");
|
||||
}
|
||||
|
||||
newProductCatalogue.addProduct(newProduct);
|
||||
}
|
||||
|
||||
productCatalogues.add(newProductCatalogue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package de.rwu.easydrop.service.retriever;
|
||||
|
||||
import de.rwu.easydrop.api.client.AmazonProductDataSource;
|
||||
import de.rwu.easydrop.api.client.DataSourceFactory;
|
||||
import de.rwu.easydrop.api.client.EbayItemDataSource;
|
||||
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
|
||||
import de.rwu.easydrop.model.Product;
|
||||
import de.rwu.easydrop.service.mapping.ProductMapper;
|
||||
import de.rwu.easydrop.service.validation.ProductValidator;
|
||||
|
||||
/**
|
||||
* Retrieves Product Objects from various data sources.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class ProductRetriever {
|
||||
/**
|
||||
* Data source factory.
|
||||
*/
|
||||
private DataSourceFactory dataSourceFactory;
|
||||
|
||||
/**
|
||||
* @param newDataSourceFactory the dataSourceFactory to set
|
||||
*/
|
||||
public void setDataSourceFactory(final DataSourceFactory newDataSourceFactory) {
|
||||
this.dataSourceFactory = newDataSourceFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newDataSourceFactory
|
||||
*/
|
||||
public ProductRetriever(final DataSourceFactory newDataSourceFactory) {
|
||||
this.setDataSourceFactory(newDataSourceFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a product from Amazon.
|
||||
*
|
||||
* @param asin Product identifier
|
||||
* @return Product from Amazon
|
||||
*/
|
||||
public Product getProductFromAmazon(final String asin) {
|
||||
AmazonProductDataSource dataSource = dataSourceFactory.createAmazonProductDataSource();
|
||||
ProductDTO dto = dataSource.getProductDTOById(asin);
|
||||
|
||||
Product product = ProductMapper.mapProductFromDTO(dto);
|
||||
ProductValidator.validate(product);
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a product from eBay.
|
||||
*
|
||||
* @param query Product search query
|
||||
* @return Product from eBay
|
||||
*/
|
||||
public Product getProductFromEbay(final String query) {
|
||||
EbayItemDataSource dataSource = dataSourceFactory.createEbayItemDataSource();
|
||||
ProductDTO dto = dataSource.getProductDTOById(query);
|
||||
|
||||
Product product = ProductMapper.mapProductFromDTO(dto);
|
||||
ProductValidator.validate(product);
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a product from persistence.
|
||||
*
|
||||
* @param productId
|
||||
* @return Product from persistence
|
||||
*/
|
||||
public Product getProductFromPersistence(final String productId) {
|
||||
AbstractProductPersistence src = dataSourceFactory.createProductPersistenceDataSource();
|
||||
|
||||
ProductDTO dto = src.getProductDTOById(productId);
|
||||
Product product = ProductMapper.mapProductFromDTO(dto);
|
||||
ProductValidator.validate(product);
|
||||
|
||||
return product;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Retrieves Objects from a data source.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
package de.rwu.easydrop.service.retriever;
|
||||
@@ -1,10 +1,56 @@
|
||||
package de.rwu.easydrop.service.validation;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import de.rwu.easydrop.exception.InvalidProductException;
|
||||
import de.rwu.easydrop.model.Product;
|
||||
|
||||
/**
|
||||
* Confirms validity of Product data.
|
||||
*
|
||||
* @since 0.1.0
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class ProductValidator {
|
||||
public final class ProductValidator {
|
||||
|
||||
/**
|
||||
* Private constructor to prevent unwanted instantiation.
|
||||
*
|
||||
* @throws UnsupportedOperationException always
|
||||
*/
|
||||
private ProductValidator() throws UnsupportedOperationException {
|
||||
throw new UnsupportedOperationException("This is a validator class, don't instantiate it.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure a Product does not contain invalid information.
|
||||
*
|
||||
* @param product the Product
|
||||
*/
|
||||
public static void validate(final Product product) {
|
||||
if (product.getCurrentPrice() == 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("")) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Supports validation processes.
|
||||
*
|
||||
* TODO implement
|
||||
* @since 0.2.0
|
||||
*/
|
||||
package de.rwu.easydrop.service.validation;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package de.rwu.easydrop.service.writer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
|
||||
import de.rwu.easydrop.model.Product;
|
||||
import de.rwu.easydrop.model.ProductCatalogue;
|
||||
import de.rwu.easydrop.service.mapping.ProductMapper;
|
||||
import de.rwu.easydrop.service.validation.ProductValidator;
|
||||
|
||||
/**
|
||||
* Writes data for all products of multiple catalogues to persistence.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public final class CatalogueWriter {
|
||||
/**
|
||||
* Holds a persistence reference.
|
||||
*/
|
||||
private AbstractProductPersistence persistence;
|
||||
|
||||
/**
|
||||
* Creates new instance.
|
||||
*
|
||||
* @param newPersistence
|
||||
*/
|
||||
public CatalogueWriter(final AbstractProductPersistence newPersistence) {
|
||||
persistence = newPersistence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes all products of specified catalogues to persistence.
|
||||
*
|
||||
* @param catalogues
|
||||
*/
|
||||
public void writeCatalogues(final List<ProductCatalogue> catalogues) {
|
||||
for (ProductCatalogue pCat : catalogues) {
|
||||
for (Product product : pCat.getProducts()) {
|
||||
ProductValidator.validate(product);
|
||||
ProductDTO dto = ProductMapper.mapProductToDTO(product);
|
||||
|
||||
persistence.saveProduct(dto);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package de.rwu.easydrop.service.writer;
|
||||
|
||||
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
|
||||
import de.rwu.easydrop.model.Product;
|
||||
import de.rwu.easydrop.service.mapping.ProductMapper;
|
||||
import de.rwu.easydrop.service.validation.ProductValidator;
|
||||
|
||||
/**
|
||||
* Wrapper for writing product info to persistence.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public class ProductWriter {
|
||||
/**
|
||||
* Persistence.
|
||||
*/
|
||||
private AbstractProductPersistence persistence;
|
||||
|
||||
/**
|
||||
* @param newPersistence the persistence to set
|
||||
*/
|
||||
public void setPersistence(final AbstractProductPersistence newPersistence) {
|
||||
this.persistence = newPersistence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and saves product to persistence.
|
||||
*
|
||||
* @param product
|
||||
*/
|
||||
public void writeProductToPersistence(final Product product) {
|
||||
ProductValidator.validate(product);
|
||||
ProductDTO dto = ProductMapper.mapProductToDTO(product);
|
||||
|
||||
persistence.saveProduct(dto);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Writes Objects to a data store.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
package de.rwu.easydrop.service.writer;
|
||||
@@ -52,11 +52,10 @@ public final class Config {
|
||||
* Returns current config instance.
|
||||
*
|
||||
* @return Config instance
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public static Config getInstance() {
|
||||
if (instance == null) {
|
||||
return new Config();
|
||||
instance = new Config();
|
||||
}
|
||||
|
||||
return instance;
|
||||
@@ -109,4 +108,11 @@ public final class Config {
|
||||
public void setProperty(final String key, final String value) {
|
||||
properties.setProperty(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the config's properties.
|
||||
*/
|
||||
public void reset() {
|
||||
properties = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,4 +27,14 @@ public final class FormattingUtil {
|
||||
public static String formatEuro(final double amount) {
|
||||
return String.format(Locale.GERMAN, "%,.2f", amount) + " €";
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a string URL ready. For now, only spaces are replaced.
|
||||
*
|
||||
* @param str
|
||||
* @return URL-ready string
|
||||
*/
|
||||
public static String urlEncode(final String str) {
|
||||
return str.replace(" ", "+");
|
||||
}
|
||||
}
|
||||
|
||||
142
src/main/java/de/rwu/easydrop/util/ProductsConfig.java
Normal file
142
src/main/java/de/rwu/easydrop/util/ProductsConfig.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package de.rwu.easydrop.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import com.jayway.jsonpath.JsonPath;
|
||||
import com.jayway.jsonpath.JsonPathException;
|
||||
|
||||
import de.rwu.easydrop.model.Product;
|
||||
import de.rwu.easydrop.model.ProductCatalogue;
|
||||
|
||||
/**
|
||||
* Reads the user-specified catalogue of products.
|
||||
*
|
||||
* @since 0.2.0
|
||||
*/
|
||||
public final class ProductsConfig {
|
||||
/**
|
||||
* Products config location.
|
||||
*/
|
||||
private String configLocation = "config/products-config.json";
|
||||
|
||||
/**
|
||||
* @return the configLocation
|
||||
*/
|
||||
public String getConfigLocation() {
|
||||
return configLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newConfigLocation the configLocation to set
|
||||
*/
|
||||
public void setConfigLocation(final String newConfigLocation) {
|
||||
this.configLocation = newConfigLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the configured products.
|
||||
*/
|
||||
private List<ProductCatalogue> productCatalogues;
|
||||
|
||||
/**
|
||||
* @return the product catalogues
|
||||
*/
|
||||
public List<ProductCatalogue> getProductCatalogues() {
|
||||
return productCatalogues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*/
|
||||
private static ProductsConfig instance = null;
|
||||
|
||||
/**
|
||||
* Private constructor to prevent external instantiation.
|
||||
*/
|
||||
private ProductsConfig() {
|
||||
productCatalogues = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current products config instance.
|
||||
*
|
||||
* @return Products Config instance
|
||||
*/
|
||||
public static ProductsConfig getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ProductsConfig();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads user-specified configuration into productCatalogues attribute.
|
||||
*
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public void loadConfig() throws ConfigurationException {
|
||||
try {
|
||||
File jsonFile = new File(configLocation).getAbsoluteFile();
|
||||
ArrayList<HashMap<String, Object>> jsonProducts = JsonPath.read(jsonFile, "$.products");
|
||||
|
||||
setProductCatalogues(jsonProducts);
|
||||
} catch (IOException e) {
|
||||
throw new ConfigurationException("Couldn't load required products config file");
|
||||
} catch (JsonPathException e) {
|
||||
throw new ConfigurationException("Products config is empty or malformed");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads catalogues from JSON.
|
||||
*
|
||||
* @param jsonCatalogues
|
||||
*/
|
||||
private void setProductCatalogues(final ArrayList<HashMap<String, Object>> jsonCatalogues) {
|
||||
for (HashMap<String, Object> productCatalogue : jsonCatalogues) {
|
||||
String name = productCatalogue.get("name").toString();
|
||||
String desc = productCatalogue.get("description").toString();
|
||||
String identifiers = productCatalogue.get("identifiers").toString();
|
||||
|
||||
ProductCatalogue pCat = new ProductCatalogue(name, desc);
|
||||
|
||||
addProductsToCatalogue(identifiers, pCat);
|
||||
productCatalogues.add(pCat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads products from JSON.
|
||||
*
|
||||
* @param jsonIdentifiers
|
||||
* @param pCat
|
||||
*/
|
||||
private void addProductsToCatalogue(final String jsonIdentifiers, final ProductCatalogue pCat) {
|
||||
ArrayList<HashMap<String, Object>> identifiers = JsonPath.read(jsonIdentifiers, "$");
|
||||
|
||||
for (HashMap<String, Object> product : identifiers) {
|
||||
String dataOrigin = product.keySet().iterator().next();
|
||||
String identifier = product.get(dataOrigin).toString();
|
||||
|
||||
Product newProduct = new Product();
|
||||
newProduct.setDataOrigin(dataOrigin);
|
||||
newProduct.setProductId(identifier);
|
||||
|
||||
pCat.addProduct(newProduct);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the contained product catalogues.
|
||||
*/
|
||||
public void reset() {
|
||||
productCatalogues = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* General utility such as formatting helpers.
|
||||
*
|
||||
* TODO implement
|
||||
* @since 0.1.0
|
||||
*/
|
||||
package de.rwu.easydrop.util;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<configuration>
|
||||
<logger name="com.jayway.jsonpath" level="OFF" />
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
@@ -6,7 +8,7 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||
import de.rwu.easydrop.exception.DataSourceException;
|
||||
|
||||
class AmazonProductDataSourceTest {
|
||||
|
||||
@@ -150,6 +151,7 @@ class AmazonProductDataSourceTest {
|
||||
|
||||
AmazonProductDataSource dataSource = mock(AmazonProductDataSource.class);
|
||||
URL mockURL = mock(URL.class);
|
||||
when(dataSource.getDataOrigin()).thenReturn(demoDataOrigin);
|
||||
when(dataSource.createApiUrl(demoProductId)).thenReturn(mockURL);
|
||||
when(dataSource.getProductDTOById(demoProductId)).thenCallRealMethod();
|
||||
HttpURLConnection mockConnection = mock(HttpURLConnection.class);
|
||||
@@ -157,7 +159,7 @@ class AmazonProductDataSourceTest {
|
||||
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_NOT_FOUND);
|
||||
|
||||
// Invoke the method and verify the exception
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||
DataSourceException exception = assertThrows(DataSourceException.class, () -> {
|
||||
dataSource.getProductDTOById(demoProductId);
|
||||
});
|
||||
|
||||
@@ -170,6 +172,7 @@ class AmazonProductDataSourceTest {
|
||||
// Set up the test environment
|
||||
AmazonProductDataSource dataSource = mock(AmazonProductDataSource.class);
|
||||
URL mockURL = mock(URL.class);
|
||||
when(dataSource.getDataOrigin()).thenReturn(demoDataOrigin);
|
||||
when(dataSource.createApiUrl(demoProductId)).thenReturn(mockURL);
|
||||
when(dataSource.getProductDTOById(demoProductId)).thenCallRealMethod();
|
||||
when(dataSource.buildProductDTO(any(), anyString())).thenCallRealMethod();
|
||||
@@ -179,11 +182,23 @@ class AmazonProductDataSourceTest {
|
||||
when(mockConnection.getInputStream()).thenThrow(new IOException());
|
||||
|
||||
// Invoke the method and verify the exception
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||
DataSourceException exception = assertThrows(DataSourceException.class, () -> {
|
||||
dataSource.getProductDTOById(demoProductId);
|
||||
});
|
||||
|
||||
// Verify the exception message
|
||||
assertEquals("Couldn't fulfill Amazon API request", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDataOrigin_ReturnsExpectedDataOrigin() {
|
||||
String dataOrigin = demoDataSource.getDataOrigin();
|
||||
assertEquals(demoDataOrigin, dataOrigin);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getApiKey_ReturnsExpectedApiKey() {
|
||||
String apiKey = demoDataSource.getApiKey();
|
||||
assertEquals(demoApiKey, apiKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package de.rwu.easydrop.api.client;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.sqlite.SQLiteDataSource;
|
||||
|
||||
import de.rwu.easydrop.data.connector.SQLiteConnector;
|
||||
import de.rwu.easydrop.exception.PersistenceException;
|
||||
import de.rwu.easydrop.util.Config;
|
||||
|
||||
class DataSourceFactoryTest {
|
||||
@Mock
|
||||
private Config config;
|
||||
|
||||
private DataSourceFactory dataSourceFactory;
|
||||
|
||||
@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");
|
||||
when(config.getProperty("EBAY_API_URL")).thenReturn("https://api.ebay.com");
|
||||
when(config.getProperty("EBAY_API_KEY")).thenReturn("ebay-api-key");
|
||||
dataSourceFactory = new DataSourceFactory(config);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createAmazonProductDataSource_ReturnsAmazonProductDataSource() {
|
||||
// Act
|
||||
AmazonProductDataSource dataSource = dataSourceFactory.createAmazonProductDataSource();
|
||||
|
||||
// Assert
|
||||
assertEquals("amazon-api-key", dataSource.getApiKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createEbayItemDataSource_ReturnsEbayItemDataSource() {
|
||||
// Act
|
||||
EbayItemDataSource dataSource = dataSourceFactory.createEbayItemDataSource();
|
||||
|
||||
// Assert
|
||||
assertEquals("ebay-api-key", dataSource.getApiKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createProductPersistenceDataSource_NullPersistence() {
|
||||
PersistenceException exception = assertThrows(PersistenceException.class, () -> {
|
||||
dataSourceFactory.createProductPersistenceDataSource();
|
||||
});
|
||||
|
||||
assertEquals("Persistence is not set", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createProductPersistenceDataSource_WorkingPersistence() {
|
||||
dataSourceFactory.setPersistence(new SQLiteConnector(new SQLiteDataSource()));
|
||||
|
||||
assertDoesNotThrow(() -> dataSourceFactory.createProductPersistenceDataSource());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
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.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
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;
|
||||
import de.rwu.easydrop.exception.DataSourceException;
|
||||
|
||||
class EbayItemDataSourceTest {
|
||||
private EbayItemDataSource demoDataSource;
|
||||
|
||||
private static String demoApiKey = "my-api-key";
|
||||
private static String demoApiUrl = "https://www.example.com/api";
|
||||
private static String demoDataOrigin = "eBay";
|
||||
private static String demoQuery = "iPhone";
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
demoDataSource = new EbayItemDataSource(demoApiUrl, demoApiKey);
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConstructor() {
|
||||
// Assert
|
||||
try {
|
||||
Field baseUrlField = EbayItemDataSource.class.getDeclaredField("baseUrl");
|
||||
baseUrlField.setAccessible(true);
|
||||
Assertions.assertEquals(demoApiUrl, baseUrlField.get(demoDataSource));
|
||||
|
||||
Field apiKeyField = EbayItemDataSource.class.getDeclaredField("apiKey");
|
||||
apiKeyField.setAccessible(true);
|
||||
Assertions.assertEquals(demoApiKey, apiKeyField.get(demoDataSource));
|
||||
} catch (NoSuchFieldException e) {
|
||||
Assertions.fail();
|
||||
} catch (IllegalAccessException e) {
|
||||
Assertions.fail();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void createApiUrl_ValidSearchQuery_ReturnsValidUrl() throws MalformedURLException {
|
||||
URL apiUrl = demoDataSource.createApiUrl(demoQuery);
|
||||
|
||||
assertNotNull(apiUrl);
|
||||
assertEquals("https://www.example.com/api/buy/browse/v1/item_summary/search?q=iPhone&limit=1&offset=0",
|
||||
apiUrl.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildProductDTO_ValidJson_ReturnsValidProductDTO() {
|
||||
ProductDTO product = new ProductDTO(demoQuery, demoDataOrigin);
|
||||
String json = "{\"itemSummaries\":[{\"shippingOptions\":[{\"guaranteedDelivery\":true,\"shippingCost\":{\"value\":10}}],\"price\":{\"value\":999.99},\"seller\":{\"username\":\"seller123\"}}]}";
|
||||
|
||||
ProductDTO result = demoDataSource.buildProductDTO(product, json);
|
||||
|
||||
assertEquals(demoDataOrigin, result.getDataOrigin());
|
||||
assertEquals(true, result.isAvailable());
|
||||
assertEquals(999.99, result.getCurrentPrice());
|
||||
assertEquals(10.0, result.getDeliveryPrice());
|
||||
assertEquals("seller123", result.getMerchant());
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildProductDTO_InvalidJson_ReturnsProductDTOWithDefaults() {
|
||||
ProductDTO product = new ProductDTO(demoQuery, demoDataOrigin);
|
||||
String json = "{\"itemSummaries\":[]}"; // Empty JSON to simulate missing data
|
||||
|
||||
ProductDTO result = demoDataSource.buildProductDTO(product, json);
|
||||
|
||||
assertEquals("eBay", result.getDataOrigin());
|
||||
assertEquals(false, result.isAvailable()); // Default value for boolean
|
||||
assertEquals(0.0, result.getCurrentPrice()); // Default value for double
|
||||
assertEquals(0.0, result.getDeliveryPrice()); // Default value for double
|
||||
assertEquals(null, result.getMerchant()); // Default value for String
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDataOrigin_ReturnsExpectedDataOrigin() {
|
||||
String dataOrigin = demoDataSource.getDataOrigin();
|
||||
assertEquals(demoDataOrigin, dataOrigin);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getApiKey_ReturnsExpectedApiKey() {
|
||||
String apiKey = demoDataSource.getApiKey();
|
||||
assertEquals(demoApiKey, apiKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetProductDTOById_failedRequest() throws IOException {
|
||||
// Set up the test environment
|
||||
|
||||
EbayItemDataSource dataSource = mock(EbayItemDataSource.class);
|
||||
URL mockURL = mock(URL.class);
|
||||
when(dataSource.getDataOrigin()).thenReturn(demoDataOrigin);
|
||||
when(dataSource.createApiUrl(demoQuery)).thenReturn(mockURL);
|
||||
when(dataSource.getProductDTOById(demoQuery)).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
|
||||
DataSourceException exception = assertThrows(DataSourceException.class, () -> {
|
||||
dataSource.getProductDTOById(demoQuery);
|
||||
});
|
||||
|
||||
// Verify the exception message
|
||||
assertEquals("Nothing found: eBay API responded with error code 404", exception.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,56 @@
|
||||
package de.rwu.easydrop.api.dto;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
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);
|
||||
void constructor_SetsProductIdAndDataOrigin() {
|
||||
// Arrange
|
||||
String productId = "12345";
|
||||
String dataOrigin = "Amazon";
|
||||
|
||||
String expectedString1 = "ProductDTO{12345 from Merchant A (Amazon) at 19,99 € (available: yes)}";
|
||||
String result1 = product1.toString();
|
||||
// Act
|
||||
ProductDTO productDTO = new ProductDTO(productId, dataOrigin);
|
||||
|
||||
assertEquals(expectedString1, result1);
|
||||
// Assert
|
||||
assertEquals(productId, productDTO.getProductId());
|
||||
assertEquals(dataOrigin, productDTO.getDataOrigin());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToString2() {
|
||||
ProductDTO product2 = new ProductDTO("67890", "eBay");
|
||||
product2.setMerchant("Merchant B");
|
||||
product2.setCurrentPrice(9.99);
|
||||
product2.setAvailable(false);
|
||||
void gettersAndSetters_WorkAsExpected() {
|
||||
// Arrange
|
||||
ProductDTO productDTO = new ProductDTO("12345", "Amazon");
|
||||
|
||||
String expectedString2 = "ProductDTO{67890 from Merchant B (eBay) at 9,99 € (available: no)}";
|
||||
String result2 = product2.toString();
|
||||
// Act and Assert
|
||||
assertEquals("12345", productDTO.getProductId());
|
||||
assertEquals("Amazon", productDTO.getDataOrigin());
|
||||
|
||||
assertEquals(expectedString2, result2);
|
||||
// Modify fields
|
||||
productDTO.setProductId("54321");
|
||||
productDTO.setDataOrigin("eBay");
|
||||
|
||||
// Assert
|
||||
assertEquals("54321", productDTO.getProductId());
|
||||
assertEquals("eBay", productDTO.getDataOrigin());
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultConstructor_SetsDefaultValues() {
|
||||
// Act
|
||||
ProductDTO productDTO = new ProductDTO(null, null);
|
||||
|
||||
// Assert
|
||||
assertNull(productDTO.getProductId());
|
||||
assertNull(productDTO.getDataOrigin());
|
||||
assertEquals(0.0, productDTO.getCurrentPrice());
|
||||
assertNull(productDTO.getMerchant());
|
||||
assertEquals(0.0, productDTO.getDeliveryPrice());
|
||||
assertFalse(productDTO.isAvailable());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package de.rwu.easydrop.data.connector;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
import org.junit.jupiter.api.TestInstance.Lifecycle;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.sqlite.SQLiteDataSource;
|
||||
|
||||
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||
import de.rwu.easydrop.exception.PersistenceException;
|
||||
|
||||
@TestInstance(Lifecycle.PER_CLASS)
|
||||
class SQLiteConnectorTest {
|
||||
private static final String TEST_PRODUCT_ID = "12345";
|
||||
private SQLiteConnector sqliteConnector;
|
||||
|
||||
@Mock
|
||||
private SQLiteDataSource mockDataSource;
|
||||
|
||||
@BeforeAll
|
||||
public void setup() {
|
||||
sqliteConnector = new SQLiteConnector(new SQLiteDataSource());
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void clearDatabase() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveProduct_ValidProduct_SuccessfullySaved() {
|
||||
// Arrange
|
||||
sqliteConnector.clearData();
|
||||
ProductDTO ProductDTO = new ProductDTO(TEST_PRODUCT_ID, "Amazon");
|
||||
ProductDTO.setDataOrigin("Amazon");
|
||||
ProductDTO.setProductId(TEST_PRODUCT_ID);
|
||||
ProductDTO.setCurrentPrice(9.99);
|
||||
ProductDTO.setMerchant("Sample Merchant");
|
||||
ProductDTO.setDeliveryPrice(2.50);
|
||||
ProductDTO.setAvailable(true);
|
||||
|
||||
// Act
|
||||
assertDoesNotThrow(() -> sqliteConnector.saveProduct(ProductDTO));
|
||||
|
||||
// Assert
|
||||
ProductDTO savedProductDTO = sqliteConnector.getProductDTOById(TEST_PRODUCT_ID);
|
||||
assertNotNull(savedProductDTO);
|
||||
assertEquals("Amazon", savedProductDTO.getDataOrigin());
|
||||
assertEquals(TEST_PRODUCT_ID, savedProductDTO.getProductId());
|
||||
assertEquals(9.99, savedProductDTO.getCurrentPrice());
|
||||
assertEquals("Sample Merchant", savedProductDTO.getMerchant());
|
||||
assertEquals(2.50, savedProductDTO.getDeliveryPrice());
|
||||
assertTrue(savedProductDTO.isAvailable());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getProductDTOById_ProductExists_ReturnsProductDTO() {
|
||||
// Arrange
|
||||
sqliteConnector.clearData();
|
||||
insertSampleProduct();
|
||||
|
||||
// Act
|
||||
ProductDTO ProductDTO = sqliteConnector.getProductDTOById(TEST_PRODUCT_ID);
|
||||
|
||||
// Assert
|
||||
assertNotNull(ProductDTO);
|
||||
assertEquals("Amazon", ProductDTO.getDataOrigin());
|
||||
assertEquals(TEST_PRODUCT_ID, ProductDTO.getProductId());
|
||||
assertEquals(9.99, ProductDTO.getCurrentPrice());
|
||||
assertEquals("Sample Merchant", ProductDTO.getMerchant());
|
||||
assertEquals(2.50, ProductDTO.getDeliveryPrice());
|
||||
assertTrue(ProductDTO.isAvailable());
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructor_ThrowsPersistenceException_OnSQLException() {
|
||||
try {
|
||||
doThrow(SQLException.class).when(mockDataSource).getConnection();
|
||||
|
||||
PersistenceException exception = assertThrows(PersistenceException.class, () -> {
|
||||
new SQLiteConnector(mockDataSource);
|
||||
});
|
||||
|
||||
assertEquals("Something went wrong while initializing SQLite DB", exception.getMessage());
|
||||
} catch (SQLException e) {
|
||||
fail("No SQLException should be thrown");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getProductDTOById_ProductDoesNotExist_ReturnsNull() {
|
||||
// Act
|
||||
ProductDTO ProductDTO = sqliteConnector.getProductDTOById("FAKE_ID");
|
||||
|
||||
// Assert
|
||||
assertNull(ProductDTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveProduct_ThrowsPersistenceException_OnSQLException() throws SQLException {
|
||||
// Arrange
|
||||
ProductDTO ProductDTO = new ProductDTO(TEST_PRODUCT_ID, "Amazon");
|
||||
sqliteConnector.setDb(mockDataSource);
|
||||
doThrow(SQLException.class).when(mockDataSource).getConnection();
|
||||
|
||||
// Act and Assert
|
||||
assertThrows(PersistenceException.class, () -> sqliteConnector.saveProduct(ProductDTO));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getProductDTOById_ThrowsPersistenceException_OnSQLException() throws SQLException {
|
||||
// Arrange
|
||||
String productId = "12345";
|
||||
sqliteConnector.setDb(mockDataSource);
|
||||
doThrow(SQLException.class).when(mockDataSource).getConnection();
|
||||
|
||||
// Act and Assert
|
||||
assertThrows(PersistenceException.class, () -> sqliteConnector.getProductDTOById(productId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearData_ThrowsPersistenceException_OnSQLException() throws SQLException {
|
||||
// Arrange
|
||||
sqliteConnector.setDb(mockDataSource);
|
||||
doThrow(SQLException.class).when(mockDataSource).getConnection();
|
||||
|
||||
// Act and Assert
|
||||
assertThrows(PersistenceException.class, () -> sqliteConnector.clearData());
|
||||
}
|
||||
|
||||
private void insertSampleProduct() {
|
||||
ProductDTO ProductDTO = new ProductDTO(TEST_PRODUCT_ID, "Amazon");
|
||||
ProductDTO.setCurrentPrice(9.99);
|
||||
ProductDTO.setMerchant("Sample Merchant");
|
||||
ProductDTO.setDeliveryPrice(2.50);
|
||||
ProductDTO.setAvailable(true);
|
||||
sqliteConnector.saveProduct(ProductDTO);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getDataOrigin_ReturnsCorrectDataOrigin() {
|
||||
// Arrange
|
||||
SQLiteConnector connector = new SQLiteConnector(new SQLiteDataSource());
|
||||
|
||||
// Act
|
||||
String dataOrigin = connector.getDataOrigin();
|
||||
|
||||
// Assert
|
||||
assertEquals("SQLite", dataOrigin);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getApiKey_UnsupportedOperationExceptionThrown() {
|
||||
// Arrange
|
||||
SQLiteConnector connector = new SQLiteConnector(new SQLiteDataSource());
|
||||
|
||||
// Act and Assert
|
||||
assertThrows(UnsupportedOperationException.class, connector::getApiKey);
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildProductDTO_UnsupportedOperationExceptionThrown() {
|
||||
// Arrange
|
||||
SQLiteConnector connector = new SQLiteConnector(new SQLiteDataSource());
|
||||
ProductDTO product = new ProductDTO("ASIN123", "Amazon");
|
||||
String json = "{\"productId\":\"ASIN123\",\"dataOrigin\":\"Amazon\"}";
|
||||
|
||||
// Act and Assert
|
||||
assertThrows(UnsupportedOperationException.class, () -> connector.buildProductDTO(product, json));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createApiUrl_UnsupportedOperationExceptionThrown() {
|
||||
// Arrange
|
||||
SQLiteConnector connector = new SQLiteConnector(new SQLiteDataSource());
|
||||
String productIdentifier = "ASIN123";
|
||||
|
||||
// Act and Assert
|
||||
assertThrows(UnsupportedOperationException.class, () -> connector.createApiUrl(productIdentifier));
|
||||
}
|
||||
}
|
||||
@@ -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 DataSourceExceptionTest {
|
||||
|
||||
@Test
|
||||
void constructor_WithMessage_SetsMessage() {
|
||||
// Arrange
|
||||
String message = "Data source error";
|
||||
|
||||
// Act
|
||||
DataSourceException exception = new DataSourceException(message);
|
||||
|
||||
// Assert
|
||||
assertEquals(message, exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructor_WithMessageAndCause_SetsMessageAndCause() {
|
||||
// Arrange
|
||||
String message = "Data source error";
|
||||
Throwable cause = new IllegalArgumentException("Invalid argument");
|
||||
|
||||
// Act
|
||||
DataSourceException exception = new DataSourceException(message, cause);
|
||||
|
||||
// Assert
|
||||
assertEquals(message, exception.getMessage());
|
||||
assertEquals(cause, exception.getCause());
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructor_WithNullMessage_SetsNullMessage() {
|
||||
// Act
|
||||
DataSourceException exception = new DataSourceException(null);
|
||||
|
||||
// Assert
|
||||
assertEquals(null, exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructor_WithNullCause_SetsNullCause() {
|
||||
// Act
|
||||
DataSourceException exception = new DataSourceException("Data source error", null);
|
||||
|
||||
// Assert
|
||||
assertEquals(null, exception.getCause());
|
||||
}
|
||||
|
||||
@Test
|
||||
void throw_DataSourceException() {
|
||||
// Act and Assert
|
||||
assertThrows(DataSourceException.class, () -> {
|
||||
throw new DataSourceException("Data source error");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 InvalidProductExceptionTest {
|
||||
|
||||
@Test
|
||||
void constructor_WithMessage_SetsMessage() {
|
||||
// Arrange
|
||||
String message = "Invalid product data";
|
||||
|
||||
// Act
|
||||
InvalidProductException exception = new InvalidProductException(message);
|
||||
|
||||
// Assert
|
||||
assertEquals(message, exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructor_WithMessageAndCause_SetsMessageAndCause() {
|
||||
// Arrange
|
||||
String message = "Invalid product data";
|
||||
Throwable cause = new IllegalArgumentException("Invalid argument");
|
||||
|
||||
// Act
|
||||
InvalidProductException exception = new InvalidProductException(message, cause);
|
||||
|
||||
// Assert
|
||||
assertEquals(message, exception.getMessage());
|
||||
assertEquals(cause, exception.getCause());
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructor_WithNullMessage_SetsNullMessage() {
|
||||
// Act
|
||||
InvalidProductException exception = new InvalidProductException(null);
|
||||
|
||||
// Assert
|
||||
assertEquals(null, exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void constructor_WithNullCause_SetsNullCause() {
|
||||
// Act
|
||||
InvalidProductException exception = new InvalidProductException("Invalid product data", null);
|
||||
|
||||
// Assert
|
||||
assertEquals(null, exception.getCause());
|
||||
}
|
||||
|
||||
@Test
|
||||
void throw_InvalidProductException() {
|
||||
// Act and Assert
|
||||
assertThrows(InvalidProductException.class, () -> {
|
||||
throw new InvalidProductException("Invalid product data");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package de.rwu.easydrop.exception;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class PersistenceExceptionTest {
|
||||
|
||||
@Test
|
||||
void testPersistenceExceptionWithMessage() {
|
||||
String errorMessage = "Error occurred during data persistence.";
|
||||
PersistenceException exception = new PersistenceException(errorMessage);
|
||||
|
||||
assertEquals(errorMessage, exception.getMessage());
|
||||
assertNull(exception.getCause());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPersistenceExceptionWithMessageAndCause() {
|
||||
String errorMessage = "Error occurred during data persistence.";
|
||||
Throwable cause = new IllegalArgumentException("Invalid argument.");
|
||||
PersistenceException exception = new PersistenceException(errorMessage, cause);
|
||||
|
||||
assertEquals(errorMessage, exception.getMessage());
|
||||
assertEquals(cause, exception.getCause());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package de.rwu.easydrop.model;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ProductCatalogueTest {
|
||||
private ProductCatalogue productCatalogue;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
productCatalogue = new ProductCatalogue("GPU", "Graphics Processing Units");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddProduct() {
|
||||
Product product = new Product();
|
||||
product.setProductId("12345");
|
||||
product.setMerchant("AmazonSeller");
|
||||
product.setDataOrigin("Amazon");
|
||||
productCatalogue.addProduct(product);
|
||||
|
||||
List<Product> products = productCatalogue.getProducts();
|
||||
Assertions.assertEquals(1, products.size());
|
||||
Assertions.assertEquals(product, products.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveProduct() {
|
||||
Product product1 = new Product();
|
||||
product1.setProductId("12345");
|
||||
product1.setMerchant("AmazonSeller");
|
||||
product1.setDataOrigin("Amazon");
|
||||
productCatalogue.addProduct(product1);
|
||||
|
||||
Product product2 = new Product();
|
||||
product2.setProductId("54321");
|
||||
product2.setMerchant("eBaySeller");
|
||||
product2.setDataOrigin("eBay");
|
||||
productCatalogue.addProduct(product2);
|
||||
|
||||
productCatalogue.removeProduct(product1);
|
||||
|
||||
List<Product> products = productCatalogue.getProducts();
|
||||
Assertions.assertEquals(1, products.size());
|
||||
Assertions.assertEquals(product2, products.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClearProducts() {
|
||||
Product product1 = new Product();
|
||||
product1.setProductId("12345");
|
||||
product1.setMerchant("AmazonSeller");
|
||||
product1.setDataOrigin("Amazon");
|
||||
productCatalogue.addProduct(product1);
|
||||
|
||||
Product product2 = new Product();
|
||||
product2.setProductId("54321");
|
||||
product2.setMerchant("eBay");
|
||||
product2.setDataOrigin("eBay");
|
||||
productCatalogue.addProduct(product2);
|
||||
|
||||
productCatalogue.clearProducts();
|
||||
|
||||
List<Product> products = productCatalogue.getProducts();
|
||||
Assertions.assertTrue(products.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToString() {
|
||||
Product product1 = new Product();
|
||||
product1.setProductId("12345");
|
||||
product1.setMerchant("AmazonSeller");
|
||||
product1.setDataOrigin("Amazon");
|
||||
productCatalogue.addProduct(product1);
|
||||
|
||||
Product product2 = new Product();
|
||||
product2.setProductId("54321");
|
||||
product2.setMerchant("eBaySeller");
|
||||
product2.setDataOrigin("eBay");
|
||||
productCatalogue.addProduct(product2);
|
||||
|
||||
String expectedString = "Product Catalogue: GPU\n" +
|
||||
"Description: Graphics Processing Units\n" +
|
||||
"Products:\n" +
|
||||
"Product: [12345 from AmazonSeller (Amazon) at 0,00 € (available: no)]\n" +
|
||||
"Product: [54321 from eBaySeller (eBay) at 0,00 € (available: no)]\n";
|
||||
|
||||
Assertions.assertEquals(expectedString, productCatalogue.toString());
|
||||
}
|
||||
}
|
||||
75
src/test/java/de/rwu/easydrop/model/ProductTest.java
Normal file
75
src/test/java/de/rwu/easydrop/model/ProductTest.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package de.rwu.easydrop.model;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ProductTest {
|
||||
@Test
|
||||
void testToString1() {
|
||||
Product product1 = new Product();
|
||||
product1.setDataOrigin("Amazon");
|
||||
product1.setProductId("12345");
|
||||
product1.setMerchant("Merchant A");
|
||||
product1.setCurrentPrice(19.99);
|
||||
product1.setAvailable(true);
|
||||
|
||||
String expectedString1 = "Product: [12345 from Merchant A (Amazon) at 19,99 € (available: yes)]";
|
||||
String result1 = product1.toString();
|
||||
|
||||
assertEquals(expectedString1, result1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToString2() {
|
||||
Product product2 = new Product();
|
||||
product2.setDataOrigin("eBay");
|
||||
product2.setProductId("67890");
|
||||
product2.setMerchant("Merchant B");
|
||||
product2.setCurrentPrice(9.99);
|
||||
product2.setAvailable(false);
|
||||
|
||||
String expectedString2 = "Product: [67890 from Merchant B (eBay) at 9,99 € (available: no)]";
|
||||
String result2 = product2.toString();
|
||||
|
||||
assertEquals(expectedString2, result2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void gettersAndSetters_WorkAsExpected() {
|
||||
// Arrange
|
||||
Product product = new Product();
|
||||
product.setDataOrigin("Amazon");
|
||||
product.setProductId("12345");
|
||||
product.setCurrentPrice(9.99);
|
||||
product.setMerchant("Example Merchant");
|
||||
product.setDeliveryPrice(2.50);
|
||||
product.setAvailable(true);
|
||||
|
||||
// Act and Assert
|
||||
assertEquals("Amazon", product.getDataOrigin());
|
||||
assertEquals("12345", product.getProductId());
|
||||
assertEquals(9.99, product.getCurrentPrice());
|
||||
assertEquals("Example Merchant", product.getMerchant());
|
||||
assertEquals(2.50, product.getDeliveryPrice());
|
||||
assertTrue(product.isAvailable());
|
||||
|
||||
// Modify fields
|
||||
product.setDataOrigin("eBay");
|
||||
product.setProductId("54321");
|
||||
product.setCurrentPrice(19.99);
|
||||
product.setMerchant("New Merchant");
|
||||
product.setDeliveryPrice(3.50);
|
||||
product.setAvailable(false);
|
||||
|
||||
// Assert
|
||||
assertEquals("eBay", product.getDataOrigin());
|
||||
assertEquals("54321", product.getProductId());
|
||||
assertEquals(19.99, product.getCurrentPrice());
|
||||
assertEquals("New Merchant", product.getMerchant());
|
||||
assertEquals(3.50, product.getDeliveryPrice());
|
||||
assertFalse(product.isAvailable());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
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.ProductDTO;
|
||||
|
||||
import de.rwu.easydrop.model.Product;
|
||||
|
||||
class ProductMapperTest {
|
||||
@Test
|
||||
void testConstructorIsPrivate()
|
||||
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||
// Check for private constructor
|
||||
Constructor<ProductMapper> constructor = ProductMapper.class.getDeclaredConstructor();
|
||||
assertTrue(Modifier.isPrivate(constructor.getModifiers()));
|
||||
|
||||
// Make sure exception is thrown when instantiating
|
||||
constructor.setAccessible(true);
|
||||
assertThrows(InvocationTargetException.class, () -> {
|
||||
constructor.newInstance();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapProductFromDTO_ReturnsProductWithMappedFields() {
|
||||
// Arrange
|
||||
ProductDTO dto = createProductDTO();
|
||||
|
||||
// Act
|
||||
Product product = ProductMapper.mapProductFromDTO(dto);
|
||||
|
||||
// Assert
|
||||
assertEquals(dto.isAvailable(), product.isAvailable());
|
||||
assertEquals(dto.getCurrentPrice(), product.getCurrentPrice());
|
||||
assertEquals(dto.getDataOrigin(), product.getDataOrigin());
|
||||
assertEquals(dto.getDeliveryPrice(), product.getDeliveryPrice());
|
||||
assertEquals(dto.getMerchant(), product.getMerchant());
|
||||
assertEquals(dto.getProductId(), product.getProductId());
|
||||
}
|
||||
|
||||
private ProductDTO createProductDTO() {
|
||||
ProductDTO dto = new ProductDTO("12345", "Amazon");
|
||||
dto.setAvailable(true);
|
||||
dto.setCurrentPrice(9.99);
|
||||
dto.setDeliveryPrice(2.50);
|
||||
dto.setMerchant("Example Merchant");
|
||||
return dto;
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapProductToDTO() {
|
||||
// Arrange
|
||||
Product product = new Product();
|
||||
product.setProductId("12345");
|
||||
product.setDataOrigin("Amazon");
|
||||
product.setAvailable(true);
|
||||
product.setCurrentPrice(9.99);
|
||||
product.setDeliveryPrice(2.50);
|
||||
product.setMerchant("Seller1");
|
||||
|
||||
// Act
|
||||
ProductDTO dto = ProductMapper.mapProductToDTO(product);
|
||||
|
||||
// Assert
|
||||
assertEquals("12345", dto.getProductId());
|
||||
assertEquals("Amazon", dto.getDataOrigin());
|
||||
assertTrue(dto.isAvailable());
|
||||
assertEquals(9.99, dto.getCurrentPrice());
|
||||
assertEquals(2.50, dto.getDeliveryPrice());
|
||||
assertEquals("Seller1", dto.getMerchant());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package de.rwu.easydrop.service.retriever;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import de.rwu.easydrop.exception.InvalidProductException;
|
||||
import de.rwu.easydrop.model.Product;
|
||||
import de.rwu.easydrop.model.ProductCatalogue;
|
||||
import de.rwu.easydrop.util.ProductsConfig;
|
||||
|
||||
class CatalogueRetrieverTest {
|
||||
private ProductsConfig productsConfig;
|
||||
private ProductRetriever productRetriever;
|
||||
private CatalogueRetriever catalogueRetriever;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
productsConfig = mock(ProductsConfig.class);
|
||||
productRetriever = mock(ProductRetriever.class);
|
||||
catalogueRetriever = new CatalogueRetriever(productsConfig, productRetriever);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadCatalogues_ValidConfig_CataloguesLoaded() throws ConfigurationException {
|
||||
// Arrange
|
||||
List<ProductCatalogue> productCatalogues = new ArrayList<>();
|
||||
|
||||
// Create a sample product catalogue with two products
|
||||
ProductCatalogue productCatalogue = new ProductCatalogue("Catalogue 1", "Sample catalogue");
|
||||
Product product1 = new Product();
|
||||
product1.setDataOrigin("Amazon");
|
||||
product1.setProductId("ASIN1");
|
||||
productCatalogue.addProduct(product1);
|
||||
|
||||
Product product2 = new Product();
|
||||
product2.setDataOrigin("eBay");
|
||||
product2.setProductId("ProductID2");
|
||||
productCatalogue.addProduct(product2);
|
||||
productCatalogues.add(productCatalogue);
|
||||
|
||||
// Mock the methods
|
||||
when(productsConfig.getProductCatalogues()).thenReturn(productCatalogues);
|
||||
when(productRetriever.getProductFromAmazon("ASIN1")).thenReturn(product1);
|
||||
when(productRetriever.getProductFromEbay("ProductID2")).thenReturn(product2);
|
||||
|
||||
// Act
|
||||
catalogueRetriever.loadCatalogues();
|
||||
|
||||
// Assert
|
||||
List<ProductCatalogue> loadedCatalogues = catalogueRetriever.getProductCatalogues();
|
||||
assertEquals(1, loadedCatalogues.size());
|
||||
|
||||
ProductCatalogue loadedCatalogue = loadedCatalogues.get(0);
|
||||
assertEquals("Catalogue 1", loadedCatalogue.getProductName());
|
||||
assertEquals("Sample catalogue", loadedCatalogue.getDescription());
|
||||
List<Product> loadedProducts = loadedCatalogue.getProducts();
|
||||
assertEquals(2, loadedProducts.size());
|
||||
assertEquals(product1, loadedProducts.get(0));
|
||||
assertEquals(product2, loadedProducts.get(1));
|
||||
|
||||
// Verify the method invocations
|
||||
verify(productsConfig).loadConfig();
|
||||
verify(productRetriever).getProductFromAmazon("ASIN1");
|
||||
verify(productRetriever).getProductFromEbay("ProductID2");
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package de.rwu.easydrop.service.retriever;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
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.ProductDTO;
|
||||
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
|
||||
import de.rwu.easydrop.model.Product;
|
||||
import de.rwu.easydrop.util.Config;
|
||||
|
||||
class ProductRetrieverTest {
|
||||
@Mock
|
||||
private Config config;
|
||||
@Mock
|
||||
private DataSourceFactory dataSourceFactory;
|
||||
@Mock
|
||||
private AmazonProductDataSource amazonDataSource;
|
||||
@Mock
|
||||
private EbayItemDataSource ebayDataSource;
|
||||
@Mock
|
||||
private ProductDTO productDTO;
|
||||
@Mock
|
||||
private Product product;
|
||||
@Mock
|
||||
private AbstractProductPersistence persistence;
|
||||
|
||||
private ProductRetriever productRetriever;
|
||||
|
||||
@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);
|
||||
productRetriever = new ProductRetriever(dataSourceFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getProductFromAmazon_ReturnsProduct() {
|
||||
// Arrange
|
||||
String asin = "B01234ABC";
|
||||
when(dataSourceFactory.createAmazonProductDataSource()).thenReturn(amazonDataSource);
|
||||
when(amazonDataSource.getProductDTOById(asin)).thenReturn(productDTO);
|
||||
when(productDTO.getProductId()).thenReturn(asin);
|
||||
when(productDTO.getCurrentPrice()).thenReturn(9.99);
|
||||
when(productDTO.getDataOrigin()).thenReturn("Amazon");
|
||||
|
||||
// Act
|
||||
Product result = productRetriever.getProductFromAmazon(asin);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
assertEquals(asin, result.getProductId());
|
||||
assertEquals(9.99, result.getCurrentPrice());
|
||||
verify(amazonDataSource, times(1)).getProductDTOById(asin);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getProductFromEbay_ReturnsProduct() {
|
||||
// Arrange
|
||||
String productQuery = "MySearchQuery";
|
||||
when(dataSourceFactory.createEbayItemDataSource()).thenReturn(ebayDataSource);
|
||||
when(ebayDataSource.getProductDTOById(productQuery)).thenReturn(productDTO);
|
||||
when(productDTO.getProductId()).thenReturn(productQuery);
|
||||
when(productDTO.getCurrentPrice()).thenReturn(9.99);
|
||||
when(productDTO.getDataOrigin()).thenReturn("eBay");
|
||||
|
||||
// Act
|
||||
Product result = productRetriever.getProductFromEbay(productQuery);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
assertEquals(productQuery, result.getProductId());
|
||||
assertEquals(9.99, result.getCurrentPrice());
|
||||
verify(ebayDataSource, times(1)).getProductDTOById(productQuery);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getProductFromPersistence_ValidProductId_ReturnsProduct() {
|
||||
// Arrange
|
||||
String productId = "123";
|
||||
when(dataSourceFactory.createProductPersistenceDataSource()).thenReturn(persistence);
|
||||
when(persistence.getProductDTOById(productId)).thenReturn(productDTO);
|
||||
when(productDTO.getProductId()).thenReturn(productId);
|
||||
when(productDTO.getCurrentPrice()).thenReturn(9.99);
|
||||
when(productDTO.getDataOrigin()).thenReturn("Amazon");
|
||||
|
||||
// Act
|
||||
Product result = productRetriever.getProductFromPersistence(productId);
|
||||
|
||||
// Assert
|
||||
assertEquals(productId, result.getProductId());
|
||||
assertEquals(9.99, result.getCurrentPrice());
|
||||
|
||||
// Verify the interactions
|
||||
verify(persistence, times(1)).getProductDTOById(productId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package de.rwu.easydrop.service.validation;
|
||||
|
||||
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.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.InvalidProductException;
|
||||
import de.rwu.easydrop.model.Product;
|
||||
|
||||
class ProductValidatorTest {
|
||||
|
||||
@Test
|
||||
void testConstructorIsPrivate()
|
||||
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||
// Check for private constructor
|
||||
Constructor<ProductValidator> constructor = ProductValidator.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_ValidProduct_NoExceptionThrown() {
|
||||
// Arrange
|
||||
Product product = new Product();
|
||||
product.setCurrentPrice(9.99);
|
||||
product.setDataOrigin("Amazon");
|
||||
product.setProductId("12345");
|
||||
|
||||
// Act and Assert
|
||||
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
|
||||
@MethodSource("invalidProductProvider")
|
||||
void validate_InvalidProduct_ThrowsInvalidProductException(Product product) {
|
||||
// Act and Assert
|
||||
assertThrows(InvalidProductException.class, () -> ProductValidator.validate(product));
|
||||
}
|
||||
|
||||
static Stream<Product> invalidProductProvider() {
|
||||
return Stream.of(
|
||||
createProductWithZeroPrice(),
|
||||
createProductWithUnknownDataOrigin(),
|
||||
createProductWithEmptyProductId());
|
||||
}
|
||||
|
||||
private static Product createProductWithZeroPrice() {
|
||||
Product product = new Product();
|
||||
product.setCurrentPrice(0.00);
|
||||
product.setDataOrigin("Amazon");
|
||||
product.setProductId("12345");
|
||||
return product;
|
||||
}
|
||||
|
||||
private static Product createProductWithUnknownDataOrigin() {
|
||||
Product product = new Product();
|
||||
product.setCurrentPrice(9.99);
|
||||
product.setDataOrigin("UnknownOrigin");
|
||||
product.setProductId("12345");
|
||||
return product;
|
||||
}
|
||||
|
||||
private static Product createProductWithEmptyProductId() {
|
||||
Product product = new Product();
|
||||
product.setCurrentPrice(9.99);
|
||||
product.setDataOrigin("Amazon");
|
||||
product.setProductId("");
|
||||
return product;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package de.rwu.easydrop.service.writer;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
|
||||
import de.rwu.easydrop.model.Product;
|
||||
import de.rwu.easydrop.model.ProductCatalogue;
|
||||
|
||||
class CatalogueWriterTest {
|
||||
|
||||
@Mock
|
||||
private AbstractProductPersistence persistenceMock;
|
||||
|
||||
private CatalogueWriter catalogueWriter;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
catalogueWriter = new CatalogueWriter(persistenceMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeCatalogues_ValidCatalogues_ProductsWrittenToPersistence() {
|
||||
// Arrange
|
||||
List<ProductCatalogue> catalogues = createSampleCatalogues();
|
||||
|
||||
// Act
|
||||
catalogueWriter.writeCatalogues(catalogues);
|
||||
|
||||
// Assert
|
||||
verify(persistenceMock, times(4)).saveProduct(any(ProductDTO.class));
|
||||
}
|
||||
|
||||
private List<ProductCatalogue> createSampleCatalogues() {
|
||||
List<ProductCatalogue> catalogues = new ArrayList<>();
|
||||
|
||||
ProductCatalogue catalogue1 = new ProductCatalogue("Catalogue 1", "Sample catalogue 1");
|
||||
catalogue1.addProduct(createSampleProduct("Amazon", "ID 1"));
|
||||
catalogue1.addProduct(createSampleProduct("eBay", "ID 2"));
|
||||
|
||||
ProductCatalogue catalogue2 = new ProductCatalogue("Catalogue 2", "Sample catalogue 2");
|
||||
catalogue2.addProduct(createSampleProduct("Amazon", "ID 3"));
|
||||
catalogue2.addProduct(createSampleProduct("eBay", "ID 4"));
|
||||
|
||||
catalogues.add(catalogue1);
|
||||
catalogues.add(catalogue2);
|
||||
|
||||
return catalogues;
|
||||
}
|
||||
|
||||
private Product createSampleProduct(String dataOrigin, String productId) {
|
||||
Product product = new Product();
|
||||
product.setDataOrigin(dataOrigin);
|
||||
product.setProductId(productId);
|
||||
product.setCurrentPrice(9999.99);
|
||||
return product;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package de.rwu.easydrop.service.writer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||
import de.rwu.easydrop.data.connector.AbstractProductPersistence;
|
||||
import de.rwu.easydrop.model.Product;
|
||||
|
||||
class ProductWriterTest {
|
||||
|
||||
@Mock
|
||||
private AbstractProductPersistence persistence;
|
||||
|
||||
private ProductWriter productWriter;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
productWriter = new ProductWriter();
|
||||
productWriter.setPersistence(persistence);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeProductToPersistence_ValidProduct_CallsSaveProduct() {
|
||||
// Arrange
|
||||
Product product = new Product();
|
||||
product.setProductId("12345");
|
||||
product.setDataOrigin("Amazon");
|
||||
product.setCurrentPrice(9.99);
|
||||
|
||||
// Act
|
||||
productWriter.writeProductToPersistence(product);
|
||||
|
||||
// Assert
|
||||
Mockito.verify(persistence).saveProduct(any(ProductDTO.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeProductToPersistence_InvalidProduct_ThrowsException() {
|
||||
// Arrange
|
||||
Product product = new Product();
|
||||
product.setProductId("");
|
||||
product.setDataOrigin("Amazon");
|
||||
|
||||
// Act and Assert
|
||||
assertThrows(Exception.class, () -> productWriter.writeProductToPersistence(product));
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.rwu.easydrop.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
@@ -11,9 +12,9 @@ import javax.naming.ConfigurationException;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class ConfigImplTest {
|
||||
class ConfigIntegrationTest {
|
||||
private Config config;
|
||||
private final static String TESTDATA_PATH = "testResources/testdata.properties";
|
||||
private final static String TESTDATA_PATH = "src/test/resources/test.config.properties";
|
||||
private final static String TESTDATA_KEY = "API_KEY";
|
||||
private final static String TESTDATA_VAL = "keyIsHere";
|
||||
|
||||
@@ -34,7 +35,9 @@ class ConfigImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetProperty_ConfigNotLoaded() {
|
||||
void testGetProperty_ConfigNotLoaded() throws Exception {
|
||||
config.reset();
|
||||
|
||||
NoSuchElementException exception = assertThrows(NoSuchElementException.class, () -> {
|
||||
config.getProperty(TESTDATA_KEY);
|
||||
});
|
||||
@@ -74,7 +77,7 @@ class ConfigImplTest {
|
||||
@Test
|
||||
void testLoadConfigSuccessfully() {
|
||||
try {
|
||||
config.setConfigLocation("testResources/testdata.properties");
|
||||
config.setConfigLocation("src/test/resources/test.config.properties");
|
||||
config.loadConfig();
|
||||
assertEquals(TESTDATA_VAL, config.getProperty(TESTDATA_KEY));
|
||||
} catch (ConfigurationException e) {
|
||||
@@ -92,4 +95,19 @@ class ConfigImplTest {
|
||||
|
||||
assertEquals("Couldn't load required config file", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReset() throws ConfigurationException {
|
||||
config.setConfigLocation("src/test/resources/test.config.properties");
|
||||
config.loadConfig();
|
||||
|
||||
assertNotNull(config.getProperty(TESTDATA_KEY));
|
||||
config.reset();
|
||||
|
||||
NoSuchElementException exception = assertThrows(NoSuchElementException.class, () -> {
|
||||
config.getProperty(TESTDATA_KEY);
|
||||
});
|
||||
|
||||
assertEquals("Config has not been loaded", exception.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package de.rwu.easydrop.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
@@ -20,17 +21,18 @@ class ConfigTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetInstanceNull() {
|
||||
config = null;
|
||||
|
||||
void testGetInstance() {
|
||||
Config newConfig = Config.getInstance();
|
||||
assertNotNull(newConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetInstanceNotNull() {
|
||||
Config newConfig = Config.getInstance();
|
||||
assertNotNull(newConfig);
|
||||
void testGetInstanceEquality() {
|
||||
// Create "two" instances to check validity of Singleton pattern
|
||||
Config instance1 = Config.getInstance();
|
||||
Config instance2 = Config.getInstance();
|
||||
|
||||
assertSame(instance1, instance2, "Instances should be equal");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package de.rwu.easydrop.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import de.rwu.easydrop.model.ProductCatalogue;
|
||||
|
||||
class ProductsConfigIntegrationTest {
|
||||
private ProductsConfig config;
|
||||
private final static String TESTDATA_PATH = "src/test/resources/test.products-config.json";
|
||||
private final static String TESTDATA_EMPTY_PATH = "src/test/resources/test.empty.products-config.json";
|
||||
private final static String TESTDATA_MALFORMED_PATH = "src/test/resources/test.malformed.products-config.json";
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
config = ProductsConfig.getInstance();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadConfigSuccessfully() {
|
||||
try {
|
||||
config.setConfigLocation(TESTDATA_PATH);
|
||||
config.loadConfig();
|
||||
String value = config.getProductCatalogues().get(0).getProductName();
|
||||
|
||||
assertEquals("Demo Product", value);
|
||||
} catch (ConfigurationException e) {
|
||||
fail("ConfigurationException should not be thrown");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadConfigMissingFile() {
|
||||
config.setConfigLocation("path/that/doesnt/exist/products-config.json");
|
||||
|
||||
ConfigurationException exception = assertThrows(ConfigurationException.class, () -> {
|
||||
config.loadConfig();
|
||||
});
|
||||
|
||||
assertEquals("Couldn't load required products config file", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadConfigEmptyFile() {
|
||||
config.setConfigLocation(TESTDATA_EMPTY_PATH);
|
||||
|
||||
ConfigurationException exception = assertThrows(ConfigurationException.class, () -> {
|
||||
config.loadConfig();
|
||||
});
|
||||
|
||||
assertEquals("Products config is empty or malformed", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadConfigMalformedFile() {
|
||||
config.setConfigLocation(TESTDATA_MALFORMED_PATH);
|
||||
|
||||
ConfigurationException exception = assertThrows(ConfigurationException.class, () -> {
|
||||
config.loadConfig();
|
||||
});
|
||||
|
||||
assertEquals("Products config is empty or malformed", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReset() {
|
||||
try {
|
||||
config.setConfigLocation(TESTDATA_PATH);
|
||||
config.loadConfig();
|
||||
} catch (ConfigurationException e) {
|
||||
fail("ConfigurationException should not be thrown");
|
||||
}
|
||||
|
||||
assertNotNull(config.getProductCatalogues());
|
||||
config.reset();
|
||||
List<ProductCatalogue> pCats = config.getProductCatalogues();
|
||||
|
||||
assertEquals(0, pCats.size(), "Catalogues list should be empty");
|
||||
}
|
||||
}
|
||||
44
src/test/java/de/rwu/easydrop/util/ProductsConfigTest.java
Normal file
44
src/test/java/de/rwu/easydrop/util/ProductsConfigTest.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package de.rwu.easydrop.util;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
class ProductsConfigTest {
|
||||
private ProductsConfig productsConfig;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws ConfigurationException {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
productsConfig = spy(ProductsConfig.getInstance());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetInstance() {
|
||||
ProductsConfig newConfig = ProductsConfig.getInstance();
|
||||
assertNotNull(newConfig);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGetInstanceEquality() {
|
||||
// Create "two" instances to check validity of Singleton pattern
|
||||
ProductsConfig instance1 = ProductsConfig.getInstance();
|
||||
ProductsConfig instance2 = ProductsConfig.getInstance();
|
||||
|
||||
assertSame(instance1, instance2, "Instances should be equal");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSetConfigLocation() {
|
||||
String newPath = "new/location/config.properties";
|
||||
productsConfig.setConfigLocation(newPath);
|
||||
assertEquals(newPath, productsConfig.getConfigLocation());
|
||||
}
|
||||
}
|
||||
5
src/test/resources/test.config.properties
Normal file
5
src/test/resources/test.config.properties
Normal file
@@ -0,0 +1,5 @@
|
||||
API_KEY=keyIsHere
|
||||
AMAZON_API_URL=THE_AMAZON_API_URL
|
||||
AMAZON_API_KEY=THE_AMAZON_API_KEY
|
||||
EBAY_API_URL=THE_EBAY_API_URL
|
||||
EBAY_API_KEY=THE_EBAY_API_KEY
|
||||
1
src/test/resources/test.empty.properties
Normal file
1
src/test/resources/test.empty.properties
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
15
src/test/resources/test.malformed.products-config.json
Normal file
15
src/test/resources/test.malformed.products-config.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"products": [
|
||||
{
|
||||
"name": "Demo Product",
|
||||
description: "Integration Testing Product",
|
||||
"identifiers": [
|
||||
{
|
||||
"Amazon": "DEMO-AMAZON-001"
|
||||
},
|
||||
{
|
||||
"eBay": "DEMO-EBAY-001"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
16
src/test/resources/test.products-config.json
Normal file
16
src/test/resources/test.products-config.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"products": [
|
||||
{
|
||||
"name": "Demo Product",
|
||||
"description": "Integration Testing Product",
|
||||
"identifiers": [
|
||||
{
|
||||
"Amazon": "DEMO-AMAZON-001"
|
||||
},
|
||||
{
|
||||
"eBay": "DEMO-EBAY-001"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
API_KEY=keyIsHere
|
||||
Reference in New Issue
Block a user