// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed 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 com.google.api.ads.adwords.lib.utils;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.when;
import com.google.api.ads.adwords.lib.client.AdWordsSession;
import com.google.api.ads.adwords.lib.utils.logging.BatchJobLogger;
import com.google.api.ads.adwords.lib.utils.testing.GenericAdWordsServices;
import com.google.api.ads.common.lib.testing.MockHttpServer;
import com.google.api.ads.common.lib.testing.MockResponse;
import com.google.api.client.http.ByteArrayContent;
import com.google.api.client.http.LowLevelHttpResponse;
import com.google.api.client.testing.http.MockHttpTransport;
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
/**
* Tests for {@link BatchJobUploader}.
*/
@RunWith(JUnit4.class)
public class BatchJobUploaderTest {
@Mock private AdWordsSession adWordsSession;
@Mock private BatchJobMutateRequestInterface request;
@Mock private BatchJobLogger batchJobLogger;
@Mock private BatchJobUploadBodyProvider uploadBodyProvider;
@Rule
public ExpectedException thrown = ExpectedException.none();
@SuppressWarnings("rawtypes")
private BatchJobUploader uploader;
private MockHttpServer mockHttpServer;
@SuppressWarnings("rawtypes")
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mockHttpServer = new MockHttpServer();
uploader =
new BatchJobUploader(adWordsSession, mockHttpServer.getHttpTransport(), batchJobLogger);
when(request.createBatchJobUploadBodyProvider()).thenReturn(uploadBodyProvider);
}
/**
* Sanity check that the public constructor works properly. All other tests use the
* package private constructor, but users of the utility will use the public one.
*/
@Test
public void testGetFromAdWordsServices() {
assertNotNull(
"Uploader from AdWordsServices is null",
new GenericAdWordsServices()
.getBootstrapper()
.getInstanceOf(adWordsSession, BatchJobUploader.class));
}
/**
* Tests that IOExceptions from executing an upload request are propagated properly.
*/
@SuppressWarnings("rawtypes")
@Test
public void testUploadBatchJobOperations_ioException_fails() throws Exception {
final IOException ioException = new IOException("mock IO exception");
MockLowLevelHttpRequest lowLevelHttpRequest = new MockLowLevelHttpRequest(){
@Override
public LowLevelHttpResponse execute() throws IOException {
throw ioException;
}
};
when(uploadBodyProvider.getHttpContent(request, true, true))
.thenReturn(new ByteArrayContent(null, "foo".getBytes(UTF_8)));
MockHttpTransport transport = new MockHttpTransport.Builder()
.setLowLevelHttpRequest(lowLevelHttpRequest).build();
uploader = new BatchJobUploader(adWordsSession, transport, batchJobLogger);
BatchJobUploadStatus uploadStatus =
new BatchJobUploadStatus(0, URI.create("http://www.example.com"));
thrown.expect(BatchJobException.class);
thrown.expectCause(Matchers.sameInstance(ioException));
uploader.uploadIncrementalBatchJobOperations(request, true, uploadStatus);
}
/**
* Tests that IOExceptions from initiating an upload are propagated properly.
*/
@SuppressWarnings("rawtypes")
@Test
public void testUploadBatchJobOperations_initiateFails_fails() throws Exception {
final IOException ioException = new IOException("mock IO exception");
MockLowLevelHttpRequest lowLevelHttpRequest = new MockLowLevelHttpRequest(){
@Override
public LowLevelHttpResponse execute() throws IOException {
throw ioException;
}
};
when(uploadBodyProvider.getHttpContent(request, true, true))
.thenReturn(new ByteArrayContent(null, "foo".getBytes(UTF_8)));
MockHttpTransport transport = new MockHttpTransport.Builder()
.setLowLevelHttpRequest(lowLevelHttpRequest).build();
uploader = new BatchJobUploader(adWordsSession, transport, batchJobLogger);
thrown.expect(BatchJobException.class);
thrown.expectCause(Matchers.sameInstance(ioException));
thrown.expectMessage("initiate upload");
uploader.uploadIncrementalBatchJobOperations(request, true, new BatchJobUploadStatus(
0, URI.create("http://www.example.com")));
}
@Test
public void testUploadIncrementalBatchJobOperations_notFirst() throws Exception {
BatchJobUploadStatus status =
new BatchJobUploadStatus(10, URI.create(mockHttpServer.getServerUrl()));
String uploadRequestBody = "<mutate>testUpload</mutate>";
when(uploadBodyProvider.getHttpContent(request, false, true))
.thenReturn(new ByteArrayContent(null, uploadRequestBody.getBytes(UTF_8)));
mockHttpServer.setMockResponse(new MockResponse("testUploadResponse"));
// Invoked the incremental upload method.
BatchJobUploadResponse response =
uploader.uploadIncrementalBatchJobOperations(request, true, status);
assertEquals("Should have made one request", 1, mockHttpServer.getAllResponses().size());
// Check the request.
String firstRequest = mockHttpServer.getLastResponse().getRequestBody();
String expectedBody = "testUpload</mutate>";
expectedBody =
Strings.padEnd(expectedBody, BatchJobUploader.REQUIRED_CONTENT_LENGTH_INCREMENT, ' ');
assertEquals("Request body is incorrect", expectedBody, firstRequest);
assertEquals("Request should have succeeded", 200, response.getHttpStatus());
// Check the BatchJobUploadStatus.
BatchJobUploadStatus expectedStatus =
new BatchJobUploadStatus(
status.getTotalContentLength() + expectedBody.getBytes(UTF_8).length,
URI.create(mockHttpServer.getServerUrl()));
BatchJobUploadStatus actualStatus = response.getBatchJobUploadStatus();
assertEquals(
"Status total content length is incorrect",
expectedStatus.getTotalContentLength(),
actualStatus.getTotalContentLength());
assertEquals(
"Status resumable upload URI is incorrect",
expectedStatus.getResumableUploadUri(),
actualStatus.getResumableUploadUri());
}
@Test
public void testUploadIncrementalBatchJobOperations_notFirst_notLast() throws Exception {
BatchJobUploadStatus status =
new BatchJobUploadStatus(10, URI.create(mockHttpServer.getServerUrl()));
String uploadRequestBody = "<mutate>testUpload</mutate>";
when(uploadBodyProvider.getHttpContent(request, false, false))
.thenReturn(new ByteArrayContent(null, uploadRequestBody.getBytes(UTF_8)));
mockHttpServer.setMockResponse(new MockResponse("testUploadResponse"));
// Invoked the incremental upload method.
BatchJobUploadResponse response =
uploader.uploadIncrementalBatchJobOperations(request, false, status);
assertEquals("Should have made one request", 1, mockHttpServer.getAllResponses().size());
// Check the request.
String firstRequest = mockHttpServer.getLastResponse().getRequestBody();
String expectedBody = "testUpload";
expectedBody =
Strings.padEnd(expectedBody, BatchJobUploader.REQUIRED_CONTENT_LENGTH_INCREMENT, ' ');
assertEquals("Request body is incorrect", expectedBody, firstRequest);
assertEquals("Request should have succeeded", 200, response.getHttpStatus());
// Check the BatchJobUploadStatus.
BatchJobUploadStatus expectedStatus =
new BatchJobUploadStatus(
status.getTotalContentLength() + expectedBody.getBytes(UTF_8).length,
URI.create(mockHttpServer.getServerUrl()));
BatchJobUploadStatus actualStatus = response.getBatchJobUploadStatus();
assertEquals(
"Status total content length is incorrect",
expectedStatus.getTotalContentLength(),
actualStatus.getTotalContentLength());
assertEquals(
"Status resumable upload URI is incorrect",
expectedStatus.getResumableUploadUri(),
actualStatus.getResumableUploadUri());
}
@Test
public void testUploadIncrementalBatchJobOperations_firstAndLast() throws Exception {
BatchJobUploadStatus status =
new BatchJobUploadStatus(0, URI.create(mockHttpServer.getServerUrl()));
String uploadRequestBody = "testUpload";
when(uploadBodyProvider.getHttpContent(request, true, true))
.thenReturn(new ByteArrayContent(null, uploadRequestBody.getBytes(UTF_8)));
List<MockResponse> expectedResponses =
Lists.newArrayList(new MockResponse("ignore"), new MockResponse("testUploadResponse"));
mockHttpServer.setMockResponses(expectedResponses);
// Invoked the incremental upload method.
BatchJobUploadResponse response =
uploader.uploadIncrementalBatchJobOperations(request, true, status);
assertEquals("Should have made two requests", 2, mockHttpServer.getAllResponses().size());
// Check the first request.
String firstRequest = mockHttpServer.getAllResponses().get(0).getRequestBody();
assertEquals("First request should have an empty body", "", firstRequest);
assertEquals(
"First request should include resumable header",
"start",
mockHttpServer.getAllResponses().get(0).getRequestHeader("x-goog-resumable").get(0));
// Check the second request.
assertEquals(
"Second request body is incorrect",
uploadRequestBody,
mockHttpServer.getLastResponse().getRequestBody());
assertEquals("Last request should have succeeded", 200, response.getHttpStatus());
// Check the BatchJobUploadStatus.
BatchJobUploadStatus expectedStatus =
new BatchJobUploadStatus(
uploadRequestBody.getBytes(UTF_8).length, URI.create(mockHttpServer.getServerUrl()));
BatchJobUploadStatus actualStatus = response.getBatchJobUploadStatus();
assertEquals(
"Status total content length is incorrect",
expectedStatus.getTotalContentLength(),
actualStatus.getTotalContentLength());
assertEquals(
"Status resumable upload URI is incorrect",
expectedStatus.getResumableUploadUri(),
actualStatus.getResumableUploadUri());
}
@Test
public void testConstructContentRangeHeaderValue_notLast_nonZeroLength_zeroPrevious() {
BatchJobUploadStatus status = new BatchJobUploadStatus(0, null);
long requestLength = 100;
String expectedContentRange = "bytes 0-99/*";
assertEquals(
expectedContentRange,
uploader.constructContentRangeHeaderValue(requestLength, false, status));
}
@Test
public void testConstructContentRangeHeaderValue_notLast_nonZeroLength_nonZeroPrevious() {
BatchJobUploadStatus status = new BatchJobUploadStatus(100, null);
long requestLength = 100;
String expectedContentRange = "bytes 100-199/*";
assertEquals(
expectedContentRange,
uploader.constructContentRangeHeaderValue(requestLength, false, status));
}
@Test
public void testConstructContentRangeHeaderValue_isLast_nonZeroLength_nonZeroPrevious() {
BatchJobUploadStatus status = new BatchJobUploadStatus(100, null);
long requestLength = 100;
String expectedContentRange = "bytes 100-199/200";
assertEquals(
expectedContentRange,
uploader.constructContentRangeHeaderValue(requestLength, true, status));
}
@Test
public void testConstructContentRangeHeaderValue_isLast_nonZeroLength_zeroPrevious() {
BatchJobUploadStatus status = new BatchJobUploadStatus(0, null);
long requestLength = 100;
String expectedContentRange = "bytes 0-99/100";
assertEquals(
expectedContentRange,
uploader.constructContentRangeHeaderValue(requestLength, true, status));
}
@Test
public void testConstructContentRangeHeaderValue_notLast_zeroLength() {
BatchJobUploadStatus status = new BatchJobUploadStatus(0, null);
thrown.expect(IllegalArgumentException.class);
uploader.constructContentRangeHeaderValue(0, false, status);
}
@Test
public void testTrimStartEndElements_isFirst_isLast() {
List<String> testData = Lists.newArrayList();
testData.add("<mutate><foo></foo></mutate>");
testData.add(
"<ns1:mutate xmlns:ns1=\"https://adwords.google.com/api/adwords/cm/v209912\"><foo></foo>"
+ "</ns1:mutate>");
testData.add("<mutate></mutate>");
for (String requestXml : testData) {
String postProcessed = uploader.trimStartEndElements(requestXml, true, true);
assertEquals(
"Trimmed result should not change if isFirst and isLast", requestXml, postProcessed);
}
}
@Test
public void testTrimStartEndElements_notFirst_isLast() {
List<List<String>> testData = Lists.newArrayList();
testData.add(Lists.newArrayList("<mutate><foo></foo></mutate>", "<foo></foo></mutate>"));
testData.add(Lists.newArrayList(
"<ns1:mutate xmlns:ns1=\"https://adwords.google.com/api/adwords/cm/v209912\"><foo></foo>"
+ "</ns1:mutate>",
"<foo></foo></ns1:mutate>"));
testData.add(Lists.newArrayList("<mutate></mutate>", "</mutate>"));
for (List<String> testPair : testData) {
String requestXml = testPair.get(0);
String postProcessed = uploader.trimStartEndElements(requestXml, false, true);
assertEquals("Trimmed result should exclude starting mutate if !isFirst and isLast",
testPair.get(1), postProcessed);
}
}
@Test
public void testTrimStartEndElements_isFirst_notLast() {
List<List<String>> testData = Lists.newArrayList();
testData.add(Lists.newArrayList("<mutate><foo></foo></mutate>", "<mutate><foo></foo>"));
testData.add(Lists.newArrayList(
"<ns1:mutate xmlns:ns1=\"https://adwords.google.com/api/adwords/cm/v209912\"><foo></foo>"
+ "</ns1:mutate>",
"<ns1:mutate xmlns:ns1=\"https://adwords.google.com/api/adwords/cm/v209912\"><foo></foo>"));
testData.add(Lists.newArrayList("<mutate></mutate>", "<mutate>"));
for (List<String> testPair : testData) {
String requestXml = testPair.get(0);
String postProcessed = uploader.trimStartEndElements(requestXml, true, false);
assertEquals("Trimmed result should exclude ending mutate if isFirst and !isLast",
testPair.get(1), postProcessed);
}
}
@Test
public void testTrimStartEndElements_notFirst_notLast() {
List<List<String>> testData = Lists.newArrayList();
testData.add(Lists.newArrayList("<mutate><foo></foo></mutate>", "<foo></foo>"));
testData.add(Lists.newArrayList(
"<ns1:mutate xmlns:ns1=\"https://adwords.google.com/api/adwords/cm/v209912\"><foo></foo>"
+ "</ns1:mutate>",
"<foo></foo>"));
testData.add(Lists.newArrayList("<mutate></mutate>", ""));
for (List<String> testPair : testData) {
String requestXml = testPair.get(0);
String postProcessed = uploader.trimStartEndElements(requestXml, false, false);
assertEquals(
"Trimmed result should exclude starting and ending mutate if !isFirst and !isLast",
testPair.get(1), postProcessed);
}
}
/**
* Verifies that {@code trimStartEndElements} fails with an {@link IllegalArgumentException} if
* the request does not contain the expected opening or closing tag.
*/
@Test
public void testTrimStartEndElements_missingMutateElements() {
List<String> testData = Lists.newArrayList();
testData.add("<bar><foo></foo></bar>");
testData.add(
"<ns1:operation xmlns:ns1=\"https://adwords.google.com/api/adwords/cm/v209912\"><foo></foo>"
+ "</ns1:operation>");
testData.add(
"<operation xmlns=\"https://adwords.google.com/api/adwords/cm/v209912\"><foo></foo>"
+ "</operation>");
for (String requestXml : testData) {
// Not using ExpectedException here because the test needs to continue after each exception
// is thrown.
try {
uploader.trimStartEndElements(requestXml, false, true);
fail("Should have thrown an IllegalArgumentException for isLast and input: " + requestXml);
} catch (IllegalArgumentException e) {
assertTrue("Expected exception", true);
}
try {
uploader.trimStartEndElements(requestXml, true, false);
fail("Should have thrown an IllegalArgumentException for isFirst and input: " + requestXml);
} catch (IllegalArgumentException e) {
assertTrue("Expected exception", true);
}
}
}
}