Home | History | Annotate | Download | only in transport
      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
     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      */
     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      */
    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      */
    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      */
    166     private Message setupSimpleMessage() {
    167         Message message = new Message();
    168         message.mTimeStamp = System.currentTimeMillis();
    169         message.mFrom = Address.parseToHeader("Jones (at) Registry.Org");
    170         message.mTo = Address.parseToHeader("Smith (at) Registry.Org");
    171         message.mMessageId = "1234567890";
    172         return message;
    173     }
    174 
    175     /**
    176      * Prepare to receive a simple message (see setupSimpleMessage)
    177      */
    178     private void expectSimpleMessage(MockTransport mockTransport) {
    179         mockTransport.expect("MAIL FROM:<Jones (at) Registry.Org>",
    180                 "250 2.1.0 <Jones (at) Registry.Org> sender ok");
    181         mockTransport.expect("RCPT TO:<Smith (at) Registry.Org>",
    182                 "250 2.1.5 <Smith (at) 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 (at) Registry.Org");
    187         mockTransport.expect("To: Smith (at) Registry.Org");
    188         mockTransport.expect("MIME-Version: 1.0");
    189     }
    190 
    191     /**
    192      * Prepare to send a simple attachment
    193      */
    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      */
    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      */
    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      */
    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      */
    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