package org.molgenis.data.cache.l1; import com.google.common.collect.Sets; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.molgenis.data.*; import org.molgenis.data.meta.model.Attribute; import org.molgenis.data.meta.model.AttributeFactory; import org.molgenis.data.meta.model.EntityType; import org.molgenis.data.meta.model.EntityTypeFactory; import org.molgenis.data.support.DynamicEntity; import org.molgenis.data.support.LazyEntity; import org.molgenis.test.data.AbstractMolgenisSpringTest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Arrays.asList; import static java.util.Optional.empty; import static java.util.Optional.of; import static junit.framework.Assert.assertNull; import static org.mockito.Mockito.*; import static org.mockito.MockitoAnnotations.initMocks; import static org.molgenis.data.EntityKey.create; import static org.molgenis.data.RepositoryCapability.CACHEABLE; import static org.molgenis.data.RepositoryCapability.WRITABLE; import static org.molgenis.data.meta.AttributeType.ONE_TO_MANY; import static org.molgenis.data.meta.AttributeType.XREF; import static org.molgenis.data.meta.model.EntityType.AttributeRole.ROLE_ID; import static org.testng.Assert.assertEquals; @ContextConfiguration(classes = L1CacheRepositoryDecoratorTest.Config.class) public class L1CacheRepositoryDecoratorTest extends AbstractMolgenisSpringTest { private L1CacheRepositoryDecorator l1CacheRepositoryDecorator; private EntityType authorMetaData; private EntityType bookMetaData; private final String authorEntityName = "Author"; private final String bookEntityName = "Book"; private final String authorID = "1"; private final String authorID2 = "2"; private final String bookID = "b1"; private final String bookID2 = "b2"; private Entity author; private Entity author2; @Autowired private EntityTypeFactory entityTypeFactory; @Autowired private AttributeFactory attributeFactory; @Mock private DataService dataService; @Mock private L1Cache l1Cache; @Mock private Repository<Entity> authorRepository; @Mock private Repository<Entity> bookRepository; @Captor private ArgumentCaptor<Stream<Entity>> entitiesCaptor; @Captor private ArgumentCaptor<Stream<Object>> entityIdsCaptor; @Captor private ArgumentCaptor<Stream<EntityKey>> entityKeysCaptor; @BeforeClass public void beforeClass() { initMocks(this); authorMetaData = entityTypeFactory.create(authorEntityName); bookMetaData = entityTypeFactory.create(bookEntityName); authorMetaData.addAttribute(attributeFactory.create().setName("ID"), ROLE_ID); authorMetaData.addAttribute(attributeFactory.create().setName("name")); Attribute authorAttribute = attributeFactory.create().setName("author").setDataType(XREF) .setRefEntity(authorMetaData); authorMetaData.addAttribute( attributeFactory.create().setName("books").setDataType(ONE_TO_MANY).setMappedBy(authorAttribute) .setRefEntity(bookMetaData)); bookMetaData.addAttribute(attributeFactory.create().setName("ID"), ROLE_ID); bookMetaData.addAttribute(attributeFactory.create().setName("title")); bookMetaData.addAttribute(authorAttribute); author = new DynamicEntity(authorMetaData); author.set("ID", authorID); author.set("name", "Terry Pratchett"); author.set("books", Arrays.asList(new LazyEntity(bookMetaData, dataService, bookID), new LazyEntity(bookMetaData, dataService, bookID2))); author2 = new DynamicEntity(authorMetaData); author2.set("ID", authorID2); author2.set("name", "Stephen King"); when(authorRepository.getCapabilities()).thenReturn(Sets.newHashSet(CACHEABLE, WRITABLE)); when(authorRepository.getName()).thenReturn(authorEntityName); when(authorRepository.getEntityType()).thenReturn(authorMetaData); l1CacheRepositoryDecorator = new L1CacheRepositoryDecorator(authorRepository, l1Cache); } @BeforeMethod public void beforeMethod() { reset(l1Cache); } @Test public void testAdd() { l1CacheRepositoryDecorator.add(author); verify(l1Cache).put(authorEntityName, author); verify(l1Cache).evict(entityKeysCaptor.capture()); assertEquals(entityKeysCaptor.getValue().collect(Collectors.toList()), Arrays.asList(EntityKey.create(bookEntityName, bookID), EntityKey.create(bookEntityName, bookID2))); verifyNoMoreInteractions(l1Cache); } @Test public void testAddWithStreamOfEntities() { l1CacheRepositoryDecorator.add(Stream.of(author)); verify(authorRepository).add(entitiesCaptor.capture()); entitiesCaptor.getValue().collect(Collectors.toList()); verify(l1Cache).put(authorEntityName, author); verify(l1Cache).evictAll(bookEntityName); verifyNoMoreInteractions(l1Cache); } @Test public void testDelete() { l1CacheRepositoryDecorator.delete(author); verify(l1Cache).putDeletion(create(author)); verify(l1Cache).evict(entityKeysCaptor.capture()); assertEquals(entityKeysCaptor.getValue().collect(Collectors.toList()), Arrays.asList(EntityKey.create(bookEntityName, bookID), EntityKey.create(bookEntityName, bookID2))); verifyNoMoreInteractions(l1Cache); } @Test public void testDeleteById() { l1CacheRepositoryDecorator.deleteById(authorID); verify(l1Cache).putDeletion(create(author)); verify(l1Cache).evictAll(bookEntityName); verifyNoMoreInteractions(l1Cache); } @Test public void testDeleteByStreamOfEntities() { l1CacheRepositoryDecorator.delete(Stream.of(author)); verify(authorRepository).delete(entitiesCaptor.capture()); entitiesCaptor.getValue().collect(Collectors.toList()); verify(l1Cache).putDeletion(EntityKey.create(author)); verify(l1Cache).evictAll(bookEntityName); verifyNoMoreInteractions(l1Cache); } @Test public void testDeleteAll() { l1CacheRepositoryDecorator.deleteAll(); verify(l1Cache).evictAll(authorEntityName); verify(l1Cache).evictAll(bookEntityName); verifyNoMoreInteractions(l1Cache); } @Test public void testDeleteAllByStreamOfIds() { l1CacheRepositoryDecorator.deleteAll(Stream.of(authorID)); verify(authorRepository).deleteAll(entityIdsCaptor.capture()); entityIdsCaptor.getValue().collect(Collectors.toList()); verify(l1Cache).putDeletion(EntityKey.create(authorEntityName, authorID)); verify(l1Cache).evictAll(bookEntityName); verifyNoMoreInteractions(l1Cache); } @Test public void testUpdate() { l1CacheRepositoryDecorator.update(author); verify(l1Cache).put(authorEntityName, author); verify(authorRepository).update(author); verify(l1Cache).evictAll(bookEntityName); verifyNoMoreInteractions(l1Cache); } @Test public void testUpdateWithStreamOfEntities() { l1CacheRepositoryDecorator.update(Stream.of(author)); verify(authorRepository).update(entitiesCaptor.capture()); entitiesCaptor.getValue().collect(Collectors.toList()); verify(l1Cache).put(authorEntityName, author); verify(l1Cache).evictAll(bookEntityName); verifyNoMoreInteractions(l1Cache); } @Test public void testFindOneByIdReturnsEntity() { when(l1Cache.get(authorEntityName, authorID, authorMetaData)).thenReturn(of(author)); Entity actualEntity = l1CacheRepositoryDecorator.findOneById(authorID); assertEquals(actualEntity, author); verify(authorRepository, never()).findOneById(Mockito.any()); } @Test public void testFindOneByIdReturnsEmpty() { when(l1Cache.get(authorEntityName, authorID, authorMetaData)).thenReturn(empty()); Entity actualEntity = l1CacheRepositoryDecorator.findOneById(authorID); assertNull(actualEntity); verify(authorRepository, never()).findOneById(Mockito.any()); } @Test public void testFindOneByIdReturnsNull() { when(l1Cache.get(authorEntityName, authorID, authorMetaData)).thenReturn(null); Entity actualEntity = l1CacheRepositoryDecorator.findOneById(authorID); assertNull(actualEntity); verify(authorRepository).findOneById(authorID); } @Test public void testFindAllByStreamOfIdsEntityNotPresentInCache() { when(authorRepository.findAll(entityIdsCaptor.capture())).thenReturn(Stream.of(author, author2)); List<Entity> actual = l1CacheRepositoryDecorator.findAll(Stream.of(authorID, authorID2)) .collect(Collectors.toList()); assertEquals(asList(author, author2), actual); List<Object> ids = entityIdsCaptor.getValue().collect(Collectors.toList()); assertEquals(ids, asList(authorID, authorID2)); } @Test public void testFindAllByStreamOfIdsOneCachedOneMissing() { when(l1Cache.get(authorEntityName, authorID, authorMetaData)).thenReturn(of(author)); when(authorRepository.findAll(entityIdsCaptor.capture())).thenReturn(Stream.of(author2)); List<Entity> actual = l1CacheRepositoryDecorator.findAll(Stream.of(authorID, authorID2)) .collect(Collectors.toList()); assertEquals(asList(author, author2), actual); List<Object> ids = entityIdsCaptor.getValue().collect(Collectors.toList()); assertEquals(ids, Collections.singletonList(authorID2)); } @Configuration public static class Config { @Mock private EntityManager entityManager; public Config() { initMocks(this); } @Bean public EntityManager entityManager() { return entityManager; } } }