SmtpSenderUnitTests.java revision 5f95d682885db306b966e245627a175b866f0f53
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.Address; 20import com.android.email.mail.MessagingException; 21import com.android.email.mail.Transport; 22import com.android.email.provider.EmailProvider; 23import com.android.email.provider.EmailContent.Attachment; 24import com.android.email.provider.EmailContent.Body; 25import com.android.email.provider.EmailContent.Message; 26 27import org.apache.commons.io.IOUtils; 28 29import android.content.Context; 30import android.test.ProviderTestCase2; 31import android.test.suitebuilder.annotation.SmallTest; 32 33import java.io.ByteArrayInputStream; 34import java.io.File; 35import java.io.FileOutputStream; 36import java.io.IOException; 37import java.io.InputStream; 38import java.io.OutputStream; 39 40/** 41 * This is a series of unit tests for the SMTP Sender class. These tests must be locally 42 * complete - no server(s) required. 43 * 44 * These tests can be run with the following command: 45 * runtest -c com.android.email.mail.transport.SmtpSenderUnitTests email 46 */ 47@SmallTest 48public class SmtpSenderUnitTests extends ProviderTestCase2<EmailProvider> { 49 50 EmailProvider mProvider; 51 Context mProviderContext; 52 Context mContext; 53 54 /* These values are provided by setUp() */ 55 private SmtpSender mSender = null; 56 57 /* Simple test string and its base64 equivalent */ 58 private final static String TEST_STRING = "Hello, world"; 59 private final static String TEST_STRING_BASE64 = "SGVsbG8sIHdvcmxk"; 60 61 public SmtpSenderUnitTests() { 62 super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY); 63 } 64 65 /** 66 * Setup code. We generate a lightweight SmtpSender for testing. 67 */ 68 @Override 69 protected void setUp() throws Exception { 70 super.setUp(); 71 mProviderContext = getMockContext(); 72 mContext = getContext(); 73 74 // These are needed so we can get at the inner classes 75 mSender = (SmtpSender) SmtpSender.newInstance(mProviderContext, 76 "smtp://user:password@server:999"); 77 } 78 79 /** 80 * Confirms simple non-SSL non-TLS login 81 */ 82 public void testSimpleLogin() throws MessagingException { 83 84 MockTransport mockTransport = openAndInjectMockTransport(); 85 86 // try to open it 87 setupOpen(mockTransport, null); 88 mSender.open(); 89 } 90 91 /** 92 * TODO: Test with SSL negotiation (faked) 93 * TODO: Test with SSL required but not supported 94 * TODO: Test with TLS negotiation (faked) 95 * TODO: Test with TLS required but not supported 96 * TODO: Test other capabilities. 97 * TODO: Test AUTH LOGIN 98 */ 99 100 /** 101 * Test: Open and send a single message (sunny day) 102 */ 103 public void testSendMessageWithBody() throws MessagingException { 104 MockTransport mockTransport = openAndInjectMockTransport(); 105 106 // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open 107 mockTransport.expectClose(); 108 setupOpen(mockTransport, null); 109 110 Message message = setupSimpleMessage(); 111 message.save(mProviderContext); 112 113 Body body = new Body(); 114 body.mMessageKey = message.mId; 115 body.mTextContent = TEST_STRING; 116 body.save(mProviderContext); 117 118 // prepare for the message traffic we'll see 119 // TODO The test is a bit fragile, as we are order-dependent (and headers are not) 120 expectSimpleMessage(mockTransport); 121 mockTransport.expect("Content-Type: text/plain; charset=utf-8"); 122 mockTransport.expect("Content-Transfer-Encoding: base64"); 123 mockTransport.expect(""); 124 mockTransport.expect(TEST_STRING_BASE64); 125 mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery"); 126 127 // Now trigger the transmission 128 mSender.sendMessage(message.mId); 129 } 130 131 /** 132 * Test: Open and send a single message with an empty attachment (no file) (sunny day) 133 */ 134 public void testSendMessageWithEmptyAttachment() throws MessagingException, IOException { 135 MockTransport mockTransport = openAndInjectMockTransport(); 136 137 // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open 138 mockTransport.expectClose(); 139 setupOpen(mockTransport, null); 140 141 Message message = setupSimpleMessage(); 142 message.save(mProviderContext); 143 144 // Creates an attachment with a bogus file (so we get headers only) 145 Attachment attachment = setupSimpleAttachment(mProviderContext, message.mId, false); 146 attachment.save(mProviderContext); 147 148 expectSimpleMessage(mockTransport); 149 mockTransport.expect("Content-Type: multipart/mixed; boundary=\".*"); 150 mockTransport.expect(""); 151 mockTransport.expect("----.*"); 152 expectSimpleAttachment(mockTransport, attachment); 153 mockTransport.expect(""); 154 mockTransport.expect("----.*--"); 155 mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery"); 156 157 // Now trigger the transmission 158 mSender.sendMessage(message.mId); 159 } 160 161 /** 162 * Test: Open and send a single message with an attachment (sunny day) 163 */ 164 public void testSendMessageWithAttachment() throws MessagingException, IOException { 165 MockTransport mockTransport = openAndInjectMockTransport(); 166 167 // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open 168 mockTransport.expectClose(); 169 setupOpen(mockTransport, null); 170 171 Message message = setupSimpleMessage(); 172 message.save(mProviderContext); 173 174 // Creates an attachment with a real file 175 Attachment attachment = setupSimpleAttachment(mProviderContext, message.mId, true); 176 attachment.save(mProviderContext); 177 178 expectSimpleMessage(mockTransport); 179 mockTransport.expect("Content-Type: multipart/mixed; boundary=\".*"); 180 mockTransport.expect(""); 181 mockTransport.expect("----.*"); 182 expectSimpleAttachment(mockTransport, attachment); 183 mockTransport.expect(""); 184 mockTransport.expect("----.*--"); 185 mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery"); 186 187 // Now trigger the transmission 188 mSender.sendMessage(message.mId); 189 } 190 191 /** 192 * Test: Open and send a single message with two attachments 193 */ 194 public void testSendMessageWithTwoAttachments() throws MessagingException, IOException { 195 MockTransport mockTransport = openAndInjectMockTransport(); 196 197 // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open 198 mockTransport.expectClose(); 199 setupOpen(mockTransport, null); 200 201 Message message = setupSimpleMessage(); 202 message.save(mProviderContext); 203 204 // Creates an attachment with a real file 205 Attachment attachment = setupSimpleAttachment(mProviderContext, message.mId, true); 206 attachment.save(mProviderContext); 207 208 // Creates an attachment with a real file 209 Attachment attachment2 = setupSimpleAttachment(mProviderContext, message.mId, true); 210 attachment2.save(mProviderContext); 211 212 expectSimpleMessage(mockTransport); 213 mockTransport.expect("Content-Type: multipart/mixed; boundary=\".*"); 214 mockTransport.expect(""); 215 mockTransport.expect("----.*"); 216 expectSimpleAttachment(mockTransport, attachment); 217 mockTransport.expect(""); 218 mockTransport.expect("----.*"); 219 expectSimpleAttachment(mockTransport, attachment2); 220 mockTransport.expect(""); 221 mockTransport.expect("----.*--"); 222 mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery"); 223 224 // Now trigger the transmission 225 mSender.sendMessage(message.mId); 226 } 227 228 /** 229 * Test: Open and send a single message with body & attachment (sunny day) 230 */ 231 public void testSendMessageWithBodyAndAttachment() throws MessagingException, IOException { 232 MockTransport mockTransport = openAndInjectMockTransport(); 233 234 // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open 235 mockTransport.expectClose(); 236 setupOpen(mockTransport, null); 237 238 Message message = setupSimpleMessage(); 239 message.save(mProviderContext); 240 241 Body body = new Body(); 242 body.mMessageKey = message.mId; 243 body.mTextContent = TEST_STRING; 244 body.save(mProviderContext); 245 246 Attachment attachment = setupSimpleAttachment(mProviderContext, message.mId, true); 247 attachment.save(mProviderContext); 248 249 // prepare for the message traffic we'll see 250 expectSimpleMessage(mockTransport); 251 mockTransport.expect("Content-Type: multipart/mixed; boundary=\".*"); 252 mockTransport.expect(""); 253 mockTransport.expect("----.*"); 254 mockTransport.expect("Content-Type: text/plain; charset=utf-8"); 255 mockTransport.expect("Content-Transfer-Encoding: base64"); 256 mockTransport.expect(""); 257 mockTransport.expect(TEST_STRING_BASE64); 258 mockTransport.expect("----.*"); 259 expectSimpleAttachment(mockTransport, attachment); 260 mockTransport.expect(""); 261 mockTransport.expect("----.*--"); 262 mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery"); 263 264 // Now trigger the transmission 265 mSender.sendMessage(message.mId); 266 } 267 268 /** 269 * Prepare to send a simple message (see setReceiveSimpleMessage) 270 */ 271 private Message setupSimpleMessage() { 272 Message message = new Message(); 273 message.mTimeStamp = System.currentTimeMillis(); 274 message.mFrom = Address.parseAndPack("Jones@Registry.Org"); 275 message.mTo = Address.parseAndPack("Smith@Registry.Org"); 276 message.mMessageId = "1234567890"; 277 return message; 278 } 279 280 /** 281 * Prepare to receive a simple message (see setupSimpleMessage) 282 */ 283 private void expectSimpleMessage(MockTransport mockTransport) { 284 mockTransport.expect("MAIL FROM: <Jones@Registry.Org>", 285 "250 2.1.0 <Jones@Registry.Org> sender ok"); 286 mockTransport.expect("RCPT TO: <Smith@Registry.Org>", 287 "250 2.1.5 <Smith@Registry.Org> recipient ok"); 288 mockTransport.expect("DATA", "354 enter mail, end with . on a line by itself"); 289 mockTransport.expect("Date: .*"); 290 mockTransport.expect("Message-ID: .*"); 291 mockTransport.expect("From: Jones@Registry.Org"); 292 mockTransport.expect("To: Smith@Registry.Org"); 293 mockTransport.expect("MIME-Version: 1.0"); 294 } 295 296 /** 297 * Prepare to send a simple attachment 298 */ 299 private Attachment setupSimpleAttachment(Context context, long messageId, boolean withBody) 300 throws IOException { 301 Attachment attachment = new Attachment(); 302 attachment.mFileName = "the file.jpg"; 303 attachment.mMimeType = "image/jpg"; 304 attachment.mSize = 0; 305 attachment.mContentId = null; 306 attachment.mContentUri = "content://com.android.email/1/1"; 307 attachment.mMessageKey = messageId; 308 attachment.mLocation = null; 309 attachment.mEncoding = null; 310 311 if (withBody) { 312 // Is there an easier way to set up a temp file? 313 InputStream inStream = new ByteArrayInputStream(TEST_STRING.getBytes()); 314 File cacheDir = context.getCacheDir(); 315 File tmpFile = File.createTempFile("setupSimpleAttachment", "tmp", cacheDir); 316 OutputStream outStream = new FileOutputStream(tmpFile); 317 318 IOUtils.copy(inStream, outStream); 319 attachment.mContentUri = "file://" + tmpFile.getAbsolutePath(); 320 } 321 322 return attachment; 323 } 324 325 /** 326 * Prepare to receive a simple attachment (note, no multipart support here) 327 */ 328 private void expectSimpleAttachment(MockTransport mockTransport, Attachment attachment) { 329 mockTransport.expect("Content-Type: " + attachment.mMimeType + ";"); 330 mockTransport.expect(" name=\"" + attachment.mFileName + "\""); 331 mockTransport.expect("Content-Transfer-Encoding: base64"); 332 mockTransport.expect("Content-Disposition: attachment;"); 333 mockTransport.expect(" filename=\"" + attachment.mFileName + "\";"); 334 mockTransport.expect(" size=" + Long.toString(attachment.mSize)); 335 mockTransport.expect(""); 336 if (attachment.mContentUri != null && attachment.mContentUri.startsWith("file://")) { 337 mockTransport.expect(TEST_STRING_BASE64); 338 } 339 } 340 341 /** 342 * Test: Recover from a server closing early (or returning an empty string) 343 */ 344 public void testEmptyLineResponse() { 345 MockTransport mockTransport = openAndInjectMockTransport(); 346 347 // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open 348 mockTransport.expectClose(); 349 350 // Load up just the bare minimum to expose the error 351 mockTransport.expect(null, "220 MockTransport 2000 Ready To Assist You Peewee"); 352 mockTransport.expect("EHLO .*", ""); 353 354 // Now trigger the transmission 355 // Note, a null message is sufficient here, as we won't even get past open() 356 try { 357 mSender.sendMessage(-1); 358 fail("Should not be able to send with failed open()"); 359 } catch (MessagingException me) { 360 // good - expected 361 // TODO maybe expect a particular exception? 362 } 363 } 364 365 /** 366 * Set up a basic MockTransport. open it, and inject it into mStore 367 */ 368 private MockTransport openAndInjectMockTransport() { 369 // Create mock transport and inject it into the SmtpSender that's already set up 370 MockTransport mockTransport = new MockTransport(); 371 mockTransport.setSecurity(Transport.CONNECTION_SECURITY_NONE, false); 372 mSender.setTransport(mockTransport); 373 return mockTransport; 374 } 375 376 /** 377 * Helper which stuffs the mock with enough strings to satisfy a call to SmtpSender.open() 378 * 379 * @param mockTransport the mock transport we're using 380 * @param capabilities if non-null, comma-separated list of capabilities 381 */ 382 private void setupOpen(MockTransport mockTransport, String capabilities) { 383 mockTransport.expect(null, "220 MockTransport 2000 Ready To Assist You Peewee"); 384 mockTransport.expect("EHLO .*", "250-10.20.30.40 hello"); 385 if (capabilities == null) { 386 mockTransport.expect(null, "250-HELP"); 387 mockTransport.expect(null, "250-AUTH LOGIN PLAIN CRAM-MD5"); 388 mockTransport.expect(null, "250-SIZE 15728640"); 389 mockTransport.expect(null, "250-ENHANCEDSTATUSCODES"); 390 mockTransport.expect(null, "250-8BITMIME"); 391 } else { 392 for (String capability : capabilities.split(",")) { 393 mockTransport.expect(null, "250-" + capability); 394 } 395 } 396 mockTransport.expect(null, "250+OK"); 397 mockTransport.expect("AUTH PLAIN .*", "235 2.7.0 ... authentication succeeded"); 398 } 399} 400