/*
* Copyright (c) 2010 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.feed;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.eurekastreams.commons.actions.context.ActionContext;
import org.eurekastreams.server.action.request.feed.RefreshFeedRequest;
import org.eurekastreams.server.domain.DomainGroup;
import org.eurekastreams.server.domain.EntityType;
import org.eurekastreams.server.domain.Person;
import org.eurekastreams.server.domain.gadgetspec.GadgetMetaDataDTO;
import org.eurekastreams.server.domain.stream.Activity;
import org.eurekastreams.server.domain.stream.BaseObjectType;
import org.eurekastreams.server.domain.stream.plugins.Feed;
import org.eurekastreams.server.domain.stream.plugins.FeedSubscriber;
import org.eurekastreams.server.domain.stream.plugins.PluginDefinition;
import org.eurekastreams.server.persistence.mappers.FindByIdMapper;
import org.eurekastreams.server.persistence.mappers.InsertMapper;
import org.eurekastreams.server.persistence.mappers.UpdateMapper;
import org.eurekastreams.server.persistence.mappers.cache.CacheKeys;
import org.eurekastreams.server.persistence.mappers.cache.MemcachedCache;
import org.eurekastreams.server.persistence.mappers.requests.FindByIdRequest;
import org.eurekastreams.server.persistence.mappers.requests.PersistenceRequest;
import org.eurekastreams.server.service.actions.strategies.activity.plugins.FeedObjectActivityBuilder;
import org.eurekastreams.server.service.actions.strategies.activity.plugins.ObjectBuilderForSpecificUrl;
import org.eurekastreams.server.service.actions.strategies.activity.plugins.rome.ActivityStreamsModule;
import org.eurekastreams.server.service.actions.strategies.activity.plugins.rome.ActivityStreamsModuleImpl;
import org.eurekastreams.server.service.actions.strategies.activity.plugins.rome.FeedFactory;
import org.eurekastreams.server.service.opensocial.gadgets.spec.GadgetMetaDataFetcher;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.jmock.lib.legacy.ClassImposteriser;
import org.junit.Before;
import org.junit.Test;
import com.sun.syndication.feed.module.SyModule;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeed;
/**
* Test for the RefreshFeedAction.
*
*/
public class RefreshFeedExecutionTest
{
/**
* Context for building mock objects.
*/
private final Mockery context = new JUnit4Mockery()
{
{
setImposteriser(ClassImposteriser.INSTANCE);
}
};
/** Test data. */
private static final String FEED_URL = "http://www.flickr.com/rss/feed";
/**
* Minutes in an hour.
*/
private static final long MININHOUR = 60L;
/**
* Minutes in a day.
*/
private static final long MININDAY = 1440L;
/**
* Minutes in a week.
*/
private static final long MININWEEK = 10080L;
/**
* Minutes in a month.
*/
private static final long MININMONTH = 44640L;
/**
* Minutes in a year.
*/
private static final long MININYEAR = 525600L;
/**
* Standard feed mappers.
*/
private final HashMap<BaseObjectType, FeedObjectActivityBuilder> standardFeedMappers
// break
= new HashMap<BaseObjectType, FeedObjectActivityBuilder>();
/**
* Mappers for specific websites.
*/
private final List<ObjectBuilderForSpecificUrl> specificUrlMappers = new LinkedList<ObjectBuilderForSpecificUrl>();
/**
* Bulk insert activity into the DB.
*/
private final InsertMapper<Activity> activityDBInserter = context.mock(InsertMapper.class);
/**
* The cache.
*/
private final MemcachedCache cache = context.mock(MemcachedCache.class);
/**
* System under test.
*/
private RefreshFeedExecution sut;
/**
* Context for tests.
*/
private final ActionContext ac = context.mock(ActionContext.class);
/**
* Feed factory mock.
*/
private final FeedFactory feedFetcherFactory = context.mock(FeedFactory.class);
/**
* Flickr mapper mock.
*/
private final ObjectBuilderForSpecificUrl flickrMapper = context.mock(ObjectBuilderForSpecificUrl.class, "flickr");
/**
* Youtube mapper mock.
*/
private final ObjectBuilderForSpecificUrl youTubeMapper = context
.mock(ObjectBuilderForSpecificUrl.class, "youtube");
/**
* Notemapper mock.
*/
private final FeedObjectActivityBuilder noteMapper = context.mock(FeedObjectActivityBuilder.class, "note");
/**
* Bookmark mapper mock.
*/
private final FeedObjectActivityBuilder bookmarkMapper = context.mock(FeedObjectActivityBuilder.class, "bookmark");
/**
* Find by id person mapper.
*/
private final FindByIdMapper<Person> personMapper = context.mock(FindByIdMapper.class);
/**
* Find by id group mapper.
*/
private final FindByIdMapper<DomainGroup> groupMapper = context.mock(FindByIdMapper.class, "gm");
/**
* Find by id feed mapper.
*/
private final FindByIdMapper<Feed> feedMapper = context.mock(FindByIdMapper.class, "fm");
/**
* Person owner.
*/
private final Person personOwner = context.mock(Person.class);
/**
* Group owner.
*/
private final DomainGroup groupOwner = context.mock(DomainGroup.class);
/**
* Group owner.
*/
private final UpdateMapper<Feed> updateMapper = context.mock(UpdateMapper.class);
/**
* Gadget metadata fetcher.
*/
private final GadgetMetaDataFetcher fetcher = context.mock(GadgetMetaDataFetcher.class);
/**
* Unordered feeds.
*/
private List<String> unorderedFeeds = new ArrayList<String>();
/** Fixture: feed. */
private final Feed feed = context.mock(Feed.class);
/** Fixture: stream plugin. */
private final PluginDefinition plugin = context.mock(PluginDefinition.class);
/** Fixture: atom feed. */
private final SyndFeed atomFeed1 = context.mock(SyndFeed.class, "atomFeed1");
/** Fixture: atom feed. */
private final SyndFeed atomFeed2 = context.mock(SyndFeed.class, "atomFeed2");
/**
* Prep the system under test for the test suite.
*
* @throws Exception
* exception.
*/
@Before
public void setUp() throws Exception
{
final List<GadgetMetaDataDTO> metaDataList = new ArrayList<GadgetMetaDataDTO>();
metaDataList.add(new GadgetMetaDataDTO(null));
FeedSubscriber sub1 = new FeedSubscriber();
sub1.setEntityId(1L);
sub1.setEntityType(EntityType.PERSON);
sub1.setRequestor(new Person("user1", "", "", "", ""));
FeedSubscriber sub2 = new FeedSubscriber();
sub2.setEntityId(2L);
sub2.setEntityType(EntityType.GROUP);
sub2.setRequestor(new Person("user2", "", "", "", ""));
final List<FeedSubscriber> feedSubs = new ArrayList<FeedSubscriber>();
feedSubs.add(sub1);
feedSubs.add(sub2);
specificUrlMappers.add(youTubeMapper);
specificUrlMappers.add(flickrMapper);
standardFeedMappers.put(BaseObjectType.NOTE, noteMapper);
standardFeedMappers.put(BaseObjectType.BOOKMARK, bookmarkMapper);
sut = new RefreshFeedExecution(standardFeedMappers, specificUrlMappers, activityDBInserter, cache,
feedFetcherFactory, personMapper, groupMapper, feedMapper, fetcher, updateMapper, unorderedFeeds);
context.checking(new Expectations()
{
{
allowing(ac).getParams();
will(returnValue(new RefreshFeedRequest(5L)));
// ---- feed ----
allowing(feed).getUrl();
will(returnValue(FEED_URL));
allowing(feed).getLastSeenGUID();
will(returnValue(""));
allowing(feed).getLastPostDate();
will(returnValue(new Date(2)));
allowing(feed).getPlugin();
will(returnValue(plugin));
allowing(feed).getFeedSubscribers();
will(returnValue(feedSubs));
allowing(feed).getTitle();
will(returnValue("This is a feed"));
// allowing(feed).getId();
// will(returnValue(5L));
// ---- plugin ----
allowing(plugin).getUrl();
allowing(plugin).getId();
will(returnValue(1L));
allowing(plugin).getObjectType();
will(returnValue(BaseObjectType.BOOKMARK));
oneOf(youTubeMapper).match(with(any(String.class)));
will(returnValue(false));
allowing(updateMapper).execute(with(any(PersistenceRequest.class)));
allowing(cache).get(CacheKeys.BUFFERED_ACTIVITIES);
will(returnValue(1L));
allowing(fetcher).getGadgetsMetaData(with(any(Map.class)));
will(returnValue(metaDataList));
allowing(activityDBInserter).execute(with(any(PersistenceRequest.class)));
allowing(cache).addToTopOfList(with(any(String.class)), with(any(ArrayList.class)));
allowing(personMapper).execute(with(any(FindByIdRequest.class)));
will(returnValue(personOwner));
allowing(groupMapper).execute(with(any(FindByIdRequest.class)));
will(returnValue(groupOwner));
allowing(feedMapper).execute(with(any(FindByIdRequest.class)));
will(returnValue(feed));
// ---- updates always made ----
oneOf(feed).setLastPostDate(with(any(Date.class)));
oneOf(feed).setLastUpdated(with(any(Long.class)));
oneOf(feed).setPending(false);
oneOf(feed).setLastSeenGUID(with(any(String.class)));
oneOf(feed).setIsFeedBroken(with(any(Boolean.class)));
}
});
}
/**
* Setup expectations for fetching anonymously.
*
* @throws Exception
* Shouldn't.
*/
private void setupFetchAnonymous() throws Exception
{
context.checking(new Expectations()
{
{
allowing(feedFetcherFactory).getSyndicatedFeed(with(equal(FEED_URL)), with(any(List.class)));
will(returnValue(Collections.singletonMap(null, atomFeed1)));
}
});
}
/**
* Setup expectations for fetching on a per-user basis.
*
* @throws Exception
* Shouldn't.
*/
private void setupFetchByUser() throws Exception
{
final Map<String, SyndFeed> feeds = new TreeMap<String, SyndFeed>(); // use TreeMap to force order
feeds.put("user1", atomFeed1);
feeds.put("user2", atomFeed2);
context.checking(new Expectations()
{
{
allowing(feedFetcherFactory).getSyndicatedFeed(with(equal(FEED_URL)), with(any(List.class)));
will(returnValue(feeds));
}
});
}
/**
* Creates expectations for no SyMod.
*/
private void setupNoSyMod()
{
context.checking(new Expectations()
{
{
allowing(atomFeed1).getModule(SyModule.URI);
will(returnValue(null));
allowing(atomFeed2).getModule(SyModule.URI);
will(returnValue(null));
oneOf(feed).setUpdateFrequency(null);
}
});
}
/**
* Core behavior for update frequency tests.
*
* @param period
* Time unit of update period.
* @param frequency
* Frequency of update.
* @param expected
* Expected value to be set in the feed.
* @throws Exception
* Shouldn't.
*/
private void coreUpdateFrequencyTest(final String period, final int frequency, final Long expected)
throws Exception
{
final SyModule syMod = context.mock(SyModule.class);
setupFetchByUser();
context.checking(new Expectations()
{
{
allowing(flickrMapper).match(FEED_URL);
will(returnValue(false));
allowing(atomFeed1).getModule(SyModule.URI);
will(returnValue(syMod));
never(atomFeed2).getModule(with(any(String.class)));
allowing(syMod).getUpdateFrequency();
will(returnValue(frequency));
allowing(syMod).getUpdatePeriod();
will(returnValue(period));
allowing(atomFeed1).getEntries();
will(returnValue(Collections.EMPTY_LIST));
allowing(atomFeed2).getEntries();
will(returnValue(Collections.EMPTY_LIST));
oneOf(feed).setUpdateFrequency(expected);
}
});
sut.execute(ac);
context.assertIsSatisfied();
}
/**
* Tests SyMod-specified update frequency.
*
* @throws Exception
* Shouldn't.
*/
@Test
public void testSymodDefault() throws Exception
{
coreUpdateFrequencyTest("Undefined Value", 1, MININHOUR);
}
/**
* Tests SyMod-specified update frequency.
*
* @throws Exception
* Shouldn't.
*/
@Test
public void testSymodHourly() throws Exception
{
coreUpdateFrequencyTest(SyModule.HOURLY, 1, MININHOUR);
}
/**
* Tests SyMod-specified update frequency.
*
* @throws Exception
* Shouldn't.
*/
@Test
public void testSymodDaily() throws Exception
{
coreUpdateFrequencyTest(SyModule.DAILY, 1, MININDAY);
}
/**
* Tests SyMod-specified update frequency.
*
* @throws Exception
* Shouldn't.
*/
@Test
public void testSymodWeekly() throws Exception
{
coreUpdateFrequencyTest(SyModule.WEEKLY, 1, MININWEEK);
}
/**
* Tests SyMod-specified update frequency.
*
* @throws Exception
* Shouldn't.
*/
@Test
public void testSymodMonthly() throws Exception
{
coreUpdateFrequencyTest(SyModule.MONTHLY, 1, MININMONTH);
}
/**
* Tests SyMod-specified update frequency.
*
* @throws Exception
* Shouldn't.
*/
@Test
public void testSymodYearly() throws Exception
{
coreUpdateFrequencyTest(SyModule.YEARLY, 1, MININYEAR);
}
/**
* Test with no mapper and a specific URL mapper for the feed and 1 entry.
*
* @throws Exception
* exception feed throws.
*/
@Test
public void withSpecificMapper() throws Exception
{
final FeedObjectActivityBuilder flickrObjectMapper = context.mock(FeedObjectActivityBuilder.class);
final SyndEntryImpl entry1 = context.mock(SyndEntryImpl.class, "e1");
final List<SyndEntryImpl> entryList = Collections.singletonList(entry1);
setupNoSyMod();
setupFetchAnonymous();
context.checking(new Expectations()
{
{
oneOf(flickrMapper).match(FEED_URL);
will(returnValue(true));
oneOf(flickrMapper).getBuilder();
will(returnValue(flickrObjectMapper));
allowing(entry1).getPublishedDate();
will(returnValue(new Date(3)));
allowing(entry1).getUpdatedDate();
will(returnValue(new Date(3)));
allowing(entry1).getUri();
will(returnValue("uri"));
oneOf(flickrObjectMapper).build(with(equal(feed)), with(equal(entry1)), with(any(Activity.class)));
allowing(personOwner).isAccountLocked();
will(returnValue(false));
allowing(personOwner).getAccountId();
allowing(personOwner).getId();
allowing(personOwner).getStreamScope();
allowing(groupOwner).getStreamScope();
allowing(groupOwner).isPublicGroup();
allowing(groupOwner).getShortName();
will(returnValue(false));
allowing(atomFeed1).getEntries();
will(returnValue(entryList));
}
});
sut.execute(ac);
context.assertIsSatisfied();
}
/**
* Test with 1 old entry, 1 activitystreams entry, 1 unsupported activitystreams object, and 1 standard note object.
*
* @throws Exception
* exception feed throws.
*/
@Test
public void withOneActivityStreamsAndOneEntryError() throws Exception
{
final ActivityStreamsModuleImpl activityModule = context.mock(ActivityStreamsModuleImpl.class);
// Feed is too old.
final SyndEntryImpl entry1 = context.mock(SyndEntryImpl.class, "e1");
// This feed should error out.
final SyndEntryImpl entry2 = context.mock(SyndEntryImpl.class, "e2");
// This feed is an activitystreams that has no object support
final SyndEntryImpl entry3 = context.mock(SyndEntryImpl.class, "e3");
// This is a standard entry with a known object
final SyndEntryImpl entry4 = context.mock(SyndEntryImpl.class, "e4");
final List<SyndEntryImpl> entryList = new LinkedList<SyndEntryImpl>();
entryList.add(entry1);
entryList.add(entry2);
entryList.add(entry3);
entryList.add(entry4);
setupNoSyMod();
setupFetchAnonymous();
context.checking(new Expectations()
{
{
allowing(personOwner).isAccountLocked();
will(returnValue(false));
allowing(personOwner).getAccountId();
allowing(personOwner).getId();
allowing(personOwner).getStreamScope();
allowing(groupOwner).getStreamScope();
allowing(groupOwner).isPublicGroup();
allowing(groupOwner).getShortName();
will(returnValue(false));
oneOf(flickrMapper).match(FEED_URL);
will(returnValue(false));
// ENTRY 1
allowing(entry1).getPublishedDate();
will(returnValue(new Date()));
allowing(entry1).getUpdatedDate();
allowing(entry1).getUri();
will(returnValue(""));
oneOf(entry1).getModule(ActivityStreamsModule.URI);
will(returnValue(null));
// ENTRY 2
allowing(entry2).getPublishedDate();
will(returnValue(new Date()));
allowing(entry2).getUpdatedDate();
will(throwException(new Exception()));
allowing(entry2).getUri();
will(returnValue("uri"));
// ENTRY 3
allowing(entry3).getPublishedDate();
will(returnValue(new Date()));
allowing(entry3).getUpdatedDate();
will(returnValue(new Date()));
allowing(entry3).getUri();
will(returnValue("uri"));
oneOf(entry3).getModule(ActivityStreamsModule.URI);
will(returnValue(activityModule));
oneOf(activityModule).getObjectType();
will(returnValue("PHOTO"));
oneOf(activityModule).getAtomEntry();
will(returnValue(entry3));
oneOf(noteMapper).build(with(equal(feed)), with(equal(entry3)), with(any(Activity.class)));
// ENTRY 4
allowing(entry4).getPublishedDate();
will(returnValue(new Date()));
allowing(entry4).getUpdatedDate();
will(returnValue(new Date()));
allowing(entry4).getUri();
will(returnValue("uri"));
oneOf(entry4).getModule(ActivityStreamsModule.URI);
will(returnValue(null));
allowing(atomFeed1).getEntries();
will(returnValue(entryList));
oneOf(bookmarkMapper).build(with(equal(feed)), with(equal(entry4)), with(any(Activity.class)));
oneOf(bookmarkMapper).build(with(equal(feed)), with(equal(entry1)), with(any(Activity.class)));
}
});
sut.execute(ac);
context.assertIsSatisfied();
}
}