// Copyright 2017 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.common.lib.soap.axis; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.api.ads.adwords.lib.soap.testing.SoapResponseXmlProvider; import com.google.api.ads.common.lib.soap.axis.HttpHandler.InputStreamEventListener; import com.google.api.ads.common.lib.testing.MockHttpServer; import com.google.api.ads.common.lib.testing.MockResponse; import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpRequest; import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.common.base.MoreObjects; import java.io.IOException; import org.apache.axis.AxisEngine; import org.apache.axis.AxisFault; import org.apache.axis.Message; import org.apache.axis.MessageContext; import org.apache.axis.client.AxisClient; import org.apache.axis.transport.http.HTTPConstants; import org.hamcrest.Matchers; import org.junit.After; 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 HttpHandler}. */ @RunWith(JUnit4.class) public class HttpHandlerTest { private static final String API_VERSION = "v201609"; @Rule public ExpectedException thrown = ExpectedException.none(); @Mock private AxisEngine axisEngine; @Mock private Message requestMessage; private HttpHandler httpHandler; private StreamListener streamListener; private MockHttpServer mockHttpServer; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); streamListener = new StreamListener(); mockHttpServer = new MockHttpServer(); httpHandler = new HttpHandler(mockHttpServer.getHttpTransport(), streamListener); } @After public void tearDown() throws Exception { // Assert that the response's input stream was properly closed if it was opened. streamListener.assertStateIsValid(); } /** * Tests that a valid XML response results in a successful invocation of the handler that produces * a valid SOAP envelope. */ @Test public void testInvokeReturnsValidXml() throws IOException { // Unlike the failure tests below, create the MessageContext here with an actual AxisClient, // not a mock AxisEngine. Otherwise, the call to getSOAPEnvelope below will fail. MessageContext messageContext = new MessageContext(new AxisClient()); messageContext.setRequestMessage(requestMessage); messageContext.setProperty(MessageContext.TRANS_URL, mockHttpServer.getServerUrl()); SoapResponseXmlProvider.getTestSoapResponse(API_VERSION); mockHttpServer.setMockResponse( new MockResponse(SoapResponseXmlProvider.getTestSoapResponse(API_VERSION))); httpHandler.invoke(messageContext); assertNotNull( "SOAP envelope of response is null", messageContext.getResponseMessage().getSOAPEnvelope()); } /** Tests that a poorly formed XML response will result in an AxisFault. */ @Test public void testInvokeReturnsInvalidXml() throws AxisFault { MessageContext messageContext = new MessageContext(axisEngine); messageContext.setRequestMessage(requestMessage); messageContext.setProperty(MessageContext.TRANS_URL, mockHttpServer.getServerUrl()); messageContext.setProperty(HTTPConstants.MC_GZIP_REQUEST, true); mockHttpServer.setMockResponse( new MockResponse( "<?xml version='1.0' encoding='UTF-8' standalone='no'?>" + "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>" + "foo...")); httpHandler.invoke(messageContext); // Expect parsing to fail. Tear down will verify the stream was closed. thrown.expect(AxisFault.class); messageContext.getResponseMessage().getSOAPEnvelope(); } /** * Tests that a failed, non-XML response results in an AxisFault containing the HTTP status and * message. */ @Test public void testInvokeReturnsNonXmlResponse() throws AxisFault { MessageContext messageContext = new MessageContext(axisEngine); messageContext.setRequestMessage(requestMessage); messageContext.setProperty(MessageContext.TRANS_URL, mockHttpServer.getServerUrl()); messageContext.setProperty(HTTPConstants.MC_GZIP_REQUEST, true); MockResponse mockResponse = new MockResponse("Something went wrong", 500); mockResponse.setContentType("text/html"); mockHttpServer.setMockResponse(mockResponse); // Expect an AxisFault based on the status code and content type. thrown.expect(AxisFault.class); httpHandler.invoke(messageContext); } /** Tests that a request with null content type will fail as expected. */ @Test public void testInvokeWithoutContentType() throws AxisFault { MessageContext messageContext = new MessageContext(axisEngine); messageContext.setRequestMessage(requestMessage); messageContext.setProperty(MessageContext.TRANS_URL, mockHttpServer.getServerUrl()); messageContext.setProperty(HTTPConstants.MC_GZIP_REQUEST, true); MockResponse mockResponse = new MockResponse("Something went wrong", 500); mockResponse.setContentType(null); mockHttpServer.setMockResponse(mockResponse); // Expect an AxisFault based on the status code and content type. thrown.expect(AxisFault.class); httpHandler.invoke(messageContext); } /** Tests that a request with null message context will fail as expected. */ @Test public void testInvokeWithoutMessageContext() throws AxisFault { thrown.expect(AxisFault.class); thrown.expectCause(Matchers.<Exception>instanceOf(NullPointerException.class)); thrown.expectMessage("context"); httpHandler.invoke(null); } /** Tests that a request with null request on the message context will fail as expected. */ @Test public void testInvokeWithoutRequestMessage() throws AxisFault { thrown.expect(AxisFault.class); thrown.expectCause(Matchers.<Exception>instanceOf(NullPointerException.class)); thrown.expectMessage("request"); MessageContext messageContext = new MessageContext(axisEngine); httpHandler.invoke(messageContext); } /** Tests that a request with null request URL will fail as expected. */ @Test public void testInvokeWithoutRequestUrl() throws AxisFault { thrown.expect(AxisFault.class); thrown.expectCause(Matchers.<Exception>instanceOf(IllegalArgumentException.class)); thrown.expectMessage("URL"); MessageContext messageContext = new MessageContext(axisEngine); messageContext.setRequestMessage(requestMessage); httpHandler.invoke(messageContext); } /** Tests that the timeout set on the message context is passed to the underlying request. */ @Test public void testInvokeSetsTimeout() { MessageContext messageContext = new MessageContext(axisEngine); messageContext.setRequestMessage(requestMessage); messageContext.setProperty(MessageContext.TRANS_URL, "https://www.example.com"); // Do not care about XML parsing for this test, so set the response's status code to 302 // to trigger an AxisFault. MockLowLevelHttpResponse lowLevelHttpResponse = new MockLowLevelHttpResponse(); lowLevelHttpResponse.setContent("Intentional failure"); lowLevelHttpResponse.setStatusCode(302); /* * Set timeout on the message context, then create a custom mock transport that will capture * invocations of LowLevelHttpRequest.setTimeout(int, int) and record the arguments passed. */ int timeout = 1234567; messageContext.setTimeout(timeout); final int[] actualTimeouts = new int[] {Integer.MIN_VALUE, Integer.MIN_VALUE}; MockLowLevelHttpRequest lowLevelHttpRequest = new MockLowLevelHttpRequest() { @Override public void setTimeout(int connectTimeout, int readTimeout) throws IOException { actualTimeouts[0] = connectTimeout; actualTimeouts[1] = readTimeout; super.setTimeout(connectTimeout, readTimeout); } }; lowLevelHttpRequest.setResponse(lowLevelHttpResponse); MockHttpTransport mockTransport = new MockHttpTransport.Builder().setLowLevelHttpRequest(lowLevelHttpRequest).build(); httpHandler = new HttpHandler(mockTransport, streamListener); try { httpHandler.invoke(messageContext); fail("Expected an AxisFault"); } catch (AxisFault e) { assertThat(e.getFaultString(), Matchers.containsString("302")); } assertArrayEquals( "Timeouts not set to expected values", new int[] {timeout, timeout}, actualTimeouts); } /** InputStreamEventListener implementation that tracks the opened/closed state of a stream. */ private static class StreamListener implements InputStreamEventListener { private boolean wasOpened; private boolean wasClosed; @Override public void afterClose() { this.wasClosed = true; } @Override public void afterCreate() { this.wasOpened = true; } /** * Asserts that the underlying input stream was closed if opened, and not closed if not opened. */ void assertStateIsValid() { if (!wasOpened) { assertFalse("Input stream was closed but not opened", wasClosed); } else { assertTrue("Input stream was opened but not closed", wasClosed); } } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("wasOpened", wasOpened) .add("wasClosed", wasClosed) .toString(); } } }