package com.android.volley.toolbox; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import com.android.volley.Request; import com.android.volley.RetryPolicy; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.http.Header; import org.apache.http.HttpRequest; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpUriRequest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; /** Tests to validate that HttpStack implementations conform with expected behavior. */ @RunWith(RobolectricTestRunner.class) public class HttpStackConformanceTest { @Mock private RetryPolicy mMockRetryPolicy; @Mock private Request mMockRequest; @Mock private HttpURLConnection mMockConnection; @Mock private OutputStream mMockOutputStream; @Spy private HurlStack mHurlStack = new HurlStack(); @Mock private HttpClient mMockHttpClient; private HttpClientStack mHttpClientStack; private final TestCase[] mTestCases = new TestCase[] { // TestCase for HurlStack. new TestCase() { @Override public HttpStack getStack() { return mHurlStack; } @Override public void setOutputHeaderMap(final Map outputHeaderMap) { doAnswer( new Answer() { @Override public Void answer(InvocationOnMock invocation) { outputHeaderMap.put( invocation.getArgument(0), invocation.getArgument(1)); return null; } }) .when(mMockConnection) .setRequestProperty(anyString(), anyString()); doAnswer( new Answer>>() { @Override public Map> answer( InvocationOnMock invocation) { Map> result = new HashMap<>(); for (Map.Entry entry : outputHeaderMap.entrySet()) { result.put( entry.getKey(), Collections.singletonList( entry.getValue())); } return result; } }) .when(mMockConnection) .getRequestProperties(); } }, // TestCase for HttpClientStack. new TestCase() { @Override public HttpStack getStack() { return mHttpClientStack; } @Override public void setOutputHeaderMap(final Map outputHeaderMap) { try { doAnswer( new Answer() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { HttpRequest request = invocation.getArgument(0); for (Header header : request.getAllHeaders()) { if (outputHeaderMap.containsKey( header.getName())) { fail( "Multiple values for header " + header.getName()); } outputHeaderMap.put( header.getName(), header.getValue()); } return null; } }) .when(mMockHttpClient) .execute(any(HttpUriRequest.class)); } catch (IOException e) { throw new RuntimeException(e); } } } }; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mHttpClientStack = spy(new HttpClientStack(mMockHttpClient)); doReturn(mMockConnection).when(mHurlStack).createConnection(any(URL.class)); doReturn(mMockOutputStream).when(mMockConnection).getOutputStream(); when(mMockRequest.getUrl()).thenReturn("http://127.0.0.1"); when(mMockRequest.getRetryPolicy()).thenReturn(mMockRetryPolicy); } @Test public void headerPrecedence() throws Exception { Map additionalHeaders = new HashMap<>(); additionalHeaders.put("A", "AddlA"); additionalHeaders.put("B", "AddlB"); Map requestHeaders = new HashMap<>(); requestHeaders.put("A", "RequestA"); requestHeaders.put("C", "RequestC"); when(mMockRequest.getHeaders()).thenReturn(requestHeaders); when(mMockRequest.getMethod()).thenReturn(Request.Method.POST); when(mMockRequest.getBody()).thenReturn(new byte[0]); when(mMockRequest.getBodyContentType()).thenReturn("BodyContentType"); for (TestCase testCase : mTestCases) { // Test once without a Content-Type header in getHeaders(). Map combinedHeaders = new HashMap<>(); testCase.setOutputHeaderMap(combinedHeaders); testCase.getStack().performRequest(mMockRequest, additionalHeaders); Map expectedHeaders = new HashMap<>(); expectedHeaders.put("A", "RequestA"); expectedHeaders.put("B", "AddlB"); expectedHeaders.put("C", "RequestC"); expectedHeaders.put(HttpHeaderParser.HEADER_CONTENT_TYPE, "BodyContentType"); assertEquals(expectedHeaders, combinedHeaders); // Reset and test again with a Content-Type header in getHeaders(). combinedHeaders.clear(); requestHeaders.put(HttpHeaderParser.HEADER_CONTENT_TYPE, "RequestContentType"); expectedHeaders.put(HttpHeaderParser.HEADER_CONTENT_TYPE, "RequestContentType"); testCase.getStack().performRequest(mMockRequest, additionalHeaders); assertEquals(expectedHeaders, combinedHeaders); // Clear the Content-Type header for the next TestCase. requestHeaders.remove(HttpHeaderParser.HEADER_CONTENT_TYPE); } } private interface TestCase { HttpStack getStack(); void setOutputHeaderMap(Map outputHeaderMap); } }