diff --git a/src/test/java/fr/xephi/authme/datasource/AbstractResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/AbstractResourceClosingTest.java index 845923b9..5b2dca58 100644 --- a/src/test/java/fr/xephi/authme/datasource/AbstractResourceClosingTest.java +++ b/src/test/java/fr/xephi/authme/datasource/AbstractResourceClosingTest.java @@ -1,14 +1,10 @@ package fr.xephi.authme.datasource; -import ch.jalu.configme.properties.Property; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import fr.xephi.authme.TestHelper; import fr.xephi.authme.data.auth.PlayerAuth; import fr.xephi.authme.security.crypts.HashedPassword; -import fr.xephi.authme.settings.Settings; -import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; @@ -33,7 +29,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; @@ -42,36 +37,29 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; /** - * Test class which runs through a datasource implementation and verifies that all + * Test class which runs through objects interacting with a database and verifies that all * instances of {@link AutoCloseable} that are created in the calls are closed again. *

* Instead of an actual connection to a datasource, we pass a mock Connection object * which is set to create additional mocks on demand for Statement and ResultSet objects. * This test ensures that all such objects that are created will be closed again by * keeping a list of mocks ({@link #closeables}) and then verifying that all have been - * closed {@link #verifyHaveMocksBeenClosed()}. + * closed ({@link #verifyHaveMocksBeenClosed()}). */ @RunWith(Parameterized.class) public abstract class AbstractResourceClosingTest { - /** List of DataSource method names not to test. */ - private static final Set IGNORED_METHODS = ImmutableSet.of("reload", "close", "getType"); - /** Collection of values to use to call methods with the parameters they expect. */ private static final Map, Object> PARAM_VALUES = getDefaultParameters(); - /** Mock of a settings instance. */ - private static Settings settings; - - /** The datasource to test. */ - private DataSource dataSource; - /** The DataSource method to test. */ private Method method; /** Keeps track of the closeables which are created during the tested call. */ private List closeables = new ArrayList<>(); + private boolean hasCreatedConnection = false; + /** * Constructor for the test instance verifying the given method. * @@ -84,65 +72,22 @@ public abstract class AbstractResourceClosingTest { this.method = method; } - /** Initialize the settings mock and makes it return the default of any given property by default. */ - @SuppressWarnings("unchecked") @BeforeClass - public static void initializeSettings() { - settings = mock(Settings.class); - given(settings.getProperty(any(Property.class))).willAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocation) { - return ((Property) invocation.getArguments()[0]).getDefaultValue(); - } - }); + public static void initializeLogger() { TestHelper.setupLogger(); } - /** Initialize the dataSource implementation to test based on a mock connection. */ - @Before - public void setUpMockConnection() throws Exception { - Connection connection = initConnection(); - dataSource = createDataSource(settings, connection); - } - /** * The actual test -- executes the method given through the constructor and then verifies that all * AutoCloseable mocks it constructed have been closed. */ @Test public void shouldCloseResources() throws IllegalAccessException, InvocationTargetException { - method.invoke(dataSource, buildParamListForMethod(method)); + method.invoke(getObjectUnderTest(), buildParamListForMethod(method)); verifyHaveMocksBeenClosed(); } - /** - * Initialization method -- provides the parameters to run the test with by scanning all DataSource methods. - * - * @return Test parameters - */ - @Parameterized.Parameters(name = "{1}") - public static Collection data() { - List methods = getDataSourceMethods(); - List data = new ArrayList<>(); - for (Method method : methods) { - data.add(new Object[]{method, method.getName()}); - } - return data; - } - - /* Create a DataSource instance with the given mock settings and mock connection. */ - protected abstract DataSource createDataSource(Settings settings, Connection connection) throws Exception; - - /* Get all methods of the DataSource interface, minus the ones in the ignored list. */ - private static List getDataSourceMethods() { - List publicMethods = new ArrayList<>(); - for (Method method : DataSource.class.getDeclaredMethods()) { - if (!IGNORED_METHODS.contains(method.getName())) { - publicMethods.add(method); - } - } - return publicMethods; - } + protected abstract Object getObjectUnderTest(); /** * Verify that all AutoCloseables that have been created during the method execution have been closed. @@ -166,7 +111,7 @@ public abstract class AbstractResourceClosingTest { * @param method The method to create a valid parameter list for * @return Parameter list to invoke the given method with */ - private static Object[] buildParamListForMethod(Method method) { + private Object[] buildParamListForMethod(Method method) { List params = new ArrayList<>(); int index = 0; for (Class paramType : method.getParameterTypes()) { @@ -174,7 +119,7 @@ public abstract class AbstractResourceClosingTest { // but that is a sensible assumption and makes our life much easier later on when juggling with Type Object param = Collection.class.isAssignableFrom(paramType) ? getTypedCollection(method.getGenericParameterTypes()[index]) - : PARAM_VALUES.get(paramType); + : getMethodParameter(paramType); Preconditions.checkNotNull(param, "No param type for " + paramType); params.add(param); ++index; @@ -182,6 +127,15 @@ public abstract class AbstractResourceClosingTest { return params.toArray(); } + private Object getMethodParameter(Class paramType) { + if (paramType.equals(Connection.class)) { + Preconditions.checkArgument(!hasCreatedConnection, "A Connection object was already created in this test run"); + hasCreatedConnection = true; + return initConnection(); + } + return PARAM_VALUES.get(paramType); + } + /** * Return a collection of the required type with some test elements that correspond to the * collection's generic type. @@ -230,11 +184,11 @@ public abstract class AbstractResourceClosingTest { // Mock initialization // --------------------- /** - * Initialize the connection mock which produces additional AutoCloseable mocks and records them. + * Initializes the connection mock which produces additional AutoCloseable mocks and records them. * * @return Connection mock */ - private Connection initConnection() { + protected Connection initConnection() { Connection connection = mock(Connection.class); try { given(connection.prepareStatement(anyString())).willAnswer(preparedStatementAnswer()); diff --git a/src/test/java/fr/xephi/authme/datasource/AbstractSqlDataSourceResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/AbstractSqlDataSourceResourceClosingTest.java new file mode 100644 index 00000000..ea92e4a0 --- /dev/null +++ b/src/test/java/fr/xephi/authme/datasource/AbstractSqlDataSourceResourceClosingTest.java @@ -0,0 +1,85 @@ +package fr.xephi.authme.datasource; + +import ch.jalu.configme.properties.Property; +import com.google.common.collect.ImmutableSet; +import fr.xephi.authme.TestHelper; +import fr.xephi.authme.settings.Settings; +import org.junit.BeforeClass; +import org.junit.runners.Parameterized; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.lang.reflect.Method; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Resource-closing test for SQL data sources. + */ +public abstract class AbstractSqlDataSourceResourceClosingTest extends AbstractResourceClosingTest { + + /** List of DataSource method names not to test. */ + private static final Set IGNORED_METHODS = ImmutableSet.of("reload", "getType"); + + private static Settings settings; + + AbstractSqlDataSourceResourceClosingTest(Method method, String name) { + super(method, name); + } + + @BeforeClass + public static void initializeSettings() { + settings = mock(Settings.class); + given(settings.getProperty(any(Property.class))).willAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + return ((Property) invocation.getArguments()[0]).getDefaultValue(); + } + }); + TestHelper.setupLogger(); + } + + protected DataSource getObjectUnderTest() { + try { + return createDataSource(settings, initConnection()); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + /* Create a DataSource instance with the given mock settings and mock connection. */ + protected abstract DataSource createDataSource(Settings settings, Connection connection) throws Exception; + + /** + * Initialization method -- provides the parameters to run the test with by scanning all DataSource methods. + * + * @return Test parameters + */ + @Parameterized.Parameters(name = "{1}") + public static Collection data() { + List methods = getDataSourceMethods(); + List data = new ArrayList<>(); + for (Method method : methods) { + data.add(new Object[]{method, method.getName()}); + } + return data; + } + + /* Get all methods of the DataSource interface, minus the ones in the ignored list. */ + private static List getDataSourceMethods() { + List publicMethods = new ArrayList<>(); + for (Method method : DataSource.class.getDeclaredMethods()) { + if (!IGNORED_METHODS.contains(method.getName())) { + publicMethods.add(method); + } + } + return publicMethods; + } +} diff --git a/src/test/java/fr/xephi/authme/datasource/MySqlResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/MySqlResourceClosingTest.java index f41a2164..3500560b 100644 --- a/src/test/java/fr/xephi/authme/datasource/MySqlResourceClosingTest.java +++ b/src/test/java/fr/xephi/authme/datasource/MySqlResourceClosingTest.java @@ -15,7 +15,7 @@ import static org.mockito.Mockito.mock; /** * Resource closing test for {@link MySQL}. */ -public class MySqlResourceClosingTest extends AbstractResourceClosingTest { +public class MySqlResourceClosingTest extends AbstractSqlDataSourceResourceClosingTest { public MySqlResourceClosingTest(Method method, String name) { super(method, name); diff --git a/src/test/java/fr/xephi/authme/datasource/SQLiteResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/SQLiteResourceClosingTest.java index 69eade8f..c85734cb 100644 --- a/src/test/java/fr/xephi/authme/datasource/SQLiteResourceClosingTest.java +++ b/src/test/java/fr/xephi/authme/datasource/SQLiteResourceClosingTest.java @@ -8,7 +8,7 @@ import java.sql.Connection; /** * Resource closing test for {@link SQLite}. */ -public class SQLiteResourceClosingTest extends AbstractResourceClosingTest { +public class SQLiteResourceClosingTest extends AbstractSqlDataSourceResourceClosingTest { public SQLiteResourceClosingTest(Method method, String name) { super(method, name); diff --git a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/AbstractMySqlExtensionResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/mysqlextensions/AbstractMySqlExtensionResourceClosingTest.java new file mode 100644 index 00000000..6425e33b --- /dev/null +++ b/src/test/java/fr/xephi/authme/datasource/mysqlextensions/AbstractMySqlExtensionResourceClosingTest.java @@ -0,0 +1,60 @@ +package fr.xephi.authme.datasource.mysqlextensions; + +import ch.jalu.configme.properties.Property; +import fr.xephi.authme.datasource.AbstractResourceClosingTest; +import fr.xephi.authme.datasource.Columns; +import fr.xephi.authme.settings.Settings; +import org.junit.BeforeClass; +import org.junit.runners.Parameterized; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +/** + * Checks that SQL resources are closed properly in {@link MySqlExtension} implementations. + */ +public abstract class AbstractMySqlExtensionResourceClosingTest extends AbstractResourceClosingTest { + + private static Settings settings; + private static Columns columns; + + public AbstractMySqlExtensionResourceClosingTest(Method method, String name) { + super(method, name); + } + + @BeforeClass + public static void initSettings() { + settings = mock(Settings.class); + given(settings.getProperty(any(Property.class))).willAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) { + return ((Property) invocation.getArguments()[0]).getDefaultValue(); + } + }); + columns = new Columns(settings); + } + + @Override + protected MySqlExtension getObjectUnderTest() { + return createExtension(settings, columns); + } + + protected abstract MySqlExtension createExtension(Settings settings, Columns columns); + + @Parameterized.Parameters(name = "{1}") + public static List createParameters() { + return Arrays.stream(MySqlExtension.class.getDeclaredMethods()) + .filter(m -> Modifier.isPublic(m.getModifiers())) + .map(m -> new Object[]{m, m.getName()}) + .collect(Collectors.toList()); + } +} diff --git a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/Ipb4ExtensionResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/mysqlextensions/Ipb4ExtensionResourceClosingTest.java new file mode 100644 index 00000000..8629a6a7 --- /dev/null +++ b/src/test/java/fr/xephi/authme/datasource/mysqlextensions/Ipb4ExtensionResourceClosingTest.java @@ -0,0 +1,21 @@ +package fr.xephi.authme.datasource.mysqlextensions; + +import fr.xephi.authme.datasource.Columns; +import fr.xephi.authme.settings.Settings; + +import java.lang.reflect.Method; + +/** + * Resource closing test for {@link Ipb4Extension}. + */ +public class Ipb4ExtensionResourceClosingTest extends AbstractMySqlExtensionResourceClosingTest { + + public Ipb4ExtensionResourceClosingTest(Method method, String name) { + super(method, name); + } + + @Override + protected MySqlExtension createExtension(Settings settings, Columns columns) { + return new Ipb4Extension(settings, columns); + } +} diff --git a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/NoOpExtensionTest.java b/src/test/java/fr/xephi/authme/datasource/mysqlextensions/NoOpExtensionTest.java new file mode 100644 index 00000000..ce7f4a20 --- /dev/null +++ b/src/test/java/fr/xephi/authme/datasource/mysqlextensions/NoOpExtensionTest.java @@ -0,0 +1,58 @@ +package fr.xephi.authme.datasource.mysqlextensions; + +import ch.jalu.configme.properties.Property; +import fr.xephi.authme.data.auth.PlayerAuth; +import fr.xephi.authme.datasource.Columns; +import fr.xephi.authme.security.crypts.HashedPassword; +import fr.xephi.authme.settings.Settings; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import java.sql.Connection; +import java.sql.SQLException; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Test for {@link NoOpExtension}. + */ +@RunWith(MockitoJUnitRunner.class) +public class NoOpExtensionTest { + + private NoOpExtension extension; + + @Before + @SuppressWarnings("unchecked") + public void createExtension() { + Settings settings = mock(Settings.class); + given(settings.getProperty(any(Property.class))).willAnswer( + invocation -> ((Property) invocation.getArgument(0)).getDefaultValue()); + Columns columns = new Columns(settings); + extension = new NoOpExtension(settings, columns); + } + + @Test + public void shouldNotHaveAnyInteractionsWithConnection() throws SQLException { + // given + Connection connection = mock(Connection.class); + PlayerAuth auth = mock(PlayerAuth.class); + int id = 3; + String name = "Bobby"; + HashedPassword password = new HashedPassword("test", "toast"); + + + // when + extension.extendAuth(auth, id, connection); + extension.changePassword(name, password, connection); + extension.removeAuth(name, connection); + extension.saveAuth(auth, connection); + + // then + verifyZeroInteractions(connection, auth); + } +} diff --git a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/PhpBbExtensionResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/mysqlextensions/PhpBbExtensionResourceClosingTest.java new file mode 100644 index 00000000..d1f43e1f --- /dev/null +++ b/src/test/java/fr/xephi/authme/datasource/mysqlextensions/PhpBbExtensionResourceClosingTest.java @@ -0,0 +1,21 @@ +package fr.xephi.authme.datasource.mysqlextensions; + +import fr.xephi.authme.datasource.Columns; +import fr.xephi.authme.settings.Settings; + +import java.lang.reflect.Method; + +/** + * Resource closing test for {@link PhpBbExtension}. + */ +public class PhpBbExtensionResourceClosingTest extends AbstractMySqlExtensionResourceClosingTest { + + public PhpBbExtensionResourceClosingTest(Method method, String name) { + super(method, name); + } + + @Override + protected MySqlExtension createExtension(Settings settings, Columns columns) { + return new PhpBbExtension(settings, columns); + } +} diff --git a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/WordpressExtensionResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/mysqlextensions/WordpressExtensionResourceClosingTest.java new file mode 100644 index 00000000..1ee141ff --- /dev/null +++ b/src/test/java/fr/xephi/authme/datasource/mysqlextensions/WordpressExtensionResourceClosingTest.java @@ -0,0 +1,21 @@ +package fr.xephi.authme.datasource.mysqlextensions; + +import fr.xephi.authme.datasource.Columns; +import fr.xephi.authme.settings.Settings; + +import java.lang.reflect.Method; + +/** + * Resource closing test for {@link WordpressExtension}. + */ +public class WordpressExtensionResourceClosingTest extends AbstractMySqlExtensionResourceClosingTest { + + public WordpressExtensionResourceClosingTest(Method method, String name) { + super(method, name); + } + + @Override + protected MySqlExtension createExtension(Settings settings, Columns columns) { + return new WordpressExtension(settings, columns); + } +} diff --git a/src/test/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtensionResourceClosingTest.java b/src/test/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtensionResourceClosingTest.java new file mode 100644 index 00000000..7da4fb19 --- /dev/null +++ b/src/test/java/fr/xephi/authme/datasource/mysqlextensions/XfBcryptExtensionResourceClosingTest.java @@ -0,0 +1,21 @@ +package fr.xephi.authme.datasource.mysqlextensions; + +import fr.xephi.authme.datasource.Columns; +import fr.xephi.authme.settings.Settings; + +import java.lang.reflect.Method; + +/** + * Resource closing test for {@link XfBcryptExtension}. + */ +public class XfBcryptExtensionResourceClosingTest extends AbstractMySqlExtensionResourceClosingTest { + + public XfBcryptExtensionResourceClosingTest(Method method, String name) { + super(method, name); + } + + @Override + protected MySqlExtension createExtension(Settings settings, Columns columns) { + return new XfBcryptExtension(settings, columns); + } +}