blob: a9dfb06ccf129df485ad8726581511de2284599f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.commons.mail;
import static org.easymock.EasyMock.expect;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.*;
import static org.powermock.api.easymock.PowerMock.createMock;
import static org.powermock.api.easymock.PowerMock.replay;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.BindException;
import java.net.URL;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import javax.activation.DataHandler;
import javax.mail.Header;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import org.apache.commons.mail.settings.EmailConfiguration;
import org.junit.After;
import org.junit.Before;
import org.subethamail.wiser.Wiser;
import org.subethamail.wiser.WiserMessage;
/**
* Base test case for Email test classes.
*
* @since 1.0
*/
public abstract class AbstractEmailTest
{
/** Padding at end of body added by wiser/send */
public static final int BODY_END_PAD = 3;
/** Padding at start of body added by wiser/send */
public static final int BODY_START_PAD = 2;
/** Line separator in email messages */
private static final String LINE_SEPARATOR = "\r\n";
/** default port */
private static int mailServerPort = 2500;
/** The fake Wiser email server */
protected Wiser fakeMailServer;
/** Mail server used for testing */
protected String strTestMailServer = "localhost";
/** From address for the test email */
protected String strTestMailFrom = "test_from@apache.org";
/** Destination address for the test email */
protected String strTestMailTo = "test_to@apache.org";
/** Mailserver username (set if needed) */
protected String strTestUser = "user";
/** Mailserver strTestPasswd (set if needed) */
protected String strTestPasswd = "password";
/** URL to used to test URL attachments (Must be valid) */
protected String strTestURL = EmailConfiguration.TEST_URL;
/** Test characters acceptable to email */
protected String[] testCharsValid =
{
" ",
"a",
"A",
"\uc5ec",
"0123456789",
"012345678901234567890"
};
/** Test characters not acceptable to email */
protected String[] endOfLineCombinations =
{
"\n",
"\r",
"\r\n",
"\n\r",
};
/** Array of test strings */
protected String[] testCharsNotValid = {"", null};
/** Where to save email output **/
private File emailOutputDir;
/** counter for creating a file name */
private static int fileNameCounter;
@Before
public void setUpAbstractEmailTest()
{
emailOutputDir = new File("target/test-emails");
if (!emailOutputDir.exists())
{
emailOutputDir.mkdirs();
}
}
@After
public void tearDownEmailTest()
{
//stop the fake email server (if started)
if (this.fakeMailServer != null && !isMailServerStopped(fakeMailServer))
{
this.fakeMailServer.stop();
assertTrue("Mail server didn't stop", isMailServerStopped(fakeMailServer));
}
this.fakeMailServer = null;
}
/**
* Gets the mail server port.
* @return the port the server is running on.
*/
protected int getMailServerPort()
{
return mailServerPort;
}
/**
* Safe a mail to a file using a more or less unique file name.
*
* @param email email
* @throws IOException writing the email failed
* @throws MessagingException writing the email failed
*/
protected void saveEmailToFile(final WiserMessage email) throws IOException, MessagingException
{
final int currCounter = fileNameCounter++ % 10;
final String emailFileName = "email" + new Date().getTime() + "-" + currCounter + ".eml";
final File emailFile = new File(emailOutputDir, emailFileName);
EmailUtils.writeMimeMessage(emailFile, email.getMimeMessage() );
}
/**
* @param intMsgNo the message to retrieve
* @return message as string
*/
public String getMessageAsString(final int intMsgNo)
{
final List<?> receivedMessages = fakeMailServer.getMessages();
assertTrue("mail server didn't get enough messages", receivedMessages.size() >= intMsgNo);
final WiserMessage emailMessage = (WiserMessage) receivedMessages.get(intMsgNo);
if (emailMessage != null)
{
try
{
return serializeEmailMessage(emailMessage);
}
catch (final Exception e)
{
// ignore, since the test will fail on an empty string return
}
}
fail("Message not found");
return "";
}
/**
* Initializes the stub mail server. Fails if the server cannot be proven
* to have started. If the server is already started, this method returns
* without changing the state of the server.
*/
public void getMailServer()
{
if (this.fakeMailServer == null || isMailServerStopped(fakeMailServer))
{
mailServerPort++;
this.fakeMailServer = new Wiser();
this.fakeMailServer.setPort(getMailServerPort());
try {
this.fakeMailServer.start();
} catch (RuntimeException e) {
if (e.getCause() != null && e.getCause() instanceof BindException) {
throw new IllegalStateException("Port " + getMailServerPort()
+ " is already in use.");
}
}
assertFalse("fake mail server didn't start", isMailServerStopped(fakeMailServer));
final Date dtStartWait = new Date();
while (isMailServerStopped(fakeMailServer))
{
// test for connected
if (this.fakeMailServer != null
&& !isMailServerStopped(fakeMailServer))
{
break;
}
// test for timeout
if (dtStartWait.getTime() + EmailConfiguration.TIME_OUT
<= new Date().getTime())
{
fail("Mail server failed to start");
}
}
}
}
/**
* Validate the message was sent properly
* @param mailServer reference to the fake mail server
* @param strSubject expected subject
* @param fromAdd expected from address
* @param toAdd list of expected to addresses
* @param ccAdd list of expected cc addresses
* @param bccAdd list of expected bcc addresses
* @param boolSaveToFile true will output to file, false doesnt
* @return WiserMessage email to check
* @throws IOException Exception
*/
protected WiserMessage validateSend(
final Wiser mailServer,
final String strSubject,
final InternetAddress fromAdd,
final List<InternetAddress> toAdd,
final List<InternetAddress> ccAdd,
final List<InternetAddress> bccAdd,
final boolean boolSaveToFile)
throws IOException
{
assertTrue("mail server doesn't contain expected message",
mailServer.getMessages().size() == 1);
final WiserMessage emailMessage = mailServer.getMessages().get(0);
if (boolSaveToFile)
{
try
{
this.saveEmailToFile(emailMessage);
}
catch(final MessagingException me)
{
final IllegalStateException ise =
new IllegalStateException("caught MessagingException during saving the email");
ise.initCause(me);
throw ise;
}
}
try
{
// get the MimeMessage
final MimeMessage mimeMessage = emailMessage.getMimeMessage();
// test subject
assertEquals("got wrong subject from mail",
strSubject, mimeMessage.getHeader("Subject", null));
//test from address
assertEquals("got wrong From: address from mail",
fromAdd.toString(), mimeMessage.getHeader("From", null));
//test to address
assertTrue("got wrong To: address from mail",
toAdd.toString().contains(mimeMessage.getHeader("To", null)));
//test cc address
if (!ccAdd.isEmpty())
{
assertTrue("got wrong Cc: address from mail",
ccAdd.toString().contains(mimeMessage.getHeader("Cc", null)));
}
//test bcc address
if (!bccAdd.isEmpty())
{
assertTrue("got wrong Bcc: address from mail",
bccAdd.toString().contains(mimeMessage.getHeader("Bcc", null)));
}
}
catch (final MessagingException me)
{
final IllegalStateException ise =
new IllegalStateException("caught MessagingException in validateSend()");
ise.initCause(me);
throw ise;
}
return emailMessage;
}
/**
* Validate the message was sent properly
* @param mailServer reference to the fake mail server
* @param strSubject expected subject
* @param content the expected message content
* @param fromAdd expected from address
* @param toAdd list of expected to addresses
* @param ccAdd list of expected cc addresses
* @param bccAdd list of expected bcc addresses
* @param boolSaveToFile true will output to file, false doesnt
* @throws IOException Exception
*/
protected void validateSend(
final Wiser mailServer,
final String strSubject,
final Multipart content,
final InternetAddress fromAdd,
final List<InternetAddress> toAdd,
final List<InternetAddress> ccAdd,
final List<InternetAddress> bccAdd,
final boolean boolSaveToFile)
throws IOException
{
// test other properties
final WiserMessage emailMessage = this.validateSend(
mailServer,
strSubject,
fromAdd,
toAdd,
ccAdd,
bccAdd,
boolSaveToFile);
// test message content
// get sent email content
final String strSentContent =
content.getContentType();
// get received email content (chop off the auto-added \n
// and -- (front and end)
final String emailMessageBody = getMessageBody(emailMessage);
final String strMessageBody =
emailMessageBody.substring(AbstractEmailTest.BODY_START_PAD,
emailMessageBody.length()
- AbstractEmailTest.BODY_END_PAD);
assertTrue("didn't find expected content type in message body",
strMessageBody.contains(strSentContent));
}
/**
* Validate the message was sent properly
* @param mailServer reference to the fake mail server
* @param strSubject expected subject
* @param strMessage the expected message as a string
* @param fromAdd expected from address
* @param toAdd list of expected to addresses
* @param ccAdd list of expected cc addresses
* @param bccAdd list of expected bcc addresses
* @param boolSaveToFile true will output to file, false doesnt
* @throws IOException Exception
*/
protected void validateSend(
final Wiser mailServer,
final String strSubject,
final String strMessage,
final InternetAddress fromAdd,
final List<InternetAddress> toAdd,
final List<InternetAddress> ccAdd,
final List<InternetAddress> bccAdd,
final boolean boolSaveToFile)
throws IOException
{
// test other properties
final WiserMessage emailMessage = this.validateSend(
mailServer,
strSubject,
fromAdd,
toAdd,
ccAdd,
bccAdd,
true);
// test message content
assertThat("didn't find expected message content in message body",
getMessageBody(emailMessage), containsString(strMessage));
}
/**
* Serializes the {@link MimeMessage} from the {@code WiserMessage}
* passed in. The headers are serialized first followed by the message
* body.
*
* @param wiserMessage The {@code WiserMessage} to serialize.
* @return The string format of the message.
* @throws MessagingException
* @throws IOException
* Thrown while serializing the body from
* {@link DataHandler#writeTo(java.io.OutputStream)}.
* @throws MessagingException
* Thrown while getting the body content from
* {@link MimeMessage#getDataHandler()}
* @since 1.1
*/
private String serializeEmailMessage(final WiserMessage wiserMessage)
throws MessagingException, IOException
{
if (wiserMessage == null)
{
return "";
}
final StringBuffer serializedEmail = new StringBuffer();
final MimeMessage message = wiserMessage.getMimeMessage();
// Serialize the headers
for (final Enumeration<?> headers = message.getAllHeaders(); headers
.hasMoreElements();)
{
final Header header = (Header) headers.nextElement();
serializedEmail.append(header.getName());
serializedEmail.append(": ");
serializedEmail.append(header.getValue());
serializedEmail.append(LINE_SEPARATOR);
}
// Serialize the body
final byte[] messageBody = getMessageBodyBytes(message);
serializedEmail.append(LINE_SEPARATOR);
serializedEmail.append(messageBody);
serializedEmail.append(LINE_SEPARATOR);
return serializedEmail.toString();
}
/**
* Returns a string representation of the message body. If the message body
* cannot be read, an empty string is returned.
*
* @param wiserMessage The wiser message from which to extract the message body
* @return The string representation of the message body
* @throws IOException
* Thrown while serializing the body from
* {@link DataHandler#writeTo(java.io.OutputStream)}.
* @since 1.1
*/
private String getMessageBody(final WiserMessage wiserMessage)
throws IOException
{
if (wiserMessage == null)
{
return "";
}
byte[] messageBody = null;
try
{
final MimeMessage message = wiserMessage.getMimeMessage();
messageBody = getMessageBodyBytes(message);
}
catch (final MessagingException me)
{
// Thrown while getting the body content from
// {@link MimeMessage#getDataHandler()}
final IllegalStateException ise =
new IllegalStateException("couldn't process MimeMessage from WiserMessage in getMessageBody()");
ise.initCause(me);
throw ise;
}
return messageBody != null ? new String(messageBody).intern() : "";
}
/**
* Gets the byte making up the body of the message.
*
* @param mimeMessage
* The mime message from which to extract the body.
* @return A byte array representing the message body
* @throws IOException
* Thrown while serializing the body from
* {@link DataHandler#writeTo(java.io.OutputStream)}.
* @throws MessagingException
* Thrown while getting the body content from
* {@link MimeMessage#getDataHandler()}
* @since 1.1
*/
private byte[] getMessageBodyBytes(final MimeMessage mimeMessage)
throws IOException, MessagingException
{
final DataHandler dataHandler = mimeMessage.getDataHandler();
final ByteArrayOutputStream byteArrayOutStream = new ByteArrayOutputStream();
final BufferedOutputStream buffOs = new BufferedOutputStream(
byteArrayOutStream);
dataHandler.writeTo(buffOs);
buffOs.flush();
return byteArrayOutStream.toByteArray();
}
/**
* Checks if an email server is running at the address stored in the
* {@code fakeMailServer}.
*
* @param fakeMailServer
* The server from which the address is picked up.
* @return {@code true} if the server claims to be running
* @since 1.1
*/
protected boolean isMailServerStopped(final Wiser fakeMailServer) {
return !fakeMailServer.getServer().isRunning();
}
/**
* Create a mocked URL object which always throws an IOException
* when the openStream() method is called.
* <p>
* Several ISPs do resolve invalid URLs like {@code http://example.invalid}
* to some error page causing tests to fail otherwise.
*
* @return an invalid URL
*/
protected URL createInvalidURL() throws Exception {
final URL url = createMock(URL.class);
expect(url.openStream()).andThrow(new IOException());
replay(url);
return url;
}
}