Amazon and eBay Buy/Sell APIs
This commit is contained in:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -4,5 +4,6 @@
|
|||||||
"connectionId": "LocalSonarQube",
|
"connectionId": "LocalSonarQube",
|
||||||
"projectKey": "EasyDrop"
|
"projectKey": "EasyDrop"
|
||||||
},
|
},
|
||||||
"java.debug.settings.onBuildFailureProceed": true
|
"java.debug.settings.onBuildFailureProceed": true,
|
||||||
|
"java.checkstyle.configuration": "${workspaceFolder}\\config\\custom-checkstyle.xml"
|
||||||
}
|
}
|
||||||
|
|||||||
11
CHANGELOG.md
11
CHANGELOG.md
@@ -4,11 +4,12 @@
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added script for local UML generation (#56)
|
- Script for local UML generation (#56)
|
||||||
|
- Transaction fulfillment API classes (#71)
|
||||||
### Removed
|
- `AmazonSeller`
|
||||||
|
- `AmazonPurchaser`
|
||||||
- Document stage from CI run (#56)
|
- `EbaySeller`
|
||||||
|
- `EbayPurchaser`
|
||||||
|
|
||||||
## 0.2.0
|
## 0.2.0
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ https://www.oracle.com/java/technologies/javase/codeconventions-contents.html
|
|||||||
|
|
||||||
<property name="fileExtensions" value="java, properties, xml" />
|
<property name="fileExtensions" value="java, properties, xml" />
|
||||||
|
|
||||||
|
<module name="SuppressionFilter">
|
||||||
|
<property name="file" value="config/suppressions.xml" />
|
||||||
|
<property name="optional" value="false" />
|
||||||
|
</module>
|
||||||
|
|
||||||
<!-- Excludes all 'module-info.java' files -->
|
<!-- Excludes all 'module-info.java' files -->
|
||||||
<!-- See https://checkstyle.org/config_filefilters.html -->
|
<!-- See https://checkstyle.org/config_filefilters.html -->
|
||||||
<module name="BeforeExecutionExclusionFileFilter">
|
<module name="BeforeExecutionExclusionFileFilter">
|
||||||
|
|||||||
6
config/suppressions.xml
Normal file
6
config/suppressions.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE suppressions PUBLIC "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN" "https://checkstyle.org/dtds/suppressions_1_2.dtd">
|
||||||
|
|
||||||
|
<suppressions>
|
||||||
|
<suppress checks=".*" files=".*[\\/]src[\\/](test)[\\/]" />
|
||||||
|
</suppressions>
|
||||||
@@ -42,7 +42,7 @@ public abstract class AbstractDataSource implements DataSource {
|
|||||||
protected abstract ProductDTO buildProductDTO(ProductDTO product, String json);
|
protected abstract ProductDTO buildProductDTO(ProductDTO product, String json);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overridable standard implementation.
|
* Pulls product data from a remote data source.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ProductDTO getProductDTOById(final String productIdentifier)
|
public ProductDTO getProductDTOById(final String productIdentifier)
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package de.rwu.easydrop.api.client;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||||
|
import de.rwu.easydrop.exception.DataWriterException;
|
||||||
|
import de.rwu.easydrop.util.FormattingUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes data to a remote API.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractDataWriter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data source's API key.
|
||||||
|
*
|
||||||
|
* @return Data source API key
|
||||||
|
*/
|
||||||
|
protected abstract String getApiKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the target API's name.
|
||||||
|
*
|
||||||
|
* @return Data target API name
|
||||||
|
*/
|
||||||
|
protected abstract String getDataTarget();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a put request to the API.
|
||||||
|
*
|
||||||
|
* @param dto Product data transfer object
|
||||||
|
* @param apiType API Type, like Purchase or Sales
|
||||||
|
*/
|
||||||
|
protected void sendPutRequest(final ProductDTO dto, final String apiType) {
|
||||||
|
String apiKey = getApiKey();
|
||||||
|
String dataTarget = getDataTarget();
|
||||||
|
|
||||||
|
if (!dataTarget.equals(dto.getDataOrigin())) {
|
||||||
|
throw new DataWriterException(
|
||||||
|
"Product data source and target " + apiType + " API are incompatible.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String urlReadyIdentifier = FormattingUtil.urlEncode(dto.getProductId());
|
||||||
|
URL apiUrl = createApiUrl(urlReadyIdentifier);
|
||||||
|
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) apiUrl.openConnection();
|
||||||
|
connection.setRequestMethod("PUT");
|
||||||
|
connection.setRequestProperty("Credential", apiKey);
|
||||||
|
|
||||||
|
int responseCode = connection.getResponseCode();
|
||||||
|
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||||
|
throw new DataWriterException(
|
||||||
|
dataTarget
|
||||||
|
+ " "
|
||||||
|
+ apiType
|
||||||
|
+ " API responded with error code "
|
||||||
|
+ responseCode);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new DataWriterException(
|
||||||
|
"Couldn't fulfill "
|
||||||
|
+ dataTarget
|
||||||
|
+ " API request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package de.rwu.easydrop.api.client;
|
||||||
|
|
||||||
|
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract construct to provide access to a purchase API.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractPurchaser extends AbstractDataWriter {
|
||||||
|
/**
|
||||||
|
* Sends a buy request to the Amazon API.
|
||||||
|
*
|
||||||
|
* @param dto
|
||||||
|
*/
|
||||||
|
public void purchaseProduct(final ProductDTO dto) {
|
||||||
|
sendPutRequest(dto, "Purchase");
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/main/java/de/rwu/easydrop/api/client/AbstractSeller.java
Normal file
19
src/main/java/de/rwu/easydrop/api/client/AbstractSeller.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package de.rwu.easydrop.api.client;
|
||||||
|
|
||||||
|
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract construct to provide access to a sales API.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
public abstract class AbstractSeller extends AbstractDataWriter {
|
||||||
|
/**
|
||||||
|
* Sends a sell request to the Amazon API.
|
||||||
|
*
|
||||||
|
* @param dto
|
||||||
|
*/
|
||||||
|
public void sellProduct(final ProductDTO dto) {
|
||||||
|
sendPutRequest(dto, "Sales");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package de.rwu.easydrop.api.client;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a buy request to the Amazon API.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
public final class AmazonPurchaser extends AbstractPurchaser {
|
||||||
|
/**
|
||||||
|
* Name of this data source.
|
||||||
|
*/
|
||||||
|
private static final String DATA_TARGET = "Amazon";
|
||||||
|
/**
|
||||||
|
* Base URL to the Amazon Purchase API.
|
||||||
|
*/
|
||||||
|
private String baseUrl;
|
||||||
|
/**
|
||||||
|
* Access credential.
|
||||||
|
*/
|
||||||
|
private String apiKey;
|
||||||
|
/**
|
||||||
|
* Product region parameter required for data writes.
|
||||||
|
*/
|
||||||
|
private static final String PRODUCT_REGION = "DE";
|
||||||
|
/**
|
||||||
|
* Locale parameter required for data writes.
|
||||||
|
*/
|
||||||
|
private static final String LOCALE = "de_DE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up instance with Base URL and API Key.
|
||||||
|
*
|
||||||
|
* @param newBaseUrl
|
||||||
|
* @param newApiKey
|
||||||
|
*/
|
||||||
|
public AmazonPurchaser(final String newBaseUrl, final String newApiKey) {
|
||||||
|
this.baseUrl = newBaseUrl;
|
||||||
|
this.apiKey = newApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param productId ASIN
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected URL createApiUrl(final String productId) throws MalformedURLException {
|
||||||
|
return new URL(baseUrl
|
||||||
|
+ "/products/2020-08-26/products/"
|
||||||
|
+ productId
|
||||||
|
+ "/buy?productRegion="
|
||||||
|
+ PRODUCT_REGION
|
||||||
|
+ "&locale="
|
||||||
|
+ LOCALE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getApiKey() {
|
||||||
|
return this.apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDataTarget() {
|
||||||
|
return DATA_TARGET;
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/main/java/de/rwu/easydrop/api/client/AmazonSeller.java
Normal file
67
src/main/java/de/rwu/easydrop/api/client/AmazonSeller.java
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package de.rwu.easydrop.api.client;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a sell request to the Amazon API.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
public final class AmazonSeller extends AbstractSeller {
|
||||||
|
/**
|
||||||
|
* Name of this data source.
|
||||||
|
*/
|
||||||
|
private static final String DATA_TARGET = "Amazon";
|
||||||
|
/**
|
||||||
|
* Base URL to the Amazon Purchase API.
|
||||||
|
*/
|
||||||
|
private String baseUrl;
|
||||||
|
/**
|
||||||
|
* Access credential.
|
||||||
|
*/
|
||||||
|
private String apiKey;
|
||||||
|
/**
|
||||||
|
* Product region parameter required for data writes.
|
||||||
|
*/
|
||||||
|
private static final String PRODUCT_REGION = "DE";
|
||||||
|
/**
|
||||||
|
* Locale parameter required for data writes.
|
||||||
|
*/
|
||||||
|
private static final String LOCALE = "de_DE";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up instance with Base URL and API Key.
|
||||||
|
*
|
||||||
|
* @param newBaseUrl
|
||||||
|
* @param newApiKey
|
||||||
|
*/
|
||||||
|
public AmazonSeller(final String newBaseUrl, final String newApiKey) {
|
||||||
|
this.baseUrl = newBaseUrl;
|
||||||
|
this.apiKey = newApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param productId ASIN
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected URL createApiUrl(final String productId) throws MalformedURLException {
|
||||||
|
return new URL(baseUrl
|
||||||
|
+ "/products/2020-08-26/products/"
|
||||||
|
+ productId
|
||||||
|
+ "/sell?productRegion="
|
||||||
|
+ PRODUCT_REGION
|
||||||
|
+ "&locale="
|
||||||
|
+ LOCALE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getApiKey() {
|
||||||
|
return apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDataTarget() {
|
||||||
|
return DATA_TARGET;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/main/java/de/rwu/easydrop/api/client/EbayPurchaser.java
Normal file
52
src/main/java/de/rwu/easydrop/api/client/EbayPurchaser.java
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package de.rwu.easydrop.api.client;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a buy request to the eBay API.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
public final class EbayPurchaser extends AbstractPurchaser {
|
||||||
|
/**
|
||||||
|
* Name of this data source.
|
||||||
|
*/
|
||||||
|
private static final String DATA_TARGET = "eBay";
|
||||||
|
/**
|
||||||
|
* Base URL to the eBay Purchase API.
|
||||||
|
*/
|
||||||
|
private String baseUrl;
|
||||||
|
/**
|
||||||
|
* Access credential.
|
||||||
|
*/
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up instance with Base URL and API Key.
|
||||||
|
*
|
||||||
|
* @param newBaseUrl
|
||||||
|
* @param newApiKey
|
||||||
|
*/
|
||||||
|
public EbayPurchaser(final String newBaseUrl, final String newApiKey) {
|
||||||
|
this.baseUrl = newBaseUrl;
|
||||||
|
this.apiKey = newApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected URL createApiUrl(final String searchQuery) throws MalformedURLException {
|
||||||
|
return new URL(baseUrl
|
||||||
|
+ "/buy/"
|
||||||
|
+ searchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getApiKey() {
|
||||||
|
return apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDataTarget() {
|
||||||
|
return DATA_TARGET;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/main/java/de/rwu/easydrop/api/client/EbaySeller.java
Normal file
52
src/main/java/de/rwu/easydrop/api/client/EbaySeller.java
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package de.rwu.easydrop.api.client;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a sell request to the eBay API.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
public final class EbaySeller extends AbstractSeller {
|
||||||
|
/**
|
||||||
|
* Name of this data source.
|
||||||
|
*/
|
||||||
|
private static final String DATA_TARGET = "eBay";
|
||||||
|
/**
|
||||||
|
* Base URL to the eBay Purchase API.
|
||||||
|
*/
|
||||||
|
private String baseUrl;
|
||||||
|
/**
|
||||||
|
* Access credential.
|
||||||
|
*/
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up instance with Base URL and API Key.
|
||||||
|
*
|
||||||
|
* @param newBaseUrl
|
||||||
|
* @param newApiKey
|
||||||
|
*/
|
||||||
|
public EbaySeller(final String newBaseUrl, final String newApiKey) {
|
||||||
|
this.baseUrl = newBaseUrl;
|
||||||
|
this.apiKey = newApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected URL createApiUrl(final String searchQuery) throws MalformedURLException {
|
||||||
|
return new URL(baseUrl
|
||||||
|
+ "/sell/"
|
||||||
|
+ searchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getApiKey() {
|
||||||
|
return this.apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDataTarget() {
|
||||||
|
return DATA_TARGET;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package de.rwu.easydrop.exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception that signifies a communication problem with a writer API.
|
||||||
|
*
|
||||||
|
* @since 0.3.0
|
||||||
|
*/
|
||||||
|
public class DataWriterException extends RuntimeException {
|
||||||
|
/**
|
||||||
|
* Throws an exception that signifies the data of a Product are invalid.
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
*/
|
||||||
|
public DataWriterException(final String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws an exception that signifies the data of a Product are invalid.
|
||||||
|
*
|
||||||
|
* @param message
|
||||||
|
* @param cause
|
||||||
|
*/
|
||||||
|
public DataWriterException(final String message, final Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
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.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.doCallRealMethod;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
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.exception.DataWriterException;
|
||||||
|
|
||||||
|
class AbstractDataWriterTest {
|
||||||
|
private static String demoProductId = "whateverId";
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HttpURLConnection mockConnection;
|
||||||
|
@Mock
|
||||||
|
private URL mockUrl;
|
||||||
|
|
||||||
|
private AbstractDataWriter writer;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws Exception {
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
writer = new AbstractDataWriter() {
|
||||||
|
@Override
|
||||||
|
protected String getApiKey() {
|
||||||
|
return "testApiKey";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDataTarget() {
|
||||||
|
return "Test";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected URL createApiUrl(String productIdentifier) throws MalformedURLException {
|
||||||
|
return mockUrl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sendPutRequest_wrongDataSource_throwsException() {
|
||||||
|
ProductDTO dto = new ProductDTO(demoProductId, "Amazon");
|
||||||
|
|
||||||
|
DataWriterException e = assertThrows(DataWriterException.class, () -> {
|
||||||
|
writer.sendPutRequest(dto, "testApiType");
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals("Product data source and target testApiType API are incompatible.", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sendPutRequest_badResponseCode_throwsException() throws IOException {
|
||||||
|
// Set up DTO
|
||||||
|
ProductDTO dto = new ProductDTO(demoProductId, "Test");
|
||||||
|
|
||||||
|
// Set up Mocks
|
||||||
|
AbstractDataWriter mockWriter = mock(AbstractDataWriter.class);
|
||||||
|
doCallRealMethod().when(mockWriter).sendPutRequest(any(), anyString());
|
||||||
|
when(mockUrl.openConnection()).thenReturn(mockConnection);
|
||||||
|
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_BAD_REQUEST);
|
||||||
|
|
||||||
|
DataWriterException e = assertThrows(DataWriterException.class, () -> {
|
||||||
|
writer.sendPutRequest(dto, "Sales");
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals("Test Sales API responded with error code 400", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sendPutRequest_ioException_throwsException() throws IOException {
|
||||||
|
// Set up DTO
|
||||||
|
ProductDTO dto = new ProductDTO(demoProductId, "Test");
|
||||||
|
|
||||||
|
// Set up Mocks
|
||||||
|
AbstractDataWriter mockWriter = mock(AbstractDataWriter.class);
|
||||||
|
doCallRealMethod().when(mockWriter).sendPutRequest(any(), anyString());
|
||||||
|
when(mockUrl.openConnection()).thenThrow(new IOException());
|
||||||
|
|
||||||
|
DataWriterException e = assertThrows(DataWriterException.class, () -> {
|
||||||
|
writer.sendPutRequest(dto, "testApiType");
|
||||||
|
});
|
||||||
|
|
||||||
|
assertEquals("Couldn't fulfill Test API request", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void sendPutRequest_successfulRequest() throws IOException {
|
||||||
|
// Set up DTO
|
||||||
|
ProductDTO dto = new ProductDTO(demoProductId, "test");
|
||||||
|
|
||||||
|
// Set up Mocks
|
||||||
|
AbstractDataWriter mockWriter = mock(AbstractDataWriter.class);
|
||||||
|
URL mockURL = mock(URL.class);
|
||||||
|
when(mockWriter.createApiUrl(demoProductId)).thenReturn(mockURL);
|
||||||
|
doCallRealMethod().when(mockWriter).sendPutRequest(any(), anyString());
|
||||||
|
HttpURLConnection mockConnection = mock(HttpURLConnection.class);
|
||||||
|
when(mockURL.openConnection()).thenReturn(mockConnection);
|
||||||
|
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
|
||||||
|
when(mockWriter.getDataTarget()).thenReturn("test");
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
mockWriter.sendPutRequest(dto, "Purchase");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package de.rwu.easydrop.api.client;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doCallRealMethod;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||||
|
|
||||||
|
class AbstractPurchaserTest {
|
||||||
|
private AbstractPurchaser mockPurchaser = mock(AbstractPurchaser.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void purchaseProduct_CorrectApiTypeInException() throws IOException {
|
||||||
|
// Set up DTO
|
||||||
|
ProductDTO dto = new ProductDTO("12345", "test");
|
||||||
|
|
||||||
|
// Set up mocks
|
||||||
|
URL mockURL = mock(URL.class);
|
||||||
|
when(mockPurchaser.createApiUrl("12345")).thenReturn(mockURL);
|
||||||
|
doCallRealMethod().when(mockPurchaser).purchaseProduct(any());
|
||||||
|
HttpURLConnection mockConnection = mock(HttpURLConnection.class);
|
||||||
|
when(mockURL.openConnection()).thenReturn(mockConnection);
|
||||||
|
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
|
||||||
|
when(mockPurchaser.getDataTarget()).thenReturn("test");
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
mockPurchaser.purchaseProduct(dto);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package de.rwu.easydrop.api.client;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doCallRealMethod;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import de.rwu.easydrop.api.dto.ProductDTO;
|
||||||
|
|
||||||
|
class AbstractSellerTest {
|
||||||
|
private AbstractSeller mockSeller = mock(AbstractSeller.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void purchaseProduct_CorrectApiTypeInException() throws IOException {
|
||||||
|
// Set up DTO
|
||||||
|
ProductDTO dto = new ProductDTO("12345", "test");
|
||||||
|
|
||||||
|
// Set up mocks
|
||||||
|
URL mockURL = mock(URL.class);
|
||||||
|
when(mockSeller.createApiUrl("12345")).thenReturn(mockURL);
|
||||||
|
doCallRealMethod().when(mockSeller).sellProduct(any());
|
||||||
|
HttpURLConnection mockConnection = mock(HttpURLConnection.class);
|
||||||
|
when(mockURL.openConnection()).thenReturn(mockConnection);
|
||||||
|
when(mockConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
|
||||||
|
when(mockSeller.getDataTarget()).thenReturn("test");
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
mockSeller.sellProduct(dto);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package de.rwu.easydrop.api.client;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
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.DataWriterException;
|
||||||
|
|
||||||
|
class AmazonPurchaserTest {
|
||||||
|
private AmazonPurchaser demoPurchaser;
|
||||||
|
|
||||||
|
private static String demoApiKey = "my-api-key";
|
||||||
|
private static String demoApiUrl = "https://www.example.com/api";
|
||||||
|
private static String demoDataTarget = "Amazon";
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
demoPurchaser = new AmazonPurchaser(
|
||||||
|
demoApiUrl,
|
||||||
|
demoApiKey);
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConstructor() {
|
||||||
|
// Assert
|
||||||
|
try {
|
||||||
|
Field baseUrlField = AmazonPurchaser.class.getDeclaredField("baseUrl");
|
||||||
|
baseUrlField.setAccessible(true);
|
||||||
|
Assertions.assertEquals(demoApiUrl, baseUrlField.get(demoPurchaser));
|
||||||
|
|
||||||
|
Field apiKeyField = AmazonPurchaser.class.getDeclaredField("apiKey");
|
||||||
|
apiKeyField.setAccessible(true);
|
||||||
|
Assertions.assertEquals(demoApiKey, apiKeyField.get(demoPurchaser));
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
Assertions.fail();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
Assertions.fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateApiUrl() throws MalformedURLException {
|
||||||
|
// Test case 1
|
||||||
|
String productId1 = "12345";
|
||||||
|
URL expectedUrl1 = new URL(
|
||||||
|
"https://www.example.com/api/products/2020-08-26/products/"
|
||||||
|
+ productId1
|
||||||
|
+ "/buy?productRegion=DE&locale=de_DE");
|
||||||
|
URL createdUrl1 = demoPurchaser.createApiUrl(productId1);
|
||||||
|
Assertions.assertEquals(expectedUrl1, createdUrl1);
|
||||||
|
|
||||||
|
// Test case 2
|
||||||
|
String productId2 = "67890";
|
||||||
|
URL expectedUrl2 = new URL(
|
||||||
|
"https://www.example.com/api/products/2020-08-26/"
|
||||||
|
+ "products/"
|
||||||
|
+ productId2
|
||||||
|
+ "/buy?productRegion=DE&locale=de_DE");
|
||||||
|
URL createdUrl2 = demoPurchaser.createApiUrl(productId2);
|
||||||
|
Assertions.assertEquals(expectedUrl2, createdUrl2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getDataTarget_ReturnsExpectedDataOrigin() {
|
||||||
|
String dataTarget = demoPurchaser.getDataTarget();
|
||||||
|
assertEquals(demoDataTarget, dataTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getApiKey_ReturnsExpectedApiKey() {
|
||||||
|
String apiKey = demoPurchaser.getApiKey();
|
||||||
|
assertEquals(demoApiKey, apiKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package de.rwu.easydrop.api.client;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
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;
|
||||||
|
|
||||||
|
class AmazonSellerTest {
|
||||||
|
private AmazonSeller demoSeller;
|
||||||
|
|
||||||
|
private static String demoApiKey = "my-api-key";
|
||||||
|
private static String demoApiUrl = "https://www.example.com/api";
|
||||||
|
private static String demoDataTarget = "Amazon";
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
demoSeller = new AmazonSeller(
|
||||||
|
demoApiUrl,
|
||||||
|
demoApiKey);
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConstructor() {
|
||||||
|
// Assert
|
||||||
|
try {
|
||||||
|
Field baseUrlField = AmazonSeller.class.getDeclaredField("baseUrl");
|
||||||
|
baseUrlField.setAccessible(true);
|
||||||
|
Assertions.assertEquals(demoApiUrl, baseUrlField.get(demoSeller));
|
||||||
|
|
||||||
|
Field apiKeyField = AmazonSeller.class.getDeclaredField("apiKey");
|
||||||
|
apiKeyField.setAccessible(true);
|
||||||
|
Assertions.assertEquals(demoApiKey, apiKeyField.get(demoSeller));
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
Assertions.fail();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
Assertions.fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateApiUrl() throws MalformedURLException {
|
||||||
|
// Test case 1
|
||||||
|
String productId1 = "12345";
|
||||||
|
URL expectedUrl1 = new URL(
|
||||||
|
"https://www.example.com/api/products/2020-08-26/products/"
|
||||||
|
+ productId1
|
||||||
|
+ "/sell?productRegion=DE&locale=de_DE");
|
||||||
|
URL createdUrl1 = demoSeller.createApiUrl(productId1);
|
||||||
|
Assertions.assertEquals(expectedUrl1, createdUrl1);
|
||||||
|
|
||||||
|
// Test case 2
|
||||||
|
String productId2 = "67890";
|
||||||
|
URL expectedUrl2 = new URL(
|
||||||
|
"https://www.example.com/api/products/2020-08-26/"
|
||||||
|
+ "products/"
|
||||||
|
+ productId2
|
||||||
|
+ "/sell?productRegion=DE&locale=de_DE");
|
||||||
|
URL createdUrl2 = demoSeller.createApiUrl(productId2);
|
||||||
|
Assertions.assertEquals(expectedUrl2, createdUrl2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getDataTarget_ReturnsExpectedDataOrigin() {
|
||||||
|
String dataTarget = demoSeller.getDataTarget();
|
||||||
|
assertEquals(demoDataTarget, dataTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getApiKey_ReturnsExpectedApiKey() {
|
||||||
|
String apiKey = demoSeller.getApiKey();
|
||||||
|
assertEquals(demoApiKey, apiKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package de.rwu.easydrop.api.client;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
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;
|
||||||
|
|
||||||
|
class EbayPurchaserTest {
|
||||||
|
private EbayPurchaser demoPurchaser;
|
||||||
|
|
||||||
|
private static String demoApiKey = "my-api-key";
|
||||||
|
private static String demoApiUrl = "https://www.example.com/api";
|
||||||
|
private static String demoDataTarget = "eBay";
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
demoPurchaser = new EbayPurchaser(
|
||||||
|
demoApiUrl,
|
||||||
|
demoApiKey);
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConstructor() {
|
||||||
|
// Assert
|
||||||
|
try {
|
||||||
|
Field baseUrlField = EbayPurchaser.class.getDeclaredField("baseUrl");
|
||||||
|
baseUrlField.setAccessible(true);
|
||||||
|
Assertions.assertEquals(demoApiUrl, baseUrlField.get(demoPurchaser));
|
||||||
|
|
||||||
|
Field apiKeyField = EbayPurchaser.class.getDeclaredField("apiKey");
|
||||||
|
apiKeyField.setAccessible(true);
|
||||||
|
Assertions.assertEquals(demoApiKey, apiKeyField.get(demoPurchaser));
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
Assertions.fail();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
Assertions.fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateApiUrl() throws MalformedURLException {
|
||||||
|
// Test case 1
|
||||||
|
String productId1 = "12345";
|
||||||
|
URL expectedUrl1 = new URL(
|
||||||
|
"https://www.example.com/api/buy/"
|
||||||
|
+ productId1);
|
||||||
|
URL createdUrl1 = demoPurchaser.createApiUrl(productId1);
|
||||||
|
Assertions.assertEquals(expectedUrl1, createdUrl1);
|
||||||
|
|
||||||
|
// Test case 2
|
||||||
|
String productId2 = "67890";
|
||||||
|
URL expectedUrl2 = new URL(
|
||||||
|
"https://www.example.com/api/buy/"
|
||||||
|
+ productId2);
|
||||||
|
URL createdUrl2 = demoPurchaser.createApiUrl(productId2);
|
||||||
|
Assertions.assertEquals(expectedUrl2, createdUrl2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getDataTarget_ReturnsExpectedDataOrigin() {
|
||||||
|
String dataTarget = demoPurchaser.getDataTarget();
|
||||||
|
assertEquals(demoDataTarget, dataTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getApiKey_ReturnsExpectedApiKey() {
|
||||||
|
String apiKey = demoPurchaser.getApiKey();
|
||||||
|
assertEquals(demoApiKey, apiKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
77
src/test/java/de/rwu/easydrop/api/client/EbaySellerTest.java
Normal file
77
src/test/java/de/rwu/easydrop/api/client/EbaySellerTest.java
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package de.rwu.easydrop.api.client;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
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;
|
||||||
|
|
||||||
|
class EbaySellerTest {
|
||||||
|
private EbaySeller demoSeller;
|
||||||
|
|
||||||
|
private static String demoApiKey = "my-api-key";
|
||||||
|
private static String demoApiUrl = "https://www.example.com/api";
|
||||||
|
private static String demoDataTarget = "eBay";
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
demoSeller = new EbaySeller(
|
||||||
|
demoApiUrl,
|
||||||
|
demoApiKey);
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConstructor() {
|
||||||
|
// Assert
|
||||||
|
try {
|
||||||
|
Field baseUrlField = EbaySeller.class.getDeclaredField("baseUrl");
|
||||||
|
baseUrlField.setAccessible(true);
|
||||||
|
Assertions.assertEquals(demoApiUrl, baseUrlField.get(demoSeller));
|
||||||
|
|
||||||
|
Field apiKeyField = EbaySeller.class.getDeclaredField("apiKey");
|
||||||
|
apiKeyField.setAccessible(true);
|
||||||
|
Assertions.assertEquals(demoApiKey, apiKeyField.get(demoSeller));
|
||||||
|
} catch (NoSuchFieldException e) {
|
||||||
|
Assertions.fail();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
Assertions.fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateApiUrl() throws MalformedURLException {
|
||||||
|
// Test case 1
|
||||||
|
String productId1 = "12345";
|
||||||
|
URL expectedUrl1 = new URL(
|
||||||
|
"https://www.example.com/api/sell/"
|
||||||
|
+ productId1);
|
||||||
|
URL createdUrl1 = demoSeller.createApiUrl(productId1);
|
||||||
|
Assertions.assertEquals(expectedUrl1, createdUrl1);
|
||||||
|
|
||||||
|
// Test case 2
|
||||||
|
String productId2 = "67890";
|
||||||
|
URL expectedUrl2 = new URL(
|
||||||
|
"https://www.example.com/api/sell/"
|
||||||
|
+ productId2);
|
||||||
|
URL createdUrl2 = demoSeller.createApiUrl(productId2);
|
||||||
|
Assertions.assertEquals(expectedUrl2, createdUrl2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getDataTarget_ReturnsExpectedDataOrigin() {
|
||||||
|
String dataTarget = demoSeller.getDataTarget();
|
||||||
|
assertEquals(demoDataTarget, dataTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getApiKey_ReturnsExpectedApiKey() {
|
||||||
|
String apiKey = demoSeller.getApiKey();
|
||||||
|
assertEquals(demoApiKey, apiKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 DataWriterExceptionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void constructor_WithMessage_SetsMessage() {
|
||||||
|
// Arrange
|
||||||
|
String message = "Data source error";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
DataWriterException exception = new DataWriterException(message);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(message, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void constructor_WithMessageAndCause_SetsMessageAndCause() {
|
||||||
|
// Arrange
|
||||||
|
String message = "Data source error";
|
||||||
|
Throwable cause = new IllegalArgumentException("Invalid argument");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
DataWriterException exception = new DataWriterException(message, cause);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(message, exception.getMessage());
|
||||||
|
assertEquals(cause, exception.getCause());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void constructor_WithNullMessage_SetsNullMessage() {
|
||||||
|
// Act
|
||||||
|
DataWriterException exception = new DataWriterException(null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(null, exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void constructor_WithNullCause_SetsNullCause() {
|
||||||
|
// Act
|
||||||
|
DataWriterException exception = new DataWriterException("Data source error", null);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(null, exception.getCause());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void throw_DataWriterException() {
|
||||||
|
// Act and Assert
|
||||||
|
assertThrows(DataWriterException.class, () -> {
|
||||||
|
throw new DataWriterException("Data source error");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user