package com.constellio.data.dao.services.transactionLog; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; 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.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.SolrParams; import org.assertj.core.api.Condition; import org.joda.time.LocalDateTime; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import com.constellio.data.conf.DataLayerConfiguration; import com.constellio.data.dao.dto.records.QueryResponseDTO; import com.constellio.data.dao.dto.records.RecordsFlushing; import com.constellio.data.dao.services.DataLayerLogger; import com.constellio.data.dao.services.bigVault.RecordDaoException; import com.constellio.data.dao.services.bigVault.solr.BigVaultServer; import com.constellio.data.dao.services.bigVault.solr.BigVaultServerTransaction; import com.constellio.data.dao.services.bigVault.solr.SolrUtils; import com.constellio.data.dao.services.contents.ContentDao; import com.constellio.data.dao.services.contents.ContentDaoRuntimeException; import com.constellio.data.dao.services.idGenerator.UUIDV1Generator; import com.constellio.data.dao.services.records.RecordDao; import com.constellio.data.dao.services.solr.ConstellioSolrInputDocument; import com.constellio.data.dao.services.transactionLog.SecondTransactionLogRuntimeException.SecondTransactionLogRuntimeException_CouldNotFlushTransaction; import com.constellio.data.dao.services.transactionLog.SecondTransactionLogRuntimeException.SecondTransactionLogRuntimeException_CouldNotRegroupAndMoveInVault; import com.constellio.data.dao.services.transactionLog.SecondTransactionLogRuntimeException.SecondTransactionLogRuntimeException_LogIsInInvalidStateCausedByPreviousException; import com.constellio.data.dao.services.transactionLog.SecondTransactionLogRuntimeException.SecondTransactionLogRuntimeException_TransactionLogHasAlreadyBeenInitialized; import com.constellio.data.dao.services.transactionLog.SecondTransactionLogRuntimeException.SecondTransactionLogRuntimeException_TransactionLogIsNotInitialized; import com.constellio.data.dao.services.transactionLog.reader1.ReaderLinesIteratorV1; import com.constellio.data.extensions.DataLayerSystemExtensions; import com.constellio.data.io.services.facades.IOServices; import com.constellio.data.threads.BackgroundThreadsManager; import com.constellio.sdk.tests.ConstellioTest; public class XMLSecondTransactionLogManagerRealTest extends ConstellioTest { @Mock DataLayerConfiguration dataLayerConfiguration; @Mock BigVaultServer bigVaultServer; @Mock RecordDao recordDao; @Mock DataLayerSystemExtensions systemExtensions; LocalDateTime shishOclockLocalDateTime = new LocalDateTime().plusHours(1); LocalDateTime tockOClockLocalDateTime = shishOclockLocalDateTime.plusMinutes(1); String shishOclock = SolrUtils.convertLocalDateTimeToSolrDate(shishOclockLocalDateTime); String tockOClock = SolrUtils.convertLocalDateTimeToSolrDate(tockOClockLocalDateTime); SolrInputDocument record1, record2, record5, record3, record6, record4 = new SolrInputDocument(); String deletedRecord6 = "deletedRecord6"; String deletedRecord7 = "deletedRecord7"; @Mock QueryResponseDTO queryResponseDTO; @Mock BackgroundThreadsManager backgroundThreadsManager; ContentDao contentDao; File baseFolder, unflushed, flushed; IOServices ioServices; XMLSecondTransactionLogManager transactionLog; String deleteByQueryParams; RecordsFlushing recordsFlushing = RecordsFlushing.LATER(); DataLayerLogger dataLayerLogger = new DataLayerLogger(); String firstTransactionId = "firstTransaction"; List<SolrInputDocument> firstTransactionNewRecords = new ArrayList<>(); List<SolrInputDocument> firstTransactionModifiedRecords = new ArrayList<>(); List<String> firstTransactionDeletedRecords = new ArrayList<>(); List<String> firstTransactionDeletedByQueries = new ArrayList<>(); BigVaultServerTransaction firstTransaction = new BigVaultServerTransaction(firstTransactionId, recordsFlushing, firstTransactionNewRecords, firstTransactionModifiedRecords, firstTransactionDeletedRecords, firstTransactionDeletedByQueries); String secondTransactionId = "secondTransaction"; List<SolrInputDocument> secondTransactionNewRecords = new ArrayList<>(); List<SolrInputDocument> secondTransactionModifiedRecords = new ArrayList<>(); List<String> secondTransactionDeletedRecords = new ArrayList<>(); List<String> secondTransactionDeletedByQueries = new ArrayList<>(); BigVaultServerTransaction secondTransaction = new BigVaultServerTransaction(secondTransactionId, recordsFlushing, secondTransactionNewRecords, secondTransactionModifiedRecords, secondTransactionDeletedRecords, secondTransactionDeletedByQueries); String expectedLogOfFirstTransaction, expectedLogOfSecondTransaction; File firstTransactionTempFile, secondTransactionTempFile, flushedTransaction1, flushedTransaction2, flushedTransaction42; @Before public void setUp() throws Exception { when(systemExtensions.isDocumentFieldLoggedInTransactionLog(anyString(), anyString(), anyString(), eq(true))) .thenReturn(true); when(systemExtensions.isDocumentFieldLoggedInTransactionLog(anyString(), anyString(), anyString(), eq(false))) .thenReturn(false); givenDisabledAfterTestValidations(); withSpiedServices(ContentDao.class); baseFolder = newTempFolder(); when(dataLayerConfiguration.getSecondTransactionLogBaseFolder()).thenReturn(baseFolder); flushed = new File(baseFolder, "flushed"); unflushed = new File(baseFolder, "unflushed"); ioServices = getIOLayerFactory().newIOServices(); contentDao = getDataLayerFactory().getContentsDao(); when(recordDao.getBigVaultServer()).thenReturn(bigVaultServer); when(bigVaultServer.countDocuments()).thenReturn(42L); transactionLog = spy(new XMLSecondTransactionLogManager(dataLayerConfiguration, ioServices, recordDao, contentDao, backgroundThreadsManager, dataLayerLogger, systemExtensions, getDataLayerFactory().getTransactionLogRecoveryManager())); transactionLog.initialize(); record1 = newSolrInputDocument("record1", -1L); record1.setField("text_s", "aValue"); record1.setField("date_dt", shishOclock); record1.setField("content_txt_fr", "ze french parsed content"); record1.setField("content_txt_en", "ze english parsed content"); record2 = newSolrInputDocument("record2", -1L); record2.setField("text_s", "anotherValue"); record2.setField("otherfield_ss", asList(true, false)); record3 = newSolrInputDocument("record3", 34L); record3.setField("text_s", "line1\nline2"); record4 = newSolrInputDocument("record4", 45L); record4.setField("text_s", "value3"); record4.setField("otherfield_ss", asList(false, true)); record5 = newSolrInputDocument("record5", 56L); record5.setField("text_s", "aValue"); record5.setField("date_dt", tockOClock); record6 = newSolrInputDocument("record6ZZ", 56L); record6.setField("text_s", "aValue"); record6.setField("date_dt", tockOClock); firstTransactionNewRecords.add(record1); firstTransactionNewRecords.add(record2); firstTransactionModifiedRecords.add(record3); firstTransactionModifiedRecords.add(record4); firstTransactionModifiedRecords.add(record6); firstTransactionDeletedByQueries.add(deleteByQueryParams = SolrUtils.toDeleteQueries(new ModifiableSolrParams() .set("q", "zeQuery").add("fq", "firstFilter").add("fq", "secondFilter"))); firstTransactionDeletedByQueries.add(SolrUtils.toDeleteQueries(new ModifiableSolrParams() .set("q", "anotherQuery").add("fq", "firstFilter").add("fq", "secondFilter").add("fq", "thirdFilter"))); secondTransactionNewRecords.add(record5); secondTransactionModifiedRecords.add(record3); secondTransactionDeletedRecords.add(deletedRecord6); secondTransactionDeletedRecords.add(deletedRecord7); StringBuilder expectedLogOfFirstTransactionBuilder = new StringBuilder(); expectedLogOfFirstTransactionBuilder.append("--transaction--\n"); expectedLogOfFirstTransactionBuilder.append("addUpdate record1 -1\n"); expectedLogOfFirstTransactionBuilder.append("text_s=aValue\n"); expectedLogOfFirstTransactionBuilder.append("date_dt=" + shishOclock + "\n"); expectedLogOfFirstTransactionBuilder.append("addUpdate record2 -1\n"); expectedLogOfFirstTransactionBuilder.append("text_s=anotherValue\n"); expectedLogOfFirstTransactionBuilder.append("otherfield_ss=true\n"); expectedLogOfFirstTransactionBuilder.append("otherfield_ss=false\n"); expectedLogOfFirstTransactionBuilder.append("addUpdate record3 34\n"); expectedLogOfFirstTransactionBuilder.append("text_s=line1__LINEBREAK__line2\n"); expectedLogOfFirstTransactionBuilder.append("addUpdate record4 45\n"); expectedLogOfFirstTransactionBuilder.append("text_s=value3\n"); expectedLogOfFirstTransactionBuilder.append("otherfield_ss=false\n"); expectedLogOfFirstTransactionBuilder.append("otherfield_ss=true\n"); expectedLogOfFirstTransactionBuilder.append("deletequery ((zeQuery) AND (firstFilter) AND (secondFilter))\n"); expectedLogOfFirstTransactionBuilder .append("deletequery ((anotherQuery) AND (firstFilter) AND (secondFilter) AND (thirdFilter))\n"); expectedLogOfFirstTransaction = expectedLogOfFirstTransactionBuilder.toString(); StringBuilder expectedLogOfSecondTransactionBuilder = new StringBuilder(); expectedLogOfSecondTransactionBuilder.append("--transaction--\n"); expectedLogOfSecondTransactionBuilder.append("addUpdate record5 56\n"); expectedLogOfSecondTransactionBuilder.append("text_s=aValue\n"); expectedLogOfSecondTransactionBuilder.append("date_dt=" + tockOClock + "\n"); expectedLogOfSecondTransactionBuilder.append("addUpdate record3 34\n"); expectedLogOfSecondTransactionBuilder.append("text_s=line1__LINEBREAK__line2\n"); expectedLogOfSecondTransactionBuilder.append("delete deletedRecord6 deletedRecord7\n"); expectedLogOfSecondTransaction = expectedLogOfSecondTransactionBuilder.toString(); firstTransactionTempFile = new File(unflushed, firstTransactionId); secondTransactionTempFile = new File(unflushed, secondTransactionId); flushedTransaction1 = new File(flushed, "000000000001"); flushedTransaction2 = new File(flushed, "000000000002"); flushedTransaction42 = new File(flushed, "000000000042"); contentDao = getDataLayerFactory().getContentsDao(); } private SolrInputDocument newSolrInputDocument(String id, long version) { SolrInputDocument solrInputDocument = new ConstellioSolrInputDocument(); solrInputDocument.setField("id", id); solrInputDocument.setField("_version_", version); return solrInputDocument; } @Test public void whenPrepareLogThenCreateFileWithTransactionModifications() throws Exception { transactionLog.prepare(firstTransactionId, firstTransaction); File firstTransactionTempFile = new File(unflushed, firstTransactionId); assertThat(firstTransactionTempFile).has(content(expectedLogOfFirstTransaction)); assertThat(flushedTransaction1).doesNotExist(); assertThat(flushedTransaction2).doesNotExist(); } @Test public void givenTwoTransactionArePreparedThenAreSavedInSeparateFiles() throws Exception { transactionLog.prepare(firstTransactionId, firstTransaction); transactionLog.prepare(secondTransactionId, secondTransaction); assertThat(FileUtils.readFileToString(firstTransactionTempFile)).isEqualTo(expectedLogOfFirstTransaction); assertThat(FileUtils.readFileToString(secondTransactionTempFile)).isEqualTo(expectedLogOfSecondTransaction); assertThat(flushedTransaction1).doesNotExist(); assertThat(flushedTransaction2).doesNotExist(); } @Test public void givenTwoPreparedTransactionsWhenATransactionIsFlushedThenAppendToLogAndKeepOtherUnflushed() throws Exception { transactionLog.prepare(firstTransactionId, firstTransaction); transactionLog.prepare(secondTransactionId, secondTransaction); transactionLog.flush(secondTransactionId); assertThat(firstTransactionTempFile).has(content(expectedLogOfFirstTransaction)); assertThat(secondTransactionTempFile).doesNotExist(); assertThat(flushedTransaction1).has(content(expectedLogOfSecondTransaction)); assertThat(flushedTransaction2).doesNotExist(); } @Test public void givenTwoPreparedTransactionsWhenTheSecondIsFlushedBeforeTheSecondThenWritenInLogFileFirst() throws Exception { transactionLog.prepare(firstTransactionId, firstTransaction); transactionLog.prepare(secondTransactionId, secondTransaction); transactionLog.flush(secondTransactionId); transactionLog.flush(firstTransactionId); assertThat(firstTransactionTempFile).doesNotExist(); assertThat(secondTransactionTempFile).doesNotExist(); assertThat(flushedTransaction1).has(content(expectedLogOfSecondTransaction)); assertThat(flushedTransaction2).has(content(expectedLogOfFirstTransaction)); } @Test public void givenTwoPreparedTransactionsWhenTheFirstIsCancelledAndTheSecondFlushedThenGetSequentialId1() throws Exception { transactionLog.prepare(firstTransactionId, firstTransaction); transactionLog.prepare(secondTransactionId, secondTransaction); transactionLog.cancel(secondTransactionId); transactionLog.flush(firstTransactionId); assertThat(firstTransactionTempFile).doesNotExist(); assertThat(secondTransactionTempFile).doesNotExist(); assertThat(flushedTransaction1).has(content(expectedLogOfFirstTransaction)); assertThat(flushedTransaction2).doesNotExist(); } @Test public void givenZZRecordsAreWrittenWhenWriteTransactionWithZZRecordsThenWrote() throws Exception { when(dataLayerConfiguration.isWriteZZRecords()).thenReturn(true); transactionLog.prepare(firstTransactionId, firstTransaction); transactionLog.flush(firstTransactionId); assertThat(FileUtils.readFileToString(flushedTransaction1)).contains("record6ZZ"); } @Test public void givenAnExceptionOccurWhenFlushingThenThrowExceptionAndBlockFutureTransactions() throws Exception { transactionLog.prepare(firstTransactionId, firstTransaction); File firstTransactionTempFile = new File(unflushed, firstTransactionId); doThrow(IOException.class).when(transactionLog).doFlush(firstTransactionId); try { transactionLog.flush(firstTransactionId); fail("SecondTransactionLogHandlerRuntimeException_CouldNotFlushTransaction expected"); } catch (SecondTransactionLogRuntimeException_CouldNotFlushTransaction e) { //OK } assertThat(firstTransactionTempFile).exists(); try { transactionLog.prepare(secondTransactionId, secondTransaction); fail("SecondTransactionLogRuntimeException_LogIsInInvalidStateCausedByPreviousException expected"); } catch (SecondTransactionLogRuntimeException_LogIsInInvalidStateCausedByPreviousException e) { //OK } } @Test public void givenTwoUnflushedTransactionThenOnlyFlushOnesThatWereCommitted() throws Exception { transactionLog.prepare(firstTransactionId, firstTransaction); transactionLog.prepare(secondTransactionId, secondTransaction); transactionLog = spy(new XMLSecondTransactionLogManager(dataLayerConfiguration, ioServices, recordDao, contentDao, backgroundThreadsManager, dataLayerLogger, systemExtensions, getDataLayerFactory().getTransactionLogRecoveryManager())); doReturn(true).when(transactionLog).isCommitted(firstTransactionTempFile, recordDao); doReturn(false).when(transactionLog).isCommitted(secondTransactionTempFile, recordDao); doReturn(new AtomicInteger(41)).when(transactionLog).newIdSequence(); transactionLog.initialize(); assertThat(firstTransactionTempFile).doesNotExist(); assertThat(secondTransactionTempFile).doesNotExist(); assertThat(flushedTransaction42).has(content(expectedLogOfFirstTransaction)); assertThat(flushedTransaction2).doesNotExist(); } @Test(expected = SecondTransactionLogRuntimeException_TransactionLogIsNotInitialized.class) public void givenPreparedIsCalledBeforeInitializingTheTransactionLogThenException() { transactionLog = spy(new XMLSecondTransactionLogManager(dataLayerConfiguration, ioServices, recordDao, contentDao, backgroundThreadsManager, dataLayerLogger, systemExtensions, getDataLayerFactory().getTransactionLogRecoveryManager())); transactionLog.prepare(firstTransactionId, firstTransaction); } @Test(expected = SecondTransactionLogRuntimeException_TransactionLogHasAlreadyBeenInitialized.class) public void givenInitializedTransactionLogWhenStartASecondTimeThenException() { transactionLog.initialize(); } @Test public void givenNoFlushedFolderWhenCreateIdSequenceThenSetToZero() throws Exception { assertThat(transactionLog.newIdSequence().get()).isEqualTo(0); } @Test public void givenFlushedFolderWhenCreateIdSequenceThenSetToZero() throws Exception { FileUtils.write(new File(transactionLog.getFlushedFolder(), ".000110000666"), "content"); FileUtils.write(new File(transactionLog.getFlushedFolder(), "000000000666"), "content"); FileUtils.write(new File(transactionLog.getFlushedFolder(), "000000000042"), "content"); FileUtils.write(new File(transactionLog.getFlushedFolder(), "thumb.db"), "content"); assertThat(transactionLog.newIdSequence().get()).isEqualTo(666); } @Test public void givenUnflushedTransactionFileIsEmptyThenDeleted() throws Exception { when(recordDao.getCurrentVersion("zeRecord")).thenReturn(-1L); File file = new File(transactionLog.getUnflushedFolder(), UUIDV1Generator.newRandomId()); FileUtils.touch(file); assertThat(transactionLog.isCommitted(file, recordDao)).isFalse(); } @Test public void givenFirstChangeOfTransactionWasRecordAddAndRecordDoesNotExistThenNotCommitted() throws Exception { when(recordDao.getCurrentVersion("zeRecord")).thenReturn(-1L); SolrInputDocument zeRecord = newSolrInputDocument("zeRecord", -1L); BigVaultServerTransaction transaction = new BigVaultServerTransaction(RecordsFlushing.NOW()) .setNewDocuments(asList(zeRecord)); transactionLog.prepare(firstTransactionId, transaction); assertThat(transactionLog.isCommitted(firstTransactionTempFile, recordDao)).isFalse(); } @Test public void givenFirstChangeOfTransactionWasRecordAddAndRecordDoesExistThenCommitted() throws Exception { when(recordDao.getCurrentVersion("zeRecord")).thenReturn(42L); SolrInputDocument zeRecord = newSolrInputDocument("zeRecord", -1L); BigVaultServerTransaction transaction = new BigVaultServerTransaction(RecordsFlushing.NOW()) .setNewDocuments(asList(zeRecord)); transactionLog.prepare(firstTransactionId, transaction); assertThat(transactionLog.isCommitted(firstTransactionTempFile, recordDao)).isTrue(); } @Test public void givenFirstChangeOfTransactionWasRecordUpdateAndRecordDoesNotExistThenNotCommitted() throws Exception { when(recordDao.getCurrentVersion("zeRecord")).thenReturn(-1L); SolrInputDocument zeRecordUpdate = newSolrInputDocument("zeRecord", 123L); BigVaultServerTransaction transaction = new BigVaultServerTransaction(RecordsFlushing.NOW()) .setUpdatedDocuments(asList(zeRecordUpdate)); transactionLog.prepare(firstTransactionId, transaction); assertThat(transactionLog.isCommitted(firstTransactionTempFile, recordDao)).isFalse(); } @Test public void givenFirstChangeOfTransactionWasRecordUpdateAndRecordExistWithSameVersionThenNotCommitted() throws Exception { SolrInputDocument zeRecordUpdate = newSolrInputDocument("zeRecord", 123L); when(recordDao.getCurrentVersion("zeRecord")).thenReturn(123L); BigVaultServerTransaction transaction = new BigVaultServerTransaction(RecordsFlushing.NOW()) .setUpdatedDocuments(asList(zeRecordUpdate)); transactionLog.prepare(firstTransactionId, transaction); assertThat(transactionLog.isCommitted(firstTransactionTempFile, recordDao)).isFalse(); } @Test public void givenFirstChangeOfTransactionWasRecordUpdateAndRecordDoesExistWithDifferentVersionThenCommitted() throws Exception { SolrInputDocument zeRecordUpdate = newSolrInputDocument("zeRecord", 123L); when(recordDao.getCurrentVersion("zeRecord")).thenReturn(42L); BigVaultServerTransaction transaction = new BigVaultServerTransaction(RecordsFlushing.NOW()) .setUpdatedDocuments(asList(zeRecordUpdate)); transactionLog.prepare(firstTransactionId, transaction); assertThat(transactionLog.isCommitted(firstTransactionTempFile, recordDao)).isTrue(); } @Test public void givenFirstChangeOfTransactionWasDeleteByIdAndFirstRecordExistThenNotCommitted() throws Exception { when(recordDao.getCurrentVersion("zeRecord")).thenReturn(42L); BigVaultServerTransaction transaction = new BigVaultServerTransaction(RecordsFlushing.NOW()) .setDeletedRecords(asList("zeRecord")); transactionLog.prepare(firstTransactionId, transaction); assertThat(transactionLog.isCommitted(firstTransactionTempFile, recordDao)).isFalse(); } @Test public void givenFirstChangeOfTransactionWasDeleteByIdAndFirstRecordDoesNotExistThenCommitted() throws Exception { when(recordDao.getCurrentVersion("zeRecord")).thenReturn(-1L); BigVaultServerTransaction transaction = new BigVaultServerTransaction(RecordsFlushing.NOW()) .setDeletedRecords(asList("zeRecord")); transactionLog.prepare(firstTransactionId, transaction); assertThat(transactionLog.isCommitted(firstTransactionTempFile, recordDao)).isTrue(); } @Test public void givenTransactionOfMultipleDeleteQueryAndRecordsForFirstQueryFoundThenNotCommitted() throws Exception { ArgumentCaptor<SolrParams> solrParamsArgumentCaptor = ArgumentCaptor.forClass(SolrParams.class); when(queryResponseDTO.getNumFound()).thenReturn(2L); when(recordDao.query(solrParamsArgumentCaptor.capture())).thenReturn(queryResponseDTO); BigVaultServerTransaction transaction = new BigVaultServerTransaction(RecordsFlushing.NOW()) .setDeletedQueries(firstTransactionDeletedByQueries); transactionLog.prepare(firstTransactionId, transaction); assertThat(transactionLog.isCommitted(firstTransactionTempFile, recordDao)).isFalse(); assertThat(solrParamsArgumentCaptor.getValue()).is(sameAsFirstQuery()); } @Test public void givenTransactionOfMultipleDeleteQueryAndNoRecordForFirstQueryFoundThenCommitted() throws Exception { ArgumentCaptor<SolrParams> solrParamsArgumentCaptor = ArgumentCaptor.forClass(SolrParams.class); when(queryResponseDTO.getNumFound()).thenReturn(0L); when(recordDao.query(solrParamsArgumentCaptor.capture())).thenReturn(queryResponseDTO); BigVaultServerTransaction transaction = new BigVaultServerTransaction(RecordsFlushing.NOW()) .setDeletedQueries(firstTransactionDeletedByQueries); transactionLog.prepare(firstTransactionId, transaction); assertThat(transactionLog.isCommitted(firstTransactionTempFile, recordDao)).isTrue(); assertThat(solrParamsArgumentCaptor.getValue()).is(sameAsFirstQuery()); } @Test public void givenTransactionOfOnlyOneDeleteQueryAndRecordFoundThenNoCommitted() throws Exception { SolrInputDocument zeRecord = newSolrInputDocument("zeRecord", 42L); SolrInputDocument zeRecordUpdate = newSolrInputDocument("zeRecord", 123L); ArgumentCaptor<SolrParams> solrParamsArgumentCaptor = ArgumentCaptor.forClass(SolrParams.class); when(queryResponseDTO.getNumFound()).thenReturn(2L); when(recordDao.query(solrParamsArgumentCaptor.capture())).thenReturn(queryResponseDTO); BigVaultServerTransaction transaction = new BigVaultServerTransaction(RecordsFlushing.NOW()) .addDeletedQuery(deleteByQueryParams); transactionLog.prepare(firstTransactionId, transaction); assertThat(transactionLog.isCommitted(firstTransactionTempFile, recordDao)).isFalse(); assertThat(solrParamsArgumentCaptor.getValue()).is(sameAsFirstQuery()); } @Test public void givenTransactionOfOnlyOneDeleteQueryAndNoRecordFoundThenCommitted() throws Exception { SolrInputDocument zeRecord = newSolrInputDocument("zeRecord", 42L); SolrInputDocument zeRecordUpdate = newSolrInputDocument("zeRecord", 123L); ArgumentCaptor<SolrParams> solrParamsArgumentCaptor = ArgumentCaptor.forClass(SolrParams.class); when(queryResponseDTO.getNumFound()).thenReturn(0L); when(recordDao.query(solrParamsArgumentCaptor.capture())).thenReturn(queryResponseDTO); when(recordDao.get("zeRecord")).thenThrow(RecordDaoException.NoSuchRecordWithId.class); BigVaultServerTransaction transaction = new BigVaultServerTransaction(RecordsFlushing.NOW()) .addDeletedQuery(deleteByQueryParams); transactionLog.prepare(firstTransactionId, transaction); assertThat(transactionLog.isCommitted(firstTransactionTempFile, recordDao)).isTrue(); assertThat(solrParamsArgumentCaptor.getValue()).is(sameAsFirstQuery()); } @Test public void whenRegroupAndMoveToContentDaoThenCreateLogFileWithCurrentTimeAsTheNameAndDeleteFiles() throws Exception { givenTimeIs(new LocalDateTime(2345, 6, 7, 8, 9, 10, 11)); transactionLog.prepare(firstTransactionId, firstTransaction); transactionLog.flush(firstTransactionId); transactionLog.prepare(secondTransactionId, secondTransaction); transactionLog.flush(secondTransactionId); String id = transactionLog.regroupAndMoveInVault(); assertThat(id).isEqualTo("tlogs/2345-06-07T08-09-10-011.tlog"); String content = IOUtils.toString(getDataLayerFactory().getContentsDao().getContentInputStream(id, SDK_STREAM)); assertThat(content).isEqualTo(expectedLogOfFirstTransaction + expectedLogOfSecondTransaction); assertThat(flushedTransaction1).doesNotExist(); assertThat(flushedTransaction2).doesNotExist(); } @Test public void givenNoFlushedTransactionsWhenRegroupAndMoveToContentDaoThenDoNothingAndReturnNull() throws Exception { givenTimeIs(new LocalDateTime(2345, 6, 7, 8, 9, 10, 11)); transactionLog.prepare(firstTransactionId, firstTransaction); transactionLog.prepare(secondTransactionId, secondTransaction); String id = transactionLog.regroupAndMoveInVault(); assertThat(id).isNull(); verify(contentDao, never()).add(anyString(), any(InputStream.class)); assertThat(flushedTransaction1).doesNotExist(); assertThat(flushedTransaction2).doesNotExist(); } @Test public void givenRecordDaoExceptionWhenRegroupAndMoveToContentDaoThenDoesNotDeleteFilesAndFuturePrepareFails() throws Exception { givenTimeIs(new LocalDateTime(2345, 6, 7, 8, 9, 10, 11)); transactionLog.prepare(firstTransactionId, firstTransaction); transactionLog.flush(firstTransactionId); transactionLog.prepare(secondTransactionId, secondTransaction); transactionLog.flush(secondTransactionId); doThrow(ContentDaoRuntimeException.class).when(contentDao).add(anyString(), any(InputStream.class)); try { transactionLog.regroupAndMoveInVault(); fail("SecondTransactionLogRuntimeException_CouldNotRegroupAndMoveInVault expected"); } catch (SecondTransactionLogRuntimeException_CouldNotRegroupAndMoveInVault e) { //OK } assertThat(flushedTransaction1).exists(); assertThat(flushedTransaction2).exists(); try { transactionLog.prepare(firstTransactionId, firstTransaction); fail("SecondTransactionLogRuntimeException_LogIsInInvalidStateCausedByPreviousException"); } catch (SecondTransactionLogRuntimeException_LogIsInInvalidStateCausedByPreviousException e) { //OK } } @Test public void givenFileWithoutCarriageReturnsThenLineIteratorReturnLines() throws Exception { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("line1\n"); stringBuilder.append("line2\\nend\n"); stringBuilder.append("line3__LINEBREAK__end"); File file = newTempFileWithContent("zeFile", stringBuilder.toString()); BufferedReader tLogBufferedReader = ioServices.newBufferedFileReader(file, SDK_STREAM); Iterator<String> lineIterator = new ReaderLinesIteratorV1(ioServices, tLogBufferedReader); assertThat(lineIterator.next()).isEqualTo("line1"); assertThat(lineIterator.next()).isEqualTo("line2\\nend"); assertThat(lineIterator.next()).isEqualTo("line3__LINEBREAK__end"); assertThat(lineIterator.hasNext()).isFalse(); } @Test public void givenFileWithCarriageReturnsThenLineIteratorReturnLines() throws Exception { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("line1\n"); stringBuilder.append("line2\\nend\n"); stringBuilder.append("line3\r\nend\n"); stringBuilder.append("line4\rend\n"); stringBuilder.append("line5__LINEBREAK__end"); File file = newTempFileWithContent("zeFile", stringBuilder.toString()); BufferedReader tLogBufferedReader = ioServices.newBufferedFileReader(file, SDK_STREAM); Iterator<String> lineIterator = new ReaderLinesIteratorV1(ioServices, tLogBufferedReader); assertThat(lineIterator.next()).isEqualTo("line1"); assertThat(lineIterator.next()).isEqualTo("line2\\nend"); assertThat(lineIterator.next()).isEqualTo("line3__LINEBREAK__end"); assertThat(lineIterator.next()).isEqualTo("line4end"); assertThat(lineIterator.next()).isEqualTo("line5__LINEBREAK__end"); assertThat(lineIterator.hasNext()).isFalse(); } //TODO Test flush exception private Condition<? super SolrParams> sameAsFirstQuery() { return new Condition<SolrParams>() { @Override public boolean matches(SolrParams value) { String strQueryValue = value.get("q"); List<String> allParamNames = new ArrayList<>(); for (Iterator<String> nameIterator = value.getParameterNamesIterator(); nameIterator.hasNext(); ) { allParamNames.add(nameIterator.next()); } assertThat(allParamNames).containsOnly("q"); assertThat(value.getParams("q")).isEqualTo(new String[] { strQueryValue }); return true; } }; } private Condition<? super File> content(final String expectedContent) { return new Condition<File>() { @Override public boolean matches(File value) { try { String content = FileUtils.readFileToString(value); assertThat(content).isEqualTo(expectedContent); } catch (IOException e) { throw new RuntimeException(e); } return true; } }; } }