package org.atlasapi.persistence.lookup; import static org.atlasapi.persistence.lookup.TransitiveLookupWriter.generatedTransitiveLookupWriter; import static org.atlasapi.persistence.lookup.entry.LookupEntry.lookupEntryFrom; import static org.hamcrest.Matchers.hasItems; import static org.mockito.Matchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import junit.framework.TestCase; import org.atlasapi.equiv.ContentRef; import org.atlasapi.media.entity.Brand; import org.atlasapi.media.entity.Content; import org.atlasapi.media.entity.Identified; import org.atlasapi.media.entity.Item; import org.atlasapi.media.entity.LookupRef; import org.atlasapi.media.entity.Publisher; import org.atlasapi.persistence.content.ContentCategory; import org.atlasapi.persistence.lookup.entry.LookupEntry; import org.atlasapi.persistence.lookup.entry.LookupEntryStore; import org.junit.Test; import org.mockito.Mockito; import com.google.common.base.Function; import com.google.common.collect.ContiguousSet; import com.google.common.collect.DiscreteDomain; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Range; public class TransitiveLookupWriterTest extends TestCase { private final LookupEntryStore store = new InMemoryLookupEntryStore(); private final TransitiveLookupWriter writer = generatedTransitiveLookupWriter(store); // Tests that trivial lookups are written reflexively for all content // identifiers public void testWriteNewLookup() { Item item = createItem("test", Publisher.BBC); store.store(LookupEntry.lookupEntryFrom(item)); writeLookup(writer, item, ImmutableSet.<Content> of(), ImmutableSet.of(Publisher.BBC)); LookupEntry uriEntry = Iterables.getOnlyElement(store.entriesForCanonicalUris(ImmutableList.of("testUri"))); assertEquals(item.getCanonicalUri(), uriEntry.uri()); assertEquals(item.getAllUris(), uriEntry.aliasUrls()); assertEquals("testUri", Iterables.getOnlyElement(uriEntry.directEquivalents()).uri()); assertNotNull(uriEntry.created()); assertNotNull(uriEntry.updated()); assertEquals(item.getCanonicalUri(), Iterables.getOnlyElement(uriEntry.equivalents()).uri()); assertEquals(item.getPublisher(), Iterables.getOnlyElement(uriEntry.equivalents()).publisher()); assertEquals(ContentCategory.TOP_LEVEL_ITEM, Iterables.getOnlyElement(uriEntry.equivalents()).category()); LookupEntry aliasEntry = Iterables.getOnlyElement(store.entriesForIdentifiers(ImmutableList.of("testAlias"), true)); assertEquals(aliasEntry, uriEntry); } private Item createItem(String itemName, Publisher publisher) { Item item = new Item(itemName + "Uri", itemName + "Curie", Publisher.BBC); item.addAliasUrl(itemName + "Alias"); item.setPublisher(publisher); return item; } public void testWriteLookup() { Item paItem = createItem("test1", Publisher.PA); Item bbcItem = createItem("test2", Publisher.BBC); Item c4Item = createItem("test3", Publisher.C4); Set<Publisher> publishers = ImmutableSet.of(Publisher.PA, Publisher.BBC, Publisher.C4, Publisher.ITUNES); // Inserts reflexive entries for items PA, BBC, C4 store.store(LookupEntry.lookupEntryFrom(paItem)); store.store(LookupEntry.lookupEntryFrom(bbcItem)); store.store(LookupEntry.lookupEntryFrom(c4Item)); // Make items BBC and C4 equivalent. writeLookup(writer, bbcItem, ImmutableSet.<Content> of(c4Item), publishers); hasEquivs(paItem, paItem); hasDirectEquivs(paItem, paItem); hasEquivs(bbcItem, bbcItem, c4Item); hasDirectEquivs(bbcItem, bbcItem, c4Item); hasEquivs(c4Item, bbcItem, c4Item); hasDirectEquivs(c4Item, bbcItem, c4Item); // Make items PA and BBC equivalent, so all three are transitively // equivalent writeLookup(writer, paItem, ImmutableSet.<Content> of(bbcItem), publishers); hasEquivs(paItem, bbcItem, c4Item, paItem); hasDirectEquivs(paItem, paItem, bbcItem); hasEquivs(bbcItem, bbcItem, c4Item, paItem); hasDirectEquivs(bbcItem, bbcItem, c4Item, paItem); hasEquivs(c4Item, bbcItem, c4Item, paItem); hasDirectEquivs(c4Item, bbcItem, c4Item); // Make item PA equivalent to nothing. Item PA just reflexive, item BBC and // C4 still equivalent. writeLookup(writer, paItem, ImmutableSet.<Content> of(), publishers); hasEquivs(paItem, paItem); hasDirectEquivs(paItem, paItem); hasEquivs(bbcItem, bbcItem, c4Item); hasDirectEquivs(bbcItem, bbcItem, c4Item); hasEquivs(c4Item, bbcItem, c4Item); hasDirectEquivs(c4Item, bbcItem, c4Item); // Make PA and BBC equivalent again. writeLookup(writer, paItem, ImmutableSet.<Content> of(bbcItem), publishers); hasEquivs(paItem, bbcItem, c4Item, paItem); hasDirectEquivs(paItem, paItem, bbcItem); hasEquivs(bbcItem, bbcItem, c4Item, paItem); hasDirectEquivs(bbcItem, bbcItem, c4Item, paItem); hasEquivs(c4Item, bbcItem, c4Item, paItem); hasDirectEquivs(c4Item, bbcItem, c4Item); // Add a new item from Itunes. Item itunesItem = createItem("test4", Publisher.ITUNES); store.store(LookupEntry.lookupEntryFrom(itunesItem)); // Make PA equivalent to just Itunes, instead of BBC. PA and Itunes equivalent, BBC and // C4 equivalent. writeLookup(writer, paItem, ImmutableSet.<Content> of(itunesItem), publishers); hasEquivs(paItem, paItem, itunesItem); hasDirectEquivs(paItem, paItem, itunesItem); hasEquivs(bbcItem, bbcItem, c4Item); hasDirectEquivs(bbcItem, bbcItem, c4Item); hasEquivs(c4Item, bbcItem, c4Item); hasDirectEquivs(c4Item, bbcItem, c4Item); hasEquivs(itunesItem, paItem, itunesItem); hasDirectEquivs(itunesItem, paItem, itunesItem); // Make all items equivalent. writeLookup(writer, paItem, ImmutableSet.<Content> of(c4Item, itunesItem), publishers); hasEquivs(paItem, paItem, bbcItem, c4Item, itunesItem); hasDirectEquivs(paItem, paItem, c4Item, itunesItem); hasEquivs(bbcItem, paItem, bbcItem, c4Item, itunesItem); hasDirectEquivs(bbcItem, bbcItem, c4Item); hasEquivs(c4Item, paItem, bbcItem, c4Item, itunesItem); hasDirectEquivs(c4Item, paItem, bbcItem, c4Item); hasEquivs(itunesItem, paItem, bbcItem, c4Item, itunesItem); hasDirectEquivs(itunesItem, paItem, itunesItem); } protected void writeLookup(TransitiveLookupWriter writer, Content subject, ImmutableSet<? extends Content> equivs, Set<Publisher> publishers) { writer.writeLookup(ContentRef.valueOf(subject), Iterables.transform(equivs, new Function<Content, ContentRef>() { @Override public ContentRef apply(@Nullable Content input) { return ContentRef.valueOf(input); } }), publishers); } private void hasEquivs(Content id, Content... transitiveEquivs) { LookupEntry entry = Iterables.getOnlyElement(store.entriesForCanonicalUris(ImmutableList.of(id.getCanonicalUri()))); assertEquals(ImmutableSet.copyOf(Iterables.transform(ImmutableSet.copyOf(transitiveEquivs),Identified.TO_URI)), ImmutableSet.copyOf(Iterables.transform(entry.equivalents(), LookupRef.TO_URI))); } private void hasDirectEquivs(Content id, Content... directEquivs) { LookupEntry entry = Iterables.getOnlyElement(store.entriesForCanonicalUris(ImmutableList.of(id.getCanonicalUri()))); assertEquals(ImmutableSet.copyOf(Iterables.transform(ImmutableSet.copyOf(directEquivs),Identified.TO_URI)), ImmutableSet.copyOf(Iterables.transform(entry.directEquivalents(), LookupRef.TO_URI))); } public void testBreakingEquivs() { Brand pivot = new Brand("pivot", "cpivot", Publisher.PA); Brand left = new Brand("left", "cleft", Publisher.PA); Brand right = new Brand("right", "cright", Publisher.PA); store.store(LookupEntry.lookupEntryFrom(pivot)); store.store(LookupEntry.lookupEntryFrom(left)); store.store(LookupEntry.lookupEntryFrom(right)); Set<Publisher> publishers = ImmutableSet.of(Publisher.PA); writeLookup(writer, pivot, ImmutableSet.of(left,right), publishers); writeLookup(writer, left, ImmutableSet.of(right), publishers); writeLookup(writer, pivot, ImmutableSet.of(left), publishers); writeLookup(writer, left, ImmutableSet.<Content>of(), publishers); hasEquivs(pivot, pivot); } public void testDoesntWriteEquivalentsForContentOfIgnoredPublishers() { Item paItem = createItem("paItem",Publisher.PA); Item c4Item = createItem("c4Item",Publisher.C4); store.store(LookupEntry.lookupEntryFrom(paItem)); store.store(LookupEntry.lookupEntryFrom(c4Item)); writeLookup(writer, paItem, ImmutableSet.<Content> of(), ImmutableSet.of(Publisher.PA)); writeLookup(writer, c4Item, ImmutableSet.<Content> of(), ImmutableSet.of(Publisher.C4)); hasEquivs(paItem, paItem); hasEquivs(c4Item, c4Item); writeLookup(writer, paItem, ImmutableSet.of(c4Item), ImmutableSet.of(Publisher.PA, Publisher.BBC)); hasEquivs(paItem, paItem); hasEquivs(c4Item, c4Item); hasEquivs(c4Item, c4Item); } public void testDoesntBreakEquivalenceForContentOfIgnoredPublishers() { Item paItem = createItem("paItem1",Publisher.PA); Item c4Item = createItem("c4Item1",Publisher.C4); Item bbcItem = createItem("bbcItem1",Publisher.BBC); Item fiveItem = createItem("fiveItem1", Publisher.FIVE); store.store(LookupEntry.lookupEntryFrom(paItem)); store.store(LookupEntry.lookupEntryFrom(c4Item)); store.store(LookupEntry.lookupEntryFrom(bbcItem)); store.store(LookupEntry.lookupEntryFrom(fiveItem)); writeLookup(writer, paItem, ImmutableSet.<Content> of(), ImmutableSet.of(Publisher.PA)); writeLookup(writer, c4Item, ImmutableSet.<Content> of(), ImmutableSet.of(Publisher.C4)); writeLookup(writer, bbcItem, ImmutableSet.<Content> of(), ImmutableSet.of(Publisher.BBC)); writeLookup(writer, fiveItem, ImmutableSet.<Content> of(), ImmutableSet.of(Publisher.FIVE)); //Make PA and BBC equivalent writeLookup(writer, paItem, ImmutableSet.of(bbcItem), ImmutableSet.of(Publisher.PA, Publisher.BBC)); hasEquivs(paItem, paItem, bbcItem); hasDirectEquivs(paItem, paItem, bbcItem); hasEquivs(bbcItem, bbcItem, paItem); hasDirectEquivs(bbcItem, bbcItem, paItem); hasEquivs(c4Item, c4Item); hasDirectEquivs(c4Item, c4Item); hasEquivs(fiveItem, fiveItem); hasDirectEquivs(fiveItem, fiveItem); //Make PA and C4 equivalent, ignoring BBC content. PA, BBC, C4 all equivalent. writeLookup(writer, paItem, ImmutableSet.of(c4Item), ImmutableSet.of(Publisher.PA, Publisher.C4)); hasEquivs(paItem, paItem, bbcItem, c4Item); hasDirectEquivs(paItem, paItem, bbcItem, c4Item); hasEquivs(bbcItem, bbcItem, paItem, c4Item); hasDirectEquivs(bbcItem, paItem, bbcItem); hasEquivs(c4Item, c4Item, paItem, bbcItem); hasDirectEquivs(c4Item, paItem, c4Item); hasEquivs(fiveItem, fiveItem); hasDirectEquivs(fiveItem, fiveItem); //Make PA and 5 equivalent, including C4 content. PA, BBC, 5 all equivalent. writeLookup(writer, paItem, ImmutableSet.of(fiveItem), ImmutableSet.of(Publisher.PA, Publisher.C4, Publisher.FIVE)); hasEquivs(paItem, paItem, bbcItem, fiveItem); hasDirectEquivs(paItem, paItem, bbcItem, fiveItem); hasEquivs(bbcItem, bbcItem, paItem, fiveItem); hasDirectEquivs(bbcItem, paItem, bbcItem); hasEquivs(c4Item, c4Item); hasDirectEquivs(c4Item, c4Item); hasEquivs(fiveItem, fiveItem, bbcItem, paItem); hasDirectEquivs(fiveItem, fiveItem, paItem); } public void testDoesntBreakEquivalenceForContentOfIgnoredPublishersWhenLinkingItemIsNotSubject() { Item paItem = createItem("paItem2",Publisher.PA); Item pnItem = createItem("pnItem2",Publisher.PREVIEW_NETWORKS); Item bbcItem = createItem("bbcItem2",Publisher.BBC); store.store(LookupEntry.lookupEntryFrom(paItem)); store.store(LookupEntry.lookupEntryFrom(pnItem)); store.store(LookupEntry.lookupEntryFrom(bbcItem)); //Make PA and BBC equivalent writeLookup(writer, paItem, ImmutableSet.of(bbcItem), ImmutableSet.of(Publisher.PA, Publisher.BBC)); writeLookup(writer, pnItem, ImmutableSet.of(paItem), ImmutableSet.of(Publisher.PREVIEW_NETWORKS, Publisher.PA)); hasEquivs(paItem, paItem, bbcItem, pnItem); hasDirectEquivs(paItem, paItem, bbcItem, pnItem); hasEquivs(bbcItem, bbcItem, paItem, pnItem); hasDirectEquivs(bbcItem, paItem, bbcItem); hasEquivs(pnItem, pnItem, paItem, bbcItem); hasDirectEquivs(pnItem, paItem, pnItem); } public void testDoesntWriteEquivalencesWhenEquivalentsDontChange() { LookupEntryStore store = mock(LookupEntryStore.class); TransitiveLookupWriter writer = generatedTransitiveLookupWriter(store); Item paItem = createItem("paItem3",Publisher.PA); Item pnItem = createItem("pnItem3",Publisher.PREVIEW_NETWORKS); LookupEntry paLookupEntry = lookupEntryFrom(paItem).copyWithDirectEquivalents(ImmutableList.of(LookupRef.from(pnItem))); LookupEntry pnLookupEntry = lookupEntryFrom(pnItem).copyWithDirectEquivalents(ImmutableList.of(LookupRef.from(paItem))); when(store.entriesForCanonicalUris(ImmutableSet.of(pnItem.getCanonicalUri(), paItem.getCanonicalUri()))) .thenReturn(ImmutableList.of(paLookupEntry, pnLookupEntry)); when(store.entriesForCanonicalUris(ImmutableList.of(paItem.getCanonicalUri()))) .thenReturn(ImmutableList.of(paLookupEntry)); writer.writeLookup(ContentRef.valueOf(paItem), ImmutableSet.of(ContentRef.valueOf(pnItem)), ImmutableSet.of(Publisher.PA, Publisher.PREVIEW_NETWORKS)); verify(store).entriesForCanonicalUris(ImmutableSet.of(pnItem.getCanonicalUri(), paItem.getCanonicalUri())); verify(store).entriesForCanonicalUris(ImmutableList.of(paItem.getCanonicalUri())); verify(store, never()).store(Mockito.isA(LookupEntry.class)); Mockito.validateMockitoUsage(); } public void testCanRunTwoWriteSimultaneously() throws InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(2); final Item one = createItem("one",Publisher.BBC); final Item two = createItem("two",Publisher.PA); final Item three = createItem("three",Publisher.ITV); final Item four = createItem("four",Publisher.C4); final Item five = createItem("five",Publisher.FIVE); store.store(LookupEntry.lookupEntryFrom(one)); store.store(LookupEntry.lookupEntryFrom(two)); store.store(LookupEntry.lookupEntryFrom(three)); store.store(LookupEntry.lookupEntryFrom(four)); store.store(LookupEntry.lookupEntryFrom(five)); writeLookup(writer, three, ImmutableSet.of(two, four), Publisher.all()); final CountDownLatch start = new CountDownLatch(1); final CountDownLatch finish= new CountDownLatch(2); executor.submit(new Callable<Void>() { @Override public Void call() throws InterruptedException { start.await(); writeLookup(writer, one, ImmutableSet.of(two), ImmutableSet.of(Publisher.BBC, Publisher.PA)); finish.countDown(); return null; } }); executor.submit(new Callable<Void>() { @Override public Void call() throws InterruptedException { start.await(); writeLookup(writer, four, ImmutableSet.of(five), ImmutableSet.of(Publisher.C4, Publisher.FIVE)); finish.countDown(); return null; } }); start.countDown(); assertTrue(finish.await(1, TimeUnit.SECONDS)); hasEquivs(one, one, three, two, four, five); hasDirectEquivs(one, one, two); hasEquivs(two, one, three, two, four, five); hasDirectEquivs(two, one, two, three); hasEquivs(three, one, three, two, four, five); hasDirectEquivs(three, two, three, four); hasEquivs(four, one, three, two, four, five); hasDirectEquivs(four, three, four, five); hasEquivs(five, one, three, two, four, five); hasDirectEquivs(five, four, five); } @Test public void testAbortsWriteWhenSetTooLarge() { LookupEntryStore store = mock(LookupEntryStore.class); TransitiveLookupWriter writer = generatedTransitiveLookupWriter(store); Item big = createItem("big", Publisher.BBC); Item equiv = createItem("equiv", Publisher.PA); LookupEntry bigEntry = LookupEntry.lookupEntryFrom(big); LookupEntry equivEntry = LookupEntry.lookupEntryFrom(equiv); bigEntry = bigEntry.copyWithEquivalents(Iterables.transform( ContiguousSet.create(Range.closedOpen(0, 1499), DiscreteDomain.integers()), new Function<Integer, LookupRef>() { @Override public LookupRef apply(Integer input) { return new LookupRef(input+"Uri", input.longValue(), Publisher.BBC_REDUX, ContentCategory.CHILD_ITEM); } } )); when(store.entriesForCanonicalUris(argThat(hasItems(big.getCanonicalUri(), equiv.getCanonicalUri())))) .thenReturn(ImmutableList.of(bigEntry, equivEntry)); writeLookup(writer, equiv, ImmutableSet.of(big), Publisher.all()); verify(store).entriesForCanonicalUris(argThat(hasItems(big.getCanonicalUri(), equiv.getCanonicalUri()))); verify(store, never()).store(Mockito.isA(LookupEntry.class)); Mockito.validateMockitoUsage(); } }