/*
* 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);
}
}