package com.constellio.model.services.records;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import com.constellio.data.dao.dto.records.RecordsFlushing;
import com.constellio.data.utils.ThreadList;
import com.constellio.model.entities.records.Record;
import com.constellio.model.entities.records.Transaction;
import com.constellio.model.services.records.BulkRecordTransactionHandlerRuntimeException.BulkRecordTransactionHandlerRuntimeException_ExceptionExecutingTransaction;
import com.constellio.model.services.records.cache.RecordsCache;
import com.constellio.model.services.records.cache.RecordsCaches;
import com.constellio.sdk.tests.ConstellioTest;
public class BulkRecordTransactionHandlerAcceptTest extends ConstellioTest {
AtomicBoolean exceptionTriggered = new AtomicBoolean();
BulkRecordTransactionHandlerOptions options = new BulkRecordTransactionHandlerOptions();
@Mock RecordServices recordServices;
@Mock ThreadList<Thread> zeThreadList;
@Mock Record aRecord, aCachedRecord, theRecordThrowingAnException, theNextRecord, aReferencedRecord, anotherReferencedRecord, aThirdReferencedRecord;
@Mock RecordsCaches recordsCaches;
@Mock RecordsCache zeCollectionRecordsCache;
BulkRecordTransactionHandler handler;
@Before
public void setUp()
throws Exception {
when(aRecord.getId()).thenReturn("aRecord");
when(aCachedRecord.getId()).thenReturn("aCachedRecord");
when(theRecordThrowingAnException.getId()).thenReturn("theRecordThrowingAnException");
when(theNextRecord.getId()).thenReturn("theNextRecord");
when(aReferencedRecord.getId()).thenReturn("aReferencedRecord");
when(anotherReferencedRecord.getId()).thenReturn("anotherReferencedRecord");
when(aThirdReferencedRecord.getId()).thenReturn("aThirdReferencedRecord");
when(aRecord.getSchemaCode()).thenReturn("zeSchema_default");
when(aCachedRecord.getSchemaCode()).thenReturn("cachedSchema_default");
when(theRecordThrowingAnException.getSchemaCode()).thenReturn("zeSchema_default");
when(theNextRecord.getSchemaCode()).thenReturn("zeSchema_default");
when(aReferencedRecord.getSchemaCode()).thenReturn("zeSchema_default");
when(anotherReferencedRecord.getSchemaCode()).thenReturn("zeSchema_default");
when(aThirdReferencedRecord.getSchemaCode()).thenReturn("zeSchema_default");
when(aRecord.getCollection()).thenReturn(zeCollection);
when(aCachedRecord.getCollection()).thenReturn(zeCollection);
when(theRecordThrowingAnException.getCollection()).thenReturn(zeCollection);
when(theNextRecord.getCollection()).thenReturn(zeCollection);
when(aReferencedRecord.getCollection()).thenReturn(zeCollection);
when(anotherReferencedRecord.getCollection()).thenReturn(zeCollection);
when(aThirdReferencedRecord.getCollection()).thenReturn(zeCollection);
when(recordServices.getRecordsCaches()).thenReturn(recordsCaches);
when(recordsCaches.getCache(zeCollection)).thenReturn(zeCollectionRecordsCache);
when(zeCollectionRecordsCache.isConfigured("zeSchema")).thenReturn(false);
when(zeCollectionRecordsCache.isConfigured("cachedSchema")).thenReturn(true);
options = options.withNumberOfThreads(2).withQueueSize(6).withRecordsPerBatch(1);
}
@Test
public void givenARecordServiceRuntimeExceptionOccurWhileExecutingTheSecondTransactionThenContinueAndThrowException()
throws Exception {
ArgumentCaptor<Transaction> transactionArgumentCaptor = ArgumentCaptor.forClass(Transaction.class);
doAnswer(workUntilExceptionTriggered()).doAnswer(triggerException(new RecordServicesRuntimeException("")))
.when(recordServices).execute(any(Transaction.class));
handler = new BulkRecordTransactionHandler(recordServices, "BulkRecordTransactionHandlerAcceptTest-test", options);
handler.append(asList(aRecord), asList(aReferencedRecord));
handler.append(asList(theRecordThrowingAnException), asList(anotherReferencedRecord, aThirdReferencedRecord));
handler.append(asList(theNextRecord));
triggerAnExceptionInAThread();
try {
handler.closeAndJoin();
fail("Exception expected");
} catch (BulkRecordTransactionHandlerRuntimeException_ExceptionExecutingTransaction e) {
//OK
}
verify(recordServices, times(3)).execute(transactionArgumentCaptor.capture());
Transaction transactionWithARecord = null;
Transaction transactionWithExceptionRecord = null;
Transaction transactionWithTheNextRecord = null;
for (Transaction capturedTransaction : transactionArgumentCaptor.getAllValues()) {
if (capturedTransaction.getRecords().contains(aRecord)) {
transactionWithARecord = capturedTransaction;
} else if (capturedTransaction.getRecords().contains(theNextRecord)) {
transactionWithTheNextRecord = capturedTransaction;
} else {
transactionWithExceptionRecord = capturedTransaction;
}
}
assertThat(transactionWithARecord.getRecords()).containsOnly(aRecord);
assertThat(transactionWithARecord.getReferencedRecords()).hasSize(1).containsValue(aReferencedRecord);
assertThat(transactionWithARecord.getRecordUpdateOptions().getRecordsFlushing())
.isEqualTo(RecordsFlushing.WITHIN_MINUTES(5));
assertThat(transactionWithTheNextRecord.getRecords()).containsOnly(theNextRecord);
assertThat(transactionWithARecord.getRecordUpdateOptions().getRecordsFlushing())
.isEqualTo(RecordsFlushing.WITHIN_MINUTES(5));
assertThat(transactionWithExceptionRecord.getRecords()).contains(theRecordThrowingAnException);
assertThat(transactionWithExceptionRecord.getReferencedRecords()).hasSize(2).containsValue(anotherReferencedRecord)
.containsValue(aThirdReferencedRecord);
assertThat(transactionWithExceptionRecord.getRecordUpdateOptions().getRecordsFlushing())
.isEqualTo(RecordsFlushing.WITHIN_MINUTES(5));
}
@Test
public void givenARecordIsCachedThenFlushingNow()
throws Exception {
ArgumentCaptor<Transaction> transactionArgumentCaptor = ArgumentCaptor.forClass(Transaction.class);
doAnswer(workUntilExceptionTriggered()).doAnswer(triggerException(new RecordServicesRuntimeException("")))
.when(recordServices).execute(any(Transaction.class));
handler = new BulkRecordTransactionHandler(recordServices, "BulkRecordTransactionHandlerAcceptTest-test", options);
handler.append(asList(aRecord), asList(aReferencedRecord));
handler.append(asList(aCachedRecord), asList(aReferencedRecord));
triggerAnExceptionInAThread();
Thread.sleep(100);
try {
handler.closeAndJoin();
fail("Exception expected");
} catch (BulkRecordTransactionHandlerRuntimeException_ExceptionExecutingTransaction e) {
//OK
}
verify(recordServices, times(2)).execute(transactionArgumentCaptor.capture());
Transaction transactionWithARecord = null;
Transaction transactionWithACachedRecord = null;
for (Transaction capturedTransaction : transactionArgumentCaptor.getAllValues()) {
if (capturedTransaction.getRecords().contains(aRecord)) {
transactionWithARecord = capturedTransaction;
} else if (capturedTransaction.getRecords().contains(aCachedRecord)) {
transactionWithACachedRecord = capturedTransaction;
}
}
assertThat(transactionWithARecord.getRecords()).containsOnly(aRecord);
assertThat(transactionWithARecord.getReferencedRecords()).hasSize(1).containsValue(aReferencedRecord);
assertThat(transactionWithARecord.getRecordUpdateOptions().getRecordsFlushing())
.isEqualTo(RecordsFlushing.WITHIN_MINUTES(5));
assertThat(transactionWithACachedRecord.getRecords()).containsOnly(aCachedRecord);
assertThat(transactionWithACachedRecord.getReferencedRecords()).hasSize(1).containsValue(aReferencedRecord);
assertThat(transactionWithACachedRecord.getRecordUpdateOptions().getRecordsFlushing()).isEqualTo(RecordsFlushing.NOW);
}
@Test
public void givenARecordServiceRuntimeExceptionOccurWhenResetExceptionThenHandlerRecovers()
throws Exception {
doAnswer(workUntilExceptionTriggered()).doAnswer(triggerException(new RecordServicesRuntimeException("")))
.when(recordServices).execute(any(Transaction.class));
handler = new BulkRecordTransactionHandler(recordServices, "BulkRecordTransactionHandlerAcceptTest-test", options);
handler.append(asList(aRecord), asList(aReferencedRecord));
handler.append(asList(theRecordThrowingAnException), asList(anotherReferencedRecord, aThirdReferencedRecord));
triggerAnExceptionInAThread();
Thread.sleep(100);
try {
handler.closeAndJoin();
fail("Exception expected");
} catch (BulkRecordTransactionHandlerRuntimeException_ExceptionExecutingTransaction e) {
//OK
}
try {
handler.append(asList(theNextRecord));
fail("Exception expected");
} catch (BulkRecordTransactionHandlerRuntimeException_ExceptionExecutingTransaction e) {
//OK
}
handler.resetException();
handler.append(asList(theNextRecord));
handler.closeAndJoin();
}
private void triggerAnExceptionInAThread() {
exceptionTriggered.set(true);
}
private Answer<Object> workUntilExceptionTriggered() {
return new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation)
throws Throwable {
while (!exceptionTriggered.get()) {
Thread.sleep(100);
}
Thread.sleep(200);
return null;
}
};
}
private Answer<Object> triggerException(final Exception exception) {
return new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation)
throws Throwable {
while (!exceptionTriggered.get()) {
Thread.sleep(10);
}
throw exception;
}
};
}
}