replicant-packages_apps_Email/tests/src/com/android/email/mail/transport/MockTransport.java

386 lines
12 KiB
Java

/*
* Copyright (C) 2008 The Android Open Source Project
*
* 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.android.email.mail.transport;
import com.android.email.mail.Transport;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Pattern;
import junit.framework.Assert;
/**
* This is a mock Transport that is used to test protocols that use MailTransport.
*/
public class MockTransport implements Transport {
// All flags defining debug or development code settings must be FALSE
// when code is checked in or released.
private static boolean DEBUG_LOG_STREAMS = true;
private static String LOG_TAG = "MockTransport";
private boolean mSslAllowed = false;
private boolean mTlsAllowed = false;
private boolean mTlsReopened = false;
private boolean mOpen;
private boolean mInputOpen;
private int mConnectionSecurity;
private boolean mTrustCertificates;
private String mHost;
private ArrayList<String> mQueuedInput = new ArrayList<String>();
private static class Transaction {
public static final int ACTION_INJECT_TEXT = 0;
public static final int ACTION_SERVER_CLOSE = 1;
public static final int ACTION_CLIENT_CLOSE = 2;
int mAction;
String mPattern;
String[] mResponses;
Transaction(String pattern, String[] responses) {
mAction = ACTION_INJECT_TEXT;
mPattern = pattern;
mResponses = responses;
}
Transaction(int otherType) {
mAction = otherType;
mPattern = null;
mResponses = null;
}
@Override
public String toString() {
switch (mAction) {
case ACTION_INJECT_TEXT:
return mPattern + ": " + Arrays.toString(mResponses);
case ACTION_SERVER_CLOSE:
return "Close the server connection";
case ACTION_CLIENT_CLOSE:
return "Expect the client to close";
default:
return "(Hmm. Unknown action.)";
}
}
}
private ArrayList<Transaction> mPairs = new ArrayList<Transaction>();
/**
* Give the mock a pattern to wait for. No response will be sent.
* @param pattern Java RegEx to wait for
*/
public void expect(String pattern) {
expect(pattern, (String[])null);
}
/**
* Give the mock a pattern to wait for and a response to send back.
* @param pattern Java RegEx to wait for
* @param response String to reply with, or null to acccept string but not respond to it
*/
public void expect(String pattern, String response) {
expect(pattern, (response == null) ? null : new String[] { response });
}
/**
* Give the mock a pattern to wait for and a multi-line response to send back.
* @param pattern Java RegEx to wait for
* @param responses Strings to reply with
*/
public void expect(String pattern, String[] responses) {
Transaction pair = new Transaction(pattern, responses);
mPairs.add(pair);
}
/**
* Same as {@link #expect(String, String[])}, but the first arg is taken literally, rather than
* as a regexp.
*/
public void expectLiterally(String literal, String[] responses) {
expect("^" + Pattern.quote(literal) + "$", responses);
}
/**
* Tell the Mock Transport that we expect it to be closed. This will preserve
* the remaining entries in the expect() stream and allow us to "ride over" the close (which
* would normally reset everything).
*/
public void expectClose() {
mPairs.add(new Transaction(Transaction.ACTION_CLIENT_CLOSE));
}
private void sendResponse(String[] responses) {
for (String s : responses) {
mQueuedInput.add(s);
}
}
public boolean canTrySslSecurity() {
return (mConnectionSecurity == CONNECTION_SECURITY_SSL);
}
public boolean canTryTlsSecurity() {
return (mConnectionSecurity == Transport.CONNECTION_SECURITY_TLS);
}
public boolean canTrustAllCertificates() {
return mTrustCertificates;
}
/**
* This simulates a condition where the server has closed its side, causing
* reads to fail.
*/
public void closeInputStream() {
mInputOpen = false;
}
public void close() {
mOpen = false;
mInputOpen = false;
// unless it was expected as part of a test, reset the stream
if (mPairs.size() > 0) {
Transaction expect = mPairs.remove(0);
if (expect.mAction == Transaction.ACTION_CLIENT_CLOSE) {
return;
}
}
mQueuedInput.clear();
mPairs.clear();
}
/**
* This is a test function (not part of the interface) and is used to set up a result
* value for getHost(), if needed for the test.
*/
public void setMockHost(String host) {
mHost = host;
}
public String getHost() {
return mHost;
}
public InputStream getInputStream() {
SmtpSenderUnitTests.assertTrue(mOpen);
return new MockInputStream();
}
/**
* This normally serves as a pseudo-clone, for use by Imap. For the purposes of unit testing,
* until we need something more complex, we'll just return the actual MockTransport. Then we
* don't have to worry about dealing with test metadata like the expects list or socket state.
*/
public Transport newInstanceWithConfiguration() {
return this;
}
public OutputStream getOutputStream() {
Assert.assertTrue(mOpen);
return new MockOutputStream();
}
public int getPort() {
SmtpSenderUnitTests.fail("getPort() not implemented");
return 0;
}
public int getSecurity() {
return mConnectionSecurity;
}
public String[] getUserInfoParts() {
SmtpSenderUnitTests.fail("getUserInfoParts() not implemented");
return null;
}
public boolean isOpen() {
return mOpen;
}
public void open() /* throws MessagingException, CertificateValidationException */ {
mOpen = true;
mInputOpen = true;
}
/**
* This returns one string (if available) to the caller. Usually this simply pulls strings
* from the mQueuedInput list, but if the list is empty, we also peek the expect list. This
* supports banners, multi-line responses, and any other cases where we respond without
* a specific expect pattern.
*
* If no response text is available, we assert (failing our test) as an underflow.
*
* Logs the read text if DEBUG_LOG_STREAMS is true.
*/
public String readLine() throws IOException {
SmtpSenderUnitTests.assertTrue(mOpen);
if (!mInputOpen) {
throw new IOException("Reading from MockTransport with closed input");
}
// if there's nothing to read, see if we can find a null-pattern response
if (0 == mQueuedInput.size()) {
Transaction pair = mPairs.get(0);
if (pair != null && pair.mPattern == null) {
mPairs.remove(0);
sendResponse(pair.mResponses);
}
}
SmtpSenderUnitTests.assertTrue("Underflow reading from MockTransport", 0 != mQueuedInput.size());
String line = mQueuedInput.remove(0);
if (DEBUG_LOG_STREAMS) {
Log.d(LOG_TAG, "<<< " + line);
}
return line;
}
public void setTlsAllowed(boolean tlsAllowed) {
mTlsAllowed = tlsAllowed;
}
public boolean getTlsAllowed() {
return mTlsAllowed;
}
public void setSslAllowed(boolean sslAllowed) {
mSslAllowed = sslAllowed;
}
public boolean getSslAllowed() {
return mSslAllowed;
}
public void reopenTls() {
SmtpSenderUnitTests.assertTrue(mOpen);
SmtpSenderUnitTests.assertTrue(mTlsAllowed);
mTlsReopened = true;
}
public boolean getTlsReopened() {
return mTlsReopened;
}
public void setSecurity(int connectionSecurity, boolean trustAllCertificates) {
mConnectionSecurity = connectionSecurity;
mTrustCertificates = trustAllCertificates;
}
public void setSoTimeout(int timeoutMilliseconds) /* throws SocketException */ {
}
public void setUri(URI uri, int defaultPort) {
SmtpSenderUnitTests.assertTrue("Don't call setUri on a mock transport", false);
}
/**
* Accepts a single string (command or text) that was written by the code under test.
* Because we are essentially mocking a server, we check to see if this string was expected.
* If the string was expected, we push the corresponding responses into the mQueuedInput
* list, for subsequent calls to readLine(). If the string does not match, we assert
* the mismatch. If no string was expected, we assert it as an overflow.
*
* Logs the written text if DEBUG_LOG_STREAMS is true.
*/
public void writeLine(String s, String sensitiveReplacement) /* throws IOException */ {
if (DEBUG_LOG_STREAMS) {
Log.d(LOG_TAG, ">>> " + s);
}
SmtpSenderUnitTests.assertTrue(mOpen);
SmtpSenderUnitTests.assertTrue("Overflow writing to MockTransport: Getting " + s,
0 != mPairs.size());
Transaction pair = mPairs.remove(0);
SmtpSenderUnitTests.assertTrue("Unexpected string written to MockTransport: Actual=" + s
+ " Expected=" + pair.mPattern,
pair.mPattern != null && s.matches(pair.mPattern));
if (pair.mResponses != null) {
sendResponse(pair.mResponses);
}
}
/**
* This is an InputStream that satisfies the needs of getInputStream()
*/
private class MockInputStream extends InputStream {
byte[] mNextLine = null;
int mNextIndex = 0;
/**
* Reads from the same input buffer as readLine()
*/
@Override
public int read() throws IOException {
if (!mInputOpen) {
throw new IOException();
}
if (mNextLine != null && mNextIndex < mNextLine.length) {
return mNextLine[mNextIndex++];
}
// previous line was exhausted so try to get another one
String next = readLine();
if (next == null) {
throw new IOException("Reading from MockTransport with closed input");
}
mNextLine = (next + "\r\n").getBytes();
mNextIndex = 0;
if (mNextLine != null && mNextIndex < mNextLine.length) {
return mNextLine[mNextIndex++];
}
// no joy - throw an exception
throw new IOException();
}
}
/**
* This is an OutputStream that satisfies the needs of getOutputStream()
*/
private class MockOutputStream extends OutputStream {
StringBuilder sb = new StringBuilder();
@Override
public void write(int oneByte) {
// CR or CRLF will immediately dump previous line (w/o CRLF)
if (oneByte == '\r') {
writeLine(sb.toString(), null);
sb = new StringBuilder();
} else if (oneByte == '\n') {
// swallow it
} else {
sb.append((char)oneByte);
}
}
}
}