diff --git a/pom.xml b/pom.xml index e37a6978..f9fff8e3 100644 --- a/pom.xml +++ b/pom.xml @@ -99,6 +99,11 @@ ${cdap.version} test + + commons-io + commons-io + 2.5 + org.apache.hadoop hadoop-common diff --git a/src/main/java/io/cdap/plugin/servicenow/ServiceNowBaseConfig.java b/src/main/java/io/cdap/plugin/servicenow/ServiceNowBaseConfig.java index 9bdcbdd7..da61a843 100644 --- a/src/main/java/io/cdap/plugin/servicenow/ServiceNowBaseConfig.java +++ b/src/main/java/io/cdap/plugin/servicenow/ServiceNowBaseConfig.java @@ -17,12 +17,14 @@ package io.cdap.plugin.servicenow; import com.google.common.annotations.VisibleForTesting; +import com.google.gson.stream.JsonReader; import io.cdap.cdap.api.annotation.Description; import io.cdap.cdap.api.annotation.Macro; import io.cdap.cdap.api.annotation.Name; import io.cdap.cdap.api.plugin.PluginConfig; import io.cdap.cdap.etl.api.FailureCollector; import io.cdap.plugin.common.ConfigUtil; +import io.cdap.plugin.servicenow.apiclient.ServiceNowAPIException; import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIClientImpl; import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIRequestBuilder; import io.cdap.plugin.servicenow.connector.ServiceNowConnectorConfig; @@ -31,7 +33,13 @@ import io.cdap.plugin.servicenow.util.SchemaType; import io.cdap.plugin.servicenow.util.ServiceNowConstants; import io.cdap.plugin.servicenow.util.SourceValueType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import javax.annotation.Nullable; /** @@ -39,6 +47,7 @@ */ public class ServiceNowBaseConfig extends PluginConfig { + private static final Logger log = LoggerFactory.getLogger(ServiceNowBaseConfig.class); @Name(ConfigUtil.NAME_USE_CONNECTION) @Nullable @Description("Whether to use an existing connection.") @@ -83,7 +92,7 @@ public void validateCredentials(FailureCollector collector) { } @VisibleForTesting - public void validateServiceNowConnection(FailureCollector collector) { + public void validateServiceNowConnection(FailureCollector collector) { try { ServiceNowTableAPIClientImpl restApi = new ServiceNowTableAPIClientImpl(connection, useConnection); restApi.getAccessToken(); @@ -140,7 +149,7 @@ public void validateTable(String tableName, SourceValueType valueType, FailureCo requestBuilder.setResponseHeaders(ServiceNowConstants.HEADER_NAME_TOTAL_COUNT); apiResponse = serviceNowTableAPIClient.executeGetWithRetries(requestBuilder.build()); - if (serviceNowTableAPIClient.parseResponseToResultListOfMap(apiResponse.getResponseBody()).isEmpty()) { + if (isResultEmpty(apiResponse)) { // Removed config property as in case of MultiSource, only first table error was populating. collector.addFailure("Table: " + tableName + " is empty.", ""); } @@ -152,4 +161,31 @@ public void validateTable(String tableName, SourceValueType valueType, FailureCo } } + /** + * Checks if the "result" array in the ServiceNow REST API response is empty. + *

+ * Determines if the "result" array in a ServiceNow REST API response is empty by specifically looking for a top-level + * key named "result". Once found, it opens the associated array and checks for the presence of a first element. + *

+ * + * @param restAPIResponse The response object containing the JSON input stream + * @return true, if the "result" array exists and is empty, or if the "result" key is never found; + * false, if the array contains at least one element. + * @throws IOException If there is an error reading the input stream or parsing the JSON. + */ + public boolean isResultEmpty(RestAPIResponse restAPIResponse) throws IOException { + JsonReader reader = new JsonReader(new InputStreamReader(restAPIResponse.getResponseStream())); + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + if (ServiceNowConstants.RESULT.equals(name)) { + reader.beginArray(); + return !reader.hasNext(); + } else { + reader.skipValue(); + } + } + return true; + } + } diff --git a/src/main/java/io/cdap/plugin/servicenow/apiclient/ServiceNowTableAPIClientImpl.java b/src/main/java/io/cdap/plugin/servicenow/apiclient/ServiceNowTableAPIClientImpl.java index 04df0178..c0bb8f41 100644 --- a/src/main/java/io/cdap/plugin/servicenow/apiclient/ServiceNowTableAPIClientImpl.java +++ b/src/main/java/io/cdap/plugin/servicenow/apiclient/ServiceNowTableAPIClientImpl.java @@ -27,6 +27,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; import io.cdap.cdap.api.data.schema.Schema; import io.cdap.cdap.etl.api.FailureCollector; import io.cdap.plugin.servicenow.connector.ServiceNowConnectorConfig; @@ -55,7 +56,10 @@ import org.slf4j.LoggerFactory; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -63,6 +67,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; import static io.cdap.plugin.servicenow.util.ServiceNowConstants.STC_FIELD_SUFFIX; @@ -85,6 +90,7 @@ public class ServiceNowTableAPIClientImpl extends RestAPIClient { public static JsonArray serviceNowJsonResultArray; public ServiceNowTableAPIClientImpl(ServiceNowConnectorConfig conf, Boolean useConnection) { + super(); this.conf = conf; this.schemaType = getSchemaTypeBasedOnUseConnection(useConnection); } @@ -131,7 +137,7 @@ public String getAccessTokenRetryableMode() throws ExecutionException, RetryExce * @param limit The number of records to be fetched * @return The list of Map; each Map representing a table row */ - public List> fetchTableRecords( + public RestAPIResponse fetchTableRecords( String tableName, SourceValueType valueType, String startDate, @@ -154,7 +160,7 @@ public List> fetchTableRecords( String accessToken = getAccessToken(); requestBuilder.setAuthHeader(accessToken); RestAPIResponse apiResponse = executeGetWithRetries(requestBuilder.build()); - return parseResponseToResultListOfMap(apiResponse.getResponseBody()); + return apiResponse; } private void applyDateRangeToRequest(ServiceNowTableAPIRequestBuilder requestBuilder, String startDate, @@ -197,6 +203,12 @@ public List> parseResponseToResultListOfMap(String responseB return GSON.fromJson(ja, type); } + public List parseResponseToResultListOfMap(InputStream in) { + APIResponse apiResponse = GSON.fromJson(new JsonReader(new InputStreamReader(in, StandardCharsets.UTF_8)), + APIResponse.class); + return apiResponse.getResult(); + } + private String getErrorMessage(String responseBody) { try { JsonObject jo = GSON.fromJson(responseBody, JsonObject.class); @@ -231,14 +243,13 @@ private String getErrorMessage(String responseBody) { * @param limit The number of records to be fetched * @return The list of Map; each Map representing a table row */ - public List> fetchTableRecordsRetryableMode(String tableName, SourceValueType valueType, + public RestAPIResponse fetchTableRecordsRetryableMode(String tableName, SourceValueType valueType, String startDate, String endDate, int offset, int limit) throws ServiceNowAPIException { - final List> results = new ArrayList<>(); - Callable fetchRecords = () -> { - results.addAll(fetchTableRecords(tableName, valueType, startDate, endDate, offset, limit)); - return true; - }; + // Using AtomicReference to capture a value inside a lambda that needs to be accessed outside. + AtomicReference responseRef = new AtomicReference<>(); + Callable fetchRecords = () -> executeFetch(tableName, valueType, startDate, endDate, offset, limit, + responseRef); Retryer retryer = RetryerBuilder.newBuilder() .retryIfException(this::isExceptionRetryable) @@ -253,8 +264,15 @@ public List> fetchTableRecordsRetryableMode(String tableName String.format("Data Recovery failed for batch %s to %s.", offset, (offset + limit)), e, null, false); } + // Return the value captured inside the lambda + return responseRef.get(); + } - return results; + private boolean executeFetch(String tableName, SourceValueType type, String startDate, String endDate, int offset, + int limit, AtomicReference ref) throws ServiceNowAPIException { + RestAPIResponse response = fetchTableRecords(tableName, type, startDate, endDate, offset, limit); + ref.set(response); + return true; } /** @@ -277,8 +295,8 @@ public Schema fetchTableSchema(String tableName, FailureCollector collector) { } @VisibleForTesting - public MetadataAPISchemaResponse parseSchemaResponse(String responseBody) { - return GSON.fromJson(responseBody, MetadataAPISchemaResponse.class); + public MetadataAPISchemaResponse parseSchemaResponse(InputStream responseStream) { + return GSON.fromJson(createJsonReader(responseStream), MetadataAPISchemaResponse.class); } /** @@ -352,7 +370,7 @@ public Schema fetchTableSchema(String tableName, String accessToken, SourceValue private Schema prepareSchemaWithSchemaAPI(RestAPIResponse restAPIResponse, List columns, String tableName) throws ServiceNowAPIException { SchemaAPISchemaResponse schemaAPISchemaResponse = - GSON.fromJson(restAPIResponse.getResponseBody(), SchemaAPISchemaResponse.class); + GSON.fromJson(createJsonReader(restAPIResponse.getResponseStream()), SchemaAPISchemaResponse.class); if (schemaAPISchemaResponse.getResult() == null || schemaAPISchemaResponse.getResult().isEmpty()) { throw new ServiceNowAPIException( @@ -386,8 +404,7 @@ private Schema prepareSchemaWithSchemaAPI(RestAPIResponse restAPIResponse, List< private Schema prepareSchemaWithMetadataAPI(RestAPIResponse restAPIResponse, List columns, String tableName, SourceValueType valueType) throws ServiceNowAPIException { - MetadataAPISchemaResponse metadataAPISchemaResponse = parseSchemaResponse(restAPIResponse.getResponseBody()); - + MetadataAPISchemaResponse metadataAPISchemaResponse = parseSchemaResponse(restAPIResponse.getResponseStream()); if (metadataAPISchemaResponse.getResult() == null || metadataAPISchemaResponse.getResult().getColumns() == null || metadataAPISchemaResponse.getResult().getColumns().isEmpty()) { throw new ServiceNowAPIException( @@ -413,6 +430,11 @@ private Schema prepareSchemaWithMetadataAPI(RestAPIResponse restAPIResponse, Lis return SchemaBuilder.constructSchema(tableName, columns); } + public JsonReader createJsonReader(InputStream inputStream) { + Objects.requireNonNull(inputStream, "InputStream must not be null"); + return new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + } + /** * Get the total number of records in the table * @@ -503,8 +525,9 @@ public String createRecordInDisplayMode(String tableName, HttpEntity entity) thr } private String getSystemId(RestAPIResponse restAPIResponse) { - CreateRecordAPIResponse apiResponse = GSON.fromJson(restAPIResponse.getResponseBody(), - CreateRecordAPIResponse.class); + CreateRecordAPIResponse apiResponse = GSON.fromJson( + new InputStreamReader(restAPIResponse.getResponseStream(), StandardCharsets.UTF_8), + CreateRecordAPIResponse.class); return apiResponse.getResult().get(ServiceNowConstants.SYSTEM_ID).toString(); } @@ -515,7 +538,7 @@ private String getSystemId(RestAPIResponse restAPIResponse) { * @param tableName The ServiceNow table name * @param query The query */ - public Map getRecordFromServiceNowTable(String tableName, String query) + public JsonObject getRecordFromServiceNowTable(String tableName, String query) throws ServiceNowAPIException { ServiceNowTableAPIRequestBuilder requestBuilder = new ServiceNowTableAPIRequestBuilder( @@ -527,7 +550,8 @@ public Map getRecordFromServiceNowTable(String tableName, String requestBuilder.setAuthHeader(accessToken); restAPIResponse = executeGetWithRetries(requestBuilder.build()); - APIResponse apiResponse = GSON.fromJson(restAPIResponse.getResponseBody(), APIResponse.class); + APIResponse apiResponse = GSON.fromJson( + new InputStreamReader(restAPIResponse.getResponseStream(), StandardCharsets.UTF_8), APIResponse.class); return apiResponse.getResult().get(0); } @@ -545,13 +569,12 @@ public Map getRecordFromServiceNowTable(String tableName, String * @throws RuntimeException if the schema response is null or contains no result. */ private Schema prepareStringBasedSchema(RestAPIResponse restAPIResponse, List columns, - String tableName) { - List> result = parseResponseToResultListOfMap(restAPIResponse.getResponseBody()); + String tableName) throws ServiceNowAPIException { + List result = parseResponseToResultListOfMap(restAPIResponse.getResponseStream()); if (result != null && !result.isEmpty()) { - Map firstRecord = result.get(0); - for (String key : firstRecord.keySet()) { - columns.add(new ServiceNowColumn(key, "string")); - } + result.get(0).entrySet().forEach(entry -> + columns.add(new ServiceNowColumn(entry.getKey(), "string")) + ); return SchemaBuilder.constructSchema(tableName, columns); } return null; diff --git a/src/main/java/io/cdap/plugin/servicenow/connector/ServiceNowConnector.java b/src/main/java/io/cdap/plugin/servicenow/connector/ServiceNowConnector.java index 81aa364c..6d9af4fc 100644 --- a/src/main/java/io/cdap/plugin/servicenow/connector/ServiceNowConnector.java +++ b/src/main/java/io/cdap/plugin/servicenow/connector/ServiceNowConnector.java @@ -16,6 +16,7 @@ package io.cdap.plugin.servicenow.connector; import com.google.gson.Gson; +import com.google.gson.JsonObject; import io.cdap.cdap.api.annotation.Description; import io.cdap.cdap.api.annotation.Name; import io.cdap.cdap.api.annotation.Plugin; @@ -135,7 +136,7 @@ private TableList listTables(String accessToken) throws ServiceNowAPIException { ServiceNowTableAPIClientImpl serviceNowTableAPIClient = new ServiceNowTableAPIClientImpl(config, true); RestAPIResponse apiResponse = serviceNowTableAPIClient.executeGetWithRetries(requestBuilder.build()); - return GSON.fromJson(apiResponse.getResponseBody(), TableList.class); + return GSON.fromJson(serviceNowTableAPIClient.createJsonReader(apiResponse.getResponseStream()), TableList.class); } public ConnectorSpec generateSpec(ConnectorContext connectorContext, ConnectorSpecRequest connectorSpecRequest) { @@ -183,8 +184,8 @@ private List getTableData(String tableName, int limit) requestBuilder.setAuthHeader(accessToken); requestBuilder.setResponseHeaders(ServiceNowConstants.HEADER_NAME_TOTAL_COUNT); RestAPIResponse apiResponse = serviceNowTableAPIClient.executeGetWithRetries(requestBuilder.build()); - List> result = serviceNowTableAPIClient.parseResponseToResultListOfMap - (apiResponse.getResponseBody()); + List result = serviceNowTableAPIClient.parseResponseToResultListOfMap + (apiResponse.getResponseStream()); List recordList = new ArrayList<>(); Schema schema = getSchema(tableName); if (schema != null) { diff --git a/src/main/java/io/cdap/plugin/servicenow/connector/ServiceNowRecordConverter.java b/src/main/java/io/cdap/plugin/servicenow/connector/ServiceNowRecordConverter.java index c7425a49..56b32171 100644 --- a/src/main/java/io/cdap/plugin/servicenow/connector/ServiceNowRecordConverter.java +++ b/src/main/java/io/cdap/plugin/servicenow/connector/ServiceNowRecordConverter.java @@ -16,6 +16,7 @@ package io.cdap.plugin.servicenow.connector; import com.google.common.annotations.VisibleForTesting; +import com.google.gson.JsonObject; import io.cdap.cdap.api.data.format.StructuredRecord; import io.cdap.cdap.api.data.format.UnexpectedFormatException; import io.cdap.cdap.api.data.schema.Schema; @@ -27,11 +28,13 @@ import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.stream.Collectors; /** * Utility class for converting the record from ServiceNow data type to CDAP schema data types @@ -82,9 +85,10 @@ public class ServiceNowRecordConverter { DateTimeFormatter.ofPattern("HH:mm") )); - public static void convertToValue(String fieldName, Schema fieldSchema, Map record, + public static void convertToValue(String fieldName, Schema fieldSchema, JsonObject record, StructuredRecord.Builder recordBuilder) { - String fieldValue = record.get(fieldName); + String fieldValue = record.has(fieldName) && !record.get(fieldName).isJsonNull() ? record.get(fieldName) + .getAsString() : null; if (fieldValue == null || fieldValue.isEmpty()) { // Set 'null' value as it is recordBuilder.set(fieldName, null); diff --git a/src/main/java/io/cdap/plugin/servicenow/model/APIResponse.java b/src/main/java/io/cdap/plugin/servicenow/model/APIResponse.java index c191e9a6..e5e37417 100644 --- a/src/main/java/io/cdap/plugin/servicenow/model/APIResponse.java +++ b/src/main/java/io/cdap/plugin/servicenow/model/APIResponse.java @@ -16,6 +16,8 @@ package io.cdap.plugin.servicenow.model; +import com.google.gson.JsonObject; + import java.util.List; import java.util.Map; @@ -24,9 +26,9 @@ */ public class APIResponse { - private List> result; + private List result; - public List> getResult() { + public List getResult() { return result; } diff --git a/src/main/java/io/cdap/plugin/servicenow/restapi/RestAPIClient.java b/src/main/java/io/cdap/plugin/servicenow/restapi/RestAPIClient.java index 7329aaaf..8821a8dd 100644 --- a/src/main/java/io/cdap/plugin/servicenow/restapi/RestAPIClient.java +++ b/src/main/java/io/cdap/plugin/servicenow/restapi/RestAPIClient.java @@ -55,17 +55,56 @@ public abstract class RestAPIClient { private static final Logger LOG = LoggerFactory.getLogger(RestAPIClient.class); - /* Connect Timout in ms for establishing the conenction with the server */ + /* Connect Timeout in ms for establishing the connection with the server */ private static final int DEFAULT_CONNECT_TIMEOUT_MS = 120000; /* Read Timeout in ms for waiting for data after the connection is established */ private static final int DEFAULT_READ_TIMEOUT_MS = 300000; + /* Maximum total connections. */ + private static final int MAX_CONNECTIONS = 200; + + /** The maximum time a connection is allowed to live in the pool before being retired. + * Helps avoid "stale connection" errors during long-running pipelines. */ + private static final long CONNECTION_TTL_MINUTES = 5; + + /** The interval at which idle connections are scanned and closed by the background monitor. */ + private static final long IDLE_EVICTION_SECONDS = 30; + private static final RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(DEFAULT_CONNECT_TIMEOUT_MS) .setSocketTimeout(DEFAULT_READ_TIMEOUT_MS) .build(); + private final CloseableHttpClient httpClient; + + /* Default constructor to initialize the HttpClient. */ + protected RestAPIClient() { + this.httpClient = getHttpClient(); + } + + /* Lazy Holder to protect unit tests from premature static initialization errors. */ + static class HttpClientHolder { + static final CloseableHttpClient HTTP_CLIENT = createClient(); + + private static CloseableHttpClient createClient() { + return HttpClientBuilder.create() + .setMaxConnTotal(MAX_CONNECTIONS) + .setMaxConnPerRoute(MAX_CONNECTIONS) + .setConnectionTimeToLive(CONNECTION_TTL_MINUTES, TimeUnit.MINUTES) + .evictIdleConnections(IDLE_EVICTION_SECONDS, TimeUnit.SECONDS) + .setDefaultRequestConfig(RequestConfig.custom().setConnectTimeout(DEFAULT_CONNECT_TIMEOUT_MS) + .setSocketTimeout(DEFAULT_READ_TIMEOUT_MS).build()) + .build(); + } + } + /** + * Protected method to return the HttpClient instance. This is used to mock the HttpClient in unit tests. + */ + protected CloseableHttpClient getHttpClient() { + return HttpClientHolder.HTTP_CLIENT; + } + /** * Executes the Rest API request and returns the response. * @@ -76,10 +115,9 @@ public RestAPIResponse executeGet(RestAPIRequest request) throws IOException { HttpGet httpGet = new HttpGet(request.getUrl()); request.getHeaders().entrySet().forEach(e -> httpGet.addHeader(e.getKey(), e.getValue())); - try (CloseableHttpClient httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build()) { - try (CloseableHttpResponse httpResponse = httpClient.execute(httpGet)) { - return RestAPIResponse.parse(httpResponse, request.getResponseHeaders()); - } + try { + CloseableHttpResponse httpResponse = httpClient.execute(httpGet); + return RestAPIResponse.parse(httpResponse, request.getResponseHeaders()); } catch (ConnectTimeoutException | SocketException e) { ServiceNowAPIException exception = new ServiceNowAPIException(e, null); return new RestAPIResponse(Collections.emptyMap(), null, exception); diff --git a/src/main/java/io/cdap/plugin/servicenow/restapi/RestAPIResponse.java b/src/main/java/io/cdap/plugin/servicenow/restapi/RestAPIResponse.java index ceeab972..df3e2c4a 100644 --- a/src/main/java/io/cdap/plugin/servicenow/restapi/RestAPIResponse.java +++ b/src/main/java/io/cdap/plugin/servicenow/restapi/RestAPIResponse.java @@ -21,11 +21,14 @@ import io.cdap.plugin.servicenow.apiclient.ServiceNowAPIException; import io.cdap.plugin.servicenow.util.ServiceNowConstants; import org.apache.http.Header; +import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; -import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; +import java.io.InputStream; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -40,26 +43,32 @@ * Pojo class to capture the API response. */ public class RestAPIResponse { + private static final Logger LOG = LoggerFactory.getLogger(RestAPIResponse.class); private static final Gson GSON = new Gson(); private static final String HTTP_ERROR_MESSAGE = "Http call to ServiceNow instance returned status code %d."; private static final String REST_ERROR_MESSAGE = "Rest Api response has errors. Error message: %s."; private static final Set SUCCESS_CODES = new HashSet<>(Arrays.asList(HttpStatus.SC_CREATED, HttpStatus.SC_OK)); private final Map headers; - private final String responseBody; @Nullable private final ServiceNowAPIException exception; + // Input stream of the response body. + private InputStream responseStream; + public RestAPIResponse( - Map headers, - @Nullable String responseBody, - @Nullable ServiceNowAPIException exception) { + Map headers, + InputStream responseStream, + @Nullable ServiceNowAPIException exception) { this.headers = headers; - this.responseBody = responseBody; + this.responseStream = responseStream; this.exception = exception; } /** * Parses HttpResponse into RestAPIResponse object when no errors occur. + * The RESTAPIResponse contains the HTTP response body as a stream. This stream is: + * single-use, forward-only and owned by the caller. Caller is responsible for consuming and closing it. + * * Throws a {@link ServiceNowAPIException}. * * @param httpResponse The HttpResponse object to parse @@ -82,15 +91,29 @@ public static RestAPIResponse parse(HttpResponse httpResponse, String... headerN if (serviceNowAPIException != null) { return new RestAPIResponse(headers, null, serviceNowAPIException); } - - String responseBody = null; try { - responseBody = EntityUtils.toString(httpResponse.getEntity()); + return prepareResponse(httpResponse, headers, serviceNowAPIException); } catch (IOException e) { return new RestAPIResponse(headers, null, new ServiceNowAPIException(e, httpResponse)); } - serviceNowAPIException = validateRestApiResponse(httpResponse, responseBody); - return new RestAPIResponse(headers, responseBody, serviceNowAPIException); + } + + public void close() throws IOException { + if (responseStream != null) { + responseStream.close(); + } + } + + public static RestAPIResponse prepareResponse(HttpResponse httpResponse, Map headers, + ServiceNowAPIException serviceNowAPIException) throws IOException { + HttpEntity httpEntity = httpResponse.getEntity(); + InputStream inputStream; + if (httpEntity != null) { + inputStream = httpEntity.getContent(); + return new RestAPIResponse(headers, inputStream, serviceNowAPIException); + } else { + return new RestAPIResponse(headers, null, serviceNowAPIException); + } } public static RestAPIResponse parse(HttpResponse httpResponse) throws IOException { @@ -126,11 +149,6 @@ public Map getHeaders() { return headers; } - @Nullable - public String getResponseBody() { - return responseBody; - } - @Nullable public ServiceNowAPIException getException() { return exception; @@ -139,4 +157,8 @@ public ServiceNowAPIException getException() { public boolean hasException() { return exception != null; } + + public InputStream getResponseStream() { + return responseStream; + } } diff --git a/src/main/java/io/cdap/plugin/servicenow/sink/service/ServiceNowSinkAPIRequestImpl.java b/src/main/java/io/cdap/plugin/servicenow/sink/service/ServiceNowSinkAPIRequestImpl.java index 6d1f8d1f..99f9ef31 100644 --- a/src/main/java/io/cdap/plugin/servicenow/sink/service/ServiceNowSinkAPIRequestImpl.java +++ b/src/main/java/io/cdap/plugin/servicenow/sink/service/ServiceNowSinkAPIRequestImpl.java @@ -25,6 +25,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; import io.cdap.cdap.api.retry.RetryableException; import io.cdap.plugin.servicenow.apiclient.ServiceNowAPIException; import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIClientImpl; @@ -43,6 +44,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; +import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; @@ -121,7 +123,8 @@ public void createPostRequest(Map restRequestsMap, String a requestBuilder.setEntity(stringEntity); apiResponse = restApi.executePost(requestBuilder.build()); - JsonObject responseJSON = jsonParser.parse(apiResponse.getResponseBody()).getAsJsonObject(); + JsonObject responseJSON = jsonParser.parse(new JsonReader(new InputStreamReader(apiResponse.getResponseStream()))) + .getAsJsonObject().getAsJsonObject(); JsonArray servicedRequestsArray = responseJSON.get(ServiceNowConstants.SERVICED_REQUESTS).getAsJsonArray(); JsonElement failedRequestId = null; for (int i = 0; i < servicedRequestsArray.size(); i++) { diff --git a/src/main/java/io/cdap/plugin/servicenow/source/ServiceNowBaseRecordReader.java b/src/main/java/io/cdap/plugin/servicenow/source/ServiceNowBaseRecordReader.java index 4cd903c4..945ed095 100644 --- a/src/main/java/io/cdap/plugin/servicenow/source/ServiceNowBaseRecordReader.java +++ b/src/main/java/io/cdap/plugin/servicenow/source/ServiceNowBaseRecordReader.java @@ -16,12 +16,26 @@ package io.cdap.plugin.servicenow.source; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; import io.cdap.cdap.api.data.format.StructuredRecord; import io.cdap.cdap.api.data.schema.Schema; +import io.cdap.plugin.servicenow.apiclient.ServiceNowAPIException; +import io.cdap.plugin.servicenow.restapi.RestAPIResponse; +import io.cdap.plugin.servicenow.util.ServiceNowConstants; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.mapreduce.RecordReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -30,6 +44,7 @@ * Base Record reader class that provides a basic structure for Derived Record Reader classes. */ public abstract class ServiceNowBaseRecordReader extends RecordReader { + private static final Logger LOG = LoggerFactory.getLogger(ServiceNowRecordReader.class); protected ServiceNowInputSplit split; protected int pos; protected List tableFields; @@ -39,12 +54,132 @@ public abstract class ServiceNowBaseRecordReader extends RecordReader> results; protected Iterator> iterator; - protected Map row; + protected JsonObject row; + protected final Gson gson = new Gson(); + protected final Type mapType = new TypeToken>() { }.getType(); + protected JsonReader jsonReader = null; public ServiceNowBaseRecordReader() { } + /** + * This method reads the next key/value pair from the input. + *

+ * The nextKeyValue() uses the jsonReader to read the next record from the current page. + *

+ * Returns true, when it assigned `row` to the next record. + * Returns false, only when there are no more pages/records (i.e., openNextPage() returns false). + */ + public boolean nextKeyValue() throws IOException { + // Ensure we have an active page/jsonReader + if (jsonReader == null) { + // Need to open the next page + boolean pageOpened; + try { + pageOpened = openNextPage(); + } catch (ServiceNowAPIException e) { + throw new IOException("Exception in nextKeyValue" + tableName, e); + } + if (!pageOpened) { + // No more pages + return false; + } + } - public abstract boolean nextKeyValue() throws IOException; + // At this point jsonReader is positioned inside the "result" array. + JsonToken token = jsonReader.peek(); + + if (token == JsonToken.BEGIN_OBJECT) { + LOG.debug("Reading record object for table {} at position {}", tableName, pos); + this.row = gson.fromJson(jsonReader, JsonObject.class); // assign row + pos++; + return true; + } + closeCurrentPage(); + return false; + } + + public boolean openNextPage() throws IOException, ServiceNowAPIException { + closeCurrentPage(); + RestAPIResponse resp = fetchData(); + InputStream in = resp.getResponseStream(); + if (in == null) { + return false; + } + this.jsonReader = new JsonReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + this.jsonReader.setLenient(true); + + // Position the reader to the "result" array: { "result": [ ... ], ... } + try { + JsonToken top; + try { + top = jsonReader.peek(); + LOG.debug("Peeking JSON token for table {}: {}", tableName, top); + } catch (IOException e) { + LOG.warn("Unexpected closure of stream while peeking JSON token for table {}", tableName, e); + closeCurrentPage(); + return false; + } + if (top == JsonToken.BEGIN_OBJECT) { + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (name.equals(ServiceNowConstants.RESULT)) { + if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) { + jsonReader.beginArray(); + return true; + } else { + jsonReader.skipValue(); + break; + } + } else { + jsonReader.skipValue(); + } + } + } else if (top == JsonToken.BEGIN_ARRAY) { + jsonReader.beginArray(); + return true; + } else if (jsonReader.peek() == JsonToken.END_ARRAY) { + // empty result array — treat as no-more-data for this split/page + LOG.debug("openNextPage: found empty result array (no records). Closing and returning false."); + jsonReader.endArray(); + // cleanup + closeCurrentPage(); + closeRestAPIResponse(resp); + return false; + } + } catch (IOException e) { + closeCurrentPage(); + throw e; + } + + // No "result" array not found, close the current page and return false + closeCurrentPage(); + return false; + } + + public void closeCurrentPage() { + if (this.jsonReader != null) { + try { + this.jsonReader.close(); + } catch (IOException e) { + LOG.warn("Error closing JSON reader", e); + } finally { + this.jsonReader = null; + } + } + } + + public void closeRestAPIResponse(RestAPIResponse resp) { + if (resp != null) { + try { + resp.close(); + } catch (IOException e) { + LOG.warn("Error closing RestAPIResponse", e); + } + } + } + + abstract RestAPIResponse fetchData() throws ServiceNowAPIException; public NullWritable getCurrentKey() { return NullWritable.get(); diff --git a/src/main/java/io/cdap/plugin/servicenow/source/ServiceNowMultiRecordReader.java b/src/main/java/io/cdap/plugin/servicenow/source/ServiceNowMultiRecordReader.java index d10d1bc7..f48806a2 100644 --- a/src/main/java/io/cdap/plugin/servicenow/source/ServiceNowMultiRecordReader.java +++ b/src/main/java/io/cdap/plugin/servicenow/source/ServiceNowMultiRecordReader.java @@ -17,23 +17,36 @@ package io.cdap.plugin.servicenow.source; import com.google.common.annotations.VisibleForTesting; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; import io.cdap.cdap.api.data.format.StructuredRecord; import io.cdap.cdap.api.data.schema.Schema; import io.cdap.plugin.servicenow.apiclient.ServiceNowAPIException; import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIClientImpl; import io.cdap.plugin.servicenow.connector.ServiceNowRecordConverter; +import io.cdap.plugin.servicenow.restapi.RestAPIResponse; +import io.cdap.plugin.servicenow.util.ServiceNowConstants; import org.apache.hadoop.mapreduce.InputSplit; import org.apache.hadoop.mapreduce.TaskAttemptContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * Record reader that reads the entire contents of a ServiceNow table. */ public class ServiceNowMultiRecordReader extends ServiceNowBaseRecordReader { - + private final ServiceNowMultiSourceConfig multiSourcePluginConf; private ServiceNowTableAPIClientImpl restApi; @@ -53,26 +66,6 @@ public void initialize(InputSplit split, TaskAttemptContext context) { fetchSchema(restApi); } - @Override - public boolean nextKeyValue() throws IOException { - try { - if (results == null) { - fetchData(); - } - - if (!iterator.hasNext()) { - return false; - } - - row = iterator.next(); - - pos++; - } catch (Exception e) { - throw new IOException("Exception in nextKeyValue", e); - } - return true; - } - @Override public StructuredRecord getCurrentValue() throws IOException { StructuredRecord.Builder recordBuilder = StructuredRecord.builder(schema); @@ -91,14 +84,13 @@ public StructuredRecord getCurrentValue() throws IOException { } @VisibleForTesting - void fetchData() throws ServiceNowAPIException { + RestAPIResponse fetchData() throws ServiceNowAPIException { // Get the table data - results = restApi.fetchTableRecordsRetryableMode(tableName, multiSourcePluginConf.getValueType(), - multiSourcePluginConf.getStartDate(), - multiSourcePluginConf.getEndDate(), split.getOffset(), - multiSourcePluginConf.getPageSize()); - - iterator = results.iterator(); + RestAPIResponse restAPIResponse = restApi.fetchTableRecordsRetryableMode(tableName, + multiSourcePluginConf.getValueType(), multiSourcePluginConf.getStartDate(), multiSourcePluginConf.getEndDate(), + split.getOffset(), multiSourcePluginConf.getPageSize()); + + return restAPIResponse; } private void fetchSchema(ServiceNowTableAPIClientImpl restApi) { diff --git a/src/main/java/io/cdap/plugin/servicenow/source/ServiceNowRecordReader.java b/src/main/java/io/cdap/plugin/servicenow/source/ServiceNowRecordReader.java index 41d58c7c..ab1b6174 100644 --- a/src/main/java/io/cdap/plugin/servicenow/source/ServiceNowRecordReader.java +++ b/src/main/java/io/cdap/plugin/servicenow/source/ServiceNowRecordReader.java @@ -16,11 +16,17 @@ package io.cdap.plugin.servicenow.source; +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; import io.cdap.cdap.api.data.format.StructuredRecord; import io.cdap.cdap.api.data.schema.Schema; import io.cdap.plugin.servicenow.apiclient.ServiceNowAPIException; import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIClientImpl; import io.cdap.plugin.servicenow.connector.ServiceNowRecordConverter; +import io.cdap.plugin.servicenow.restapi.RestAPIResponse; +import io.cdap.plugin.servicenow.util.ServiceNowConstants; import io.cdap.plugin.servicenow.util.ServiceNowTableInfo; import io.cdap.plugin.servicenow.util.SourceQueryMode; import org.apache.hadoop.mapreduce.InputSplit; @@ -28,8 +34,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * Record reader that reads the entire contents of a ServiceNow table. @@ -62,27 +73,6 @@ public void initialize(InputSplit split, Schema schema) { initializeSchema(tableName, schema); } - @Override - public boolean nextKeyValue() throws IOException { - try { - if (results == null) { - fetchData(); - } - - if (!iterator.hasNext()) { - return false; - } - - row = iterator.next(); - - pos++; - } catch (Exception e) { - LOG.error("Error in nextKeyValue", e); - throw new IOException("Exception in nextKeyValue", e); - } - return true; - } - @Override public StructuredRecord getCurrentValue() throws IOException { StructuredRecord.Builder recordBuilder = StructuredRecord.builder(schema); @@ -103,14 +93,12 @@ public StructuredRecord getCurrentValue() throws IOException { return recordBuilder.build(); } - private void fetchData() throws ServiceNowAPIException { + RestAPIResponse fetchData() throws ServiceNowAPIException { // Get the table data - results = restApi.fetchTableRecordsRetryableMode(tableName, pluginConf.getValueType(), pluginConf.getStartDate(), - pluginConf.getEndDate(), split.getOffset(), - pluginConf.getPageSize()); - LOG.debug("Results size={}", results.size()); + RestAPIResponse restAPIResponse = restApi.fetchTableRecordsRetryableMode(tableName, pluginConf.getValueType(), + pluginConf.getStartDate(), pluginConf.getEndDate(), split.getOffset(), pluginConf.getPageSize()); - iterator = results.iterator(); + return restAPIResponse; } protected void initialize(InputSplit split) { diff --git a/src/test/java/io/cdap/plugin/servicenow/apiclient/ServiceNowTableAPIClientImplTest.java b/src/test/java/io/cdap/plugin/servicenow/apiclient/ServiceNowTableAPIClientImplTest.java index 65902ad9..8c3b48b2 100644 --- a/src/test/java/io/cdap/plugin/servicenow/apiclient/ServiceNowTableAPIClientImplTest.java +++ b/src/test/java/io/cdap/plugin/servicenow/apiclient/ServiceNowTableAPIClientImplTest.java @@ -15,6 +15,9 @@ import org.junit.rules.ExpectedException; import org.mockito.Mockito; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -34,10 +37,11 @@ public void testFetchTableRecordsRetryableMode_RetriesAndSucceeds() throws Servi put("keyTest", "valueTest"); }}); HttpResponse mockResponse = Mockito.mock(HttpResponse.class); + RestAPIResponse mockApiResponse = new RestAPIResponse(Collections.emptyMap(), null, null); Mockito.when(mockResponse.getStatusLine()).thenReturn(Mockito.mock(StatusLine.class)); Mockito.when(mockResponse.getStatusLine().getStatusCode()).thenReturn(HttpStatus.SC_REQUEST_TIMEOUT); Mockito.doThrow(new ServiceNowAPIException("Retryable Error", mockResponse)) - .doReturn(mockResults) + .doReturn(mockApiResponse) .when(implSpy).fetchTableRecords( Mockito.anyString(), Mockito.any(), @@ -45,7 +49,7 @@ public void testFetchTableRecordsRetryableMode_RetriesAndSucceeds() throws Servi Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt()); - List> receivedResults = + RestAPIResponse restAPIResponse = implSpy.fetchTableRecordsRetryableMode( "test", SourceValueType.SHOW_DISPLAY_VALUE, "", "", 0, 0); Mockito.verify(implSpy, Mockito.times(2)).fetchTableRecords( @@ -55,7 +59,7 @@ public void testFetchTableRecordsRetryableMode_RetriesAndSucceeds() throws Servi Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt()); - Assert.assertEquals(receivedResults, mockResults); + Assert.assertEquals(restAPIResponse, mockApiResponse); } @Test @@ -65,11 +69,12 @@ public void testFetchTableRecordsRetryableMode_nonRetryable() ServiceNowTableAPIClientImpl impl = new ServiceNowTableAPIClientImpl(mockConfig, true); ServiceNowTableAPIClientImpl implSpy = Mockito.spy(impl); HttpResponse mockResponse = Mockito.mock(HttpResponse.class); + RestAPIResponse mockAPIResponse = new RestAPIResponse(Collections.emptyMap(), null, null); Mockito.when(mockResponse.getStatusLine()).thenReturn(Mockito.mock(StatusLine.class)); Mockito.when(mockResponse.getStatusLine().getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR); Mockito.doThrow( new ServiceNowAPIException("Non-retryable Error", mockResponse)) - .doReturn(new ArrayList<>()) + .doReturn(mockAPIResponse) .when(implSpy).fetchTableRecords( Mockito.anyString(), Mockito.any(), @@ -104,7 +109,9 @@ public void testFetchTableSchema_ActualValueType() throws Exception { " }\n" + " ]\n" + "}"; - RestAPIResponse mockResponse = new RestAPIResponse(Collections.emptyMap(), jsonResponse, null); + byte[] body = jsonResponse.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse mockResponse = new RestAPIResponse(Collections.emptyMap(), inputStream, null); Mockito.doReturn(mockResponse).when(implSpy).executeGetWithRetries(Mockito.any()); Schema schema = implSpy.fetchTableSchema("sys_user", "dummy-access-token", SourceValueType.SHOW_ACTUAL_VALUE, SchemaType.SCHEMA_API_BASED); @@ -136,7 +143,9 @@ public void testFetchTableSchema_GlideTimeFieldWithActualValueType() throws Exce " }\n" + "}"; - RestAPIResponse mockResponse = new RestAPIResponse(Collections.emptyMap(), jsonResponse, null); + byte[] body = jsonResponse.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse mockResponse = new RestAPIResponse(Collections.emptyMap(), inputStream, null); Mockito.doReturn(mockResponse).when(implSpy).executeGetWithRetries(Mockito.any()); Schema schema = implSpy.fetchTableSchema("u_custom_table", @@ -175,7 +184,9 @@ public void testFetchTableSchema_DisplayValueType() throws Exception { " }\n" + " ]\n" + "}"; - RestAPIResponse mockResponse = new RestAPIResponse(Collections.emptyMap(), jsonResponse, null); + byte[] body = jsonResponse.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse mockResponse = new RestAPIResponse(Collections.emptyMap(), inputStream, null); Mockito.doReturn(mockResponse).when(implSpy).executeGetWithRetries(Mockito.any()); Schema schema = implSpy.fetchTableSchema("sys_user", "dummy-access-token", SourceValueType.SHOW_DISPLAY_VALUE, SchemaType.SCHEMA_API_BASED); @@ -212,7 +223,9 @@ public void testFetchTableSchema_StcFieldsWithDisplayValueType_ParseAsString() t " }\n" + " }\n" + "}"; - RestAPIResponse mockResponse = new RestAPIResponse(Collections.emptyMap(), jsonResponse, null); + byte[] body = jsonResponse.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse mockResponse = new RestAPIResponse(Collections.emptyMap(), inputStream, null); Mockito.doReturn(mockResponse).when(implSpy).executeGetWithRetries(Mockito.any()); Schema schema = implSpy.fetchTableSchema("incident", "dummy-access-token", SourceValueType.SHOW_DISPLAY_VALUE, SchemaType.METADATA_API_BASED); diff --git a/src/test/java/io/cdap/plugin/servicenow/connector/ServiceNowConnectorTest.java b/src/test/java/io/cdap/plugin/servicenow/connector/ServiceNowConnectorTest.java index 2240e8b5..94be7c57 100644 --- a/src/test/java/io/cdap/plugin/servicenow/connector/ServiceNowConnectorTest.java +++ b/src/test/java/io/cdap/plugin/servicenow/connector/ServiceNowConnectorTest.java @@ -15,6 +15,7 @@ */ package io.cdap.plugin.servicenow.connector; +import com.google.gson.JsonObject; import io.cdap.cdap.api.data.schema.Schema; import io.cdap.cdap.etl.api.batch.BatchSource; import io.cdap.cdap.etl.api.connector.ConnectorContext; @@ -26,6 +27,7 @@ import io.cdap.cdap.etl.mock.validation.MockFailureCollector; import io.cdap.plugin.common.ConfigUtil; import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIClientImpl; +import io.cdap.plugin.servicenow.restapi.RestAPIClient; import io.cdap.plugin.servicenow.restapi.RestAPIResponse; import io.cdap.plugin.servicenow.source.ServiceNowBaseSourceConfig; import io.cdap.plugin.servicenow.source.ServiceNowInputFormat; @@ -53,7 +55,10 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -95,6 +100,9 @@ public void initialize() { public void testTest() throws Exception { MockFailureCollector collector = new MockFailureCollector(); ConnectorContext context = new MockConnectorContext(new MockConnectorConfigurer()); + CloseableHttpClient mockHttpClient = Mockito.mock(CloseableHttpClient.class); + PowerMockito.stub(PowerMockito.method(RestAPIClient.class, "getHttpClient")) + .toReturn(mockHttpClient); ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); Mockito.when(restApi.getAccessToken()).thenReturn("token"); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); @@ -106,6 +114,9 @@ public void testTest() throws Exception { @Test public void testTestWithInvalidToken() throws Exception { ConnectorContext context = new MockConnectorContext(new MockConnectorConfigurer()); + CloseableHttpClient mockHttpClient = Mockito.mock(CloseableHttpClient.class); + PowerMockito.stub(PowerMockito.method(RestAPIClient.class, "getHttpClient")) + .toReturn(mockHttpClient); ServiceNowConnector serviceNowConnector = new ServiceNowConnector(serviceNowSourceConfig.getConnection()); serviceNowConnector.test(context); Assert.assertEquals(1, context.getFailureCollector().getValidationFailures().size()); @@ -116,10 +127,10 @@ public void testGenerateSpec() throws Exception { ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); Mockito.when(restApi.getAccessToken()).thenReturn("token"); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - Map map = new HashMap<>(); - List> result = new ArrayList<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject = new JsonObject(); + List result = new ArrayList<>(); + jsonObject.addProperty("key", "value"); + result.add(jsonObject); int httpStatus = HttpStatus.SC_OK; Map headers = new HashMap<>(); String responseBody = "{\n" + @@ -131,9 +142,11 @@ public void testGenerateSpec() throws Exception { " }\n" + " ]\n" + "}"; - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); OAuthClient oAuthClient = Mockito.mock(OAuthClient.class); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); OAuthJSONAccessTokenResponse accessTokenResponse = Mockito.mock(OAuthJSONAccessTokenResponse.class); diff --git a/src/test/java/io/cdap/plugin/servicenow/restapi/RestAPIClientTest.java b/src/test/java/io/cdap/plugin/servicenow/restapi/RestAPIClientTest.java index 93649af7..b288bd6e 100644 --- a/src/test/java/io/cdap/plugin/servicenow/restapi/RestAPIClientTest.java +++ b/src/test/java/io/cdap/plugin/servicenow/restapi/RestAPIClientTest.java @@ -40,16 +40,14 @@ public void testExecuteGet_throwRetryableException() throws IOException { Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine); CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); - HttpClientBuilder httpClientBuilder = Mockito.mock(HttpClientBuilder.class); - PowerMockito.mockStatic(HttpClientBuilder.class); - PowerMockito.when(HttpClientBuilder.create()).thenReturn(httpClientBuilder); - Mockito.when(httpClientBuilder.build()).thenReturn(httpClient); Mockito.when(httpClient.execute(Mockito.any())).thenReturn(httpResponse); ServiceNowTableAPIRequestBuilder builder = new ServiceNowTableAPIRequestBuilder("url"); RestAPIRequest request = builder.build(); ServiceNowConnectorConfig config = Mockito.mock(ServiceNowConnectorConfig.class); + PowerMockito.stub(PowerMockito.method(RestAPIClient.class, "getHttpClient")) + .toReturn(httpClient); ServiceNowTableAPIClientImpl client = new ServiceNowTableAPIClientImpl(config, true); RestAPIResponse actualResponse = client.executeGet(request); Assert.assertNotNull(actualResponse.getException()); @@ -64,16 +62,14 @@ public void testExecuteGet_throwNonRetryableException() throws IOException { Mockito.when(httpResponse.getStatusLine()).thenReturn(statusLine); CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); - HttpClientBuilder httpClientBuilder = Mockito.mock(HttpClientBuilder.class); - PowerMockito.mockStatic(HttpClientBuilder.class); - PowerMockito.when(HttpClientBuilder.create()).thenReturn(httpClientBuilder); - Mockito.when(httpClientBuilder.build()).thenReturn(httpClient); Mockito.when(httpClient.execute(Mockito.any())).thenReturn(httpResponse); ServiceNowTableAPIRequestBuilder builder = new ServiceNowTableAPIRequestBuilder("url"); RestAPIRequest request = builder.build(); ServiceNowConnectorConfig config = Mockito.mock(ServiceNowConnectorConfig.class); + PowerMockito.stub(PowerMockito.method(RestAPIClient.class, "getHttpClient")) + .toReturn(httpClient); ServiceNowTableAPIClientImpl client = new ServiceNowTableAPIClientImpl(config, true); RestAPIResponse actualResponse = client.executeGet(request); Assert.assertNotNull(actualResponse.getException()); @@ -108,10 +104,6 @@ public void testExecuteGet_StatusOk() throws IOException { @Test public void testExecuteGet_throwConnectTimeoutException_markAsRetryable() throws IOException { CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); - HttpClientBuilder httpClientBuilder = Mockito.mock(HttpClientBuilder.class); - PowerMockito.mockStatic(HttpClientBuilder.class); - PowerMockito.when(HttpClientBuilder.create()).thenReturn(httpClientBuilder); - Mockito.when(httpClientBuilder.build()).thenReturn(httpClient); Mockito.when(httpClient.execute(Mockito.any())) .thenThrow(new ConnectTimeoutException("Connection timed out")); @@ -119,6 +111,8 @@ public void testExecuteGet_throwConnectTimeoutException_markAsRetryable() throws RestAPIRequest request = builder.build(); ServiceNowConnectorConfig config = Mockito.mock(ServiceNowConnectorConfig.class); + PowerMockito.stub(PowerMockito.method(RestAPIClient.class, "getHttpClient")) + .toReturn(httpClient); ServiceNowTableAPIClientImpl client = new ServiceNowTableAPIClientImpl(config, true); RestAPIResponse actualResponse = client.executeGet(request); Assert.assertNotNull(actualResponse.getException()); @@ -131,10 +125,6 @@ public void testExecuteGet_throwConnectTimeoutException_markAsRetryable() throws @Test public void testExecuteGet_throwSocketException_markAsRetryable() throws IOException { CloseableHttpClient httpClient = Mockito.mock(CloseableHttpClient.class); - HttpClientBuilder httpClientBuilder = Mockito.mock(HttpClientBuilder.class); - PowerMockito.mockStatic(HttpClientBuilder.class); - PowerMockito.when(HttpClientBuilder.create()).thenReturn(httpClientBuilder); - Mockito.when(httpClientBuilder.build()).thenReturn(httpClient); Mockito.when(httpClient.execute(Mockito.any())) .thenThrow(new SocketException()); @@ -142,6 +132,8 @@ public void testExecuteGet_throwSocketException_markAsRetryable() throws IOExcep RestAPIRequest request = builder.build(); ServiceNowConnectorConfig config = Mockito.mock(ServiceNowConnectorConfig.class); + PowerMockito.stub(PowerMockito.method(RestAPIClient.class, "getHttpClient")) + .toReturn(httpClient); ServiceNowTableAPIClientImpl client = new ServiceNowTableAPIClientImpl(config, true); RestAPIResponse actualResponse = client.executeGet(request); Assert.assertNotNull(actualResponse.getException()); diff --git a/src/test/java/io/cdap/plugin/servicenow/sink/ServiceNowRecordWriterTest.java b/src/test/java/io/cdap/plugin/servicenow/sink/ServiceNowRecordWriterTest.java index 259e4582..0ecd893b 100644 --- a/src/test/java/io/cdap/plugin/servicenow/sink/ServiceNowRecordWriterTest.java +++ b/src/test/java/io/cdap/plugin/servicenow/sink/ServiceNowRecordWriterTest.java @@ -93,15 +93,15 @@ public void testWriteWithUnSuccessfulApiResponse() throws Exception { ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); Mockito.when(restApi.getAccessToken()).thenReturn("token"); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - Map map = new HashMap<>(); - List> result = new ArrayList<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject1 = new JsonObject(); + List result = new ArrayList<>(); + jsonObject1.addProperty("key", "value"); + result.add(jsonObject); Map headers = new HashMap<>(); RestAPIResponse restAPIResponse = new RestAPIResponse( - headers, responseBody, null); + headers, null, null); Mockito.when(restApi.executePost(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); OAuthClient oAuthClient = Mockito.mock(OAuthClient.class); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); OAuthJSONAccessTokenResponse accessTokenResponse = Mockito.mock(OAuthJSONAccessTokenResponse.class); @@ -138,14 +138,14 @@ public void testWriteWithSuccessFulApiResponse() throws Exception { ServiceNowSinkAPIRequestImpl serviceNowSinkAPIRequest = Mockito.mock(ServiceNowSinkAPIRequestImpl.class); PowerMockito.whenNew(ServiceNowSinkAPIRequestImpl.class).withParameterTypes(ServiceNowSinkConfig.class) .withArguments(Mockito.any(ServiceNowSinkConfig.class)).thenReturn(serviceNowSinkAPIRequest); - Map map = new HashMap<>(); - List> result = new ArrayList<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject1 = new JsonObject(); + List result = new ArrayList<>(); + jsonObject1.addProperty("key", "value"); + result.add(jsonObject); Map headers = new HashMap<>(); - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, null, null); Mockito.when(restApi.executePost(Mockito.any(RestAPIRequest.class))).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); OAuthClient oAuthClient = Mockito.mock(OAuthClient.class); PowerMockito.whenNew(OAuthClient.class). withArguments(Mockito.any(URLConnectionClient.class)).thenReturn(oAuthClient); @@ -186,14 +186,14 @@ public void testWriteWithUnservicedRequests() throws Exception { ServiceNowSinkAPIRequestImpl serviceNowSinkAPIRequest = Mockito.mock(ServiceNowSinkAPIRequestImpl.class); PowerMockito.whenNew(ServiceNowSinkAPIRequestImpl.class).withParameterTypes(ServiceNowSinkConfig.class) .withArguments(Mockito.any(ServiceNowSinkConfig.class)).thenReturn(serviceNowSinkAPIRequest); - Map map = new HashMap<>(); - List> result = new ArrayList<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject1 = new JsonObject(); + List result = new ArrayList<>(); + jsonObject1.addProperty("key", "value"); + result.add(jsonObject); Map headers = new HashMap<>(); - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, null, null); Mockito.when(restApi.executePost(Mockito.any(RestAPIRequest.class))).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); OAuthClient oAuthClient = Mockito.mock(OAuthClient.class); PowerMockito.whenNew(OAuthClient.class). withArguments(Mockito.any(URLConnectionClient.class)).thenReturn(oAuthClient); diff --git a/src/test/java/io/cdap/plugin/servicenow/sink/ServiceNowSinkConfigTest.java b/src/test/java/io/cdap/plugin/servicenow/sink/ServiceNowSinkConfigTest.java index 04ba38e3..5d08d717 100644 --- a/src/test/java/io/cdap/plugin/servicenow/sink/ServiceNowSinkConfigTest.java +++ b/src/test/java/io/cdap/plugin/servicenow/sink/ServiceNowSinkConfigTest.java @@ -50,6 +50,9 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -296,6 +299,8 @@ public void testValidateSchema() throws Exception { " }\n" + " ]\n" + "}"; + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); MetadataAPISchemaField schemaField = new MetadataAPISchemaField("Class", "sys_class_name", "sys_class_name", "sys_class_name"); Map columns = new HashMap<>(); @@ -306,7 +311,7 @@ public void testValidateSchema() throws Exception { Mockito.when(mockResponse.getStatusLine()).thenReturn(Mockito.mock(StatusLine.class)); Mockito.when(mockResponse.getStatusLine().getStatusCode()).thenReturn(httpStatus); RestAPIResponse restAPIResponse = new RestAPIResponse( - headers, responseBody, new ServiceNowAPIException("", mockResponse)); + headers, inputStream, new ServiceNowAPIException("", mockResponse)); OAuthClient oAuthClient = Mockito.mock(OAuthClient.class); PowerMockito.whenNew(OAuthClient.class). withArguments(Mockito.any(URLConnectionClient.class)).thenReturn(oAuthClient); @@ -326,7 +331,7 @@ public void testValidateSchema() throws Exception { PowerMockito.when(RestAPIResponse.parse(httpResponse, null)).thenReturn(response); Mockito.when(restApi.executeGetWithRetries(Mockito.any(RestAPIRequest.class))).thenReturn(restAPIResponse); Mockito.when(restApi.fetchTableSchema(Mockito.anyString(), Mockito.any(FailureCollector.class))).thenReturn(schema); - Mockito.when(restApi.parseSchemaResponse(restAPIResponse.getResponseBody())) + Mockito.when(restApi.parseSchemaResponse(restAPIResponse.getResponseStream())) .thenReturn(metadataAPISchemaResponse); try { config.validateSchema(schema, collector); @@ -362,7 +367,9 @@ public void testValidateSchemaWithOperation() throws Exception { " }\n" + " ]\n" + "}"; - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); OAuthClient oAuthClient = Mockito.mock(OAuthClient.class); PowerMockito.whenNew(OAuthClient.class). withArguments(Mockito.any(URLConnectionClient.class)).thenReturn(oAuthClient); diff --git a/src/test/java/io/cdap/plugin/servicenow/sink/ServiceNowSinkTest.java b/src/test/java/io/cdap/plugin/servicenow/sink/ServiceNowSinkTest.java index 3f748d61..05cf3b4a 100644 --- a/src/test/java/io/cdap/plugin/servicenow/sink/ServiceNowSinkTest.java +++ b/src/test/java/io/cdap/plugin/servicenow/sink/ServiceNowSinkTest.java @@ -25,14 +25,11 @@ import io.cdap.cdap.etl.mock.common.MockArguments; import io.cdap.cdap.etl.mock.common.MockPipelineConfigurer; import io.cdap.cdap.etl.mock.validation.MockFailureCollector; -import io.cdap.plugin.servicenow.ServiceNowBaseConfig; import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIClientImpl; -import io.cdap.plugin.servicenow.connector.ServiceNowConnectorConfig; import io.cdap.plugin.servicenow.restapi.RestAPIResponse; import io.cdap.plugin.servicenow.sink.transform.ServiceNowTransformer; import io.cdap.plugin.servicenow.source.ServiceNowBaseSourceConfig; import org.apache.hadoop.io.NullWritable; -import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -48,6 +45,9 @@ import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -87,15 +87,17 @@ public void testConfigurePipeline() throws Exception { ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); Mockito.when(restApi.getAccessToken()).thenReturn("token"); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - List> result = new ArrayList<>(); + List result = new ArrayList<>(); Map headers = new HashMap<>(); String responseBody = "{\n" + " \"result\": []\n" + "}"; + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); MockFailureCollector collector = new MockFailureCollector(); - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); serviceNowSink.configurePipeline(mockPipelineConfigurer); Assert.assertNull(restAPIResponse.getException()); Assert.assertEquals(0, collector.getValidationFailures().size()); @@ -110,10 +112,10 @@ public void testPrepareRun() throws Exception { Mockito.when(context.getArguments()).thenReturn(mockArguments); ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - List> result = new ArrayList<>(); - Map map = new HashMap<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject = new JsonObject(); + List result = new ArrayList<>(); + jsonObject.addProperty("key", "value"); + result.add(jsonObject); Map headers = new HashMap<>(); String responseBody = "{\n" + " \"result\": [\n" + @@ -125,14 +127,16 @@ public void testPrepareRun() throws Exception { " }\n" + " ]\n" + "}"; + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); Schema schema = Schema.recordOf("record", Schema.Field.of("id", Schema.of(Schema.Type.LONG)), Schema.Field.of("price", Schema.of(Schema.Type.DOUBLE))); Emitter> emitter = Mockito.mock(Emitter.class); Mockito.when(context.getInputSchema()).thenReturn(schema); - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); OAuthClient oAuthClient = Mockito.mock(OAuthClient.class); PowerMockito.whenNew(OAuthClient.class). withArguments(Mockito.any(URLConnectionClient.class)).thenReturn(oAuthClient); diff --git a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowInputFormatTest.java b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowInputFormatTest.java index d206458c..6b3b82d0 100644 --- a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowInputFormatTest.java +++ b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowInputFormatTest.java @@ -17,6 +17,7 @@ package io.cdap.plugin.servicenow.source; +import com.google.gson.JsonObject; import io.cdap.cdap.api.data.schema.Schema; import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIClientImpl; import io.cdap.plugin.servicenow.connector.ServiceNowConnectorConfig; @@ -42,6 +43,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -71,10 +75,10 @@ public void testFetchTableInfo() throws Exception { SourceQueryMode mode = SourceQueryMode.TABLE; ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - List> result = new ArrayList<>(); - Map map = new HashMap<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject = new JsonObject(); + List result = new ArrayList<>(); + jsonObject.addProperty("key", "value"); + result.add(jsonObject); Map headers = new HashMap<>(); String responseBody = "{\n" + " \"result\": [\n" + @@ -138,13 +142,15 @@ public void testFetchTableInfo() throws Exception { " }\n" + " ]\n" + "}"; + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); String schemaString = "{\"type\":\"record\",\"name\":\"ServiceNowColumnMetaData\",\"fields\":[{\"name\":" + "\"backgroundElementId\",\"type\":\"long\"},{\"name\":\"bgOrderPos\",\"type\":\"long\"},{\"name\":" + "\"description\",\"type\":[\"string\",\"null\"]},{\"name\":\"userId\",\"type\":\"string\"}]}"; Schema schema = Schema.parseJson(schemaString); - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); Mockito.when(restApi.fetchTableSchema("table", SourceValueType.SHOW_ACTUAL_VALUE)).thenReturn(schema); OAuthClient oAuthClient = Mockito.mock(OAuthClient.class); PowerMockito.whenNew(OAuthClient.class). @@ -175,10 +181,10 @@ public void testFetchTableInfoReportingMode() throws Exception { SourceQueryMode mode = SourceQueryMode.REPORTING; ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - List> result = new ArrayList<>(); - Map map = new HashMap<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject = new JsonObject(); + List result = new ArrayList<>(); + jsonObject.addProperty("key", "value"); + result.add(jsonObject); Map headers = new HashMap<>(); String responseBody = "{\n" + " \"result\": [\n" + @@ -242,13 +248,15 @@ public void testFetchTableInfoReportingMode() throws Exception { " }\n" + " ]\n" + "}"; + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); String schemaString = "{\"type\":\"record\",\"name\":\"ServiceNowColumnMetaData\",\"fields\":[{\"name\":" + "\"backgroundElementId\",\"type\":\"long\"},{\"name\":\"bgOrderPos\",\"type\":\"long\"},{\"name\":" + "\"description\",\"type\":[\"string\",\"null\"]},{\"name\":\"userId\",\"type\":\"string\"}]}"; Schema schema = Schema.parseJson(schemaString); - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); Mockito.when(restApi.fetchTableSchema("proc_po", SourceValueType.SHOW_ACTUAL_VALUE)).thenReturn(schema); Mockito.when(restApi.fetchTableSchema("proc_po_item", SourceValueType.SHOW_ACTUAL_VALUE)).thenReturn(schema); @@ -283,17 +291,19 @@ public void testFetchTableInfoWithEmptyTableName() throws Exception { SourceQueryMode mode = SourceQueryMode.TABLE; ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - List> result = new ArrayList<>(); - Map map = new HashMap<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject = new JsonObject(); + List result = new ArrayList<>(); + jsonObject.addProperty("key", "value"); + result.add(jsonObject); Map headers = new HashMap<>(); String responseBody = "{\n" + " \"result\": []\n" + "}"; - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); OAuthClient oAuthClient = Mockito.mock(OAuthClient.class); PowerMockito.mockStatic(ServiceNowInputFormat.class); PowerMockito.whenNew(OAuthClient.class). diff --git a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowMultiRecordReaderTest.java b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowMultiRecordReaderTest.java index 0169d88b..e03bf6ef 100644 --- a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowMultiRecordReaderTest.java +++ b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowMultiRecordReaderTest.java @@ -16,6 +16,7 @@ package io.cdap.plugin.servicenow.source; +import com.google.gson.JsonObject; import io.cdap.cdap.api.data.format.StructuredRecord; import io.cdap.cdap.api.data.format.UnexpectedFormatException; import io.cdap.cdap.api.data.schema.Schema; @@ -24,7 +25,9 @@ import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIClientImpl; import io.cdap.plugin.servicenow.apiclient.ServiceNowTableDataResponse; import io.cdap.plugin.servicenow.connector.ServiceNowRecordConverter; -import io.cdap.plugin.servicenow.util.ServiceNowConstants; +import io.cdap.plugin.servicenow.restapi.RestAPIClient; +import io.cdap.plugin.servicenow.restapi.RestAPIResponse; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.oltu.oauth2.common.exception.OAuthProblemException; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.junit.Assert; @@ -32,9 +35,16 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; @@ -42,6 +52,9 @@ import java.util.List; import java.util.Map; +@RunWith(PowerMockRunner.class) +@PrepareForTest({ServiceNowTableAPIClientImpl.class, ServiceNowMultiSourceConfig.class, + ServiceNowMultiRecordReader.class}) public class ServiceNowMultiRecordReaderTest { private static final String CLIENT_ID = "clientId"; @@ -95,9 +108,9 @@ public void testConvertToValueInvalidFieldType() { Schema fieldSchema = Schema.recordOf("record", Schema.Field.of("TimeField", Schema.of(Schema.LogicalType.TIMESTAMP_MILLIS))); StructuredRecord.Builder recordBuilder = StructuredRecord.builder(fieldSchema); - Map map = new HashMap<>(); - map.put("TimeField", "value"); - ServiceNowRecordConverter.convertToValue("TimeField", fieldSchema, map, recordBuilder); + JsonObject record = new JsonObject(); + record.addProperty("TimeField", "value"); + ServiceNowRecordConverter.convertToValue("TimeField", fieldSchema, record, recordBuilder); } @Test @@ -121,25 +134,61 @@ public void testConvertToBooleanValueForInvalidFieldValue() { } @Test - public void testFetchData() throws ServiceNowAPIException, IOException { + public void testFetchData() throws Exception { String tableName = serviceNowMultiSourceConfig.getTableNames(); ServiceNowInputSplit split = new ServiceNowInputSplit(tableName, 1); - - List> results = new ArrayList<>(); - Map map = new HashMap<>(); - map.put("calendar_integration", "1"); - map.put("country", "India"); - map.put("sys_updated_on", "2019-04-05 21:54:45"); - map.put("web_service_access_only", "false"); - map.put("notification", "2"); - map.put("enable_multifactor_authn", "false"); - map.put("sys_updated_by", "system"); - map.put("sys_created_on", "2019-04-05 21:09:12"); - results.add(map); - - ServiceNowTableDataResponse response = new ServiceNowTableDataResponse(); - response.setResult(results); ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); + ServiceNowMultiRecordReader serviceNowMultiRecordReader = + new ServiceNowMultiRecordReader(serviceNowMultiSourceConfig); + String responseBody = "{\n" + + " \"result\": [\n" + + " {\n" + + " \"bill_to\": \"\",\n" + + " \"init_request\": \"\",\n" + + " \"short_description\": \"\",\n" + + " \"total_cost\": \"0\",\n" + + " \"due_by\": \"\",\n" + + " \"description\": \"\",\n" + + " \"requested_for\": \"\",\n" + + " \"sys_updated_on\": \"2022-06-16 18:56:23\",\n" + + " \"budget_number\": \"\",\n" + + " \"number\": \"RCS397871\",\n" + + " \"sys_id\": \"00000b7287405910827733373cbb35d5\",\n" + + " \"sys_updated_by\": \"pipeline.user.1\",\n" + + " \"shipping\": \"\",\n" + + " \"terms\": \"\",\n" + + " \"sys_created_on\": \"2022-06-16 18:56:23\",\n" + + " \"vendor\": \"\",\n" + + " \"sys_domain\": \"global\",\n" + + " \"department\": \"\",\n" + + " \"sys_created_by\": \"pipeline.user.1\",\n" + + " \"assigned_to\": \"\",\n" + + " \"ordered\": \"\",\n" + + " \"po_date\": \"2022-06-16 18:56:23\",\n" + + " \"vendor_contract\": \"\",\n" + + " \"contract\": \"\",\n" + + " \"expected_delivery\": \"\",\n" + + " \"sys_mod_count\": \"0\",\n" + + " \"received\": \"2158-05-10 17:14:20\",\n" + + " \"asset_operation\": \"\",\n" + + " \"sys_tags\": \"\",\n" + + " \"requested\": \"2022-06-16 18:56:23\",\n" + + " \"requested_by\": \"\",\n" + + " \"ship_rate\": \"0\",\n" + + " \"location\": \"\",\n" + + " \"vendor_account\": \"\",\n" + + " \"ship_to\": \"\",\n" + + " \"status\": \"requested\"\n" + + " }\n" + + " ]\n" + + "}"; + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(Collections.emptyMap(), inputStream, null); + PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); + Mockito.when(restApi.fetchTableRecordsRetryableMode(tableName, serviceNowMultiSourceConfig.getValueType(), + serviceNowMultiSourceConfig.getStartDate(), serviceNowMultiSourceConfig.getEndDate(), split.getOffset(), + serviceNowMultiSourceConfig.getPageSize())).thenReturn(restAPIResponse); try { Mockito.when(restApi.fetchTableSchema(tableName, serviceNowMultiSourceConfig.getValueType())) .thenReturn(Schema.recordOf(Schema.Field.of("calendar_integration", Schema.of(Schema.Type.STRING)))); @@ -148,10 +197,6 @@ public void testFetchData() throws ServiceNowAPIException, IOException { | ServiceNowAPIException e) { Assert.assertTrue(e instanceof RuntimeException); } - Mockito.doNothing().when(serviceNowMultiRecordReader).fetchData(); - Collections.singletonList(new Object()); - serviceNowMultiRecordReader.iterator = Collections.singletonList(Collections.singletonMap("key", new String())). - iterator(); Assert.assertTrue(serviceNowMultiRecordReader.nextKeyValue()); } @@ -174,6 +219,9 @@ public void testFetchDataOnInvalidTable() .setTableNameField("tablename") .buildMultiSource(); + CloseableHttpClient mockHttpClient = Mockito.mock(CloseableHttpClient.class); + PowerMockito.stub(PowerMockito.method(RestAPIClient.class, "getHttpClient")) + .toReturn(mockHttpClient); String tableName = serviceNowMultiSourceConfig.getTableNames(); ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); ServiceNowInputSplit split = new ServiceNowInputSplit(tableName, 1); diff --git a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowMultiSourceConfigTest.java b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowMultiSourceConfigTest.java index c0a0302a..cdef326b 100644 --- a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowMultiSourceConfigTest.java +++ b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowMultiSourceConfigTest.java @@ -16,11 +16,14 @@ package io.cdap.plugin.servicenow.source; +import com.google.gson.JsonObject; import io.cdap.cdap.etl.api.validation.ValidationException; import io.cdap.cdap.etl.api.validation.ValidationFailure; import io.cdap.cdap.etl.mock.validation.MockFailureCollector; import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIClientImpl; +import io.cdap.plugin.servicenow.restapi.RestAPIClient; import io.cdap.plugin.servicenow.restapi.RestAPIResponse; +import org.apache.http.impl.client.CloseableHttpClient; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -31,6 +34,9 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -77,6 +83,9 @@ public void testValidateInvalidConnection() { .setEndDate("2021-12-31") .setTableNameField("tablename") .buildMultiSource(); + CloseableHttpClient mockHttpClient = Mockito.mock(CloseableHttpClient.class); + PowerMockito.stub(PowerMockito.method(RestAPIClient.class, "getHttpClient")) + .toReturn(mockHttpClient); try { serviceNowMultiSourceConfig.validate(mockFailureCollector); Assert.fail("Exception is not thrown if connection is successful"); @@ -108,10 +117,10 @@ public void testValidate() throws Exception { Mockito.when(restApi.getAccessToken()).thenReturn("token"); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); Map headers = new HashMap<>(); - Map map = new HashMap<>(); - List> result = new ArrayList<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject = new JsonObject(); + List result = new ArrayList<>(); + jsonObject.addProperty("key", "value"); + result.add(jsonObject); String responseBody = "{\n" + " \"result\": [\n" + " {\n" + @@ -174,9 +183,11 @@ public void testValidate() throws Exception { " }\n" + " ]\n" + "}"; - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); serviceNowMultiSourceConfig.validate(mockFailureCollector); Assert.assertEquals(0, mockFailureCollector.getValidationFailures().size()); @@ -207,7 +218,9 @@ public void testValidateWhenTableIsEmpty() throws Exception { String responseBody = "{\n" + " \"result\": []\n" + "}"; - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); serviceNowMultiSourceConfig.validate(mockFailureCollector); Assert.assertEquals(1, mockFailureCollector.getValidationFailures().size()); @@ -235,10 +248,10 @@ public void testValidateReferenceName() throws Exception { Mockito.when(restApi.getAccessToken()).thenReturn("token"); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); Map headers = new HashMap<>(); - Map map = new HashMap<>(); - List> result = new ArrayList<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject = new JsonObject(); + List result = new ArrayList<>(); + jsonObject.addProperty("key", "value"); + result.add(jsonObject); String responseBody = "{\n" + " \"result\": [\n" + " {\n" + @@ -301,9 +314,11 @@ public void testValidateReferenceName() throws Exception { " }\n" + " ]\n" + "}"; - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); try { serviceNowMultiSourceConfig.validate(mockFailureCollector); Assert.fail("Exception is not thrown with valid reference name"); @@ -336,10 +351,10 @@ public void testValidateWhenTableFieldNameIsEmpty() throws Exception { Mockito.when(restApi.getAccessToken()).thenReturn("token"); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); Map headers = new HashMap<>(); - Map map = new HashMap<>(); - List> result = new ArrayList<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject = new JsonObject(); + List result = new ArrayList<>(); + jsonObject.addProperty("key", "value"); + result.add(jsonObject); String responseBody = "{\n" + " \"result\": [\n" + " {\n" + @@ -402,9 +417,11 @@ public void testValidateWhenTableFieldNameIsEmpty() throws Exception { " }\n" + " ]\n" + "}"; - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); serviceNowMultiSourceConfig.validate(mockFailureCollector); Assert.assertEquals(1, mockFailureCollector.getValidationFailures().size()); Assert.assertEquals("Table name field must be specified.", diff --git a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowMultiSourceTest.java b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowMultiSourceTest.java index 033f05ae..7de5d35b 100644 --- a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowMultiSourceTest.java +++ b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowMultiSourceTest.java @@ -16,6 +16,7 @@ package io.cdap.plugin.servicenow.source; +import com.google.gson.JsonObject; import io.cdap.cdap.api.data.schema.Schema; import io.cdap.cdap.etl.api.batch.BatchSourceContext; import io.cdap.cdap.etl.api.validation.ValidationException; @@ -23,10 +24,8 @@ import io.cdap.cdap.etl.mock.common.MockPipelineConfigurer; import io.cdap.cdap.etl.mock.validation.MockFailureCollector; import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIClientImpl; -import io.cdap.plugin.servicenow.connector.ServiceNowConnectorConfig; import io.cdap.plugin.servicenow.restapi.RestAPIResponse; import io.cdap.plugin.servicenow.util.ServiceNowTableInfo; -import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -43,6 +42,9 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -89,10 +91,10 @@ public void testConfigurePipeline() throws Exception { ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); Mockito.when(restApi.getAccessToken()).thenReturn("token"); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - Map map = new HashMap<>(); - List> result = new ArrayList<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject = new JsonObject(); + List result = new ArrayList<>(); + jsonObject.addProperty("key", "value"); + result.add(jsonObject); Map headers = new HashMap<>(); String responseBody = "{\n" + " \"result\": [\n" + @@ -156,9 +158,11 @@ public void testConfigurePipeline() throws Exception { " }\n" + " ]\n" + "}"; - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); serviceNowMultiSource.configurePipeline(mockPipelineConfigurer); Assert.assertNull(mockPipelineConfigurer.getOutputSchema()); Assert.assertEquals(0, mockFailureCollector.getValidationFailures().size()); @@ -171,14 +175,16 @@ public void testConfigurePipelineWithEmptyTable() throws Exception { ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); Mockito.when(restApi.getAccessToken()).thenReturn("token"); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - List> result = new ArrayList<>(); + List result = new ArrayList<>(); Map headers = new HashMap<>(); String responseBody = "{\n" + " \"result\": []\n" + "}"; - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); try { serviceNowMultiSource.configurePipeline(mockPipelineConfigurer); Assert.fail("Exception is not thrown for Non-Empty Tables"); @@ -211,10 +217,10 @@ public void testPrepareRun() throws Exception { Mockito.when(context.getArguments()).thenReturn(mockArguments); ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - List> result = new ArrayList<>(); - Map map = new HashMap<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject = new JsonObject(); + List result = new ArrayList<>(); + jsonObject.addProperty("key", "value"); + result.add(jsonObject); Map headers = new HashMap<>(); String responseBody = "{\n" + " \"result\": [\n" + @@ -278,11 +284,13 @@ public void testPrepareRun() throws Exception { " }\n" + " ]\n" + "}"; + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); PowerMockito.mockStatic(ServiceNowMultiInputFormat.class); Mockito.when(ServiceNowMultiInputFormat.setInput(Mockito.any(), Mockito.any())).thenReturn((tableInfo)); - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); OAuthClient oAuthClient = Mockito.mock(OAuthClient.class); PowerMockito.whenNew(OAuthClient.class). withArguments(Mockito.any(URLConnectionClient.class)).thenReturn(oAuthClient); diff --git a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowRecordReaderTest.java b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowRecordReaderTest.java index 3366f527..2e5ef45b 100644 --- a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowRecordReaderTest.java +++ b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowRecordReaderTest.java @@ -16,6 +16,7 @@ package io.cdap.plugin.servicenow.source; +import com.google.gson.JsonObject; import io.cdap.cdap.api.data.format.StructuredRecord; import io.cdap.cdap.api.data.format.UnexpectedFormatException; import io.cdap.cdap.api.data.schema.Schema; @@ -25,6 +26,7 @@ import io.cdap.plugin.servicenow.apiclient.ServiceNowTableDataResponse; import io.cdap.plugin.servicenow.connector.ServiceNowConnectorConfig; import io.cdap.plugin.servicenow.connector.ServiceNowRecordConverter; +import io.cdap.plugin.servicenow.restapi.RestAPIResponse; import io.cdap.plugin.servicenow.util.ServiceNowColumn; import io.cdap.plugin.servicenow.util.ServiceNowConstants; import io.cdap.plugin.servicenow.util.SourceQueryMode; @@ -39,11 +41,17 @@ import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -51,7 +59,6 @@ @RunWith(PowerMockRunner.class) @PrepareForTest({ServiceNowTableAPIClientImpl.class, ServiceNowSourceConfig.class, ServiceNowRecordReader.class}) public class ServiceNowRecordReaderTest { - private static final String CLIENT_ID = "client_id"; private static final String CLIENT_SECRET = "client_secret"; private static final String REST_API_ENDPOINT = "https://ven05127.service-now.com"; @@ -128,10 +135,10 @@ public void testConvertToValue() { Schema fieldSchema = Schema.recordOf("record", Schema.Field.of("TimeField", Schema.of(Schema.LogicalType.TIMESTAMP_MILLIS))); StructuredRecord.Builder recordBuilder = StructuredRecord.builder(fieldSchema); - Map map = new HashMap<>(); - map.put("TimeField", "value"); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("TimeField", "value"); thrown.expect(IllegalStateException.class); - ServiceNowRecordConverter.convertToValue("TimeField", fieldSchema, map, recordBuilder); + ServiceNowRecordConverter.convertToValue("TimeField", fieldSchema, jsonObject, recordBuilder); } @Test @@ -154,12 +161,12 @@ public void testConvertToDateTimeValue() { ); for (String value : dateTimeValues) { - Map inputMap = new HashMap<>(); - inputMap.put("DateTimeField", value); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("DateTimeField", value); StructuredRecord.Builder recordBuilder = StructuredRecord.builder(recordSchema); try { - ServiceNowRecordConverter.convertToValue("DateTimeField", fieldSchema, inputMap, recordBuilder); + ServiceNowRecordConverter.convertToValue("DateTimeField", fieldSchema, jsonObject, recordBuilder); StructuredRecord record = recordBuilder.build(); Assert.assertNotNull("Parsed datetime should not be null for input: " + value, record.get("DateTimeField")); @@ -185,12 +192,12 @@ public void testConvertToDateValue() { ); for (String value : dateValues) { - Map inputMap = new HashMap<>(); - inputMap.put("DateField", value); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("DateField", value); StructuredRecord.Builder recordBuilder = StructuredRecord.builder(recordSchema); try { - ServiceNowRecordConverter.convertToValue("DateField", fieldSchema, inputMap, recordBuilder); + ServiceNowRecordConverter.convertToValue("DateField", fieldSchema, jsonObject, recordBuilder); StructuredRecord record = recordBuilder.build(); Assert.assertNotNull("Parsed date should not be null for input: " + value, record.get("DateField")); @@ -214,12 +221,12 @@ public void testConvertToTimeValue() { ); for (String value : timeValues) { - Map inputMap = new HashMap<>(); - inputMap.put("TimeField", value); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("TimeField", value); StructuredRecord.Builder recordBuilder = StructuredRecord.builder(recordSchema); try { - ServiceNowRecordConverter.convertToValue("TimeField", fieldSchema, inputMap, recordBuilder); + ServiceNowRecordConverter.convertToValue("TimeField", fieldSchema, jsonObject, recordBuilder); StructuredRecord record = recordBuilder.build(); Assert.assertNotNull("Parsed date should not be null for input: " + value, record.get("TimeField")); @@ -257,31 +264,56 @@ public void testFetchData() throws Exception { ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); ServiceNowInputSplit split = new ServiceNowInputSplit(tableName, 1); ServiceNowRecordReader serviceNowRecordReader = new ServiceNowRecordReader(serviceNowSourceConfig); - List> results = new ArrayList<>(); - Map map = new HashMap<>(); - map.put("calendar_integration", "1"); - map.put("country", "India"); - map.put("sys_updated_on", "2019-04-05 21:54:45"); - map.put("web_service_access_only", "false"); - map.put("notification", "2"); - map.put("enable_multifactor_authn", "false"); - map.put("sys_updated_by", "system"); - map.put("sys_created_on", "2019-04-05 21:09:12"); - results.add(map); - ServiceNowTableDataResponse response = new ServiceNowTableDataResponse(); - ServiceNowColumn column1 = new ServiceNowColumn("calendar_integration", "integer"); - ServiceNowColumn column2 = new ServiceNowColumn("vip", "boolean"); - List columns = new ArrayList<>(); - columns.add(column1); - columns.add(column2); - response.setColumns(columns); - response.setResult(results); - response.setTotalRecordCount(1); + RestAPIResponse mockResponse = Mockito.mock(RestAPIResponse.class); + String responseBody = "{\n" + + " \"result\": [\n" + + " {\n" + + " \"bill_to\": \"\",\n" + + " \"init_request\": \"\",\n" + + " \"short_description\": \"\",\n" + + " \"total_cost\": \"0\",\n" + + " \"due_by\": \"\",\n" + + " \"description\": \"\",\n" + + " \"requested_for\": \"\",\n" + + " \"sys_updated_on\": \"2022-06-16 18:56:23\",\n" + + " \"budget_number\": \"\",\n" + + " \"number\": \"RCS397871\",\n" + + " \"sys_id\": \"00000b7287405910827733373cbb35d5\",\n" + + " \"sys_updated_by\": \"pipeline.user.1\",\n" + + " \"shipping\": \"\",\n" + + " \"terms\": \"\",\n" + + " \"sys_created_on\": \"2022-06-16 18:56:23\",\n" + + " \"vendor\": \"\",\n" + + " \"sys_domain\": \"global\",\n" + + " \"department\": \"\",\n" + + " \"sys_created_by\": \"pipeline.user.1\",\n" + + " \"assigned_to\": \"\",\n" + + " \"ordered\": \"\",\n" + + " \"po_date\": \"2022-06-16 18:56:23\",\n" + + " \"vendor_contract\": \"\",\n" + + " \"contract\": \"\",\n" + + " \"expected_delivery\": \"\",\n" + + " \"sys_mod_count\": \"0\",\n" + + " \"received\": \"2158-05-10 17:14:20\",\n" + + " \"asset_operation\": \"\",\n" + + " \"sys_tags\": \"\",\n" + + " \"requested\": \"2022-06-16 18:56:23\",\n" + + " \"requested_by\": \"\",\n" + + " \"ship_rate\": \"0\",\n" + + " \"location\": \"\",\n" + + " \"vendor_account\": \"\",\n" + + " \"ship_to\": \"\",\n" + + " \"status\": \"requested\"\n" + + " }\n" + + " ]\n" + + "}"; + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(Collections.emptyMap(), inputStream, null); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); Mockito.when(restApi.fetchTableRecordsRetryableMode(tableName, serviceNowSourceConfig.getValueType(), - serviceNowSourceConfig.getStartDate(), serviceNowSourceConfig. - getEndDate(), split.getOffset(), - serviceNowSourceConfig.getPageSize())).thenReturn(results); + serviceNowSourceConfig.getStartDate(), serviceNowSourceConfig.getEndDate(), split.getOffset(), + serviceNowSourceConfig.getPageSize())).thenReturn(restAPIResponse); Mockito.when(restApi.fetchTableSchema(tableName, valueType)) .thenReturn(Schema.recordOf(Schema.Field.of("calendar_integration", Schema.of(Schema.Type.STRING)))); serviceNowRecordReader.initialize(split); @@ -310,31 +342,55 @@ public void testFetchDataReportingMode() throws Exception { ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); ServiceNowInputSplit split = new ServiceNowInputSplit(tableName, 1); ServiceNowRecordReader serviceNowRecordReader = new ServiceNowRecordReader(serviceNowSourceConfig); - List> results = new ArrayList<>(); - Map map = new HashMap<>(); - map.put("calendar_integration", "1"); - map.put("country", "India"); - map.put("sys_updated_on", "2019-04-05 21:54:45"); - map.put("web_service_access_only", "false"); - map.put("notification", "2"); - map.put("enable_multifactor_authn", "false"); - map.put("sys_updated_by", "system"); - map.put("sys_created_on", "2019-04-05 21:09:12"); - results.add(map); - ServiceNowTableDataResponse response = new ServiceNowTableDataResponse(); - ServiceNowColumn column1 = new ServiceNowColumn("calendar_integration", "integer"); - ServiceNowColumn column2 = new ServiceNowColumn("vip", "boolean"); - List columns = new ArrayList<>(); - columns.add(column1); - columns.add(column2); - response.setColumns(columns); - response.setResult(results); - response.setTotalRecordCount(1); + String responseBody = "{\n" + + " \"result\": [\n" + + " {\n" + + " \"bill_to\": \"\",\n" + + " \"init_request\": \"\",\n" + + " \"short_description\": \"\",\n" + + " \"total_cost\": \"0\",\n" + + " \"due_by\": \"\",\n" + + " \"description\": \"\",\n" + + " \"requested_for\": \"\",\n" + + " \"sys_updated_on\": \"2022-06-16 18:56:23\",\n" + + " \"budget_number\": \"\",\n" + + " \"number\": \"RCS397871\",\n" + + " \"sys_id\": \"00000b7287405910827733373cbb35d5\",\n" + + " \"sys_updated_by\": \"pipeline.user.1\",\n" + + " \"shipping\": \"\",\n" + + " \"terms\": \"\",\n" + + " \"sys_created_on\": \"2022-06-16 18:56:23\",\n" + + " \"vendor\": \"\",\n" + + " \"sys_domain\": \"global\",\n" + + " \"department\": \"\",\n" + + " \"sys_created_by\": \"pipeline.user.1\",\n" + + " \"assigned_to\": \"\",\n" + + " \"ordered\": \"\",\n" + + " \"po_date\": \"2022-06-16 18:56:23\",\n" + + " \"vendor_contract\": \"\",\n" + + " \"contract\": \"\",\n" + + " \"expected_delivery\": \"\",\n" + + " \"sys_mod_count\": \"0\",\n" + + " \"received\": \"2158-05-10 17:14:20\",\n" + + " \"asset_operation\": \"\",\n" + + " \"sys_tags\": \"\",\n" + + " \"requested\": \"2022-06-16 18:56:23\",\n" + + " \"requested_by\": \"\",\n" + + " \"ship_rate\": \"0\",\n" + + " \"location\": \"\",\n" + + " \"vendor_account\": \"\",\n" + + " \"ship_to\": \"\",\n" + + " \"status\": \"requested\"\n" + + " }\n" + + " ]\n" + + "}"; + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(Collections.emptyMap(), inputStream, null); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); Mockito.when(restApi.fetchTableRecordsRetryableMode(tableName, serviceNowSourceConfig.getValueType(), - serviceNowSourceConfig.getStartDate(), - serviceNowSourceConfig.getEndDate(), split.getOffset(), - serviceNowSourceConfig.getPageSize())).thenReturn(results); + serviceNowSourceConfig.getStartDate(), serviceNowSourceConfig.getEndDate(), split.getOffset(), + serviceNowSourceConfig.getPageSize())).thenReturn(restAPIResponse); Mockito.when(restApi.fetchTableSchema(tableName, serviceNowSourceConfig.getValueType())) .thenReturn(Schema.recordOf(Schema.Field.of("calendar_integration", Schema.of(Schema.Type.STRING)))); serviceNowRecordReader.initialize(split); @@ -350,7 +406,7 @@ public void testFetchDataOnInvalidTable() throws Exception { .setPassword(PASSWORD) .setClientId(CLIENT_ID) .setClientSecret(CLIENT_SECRET) - .setTableName("") + .setTableName("abc") .setValueType("Actual") .setStartDate("2021-01-01") .setEndDate("2022-02-18") @@ -362,11 +418,22 @@ public void testFetchDataOnInvalidTable() throws Exception { ServiceNowInputSplit split = new ServiceNowInputSplit(tableName, 1); ServiceNowRecordReader serviceNowRecordReader = new ServiceNowRecordReader(serviceNowSourceConfig); List> results = new ArrayList<>(); + String responseBody = "{\n " + + "\"error\": " + + "{\n " + + "\"message\": \"Invalid table abc\",\n" + + " \"detail\": null\n " + + "},\n " + + "\"status\": \"failure\"\n" + + "}"; + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(Collections.emptyMap(), inputStream, null); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - Mockito.when(restApi.fetchTableRecords(tableName, serviceNowSourceConfig.getValueType(), + Mockito.when(restApi.fetchTableRecordsRetryableMode(tableName, serviceNowSourceConfig.getValueType(), serviceNowSourceConfig.getStartDate(), serviceNowSourceConfig.getEndDate(), split.getOffset(), - serviceNowSourceConfig.getPageSize())).thenReturn(results); + serviceNowSourceConfig.getPageSize())).thenReturn(restAPIResponse); ServiceNowTableDataResponse response = new ServiceNowTableDataResponse(); response.setResult(results); Mockito.when(restApi.fetchTableSchema(tableName, serviceNowSourceConfig.getValueType())) diff --git a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowSourceConfigTest.java b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowSourceConfigTest.java index d9bc48c6..2fd96e0a 100644 --- a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowSourceConfigTest.java +++ b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowSourceConfigTest.java @@ -23,7 +23,6 @@ import io.cdap.cdap.etl.mock.validation.MockFailureCollector; import io.cdap.plugin.servicenow.apiclient.ServiceNowAPIException; import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIClientImpl; -import io.cdap.plugin.servicenow.connector.ServiceNowConnectorConfig; import io.cdap.plugin.servicenow.restapi.RestAPIResponse; import io.cdap.plugin.servicenow.util.ServiceNowConstants; import io.cdap.plugin.servicenow.util.SourceApplication; @@ -42,7 +41,9 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import java.io.IOException; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -624,7 +625,9 @@ public void testValidateWhenTableIsEmpty() throws Exception { String responseBody = "{\n" + " \"result\": []\n" + "}"; - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); config.validate(mockFailureCollector); Assert.assertEquals(1, mockFailureCollector.getValidationFailures().size()); diff --git a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowSourceTest.java b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowSourceTest.java index 29871ad1..a62bdee4 100644 --- a/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowSourceTest.java +++ b/src/test/java/io/cdap/plugin/servicenow/source/ServiceNowSourceTest.java @@ -16,6 +16,7 @@ package io.cdap.plugin.servicenow.source; +import com.google.gson.JsonObject; import io.cdap.cdap.api.data.schema.Schema; import io.cdap.cdap.etl.api.batch.BatchSourceContext; import io.cdap.cdap.etl.api.validation.ValidationException; @@ -23,11 +24,9 @@ import io.cdap.cdap.etl.mock.common.MockPipelineConfigurer; import io.cdap.cdap.etl.mock.validation.MockFailureCollector; import io.cdap.plugin.servicenow.apiclient.ServiceNowTableAPIClientImpl; -import io.cdap.plugin.servicenow.connector.ServiceNowConnectorConfig; import io.cdap.plugin.servicenow.restapi.RestAPIResponse; import io.cdap.plugin.servicenow.util.ServiceNowTableInfo; -import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; @@ -44,6 +43,9 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -101,10 +103,10 @@ public void testConfigurePipeline() throws Exception { ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); Mockito.when(restApi.getAccessToken()).thenReturn("token1"); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - Map map = new HashMap<>(); - List> result = new ArrayList<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject = new JsonObject(); + List result = new ArrayList<>(); + jsonObject.addProperty("key", "value"); + result.add(jsonObject); Map headers = new HashMap<>(); String responseBody = "{\n" + " \"result\": [\n" + @@ -168,12 +170,14 @@ public void testConfigurePipeline() throws Exception { " }\n" + " ]\n" + "}"; + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); PowerMockito.mockStatic(ServiceNowInputFormat.class); Mockito.when(ServiceNowInputFormat.fetchTableInfo(Mockito.any(), Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(tableInfo); - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); OAuthClient oAuthClient = Mockito.mock(OAuthClient.class); PowerMockito.whenNew(OAuthClient.class). withArguments(Mockito.any(URLConnectionClient.class)).thenReturn(oAuthClient); @@ -204,14 +208,16 @@ public void testConfigurePipelineWithEmptyTable() throws Exception { ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); Mockito.when(restApi.getAccessToken()).thenReturn("token"); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - List> result = new ArrayList<>(); + List result = new ArrayList<>(); Map headers = new HashMap<>(); String responseBody = "{\n" + " \"result\": []\n" + "}"; - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); Mockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); try { serviceNowSource.configurePipeline(mockPipelineConfigurer); Assert.fail("Exception is not thrown for Non-Empty Tables"); @@ -230,10 +236,10 @@ public void testPrepareRun() throws Exception { Mockito.when(context.getArguments()).thenReturn(mockArguments); ServiceNowTableAPIClientImpl restApi = Mockito.mock(ServiceNowTableAPIClientImpl.class); PowerMockito.whenNew(ServiceNowTableAPIClientImpl.class).withAnyArguments().thenReturn(restApi); - List> result = new ArrayList<>(); - Map map = new HashMap<>(); - map.put("key", "value"); - result.add(map); + JsonObject jsonObject = new JsonObject(); + List result = new ArrayList<>(); + jsonObject.addProperty("key", "value"); + result.add(jsonObject); Map headers = new HashMap<>(); String responseBody = "{\n" + " \"result\": [\n" + @@ -297,9 +303,11 @@ public void testPrepareRun() throws Exception { " }\n" + " ]\n" + "}"; - RestAPIResponse restAPIResponse = new RestAPIResponse(headers, responseBody, null); + byte[] body = responseBody.getBytes(StandardCharsets.UTF_8); + InputStream inputStream = new ByteArrayInputStream(body); + RestAPIResponse restAPIResponse = new RestAPIResponse(headers, inputStream, null); PowerMockito.when(restApi.executeGetWithRetries(Mockito.any())).thenReturn(restAPIResponse); - Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseBody())).thenReturn(result); + Mockito.when(restApi.parseResponseToResultListOfMap(restAPIResponse.getResponseStream())).thenReturn(result); OAuthClient oAuthClient = Mockito.mock(OAuthClient.class); PowerMockito.whenNew(OAuthClient.class). withArguments(Mockito.any(URLConnectionClient.class)).thenReturn(oAuthClient);