• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.content.Context;
20 import android.test.AndroidTestCase;
21 import android.test.suitebuilder.annotation.SmallTest;
22 import android.test.suitebuilder.annotation.Suppress;
23 
24 import com.android.email.DBTestHelper;
25 import com.android.email.provider.EmailProvider;
26 import com.android.emailcommon.mail.Address;
27 import com.android.emailcommon.mail.MessagingException;
28 import com.android.emailcommon.provider.Account;
29 import com.android.emailcommon.provider.EmailContent.Attachment;
30 import com.android.emailcommon.provider.EmailContent.Body;
31 import com.android.emailcommon.provider.EmailContent.Message;
32 import com.android.emailcommon.provider.HostAuth;
33 
34 import java.io.IOException;
35 import java.net.InetAddress;
36 import java.net.UnknownHostException;
37 
38 /**
39  * This is a series of unit tests for the SMTP Sender class.  These tests must be locally
40  * complete - no server(s) required.
41  *
42  * These tests can be run with the following command:
43  *   runtest -c com.android.email.mail.transport.SmtpSenderUnitTests email
44  */
45 @Suppress
46 @SmallTest
47 public class SmtpSenderUnitTests extends AndroidTestCase {
48 
49     EmailProvider mProvider;
50     Context mProviderContext;
51     Context mContext;
52     HostAuth mHostAuth;
53     private static final String LOCAL_ADDRESS = "1.2.3.4";
54 
55     /* These values are provided by setUp() */
56     private SmtpSender mSender = null;
57 
58     /* Simple test string and its base64 equivalent */
59     private final static String TEST_STRING = "Hello, world";
60     private final static String TEST_STRING_BASE64 = "SGVsbG8sIHdvcmxk";
61 
62     /**
63      * Setup code.  We generate a lightweight SmtpSender for testing.
64      */
65     @Override
setUp()66     protected void setUp() throws Exception {
67         super.setUp();
68         mProviderContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext(
69                 getContext());
70         mContext = getContext();
71 
72         mHostAuth = new HostAuth();
73         Account testAccount = new Account();
74 
75         mHostAuth.setLogin("user", "password");
76         mHostAuth.setConnection("smtp", "server", 999);
77         testAccount.mHostAuthSend = mHostAuth;
78         mSender = (SmtpSender) SmtpSender.newInstance(testAccount, mProviderContext);
79     }
80 
81     /**
82      * Confirms simple non-SSL non-TLS login
83      */
testSimpleLogin()84     public void testSimpleLogin() throws Exception {
85 
86         MockTransport mockTransport = openAndInjectMockTransport();
87 
88         // try to open it
89         setupOpen(mockTransport, null);
90         mSender.open();
91     }
92 
93     /**
94      * TODO: Test with SSL negotiation (faked)
95      * TODO: Test with SSL required but not supported
96      * TODO: Test with TLS negotiation (faked)
97      * TODO: Test with TLS required but not supported
98      * TODO: Test other capabilities.
99      * TODO: Test AUTH LOGIN
100      */
101 
102     /**
103      * Test:  Open and send a single message (sunny day)
104      */
testSendMessageWithBody()105     public void testSendMessageWithBody() throws Exception {
106         MockTransport mockTransport = openAndInjectMockTransport();
107 
108         // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
109         mockTransport.expectClose();
110         setupOpen(mockTransport, null);
111 
112         Message message = setupSimpleMessage();
113         message.save(mProviderContext);
114 
115         Body body = new Body();
116         body.mMessageKey = message.mId;
117         body.mTextContent = TEST_STRING;
118         body.save(mProviderContext);
119 
120         // prepare for the message traffic we'll see
121         // TODO The test is a bit fragile, as we are order-dependent (and headers are not)
122         expectSimpleMessage(mockTransport);
123         mockTransport.expect("Content-Type: text/plain; charset=utf-8");
124         mockTransport.expect("Content-Transfer-Encoding: base64");
125         mockTransport.expect("");
126         mockTransport.expect(TEST_STRING_BASE64);
127         mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery");
128 
129         // Now trigger the transmission
130         mSender.sendMessage(message.mId);
131     }
132 
133     /**
134      * Test:  Open and send a single message with an empty attachment (no file) (sunny day)
135      */
testSendMessageWithEmptyAttachment()136     public void testSendMessageWithEmptyAttachment() throws MessagingException, IOException {
137         MockTransport mockTransport = openAndInjectMockTransport();
138 
139         // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
140         mockTransport.expectClose();
141         setupOpen(mockTransport, null);
142 
143         Message message = setupSimpleMessage();
144         message.save(mProviderContext);
145 
146         // Creates an attachment with a bogus file (so we get headers only)
147         Attachment attachment = setupSimpleAttachment(mProviderContext, message.mId);
148         attachment.save(mProviderContext);
149 
150         expectSimpleMessage(mockTransport);
151         mockTransport.expect("Content-Type: multipart/mixed; boundary=\".*");
152         mockTransport.expect("");
153         mockTransport.expect("----.*");
154         expectSimpleAttachment(mockTransport, attachment);
155         mockTransport.expect("");
156         mockTransport.expect("----.*--");
157         mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery");
158 
159         // Now trigger the transmission
160         mSender.sendMessage(message.mId);
161     }
162 
163     /**
164      * Prepare to send a simple message (see setReceiveSimpleMessage)
165      */
setupSimpleMessage()166     private Message setupSimpleMessage() {
167         Message message = new Message();
168         message.mTimeStamp = System.currentTimeMillis();
169         message.mFrom = Address.parseToHeader("Jones@Registry.Org");
170         message.mTo = Address.parseToHeader("Smith@Registry.Org");
171         message.mMessageId = "1234567890";
172         return message;
173     }
174 
175     /**
176      * Prepare to receive a simple message (see setupSimpleMessage)
177      */
expectSimpleMessage(MockTransport mockTransport)178     private void expectSimpleMessage(MockTransport mockTransport) {
179         mockTransport.expect("MAIL FROM:<Jones@Registry.Org>",
180                 "250 2.1.0 <Jones@Registry.Org> sender ok");
181         mockTransport.expect("RCPT TO:<Smith@Registry.Org>",
182                 "250 2.1.5 <Smith@Registry.Org> recipient ok");
183         mockTransport.expect("DATA", "354 enter mail, end with . on a line by itself");
184         mockTransport.expect("Date: .*");
185         mockTransport.expect("Message-ID: .*");
186         mockTransport.expect("From: Jones@Registry.Org");
187         mockTransport.expect("To: Smith@Registry.Org");
188         mockTransport.expect("MIME-Version: 1.0");
189     }
190 
191     /**
192      * Prepare to send a simple attachment
193      */
setupSimpleAttachment(Context context, long messageId)194     private Attachment setupSimpleAttachment(Context context, long messageId) {
195         Attachment attachment = new Attachment();
196         attachment.mFileName = "the file.jpg";
197         attachment.mMimeType = "image/jpg";
198         attachment.mSize = 0;
199         attachment.mContentId = null;
200         attachment.setContentUri("content://com.android.email/1/1");
201         attachment.mMessageKey = messageId;
202         attachment.mLocation = null;
203         attachment.mEncoding = null;
204 
205         return attachment;
206     }
207 
208     /**
209      * Prepare to receive a simple attachment (note, no multipart support here)
210      */
expectSimpleAttachment(MockTransport mockTransport, Attachment attachment)211     private void expectSimpleAttachment(MockTransport mockTransport, Attachment attachment) {
212         mockTransport.expect("Content-Type: " + attachment.mMimeType + ";");
213         mockTransport.expect(" name=\"" + attachment.mFileName + "\"");
214         mockTransport.expect("Content-Transfer-Encoding: base64");
215         mockTransport.expect("Content-Disposition: attachment;");
216         mockTransport.expect(" filename=\"" + attachment.mFileName + "\";");
217         mockTransport.expect(" size=" + Long.toString(attachment.mSize));
218         mockTransport.expect("");
219         String attachmentContentUri = attachment.getContentUri();
220         if (attachmentContentUri != null && attachmentContentUri.startsWith("file://")) {
221             mockTransport.expect(TEST_STRING_BASE64);
222         }
223     }
224 
225     /**
226      * Test:  Recover from a server closing early (or returning an empty string)
227      */
testEmptyLineResponse()228     public void testEmptyLineResponse() throws Exception {
229         MockTransport mockTransport = openAndInjectMockTransport();
230 
231         // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
232         mockTransport.expectClose();
233 
234         // Load up just the bare minimum to expose the error
235         mockTransport.expect(null, "220 MockTransport 2000 Ready To Assist You Peewee");
236         mockTransport.expectLiterally("EHLO [" + LOCAL_ADDRESS + "]", null);
237 
238         // Now trigger the transmission
239         // Note, a null message is sufficient here, as we won't even get past open()
240         try {
241             mSender.sendMessage(-1);
242             fail("Should not be able to send with failed open()");
243         } catch (MessagingException me) {
244             // good - expected
245             // TODO maybe expect a particular exception?
246         }
247     }
248 
249     /**
250      * Set up a basic MockTransport. open it, and inject it into mStore
251      */
openAndInjectMockTransport()252     private MockTransport openAndInjectMockTransport() throws UnknownHostException {
253         // Create mock transport and inject it into the SmtpSender that's already set up
254         MockTransport mockTransport = new MockTransport(mContext, mHostAuth);
255         mockTransport.setSecurity(HostAuth.FLAG_NONE, false);
256         mSender.setTransport(mockTransport);
257         mockTransport.setMockLocalAddress(InetAddress.getByName(LOCAL_ADDRESS));
258         return mockTransport;
259     }
260 
261     /**
262      * Helper which stuffs the mock with enough strings to satisfy a call to SmtpSender.open()
263      *
264      * @param mockTransport the mock transport we're using
265      * @param capabilities if non-null, comma-separated list of capabilities
266      */
setupOpen(MockTransport mockTransport, String capabilities)267     private void setupOpen(MockTransport mockTransport, String capabilities) {
268         mockTransport.expect(null, "220 MockTransport 2000 Ready To Assist You Peewee");
269         mockTransport.expect("EHLO .*", "250-10.20.30.40 hello");
270         if (capabilities == null) {
271             mockTransport.expect(null, "250-HELP");
272             mockTransport.expect(null, "250-AUTH LOGIN PLAIN CRAM-MD5");
273             mockTransport.expect(null, "250-SIZE 15728640");
274             mockTransport.expect(null, "250-ENHANCEDSTATUSCODES");
275             mockTransport.expect(null, "250-8BITMIME");
276         } else {
277             for (String capability : capabilities.split(",")) {
278                 mockTransport.expect(null, "250-" + capability);
279             }
280         }
281         mockTransport.expect(null, "250+OK");
282         mockTransport.expect("AUTH PLAIN .*", "235 2.7.0 ... authentication succeeded");
283     }
284 }
285