package org.atlasapi.persistence.lookup.mongo; import static com.google.common.base.Predicates.equalTo; import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isOneOf; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import java.util.Map; import org.atlasapi.media.entity.Alias; import org.atlasapi.media.entity.Brand; import org.atlasapi.media.entity.Episode; import org.atlasapi.media.entity.Item; import org.atlasapi.media.entity.LookupRef; import org.atlasapi.media.entity.ParentRef; import org.atlasapi.media.entity.Publisher; import org.atlasapi.media.entity.Series; import org.atlasapi.persistence.audit.NoLoggingPersistenceAuditLog; import org.atlasapi.persistence.content.ContentCategory; import org.atlasapi.persistence.content.listing.ContentListingProgress; import org.atlasapi.persistence.lookup.entry.LookupEntry; import org.junit.After; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.runners.MockitoJUnitRunner; import org.slf4j.Logger; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.metabroadcast.common.persistence.MongoTestHelper; import com.metabroadcast.common.persistence.mongo.DatabasedMongo; import com.metabroadcast.common.persistence.mongo.MongoQueryBuilder; import com.metabroadcast.common.persistence.mongo.MongoUpdateBuilder; import com.mongodb.BasicDBObject; import com.mongodb.DBCollection; import com.mongodb.ReadPreference; @RunWith( MockitoJUnitRunner.class ) public class MongoLookupEntryStoreTest { private static DatabasedMongo mongo; private static MongoLookupEntryStore entryStore; private static Logger log = mock(Logger.class); private static DBCollection collection; @BeforeClass public static void setUp() { mongo = MongoTestHelper.anEmptyTestDatabase(); collection = mongo.collection("lookup"); entryStore = new MongoLookupEntryStore( collection, ReadPreference.primary(), new NoLoggingPersistenceAuditLog(), log ); } @After public void clear() { mongo.collection("lookup").remove(new BasicDBObject()); reset(log); } @Test public void testStore() { Item testItemOne = new Item("testItemOneUri", "testItem1Curie", Publisher.BBC); testItemOne.addAliasUrl("testItemOneAlias"); testItemOne.addAlias(new Alias("testItemOneNamespace", "testItemOneValue")); testItemOne.addAliasUrl("sharedAlias"); testItemOne.addAlias(new Alias("sharedNamespace", "sharedValue")); Item testItemTwo = new Item("testItemTwoUri", "testItem2Curie", Publisher.BBC); testItemTwo.addAliasUrl("testItemTwoAlias"); testItemTwo.addAlias(new Alias("testItemTwoNamespace", "testItemTwoValue")); testItemTwo.addAliasUrl("sharedAlias"); testItemTwo.addAlias(new Alias("sharedNamespace", "sharedValue")); LookupEntry testEntryOne = LookupEntry.lookupEntryFrom(testItemOne); LookupEntry testEntryTwo = LookupEntry.lookupEntryFrom(testItemTwo); entryStore.store(testEntryOne); entryStore.store(testEntryTwo); Iterable<LookupEntry> uriEntry = entryStore.entriesForCanonicalUris(ImmutableList.of("testItemOneUri")); assertEquals(testEntryOne, Iterables.getOnlyElement(uriEntry)); //Shouldn't be able to find entry by canonical URI using alias Iterable<LookupEntry> aliasEntry = entryStore.entriesForCanonicalUris(ImmutableList.of("testItemOneAlias")); assertTrue("Found entry by canonical URI using alias", Iterables.isEmpty(aliasEntry)); aliasEntry = entryStore.entriesForIdentifiers(ImmutableList.of("testItemOneAlias"), true); assertEquals(testEntryOne, Iterables.getOnlyElement(aliasEntry)); aliasEntry = entryStore.entriesForAliases(Optional.of("testItemOneNamespace"), ImmutableList.of("testItemOneValue")); assertEquals(testEntryOne, Iterables.getOnlyElement(aliasEntry)); aliasEntry = entryStore.entriesForAliases(Optional.<String>absent(), ImmutableList.of("testItemOneValue")); assertEquals(testEntryOne, Iterables.getOnlyElement(aliasEntry)); uriEntry = entryStore.entriesForCanonicalUris(ImmutableList.of("testItemTwoUri")); assertEquals(testEntryTwo, Iterables.getOnlyElement(uriEntry)); //Shouldn't be able to find entry by canonical URI using alias aliasEntry = entryStore.entriesForCanonicalUris(ImmutableList.of("testItemTwoAlias")); assertTrue("Found entry by canonical URI using alias", Iterables.isEmpty(aliasEntry)); aliasEntry = entryStore.entriesForIdentifiers(ImmutableList.of("sharedAlias"), true); LookupEntry first = Iterables.get(aliasEntry, 0); LookupEntry second = Iterables.get(aliasEntry, 1); assertThat(first, isOneOf(testEntryOne, testEntryTwo)); assertThat(second, isOneOf(testEntryOne, testEntryTwo)); assertThat(first, is(not(second))); aliasEntry = entryStore.entriesForAliases(Optional.of("sharedNamespace"), ImmutableList.of("sharedValue")); first = Iterables.get(aliasEntry, 0); second = Iterables.get(aliasEntry, 1); assertThat(first, isOneOf(testEntryOne, testEntryTwo)); assertThat(second, isOneOf(testEntryOne, testEntryTwo)); assertThat(first, is(not(second))); aliasEntry = entryStore.entriesForAliases(Optional.<String>absent(), ImmutableList.of("sharedValue")); first = Iterables.get(aliasEntry, 0); second = Iterables.get(aliasEntry, 1); assertThat(first, isOneOf(testEntryOne, testEntryTwo)); assertThat(second, isOneOf(testEntryOne, testEntryTwo)); assertThat(first, is(not(second))); } @Test public void testEnsureLookup() { Item testItem = new Item("newItemUri", "newItemCurie", Publisher.BBC); testItem.addAliasUrl("newItemAlias"); entryStore.ensureLookup(testItem); LookupEntry firstEntry = Iterables.getOnlyElement(entryStore.entriesForCanonicalUris(ImmutableList.of("newItemUri"))); assertNotNull(firstEntry); entryStore.ensureLookup(testItem); assertEquals(firstEntry.created(), Iterables.getOnlyElement(entryStore.entriesForCanonicalUris(ImmutableList.of("newItemUri"))).created()); } @Test public void testEnsureLookupWritesEntryWhenOfDifferentType() { Item testItem = new Item("oldItemUri", "oldItemCurie", Publisher.BBC); testItem.addAliasUrl("oldItemAlias"); entryStore.ensureLookup(testItem); LookupEntry firstEntry = Iterables.getOnlyElement(entryStore.entriesForCanonicalUris(ImmutableList.of("oldItemUri"))); Item transitiveItem = new Item("transitiveUri", "transitiveCurie", Publisher.PA); transitiveItem.addAliasUrl("transitiveAlias"); entryStore.ensureLookup(transitiveItem); LookupEntry secondEntry = Iterables.getOnlyElement(entryStore.entriesForCanonicalUris(ImmutableList.of("transitiveUri"))); ImmutableSet<LookupRef> secondRef = ImmutableSet.of(secondEntry.lookupRef()); firstEntry = firstEntry.copyWithDirectEquivalents(secondRef).copyWithEquivalents(secondRef); ImmutableSet<LookupRef> firstRef = ImmutableSet.of(firstEntry.lookupRef()); secondEntry= secondEntry.copyWithDirectEquivalents(firstRef).copyWithEquivalents(firstRef); entryStore.store(firstEntry); entryStore.store(secondEntry); Episode testEpisode = new Episode("oldItemUri", "oldItemCurie", Publisher.BBC); testEpisode.addAliasUrl("oldItemAlias"); testEpisode.setParentRef(new ParentRef("aBrand")); entryStore.ensureLookup(testEpisode); LookupEntry newEntry = Iterables.getOnlyElement(entryStore.entriesForCanonicalUris(ImmutableList.of("oldItemUri"))); assertEquals(ContentCategory.CHILD_ITEM, newEntry.lookupRef().category()); assertTrue(newEntry.directEquivalents().contains(secondEntry.lookupRef())); assertTrue(newEntry.equivalents().contains(secondEntry.lookupRef())); assertEquals(firstEntry.created(), newEntry.created()); assertTrue(Iterables.isEmpty(entryStore.entriesForCanonicalUris(ImmutableList.of("oldItemAlias")))); LookupEntry transtiveEntry = Iterables.getOnlyElement(entryStore.entriesForCanonicalUris(ImmutableList.of("transitiveUri"))); assertEquals(ContentCategory.CHILD_ITEM, Iterables.find(transtiveEntry.equivalents(), equalTo(newEntry.lookupRef())).category()); assertEquals(ContentCategory.CHILD_ITEM, Iterables.find(transtiveEntry.directEquivalents(), equalTo(newEntry.lookupRef())).category()); assertEquals(transtiveEntry.created(), secondEntry.created()); assertTrue(Iterables.isEmpty(entryStore.entriesForCanonicalUris(ImmutableList.of("transitiveAlias")))); } @Test public void testEnsureLookupChangesTypeForNonTopLevelSeries() { Series series = new Series("seriesUri","seriesCurie", Publisher.BBC); entryStore.ensureLookup(series); LookupEntry newEntry = Iterables.getOnlyElement(entryStore.entriesForCanonicalUris(ImmutableList.of(series.getCanonicalUri()))); assertEquals(ContentCategory.CONTAINER, newEntry.lookupRef().category()); series = new Series("seriesUri","seriesCurie", Publisher.BBC); series.setParent(new Brand("brandUri","brandCurie",Publisher.BBC)); entryStore.ensureLookup(series); LookupEntry changedEntry = Iterables.getOnlyElement(entryStore.entriesForCanonicalUris(ImmutableList.of(series.getCanonicalUri()))); assertEquals(ContentCategory.PROGRAMME_GROUP, changedEntry.lookupRef().category()); } @Test public void testEnsureLookupRewritesEntryWhenAliasesChange() { Brand brand = new Brand("brandUri", "brandCurie", Publisher.BBC_REDUX); brand.addAliasUrl("brandAlias"); entryStore.ensureLookup(brand); brand.addAliasUrl("anotherBrandAlias"); entryStore.ensureLookup(brand); LookupEntry entry = Iterables.getOnlyElement(entryStore.entriesForCanonicalUris(ImmutableList.of(brand.getCanonicalUri()))); assertThat(entry.aliasUrls().size(), is(3)); assertThat(entry.aliasUrls(), hasItems("brandUri", "brandAlias", "anotherBrandAlias")); brand.setAliasUrls(ImmutableSet.of("anotherBrandAlias")); entryStore.ensureLookup(brand); entry = Iterables.getOnlyElement(entryStore.entriesForCanonicalUris(ImmutableList.of(brand.getCanonicalUri()))); assertThat(entry.aliasUrls().size(), is(2)); assertThat(entry.aliasUrls(), hasItems("brandUri", "anotherBrandAlias")); } // Ensure that for a lookup with 2 aliases : // { // namespace : "a", // value : "b" // }, // { // namespace : "c", // value : "d" // }, // a lookup for namespace = "a" and value = "b" does not succeed, i.e. lookup both values together @Test public void testEnsureMatchingNamespaceValueLookup() { Item testItemOne = new Item("testItemOneUri", "testItem1Curie", Publisher.BBC); testItemOne.addAlias(new Alias("a", "b")); testItemOne.addAlias(new Alias("c", "d")); LookupEntry testEntryOne = LookupEntry.lookupEntryFrom(testItemOne); entryStore.store(testEntryOne); Iterable<LookupEntry> aliasEntry = entryStore.entriesForAliases(Optional.of("a"), ImmutableList.of("b")); assertEquals(testEntryOne, Iterables.getOnlyElement(aliasEntry)); aliasEntry = entryStore.entriesForAliases(Optional.of("a"), ImmutableList.of("d")); assertTrue(Iterables.isEmpty(aliasEntry)); aliasEntry = entryStore.entriesForAliases(Optional.of("c"), ImmutableList.of("b")); assertTrue(Iterables.isEmpty(aliasEntry)); aliasEntry = entryStore.entriesForAliases(Optional.of("c"), ImmutableList.of("d")); assertEquals(testEntryOne, Iterables.getOnlyElement(aliasEntry)); } @Test public void testMultipleValueLookup() { Item testItemOne = new Item("testItemOneUri", "testItem1Curie", Publisher.BBC); testItemOne.addAlias(new Alias("namespace", "valueOne")); Item testItemTwo = new Item("testItemTwoUri", "testItem2Curie", Publisher.BBC); testItemTwo.addAlias(new Alias("namespace", "valueTwo")); LookupEntry testEntryOne = LookupEntry.lookupEntryFrom(testItemOne); LookupEntry testEntryTwo = LookupEntry.lookupEntryFrom(testItemTwo); entryStore.store(testEntryOne); entryStore.store(testEntryTwo); Iterable<LookupEntry> aliasEntry = entryStore.entriesForAliases(Optional.of("namespace"), ImmutableList.of("valueOne", "valueTwo")); LookupEntry first = Iterables.get(aliasEntry, 0); LookupEntry second = Iterables.get(aliasEntry, 1); assertThat(first, isOneOf(testEntryOne, testEntryTwo)); assertThat(second, isOneOf(testEntryOne, testEntryTwo)); assertThat(first, is(not(second))); aliasEntry = entryStore.entriesForAliases(Optional.<String>absent(), ImmutableList.of("valueOne", "valueTwo")); first = Iterables.get(aliasEntry, 0); second = Iterables.get(aliasEntry, 1); assertThat(first, isOneOf(testEntryOne, testEntryTwo)); assertThat(second, isOneOf(testEntryOne, testEntryTwo)); assertThat(first, is(not(second))); } @Test public void testLookupIdsByUri() { Item testItemOne = new Item("testItemOneUri", "testItem1Curie", Publisher.BBC); testItemOne.setId(1L); Item testItemTwo = new Item("testItemTwoUri", "testItem2Curie", Publisher.BBC); testItemTwo.setId(2L); Item testItemThree = new Item("testItemThreeUri", "testItem3Curie", Publisher.BBC); entryStore.store(LookupEntry.lookupEntryFrom(testItemOne)); entryStore.store(LookupEntry.lookupEntryFrom(testItemTwo)); entryStore.store(LookupEntry.lookupEntryFrom(testItemThree)); Map<String, Long> idsForCanonicalUris = entryStore.idsForCanonicalUris( ImmutableSet.of(testItemOne.getCanonicalUri(), testItemTwo.getCanonicalUri(), testItemThree.getCanonicalUri())); assertThat(idsForCanonicalUris.get(testItemOne.getCanonicalUri()), is(testItemOne.getId())); assertThat(idsForCanonicalUris.get(testItemTwo.getCanonicalUri()), is(testItemTwo.getId())); assertThat(idsForCanonicalUris.size(), is(2)); } @Test // This isn't an ideal way of asserting the correct behaviour, but as // a DbCollection isn't an interface, we can't mock it out. // // I decided to add a marker field to confirm that a second save() is // not performed. This is slightly fragile as if we change the // implementation in future to do an update() then we'd not catch a // rogue save, so I decided to also check the log messages. String checking // is obviously quite fragile but solves the case of a future change to // using update() public void testNoWriteIfEntrySame() { String uri = "testItemOneUri"; Item testItemOne = new Item(uri, "testItem1Curie", Publisher.BBC); testItemOne.setId(1L); entryStore.store(LookupEntry.lookupEntryFrom(testItemOne)); String MARKER_FIELD = "not_updated"; String MARKER_VALUE = "x"; collection.update( new MongoQueryBuilder().idEquals(uri).build(), new MongoUpdateBuilder().setField(MARKER_FIELD, MARKER_VALUE).build() ); entryStore.store(LookupEntry.lookupEntryFrom(testItemOne)); ArgumentCaptor<String> arg1captor = ArgumentCaptor.forClass(String.class); ArgumentCaptor<String> arg2captor = ArgumentCaptor.forClass(String.class); verify(log, atLeastOnce()).debug(arg1captor.capture(), arg2captor.capture()); assertEquals( MARKER_VALUE, collection.findOne(new MongoQueryBuilder().idEquals(uri).build()).get(MARKER_FIELD) ); assertEquals( ImmutableList.of("New entry or hash code changed for URI {}; writing", "Hash code not changed for URI {}; skipping write"), arg1captor.getAllValues() ); } @Test public void testEnsureLookupWritesWhenActivelyPublishedChanges() { Item item = new Item("http://example.org/item", "testItem1Curie", Publisher.BBC); entryStore.ensureLookup(item); LookupEntry entry = Iterables.getOnlyElement(entryStore.entriesForCanonicalUris(ImmutableList.of(item.getCanonicalUri()))); assertTrue(entry.activelyPublished()); item.setActivelyPublished(false); entryStore.ensureLookup(item); entry = Iterables.getOnlyElement(entryStore.entriesForCanonicalUris(ImmutableList.of(item.getCanonicalUri()))); assertFalse(entry.activelyPublished()); } @Test public void testEntriesForPublishersReturnsItemsForRequestedPublishers() throws Exception { LookupEntry shouldReturnFirst = getLookupEntry("uriA", 0L, Publisher.BBC); LookupEntry shouldReturnSecond = getLookupEntry("uriB", 1L, Publisher.BBC); LookupEntry shouldNotReturn = getLookupEntry("uriC", 2L, Publisher.METABROADCAST); entryStore.store(shouldReturnFirst); entryStore.store(shouldReturnSecond); entryStore.store(shouldNotReturn); Iterable<LookupEntry> entries = entryStore.allEntriesForPublishers( ImmutableList.of(Publisher.BBC), ContentListingProgress.START ); assertThat(Iterables.size(entries), is(2)); assertThat(Iterables.get(entries, 0).uri(), is(shouldReturnFirst.uri())); assertThat(Iterables.get(entries, 1).uri(), is(shouldReturnSecond.uri())); } @Test public void testEntriesForPublishersReturnsNonPublishedItems() throws Exception { LookupEntry published = getLookupEntry("uriA", 0L, Publisher.BBC); Item unpublishedItem = new Item("uriB", "uriB", Publisher.BBC); unpublishedItem.setId(1L); unpublishedItem.setActivelyPublished(false); LookupEntry unpublished = LookupEntry.lookupEntryFrom(unpublishedItem); entryStore.store(published); entryStore.store(unpublished); Iterable<LookupEntry> entries = entryStore.allEntriesForPublishers( ImmutableList.of(Publisher.BBC), ContentListingProgress.START ); assertThat(Iterables.size(entries), is(2)); assertThat(Iterables.get(entries, 0).uri(), is(published.uri())); assertThat(Iterables.get(entries, 1).uri(), is(unpublished.uri())); } @Test public void testEntriesForPublishersWithProgressReturnsRemainingItems() throws Exception { LookupEntry first = getLookupEntry("uriA", 0L, Publisher.BBC); LookupEntry second = getLookupEntry("uriB", 1L, Publisher.BBC); Item lastOneProcessed = new Item("uriC", "uriC", Publisher.METABROADCAST); lastOneProcessed.setId(2L); LookupEntry third = LookupEntry.lookupEntryFrom(lastOneProcessed); LookupEntry fourth = getLookupEntry("uriD", 3L, Publisher.METABROADCAST); LookupEntry fifth = getLookupEntry("uriE", 4L, Publisher.CANARY); entryStore.store(first); entryStore.store(second); entryStore.store(third); entryStore.store(fourth); entryStore.store(fifth); ImmutableList<Publisher> publishers = ImmutableList.of( Publisher.BBC, Publisher.METABROADCAST, Publisher.CANARY ); Iterable<LookupEntry> entries = entryStore.allEntriesForPublishers( publishers, ContentListingProgress.progressFrom(lastOneProcessed) ); assertThat(Iterables.size(entries), is(2)); assertThat(Iterables.get(entries, 0).uri(), is(fourth.uri())); assertThat(Iterables.get(entries, 1).uri(), is(fifth.uri())); } @Test public void testEntriesForPublishersWithProgressReturnsEverythingForInvalidProgress() throws Exception { LookupEntry first = getLookupEntry("uriA", 0L, Publisher.BBC); LookupEntry second = getLookupEntry("uriB", 1L, Publisher.BBC); entryStore.store(first); entryStore.store(second); ImmutableList<Publisher> publishers = ImmutableList.of( Publisher.BBC, Publisher.METABROADCAST, Publisher.CANARY ); Item invalidProgressedToItem = new Item("uri", "uri", Publisher.BBC); invalidProgressedToItem.setId(100L); Iterable<LookupEntry> entries = entryStore.allEntriesForPublishers( publishers, ContentListingProgress.progressFrom(invalidProgressedToItem) ); assertThat(Iterables.size(entries), is(2)); assertThat(Iterables.get(entries, 0).uri(), is(first.uri())); assertThat(Iterables.get(entries, 1).uri(), is(second.uri())); } private LookupEntry getLookupEntry(String uri, long id, Publisher publisher) { Item publishedItem = new Item(uri, uri, publisher); publishedItem.setId(id); return LookupEntry.lookupEntryFrom(publishedItem); } }