package com.constellio.model.services.contents; import static com.constellio.data.conf.HashingEncoding.BASE64_URL_ENCODED; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import com.constellio.model.services.migrations.ConstellioEIMConfigs; import org.junit.Before; import org.junit.Test; import org.mockito.InOrder; import org.mockito.Mock; import com.constellio.data.conf.DataLayerConfiguration; import com.constellio.data.dao.dto.records.RecordDTO; import com.constellio.data.dao.dto.records.RecordsFlushing; import com.constellio.data.dao.dto.records.TransactionDTO; import com.constellio.data.dao.services.contents.ContentDao; import com.constellio.data.dao.services.contents.ContentDaoException; import com.constellio.data.dao.services.contents.ContentDaoException.ContentDaoException_NoSuchContent; import com.constellio.data.dao.services.factories.DataLayerFactory; import com.constellio.data.dao.services.idGenerator.UniqueIdGenerator; import com.constellio.data.dao.services.records.RecordDao; import com.constellio.data.io.IOServicesFactory; import com.constellio.data.io.services.facades.IOServices; import com.constellio.data.io.streamFactories.CloseableStreamFactory; import com.constellio.data.io.streamFactories.impl.CopyInputStreamFactory; import com.constellio.data.io.streamFactories.services.one.StreamOperationReturningValueOrThrowingException; import com.constellio.data.io.streamFactories.services.one.StreamOperationThrowingException; import com.constellio.data.utils.hashing.HashingService; import com.constellio.data.utils.hashing.HashingServiceException; import com.constellio.model.conf.ModelLayerConfiguration; import com.constellio.model.entities.Language; import com.constellio.model.entities.records.ParsedContent; import com.constellio.model.entities.records.Record; import com.constellio.model.entities.schemas.MetadataSchemaTypes; import com.constellio.model.services.contents.ContentManagerRuntimeException.ContentManagerRuntimeException_CannotReadInputStream; import com.constellio.model.services.contents.ContentManagerRuntimeException.ContentManagerRuntimeException_NoSuchContent; import com.constellio.model.services.factories.ModelLayerFactory; import com.constellio.model.services.parser.FileParser; import com.constellio.model.services.parser.FileParserException; import com.constellio.model.services.parser.FileParserException.FileParserException_CannotParse; import com.constellio.model.services.records.RecordServices; import com.constellio.model.services.schemas.MetadataSchemasManager; import com.constellio.model.services.search.SearchServices; import com.constellio.sdk.tests.ConstellioTest; @SuppressWarnings("unchecked") public class ContentManagerTest extends ConstellioTest { String zeStreamName = "zeStreamName"; @Mock RecordServices recordServices; @Mock ContentDao contentDao; @Mock RecordDao recordDao; @Mock FileParser fileParser; @Mock HashingService hashingService; @Mock IOServices ioServices; @Mock SearchServices searchServices; @Mock InputStream contentInputStream, firstCreatedStream, secondCreatedStream; @Mock CopyInputStreamFactory streamFactory; @Mock UniqueIdGenerator uniqueIdGenerator; String newContentId = aString(); String theParsedContent = aString(); ContentManager contentManager; @Mock StreamOperationReturningValueOrThrowingException<InputStream, ParsedContent, FileParserException> parseOperation; @Mock StreamOperationThrowingException<InputStream, ContentDaoException> addOperation; @Mock StreamOperationThrowingException<InputStream, ContentDaoException> addParsedContentOperation; @Mock StreamOperationThrowingException<InputStream, ContentDaoException> addWithEmptyContentOperation; @Mock ContentModificationsBuilder contentModificationsBuilder; @Mock MetadataSchemaTypes metadataSchemaTypes; @Mock Record aRecord; @Mock Record anotherRecord; @Mock ContentImpl aContent; @Mock ContentImpl anotherContent; String aContentHash = "aContentHash"; @Mock CopyInputStreamFactory closeableStreamFactory; @Mock InputStream aContentNewVersionInputStream; @Mock InputStream anotherContentNewVersionInputStream; String theMimetype = "mimetype"; @Mock ParsedContent parsingResults; @Mock ParsedContentConverter parsedContentConverter; @Mock IOServicesFactory ioServicesFactory; @Mock DataLayerFactory dataLayerFactory; @Mock MetadataSchemasManager metadataSchemasManager; @Mock ModelLayerConfiguration modelLayerConfiguration; @Mock ModelLayerFactory modelLayerFactory; @Mock DataLayerConfiguration dataLayerConfiguration; @Mock ConstellioEIMConfigs constellioEIMConfigs; @Before public void setUp() throws Exception { when(modelLayerFactory.getDataLayerFactory()).thenReturn(dataLayerFactory); when(modelLayerFactory.newSearchServices()).thenReturn(searchServices); when(modelLayerFactory.newRecordServices()).thenReturn(recordServices); when(modelLayerFactory.getMetadataSchemasManager()).thenReturn(metadataSchemasManager); when(modelLayerFactory.getConfiguration()).thenReturn(modelLayerConfiguration); when(modelLayerFactory.newFileParser()).thenReturn(fileParser); when(modelLayerFactory.getSystemConfigs()).thenReturn(constellioEIMConfigs); when(dataLayerFactory.newRecordDao()).thenReturn(recordDao); when(dataLayerFactory.getContentsDao()).thenReturn(contentDao); when(dataLayerFactory.getIOServicesFactory()).thenReturn(ioServicesFactory); when(dataLayerFactory.getUniqueIdGenerator()).thenReturn(uniqueIdGenerator); when(ioServicesFactory.newHashingService(BASE64_URL_ENCODED)).thenReturn(hashingService); when(ioServicesFactory.newIOServices()).thenReturn(ioServices); when(dataLayerFactory.getDataLayerConfiguration()).thenReturn(dataLayerConfiguration); when(dataLayerConfiguration.getHashingEncoding()).thenReturn(BASE64_URL_ENCODED); contentManager = spy(new ContentManager(modelLayerFactory)); when(ioServices.copyToReusableStreamFactory(contentInputStream, null)).thenReturn(streamFactory); when(streamFactory.create(anyString())).thenReturn(firstCreatedStream).thenReturn(secondCreatedStream) .thenThrow(new Error()); doReturn(addOperation).when(contentManager).addInContentDaoOperation(newContentId); doReturn(addParsedContentOperation).when(contentManager).addInContentDaoOperation(newContentId); doReturn(addWithEmptyContentOperation).when(contentManager).addInContentDaoOperation(newContentId); doReturn(contentModificationsBuilder).when(contentManager).newContentsModificationBuilder(metadataSchemaTypes); when(parsingResults.getParsedContent()).thenReturn(theParsedContent); when(parsingResults.getMimeType()).thenReturn(theMimetype); doReturn(parsedContentConverter).when(contentManager).newParsedContentConverter(); } @Test public void whenGetParsedContentThenAskContentDaoAndConvertResult() throws Exception { String newContentParsedContentId = newContentId + "__parsed"; InputStream parsedContentInputStream = mock(InputStream.class); String parsedContentString = "parsedContentString"; ParsedContent parsedContentObject = mock(ParsedContent.class); when(parsedContentConverter.convertToParsedContent(parsedContentString)).thenReturn(parsedContentObject); when(contentDao.getContentInputStream(newContentParsedContentId, ContentManager.READ_PARSED_CONTENT)).thenReturn( parsedContentInputStream); when(ioServices.readStreamToString(parsedContentInputStream)).thenReturn(parsedContentString); ParsedContent parsedContent = contentManager.getParsedContent(newContentId); assertThat(parsedContent).isSameAs(parsedContentObject); verify(contentDao).getContentInputStream(newContentParsedContentId, ContentManager.READ_PARSED_CONTENT); } @Test(expected = ContentManagerRuntimeException_NoSuchContent.class) public void givenNoParsedContentWhenGetParsedContentThenThrowException() throws Exception { String newContentParsedContentId = newContentId + "__parsed"; String parsedContentString = "parsedContentString"; ParsedContent parsedContentObject = mock(ParsedContent.class); when(parsedContentConverter.convertToParsedContent(parsedContentString)).thenReturn(parsedContentObject); when(contentDao.getContentInputStream(newContentParsedContentId, ContentManager.READ_PARSED_CONTENT)) .thenThrow(ContentDaoException_NoSuchContent.class); contentManager.getParsedContent(newContentId); } @Test(expected = ContentManagerRuntimeException_CannotReadInputStream.class) public void givenIExceptionWhenGetParsedContentThenThrowException() throws Exception { String newContentParsedContentId = newContentId + "__parsed"; String parsedContentString = "parsedContentString"; ParsedContent parsedContentObject = mock(ParsedContent.class); when(parsedContentConverter.convertToParsedContent(parsedContentString)).thenReturn(parsedContentObject); when(contentDao.getContentInputStream(newContentParsedContentId, ContentManager.READ_PARSED_CONTENT)) .thenThrow(IOException.class); contentManager.getParsedContent(newContentId); } @Test(expected = ContentManagerRuntimeException_NoSuchContent.class) public void givenNoSuchContentContentDaoExceptionWhenGetParsedContentThenThrowNoSuchContentContentServicesException() throws Exception { when(contentDao.getContentInputStream(newContentId + "__parsed", ContentManager.READ_PARSED_CONTENT)) .thenThrow(ContentDaoException_NoSuchContent.class); contentManager.getParsedContent(newContentId); } @Test public void whenGetContentInputStreamThenAskContentDao() throws Exception { when(contentDao.getContentInputStream(newContentId, zeStreamName)).thenReturn(contentInputStream); InputStream returnedContentInputStream = contentManager.getContentInputStream(newContentId, zeStreamName); assertThat(returnedContentInputStream).isEqualTo(contentInputStream); verify(contentDao).getContentInputStream(newContentId, zeStreamName); } @Test(expected = ContentManagerRuntimeException_NoSuchContent.class) public void givenNoSuchContentContentDaoExceptionWhenGetContentInputStreamThenThrowNoSuchContentContentServicesException() throws Exception { when(contentDao.getContentInputStream(newContentId, zeStreamName)).thenThrow(ContentDaoException_NoSuchContent.class); contentManager.getContentInputStream(newContentId, zeStreamName); } @Test() public void givenContentDaoReturnNullWhenGetContentParsedContentThenReturnEmptyString() throws Exception { when(contentDao.getContentInputStream(newContentId + "__parsed", zeStreamName)).thenReturn(null); assertThat(contentManager.getParsedContent(newContentId)).isNull(); } @Test public void whenSaveContentThenReadInputStreamToReusableInputStreamFactorySaveAndClose() throws Exception { when(ioServices.copyToReusableStreamFactory(aContentNewVersionInputStream, null)).thenReturn(closeableStreamFactory); when(closeableStreamFactory.length()).thenReturn(42L); doReturn(aContentHash).when(hashingService).getHashFromStream(closeableStreamFactory); doReturn(parsingResults).when(contentManager).getPreviouslyParsedContentOrParseFromStream(aContentHash, closeableStreamFactory); doNothing().when(contentManager).saveContent(anyString(), any(CopyInputStreamFactory.class)); ContentVersionDataSummary dataSummary = contentManager.upload(aContentNewVersionInputStream); InOrder inOrder = inOrder(ioServices, contentManager, aContent); inOrder.verify(ioServices).copyToReusableStreamFactory(aContentNewVersionInputStream, null); inOrder.verify(contentManager).getPreviouslyParsedContentOrParseFromStream(aContentHash, closeableStreamFactory); inOrder.verify(contentManager).saveContent(anyString(), any(CopyInputStreamFactory.class)); inOrder.verify(ioServices).closeQuietly(closeableStreamFactory); assertThat(dataSummary.getHash()).isEqualTo(aContentHash); assertThat(dataSummary.getMimetype()).isEqualTo(theMimetype); assertThat(dataSummary.getLength()).isEqualTo(42); } @Test public void givenHashingServiceExceptionWhenUploadContentThenCloseInputStreamFactoryAndThrowException() throws Exception { doThrow(HashingServiceException.class).when(hashingService).getHashFromStream(closeableStreamFactory); when(ioServices.copyToReusableStreamFactory(aContentNewVersionInputStream, null)).thenReturn(closeableStreamFactory); try { contentManager.upload(aContentNewVersionInputStream); fail("ContentServicesRuntimeException_CannotReadInputStream"); } catch (ContentManagerRuntimeException_CannotReadInputStream e) { //OK } verify(contentManager, never()).saveContent(anyString(), any(CopyInputStreamFactory.class)); verify(contentManager, never()).getPreviouslyParsedContentOrParseFromStream(anyString(), any(CloseableStreamFactory.class)); verify(ioServices).closeQuietly(closeableStreamFactory); } @Test public void givenCannotParseExceptionWhenTryToParseThenReturnParsedContentWithEmptyTextAndUnknownLanguageAndCorrectMimetype() throws Exception { doReturn(aContentHash).when(hashingService).getHashFromStream(closeableStreamFactory); doThrow(new FileParserException_CannotParse(mock(Exception.class), "zeMimetype")).when(fileParser) .parse(eq(closeableStreamFactory), anyInt()); ParsedContent parsedContent = contentManager.tryToParse(closeableStreamFactory); assertThat(parsedContent.getProperties()).isEmpty(); assertThat(parsedContent.getParsedContent()).isEmpty(); assertThat(parsedContent.getLanguage()).isEqualTo(Language.UNKNOWN.getCode()); assertThat(parsedContent.getMimeType()).isEqualTo("zeMimetype"); } @Test public void whenTryToParseThenParsedContentWithEmptyTextAndUnknownLanguageAndCorrectMimetype() throws Exception { doReturn(aContentHash).when(hashingService).getHashFromStream(closeableStreamFactory); doReturn(parsingResults).when(fileParser).parse(eq(closeableStreamFactory), anyInt()); assertThat(contentManager.tryToParse(closeableStreamFactory)).isSameAs(parsingResults); } @Test public void givenContentServiceRuntimeExceptionWhileSavingContentWhenUploadContentThenCloseInputStreamFactoryAndThrowException() throws Exception { when(ioServices.copyToReusableStreamFactory(aContentNewVersionInputStream, null)).thenReturn(closeableStreamFactory); doReturn(aContentHash).when(hashingService).getHashFromStream(closeableStreamFactory); doReturn(parsingResults).when(contentManager).getPreviouslyParsedContentOrParseFromStream(aContentHash, closeableStreamFactory); doThrow(ContentManagerRuntimeException.class).when(contentManager) .saveContent(aContentHash, closeableStreamFactory); try { contentManager.upload(aContentNewVersionInputStream); fail("ContentServicesRuntimeException_CannotReadInputStream"); } catch (ContentManagerRuntimeException e) { //OK } verify(ioServices).closeQuietly(closeableStreamFactory); } @Test public void givenContentServiceRuntimeExceptionWhileParsingThenCloseInputStreamFactoryAndThrowException() throws Exception { when(ioServices.copyToReusableStreamFactory(aContentNewVersionInputStream, null)).thenReturn(closeableStreamFactory); doReturn(aContentHash).when(hashingService).getHashFromStream(closeableStreamFactory); doReturn(parsingResults).when(contentManager).getPreviouslyParsedContentOrParseFromStream(aContentHash, closeableStreamFactory); doThrow(IOException.class).when(contentManager) .getPreviouslyParsedContentOrParseFromStream(aContentHash, closeableStreamFactory); try { contentManager.upload(aContentNewVersionInputStream); fail("ContentServicesRuntimeException_CannotReadInputStream"); } catch (ContentManagerRuntimeException e) { //OK } verify(ioServices).closeQuietly(closeableStreamFactory); } @Test public void whenDeleteUnreferencedContentsThenDeleteUnreferencedContentAndAllMarkers() throws Exception { MetadataSchemaTypes types1 = mock(MetadataSchemaTypes.class, "types1"); MetadataSchemaTypes types2 = mock(MetadataSchemaTypes.class, "types2"); when(metadataSchemasManager.getAllCollectionsSchemaTypes()).thenReturn(Arrays.asList(types1, types2)); RecordDTO recordDTO1 = mockMarkerWithHash("hash1"); RecordDTO recordDTO2 = mockMarkerWithHash("hash2"); RecordDTO recordDTO3 = mockMarkerWithHash("hash3"); RecordDTO recordDTO4 = mockMarkerWithHash("hash4"); RecordDTO recordDTO5 = mockMarkerWithHash("hash5"); RecordDTO recordDTO6 = mockMarkerWithHash("hash6"); RecordDTO recordDTO7 = mockMarkerWithHash("hash7"); RecordDTO recordDTO8 = mockMarkerWithHash("hash8"); RecordDTO recordDTO9 = mockMarkerWithHash("hash9"); doReturn(true).when(contentManager).isReferenced("hash1"); doReturn(false).when(contentManager).isReferenced("hash2"); doReturn(false).when(contentManager).isReferenced("hash3"); doReturn(true).when(contentManager).isReferenced("hash4"); doReturn(true).when(contentManager).isReferenced("hash5"); doReturn(false).when(contentManager).isReferenced("hash6"); doReturn(true).when(contentManager).isReferenced("hash7"); doReturn(false).when(contentManager).isReferenced("hash8"); doReturn(true).when(contentManager).isReferenced("hash9"); doReturn(Arrays.asList(recordDTO1)) .doReturn(Arrays.asList(recordDTO2, recordDTO3)) .doReturn(Arrays.asList(recordDTO7)) .doReturn(Arrays.asList(recordDTO5, recordDTO6)) .doReturn(Arrays.asList(recordDTO7)) .doReturn(Arrays.asList(recordDTO8)) .doReturn(new ArrayList<String>()) .doReturn(Arrays.asList(recordDTO9)) .when(contentManager).getNextPotentiallyUnreferencedContentMarkers(); contentManager.deleteUnreferencedContents(); TransactionDTO flushNow = new TransactionDTO(RecordsFlushing.NOW()); InOrder inOrder = inOrder(contentManager, recordDao, contentDao); inOrder.verify(contentManager).getNextPotentiallyUnreferencedContentMarkers(); inOrder.verify(contentManager).isReferenced("hash1"); inOrder.verify(recordDao).execute(flushNow.withDeletedRecords(Arrays.asList(recordDTO1))); inOrder.verify(contentManager).getNextPotentiallyUnreferencedContentMarkers(); inOrder.verify(contentManager).isReferenced("hash2"); inOrder.verify(contentManager).isReferenced("hash3"); inOrder.verify(contentDao).delete(Arrays.asList("hash2", "hash2__parsed", "hash3", "hash3__parsed")); inOrder.verify(recordDao).execute(flushNow.withDeletedRecords(Arrays.asList(recordDTO2, recordDTO3))); inOrder.verify(contentManager).getNextPotentiallyUnreferencedContentMarkers(); inOrder.verify(contentManager).isReferenced("hash5"); inOrder.verify(contentManager).isReferenced("hash6"); inOrder.verify(contentDao).delete(Arrays.asList("hash6", "hash6__parsed")); inOrder.verify(recordDao).execute(flushNow.withDeletedRecords(Arrays.asList(recordDTO5, recordDTO6))); inOrder.verify(contentManager).getNextPotentiallyUnreferencedContentMarkers(); inOrder.verify(contentManager).isReferenced("hash7"); inOrder.verify(recordDao).execute(flushNow.withDeletedRecords(Arrays.asList(recordDTO7))); inOrder.verify(contentManager).getNextPotentiallyUnreferencedContentMarkers(); inOrder.verify(contentManager).isReferenced("hash8"); inOrder.verify(contentDao).delete(Arrays.asList("hash8", "hash8__parsed")); inOrder.verify(recordDao).execute(flushNow.withDeletedRecords(Arrays.asList(recordDTO8))); inOrder.verify(contentManager).getNextPotentiallyUnreferencedContentMarkers(); } @Test public void givenContentAlreadyParsedWhenGetPreviouslyParsedContentOrParseFromStreamThenReturnPreviouslyParsedContent() throws Exception { doReturn(parsingResults).when(contentManager).getParsedContent(aContentHash); ParsedContent parsedContent = contentManager.getPreviouslyParsedContentOrParseFromStream(aContentHash, streamFactory); assertThat(parsedContent).isSameAs(parsingResults); verify(contentManager, never()).saveParsedContent(aContentHash, parsingResults); verify(contentManager, never()).tryToParse(streamFactory); verify(ioServices, never()).closeQuietly(firstCreatedStream); } @Test public void givenNewContentParsedWhenGetPreviouslyParsedContentOrParseFromStreamThenReturnParseAndSaveParsedContent() throws Exception { doThrow(ContentManagerRuntimeException_NoSuchContent.class).when(contentManager).getParsedContent(aContentHash); doReturn(parsingResults).when(contentManager).tryToParse(streamFactory); ParsedContent parsedContent = contentManager.getPreviouslyParsedContentOrParseFromStream(aContentHash, streamFactory); assertThat(parsedContent).isSameAs(parsingResults); verify(contentManager).saveParsedContent(aContentHash, parsingResults); verify(contentManager).tryToParse(streamFactory); } private RecordDTO mockMarkerWithHash(String hash) { RecordDTO marker = mock(RecordDTO.class, hash + "Marker"); Map<String, Object> fields = new HashMap<>(); fields.put("contentMarkerHash_s", hash); fields.put("id", hash + "Marker"); when(marker.getFields()).thenReturn(fields); return marker; } }