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 
     23 import com.android.email.DBTestHelper;
     24 import com.android.email.mail.Transport;
     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 @SmallTest
     46 public class SmtpSenderUnitTests extends AndroidTestCase {
     47 
     48     EmailProvider mProvider;
     49     Context mProviderContext;
     50     Context mContext;
     51     private static final String LOCAL_ADDRESS = "1.2.3.4";
     52 
     53     /* These values are provided by setUp() */
     54     private SmtpSender mSender = null;
     55 
     56     /* Simple test string and its base64 equivalent */
     57     private final static String TEST_STRING = "Hello, world";
     58     private final static String TEST_STRING_BASE64 = "SGVsbG8sIHdvcmxk";
     59 
     60     /**
     61      * Setup code.  We generate a lightweight SmtpSender for testing.
     62      */
     63     @Override
     64     protected void setUp() throws Exception {
     65         super.setUp();
     66         mProviderContext = DBTestHelper.ProviderContextSetupHelper.getProviderContext(
     67                 getContext());
     68         mContext = getContext();
     69 
     70         HostAuth testAuth = new HostAuth();
     71         Account testAccount = new Account();
     72 
     73         testAuth.setLogin("user", "password");
     74         testAuth.setConnection("smtp", "server", 999);
     75         testAccount.mHostAuthSend = testAuth;
     76         mSender = (SmtpSender) SmtpSender.newInstance(testAccount, mProviderContext);
     77     }
     78 
     79     /**
     80      * Confirms simple non-SSL non-TLS login
     81      */
     82     public void testSimpleLogin() throws Exception {
     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 Exception {
    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);
    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      * Prepare to send a simple message (see setReceiveSimpleMessage)
    163      */
    164     private Message setupSimpleMessage() {
    165         Message message = new Message();
    166         message.mTimeStamp = System.currentTimeMillis();
    167         message.mFrom = Address.parseAndPack("Jones (at) Registry.Org");
    168         message.mTo = Address.parseAndPack("Smith (at) Registry.Org");
    169         message.mMessageId = "1234567890";
    170         return message;
    171     }
    172 
    173     /**
    174      * Prepare to receive a simple message (see setupSimpleMessage)
    175      */
    176     private void expectSimpleMessage(MockTransport mockTransport) {
    177         mockTransport.expect("MAIL FROM: <Jones (at) Registry.Org>",
    178                 "250 2.1.0 <Jones (at) Registry.Org> sender ok");
    179         mockTransport.expect("RCPT TO: <Smith (at) Registry.Org>",
    180                 "250 2.1.5 <Smith (at) Registry.Org> recipient ok");
    181         mockTransport.expect("DATA", "354 enter mail, end with . on a line by itself");
    182         mockTransport.expect("Date: .*");
    183         mockTransport.expect("Message-ID: .*");
    184         mockTransport.expect("From: Jones (at) Registry.Org");
    185         mockTransport.expect("To: Smith (at) Registry.Org");
    186         mockTransport.expect("MIME-Version: 1.0");
    187     }
    188 
    189     /**
    190      * Prepare to send a simple attachment
    191      */
    192     private Attachment setupSimpleAttachment(Context context, long messageId) {
    193         Attachment attachment = new Attachment();
    194         attachment.mFileName = "the file.jpg";
    195         attachment.mMimeType = "image/jpg";
    196         attachment.mSize = 0;
    197         attachment.mContentId = null;
    198         attachment.mContentUri = "content://com.android.email/1/1";
    199         attachment.mMessageKey = messageId;
    200         attachment.mLocation = null;
    201         attachment.mEncoding = null;
    202 
    203         return attachment;
    204     }
    205 
    206     /**
    207      * Prepare to receive a simple attachment (note, no multipart support here)
    208      */
    209     private void expectSimpleAttachment(MockTransport mockTransport, Attachment attachment) {
    210         mockTransport.expect("Content-Type: " + attachment.mMimeType + ";");
    211         mockTransport.expect(" name=\"" + attachment.mFileName + "\"");
    212         mockTransport.expect("Content-Transfer-Encoding: base64");
    213         mockTransport.expect("Content-Disposition: attachment;");
    214         mockTransport.expect(" filename=\"" + attachment.mFileName + "\";");
    215         mockTransport.expect(" size=" + Long.toString(attachment.mSize));
    216         mockTransport.expect("");
    217         if (attachment.mContentUri != null && attachment.mContentUri.startsWith("file://")) {
    218             mockTransport.expect(TEST_STRING_BASE64);
    219         }
    220     }
    221 
    222     /**
    223      * Test:  Recover from a server closing early (or returning an empty string)
    224      */
    225     public void testEmptyLineResponse() throws Exception {
    226         MockTransport mockTransport = openAndInjectMockTransport();
    227 
    228         // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
    229         mockTransport.expectClose();
    230 
    231         // Load up just the bare minimum to expose the error
    232         mockTransport.expect(null, "220 MockTransport 2000 Ready To Assist You Peewee");
    233         mockTransport.expectLiterally("EHLO [" + LOCAL_ADDRESS + "]", null);
    234 
    235         // Now trigger the transmission
    236         // Note, a null message is sufficient here, as we won't even get past open()
    237         try {
    238             mSender.sendMessage(-1);
    239             fail("Should not be able to send with failed open()");
    240         } catch (MessagingException me) {
    241             // good - expected
    242             // TODO maybe expect a particular exception?
    243         }
    244     }
    245 
    246     /**
    247      * Set up a basic MockTransport. open it, and inject it into mStore
    248      */
    249     private MockTransport openAndInjectMockTransport() throws UnknownHostException {
    250         // Create mock transport and inject it into the SmtpSender that's already set up
    251         MockTransport mockTransport = new MockTransport();
    252         mockTransport.setSecurity(Transport.CONNECTION_SECURITY_NONE, false);
    253         mSender.setTransport(mockTransport);
    254         mockTransport.setMockLocalAddress(InetAddress.getByName(LOCAL_ADDRESS));
    255         return mockTransport;
    256     }
    257 
    258     /**
    259      * Helper which stuffs the mock with enough strings to satisfy a call to SmtpSender.open()
    260      *
    261      * @param mockTransport the mock transport we're using
    262      * @param capabilities if non-null, comma-separated list of capabilities
    263      */
    264     private void setupOpen(MockTransport mockTransport, String capabilities) {
    265         mockTransport.expect(null, "220 MockTransport 2000 Ready To Assist You Peewee");
    266         mockTransport.expect("EHLO .*", "250-10.20.30.40 hello");
    267         if (capabilities == null) {
    268             mockTransport.expect(null, "250-HELP");
    269             mockTransport.expect(null, "250-AUTH LOGIN PLAIN CRAM-MD5");
    270             mockTransport.expect(null, "250-SIZE 15728640");
    271             mockTransport.expect(null, "250-ENHANCEDSTATUSCODES");
    272             mockTransport.expect(null, "250-8BITMIME");
    273         } else {
    274             for (String capability : capabilities.split(",")) {
    275                 mockTransport.expect(null, "250-" + capability);
    276             }
    277         }
    278         mockTransport.expect(null, "250+OK");
    279         mockTransport.expect("AUTH PLAIN .*", "235 2.7.0 ... authentication succeeded");
    280     }
    281 }
    282