OSDN Git Service

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