/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.mail.test.ui; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; import javax.mail.internet.MimeMessage; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.xwiki.administration.test.po.AdministrationPage; import org.xwiki.mail.test.po.MailStatusAdministrationSectionPage; import org.xwiki.mail.test.po.SendMailAdministrationSectionPage; import org.xwiki.test.ui.AbstractTest; import org.xwiki.test.ui.SuperAdminAuthenticationRule; import org.xwiki.test.ui.po.LiveTableElement; import org.xwiki.test.ui.po.ViewPage; import com.icegreen.greenmail.util.GreenMail; import com.icegreen.greenmail.util.ServerSetupTest; import static org.junit.Assert.*; /** * UI tests for the Mail application. * * @version $Id: e1bdd918d2191b767417731e92e688d3d7fda1c8 $ * @since 6.4M2 */ public class MailTest extends AbstractTest { @Rule public SuperAdminAuthenticationRule authenticationRule = new SuperAdminAuthenticationRule(getUtil()); private GreenMail mail; private List<String> alreadyAssertedMessages = new ArrayList<>(); @Before public void startMail() { this.mail = new GreenMail(ServerSetupTest.SMTP); this.mail.start(); } @After public void stopMail() { if (this.mail != null) { this.mail.stop(); } } @Test public void testMail() throws Exception { // Step 0: Delete all pre-existing mails to start clean. This also verifies the deleteAll() script service // API. String content = "{{velocity}}$services.mailstorage.deleteAll(){{/velocity}}"; ViewPage deleteAllPage = getUtil().createPage(getTestClassName(), "DeleteAll", content, ""); // Verify that the page doesn't display any content (unless there's an error!) assertEquals("", deleteAllPage.getContent()); // Step 1: Verify that there are 2 email sections in the Mail category AdministrationPage wikiAdministrationPage = AdministrationPage.gotoPage(); Assert.assertTrue(wikiAdministrationPage.hasSection("Mail", "Mail Sending")); Assert.assertTrue(wikiAdministrationPage.hasSection("Mail", "Mail Sending Status")); Assert.assertTrue(wikiAdministrationPage.hasSection("Mail", "Advanced")); // Verify we can click on Mail > Advanced wikiAdministrationPage.clickSection("Mail", "Advanced"); // Step 2: Before validating that we can send email, let's verify that we can report errors when the mail // setup is not correct // Make sure there's an invalid mail server set. wikiAdministrationPage.clickSection("Mail", "Mail Sending"); SendMailAdministrationSectionPage sendMailPage = new SendMailAdministrationSectionPage(); sendMailPage.setHost("invalidmailserver"); sendMailPage.clickSave(); // Send the mail that's supposed to fail and validate that it fails sendMailWithInvalidMailSetup(); // Step 3: Navigate to each mail section and set the mail sending parameters (SMTP host/port) wikiAdministrationPage = AdministrationPage.gotoPage(); wikiAdministrationPage.clickSection("Mail", "Mail Sending"); sendMailPage = new SendMailAdministrationSectionPage(); sendMailPage.setHost("localhost"); sendMailPage.setPort("3025"); // Make sure we don't wait between email sending in order to speed up the test (and not incur timeouts when // we wait to receive the mails) sendMailPage.setSendWaitTime("0"); // Keep all mail statuses including successful ones (so that we verify this works fine) sendMailPage.setDiscardSuccessStatuses(false); sendMailPage.clickSave(); // Step 3: Verify that there are no admin email sections when administering a space // Select XWiki space administration. AdministrationPage spaceAdministrationPage = AdministrationPage.gotoSpaceAdministrationPage("XWiki"); // All those sections should not be present Assert.assertTrue(spaceAdministrationPage.hasNotSection("Mail", "Mail Sending")); Assert.assertTrue(spaceAdministrationPage.hasNotSection("Mail", "Mail Sending Status")); Assert.assertTrue(spaceAdministrationPage.hasNotSection("Mail", "Advanced")); // Step 4: Prepare a Template Mail getUtil().deletePage(getTestClassName(), "MailTemplate"); // Create a Wiki page containing a Mail Template (ie a XWiki.Mail object) getUtil().createPage(getTestClassName(), "MailTemplate", "", ""); // Note: We use the following bindings in the Template subject and content so that we ensure that they are // provided by default: // - "$xwiki" // - "$xcontext" // - "$escapetool" // - "$services" // - "$request" // Note: We also use the $name and $doc bindings to show that the user can add new bindings ($doc is not bound // by default since there isn't always a notion of current doc in all places where mail sending is done). // Note: We use $xwiki.getURL() in the content to verify that we generate full external URLs. String velocityContent = "Hello $name from $escapetool.xml($services.model.resolveDocument(" + "$xcontext.getUser()).getName()) - Served from $request.getRequestURL().toString() - " + "url: $xwiki.getURL('Main.WebHome')"; getUtil().addObject(getTestClassName(), "MailTemplate", "XWiki.Mail", "subject", "#if ($xwiki.exists($doc.documentReference))Status for $name on $doc.fullName#{else}wrong#end", "language", "en", "html", "<strong>" + velocityContent + "</strong>", "text", velocityContent); // We also add an attachment to the Mail Template page to verify that it is sent in the mail ByteArrayInputStream bais = new ByteArrayInputStream("Content of attachment".getBytes()); getUtil().attachFile(getTestClassName(), "MailTemplate", "something.txt", bais, true, new UsernamePasswordCredentials("superadmin", "pass")); // Step 5: Send a template email (with an attachment) to a single email address sendTemplateMailToEmail(); // Step 6: Send a template email to all the users in the XWikiAllGroup Group (we'll create 2 users) + to // two other users (however since they're part of the group they'll receive only one mail each, we thus test // deduplicatio!). sendTemplateMailToUsersAndGroup(); // Step 7: Navigate to the Mail Sending Status Admin page and assert that the Livetable displays the entry for // the sent mails wikiAdministrationPage = AdministrationPage.gotoPage(); wikiAdministrationPage.clickSection("Mail", "Mail Sending Status"); MailStatusAdministrationSectionPage statusPage = new MailStatusAdministrationSectionPage(); LiveTableElement liveTableElement = statusPage.getLiveTable(); liveTableElement.filterColumn("xwiki-livetable-sendmailstatus-filter-3", "Test"); liveTableElement.filterColumn("xwiki-livetable-sendmailstatus-filter-5", "send_success"); liveTableElement.filterColumn("xwiki-livetable-sendmailstatus-filter-6", "xwiki"); // Let's wait till we have at least 3 rows. Note that we wait because we could have received the mails above // but the last mail's status in the database may not have been updated yet. Note that The first 2 are // guaranteed to have been updated since we send mail in one thread one after another and we update the // database after sending each mail. liveTableElement.waitUntilRowCountGreaterThan(3); liveTableElement.filterColumn("xwiki-livetable-sendmailstatus-filter-4", "john@doe.com"); assertTrue(liveTableElement.getRowCount() > 0); assertTrue(liveTableElement.hasRow("Error", "")); } private void sendMailWithInvalidMailSetup() throws Exception { // Remove existing pages (for pages that we create below) getUtil().deletePage(getTestClassName(), "SendInvalidMail"); // Create a page with the Velocity script to send the template email. // Note that we don't set the type and thus this message should not appear in the LiveTable filter at the end // of the test. String velocity = "{{velocity}}\n" + "#set ($message = $services.mailsender.createMessage('from@doe.com', 'to@doe.com', 'Subject'))\n" + "#set ($discard = $message.addPart('text/plain', 'text message'))\n" + "#set ($result = $services.mailsender.send([$message], 'database'))\n" + "#foreach ($status in $result.statusResult.getAllErrors())\n" + " MSGID $status.messageId SUMMARY $status.errorSummary DESCRIPTION $status.errorDescription\n" + "#end\n" + "{{/velocity}}"; // This will create the page and execute its content and thus send the mail ViewPage vp = getUtil().createPage(getTestClassName(), "SendInvalidMail", velocity, ""); // Verify that the page is not empty (and thus an error message is displayed). Note that it's difficult to // assert what is displayed because it could vary from system to system. This is why we only assert that // something is displayed and that it matches the defined pattern. assertTrue(vp.getContent().matches("(?s)MSGID.*SUMMARY.*DESCRIPTION.*")); } private void sendTemplateMailToEmail() throws Exception { // Remove existing pages (for pages that we create below) getUtil().deletePage(getTestClassName(), "SendMail"); // Create another page with the Velocity script to send the template email // Note that we didn't need to bind the "$doc" velocity variable because the send is done synchronously and // thus the current XWiki Context is cloned before being passed to the template evaluation, and thus it // already contains the "$doc" binding! String velocity = "{{velocity}}\n" + "#set ($templateReference = $services.model.createDocumentReference('', '" + getTestClassName() + "', 'MailTemplate'))\n" + "#set ($parameters = {'velocityVariables' : { 'name' : 'John' }, 'language' : 'en', " + "'includeTemplateAttachments' : true})\n" + "#set ($message = $services.mailsender.createMessage('template', $templateReference, $parameters))\n" + "#set ($discard = $message.setFrom('localhost@xwiki.org'))\n" + "#set ($discard = $message.addRecipients('to', 'john@doe.com'))\n" + "#set ($discard = $message.setType('Test'))\n" + "#set ($result = $services.mailsender.send([$message], 'database'))\n" + "#if ($services.mailsender.lastError)\n" + " {{error}}$exceptiontool.getStackTrace($services.mailsender.lastError){{/error}}\n" + "#end\n" + "#foreach ($status in $result.statusResult.getByState('SEND_ERROR'))\n" + " {{error}}\n" + " $status.messageId - $status.errorSummary\n" + " $status.errorDescription\n" + " {{/error}}\n" + "#end\n" + "{{/velocity}}"; // This will create the page and execute its content and thus send the mail ViewPage vp = getUtil().createPage(getTestClassName(), "SendMail", velocity, ""); // Verify that the page doesn't display any content (unless there's an error!) assertEquals("", vp.getContent()); // Verify that the mail has been received. this.mail.waitForIncomingEmail(30000L, 1); assertEquals(1, this.mail.getReceivedMessages().length); assertReceivedMessages(1, "Subject: Status for John on " + getTestClassName() + ".SendMail", "Hello John from superadmin - Served from http://localhost:8080/xwiki/bin/view/MailTest/SendMail", "<strong>Hello John from superadmin - Served from " + "http://localhost:8080/xwiki/bin/view/MailTest/SendMail - " + "url: http://localhost:8080/xwiki/bin/view/Main/</strong>", "X-MailType: Test", "Content-Type: text/plain; name=something.txt", "Content-ID: <something.txt>", "Content-Disposition: attachment; filename=something.txt", "Content of attachment"); } private void sendTemplateMailToUsersAndGroup() throws Exception { // Remove existing pages (for pages that we create below) getUtil().deletePage(getTestClassName(), "SendMailGroupAndUsers"); // Create 2 users getUtil().createUser("user1", "password1", getUtil().getURLToNonExistentPage(), "email", "user1@doe.com"); getUtil().createUser("user2", "password2", getUtil().getURLToNonExistentPage(), "email", "user2@doe.com"); // Create another page with the Velocity script to send the template email // Note: the $xcontext and $request bindings are present and have their values at the moment the call to send // the mail asynchronously was done. String velocity = "{{velocity}}\n" + "#set ($templateParameters = " + " {'velocityVariables' : { 'name' : 'John', 'doc' : $doc }, " + " 'language' : 'en', 'from' : 'localhost@xwiki.org'})\n" + "#set ($templateReference = $services.model.createDocumentReference('', '" + getTestClassName() + "', 'MailTemplate'))\n" + "#set ($parameters = {'hint' : 'template', 'source' : $templateReference, " + "'parameters' : $templateParameters, 'type' : 'Test'})\n" + "#set ($groupReference = $services.model.createDocumentReference('', 'XWiki', 'XWikiAllGroup'))\n" + "#set ($user1Reference = $services.model.createDocumentReference('', 'XWiki', 'user1'))\n" + "#set ($user2Reference = $services.model.createDocumentReference('', 'XWiki', 'user2'))\n" + "#set ($source = {'groups' : [$groupReference], 'users' : [$user1Reference, $user2Reference]})\n" + "#set ($messages = $services.mailsender.createMessages('usersandgroups', $source, $parameters))\n" + "#set ($result = $services.mailsender.send($messages, 'database'))\n" + "#if ($services.mailsender.lastError)\n" + " {{error}}$exceptiontool.getStackTrace($services.mailsender.lastError){{/error}}\n" + "#end\n" + "#foreach ($status in $result.statusResult.getByState('SEND_ERROR'))\n" + " {{error}}\n" + " $status.messageId - $status.errorSummary\n" + " $status.errorDescription\n" + " {{/error}}\n" + "#end\n" + "{{/velocity}}"; // This will create the page and execute its content and thus send the mail ViewPage vp = getUtil().createPage(getTestClassName(), "SendMailGroupAndUsers", velocity, ""); // Verify that the page doesn't display any content (unless there's an error!) assertEquals("", vp.getContent()); // Verify that the mails have been received (first mail above + the 2 mails sent to the group) this.mail.waitForIncomingEmail(30000L, 3); assertEquals(3, this.mail.getReceivedMessages().length); assertReceivedMessages(2, "Subject: Status for John on " + getTestClassName() + ".SendMailGroupAndUsers", "Hello John from superadmin - Served from " + "http://localhost:8080/xwiki/bin/view/MailTest/SendMailGroupAndUsers - " + "url: http://localhost:8080/xwiki/bin/view/Main/"); } private void assertReceivedMessages(int expectedMatchingCount, String... expectedLines) throws Exception { StringBuilder builder = new StringBuilder(); int count = 0; for (MimeMessage message : this.mail.getReceivedMessages()) { if (this.alreadyAssertedMessages.contains(message.getMessageID())) { continue; } ByteArrayOutputStream baos = new ByteArrayOutputStream(); message.writeTo(baos); String fullContent = baos.toString(); boolean match = true; for (int i = 0; i < expectedLines.length; i++) { if (!fullContent.contains(expectedLines[i])) { match = false; break; } } if (!match) { builder.append("- Content [" + fullContent + "]").append('\n'); } else { count++; } this.alreadyAssertedMessages.add(message.getMessageID()); } StringBuilder expected = new StringBuilder(); for (int i = 0; i < expectedLines.length; i++) { expected.append("- '" + expectedLines[i] + "'").append('\n'); } assertEquals(String.format("We got [%s] mails matching the expected content instead of [%s]. We were expecting " + "the following content:\n%s\nWe got the following:\n%s", count, expectedMatchingCount, expected.toString(), builder.toString()), expectedMatchingCount, count); } }