MockTransport.java revision dc0753373ea6cc87902ea907eb654850ed90ce57
1/* 2 * Copyright (C) 2008 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.email.mail.transport; 18 19import com.android.email.mail.Transport; 20 21import android.util.Log; 22 23import java.io.IOException; 24import java.io.InputStream; 25import java.io.OutputStream; 26import java.net.URI; 27import java.util.ArrayList; 28import java.util.Arrays; 29 30/** 31 * This is a mock Transport that is used to test protocols that use MailTransport. 32 */ 33public class MockTransport implements Transport { 34 35 // All flags defining debug or development code settings must be FALSE 36 // when code is checked in or released. 37 private static boolean DEBUG_LOG_STREAMS = true; 38 39 private static String LOG_TAG = "MockTransport"; 40 41 private boolean mSslAllowed = false; 42 private boolean mTlsAllowed = false; 43 44 private boolean mTlsReopened = false; 45 46 private boolean mOpen; 47 private boolean mInputOpen; 48 private int mConnectionSecurity; 49 private boolean mTrustCertificates; 50 private String mHost; 51 52 private ArrayList<String> mQueuedInput = new ArrayList<String>(); 53 54 private static class Transaction { 55 public static final int ACTION_INJECT_TEXT = 0; 56 public static final int ACTION_SERVER_CLOSE = 1; 57 public static final int ACTION_CLIENT_CLOSE = 2; 58 59 int mAction; 60 String mPattern; 61 String[] mResponses; 62 63 Transaction(String pattern, String[] responses) { 64 mAction = ACTION_INJECT_TEXT; 65 mPattern = pattern; 66 mResponses = responses; 67 } 68 69 Transaction(int otherType) { 70 mAction = otherType; 71 mPattern = null; 72 mResponses = null; 73 } 74 75 @Override 76 public String toString() { 77 switch (mAction) { 78 case ACTION_INJECT_TEXT: 79 return mPattern + ": " + Arrays.toString(mResponses); 80 case ACTION_SERVER_CLOSE: 81 return "Close the server connection"; 82 case ACTION_CLIENT_CLOSE: 83 return "Expect the client to close"; 84 default: 85 return "(Hmm. Unknown action.)"; 86 } 87 } 88 } 89 90 private ArrayList<Transaction> mPairs = new ArrayList<Transaction>(); 91 92 /** 93 * Give the mock a pattern to wait for. No response will be sent. 94 * @param pattern Java RegEx to wait for 95 */ 96 public void expect(String pattern) { 97 expect(pattern, (String[])null); 98 } 99 100 /** 101 * Give the mock a pattern to wait for and a response to send back. 102 * @param pattern Java RegEx to wait for 103 * @param response String to reply with, or null to acccept string but not respond to it 104 */ 105 public void expect(String pattern, String response) { 106 expect(pattern, (response == null) ? null : new String[] { response }); 107 } 108 109 /** 110 * Give the mock a pattern to wait for and a multi-line response to send back. 111 * @param pattern Java RegEx to wait for 112 * @param responses Strings to reply with 113 */ 114 public void expect(String pattern, String[] responses) { 115 Transaction pair = new Transaction(pattern, responses); 116 mPairs.add(pair); 117 } 118 119 /** 120 * Tell the Mock Transport that we expect it to be closed. This will preserve 121 * the remaining entries in the expect() stream and allow us to "ride over" the close (which 122 * would normally reset everything). 123 */ 124 public void expectClose() { 125 mPairs.add(new Transaction(Transaction.ACTION_CLIENT_CLOSE)); 126 } 127 128 private void sendResponse(String[] responses) { 129 for (String s : responses) { 130 mQueuedInput.add(s); 131 } 132 } 133 134 public boolean canTrySslSecurity() { 135 return (mConnectionSecurity == CONNECTION_SECURITY_SSL); 136 } 137 138 public boolean canTryTlsSecurity() { 139 return (mConnectionSecurity == Transport.CONNECTION_SECURITY_TLS); 140 } 141 142 public boolean canTrustAllCertificates() { 143 return mTrustCertificates; 144 } 145 146 /** 147 * This simulates a condition where the server has closed its side, causing 148 * reads to fail. 149 */ 150 public void closeInputStream() { 151 mInputOpen = false; 152 } 153 154 public void close() { 155 mOpen = false; 156 mInputOpen = false; 157 // unless it was expected as part of a test, reset the stream 158 if (mPairs.size() > 0) { 159 Transaction expect = mPairs.remove(0); 160 if (expect.mAction == Transaction.ACTION_CLIENT_CLOSE) { 161 return; 162 } 163 } 164 mQueuedInput.clear(); 165 mPairs.clear(); 166 } 167 168 /** 169 * This is a test function (not part of the interface) and is used to set up a result 170 * value for getHost(), if needed for the test. 171 */ 172 public void setMockHost(String host) { 173 mHost = host; 174 } 175 176 public String getHost() { 177 return mHost; 178 } 179 180 public InputStream getInputStream() { 181 SmtpSenderUnitTests.assertTrue(mOpen); 182 return new MockInputStream(); 183 } 184 185 /** 186 * This normally serves as a pseudo-clone, for use by Imap. For the purposes of unit testing, 187 * until we need something more complex, we'll just return the actual MockTransport. Then we 188 * don't have to worry about dealing with test metadata like the expects list or socket state. 189 */ 190 public Transport newInstanceWithConfiguration() { 191 return this; 192 } 193 194 public OutputStream getOutputStream() { 195 SmtpSenderUnitTests.assertTrue(mOpen); 196 return new MockOutputStream(); 197 } 198 199 public int getPort() { 200 SmtpSenderUnitTests.fail("getPort() not implemented"); 201 return 0; 202 } 203 204 public int getSecurity() { 205 return mConnectionSecurity; 206 } 207 208 public String[] getUserInfoParts() { 209 SmtpSenderUnitTests.fail("getUserInfoParts() not implemented"); 210 return null; 211 } 212 213 public boolean isOpen() { 214 return mOpen; 215 } 216 217 public void open() /* throws MessagingException, CertificateValidationException */ { 218 mOpen = true; 219 mInputOpen = true; 220 } 221 222 /** 223 * This returns one string (if available) to the caller. Usually this simply pulls strings 224 * from the mQueuedInput list, but if the list is empty, we also peek the expect list. This 225 * supports banners, multi-line responses, and any other cases where we respond without 226 * a specific expect pattern. 227 * 228 * If no response text is available, we assert (failing our test) as an underflow. 229 * 230 * Logs the read text if DEBUG_LOG_STREAMS is true. 231 */ 232 public String readLine() throws IOException { 233 SmtpSenderUnitTests.assertTrue(mOpen); 234 if (!mInputOpen) { 235 throw new IOException("Reading from MockTransport with closed input"); 236 } 237 // if there's nothing to read, see if we can find a null-pattern response 238 if (0 == mQueuedInput.size()) { 239 Transaction pair = mPairs.get(0); 240 if (pair != null && pair.mPattern == null) { 241 mPairs.remove(0); 242 sendResponse(pair.mResponses); 243 } 244 } 245 SmtpSenderUnitTests.assertTrue("Underflow reading from MockTransport", 0 != mQueuedInput.size()); 246 String line = mQueuedInput.remove(0); 247 if (DEBUG_LOG_STREAMS) { 248 Log.d(LOG_TAG, "<<< " + line); 249 } 250 return line; 251 } 252 253 public void setTlsAllowed(boolean tlsAllowed) { 254 mTlsAllowed = tlsAllowed; 255 } 256 257 public boolean getTlsAllowed() { 258 return mTlsAllowed; 259 } 260 261 public void setSslAllowed(boolean sslAllowed) { 262 mSslAllowed = sslAllowed; 263 } 264 265 public boolean getSslAllowed() { 266 return mSslAllowed; 267 } 268 269 public void reopenTls() { 270 SmtpSenderUnitTests.assertTrue(mOpen); 271 SmtpSenderUnitTests.assertTrue(mTlsAllowed); 272 mTlsReopened = true; 273 } 274 275 public boolean getTlsReopened() { 276 return mTlsReopened; 277 } 278 279 public void setSecurity(int connectionSecurity, boolean trustAllCertificates) { 280 mConnectionSecurity = connectionSecurity; 281 mTrustCertificates = trustAllCertificates; 282 } 283 284 public void setSoTimeout(int timeoutMilliseconds) /* throws SocketException */ { 285 } 286 287 public void setUri(URI uri, int defaultPort) { 288 SmtpSenderUnitTests.assertTrue("Don't call setUri on a mock transport", false); 289 } 290 291 /** 292 * Accepts a single string (command or text) that was written by the code under test. 293 * Because we are essentially mocking a server, we check to see if this string was expected. 294 * If the string was expected, we push the corresponding responses into the mQueuedInput 295 * list, for subsequent calls to readLine(). If the string does not match, we assert 296 * the mismatch. If no string was expected, we assert it as an overflow. 297 * 298 * Logs the written text if DEBUG_LOG_STREAMS is true. 299 */ 300 public void writeLine(String s, String sensitiveReplacement) /* throws IOException */ { 301 if (DEBUG_LOG_STREAMS) { 302 Log.d(LOG_TAG, ">>> " + s); 303 } 304 SmtpSenderUnitTests.assertTrue(mOpen); 305 SmtpSenderUnitTests.assertTrue("Overflow writing to MockTransport", 0 != mPairs.size()); 306 Transaction pair = mPairs.remove(0); 307 SmtpSenderUnitTests.assertTrue("Unexpected string written to MockTransport", 308 pair.mPattern != null && s.matches(pair.mPattern)); 309 if (pair.mResponses != null) { 310 sendResponse(pair.mResponses); 311 } 312 } 313 314 /** 315 * This is an InputStream that satisfies the needs of getInputStream() 316 */ 317 private class MockInputStream extends InputStream { 318 319 byte[] mNextLine = null; 320 int mNextIndex = 0; 321 322 /** 323 * Reads from the same input buffer as readLine() 324 */ 325 @Override 326 public int read() throws IOException { 327 if (!mInputOpen) { 328 throw new IOException(); 329 } 330 331 if (mNextLine != null && mNextIndex < mNextLine.length) { 332 return mNextLine[mNextIndex++]; 333 } 334 335 // previous line was exhausted so try to get another one 336 String next = readLine(); 337 if (next == null) { 338 throw new IOException("Reading from MockTransport with closed input"); 339 } 340 mNextLine = (next + "\r\n").getBytes(); 341 mNextIndex = 0; 342 343 if (mNextLine != null && mNextIndex < mNextLine.length) { 344 return mNextLine[mNextIndex++]; 345 } 346 347 // no joy - throw an exception 348 throw new IOException(); 349 } 350 } 351 352 /** 353 * This is an OutputStream that satisfies the needs of getOutputStream() 354 */ 355 private class MockOutputStream extends OutputStream { 356 357 StringBuilder sb = new StringBuilder(); 358 359 @Override 360 public void write(int oneByte) { 361 // CR or CRLF will immediately dump previous line (w/o CRLF) 362 if (oneByte == '\r') { 363 writeLine(sb.toString(), null); 364 sb = new StringBuilder(); 365 } else if (oneByte == '\n') { 366 // swallow it 367 } else { 368 sb.append((char)oneByte); 369 } 370 } 371 } 372} 373