package com.github.kristofa.brave.mysql; import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import com.github.kristofa.brave.ClientTracer; import com.mysql.jdbc.Connection; import com.mysql.jdbc.PreparedStatement; import com.mysql.jdbc.ResultSetInternalMethods; import com.mysql.jdbc.Statement; import com.twitter.zipkin.gen.Endpoint; import org.junit.Before; import org.junit.Test; import org.mockito.InOrder; import zipkin.Constants; import zipkin.TraceKeys; import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.util.Properties; public class MySQLStatementInterceptorTest { private final ClientTracer clientTracer = mock(ClientTracer.class); private MySQLStatementInterceptor subject; @Before public void setUp() throws Exception { subject = new MySQLStatementInterceptor(); MySQLStatementInterceptor.setClientTracer(clientTracer); } @Test public void preProcessShouldNotFailIfNoClientTracer() throws Exception { MySQLStatementInterceptor.setClientTracer(null); assertNull(subject.preProcess("sql", mock(Statement.class), mock(Connection.class))); verifyZeroInteractions(clientTracer); } @Test public void preProcessShouldBeginTracingSQLCall() throws Exception { final String sql = randomAlphanumeric(20); final Connection connection = mock(Connection.class); assertNull(subject.preProcess(sql, mock(Statement.class), connection)); final InOrder order = inOrder(clientTracer); order.verify(clientTracer).startNewSpan("query"); order.verify(clientTracer).submitBinaryAnnotation(eq(TraceKeys.SQL_QUERY), eq(sql)); order.verify(clientTracer).setClientSent(); order.verifyNoMoreInteractions(); } @Test public void preProcessShouldLogServerAddress() throws Exception { final String sql = randomAlphanumeric(20); final String schema = randomAlphanumeric(20); final Connection connection = mock(Connection.class); when(connection.getSchema()).thenReturn(schema); when(connection.getHost()).thenReturn("1.2.3.4"); final DatabaseMetaData metadata = mock(DatabaseMetaData.class); when(connection.getMetaData()).thenReturn(metadata); when(metadata.getURL()).thenReturn("jdbc:mysql://foo:9999/test"); Properties props = new Properties(); when(connection.getProperties()).thenReturn(props); when(connection.getCatalog()).thenReturn("test"); subject.preProcess(sql, mock(Statement.class), connection); verify(clientTracer).setClientSent(Endpoint.builder() .ipv4(1 << 24 | 2 << 16 | 3 << 8 | 4).port(9999).serviceName("mysql-test").build()); } @Test public void preProcessShouldLogServerAddress_defaultsPortTo3306() throws Exception { final String sql = randomAlphanumeric(20); final String schema = randomAlphanumeric(20); final Connection connection = mock(Connection.class); when(connection.getSchema()).thenReturn(schema); when(connection.getHost()).thenReturn("1.2.3.4"); final DatabaseMetaData metadata = mock(DatabaseMetaData.class); when(connection.getMetaData()).thenReturn(metadata); when(metadata.getURL()).thenReturn("jdbc:mysql://foo/test"); Properties props = new Properties(); when(connection.getProperties()).thenReturn(props); when(connection.getCatalog()).thenReturn("test"); subject.preProcess(sql, mock(Statement.class), connection); verify(clientTracer).setClientSent(Endpoint.builder() .ipv4(1 << 24 | 2 << 16 | 3 << 8 | 4).port(3306).serviceName("mysql-test").build()); } @Test public void preProcessShouldLogProvidedServiceName() throws Exception { final String sql = randomAlphanumeric(20); final String schema = randomAlphanumeric(20); final Connection connection = mock(Connection.class); when(connection.getSchema()).thenReturn(schema); when(connection.getHost()).thenReturn("1.2.3.4"); final DatabaseMetaData metadata = mock(DatabaseMetaData.class); when(connection.getMetaData()).thenReturn(metadata); when(metadata.getURL()).thenReturn("jdbc:mysql://foo/test"); Properties props = new Properties(); props.setProperty("zipkinServiceName", "hello-brave"); when(connection.getProperties()).thenReturn(props); when(connection.getCatalog()).thenReturn("test"); subject.preProcess(sql, mock(Statement.class), connection); verify(clientTracer).setClientSent(Endpoint.builder() .ipv4(1 << 24 | 2 << 16 | 3 << 8 | 4).port(3306).serviceName("hello-brave").build()); } @Test public void preProcessShouldIgnoreExceptionsLoggingServerAddress() throws Exception { final String sql = randomAlphanumeric(20); final String schema = randomAlphanumeric(20); final Connection connection = mock(Connection.class); when(connection.getSchema()).thenReturn(schema); when(connection.getHost()).thenReturn("1.2.3.4"); when(connection.getMetaData()).thenThrow(new SQLException()); subject.preProcess(sql, mock(Statement.class), connection); verify(clientTracer).setClientSent(); } @Test public void preProcessShouldBeginTracingPreparedStatementCall() throws Exception { final String sql = randomAlphanumeric(20); final PreparedStatement statement = mock(PreparedStatement.class); when(statement.getPreparedSql()).thenReturn(sql); final Connection connection = mock(Connection.class); assertNull(subject.preProcess(null, statement, connection)); final InOrder order = inOrder(clientTracer); order.verify(clientTracer).startNewSpan("query"); order.verify(clientTracer).submitBinaryAnnotation(eq(TraceKeys.SQL_QUERY), eq(sql)); order.verify(clientTracer).setClientSent(); order.verifyNoMoreInteractions(); } @Test public void postProcessShouldNotFailIfNoClientTracer() throws Exception { MySQLStatementInterceptor.setClientTracer(null); assertNull(subject.postProcess("sql", mock(Statement.class), mock(ResultSetInternalMethods.class), mock(Connection.class), 1, true, true, null)); verifyZeroInteractions(clientTracer); } @Test public void postProcessShouldFinishTracingFailedSQLCall() throws Exception { final int warningCount = 1; final int errorCode = 2; assertNull(subject.postProcess("sql", mock(Statement.class), mock(ResultSetInternalMethods.class), mock(Connection.class), warningCount, true, true, new SQLException("", "", errorCode))); final InOrder order = inOrder(clientTracer); order.verify(clientTracer).submitBinaryAnnotation(eq("warning.count"), eq(warningCount + "")); order.verify(clientTracer).submitBinaryAnnotation(eq(Constants.ERROR), eq(errorCode + "")); order.verify(clientTracer).setClientReceived(); order.verifyNoMoreInteractions(); } @Test public void postProcessShouldFinishTracingSuccessfulSQLCall() throws Exception { assertNull(subject.postProcess("sql", mock(Statement.class), mock(ResultSetInternalMethods.class), mock(Connection.class), 0, true, true, null)); final InOrder order = inOrder(clientTracer); order.verify(clientTracer).setClientReceived(); order.verifyNoMoreInteractions(); } @Test public void executeTopLevelOnlyShouldOnlyExecuteTopLevelQueries() throws Exception { assertTrue(subject.executeTopLevelOnly()); verifyZeroInteractions(clientTracer); } }