/* * 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.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 = "[email protected]"; /** Destination address for the test email */ protected String strTestMailTo = "[email protected]"; /** 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()); this.fakeMailServer.start(); 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.size() > 0) { assertTrue("got wrong Cc: address from mail", ccAdd.toString().contains(mimeMessage.getHeader("Cc", null))); } //test bcc address if (bccAdd.size() > 0) { 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; } }