package com.equalexperts.logging.impl; import com.equalexperts.logging.DiagnosticContextSupplier; import com.equalexperts.logging.LogMessage; import com.equalexperts.logging.OpsLogger; import org.junit.Before; import org.junit.Test; import org.mockito.*; import java.io.IOException; import java.time.Clock; import java.time.Instant; import java.time.ZoneOffset; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.locks.Lock; import java.util.function.Consumer; import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; public class BasicOpsLoggerTest { private Clock fixedClock = Clock.fixed(Instant.parse("2014-02-01T14:57:12.500Z"), ZoneOffset.UTC); @Mock private Destination<TestMessages> destination; @Mock private DiagnosticContextSupplier diagnosticContextSupplier; @Mock private Consumer<Throwable> exceptionConsumer; @Mock private Lock lock; @Captor private ArgumentCaptor<LogicalLogRecord<TestMessages>> captor; private OpsLogger<TestMessages> logger; @Before public void setup() { MockitoAnnotations.initMocks(this); logger = new BasicOpsLogger<>(fixedClock, diagnosticContextSupplier, destination, lock, exceptionConsumer); } //region tests for log @Test public void log_shouldWriteALogicalLogRecordToTheDestination_givenALogMessageInstance() throws Exception { Map<String,String> expectedCorrelationIds = generateCorrelationIds(); when(diagnosticContextSupplier.getMessageContext()).thenReturn(expectedCorrelationIds); doNothing().when(destination).publish(captor.capture()); logger.log(TestMessages.Bar, 64, "Hello, World"); verify(destination, times(1)).publish(any()); verify(diagnosticContextSupplier).getMessageContext(); verifyNoMoreInteractions(diagnosticContextSupplier); LogicalLogRecord<TestMessages> record = captor.getValue(); assertEquals(fixedClock.instant(), record.getTimestamp()); assertEquals(expectedCorrelationIds, record.getDiagnosticContext().getContext()); assertEquals(TestMessages.Bar, record.getMessage()); assertNotNull(record.getCause()); assertFalse(record.getCause().isPresent()); assertArrayEquals(new Object[] {64, "Hello, World"}, record.getDetails()); } @Test public void log_shouldObtainAndReleaseALockAndBeginAndEndADestinationBatch_givenALogMessageInstance() throws Exception { logger.log(TestMessages.Foo); InOrder inOrder = inOrder(lock, destination); inOrder.verify(lock).lock(); inOrder.verify(destination).beginBatch(); inOrder.verify(destination).publish(any()); inOrder.verify(destination).endBatch(); inOrder.verify(lock).unlock(); } @Test public void log_shouldExposeAnExceptionToTheHandler_givenAProblemCreatingTheLogRecord() throws Exception { logger.log(null); verify(exceptionConsumer).accept(Mockito.isA(NullPointerException.class)); } @Test public void log_shouldNotAcquireALockOrInteractWithTheDestination_givenAProblemCreatingTheLogRecord() throws Exception { logger.log(null); verifyZeroInteractions(lock, destination); } @Test public void log_shouldExposeAnExceptionToTheHandler_givenAProblemObtainingCorrelationIds() throws Exception { Error expectedThrowable = new Error(); when(diagnosticContextSupplier.getMessageContext()).thenThrow(expectedThrowable); logger.log(TestMessages.Foo); verify(exceptionConsumer).accept(Mockito.same(expectedThrowable)); } @Test public void log_shouldNotAcquireALockOrInteractWithTheDestination_givenAProblemObtainingCorrelationIds() throws Exception { when(diagnosticContextSupplier.getMessageContext()).thenThrow(new RuntimeException()); logger.log(TestMessages.Foo); verifyZeroInteractions(lock, destination); } @Test public void log_shouldExposeAnExceptionToTheHandler_givenAProblemPublishingALogRecord() throws Exception { RuntimeException expectedException = new NullPointerException(); doThrow(expectedException).when(destination).publish(any()); logger.log(TestMessages.Foo); verify(exceptionConsumer).accept(Mockito.same(expectedException)); } @Test public void log_shouldEndTheBatchAndReleaseTheLock_givenAProblemPublishingALogRecord() throws Exception { doThrow(new RuntimeException()).when(destination).publish(any()); logger.log(TestMessages.Foo); InOrder inOrder = inOrder(lock, destination); inOrder.verify(lock).lock(); inOrder.verify(destination).beginBatch(); inOrder.verify(destination).publish(any()); inOrder.verify(destination).endBatch(); inOrder.verify(lock).unlock(); } //endregion //region tests for logThrowable @Test public void logThrowable_shouldWriteALogicalLogRecordToTheDestination_givenALogMessageInstanceAndAThrowable() throws Exception { Map<String, String> expectedCorrelationIds = generateCorrelationIds(); when(diagnosticContextSupplier.getMessageContext()).thenReturn(expectedCorrelationIds); doNothing().when(destination).publish(captor.capture()); RuntimeException expectedException = new RuntimeException("expected"); logger.logThrowable(TestMessages.Bar, expectedException, 64, "Hello, World"); verify(destination, times(1)).publish(any()); verify(diagnosticContextSupplier).getMessageContext(); verifyNoMoreInteractions(diagnosticContextSupplier); LogicalLogRecord<TestMessages> record = captor.getValue(); assertEquals(fixedClock.instant(), record.getTimestamp()); assertEquals(expectedCorrelationIds, record.getDiagnosticContext().getContext()); assertEquals(TestMessages.Bar, record.getMessage()); assertNotNull(record.getCause()); assertSame(expectedException, record.getCause().get()); assertArrayEquals(new Object[]{64, "Hello, World"}, record.getDetails()); } @Test public void logThrowable_shouldObtainAndReleaseALockAndBeginAndEndADestinationBatch_givenALogMessageInstanceAndAThrowable() throws Exception { logger.logThrowable(TestMessages.Foo, new RuntimeException()); InOrder inOrder = inOrder(lock, destination); inOrder.verify(lock).lock(); inOrder.verify(destination).beginBatch(); inOrder.verify(destination).publish(any()); inOrder.verify(destination).endBatch(); inOrder.verify(lock).unlock(); } @Test public void logThrowable_shouldExposeAnExceptionToTheHandler_givenAProblemCreatingTheLogRecordWithAThrowable() throws Exception { logger.logThrowable(TestMessages.Foo, null); verify(exceptionConsumer).accept(Mockito.isA(NullPointerException.class)); } @Test public void logThrowable_shouldNotObtainALockOrInteractWithTheDestination_givenAProblemCreatingTheLogRecordWithAThrowable() throws Exception { logger.logThrowable(null, new Throwable()); verifyZeroInteractions(lock, destination); } @Test public void logThrowable_shouldExposeAnExceptionToTheHandler_givenAProblemPublishingALogRecordWithAThrowable() throws Exception { Exception expectedException = new IOException("Couldn't write to the output stream"); doThrow(expectedException).when(destination).publish(any()); logger.logThrowable(TestMessages.Foo, new NullPointerException()); verify(exceptionConsumer).accept(Mockito.same(expectedException)); } @Test public void logThrowable_shouldEndTheBatchAndReleaseTheLock_givenAProblemPublishingTheLogRecordWithAThrowable() throws Exception { doThrow(new RuntimeException()).when(destination).publish(any()); logger.logThrowable(TestMessages.Foo, new Error()); InOrder inOrder = inOrder(lock, destination); inOrder.verify(lock).lock(); inOrder.verify(destination).beginBatch(); inOrder.verify(destination).publish(any()); inOrder.verify(destination).endBatch(); inOrder.verify(lock).unlock(); } @Test public void logThrowable_shouldExposeAnExceptionToTheHandler_givenAProblemObtainingCorrelationIdsWithAThrowable() throws Exception { Error expectedThrowable = new Error(); when(diagnosticContextSupplier.getMessageContext()).thenThrow(expectedThrowable); logger.logThrowable(TestMessages.Foo, new RuntimeException()); verify(exceptionConsumer).accept(Mockito.same(expectedThrowable)); } @Test public void logThrowable_shouldNotObtainALockOrInteractWithTheDestination_givenAProblemObtainingCorrelationIdsWithAThrowable() throws Exception { when(diagnosticContextSupplier.getMessageContext()).thenThrow(new RuntimeException()); logger.logThrowable(TestMessages.Foo, new Exception()); verifyZeroInteractions(lock, destination); } //endregion @Test public void with_shouldReturnANewBasicOpsLoggerWithAnOverriddenDiagnosticContextSupplier_givenADiagnosticContextSupplier() throws Exception { DiagnosticContextSupplier localSupplier = Collections::emptyMap; BasicOpsLogger<TestMessages> basicLogger = (BasicOpsLogger<TestMessages>) logger; BasicOpsLogger<TestMessages> result = basicLogger.with(localSupplier); assertNotSame(basicLogger, result); assertSame(basicLogger.getClock(), result.getClock()); assertSame(basicLogger.getErrorHandler(), result.getErrorHandler()); assertSame(basicLogger.getDestination(), result.getDestination()); assertSame(basicLogger.getLock(), result.getLock()); assertSame(localSupplier, result.getDiagnosticContextSupplier()); assertNotSame(basicLogger.getDiagnosticContextSupplier(), result.getDiagnosticContextSupplier()); } @Test public void close_shouldCloseTheDestination() throws Exception { logger.close(); verify(destination).close(); } @Test public void close_shouldIgnoreCalls_givenANestedLoggerCreatedByWith() throws Exception { BasicOpsLogger<TestMessages> basicLogger = (BasicOpsLogger<TestMessages>) logger; BasicOpsLogger<TestMessages> nested = basicLogger.with(Collections::emptyMap); nested.close(); verifyZeroInteractions(destination); } private Map<String, String> generateCorrelationIds() { Map<String, String> result = new HashMap<>(); result.put("foo", UUID.randomUUID().toString()); result.put(UUID.randomUUID().toString(), UUID.randomUUID().toString()); result.put(UUID.randomUUID().toString(), UUID.randomUUID().toString()); return result; } private enum TestMessages implements LogMessage { Foo("CODE-Foo", "An event of some kind occurred"), Bar("CODE-Bar", "An event with %d %s messages"); //region LogMessage implementation guts private final String messageCode; private final String messagePattern; TestMessages(String messageCode, String messagePattern) { this.messageCode = messageCode; this.messagePattern = messagePattern; } @Override public String getMessageCode() { return messageCode; } @Override public String getMessagePattern() { return messagePattern; } //endregion } }