com.mwebster.iemail.mail.transport.SmtpSenderUnitTests.java Source code

Java tutorial

Introduction

Here is the source code for com.mwebster.iemail.mail.transport.SmtpSenderUnitTests.java

Source

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.mwebster.iemail.mail.transport;

import com.mwebster.iemail.mail.Address;
import com.mwebster.iemail.mail.MessagingException;
import com.mwebster.iemail.mail.Transport;
import com.mwebster.iemail.provider.EmailProvider;
import com.mwebster.iemail.provider.EmailContent.Attachment;
import com.mwebster.iemail.provider.EmailContent.Body;
import com.mwebster.iemail.provider.EmailContent.Message;

import org.apache.commons.io.IOUtils;

import android.content.Context;
import android.test.ProviderTestCase2;
import android.test.suitebuilder.annotation.SmallTest;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * This is a series of unit tests for the SMTP Sender class.  These tests must be locally
 * complete - no server(s) required.
 *
 * These tests can be run with the following command:
 *   runtest -c com.mwebster.iemail.mail.transport.SmtpSenderUnitTests email
 */
@SmallTest
public class SmtpSenderUnitTests extends ProviderTestCase2<EmailProvider> {

    EmailProvider mProvider;
    Context mProviderContext;
    Context mContext;

    /* These values are provided by setUp() */
    private SmtpSender mSender = null;

    /* Simple test string and its base64 equivalent */
    private final static String TEST_STRING = "Hello, world";
    private final static String TEST_STRING_BASE64 = "SGVsbG8sIHdvcmxk";

    public SmtpSenderUnitTests() {
        super(EmailProvider.class, EmailProvider.EMAIL_AUTHORITY);
    }

    /**
     * Setup code.  We generate a lightweight SmtpSender for testing.
     */
    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mProviderContext = getMockContext();
        mContext = getContext();

