/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package gobblin.writer.http;
import static org.mockito.Mockito.*;
import static gobblin.writer.http.SalesForceRestWriterBuilder.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import gobblin.configuration.State;
import gobblin.converter.http.RestEntry;
import gobblin.writer.http.SalesforceRestWriter.Operation;
import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.base.Optional;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
@Test(groups = { "gobblin.writer" })
public class SalesforceRestWriterTest {
private SalesforceRestWriter writer;
private CloseableHttpClient client;
private void setup(Operation operation) throws ClientProtocolException, IOException, URISyntaxException {
setup(operation, new State());
}
private void setup(Operation operation, State state) throws ClientProtocolException, IOException, URISyntaxException {
state.appendToSetProp(CONF_PREFIX + STATIC_SVC_ENDPOINT, "test");
state.appendToSetProp(CONF_PREFIX + CLIENT_ID, "test");
state.appendToSetProp(CONF_PREFIX + CLIENT_SECRET, "test");
state.appendToSetProp(CONF_PREFIX + USER_ID, "test");
state.appendToSetProp(CONF_PREFIX + PASSWORD, "test");
state.appendToSetProp(CONF_PREFIX + USE_STRONG_ENCRYPTION, "test");
state.appendToSetProp(CONF_PREFIX + SECURITY_TOKEN, "test");
state.appendToSetProp(CONF_PREFIX + OPERATION, operation.name());
SalesForceRestWriterBuilder builder = new SalesForceRestWriterBuilder();
builder = spy(builder);
HttpClientBuilder httpClientBuilder = mock(HttpClientBuilder.class);
doReturn(httpClientBuilder).when(builder).getHttpClientBuilder();
client = mock(CloseableHttpClient.class);
when(httpClientBuilder.build()).thenReturn(client);
builder.fromState(state);
writer = new SalesforceRestWriter(builder, "test");
writer.setCurServerHost(new URI("http://test.nowhere.com"));
}
public void testInsertSuccess() throws IOException, URISyntaxException {
setup(SalesforceRestWriter.Operation.INSERT_ONLY_NOT_EXIST);
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
when(client.execute(any(HttpUriRequest.class))).thenReturn(response);
when(response.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(200);
RestEntry<JsonObject> restEntry = new RestEntry<JsonObject>("test", new JsonObject());
Optional<HttpUriRequest> request = writer.onNewRecord(restEntry);
Assert.assertTrue(request.isPresent(), "No HttpUriRequest from onNewRecord");
Assert.assertEquals("POST", request.get().getMethod());
writer = spy(writer);
writer.write(restEntry);
verify(writer, times(1)).writeImpl(restEntry);
verify(writer, times(1)).onNewRecord(restEntry);
verify(writer, times(1)).sendRequest(any(HttpUriRequest.class));
verify(client, times(1)).execute(any(HttpUriRequest.class));
verify(writer, times(1)).waitForResponse(any(ListenableFuture.class));
verify(writer, times(1)).processResponse(any(CloseableHttpResponse.class));
verify(writer, never()).onConnect(any(URI.class));
}
public void testBatchInsertSuccess() throws IOException, URISyntaxException {
final int recordSize = 113;
final int batchSize = 25;
State state = new State();
state.appendToSetProp(CONF_PREFIX + BATCH_SIZE, Integer.toString(batchSize));
state.appendToSetProp(CONF_PREFIX + BATCH_RESOURCE_PATH, "test");
setup(SalesforceRestWriter.Operation.INSERT_ONLY_NOT_EXIST, state);
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
when(client.execute(any(HttpUriRequest.class))).thenReturn(response);
when(response.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(200);
HttpEntity entity = mock(HttpEntity.class);
when(response.getEntity()).thenReturn(entity);
JsonObject jsonResponse = new JsonObject();
jsonResponse.addProperty("hasErrors", false);
ByteArrayInputStream[] streams = new ByteArrayInputStream[recordSize];
for (int i=0; i < recordSize-1; i++) {
streams[i] = new ByteArrayInputStream(jsonResponse.toString().getBytes());
}
when(entity.getContent()).thenReturn(new ByteArrayInputStream(jsonResponse.toString().getBytes()), streams);
RestEntry<JsonObject> restEntry = new RestEntry<JsonObject>("test", new JsonObject());
writer = spy(writer);
for (int i = 0; i < recordSize; i++) {
writer.write(restEntry);
}
writer.commit();
Assert.assertEquals(writer.recordsWritten(), recordSize);
verify(writer, times(recordSize)).writeImpl(restEntry);
verify(writer, times(recordSize)).onNewRecord(restEntry);
double sendCount = ((double)recordSize) / ((double)batchSize);
sendCount = Math.ceil(sendCount);
verify(writer, times((int)sendCount)).sendRequest(any(HttpUriRequest.class));
verify(client, times((int)sendCount)).execute(any(HttpUriRequest.class));
verify(writer, times((int)sendCount)).waitForResponse(any(ListenableFuture.class));
verify(writer, times((int)sendCount)).processResponse(any(CloseableHttpResponse.class));
verify(writer, times(1)).flush();
verify(writer, never()).onConnect(any(URI.class));
}
public void testBatchInsertFailure() throws IOException, URISyntaxException {
final int recordSize = 25;
final int batchSize = recordSize;
State state = new State();
state.appendToSetProp(CONF_PREFIX + BATCH_SIZE, Integer.toString(batchSize));
state.appendToSetProp(CONF_PREFIX + BATCH_RESOURCE_PATH, "test");
setup(SalesforceRestWriter.Operation.INSERT_ONLY_NOT_EXIST, state);
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
when(client.execute(any(HttpUriRequest.class))).thenReturn(response);
when(response.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(200);
HttpEntity entity = mock(HttpEntity.class);
when(response.getEntity()).thenReturn(entity);
JsonObject jsonResponse = new JsonObject();
jsonResponse.addProperty("hasErrors", true);
JsonArray resultJsonArr = new JsonArray();
jsonResponse.add("results", resultJsonArr);
JsonObject subResult1 = new JsonObject();
subResult1.addProperty("statusCode", 201); //Success
JsonObject subResult2 = new JsonObject();
subResult2.addProperty("statusCode", 500); //Failure
resultJsonArr.add(subResult1);
resultJsonArr.add(subResult2);
when(entity.getContent()).thenReturn(new ByteArrayInputStream(jsonResponse.toString().getBytes()));
RestEntry<JsonObject> restEntry = new RestEntry<JsonObject>("test", new JsonObject());
writer = spy(writer);
for (int i = 0; i < recordSize-1; i++) {
writer.write(restEntry);
}
try {
writer.write(restEntry);
Assert.fail("Should have failed with failed response. " + jsonResponse.toString());
} catch (Exception e) {
Assert.assertTrue(e instanceof RuntimeException);
}
Assert.assertEquals(writer.recordsWritten(), (long) 0L);
verify(writer, times(recordSize)).writeImpl(restEntry);
verify(writer, times(recordSize)).onNewRecord(restEntry);
double sendCount = ((double)recordSize) / ((double)batchSize);
sendCount = Math.ceil(sendCount);
verify(writer, times((int)sendCount)).sendRequest(any(HttpUriRequest.class));
verify(client, times((int)sendCount)).execute(any(HttpUriRequest.class));
verify(writer, times((int)sendCount)).waitForResponse(any(ListenableFuture.class));
verify(writer, times((int)sendCount)).processResponse(any(CloseableHttpResponse.class));
verify(writer, never()).flush();
verify(writer, never()).onConnect(any(URI.class));
}
public void testBatchInsertDuplicate() throws IOException, URISyntaxException {
final int recordSize = 25;
final int batchSize = recordSize;
State state = new State();
state.appendToSetProp(CONF_PREFIX + BATCH_SIZE, Integer.toString(batchSize));
state.appendToSetProp(CONF_PREFIX + BATCH_RESOURCE_PATH, "test");
setup(SalesforceRestWriter.Operation.INSERT_ONLY_NOT_EXIST, state);
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
when(client.execute(any(HttpUriRequest.class))).thenReturn(response);
when(response.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(200);
HttpEntity entity = mock(HttpEntity.class);
when(response.getEntity()).thenReturn(entity);
JsonObject jsonResponse = new JsonObject();
jsonResponse.addProperty("hasErrors", true);
JsonArray resultJsonArr = new JsonArray();
jsonResponse.add("results", resultJsonArr);
JsonObject subResult1 = new JsonObject();
subResult1.addProperty("statusCode", 400);
JsonArray subResultArr = new JsonArray();
JsonObject errJson = new JsonObject();
errJson.addProperty("errorCode", SalesforceRestWriter.DUPLICATE_VALUE_ERR_CODE);
subResultArr.add(errJson);
subResult1.add("result", subResultArr);
JsonObject subResult2 = new JsonObject();
subResult2.addProperty("statusCode", 400);
subResult2.add("result", subResultArr);
resultJsonArr.add(subResult1);
resultJsonArr.add(subResult2);
when(entity.getContent()).thenReturn(new ByteArrayInputStream(jsonResponse.toString().getBytes()));
RestEntry<JsonObject> restEntry = new RestEntry<JsonObject>("test", new JsonObject());
writer = spy(writer);
for (int i = 0; i < recordSize; i++) {
writer.write(restEntry);
}
writer.commit();
Assert.assertEquals(writer.recordsWritten(), recordSize);
verify(writer, times(recordSize)).writeImpl(restEntry);
verify(writer, times(recordSize)).onNewRecord(restEntry);
double sendCount = ((double)recordSize) / ((double)batchSize);
sendCount = Math.ceil(sendCount);
verify(writer, times((int)sendCount)).sendRequest(any(HttpUriRequest.class));
verify(client, times((int)sendCount)).execute(any(HttpUriRequest.class));
verify(writer, times((int)sendCount)).waitForResponse(any(ListenableFuture.class));
verify(writer, times((int)sendCount)).processResponse(any(CloseableHttpResponse.class));
verify(writer, times(1)).flush();
verify(writer, never()).onConnect(any(URI.class));
}
public void testUpsertSuccess() throws IOException, URISyntaxException {
setup(SalesforceRestWriter.Operation.UPSERT);
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
when(client.execute(any(HttpUriRequest.class))).thenReturn(response);
when(response.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(200);
RestEntry<JsonObject> restEntry = new RestEntry<JsonObject>("test", new JsonObject());
Optional<HttpUriRequest> request = writer.onNewRecord(restEntry);
Assert.assertTrue(request.isPresent(), "No HttpUriRequest from onNewRecord");
Assert.assertEquals("PATCH", request.get().getMethod());
writer = spy(writer);
writer.write(restEntry);
verify(writer, times(1)).writeImpl(restEntry);
verify(writer, times(1)).onNewRecord(restEntry);
verify(writer, times(1)).sendRequest(any(HttpUriRequest.class));
verify(client, times(1)).execute(any(HttpUriRequest.class));
verify(writer, times(1)).waitForResponse(any(ListenableFuture.class));
verify(writer, times(1)).processResponse(any(CloseableHttpResponse.class));
verify(writer, never()).onConnect(any(URI.class));
}
public void testInsertDuplicate() throws IOException, URISyntaxException {
setup(SalesforceRestWriter.Operation.INSERT_ONLY_NOT_EXIST);
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
when(client.execute(any(HttpUriRequest.class))).thenReturn(response);
when(response.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(400);
HttpEntity entity = mock(HttpEntity.class);
when(response.getEntity()).thenReturn(entity);
JsonObject json = new JsonObject();
json.addProperty("errorCode", SalesforceRestWriter.DUPLICATE_VALUE_ERR_CODE);
JsonArray jsonArray = new JsonArray();
jsonArray.add(json);
when(entity.getContent()).thenReturn(new ByteArrayInputStream(jsonArray.toString().getBytes()));
RestEntry<JsonObject> restEntry = new RestEntry<JsonObject>("test", new JsonObject());
Optional<HttpUriRequest> request = writer.onNewRecord(restEntry);
Assert.assertTrue(request.isPresent(), "No HttpUriRequest from onNewRecord");
Assert.assertEquals("POST", request.get().getMethod());
writer = spy(writer);
writer.write(restEntry);
verify(writer, times(1)).writeImpl(restEntry);
verify(writer, times(1)).onNewRecord(restEntry);
verify(writer, times(1)).sendRequest(any(HttpUriRequest.class));
verify(client, times(1)).execute(any(HttpUriRequest.class));
verify(writer, times(1)).waitForResponse(any(ListenableFuture.class));
verify(writer, times(1)).processResponse(any(CloseableHttpResponse.class));
verify(writer, never()).onConnect(any(URI.class));
}
public void testFailure() throws IOException, URISyntaxException {
setup(SalesforceRestWriter.Operation.INSERT_ONLY_NOT_EXIST);
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
when(client.execute(any(HttpUriRequest.class))).thenReturn(response);
when(response.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(400);
RestEntry<JsonObject> restEntry = new RestEntry<JsonObject>("test", new JsonObject());
Optional<HttpUriRequest> request = writer.onNewRecord(restEntry);
Assert.assertTrue(request.isPresent(), "No HttpUriRequest from onNewRecord");
Assert.assertEquals("POST", request.get().getMethod());
writer = spy(writer);
try {
writer.write(restEntry);
Assert.fail("Should fail on 400 status code");
} catch (Exception e) {}
verify(writer, times(1)).writeImpl(restEntry);
verify(writer, times(1)).onNewRecord(restEntry);
verify(writer, times(1)).sendRequest(any(HttpUriRequest.class));
verify(client, times(1)).execute(any(HttpUriRequest.class));
verify(writer, times(1)).waitForResponse(any(ListenableFuture.class));
verify(writer, times(1)).processResponse(any(CloseableHttpResponse.class));
verify(writer, never()).onConnect(any(URI.class));
}
public void testAccessTokenReacquire() throws IOException, URISyntaxException {
setup(SalesforceRestWriter.Operation.INSERT_ONLY_NOT_EXIST);
CloseableHttpResponse response = mock(CloseableHttpResponse.class);
StatusLine statusLine = mock(StatusLine.class);
when(client.execute(any(HttpUriRequest.class))).thenReturn(response);
when(response.getStatusLine()).thenReturn(statusLine);
when(statusLine.getStatusCode()).thenReturn(401);
RestEntry<JsonObject> restEntry = new RestEntry<JsonObject>("test", new JsonObject());
Optional<HttpUriRequest> request = writer.onNewRecord(restEntry);
Assert.assertTrue(request.isPresent(), "No HttpUriRequest from onNewRecord");
Assert.assertEquals("POST", request.get().getMethod());
writer = spy(writer);
try {
writer.write(restEntry);
} catch (Exception e) { }
verify(writer, times(1)).writeImpl(restEntry);
verify(writer, times(1)).onNewRecord(restEntry);
verify(writer, times(1)).sendRequest(any(HttpUriRequest.class));
verify(client, times(1)).execute(any(HttpUriRequest.class));
verify(writer, times(1)).waitForResponse(any(ListenableFuture.class));
verify(writer, times(1)).processResponse(any(CloseableHttpResponse.class));
verify(writer, times(1)).onConnect(any(URI.class));
}
}