/* * Copyright (c) 2010-2012 Lockheed Martin Corporation * * Licensed 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.eurekastreams.server.action.execution.notification.notifier; import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.StringWriter; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.app.VelocityEngine; import org.apache.velocity.context.Context; import org.eurekastreams.commons.exceptions.ExecutionException; import org.eurekastreams.commons.server.UserActionRequest; import org.eurekastreams.server.action.execution.email.NotificationEmailDTO; import org.eurekastreams.server.action.execution.notification.NotificationPropertyKeys; import org.eurekastreams.server.action.execution.notification.notifier.EmailNotificationTemplate.ReplyAction; import org.eurekastreams.server.domain.HasEmail; import org.eurekastreams.server.domain.NotificationType; import org.eurekastreams.server.domain.stream.ActivityDTO; import org.eurekastreams.server.search.modelview.PersonModelView; import org.eurekastreams.server.service.actions.strategies.ActivityInteractionType; import org.eurekastreams.server.service.email.TokenContentEmailAddressBuilder; import org.eurekastreams.server.service.email.TokenContentFormatter; import org.eurekastreams.server.service.utility.authorization.ActivityInteractionAuthorizationStrategy; import org.jmock.Expectations; import org.jmock.api.Invocation; import org.jmock.integration.junit4.JUnit4Mockery; import org.jmock.lib.action.CustomAction; import org.jmock.lib.legacy.ClassImposteriser; import org.junit.Before; import org.junit.Test; /** * Tests EmailNotifier. */ public class EmailNotifierTest { /** Test data. */ private static final String PREFIX = "Subject Prefix: "; /** Test data. */ private static final Long RECIPIENT1 = 51L; /** Test data. */ private static final Long RECIPIENT2 = 52L; /** Test data. */ private static final Long RECIPIENT3 = 53L; /** Test data. */ private static final String SUBJECT_TEMPLATE = "This is the subject template"; /** Test data. */ private static final String TEXT_BODY_RESOURCE = "This is the text body resource path"; /** Test data. */ private static final String HTML_BODY_RESOURCE = "This is the HTML body resource path"; /** Test data. */ private static final String SUBJECT_RENDERED = "This is the rendered subject template"; /** Test data. */ private static final String TEXT_BODY_RENDERED = "This is the rendered text body template"; /** Test data. */ private static final String HTML_BODY_RENDERED = "This is the rendered HTML body template"; /** Test data. */ private static final NotificationType NOTIFICATION_TYPE_NO_REPLY = NotificationType.COMMENT_TO_COMMENTED_POST; /** Test data. */ private static final NotificationType NOTIFICATION_TYPE_TOKEN = NotificationType.COMMENT_TO_PERSONAL_POST; /** Test data. */ private static final NotificationType NOTIFICATION_TYPE_ACTOR_REPLY = NotificationType.FOLLOW_PERSON; /** Test data. */ private static final String EMAIL1 = "person1@eurekastreams.org"; /** Test data. */ private static final String EMAIL2 = "person2@eurekastreams.org"; /** Test data. */ private static final long ACTIVITY_ID = 80L; /** Test data. */ private static final String TOKEN_CONTENT = "Stuff in the token"; /** Used for mocking objects. */ private final JUnit4Mockery mockery = new JUnit4Mockery() { { setImposteriser(ClassImposteriser.INSTANCE); } }; /** Apache Velocity templating engine. */ private final VelocityEngine velocityEngine = mockery.mock(VelocityEngine.class); /** Global context for Apache Velocity templating engine. (Holds system-wide properties.) */ private final Context velocityGlobalContext = mockery.mock(Context.class); /** Fixture: velocity template. */ private final Template textBodyTemplate = mockery.mock(Template.class, "textBodyTemplate"); /** Fixture: velocity template. */ private final Template htmlBodyTemplate = mockery.mock(Template.class, "htmlBodyTemplate"); /** Dummy person. */ private final PersonModelView person1 = mockery.mock(PersonModelView.class, "person1"); /** Dummy person. */ private final PersonModelView person2 = mockery.mock(PersonModelView.class, "person2"); /** Dummy person. */ private final PersonModelView person1a = mockery.mock(PersonModelView.class, "person1a"); /** Dummy person. */ private final PersonModelView person2a = mockery.mock(PersonModelView.class, "person2a"); /** Dummy person. */ private final PersonModelView person3a = mockery.mock(PersonModelView.class, "person3a"); /** Fixture: activity. */ private final ActivityDTO activity = mockery.mock(ActivityDTO.class, "activity"); /** SUT. */ private EmailNotifier sut; /** Templates. */ private final Map<NotificationType, EmailNotificationTemplate> templates; /** Recipients. */ private final Collection<Long> recipients = Collections.unmodifiableList(Arrays.asList(RECIPIENT1, RECIPIENT2, RECIPIENT3)); /** Recipient index. */ private final Map<Long, PersonModelView> recipientIndex = new HashMap<Long, PersonModelView>(); /** Builds the token content. */ private final TokenContentFormatter tokenContentFormatter = mockery.mock(TokenContentFormatter.class, "tokenContentFormatter"); /** Builds the recipient email address with a token. */ private final TokenContentEmailAddressBuilder tokenAddressBuilder = mockery.mock( TokenContentEmailAddressBuilder.class, "tokenAddressBuilder"); /** For determining if users can comment on an activity. */ private final ActivityInteractionAuthorizationStrategy activityAuthorizer = mockery .mock(ActivityInteractionAuthorizationStrategy.class); /** * One-time setup. */ public EmailNotifierTest() { Map map = new HashMap(2); EmailNotificationTemplate template = new EmailNotificationTemplate(); template.setHtmlBody(HTML_BODY_RESOURCE); template.setSubject(SUBJECT_TEMPLATE); template.setTextBody(TEXT_BODY_RESOURCE); map.put(NOTIFICATION_TYPE_NO_REPLY, template); template = new EmailNotificationTemplate(); template.setHtmlBody(HTML_BODY_RESOURCE); template.setSubject(SUBJECT_TEMPLATE); template.setTextBody(TEXT_BODY_RESOURCE); template.setReplyAddressType(ReplyAction.ACTOR); map.put(NOTIFICATION_TYPE_ACTOR_REPLY, template); template = new EmailNotificationTemplate(); template.setHtmlBody(HTML_BODY_RESOURCE); template.setSubject(SUBJECT_TEMPLATE); template.setTextBody(TEXT_BODY_RESOURCE); template.setReplyAddressType(ReplyAction.COMMENT); map.put(NOTIFICATION_TYPE_TOKEN, template); templates = Collections.unmodifiableMap(map); } /** * Setup for testing. * * @param sendHtml * If HTML emails should be built. */ private void setup(final boolean sendHtml) { sut = new EmailNotifier(velocityEngine, velocityGlobalContext, templates, PREFIX, tokenContentFormatter, tokenAddressBuilder, activityAuthorizer, sendHtml); recipientIndex.clear(); recipientIndex.put(RECIPIENT1, person1); recipientIndex.put(RECIPIENT2, person2); recipientIndex.put(RECIPIENT3, person3a); } /** * Setup before each test. */ @Before public void setUp() { setup(true); } /** * Tests notify. * * @throws Exception * Won't. */ @Test public void testNotifyUnknownTemplate() throws Exception { Collection<UserActionRequest> result = sut.notify(NotificationType.PASS_THROUGH, recipients, Collections.EMPTY_MAP, recipientIndex); mockery.assertIsSatisfied(); assertNull(result); } /** * Common setup for rendering tests. */ private void commonSetup() { mockery.checking(new Expectations() { { allowing(velocityEngine).getTemplate(HTML_BODY_RESOURCE); will(returnValue(htmlBodyTemplate)); allowing(velocityEngine).getTemplate(TEXT_BODY_RESOURCE); will(returnValue(textBodyTemplate)); allowing(velocityEngine).evaluate(with(any(VelocityContext.class)), with(any(StringWriter.class)), with(any(String.class)), with(equal(SUBJECT_TEMPLATE))); will(new CustomAction("Render subject") { public Object invoke(final Invocation inv) throws Throwable { ((StringWriter) inv.getParameter(1)).append(SUBJECT_RENDERED); return true; } }); allowing(textBodyTemplate).merge(with(any(VelocityContext.class)), with(any(StringWriter.class))); will(new CustomAction("Render text body") { public Object invoke(final Invocation inv) throws Throwable { ((StringWriter) inv.getParameter(1)).append(TEXT_BODY_RENDERED); return null; } }); allowing(htmlBodyTemplate).merge(with(any(VelocityContext.class)), with(any(StringWriter.class))); will(new CustomAction("Render HTML body") { public Object invoke(final Invocation inv) throws Throwable { ((StringWriter) inv.getParameter(1)).append(HTML_BODY_RENDERED); return null; } }); allowing(person1).getEmail(); will(returnValue(EMAIL1)); allowing(person2).getEmail(); will(returnValue(EMAIL2)); allowing(person1a).getEmail(); will(returnValue(null)); allowing(person2a).getEmail(); will(returnValue("")); allowing(person3a).getEmail(); will(returnValue(" ")); } }); } /** * Helper: asserts the result is a non-null collection containing a single UserActionRequest for sending email. * * @param results * Result list. * @return Param data from the result. */ private NotificationEmailDTO assertGetSingleResult(final Collection<UserActionRequest> results) { assertNotNull(results); assertEquals(1, results.size()); UserActionRequest result = results.iterator().next(); assertNotNull(result); assertEquals("sendEmailNotificationAction", result.getActionKey()); return (NotificationEmailDTO) result.getParams(); } /** * Tests notify. * * @throws Exception * Won't. */ @Test public void testNotifyTokenNone() throws Exception { commonSetup(); mockery.checking(new Expectations() { { allowing(activity).getId(); will(returnValue(ACTIVITY_ID)); allowing(tokenContentFormatter).buildForActivity(ACTIVITY_ID); will(returnValue(TOKEN_CONTENT)); allowing(activityAuthorizer).authorize(activity, ActivityInteractionType.COMMENT, false); will(returnValue(true)); } }); recipientIndex.put(RECIPIENT1, person1a); recipientIndex.put(RECIPIENT2, person2a); Collection<UserActionRequest> result = sut.notify(NOTIFICATION_TYPE_TOKEN, recipients, Collections.singletonMap("activity", (Object) activity), recipientIndex); mockery.assertIsSatisfied(); assertNull(result); } /** * Tests notify. * * @throws Exception * Won't. */ @Test public void testNotifyNone() throws Exception { commonSetup(); recipientIndex.put(RECIPIENT1, person1a); recipientIndex.put(RECIPIENT2, person2a); Collection<UserActionRequest> result = sut.notify(NOTIFICATION_TYPE_NO_REPLY, recipients, Collections.EMPTY_MAP, recipientIndex); mockery.assertIsSatisfied(); assertNull(result); } /** * Tests notify. * * @throws Exception * Won't. */ @Test public void testNotifyOne() throws Exception { commonSetup(); recipientIndex.put(RECIPIENT2, person2a); Collection<UserActionRequest> results = sut.notify(NOTIFICATION_TYPE_NO_REPLY, recipients, Collections.EMPTY_MAP, recipientIndex); mockery.assertIsSatisfied(); NotificationEmailDTO request = assertGetSingleResult(results); assertEquals(HTML_BODY_RENDERED, request.getHtmlBody()); assertEquals(TEXT_BODY_RENDERED, request.getTextBody()); assertEquals(PREFIX + SUBJECT_RENDERED, request.getSubject()); assertEquals(EMAIL1, request.getToRecipient()); assertTrue(request.getBccRecipients() == null || request.getBccRecipients().isEmpty()); assertTrue(request.getDescription() != null && !request.getDescription().isEmpty()); assertFalse(request.isHighPriority()); assertNull(request.getReplyTo()); } /** * Tests notify. * * @throws Exception * Won't. */ @Test public void testNotifyMultiple() throws Exception { commonSetup(); final Object actorEmail = "actor@eurekastreams.org"; final HasEmail actor = mockery.mock(HasEmail.class, "actor"); mockery.checking(new Expectations() { { allowing(actor).getEmail(); will(returnValue(actorEmail)); } }); Collection<UserActionRequest> results = sut.notify(NOTIFICATION_TYPE_ACTOR_REPLY, recipients, Collections.singletonMap(NotificationPropertyKeys.ACTOR, (Object) actor), recipientIndex); mockery.assertIsSatisfied(); NotificationEmailDTO request = assertGetSingleResult(results); assertEquals(HTML_BODY_RENDERED, request.getHtmlBody()); assertEquals(TEXT_BODY_RENDERED, request.getTextBody()); assertEquals(PREFIX + SUBJECT_RENDERED, request.getSubject()); assertTrue(request.getToRecipient() == null || request.getToRecipient().isEmpty()); assertEquals(EMAIL1 + "," + EMAIL2, request.getBccRecipients()); assertEquals(actorEmail, request.getReplyTo()); assertTrue(request.getDescription() != null && !request.getDescription().isEmpty()); assertFalse(request.isHighPriority()); } /** * Tests notify. * * @throws Exception * Won't. */ @Test public void testNotifyMultipleToken() throws Exception { commonSetup(); final String person1Reply = "system+ABC@eurekastreams.org"; final String person2Reply = "system+DEF@eurekastreams.org"; mockery.checking(new Expectations() { { allowing(activity).getId(); will(returnValue(ACTIVITY_ID)); allowing(tokenContentFormatter).buildForActivity(ACTIVITY_ID); will(returnValue(TOKEN_CONTENT)); allowing(tokenAddressBuilder).build(TOKEN_CONTENT, RECIPIENT1); will(returnValue(person1Reply)); allowing(tokenAddressBuilder).build(TOKEN_CONTENT, RECIPIENT2); will(returnValue(person2Reply)); allowing(activityAuthorizer).authorize(activity, ActivityInteractionType.COMMENT, false); will(returnValue(true)); } }); Collection<UserActionRequest> results = sut.notify(NOTIFICATION_TYPE_TOKEN, recipients, Collections.singletonMap("activity", (Object) activity), recipientIndex); mockery.assertIsSatisfied(); assertNotNull(results); assertEquals(2, results.size()); List<String> addresses = Arrays.asList(EMAIL1, EMAIL2); List<String> replyAddresses = Arrays.asList(person1Reply, person2Reply); for (UserActionRequest result : results) { assertNotNull(result); assertEquals("sendEmailNotificationAction", result.getActionKey()); NotificationEmailDTO request = (NotificationEmailDTO) result.getParams(); assertEquals(HTML_BODY_RENDERED, request.getHtmlBody()); assertEquals(TEXT_BODY_RENDERED, request.getTextBody()); assertEquals(PREFIX + SUBJECT_RENDERED, request.getSubject()); assertTrue(addresses.contains(request.getToRecipient())); assertTrue(request.getBccRecipients() == null || request.getBccRecipients().isEmpty()); assertTrue(replyAddresses.contains(request.getReplyTo())); assertTrue(request.getDescription() != null && !request.getDescription().isEmpty()); assertFalse(request.isHighPriority()); } } /** * Tests notify. * * @throws Exception * Won't. */ @Test public void testNotifyMultipleTokenSplitPermissions() throws Exception { commonSetup(); final String person1Reply = "system+ABC@eurekastreams.org"; mockery.checking(new Expectations() { { allowing(activity).getId(); will(returnValue(ACTIVITY_ID)); allowing(tokenContentFormatter).buildForActivity(ACTIVITY_ID); will(returnValue(TOKEN_CONTENT)); allowing(tokenAddressBuilder).build(TOKEN_CONTENT, RECIPIENT1); will(returnValue(person1Reply)); allowing(activityAuthorizer).authorize(activity, ActivityInteractionType.COMMENT, false); will(returnValue(false)); allowing(activityAuthorizer).authorize(RECIPIENT1, activity, ActivityInteractionType.COMMENT); will(returnValue(true)); allowing(activityAuthorizer).authorize(RECIPIENT2, activity, ActivityInteractionType.COMMENT); will(returnValue(false)); } }); Collection<UserActionRequest> results = sut.notify(NOTIFICATION_TYPE_TOKEN, recipients, Collections.singletonMap("activity", (Object) activity), recipientIndex); mockery.assertIsSatisfied(); assertNotNull(results); assertEquals(2, results.size()); for (UserActionRequest result : results) { assertNotNull(result); assertEquals("sendEmailNotificationAction", result.getActionKey()); NotificationEmailDTO request = (NotificationEmailDTO) result.getParams(); assertEquals(HTML_BODY_RENDERED, request.getHtmlBody()); assertEquals(TEXT_BODY_RENDERED, request.getTextBody()); assertEquals(PREFIX + SUBJECT_RENDERED, request.getSubject()); assertTrue(request.getBccRecipients() == null || request.getBccRecipients().isEmpty()); assertTrue(request.getDescription() != null && !request.getDescription().isEmpty()); assertFalse(request.isHighPriority()); // can't be sure of the order, so make sure to and reply-to match up final String toRecipient = request.getToRecipient(); if (EMAIL1.equals(toRecipient)) { assertEquals(person1Reply, request.getReplyTo()); } else if (EMAIL2.equals(toRecipient)) { assertNull(request.getReplyTo()); } else { fail("Unexpected recipient " + toRecipient); } } } // ---- MISCELLANY TESTS ----- /** * Tests notify. * * @throws Exception * Won't. */ @Test public void testNotifyHighPriority() throws Exception { commonSetup(); Collection<UserActionRequest> results = sut.notify(NOTIFICATION_TYPE_NO_REPLY, Collections.singletonList(RECIPIENT1), Collections.singletonMap(NotificationPropertyKeys.HIGH_PRIORITY, (Object) true), recipientIndex); mockery.assertIsSatisfied(); NotificationEmailDTO request = assertGetSingleResult(results); assertTrue(request.isHighPriority()); } // ----- NON-HTML TESTS ----- /** * Test. * * @throws Exception * Won't. */ @Test public void testNoHtmlTemplate() throws Exception { EmailNotificationTemplate template = new EmailNotificationTemplate(); template.setSubject(SUBJECT_TEMPLATE); template.setTextBody(TEXT_BODY_RESOURCE); sut = new EmailNotifier(velocityEngine, velocityGlobalContext, Collections.singletonMap( NOTIFICATION_TYPE_NO_REPLY, template), PREFIX, tokenContentFormatter, tokenAddressBuilder, activityAuthorizer, true); recipientIndex.clear(); recipientIndex.put(RECIPIENT1, person1); recipientIndex.put(RECIPIENT2, person2); recipientIndex.put(RECIPIENT3, person3a); commonSetup(); recipientIndex.put(RECIPIENT2, person2a); Collection<UserActionRequest> results = sut.notify(NOTIFICATION_TYPE_NO_REPLY, recipients, Collections.EMPTY_MAP, recipientIndex); mockery.assertIsSatisfied(); NotificationEmailDTO request = assertGetSingleResult(results); assertNull(request.getHtmlBody()); assertEquals(TEXT_BODY_RENDERED, request.getTextBody()); assertEquals(PREFIX + SUBJECT_RENDERED, request.getSubject()); assertEquals(EMAIL1, request.getToRecipient()); assertTrue(request.getBccRecipients() == null || request.getBccRecipients().isEmpty()); assertTrue(request.getDescription() != null && !request.getDescription().isEmpty()); assertFalse(request.isHighPriority()); assertNull(request.getReplyTo()); } /** * Test. * * @throws Exception * Won't. */ @Test public void testDisableHtml() throws Exception { setup(false); commonSetup(); recipientIndex.put(RECIPIENT2, person2a); Collection<UserActionRequest> results = sut.notify(NOTIFICATION_TYPE_NO_REPLY, recipients, Collections.EMPTY_MAP, recipientIndex); mockery.assertIsSatisfied(); NotificationEmailDTO request = assertGetSingleResult(results); assertNull(request.getHtmlBody()); assertEquals(TEXT_BODY_RENDERED, request.getTextBody()); assertEquals(PREFIX + SUBJECT_RENDERED, request.getSubject()); assertEquals(EMAIL1, request.getToRecipient()); assertTrue(request.getBccRecipients() == null || request.getBccRecipients().isEmpty()); assertTrue(request.getDescription() != null && !request.getDescription().isEmpty()); assertFalse(request.isHighPriority()); assertNull(request.getReplyTo()); } // ----- ANOMALY TESTS ----- /** * Tests notify. * * @throws Exception * Won't. */ @Test(expected = ExecutionException.class) public void testNotifyMultipleTokenMissingActivity() throws Exception { commonSetup(); sut.notify(NOTIFICATION_TYPE_TOKEN, recipients, Collections.EMPTY_MAP, recipientIndex); mockery.assertIsSatisfied(); } /** * Tests notify. * * @throws Exception * Won't. */ @Test(expected = ExecutionException.class) public void testNotifyTokenActivityWrongType() throws Exception { commonSetup(); sut.notify(NOTIFICATION_TYPE_TOKEN, recipients, Collections.singletonMap("activity", new Object()), recipientIndex); mockery.assertIsSatisfied(); } /** * Tests notify. * * @throws Exception * Won't. */ @Test public void testNotifyActorWrongType() throws Exception { commonSetup(); Collection<UserActionRequest> results = sut.notify(NOTIFICATION_TYPE_ACTOR_REPLY, recipients, Collections.singletonMap(NotificationPropertyKeys.ACTOR, (Object) "WRONG!"), recipientIndex); mockery.assertIsSatisfied(); NotificationEmailDTO request = assertGetSingleResult(results); assertNull(request.getReplyTo()); } /** * Tests notify. * * @throws Exception * Won't. */ @Test public void testNotifyActorBadEmail() throws Exception { commonSetup(); final HasEmail actor = mockery.mock(HasEmail.class, "actor"); mockery.checking(new Expectations() { { allowing(actor).getEmail(); will(returnValue(" ")); } }); Collection<UserActionRequest> results = sut.notify(NOTIFICATION_TYPE_ACTOR_REPLY, recipients, Collections.singletonMap(NotificationPropertyKeys.ACTOR, (Object) "WRONG!"), recipientIndex); mockery.assertIsSatisfied(); NotificationEmailDTO request = assertGetSingleResult(results); assertNull(request.getReplyTo()); } }