        // These are needed so we can get at the inner classes
        mSender = (SmtpSender) SmtpSender.newInstance(mProviderContext, "smtp://user:password@server:999");
    }

    /**
     * Confirms simple non-SSL non-TLS login
     */
    public void testSimpleLogin() throws MessagingException {

        MockTransport mockTransport = openAndInjectMockTransport();

        // try to open it
        setupOpen(mockTransport, null);
        mSender.open();
    }

    /**
     * TODO: Test with SSL negotiation (faked)
     * TODO: Test with SSL required but not supported
     * TODO: Test other capabilities.
     * TODO: Test AUTH LOGIN
     */

    /**
     * Confirms TLS login
     */
    public void testTlsLogin() throws MessagingException {

        // test with "250-STARTTLS"
        MockTransport mockDash = new MockTransport();
        mockDash.setSecurity(Transport.CONNECTION_SECURITY_TLS, false);
        mockDash.setTlsAllowed(true);
        mSender.setTransport(mockDash);

        // try to open it
        mockDash.expect(null, "220 MockTransport 2000 Ready To Assist You Peewee");
        mockDash.expect("EHLO .*", "250-10.20.30.40 hello");
        mockDash.expect(null, "250-STARTTLS");
        mockDash.expect(null, "250 AUTH LOGIN PLAIN CRAM-MD5");
        mockDash.expect("STARTTLS", "220 Ready to start TLS");
        mockDash.expect("EHLO .*", "250-10.20.30.40 hello");
        mockDash.expect(null, "250-STARTTLS");
        mockDash.expect(null, "250 AUTH LOGIN PLAIN CRAM-MD5");
        mockDash.expect("AUTH PLAIN .*", "235 2.7.0 ... authentication succeeded");
        mSender.open();
        assertTrue("dash", mockDash.getTlsReopened());

        // test with "250 STARTTLS"
        MockTransport mockSpace = new MockTransport();
        mockSpace.setSecurity(Transport.CONNECTION_SECURITY_TLS, false);
        mockSpace.setTlsAllowed(true);
        mSender.setTransport(mockSpace);

        // try to open it
        mockSpace.expect(null, "220 MockTransport 2000 Ready To Assist You Peewee");
        mockSpace.expect("EHLO .*", "250-10.20.30.40 hello");
        mockSpace.expect(null, "250-AUTH LOGIN PLAIN CRAM-MD5");
        mockSpace.expect(null, "250 STARTTLS");
        mockSpace.expect("STARTTLS", "220 Ready to start TLS");
        mockSpace.expect("EHLO .*", "250-10.20.30.40 hello");
        mockSpace.expect(null, "250-AUTH LOGIN PLAIN CRAM-MD5");
        mockSpace.expect(null, "250 STARTTLS");
        mockSpace.expect("AUTH PLAIN .*", "235 2.7.0 ... authentication succeeded");
        mSender.open();
        assertTrue("space", mockSpace.getTlsReopened());
    }

    /**
     * Confirms TLS required but not supported
     */
    public void testTlsRequiredNotSupported() throws MessagingException {
        MockTransport mockTransport = new MockTransport();
        mockTransport.setSecurity(Transport.CONNECTION_SECURITY_TLS, false);
        mSender.setTransport(mockTransport);

        // try to open it
        mockTransport.expect(null, "220 MockTransport 2000 Ready To Assist You Peewee");
        mockTransport.expect("EHLO .*", "");
        setupOpen(mockTransport, "");
        try {
            mSender.open();
            fail("Should not be able to open() without TLS.");
        } catch (MessagingException me) {
            // good - expected
        }
    }

    /**
     * Test:  Open and send a single message (sunny day)
     */
    public void testSendMessageWithBody() throws MessagingException {
        MockTransport mockTransport = openAndInjectMockTransport();

        // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
        mockTransport.expectClose();
        setupOpen(mockTransport, null);

        Message message = setupSimpleMessage();
        message.save(mProviderContext);

        Body body = new Body();
        body.mMessageKey = message.mId;
        body.mTextContent = TEST_STRING;
        body.save(mProviderContext);

        // prepare for the message traffic we'll see
        // TODO The test is a bit fragile, as we are order-dependent (and headers are not)
        expectSimpleMessage(mockTransport);
        mockTransport.expect("Content-Type: text/plain; charset=utf-8");
        mockTransport.expect("Content-Transfer-Encoding: base64");
        mockTransport.expect("");
        mockTransport.expect(TEST_STRING_BASE64);
        mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery");

        // Now trigger the transmission
        mSender.sendMessage(message.mId);
    }

    /**
     * Test:  Open and send a single message with an empty attachment (no file) (sunny day)
     */
    public void testSendMessageWithEmptyAttachment() throws MessagingException, IOException {
        MockTransport mockTransport = openAndInjectMockTransport();

        // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
        mockTransport.expectClose();
        setupOpen(mockTransport, null);

        Message message = setupSimpleMessage();
        message.save(mProviderContext);

        // Creates an attachment with a bogus file (so we get headers only)
        Attachment attachment = setupSimpleAttachment(mProviderContext, message.mId, false);
        attachment.save(mProviderContext);

        expectSimpleMessage(mockTransport);
        mockTransport.expect("Content-Type: multipart/mixed; boundary=\".*");
        mockTransport.expect("");
        mockTransport.expect("----.*");
        expectSimpleAttachment(mockTransport, attachment);
        mockTransport.expect("");
        mockTransport.expect("----.*--");
        mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery");

        // Now trigger the transmission
        mSender.sendMessage(message.mId);
    }

    /**
     * Test:  Open and send a single message with an attachment (sunny day)
     */
    public void testSendMessageWithAttachment() throws MessagingException, IOException {
        MockTransport mockTransport = openAndInjectMockTransport();

        // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
        mockTransport.expectClose();
        setupOpen(mockTransport, null);

        Message message = setupSimpleMessage();
        message.save(mProviderContext);

        // Creates an attachment with a real file
        Attachment attachment = setupSimpleAttachment(mProviderContext, message.mId, true);
        attachment.save(mProviderContext);

        expectSimpleMessage(mockTransport);
        mockTransport.expect("Content-Type: multipart/mixed; boundary=\".*");
        mockTransport.expect("");
        mockTransport.expect("----.*");
        expectSimpleAttachment(mockTransport, attachment);
        mockTransport.expect("");
        mockTransport.expect("----.*--");
        mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery");

        // Now trigger the transmission
        mSender.sendMessage(message.mId);
    }

    /**
     * Test:  Open and send a single message with two attachments
     */
    public void testSendMessageWithTwoAttachments() throws MessagingException, IOException {
        MockTransport mockTransport = openAndInjectMockTransport();

        // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
        mockTransport.expectClose();
        setupOpen(mockTransport, null);

        Message message = setupSimpleMessage();
        message.save(mProviderContext);

        // Creates an attachment with a real file
        Attachment attachment = setupSimpleAttachment(mProviderContext, message.mId, true);
        attachment.save(mProviderContext);

        // Creates an attachment with a real file
        Attachment attachment2 = setupSimpleAttachment(mProviderContext, message.mId, true);
        attachment2.save(mProviderContext);

        expectSimpleMessage(mockTransport);
        mockTransport.expect("Content-Type: multipart/mixed; boundary=\".*");
        mockTransport.expect("");
        mockTransport.expect("----.*");
        expectSimpleAttachment(mockTransport, attachment);
        mockTransport.expect("");
        mockTransport.expect("----.*");
        expectSimpleAttachment(mockTransport, attachment2);
        mockTransport.expect("");
        mockTransport.expect("----.*--");
        mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery");

        // Now trigger the transmission
        mSender.sendMessage(message.mId);
    }

    /**
     * Test:  Open and send a single message with body & attachment (sunny day)
     */
    public void testSendMessageWithBodyAndAttachment() throws MessagingException, IOException {
        MockTransport mockTransport = openAndInjectMockTransport();

        // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
        mockTransport.expectClose();
        setupOpen(mockTransport, null);

        Message message = setupSimpleMessage();
        message.save(mProviderContext);

        Body body = new Body();
        body.mMessageKey = message.mId;
        body.mTextContent = TEST_STRING;
        body.save(mProviderContext);

        Attachment attachment = setupSimpleAttachment(mProviderContext, message.mId, true);
        attachment.save(mProviderContext);

        // prepare for the message traffic we'll see
        expectSimpleMessage(mockTransport);
        mockTransport.expect("Content-Type: multipart/mixed; boundary=\".*");
        mockTransport.expect("");
        mockTransport.expect("----.*");
        mockTransport.expect("Content-Type: text/plain; charset=utf-8");
        mockTransport.expect("Content-Transfer-Encoding: base64");
        mockTransport.expect("");
        mockTransport.expect(TEST_STRING_BASE64);
        mockTransport.expect("----.*");
        expectSimpleAttachment(mockTransport, attachment);
        mockTransport.expect("");
        mockTransport.expect("----.*--");
        mockTransport.expect("\r\n\\.", "250 2.0.0 kv2f1a00C02Rf8w3Vv mail accepted for delivery");

        // Now trigger the transmission
        mSender.sendMessage(message.mId);
    }

    /**
     * Prepare to send a simple message (see setReceiveSimpleMessage)
     */
    private Message setupSimpleMessage() {
        Message message = new Message();
        message.mTimeStamp = System.currentTimeMillis();
        message.mFrom = Address.parseAndPack("Jones@Registry.Org");
        message.mTo = Address.parseAndPack("Smith@Registry.Org");
        message.mMessageId = "1234567890";
        return message;
    }

    /**
     * Prepare to receive a simple message (see setupSimpleMessage)
     */
    private void expectSimpleMessage(MockTransport mockTransport) {
        mockTransport.expect("MAIL FROM: <Jones@Registry.Org>", "250 2.1.0 <Jones@Registry.Org> sender ok");
        mockTransport.expect("RCPT TO: <Smith@Registry.Org>", "250 2.1.5 <Smith@Registry.Org> recipient ok");
        mockTransport.expect("DATA", "354 enter mail, end with . on a line by itself");
        mockTransport.expect("Date: .*");
        mockTransport.expect("Message-ID: .*");
        mockTransport.expect("From: Jones@Registry.Org");
        mockTransport.expect("To: Smith@Registry.Org");
        mockTransport.expect("MIME-Version: 1.0");
    }

    /**
     * Prepare to send a simple attachment
     */
    private Attachment setupSimpleAttachment(Context context, long messageId, boolean withBody) throws IOException {
        Attachment attachment = new Attachment();
        attachment.mFileName = "the file.jpg";
        attachment.mMimeType = "image/jpg";
        attachment.mSize = 0;
        attachment.mContentId = null;
        attachment.mContentUri = "content://com.mwebster.iemail.1/1";
        attachment.mMessageKey = messageId;
        attachment.mLocation = null;
        attachment.mEncoding = null;

        if (withBody) {
            // Is there an easier way to set up a temp file?
            InputStream inStream = new ByteArrayInputStream(TEST_STRING.getBytes());
            File cacheDir = context.getCacheDir();
            File tmpFile = File.createTempFile("setupSimpleAttachment", "tmp", cacheDir);
            OutputStream outStream = new FileOutputStream(tmpFile);

            IOUtils.copy(inStream, outStream);
            attachment.mContentUri = "file://" + tmpFile.getAbsolutePath();
        }

        return attachment;
    }

    /**
     * Prepare to receive a simple attachment (note, no multipart support here)
     */
    private void expectSimpleAttachment(MockTransport mockTransport, Attachment attachment) {
        mockTransport.expect("Content-Type: " + attachment.mMimeType + ";");
        mockTransport.expect(" name=\"" + attachment.mFileName + "\"");
        mockTransport.expect("Content-Transfer-Encoding: base64");
        mockTransport.expect("Content-Disposition: attachment;");
        mockTransport.expect(" filename=\"" + attachment.mFileName + "\";");
        mockTransport.expect(" size=" + Long.toString(attachment.mSize));
        mockTransport.expect("");
        if (attachment.mContentUri != null && attachment.mContentUri.startsWith("file://")) {
            mockTransport.expect(TEST_STRING_BASE64);
        }
    }

    /**
     * Test:  Recover from a server closing early (or returning an empty string)
     */
    public void testEmptyLineResponse() {
        MockTransport mockTransport = openAndInjectMockTransport();

        // Since SmtpSender.sendMessage() does a close then open, we need to preset for the open
        mockTransport.expectClose();

        // Load up just the bare minimum to expose the error
        mockTransport.expect(null, "220 MockTransport 2000 Ready To Assist You Peewee");
        mockTransport.expect("EHLO .*", "");

        // Now trigger the transmission
        // Note, a null message is sufficient here, as we won't even get past open()
        try {
            mSender.sendMessage(-1);
            fail("Should not be able to send with failed open()");
        } catch (MessagingException me) {
            // good - expected
            // TODO maybe expect a particular exception?
        }
    }

    /**
     * Set up a basic MockTransport. open it, and inject it into mStore
     */
    private MockTransport openAndInjectMockTransport() {
        // Create mock transport and inject it into the SmtpSender that's already set up
        MockTransport mockTransport = new MockTransport();
        mockTransport.setSecurity(Transport.CONNECTION_SECURITY_NONE, false);
        mSender.setTransport(mockTransport);
        return mockTransport;
    }

    /**
     * Helper which stuffs the mock with enough strings to satisfy a call to SmtpSender.open()
     * 
     * @param mockTransport the mock transport we're using
     * @param capabilities if non-null, comma-separated list of capabilities
     */
    private void setupOpen(MockTransport mockTransport, String capabilities) {
        mockTransport.expect(null, "220 MockTransport 2000 Ready To Assist You Peewee");
        mockTransport.expect("EHLO .*", "250-10.20.30.40 hello");
        if (capabilities == null) {
            mockTransport.expect(null, "250-HELP");
            mockTransport.expect(null, "250-AUTH LOGIN PLAIN CRAM-MD5");
            mockTransport.expect(null, "250-SIZE 15728640");
            mockTransport.expect(null, "250-ENHANCEDSTATUSCODES");
            mockTransport.expect(null, "250-8BITMIME");
        } else {
            for (String capability : capabilities.split(",")) {
                mockTransport.expect(null, "250-" + capability);
            }
        }
        mockTransport.expect(null, "250+OK");
        mockTransport.expect("AUTH PLAIN .*", "235 2.7.0 ... authentication succeeded");
    }
}