/*
* Copyright (c) 2010-2011 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 org.junit.Assert.assertNull;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.eurekastreams.commons.server.UserActionRequest;
import org.eurekastreams.commons.test.EasyMatcher;
import org.eurekastreams.server.action.execution.notification.NotificationPropertyKeys;
import org.eurekastreams.server.domain.EntityType;
import org.eurekastreams.server.domain.Identifiable;
import org.eurekastreams.server.domain.InAppNotificationEntity;
import org.eurekastreams.server.domain.NotificationType;
import org.eurekastreams.server.domain.Person;
import org.eurekastreams.server.domain.UnreadInAppNotificationCountDTO;
import org.eurekastreams.server.persistence.mappers.DomainMapper;
import org.eurekastreams.server.persistence.mappers.requests.PersistenceRequest;
import org.eurekastreams.server.search.modelview.PersonModelView;
import org.hamcrest.Description;
import org.jmock.Expectations;
import org.jmock.States;
import org.jmock.api.Action;
import org.jmock.api.Invocation;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Before;
import org.junit.Test;
/**
* Tests InAppNotificationNotifier.
*/
public class InAppNotificationNotifierTest
{
/** Test data. */
private static final Long RECIPIENT1 = 50L;
/** Test data. */
private static final Long RECIPIENT2 = 52L;
/** Test data. */
private static final String TEMPLATE = "This is the template";
/** Test data. */
private static final String AGGREGATE_TEMPLATE = "This is the aggregate template";
/** Test data. */
private static final String RENDERED = "This is the rendered template";
/** Test data. */
private static final NotificationType OK_TYPE = NotificationType.POST_TO_PERSONAL_STREAM;
/** Used for mocking objects. */
private final JUnit4Mockery context = new JUnit4Mockery()
{
{
setImposteriser(ClassImposteriser.INSTANCE);
}
};
/** Apache Velocity templating engine. */
private final VelocityEngine velocityEngine = context.mock(VelocityEngine.class);
/**
* Global context for Apache Velocity templating engine. (Holds system-wide properties.)
*/
private final Context velocityGlobalContext = context.mock(Context.class);
/** Mapper to persist the notification. */
private final DomainMapper<PersistenceRequest<InAppNotificationEntity>, Boolean> insertMapper = context.mock(
DomainMapper.class, "insertMapper");
/** Mapper to update aggregate notifications. */
private final DomainMapper<PersistenceRequest<InAppNotificationEntity>, Boolean> updateMapper = context.mock(
DomainMapper.class, "updateMapper");
/** Mapper to sync unread alert count in cache. */
private final DomainMapper<Long, UnreadInAppNotificationCountDTO> syncMapper = context.mock(DomainMapper.class,
"syncMapper");
/** Provides a dummy person object for persisting the in-app entity. */
private final DomainMapper<Long, Person> placeholderPersonMapper = context.mock(DomainMapper.class,
"placeholderPersonMapper");
/** Looks up existing notifications for aggregation. */
private final DomainMapper<InAppNotificationEntity, InAppNotificationEntity> existingNotificationMapper = context
.mock(DomainMapper.class, "existingNotificationMapper");
/** Dummy person. */
private final Person person1 = context.mock(Person.class, "person1");
/** Dummy person. */
private final Person person2 = context.mock(Person.class, "person2");
/** SUT. */
private InAppNotificationNotifier sut;
/** Templates. */
private final Map<NotificationType, String> templates = Collections.unmodifiableMap(Collections.singletonMap(
OK_TYPE, TEMPLATE));
/** Aggregated Templates. */
private final Map<NotificationType, String> aggregateTemplates = Collections.unmodifiableMap(Collections
.singletonMap(NotificationType.COMMENT_TO_COMMENTED_POST, AGGREGATE_TEMPLATE));
/** Recipients. */
private final Collection<Long> recipients = Collections.unmodifiableList(Arrays.asList(RECIPIENT1, RECIPIENT2));
/** Recipient index. */
private final Map<Long, PersonModelView> recipientIndex;
/**
* One-time setup.
*/
public InAppNotificationNotifierTest()
{
Map<Long, PersonModelView> map = new HashMap<Long, PersonModelView>();
map.put(RECIPIENT1, context.mock(PersonModelView.class, "recipient1"));
map.put(RECIPIENT2, context.mock(PersonModelView.class, "recipient2"));
recipientIndex = Collections.unmodifiableMap(map);
}
/**
* Setup before each test.
*/
@Before
public void setUp()
{
sut = new InAppNotificationNotifier(velocityEngine, velocityGlobalContext, templates, aggregateTemplates,
insertMapper, updateMapper, syncMapper, placeholderPersonMapper, existingNotificationMapper);
}
/**
* Tests notify.
*
* @throws Exception
* Won't.
*/
@Test
public void testNotifyUnknownTemplate() throws Exception
{
context.checking(new Expectations()
{
{
oneOf(placeholderPersonMapper).execute(with(equal(RECIPIENT1)));
will(returnValue(person1));
oneOf(placeholderPersonMapper).execute(with(equal(RECIPIENT2)));
will(returnValue(person2));
}
});
Collection<UserActionRequest> result = sut.notify(NotificationType.PASS_THROUGH, recipients,
Collections.EMPTY_MAP, null);
context.assertIsSatisfied();
assertNull(result);
}
/**
* Tests notify.
*
* @throws Exception
* Won't.
*/
@Test
public void testNotifyBasic() throws Exception
{
final States state = context.states("main");
state.startsAs("none");
context.checking(new Expectations()
{
{
oneOf(placeholderPersonMapper).execute(RECIPIENT1);
will(returnValue(person1));
then(state.is("person1"));
exactly(recipients.size()).of(velocityEngine).evaluate(with(any(VelocityContext.class)),
with(any(StringWriter.class)), with(equal("InAppNotification-POST_TO_PERSONAL_STREAM")),
with(equal(TEMPLATE)));
will(new AppendRenderedAction());
oneOf(insertMapper).execute(with(new EasyMatcher<PersistenceRequest>()
{
@Override
protected boolean isMatch(final PersistenceRequest testObject)
{
InAppNotificationEntity notif = (InAppNotificationEntity) testObject.getDomainEnity();
return person1 == notif.getRecipient() && RENDERED.equals(notif.getMessage())
&& OK_TYPE == notif.getNotificationType() && notif.getUrl() == null
&& !notif.isHighPriority() && notif.getSourceType() == EntityType.NOTSET
&& notif.getSourceUniqueId() == null && notif.getSourceName() == null
&& notif.getAvatarOwnerType() == EntityType.NOTSET
&& notif.getAvatarOwnerUniqueId() == null;
}
}));
when(state.is("person1"));
oneOf(syncMapper).execute(RECIPIENT1);
when(state.is("person1"));
oneOf(placeholderPersonMapper).execute(RECIPIENT2);
will(returnValue(person2));
then(state.is("person2"));
oneOf(person1).getId();
will(returnValue(RECIPIENT1));
when(state.is("person1"));
oneOf(person2).getId();
will(returnValue(RECIPIENT2));
when(state.is("person2"));
oneOf(insertMapper).execute(with(new EasyMatcher<PersistenceRequest>()
{
@Override
protected boolean isMatch(final PersistenceRequest testObject)
{
InAppNotificationEntity notif = (InAppNotificationEntity) testObject.getDomainEnity();
return person2 == notif.getRecipient() && RENDERED.equals(notif.getMessage())
&& OK_TYPE == notif.getNotificationType() && notif.getUrl() == null
&& !notif.isHighPriority() && notif.getSourceType() == EntityType.NOTSET
&& notif.getSourceUniqueId() == null && notif.getSourceName() == null
&& notif.getAvatarOwnerType() == EntityType.NOTSET
&& notif.getAvatarOwnerUniqueId() == null;
}
}));
when(state.is("person2"));
oneOf(syncMapper).execute(RECIPIENT2);
when(state.is("person2"));
}
});
Collection<UserActionRequest> result = sut.notify(NotificationType.POST_TO_PERSONAL_STREAM, recipients,
Collections.EMPTY_MAP, recipientIndex);
context.assertIsSatisfied();
assertNull(result);
}
/**
* Tests notify.
*
* @throws Exception
* Won't.
*/
@Test
public void testNotifyFullFields() throws Exception
{
final String url = "http://www.eurekastreams.org";
final String sourceName = "Source Name";
final String sourceUniqueId = "Source Unique ID";
final EntityType sourceType = EntityType.GROUP;
final String actorUniqueId = "Actor Unique ID";
final EntityType actorType = EntityType.PERSON;
final Identifiable source = context.mock(Identifiable.class, "source");
final Identifiable actor = context.mock(Identifiable.class, "actor");
context.checking(new Expectations()
{
{
oneOf(placeholderPersonMapper).execute(RECIPIENT1);
will(returnValue(person1));
oneOf(velocityEngine).evaluate(with(any(VelocityContext.class)), with(any(StringWriter.class)),
with(equal("InAppNotification-POST_TO_PERSONAL_STREAM")), with(equal(TEMPLATE)));
will(new AppendRenderedAction());
oneOf(insertMapper).execute(with(new EasyMatcher<PersistenceRequest>()
{
@Override
protected boolean isMatch(final PersistenceRequest testObject)
{
InAppNotificationEntity notif = (InAppNotificationEntity) testObject.getDomainEnity();
return person1 == notif.getRecipient() && RENDERED.equals(notif.getMessage())
&& OK_TYPE == notif.getNotificationType() && url.equals(notif.getUrl())
&& notif.isHighPriority() && notif.getSourceType() == EntityType.GROUP
&& sourceUniqueId.equals(notif.getSourceUniqueId())
&& sourceName.equals(notif.getSourceName())
&& notif.getAvatarOwnerType() == EntityType.PERSON
&& actorUniqueId.equals(notif.getAvatarOwnerUniqueId());
}
}));
oneOf(syncMapper).execute(RECIPIENT1);
allowing(source).getDisplayName();
will(returnValue(sourceName));
allowing(source).getUniqueId();
will(returnValue(sourceUniqueId));
allowing(source).getEntityType();
will(returnValue(sourceType));
allowing(actor).getUniqueId();
will(returnValue(actorUniqueId));
allowing(actor).getEntityType();
will(returnValue(actorType));
oneOf(person1).getId();
will(returnValue(RECIPIENT1));
}
});
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(NotificationPropertyKeys.URL, url);
properties.put(NotificationPropertyKeys.HIGH_PRIORITY, Boolean.TRUE);
properties.put(NotificationPropertyKeys.SOURCE, source);
properties.put(NotificationPropertyKeys.ACTOR, actor);
Collection<UserActionRequest> result = sut.notify(NotificationType.POST_TO_PERSONAL_STREAM,
Collections.singletonList(RECIPIENT1), properties, recipientIndex);
context.assertIsSatisfied();
assertNull(result);
}
/**
* Tests notify.
*
* @throws Exception
* Won't.
*/
@Test
public void testNotifyUnknownRecipient() throws Exception
{
context.checking(new Expectations()
{
{
oneOf(placeholderPersonMapper).execute(RECIPIENT1);
will(returnValue(null));
}
});
Collection<UserActionRequest> result = sut.notify(NotificationType.COMMENT_TO_COMMENTED_POST,
Collections.singletonList(RECIPIENT1), Collections.EMPTY_MAP, recipientIndex);
context.assertIsSatisfied();
assertNull(result);
}
/**
* Tests notification aggregation.
*
* @throws Exception
* Won't
*/
@Test
public void testNotifyWithAggregation() throws Exception
{
context.checking(new Expectations()
{
{
oneOf(placeholderPersonMapper).execute(RECIPIENT1);
will(returnValue(person1));
oneOf(velocityEngine).evaluate(with(any(VelocityContext.class)), with(any(StringWriter.class)),
with(equal("InAppNotification-COMMENT_TO_COMMENTED_POST")), with(equal(AGGREGATE_TEMPLATE)));
will(new AppendRenderedAction());
InAppNotificationEntity existingNotification = new InAppNotificationEntity();
existingNotification.setAggregationCount(3);
oneOf(existingNotificationMapper).execute(with(any(InAppNotificationEntity.class)));
will(returnValue(existingNotification));
oneOf(updateMapper).execute(with(new EasyMatcher<PersistenceRequest>()
{
@Override
protected boolean isMatch(final PersistenceRequest testObject)
{
InAppNotificationEntity notif = (InAppNotificationEntity) testObject.getDomainEnity();
return RENDERED.equals(notif.getMessage()) && notif.getAggregationCount() == 4;
}
}));
oneOf(person1).getId();
will(returnValue(RECIPIENT1));
oneOf(syncMapper).execute(RECIPIENT1);
}
});
Collection<UserActionRequest> result = sut.notify(NotificationType.COMMENT_TO_COMMENTED_POST,
Collections.singletonList(RECIPIENT1), Collections.EMPTY_MAP, recipientIndex);
context.assertIsSatisfied();
}
/** Custom action to simulate side effects of Velocity. */
private class AppendRenderedAction implements Action
{
@Override
public Object invoke(final Invocation inv) throws Throwable
{
((StringWriter) inv.getParameter(1)).append(RENDERED);
return true;
}
@Override
public void describeTo(final Description arg0)
{
}
}
}