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