1 package com.android.volley.toolbox; 2 3 import static org.junit.Assert.assertEquals; 4 import static org.junit.Assert.fail; 5 import static org.mockito.ArgumentMatchers.any; 6 import static org.mockito.ArgumentMatchers.anyString; 7 import static org.mockito.Mockito.doAnswer; 8 import static org.mockito.Mockito.doReturn; 9 import static org.mockito.Mockito.spy; 10 import static org.mockito.Mockito.when; 11 12 import com.android.volley.Request; 13 import com.android.volley.RetryPolicy; 14 import java.io.IOException; 15 import java.io.OutputStream; 16 import java.net.HttpURLConnection; 17 import java.net.URL; 18 import java.util.Collections; 19 import java.util.HashMap; 20 import java.util.List; 21 import java.util.Map; 22 import org.apache.http.Header; 23 import org.apache.http.HttpRequest; 24 import org.apache.http.client.HttpClient; 25 import org.apache.http.client.methods.HttpUriRequest; 26 import org.junit.Before; 27 import org.junit.Test; 28 import org.junit.runner.RunWith; 29 import org.mockito.Mock; 30 import org.mockito.MockitoAnnotations; 31 import org.mockito.Spy; 32 import org.mockito.invocation.InvocationOnMock; 33 import org.mockito.stubbing.Answer; 34 import org.robolectric.RobolectricTestRunner; 35 36 /** Tests to validate that HttpStack implementations conform with expected behavior. */ 37 @RunWith(RobolectricTestRunner.class) 38 public class HttpStackConformanceTest { 39 @Mock private RetryPolicy mMockRetryPolicy; 40 @Mock private Request mMockRequest; 41 42 @Mock private HttpURLConnection mMockConnection; 43 @Mock private OutputStream mMockOutputStream; 44 @Spy private HurlStack mHurlStack = new HurlStack(); 45 46 @Mock private HttpClient mMockHttpClient; 47 private HttpClientStack mHttpClientStack; 48 49 private final TestCase[] mTestCases = 50 new TestCase[] { 51 // TestCase for HurlStack. 52 new TestCase() { 53 @Override 54 public HttpStack getStack() { 55 return mHurlStack; 56 } 57 58 @Override 59 public void setOutputHeaderMap(final Map<String, String> outputHeaderMap) { 60 doAnswer( 61 new Answer<Void>() { 62 @Override 63 public Void answer(InvocationOnMock invocation) { 64 outputHeaderMap.put( 65 invocation.<String>getArgument(0), 66 invocation.<String>getArgument(1)); 67 return null; 68 } 69 }) 70 .when(mMockConnection) 71 .setRequestProperty(anyString(), anyString()); 72 doAnswer( 73 new Answer<Map<String, List<String>>>() { 74 @Override 75 public Map<String, List<String>> answer( 76 InvocationOnMock invocation) { 77 Map<String, List<String>> result = new HashMap<>(); 78 for (Map.Entry<String, String> entry : 79 outputHeaderMap.entrySet()) { 80 result.put( 81 entry.getKey(), 82 Collections.singletonList( 83 entry.getValue())); 84 } 85 return result; 86 } 87 }) 88 .when(mMockConnection) 89 .getRequestProperties(); 90 } 91 }, 92 93 // TestCase for HttpClientStack. 94 new TestCase() { 95 @Override 96 public HttpStack getStack() { 97 return mHttpClientStack; 98 } 99 100 @Override 101 public void setOutputHeaderMap(final Map<String, String> outputHeaderMap) { 102 try { 103 doAnswer( 104 new Answer<Void>() { 105 @Override 106 public Void answer(InvocationOnMock invocation) 107 throws Throwable { 108 HttpRequest request = invocation.getArgument(0); 109 for (Header header : request.getAllHeaders()) { 110 if (outputHeaderMap.containsKey( 111 header.getName())) { 112 fail( 113 "Multiple values for header " 114 + header.getName()); 115 } 116 outputHeaderMap.put( 117 header.getName(), 118 header.getValue()); 119 } 120 return null; 121 } 122 }) 123 .when(mMockHttpClient) 124 .execute(any(HttpUriRequest.class)); 125 } catch (IOException e) { 126 throw new RuntimeException(e); 127 } 128 } 129 } 130 }; 131 132 @Before 133 public void setUp() throws Exception { 134 MockitoAnnotations.initMocks(this); 135 mHttpClientStack = spy(new HttpClientStack(mMockHttpClient)); 136 137 doReturn(mMockConnection).when(mHurlStack).createConnection(any(URL.class)); 138 doReturn(mMockOutputStream).when(mMockConnection).getOutputStream(); 139 when(mMockRequest.getUrl()).thenReturn("http://127.0.0.1"); 140 when(mMockRequest.getRetryPolicy()).thenReturn(mMockRetryPolicy); 141 } 142 143 @Test 144 public void headerPrecedence() throws Exception { 145 Map<String, String> additionalHeaders = new HashMap<>(); 146 additionalHeaders.put("A", "AddlA"); 147 additionalHeaders.put("B", "AddlB"); 148 149 Map<String, String> requestHeaders = new HashMap<>(); 150 requestHeaders.put("A", "RequestA"); 151 requestHeaders.put("C", "RequestC"); 152 when(mMockRequest.getHeaders()).thenReturn(requestHeaders); 153 154 when(mMockRequest.getMethod()).thenReturn(Request.Method.POST); 155 when(mMockRequest.getBody()).thenReturn(new byte[0]); 156 when(mMockRequest.getBodyContentType()).thenReturn("BodyContentType"); 157 158 for (TestCase testCase : mTestCases) { 159 // Test once without a Content-Type header in getHeaders(). 160 Map<String, String> combinedHeaders = new HashMap<>(); 161 testCase.setOutputHeaderMap(combinedHeaders); 162 163 testCase.getStack().performRequest(mMockRequest, additionalHeaders); 164 165 Map<String, String> expectedHeaders = new HashMap<>(); 166 expectedHeaders.put("A", "RequestA"); 167 expectedHeaders.put("B", "AddlB"); 168 expectedHeaders.put("C", "RequestC"); 169 expectedHeaders.put(HttpHeaderParser.HEADER_CONTENT_TYPE, "BodyContentType"); 170 171 assertEquals(expectedHeaders, combinedHeaders); 172 173 // Reset and test again with a Content-Type header in getHeaders(). 174 combinedHeaders.clear(); 175 176 requestHeaders.put(HttpHeaderParser.HEADER_CONTENT_TYPE, "RequestContentType"); 177 expectedHeaders.put(HttpHeaderParser.HEADER_CONTENT_TYPE, "RequestContentType"); 178 179 testCase.getStack().performRequest(mMockRequest, additionalHeaders); 180 assertEquals(expectedHeaders, combinedHeaders); 181 182 // Clear the Content-Type header for the next TestCase. 183 requestHeaders.remove(HttpHeaderParser.HEADER_CONTENT_TYPE); 184 } 185 } 186 187 private interface TestCase { 188 HttpStack getStack(); 189 190 void setOutputHeaderMap(Map<String, String> outputHeaderMap); 191 } 192 } 193