1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.phone.common.mail; 18 19import static org.mockito.Mockito.mock; 20import static org.mockito.Mockito.verify; 21import static org.mockito.Mockito.when; 22 23import android.net.Network; 24import android.test.AndroidTestCase; 25 26import com.android.phone.MockitoHelper; 27import com.android.phone.common.mail.MailTransport.SocketCreator; 28import com.android.phone.common.mail.store.ImapStore; 29import com.android.phone.vvm.omtp.imap.ImapHelper; 30 31import junit.framework.AssertionFailedError; 32 33import org.mockito.MockitoAnnotations; 34 35import java.io.IOException; 36import java.io.InputStream; 37import java.io.OutputStream; 38import java.net.InetAddress; 39import java.net.InetSocketAddress; 40import java.net.Socket; 41import java.net.SocketAddress; 42import java.net.SocketException; 43import java.net.UnknownHostException; 44 45import javax.net.SocketFactory; 46 47public class MailTransportTest extends AndroidTestCase { 48 49 private static final String HOST_ADDRESS = "127.0.0.1"; 50 private static final String INVALID_HOST_ADDRESS = "255.255.255.255"; 51 private static final int HOST_PORT = 80; 52 private static final int HOST_FLAGS = 0; 53 // bypass verifyHostname() in open() by setting ImapStore.FLAG_TRUST_ALL 54 private static final int HOST_FLAGS_SSL = ImapStore.FLAG_SSL & ImapStore.FLAG_TRUST_ALL; 55 private static final InetAddress VALID_INET_ADDRESS = createInetAddress(HOST_ADDRESS); 56 private static final InetAddress INVALID_INET_ADDRESS = createInetAddress(INVALID_HOST_ADDRESS); 57 58 // ClassLoader need to be replaced for mockito to work. 59 private MockitoHelper mMokitoHelper = new MockitoHelper(); 60 61 @Override 62 public void setUp() throws Exception { 63 super.setUp(); 64 mMokitoHelper.setUp(getContext(), getClass()); 65 MockitoAnnotations.initMocks(this); 66 } 67 68 @Override 69 public void tearDown() throws Exception { 70 mMokitoHelper.tearDown(); 71 super.tearDown(); 72 } 73 74 public void testCreateSocket_anyNetwork() throws MessagingException { 75 // With no network, Socket#Socket() should be called. 76 MailTransport transport = 77 new MailTransport(getContext(), createMockImapHelper(), null, HOST_ADDRESS, 78 HOST_PORT, HOST_FLAGS); 79 Socket socket = transport.createSocket(); 80 assertTrue(socket != null); 81 } 82 83 public void testCreateSocket_networkSpecified() throws MessagingException, IOException { 84 // Network#getSocketFactory should be used to create socket. 85 Network mockNetwork = createMockNetwork(); 86 MailTransport transport = 87 new MailTransport(getContext(), createMockImapHelper(), mockNetwork, HOST_ADDRESS, 88 HOST_PORT, HOST_FLAGS); 89 Socket socket = transport.createSocket(); 90 assertTrue(socket != null); 91 verify(mockNetwork).getSocketFactory(); 92 } 93 94 public void testCreateSocket_socketCreator() throws MessagingException, IOException { 95 // For testing purposes, how sockets are created can be overridden. 96 SocketCreator socketCreator = new SocketCreator() { 97 98 private final Socket mSocket = new Socket(); 99 100 @Override 101 public Socket createSocket() { 102 return mSocket; 103 } 104 }; 105 106 MailTransport transport = new 107 MailTransport(getContext(), createMockImapHelper(), null, HOST_ADDRESS, HOST_PORT, 108 HOST_FLAGS); 109 110 transport.setSocketCreator(socketCreator); 111 112 Socket socket = transport.createSocket(); 113 assertTrue(socket == socketCreator.createSocket()); 114 } 115 116 public void testOpen() throws MessagingException { 117 MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), null, 118 HOST_ADDRESS, 119 HOST_PORT, HOST_FLAGS); 120 transport.setSocketCreator(new TestSocketCreator()); 121 transport.open(); 122 assertTrue(transport.isOpen()); 123 124 } 125 126 public void testOpen_Ssl() throws MessagingException { 127 //opening with ssl support. 128 MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), null, 129 HOST_ADDRESS, HOST_PORT, HOST_FLAGS_SSL); 130 transport.setSocketCreator(new TestSocketCreator()); 131 transport.open(); 132 assertTrue(transport.isOpen()); 133 134 } 135 136 public void testOpen_MultiIp() throws MessagingException { 137 //In case of round robin DNS, try all resolved address until one succeeded. 138 Network network = createMultiIpMockNetwork(); 139 MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), network, 140 HOST_ADDRESS, 141 HOST_PORT, HOST_FLAGS); 142 transport.setSocketCreator(new TestSocketCreator()); 143 transport.open(); 144 assertTrue(transport.isOpen()); 145 } 146 147 public void testOpen_MultiIp_SSL() throws MessagingException { 148 Network network = createMultiIpMockNetwork(); 149 150 MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), network, 151 HOST_ADDRESS, 152 HOST_PORT, HOST_FLAGS_SSL); 153 transport.setSocketCreator(new TestSocketCreator()); 154 transport.open(); 155 assertTrue(transport.isOpen()); 156 } 157 158 public void testOpen_network_hostResolutionFailed() { 159 // Couldn't resolve host on the network. Open() should fail. 160 Network network = createMockNetwork(); 161 try { 162 when(network.getAllByName(HOST_ADDRESS)) 163 .thenThrow(new UnknownHostException("host resolution failed")); 164 } catch (IOException e) { 165 //ignored 166 } 167 168 MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), network, 169 HOST_ADDRESS, 170 HOST_PORT, HOST_FLAGS); 171 try { 172 transport.open(); 173 throw new AssertionFailedError("Should throw MessagingException"); 174 } catch (MessagingException e) { 175 //expected 176 } 177 assertFalse(transport.isOpen()); 178 } 179 180 public void testOpen_createSocketFailed() { 181 // Unable to create socket. Open() should fail. 182 MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), null, 183 HOST_ADDRESS, 184 HOST_PORT, HOST_FLAGS); 185 transport.setSocketCreator(new SocketCreator() { 186 @Override 187 public Socket createSocket() throws MessagingException { 188 throw new MessagingException("createSocket failed"); 189 } 190 }); 191 try { 192 transport.open(); 193 throw new AssertionFailedError("Should throw MessagingException"); 194 } catch (MessagingException e) { 195 //expected 196 } 197 assertFalse(transport.isOpen()); 198 } 199 200 public void testOpen_network_createSocketFailed() { 201 // Unable to create socket. Open() should fail. 202 203 Network network = createOneIpMockNetwork(); 204 SocketFactory mockSocketFactory = mock(SocketFactory.class); 205 try { 206 when(mockSocketFactory.createSocket()) 207 .thenThrow(new IOException("unable to create socket")); 208 } catch (IOException e) { 209 //ignored 210 } 211 when(network.getSocketFactory()).thenReturn(mockSocketFactory); 212 213 MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), network, 214 HOST_ADDRESS, HOST_PORT, HOST_FLAGS); 215 216 try { 217 transport.open(); 218 throw new AssertionFailedError("Should throw MessagingException"); 219 } catch (MessagingException e) { 220 //expected 221 } 222 assertFalse(transport.isOpen()); 223 } 224 225 public void testOpen_connectFailed_one() { 226 // There is only one IP for this host, and we failed to connect to it. Open() should fail. 227 228 MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), null, 229 HOST_ADDRESS, HOST_PORT, HOST_FLAGS); 230 transport.setSocketCreator(new SocketCreator() { 231 @Override 232 public Socket createSocket() throws MessagingException { 233 return new Socket() { 234 @Override 235 public void connect(SocketAddress address, int timeout) throws IOException { 236 throw new IOException("connect failed"); 237 } 238 }; 239 } 240 }); 241 try { 242 transport.open(); 243 throw new AssertionFailedError("Should throw MessagingException"); 244 } catch (MessagingException e) { 245 //expected 246 } 247 assertFalse(transport.isOpen()); 248 } 249 250 public void testOpen_connectFailed_multi() { 251 // There are multiple IP for this host, and we failed to connect to any of it. 252 // Open() should fail. 253 MailTransport transport = new MailTransport(getContext(), createMockImapHelper(), 254 createMultiIpMockNetwork(), HOST_ADDRESS, HOST_PORT, HOST_FLAGS); 255 transport.setSocketCreator(new SocketCreator() { 256 @Override 257 public Socket createSocket() throws MessagingException { 258 return new Socket() { 259 @Override 260 public void connect(SocketAddress address, int timeout) throws IOException { 261 throw new IOException("connect failed"); 262 } 263 }; 264 } 265 }); 266 try { 267 transport.open(); 268 throw new AssertionFailedError("Should throw MessagingException"); 269 } catch (MessagingException e) { 270 //expected 271 } 272 assertFalse(transport.isOpen()); 273 } 274 275 private class TestSocket extends Socket { 276 277 boolean mConnected = false; 278 279 280 /** 281 * A make a mock connection to the address. 282 * 283 * @param address Only address equivalent to VALID_INET_ADDRESS or INVALID_INET_ADDRESS is 284 * accepted 285 * @param timeout Ignored but should >= 0. 286 */ 287 @Override 288 public void connect(SocketAddress address, int timeout) throws IOException { 289 // copied from Socket#connect 290 if (isClosed()) { 291 throw new SocketException("Socket is closed"); 292 } 293 if (timeout < 0) { 294 throw new IllegalArgumentException("timeout < 0"); 295 } 296 if (isConnected()) { 297 throw new SocketException("Already connected"); 298 } 299 if (address == null) { 300 throw new IllegalArgumentException("remoteAddr == null"); 301 } 302 303 if (!(address instanceof InetSocketAddress)) { 304 throw new AssertionError("address should be InetSocketAddress"); 305 } 306 307 InetSocketAddress inetSocketAddress = (InetSocketAddress) address; 308 if (inetSocketAddress.getAddress().equals(INVALID_INET_ADDRESS)) { 309 throw new IOException("invalid address"); 310 } else if (inetSocketAddress.getAddress().equals(VALID_INET_ADDRESS)) { 311 mConnected = true; 312 } else { 313 throw new AssertionError("Only INVALID_ADDRESS or VALID_ADDRESS are allowed"); 314 } 315 } 316 317 @Override 318 public InputStream getInputStream() { 319 return null; 320 } 321 322 @Override 323 public OutputStream getOutputStream() { 324 return null; 325 } 326 327 @Override 328 public boolean isConnected() { 329 return mConnected; 330 } 331 332 } 333 334 335 private class TestSocketCreator implements MailTransport.SocketCreator { 336 337 @Override 338 public Socket createSocket() throws MessagingException { 339 Socket socket = new TestSocket(); 340 return socket; 341 } 342 343 } 344 345 private ImapHelper createMockImapHelper() { 346 return mock(ImapHelper.class); 347 } 348 349 /** 350 * @return a mock Network that can create a TestSocket with {@code getSocketFactory() 351 * .createSocket()} 352 */ 353 private Network createMockNetwork() { 354 Network network = mock(Network.class); 355 SocketFactory mockSocketFactory = mock(SocketFactory.class); 356 try { 357 when(mockSocketFactory.createSocket()).thenReturn(new TestSocket()); 358 } catch (IOException e) { 359 //ignored 360 } 361 when(network.getSocketFactory()).thenReturn(mockSocketFactory); 362 return network; 363 } 364 365 /** 366 * @return a mock Network like {@link MailTransportTest#createMockNetwork()}, but also supports 367 * {@link Network#getAllByName(String)} with one valid result. 368 */ 369 private Network createOneIpMockNetwork() { 370 Network network = createMockNetwork(); 371 try { 372 when(network.getAllByName(HOST_ADDRESS)) 373 .thenReturn(new InetAddress[] {VALID_INET_ADDRESS}); 374 } catch (UnknownHostException e) { 375 //ignored 376 } 377 378 return network; 379 } 380 381 /** 382 * @return a mock Network like {@link MailTransportTest#createMockNetwork()}, but also supports 383 * {@link Network#getAllByName(String)}, which will return 2 address with the first one 384 * invalid. 385 */ 386 private Network createMultiIpMockNetwork() { 387 Network network = createMockNetwork(); 388 try { 389 when(network.getAllByName(HOST_ADDRESS)) 390 .thenReturn(new InetAddress[] {INVALID_INET_ADDRESS, VALID_INET_ADDRESS}); 391 } catch (UnknownHostException e) { 392 //ignored 393 } 394 395 return network; 396 } 397 398 /** 399 * helper method to translate{@code host} into a InetAddress. 400 * 401 * @param host IP address of the host. Domain name should not be used as this method should not 402 * access the internet. 403 */ 404 private static InetAddress createInetAddress(String host) { 405 try { 406 return InetAddress.getByName(host); 407 } catch (UnknownHostException e) { 408 return null; 409 } 410 } 411 412 413} 414