View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.mail;
18  
19  import static org.easymock.EasyMock.expect;
20  import static org.powermock.api.easymock.PowerMock.createMock;
21  import static org.powermock.api.easymock.PowerMock.replay;
22  
23  import java.io.BufferedOutputStream;
24  import java.io.ByteArrayOutputStream;
25  import java.io.File;
26  import java.io.IOException;
27  import java.net.URL;
28  import java.util.Date;
29  import java.util.Enumeration;
30  import java.util.List;
31  
32  import javax.activation.DataHandler;
33  import javax.mail.Header;
34  import javax.mail.MessagingException;
35  import javax.mail.Multipart;
36  import javax.mail.internet.InternetAddress;
37  import javax.mail.internet.MimeMessage;
38  
39  import junit.framework.TestCase;
40  
41  import org.apache.commons.mail.settings.EmailConfiguration;
42  import org.subethamail.wiser.Wiser;
43  import org.subethamail.wiser.WiserMessage;
44  
45  /**
46   * Base test case for Email test classes
47   *
48   * @since 1.0
49   * @author <a href="mailto:corey.scott@gmail.com">Corey Scott</a>
50   * @author <a href="mailto:epugh@opensourceconnections.com">Eric Pugh</a>
51   * @version $Id: BaseEmailTestCase.java 1429506 2013-01-06 12:33:56Z tn $
52   */
53  public abstract class BaseEmailTestCase extends TestCase
54  {
55      /** Padding at end of body added by wiser/send */
56      public static final int BODY_END_PAD = 3;
57  
58      /** Padding at start of body added by wiser/send */
59      public static final int BODY_START_PAD = 2;
60  
61      /** Line separator in email messages */
62      private static final String LINE_SEPARATOR = "\r\n";
63  
64      /** default port */
65      private static int mailServerPort = 2500;
66  
67      /** The fake Wiser email server */
68      protected Wiser fakeMailServer;
69  
70      /** Mail server used for testing */
71      protected String strTestMailServer = "localhost";
72  
73      /** From address for the test email */
74      protected String strTestMailFrom = "test_from@apache.org";
75  
76      /** Destination address for the test email */
77      protected String strTestMailTo = "test_to@apache.org";
78  
79      /** Mailserver username (set if needed) */
80      protected String strTestUser = "user";
81  
82      /** Mailserver strTestPasswd (set if needed) */
83      protected String strTestPasswd = "password";
84  
85      /** URL to used to test URL attachmetns (Must be valid) */
86      protected String strTestURL = EmailConfiguration.TEST_URL;
87  
88      /** Test characters acceptable to email */
89      protected String[] testCharsValid =
90      {
91              " ",
92              "a",
93              "A",
94              "\uc5ec",
95              "0123456789",
96              "012345678901234567890",
97              "\n"
98      };
99  
100     /** Array of test strings */
101     protected String[] testCharsNotValid = {"", null};
102 
103     /** Where to save email output **/
104     private File emailOutputDir;
105 
106     /** counter for creating a file name */
107     private static int fileNameCounter;
108 
109     /**
110      * @param name name
111      */
112     public BaseEmailTestCase(String name)
113     {
114         super(name);
115         emailOutputDir = new File("target/test-emails");
116         if (!emailOutputDir.exists())
117         {
118             emailOutputDir.mkdirs();
119         }
120     }
121 
122     /** */
123     @Override
124     protected void tearDown()
125     {
126         //stop the fake email server (if started)
127         if (this.fakeMailServer != null && !isMailServerStopped(fakeMailServer))
128         {
129             this.fakeMailServer.stop();
130             assertTrue("Mail server didn't stop", isMailServerStopped(fakeMailServer));
131         }
132 
133         this.fakeMailServer = null;
134     }
135 
136     /**
137      * Gets the mail server port.
138      * @return the port the server is running on.
139      */
140     protected int getMailServerPort()
141     {
142         return mailServerPort;
143     }
144 
145     /**
146      * Safe a mail to a file using a more or less unique file name.
147      *
148      * @param email email
149      * @throws IOException writing the email failed
150      * @throws MessagingException writing the email failed
151      */
152     protected void saveEmailToFile(WiserMessage email) throws IOException, MessagingException
153     {
154         int currCounter = fileNameCounter++ % 10;
155         String emailFileName = "email" + new Date().getTime() + "-" + currCounter + ".eml";
156         File emailFile = new File(emailOutputDir, emailFileName);
157         EmailUtils.writeMimeMessage(emailFile, email.getMimeMessage() );
158     }
159 
160     /**
161      * @param intMsgNo the message to retrieve
162      * @return message as string
163      */
164     public String getMessageAsString(int intMsgNo)
165     {
166         List<?> receivedMessages = fakeMailServer.getMessages();
167         assertTrue("mail server didn't get enough messages", receivedMessages.size() >= intMsgNo);
168 
169         WiserMessage emailMessage = (WiserMessage) receivedMessages.get(intMsgNo);
170 
171         if (emailMessage != null)
172         {
173             try
174             {
175                 return serializeEmailMessage(emailMessage);
176             }
177             catch (Exception e)
178             {
179                 // ignore, since the test will fail on an empty string return
180             }
181         }
182         fail("Message not found");
183         return "";
184     }
185 
186     /**
187      * Initializes the stub mail server. Fails if the server cannot be proven
188      * to have started. If the server is already started, this method returns
189      * without changing the state of the server.
190      */
191     public void getMailServer()
192     {
193         if (this.fakeMailServer == null || isMailServerStopped(fakeMailServer))
194         {
195             mailServerPort++;
196 
197             this.fakeMailServer = new Wiser();
198             this.fakeMailServer.setPort(getMailServerPort());
199             this.fakeMailServer.start();
200 
201             assertFalse("fake mail server didn't start", isMailServerStopped(fakeMailServer));
202 
203             Date dtStartWait = new Date();
204             while (isMailServerStopped(fakeMailServer))
205             {
206                 // test for connected
207                 if (this.fakeMailServer != null
208                     && !isMailServerStopped(fakeMailServer))
209                 {
210                     break;
211                 }
212 
213                 // test for timeout
214                 if ((dtStartWait.getTime() + EmailConfiguration.TIME_OUT)
215                     <= new Date().getTime())
216                 {
217                     fail("Mail server failed to start");
218                 }
219             }
220         }
221     }
222 
223     /**
224      * Validate the message was sent properly
225      * @param mailServer reference to the fake mail server
226      * @param strSubject expected subject
227      * @param fromAdd expected from address
228      * @param toAdd list of expected to addresses
229      * @param ccAdd list of expected cc addresses
230      * @param bccAdd list of expected bcc addresses
231      * @param boolSaveToFile true will output to file, false doesnt
232      * @return WiserMessage email to check
233      * @throws IOException Exception
234      */
235     protected WiserMessage validateSend(
236         Wiser mailServer,
237         String strSubject,
238         InternetAddress fromAdd,
239         List<InternetAddress> toAdd,
240         List<InternetAddress> ccAdd,
241         List<InternetAddress> bccAdd,
242         boolean boolSaveToFile)
243         throws IOException
244     {
245         assertTrue("mail server doesn't contain expected message",
246                 mailServer.getMessages().size() == 1);
247         WiserMessage emailMessage = mailServer.getMessages().get(0);
248 
249         if (boolSaveToFile)
250         {
251             try
252             {
253                 this.saveEmailToFile(emailMessage);
254             }
255             catch(MessagingException me)
256             {
257                 IllegalStateException ise =
258                     new IllegalStateException("caught MessagingException during saving the email");
259                 ise.initCause(me);
260                 throw ise;
261             }
262         }
263 
264         try
265         {
266             // get the MimeMessage
267             MimeMessage mimeMessage = emailMessage.getMimeMessage();
268 
269             // test subject
270             assertEquals("got wrong subject from mail",
271                     strSubject, mimeMessage.getHeader("Subject", null));
272 
273             //test from address
274             assertEquals("got wrong From: address from mail",
275                     fromAdd.toString(), mimeMessage.getHeader("From", null));
276 
277             //test to address
278             assertTrue("got wrong To: address from mail",
279                 toAdd.toString().indexOf(mimeMessage.getHeader("To", null)) != -1);
280 
281             //test cc address
282             if (ccAdd.size() > 0)
283             {
284                 assertTrue("got wrong Cc: address from mail",
285                     ccAdd.toString().indexOf(mimeMessage.getHeader("Cc", null))
286                         != -1);
287             }
288 
289             //test bcc address
290             if (bccAdd.size() > 0)
291             {
292                 assertTrue("got wrong Bcc: address from mail",
293                     bccAdd.toString().indexOf(mimeMessage.getHeader("Bcc", null))
294                         != -1);
295             }
296         }
297         catch (MessagingException me)
298         {
299             IllegalStateException ise =
300                 new IllegalStateException("caught MessagingException in validateSend()");
301             ise.initCause(me);
302             throw ise;
303         }
304 
305         return emailMessage;
306     }
307 
308     /**
309      * Validate the message was sent properly
310      * @param mailServer reference to the fake mail server
311      * @param strSubject expected subject
312      * @param content the expected message content
313      * @param fromAdd expected from address
314      * @param toAdd list of expected to addresses
315      * @param ccAdd list of expected cc addresses
316      * @param bccAdd list of expected bcc addresses
317      * @param boolSaveToFile true will output to file, false doesnt
318      * @throws IOException Exception
319      */
320     protected void validateSend(
321         Wiser mailServer,
322         String strSubject,
323         Multipart content,
324         InternetAddress fromAdd,
325         List<InternetAddress> toAdd,
326         List<InternetAddress> ccAdd,
327         List<InternetAddress> bccAdd,
328         boolean boolSaveToFile)
329         throws IOException
330     {
331         // test other properties
332         WiserMessage emailMessage = this.validateSend(
333             mailServer,
334             strSubject,
335             fromAdd,
336             toAdd,
337             ccAdd,
338             bccAdd,
339             boolSaveToFile);
340 
341         // test message content
342 
343         // get sent email content
344         String strSentContent =
345             content.getContentType();
346         // get received email content (chop off the auto-added \n
347         // and -- (front and end)
348         String emailMessageBody = getMessageBody(emailMessage);
349         String strMessageBody =
350             emailMessageBody.substring(BaseEmailTestCase.BODY_START_PAD,
351                                        emailMessageBody.length()
352                                        - BaseEmailTestCase.BODY_END_PAD);
353         assertTrue("didn't find expected content type in message body",
354                 strMessageBody.indexOf(strSentContent) != -1);
355     }
356 
357     /**
358      * Validate the message was sent properly
359      * @param mailServer reference to the fake mail server
360      * @param strSubject expected subject
361      * @param strMessage the expected message as a string
362      * @param fromAdd expected from address
363      * @param toAdd list of expected to addresses
364      * @param ccAdd list of expected cc addresses
365      * @param bccAdd list of expected bcc addresses
366      * @param boolSaveToFile true will output to file, false doesnt
367      * @throws IOException Exception
368      */
369     protected void validateSend(
370         Wiser mailServer,
371         String strSubject,
372         String strMessage,
373         InternetAddress fromAdd,
374         List<InternetAddress> toAdd,
375         List<InternetAddress> ccAdd,
376         List<InternetAddress> bccAdd,
377         boolean boolSaveToFile)
378         throws IOException
379     {
380         // test other properties
381         WiserMessage emailMessage = this.validateSend(
382             mailServer,
383             strSubject,
384             fromAdd,
385             toAdd,
386             ccAdd,
387             bccAdd,
388             true);
389 
390         // test message content
391         assertTrue("didn't find expected message content in message body",
392                 getMessageBody(emailMessage).indexOf(strMessage) != -1);
393     }
394 
395     /**
396      * Serializes the {@link MimeMessage} from the <code>WiserMessage</code>
397      * passed in. The headers are serialized first followed by the message
398      * body.
399      *
400      * @param wiserMessage The <code>WiserMessage</code> to serialize.
401      * @return The string format of the message.
402      * @throws MessagingException
403      * @throws IOException
404      *             Thrown while serializing the body from
405      *             {@link DataHandler#writeTo(java.io.OutputStream)}.
406      * @throws MessagingException
407      *             Thrown while getting the body content from
408      *             {@link MimeMessage#getDataHandler()}
409      * @since 1.1
410      */
411     private String serializeEmailMessage(WiserMessage wiserMessage)
412             throws MessagingException, IOException
413     {
414         if (wiserMessage == null)
415         {
416             return "";
417         }
418 
419         StringBuffer serializedEmail = new StringBuffer();
420         MimeMessage message = wiserMessage.getMimeMessage();
421 
422         // Serialize the headers
423         for (Enumeration<?> headers = message.getAllHeaders(); headers
424                 .hasMoreElements();)
425         {
426             Header header = (Header) headers.nextElement();
427             serializedEmail.append(header.getName());
428             serializedEmail.append(": ");
429             serializedEmail.append(header.getValue());
430             serializedEmail.append(LINE_SEPARATOR);
431         }
432 
433         // Serialize the body
434         byte[] messageBody = getMessageBodyBytes(message);
435 
436         serializedEmail.append(LINE_SEPARATOR);
437         serializedEmail.append(messageBody);
438         serializedEmail.append(LINE_SEPARATOR);
439 
440         return serializedEmail.toString();
441     }
442 
443     /**
444      * Returns a string representation of the message body. If the message body
445      * cannot be read, an empty string is returned.
446      *
447      * @param wiserMessage The wiser message from which to extract the message body
448      * @return The string representation of the message body
449      * @throws IOException
450      *             Thrown while serializing the body from
451      *             {@link DataHandler#writeTo(java.io.OutputStream)}.
452      * @since 1.1
453      */
454     private String getMessageBody(WiserMessage wiserMessage)
455             throws IOException
456     {
457         if (wiserMessage == null)
458         {
459             return "";
460         }
461 
462         byte[] messageBody = null;
463 
464         try
465         {
466             MimeMessage message = wiserMessage.getMimeMessage();
467             messageBody = getMessageBodyBytes(message);
468         }
469         catch (MessagingException me)
470         {
471             // Thrown while getting the body content from
472             // {@link MimeMessage#getDataHandler()}
473             IllegalStateException ise =
474                 new IllegalStateException("couldn't process MimeMessage from WiserMessage in getMessageBody()");
475             ise.initCause(me);
476             throw ise;
477         }
478 
479         return (messageBody != null) ? (new String(messageBody).intern()) : "";
480     }
481 
482     /**
483      * Gets the byte making up the body of the message.
484      *
485      * @param mimeMessage
486      *            The mime message from which to extract the body.
487      * @return A byte array representing the message body
488      * @throws IOException
489      *             Thrown while serializing the body from
490      *             {@link DataHandler#writeTo(java.io.OutputStream)}.
491      * @throws MessagingException
492      *             Thrown while getting the body content from
493      *             {@link MimeMessage#getDataHandler()}
494      * @since 1.1
495      */
496     private byte[] getMessageBodyBytes(MimeMessage mimeMessage)
497             throws IOException, MessagingException
498     {
499         DataHandler dataHandler = mimeMessage.getDataHandler();
500         ByteArrayOutputStream byteArrayOutStream = new ByteArrayOutputStream();
501         BufferedOutputStream buffOs = new BufferedOutputStream(
502                 byteArrayOutStream);
503         dataHandler.writeTo(buffOs);
504         buffOs.flush();
505 
506         return byteArrayOutStream.toByteArray();
507     }
508 
509     /**
510      * Checks if an email server is running at the address stored in the
511      * <code>fakeMailServer</code>.
512      *
513      * @param fakeMailServer
514      *            The server from which the address is picked up.
515      * @return <code>true</code> if the server claims to be running
516      * @since 1.1
517      */
518     protected boolean isMailServerStopped(Wiser fakeMailServer) {
519         return !fakeMailServer.getServer().isRunning();
520     }
521     
522     /**
523      * Create a mocked URL object which always throws an IOException
524      * when the openStream() method is called.
525      * <p>
526      * Several ISPs do resolve invalid URLs like {@code http://example.invalid}
527      * to some error page causing tests to fail otherwise.
528      *
529      * @return an invalid URL
530      */
531     protected URL createInvalidURL() throws Exception {
532         URL url = createMock(URL.class);
533         expect(url.openStream()).andThrow(new IOException());
534         replay(url);
535         
536         return url;
537     }
538 }