diff --git a/pom.xml b/pom.xml
index b05566938..fd994681a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -376,6 +376,13 @@
compile
true
+
+
+ org.xerial
+ sqlite-jdbc
+ 3.8.11.2
+ test
+
diff --git a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java
index 41672c0c9..c2bb4f704 100644
--- a/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java
+++ b/src/main/java/fr/xephi/authme/cache/auth/PlayerAuth.java
@@ -1,12 +1,11 @@
package fr.xephi.authme.cache.auth;
+import fr.xephi.authme.security.crypts.HashedPassword;
+import org.bukkit.Location;
+
import static com.google.common.base.Objects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
-import org.bukkit.Location;
-
-import fr.xephi.authme.security.crypts.HashedPassword;
-
/**
*/
@@ -85,20 +84,6 @@ public class PlayerAuth {
this(nickname, new HashedPassword(hash), -1, ip, lastLogin, 0, 0, 0, "world", email, realName);
}
- /**
- * Constructor for PlayerAuth.
- *
- * @param nickname String
- * @param hash String
- * @param salt String
- * @param ip String
- * @param lastLogin long
- * @param realName String
- */
- public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, String realName) {
- this(nickname, new HashedPassword(hash, salt), -1, ip, lastLogin, 0, 0, 0, "world", "your@email.com", realName);
- }
-
/**
* Constructor for PlayerAuth.
*
@@ -118,44 +103,6 @@ public class PlayerAuth {
this(nickname, new HashedPassword(hash), -1, ip, lastLogin, x, y, z, world, email, realName);
}
- /**
- * Constructor for PlayerAuth.
- *
- * @param nickname String
- * @param hash String
- * @param salt String
- * @param ip String
- * @param lastLogin long
- * @param x double
- * @param y double
- * @param z double
- * @param world String
- * @param email String
- * @param realName String
- */
- public PlayerAuth(String nickname, String hash, String salt, String ip, long lastLogin, double x, double y,
- double z, String world, String email, String realName) {
- this(nickname, new HashedPassword(hash, salt), -1, ip, lastLogin,
- x, y, z, world, email, realName);
- }
-
- /**
- * Constructor for PlayerAuth.
- *
- * @param nickname String
- * @param hash String
- * @param salt String
- * @param groupId int
- * @param ip String
- * @param lastLogin long
- * @param realName String
- */
- public PlayerAuth(String nickname, String hash, String salt, int groupId, String ip,
- long lastLogin, String realName) {
- this(nickname, new HashedPassword(hash, salt), groupId, ip, lastLogin,
- 0, 0, 0, "world", "your@email.com", realName);
- }
-
/**
* Constructor for PlayerAuth.
*
@@ -171,8 +118,8 @@ public class PlayerAuth {
* @param email String
* @param realName String
*/
- public PlayerAuth(String nickname, HashedPassword password, int groupId, String ip, long lastLogin,
- double x, double y, double z, String world, String email, String realName) {
+ private PlayerAuth(String nickname, HashedPassword password, int groupId, String ip, long lastLogin,
+ double x, double y, double z, String world, String email, String realName) {
this.nickname = nickname.toLowerCase();
this.password = password;
this.ip = ip;
diff --git a/src/main/java/fr/xephi/authme/datasource/SQLite.java b/src/main/java/fr/xephi/authme/datasource/SQLite.java
index 081e386cf..f27d8ef0b 100644
--- a/src/main/java/fr/xephi/authme/datasource/SQLite.java
+++ b/src/main/java/fr/xephi/authme/datasource/SQLite.java
@@ -1,5 +1,6 @@
package fr.xephi.authme.datasource;
+import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.cache.auth.PlayerAuth;
import fr.xephi.authme.security.crypts.HashedPassword;
@@ -46,6 +47,21 @@ public class SQLite implements DataSource {
}
}
+ @VisibleForTesting
+ SQLite(NewSetting settings, Connection connection, boolean executeSetup) {
+ this.database = settings.getProperty(DatabaseSettings.MYSQL_DATABASE);
+ this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);
+ this.col = new Columns(settings);
+ this.con = connection;
+ if (executeSetup) {
+ try {
+ setup();
+ } catch (SQLException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
private synchronized void connect() throws ClassNotFoundException, SQLException {
Class.forName("org.sqlite.JDBC");
ConsoleLogger.info("SQLite driver loaded");
diff --git a/src/test/java/fr/xephi/authme/TestHelper.java b/src/test/java/fr/xephi/authme/TestHelper.java
index a7a60865f..06ff06a4b 100644
--- a/src/test/java/fr/xephi/authme/TestHelper.java
+++ b/src/test/java/fr/xephi/authme/TestHelper.java
@@ -2,6 +2,8 @@ package fr.xephi.authme;
import java.io.File;
import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
/**
* AuthMe test utilities.
@@ -18,11 +20,31 @@ public final class TestHelper {
* @return The project file
*/
public static File getJarFile(String path) {
+ URL url = getUrlOrThrow(path);
+ return new File(url.getFile());
+ }
+
+ /**
+ * Return a {@link Path} to a file in the JAR's resources (main or test).
+ *
+ * @param path The absolute path to the file
+ * @return The Path object to the file
+ */
+ public static Path getJarPath(String path) {
+ String sqlFilePath = getUrlOrThrow(path).getPath();
+ // Windows preprends the path with a '/' or '\', which Paths cannot handle
+ String appropriatePath = System.getProperty("os.name").contains("indow")
+ ? sqlFilePath.substring(1)
+ : sqlFilePath;
+ return Paths.get(appropriatePath);
+ }
+
+ private static URL getUrlOrThrow(String path) {
URL url = TestHelper.class.getResource(path);
if (url == null) {
throw new IllegalStateException("File '" + path + "' could not be loaded");
}
- return new File(url.getFile());
+ return url;
}
}
diff --git a/src/test/java/fr/xephi/authme/datasource/AuthMeMatchers.java b/src/test/java/fr/xephi/authme/datasource/AuthMeMatchers.java
new file mode 100644
index 000000000..3bb8af816
--- /dev/null
+++ b/src/test/java/fr/xephi/authme/datasource/AuthMeMatchers.java
@@ -0,0 +1,91 @@
+package fr.xephi.authme.datasource;
+
+import fr.xephi.authme.cache.auth.PlayerAuth;
+import fr.xephi.authme.security.crypts.HashedPassword;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+
+import java.util.Objects;
+
+/**
+ * Custom matchers for AuthMe entities.
+ */
+public final class AuthMeMatchers {
+
+ private AuthMeMatchers() {
+ }
+
+ public static Matcher super HashedPassword> equalToHash(final String hash) {
+ return equalToHash(hash, null);
+ }
+
+ public static Matcher super HashedPassword> equalToHash(final String hash, final String salt) {
+ return new BaseMatcher() {
+ @Override
+ public boolean matches(Object item) {
+ if (item instanceof HashedPassword) {
+ HashedPassword input = (HashedPassword) item;
+ return Objects.equals(hash, input.getHash()) && Objects.equals(salt, input.getSalt());
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ String representation = "'" + hash + "'";
+ if (salt != null) {
+ representation += ", '" + salt + "'";
+ }
+ description.appendValue("HashedPassword(" + representation + ")");
+ }
+ };
+ }
+
+ public static Matcher super PlayerAuth> hasAuthBasicData(final String name, final String realName,
+ final String email, final String ip) {
+ return new BaseMatcher() {
+ @Override
+ public boolean matches(Object item) {
+ if (item instanceof PlayerAuth) {
+ PlayerAuth input = (PlayerAuth) item;
+ return Objects.equals(name, input.getNickname())
+ && Objects.equals(realName, input.getRealName())
+ && Objects.equals(email, input.getEmail())
+ && Objects.equals(ip, input.getIp());
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendValue(String.format("PlayerAuth with name %s, realname %s, email %s, ip %s",
+ name, realName, email, ip));
+ }
+ };
+ }
+
+ public static Matcher super PlayerAuth> hasAuthLocation(final double x, final double y, final double z,
+ final String world) {
+ return new BaseMatcher() {
+ @Override
+ public boolean matches(Object item) {
+ if (item instanceof PlayerAuth) {
+ PlayerAuth input = (PlayerAuth) item;
+ return Objects.equals(x, input.getQuitLocX())
+ && Objects.equals(y, input.getQuitLocY())
+ && Objects.equals(z, input.getQuitLocZ())
+ && Objects.equals(world, input.getWorld());
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendValue(String.format("PlayerAuth with quit location (x: %f, y: %f, z: %f, world: %s)",
+ x, y, z, world));
+ }
+ };
+ }
+
+}
diff --git a/src/test/java/fr/xephi/authme/datasource/SQLiteIntegrationTest.java b/src/test/java/fr/xephi/authme/datasource/SQLiteIntegrationTest.java
new file mode 100644
index 000000000..e23126356
--- /dev/null
+++ b/src/test/java/fr/xephi/authme/datasource/SQLiteIntegrationTest.java
@@ -0,0 +1,160 @@
+package fr.xephi.authme.datasource;
+
+import fr.xephi.authme.ConsoleLoggerTestInitializer;
+import fr.xephi.authme.TestHelper;
+import fr.xephi.authme.cache.auth.PlayerAuth;
+import fr.xephi.authme.security.crypts.HashedPassword;
+import fr.xephi.authme.settings.NewSetting;
+import fr.xephi.authme.settings.domain.Property;
+import fr.xephi.authme.settings.properties.DatabaseSettings;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import static fr.xephi.authme.datasource.AuthMeMatchers.equalToHash;
+import static fr.xephi.authme.datasource.AuthMeMatchers.hasAuthBasicData;
+import static fr.xephi.authme.datasource.AuthMeMatchers.hasAuthLocation;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Integration test for {@link SQLite}.
+ */
+public class SQLiteIntegrationTest {
+
+ /** Mock for a settings instance. */
+ private static NewSetting settings;
+ /** Collection of SQL statements to execute for initialization of a test. */
+ private static String[] sqlInitialize;
+ /** Connection to the SQLite test database. */
+ private Connection con;
+
+ /**
+ * Set up the settings mock to return specific values for database settings and load {@link #sqlInitialize}.
+ */
+ @BeforeClass
+ public static void initializeSettings() throws IOException, ClassNotFoundException {
+ // Check that we have an implementation for SQLite
+ Class.forName("org.sqlite.JDBC");
+
+ settings = mock(NewSetting.class);
+ when(settings.getProperty(any(Property.class))).thenAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ return ((Property) invocation.getArguments()[0]).getDefaultValue();
+ }
+ });
+ set(DatabaseSettings.MYSQL_DATABASE, "sqlite-test");
+ set(DatabaseSettings.MYSQL_TABLE, "authme");
+ set(DatabaseSettings.MYSQL_COL_SALT, "salt");
+ ConsoleLoggerTestInitializer.setupLogger();
+
+ Path sqlInitFile = TestHelper.getJarPath("/datasource-integration/sqlite-initialize.sql");
+ // Note ljacqu 20160221: It appears that we can only run one statement per Statement.execute() so we split
+ // the SQL file by ";\n" as to get the individual statements
+ sqlInitialize = new String(Files.readAllBytes(sqlInitFile)).split(";\\n");
+ }
+
+ @Before
+ public void initializeConnectionAndTable() throws SQLException, ClassNotFoundException {
+ silentClose(con);
+ Connection connection = DriverManager.getConnection("jdbc:sqlite::memory:");
+ try (Statement st = connection.createStatement()) {
+ st.execute("DROP TABLE IF EXISTS authme");
+ for (String statement : sqlInitialize) {
+ st.execute(statement);
+ }
+ }
+ con = connection;
+ }
+
+ @Test
+ public void shouldReturnIfAuthIsAvailableOrNot() {
+ // given
+ DataSource dataSource = new SQLite(settings, con, false);
+
+ // when
+ boolean bobby = dataSource.isAuthAvailable("bobby");
+ boolean chris = dataSource.isAuthAvailable("chris");
+ boolean user = dataSource.isAuthAvailable("USER");
+
+ // then
+ assertThat(bobby, equalTo(true));
+ assertThat(chris, equalTo(false));
+ assertThat(user, equalTo(true));
+ }
+
+ @Test
+ public void shouldReturnPassword() {
+ // given
+ DataSource dataSource = new SQLite(settings, con, false);
+
+ // when
+ HashedPassword bobbyPassword = dataSource.getPassword("bobby");
+ HashedPassword invalidPassword = dataSource.getPassword("doesNotExist");
+ HashedPassword userPassword = dataSource.getPassword("user");
+
+ // then
+ assertThat(bobbyPassword, equalToHash(
+ "$SHA$11aa0706173d7272$dbba96681c2ae4e0bfdf226d70fbbc5e4ee3d8071faa613bc533fe8a64817d10"));
+ assertThat(invalidPassword, nullValue());
+ assertThat(userPassword, equalToHash("b28c32f624a4eb161d6adc9acb5bfc5b", "f750ba32"));
+ }
+
+ @Test
+ public void shouldGetAuth() {
+ // given
+ DataSource dataSource = new SQLite(settings, con, false);
+
+ // when
+ PlayerAuth invalidAuth = dataSource.getAuth("notInDB");
+ PlayerAuth bobbyAuth = dataSource.getAuth("Bobby");
+ PlayerAuth userAuth = dataSource.getAuth("user");
+
+ // then
+ assertThat(invalidAuth, nullValue());
+
+ assertThat(bobbyAuth, hasAuthBasicData("bobby", "Bobby", "your@email.com", "123.45.67.89"));
+ assertThat(bobbyAuth, hasAuthLocation(1.05, 2.1, 4.2, "world"));
+ assertThat(bobbyAuth.getLastLogin(), equalTo(1449136800L));
+ assertThat(bobbyAuth.getPassword(), equalToHash(
+ "$SHA$11aa0706173d7272$dbba96681c2ae4e0bfdf226d70fbbc5e4ee3d8071faa613bc533fe8a64817d10"));
+
+ assertThat(userAuth, hasAuthBasicData("user", "user", "user@example.org", "34.56.78.90"));
+ assertThat(userAuth, hasAuthLocation(124.1, 76.3, -127.8, "nether"));
+ assertThat(userAuth.getLastLogin(), equalTo(1453242857L));
+ assertThat(userAuth.getPassword(), equalToHash("b28c32f624a4eb161d6adc9acb5bfc5b", "f750ba32"));
+ }
+
+ private static void set(Property property, T value) {
+ when(settings.getProperty(property)).thenReturn(value);
+ }
+
+ private static void silentClose(Connection con) {
+ if (con != null) {
+ try {
+ if (!con.isClosed()) {
+ con.close();
+ }
+ } catch (SQLException e) {
+ // silent
+ }
+ }
+ }
+
+
+}
diff --git a/src/test/resources/datasource-integration/sqlite-initialize.sql b/src/test/resources/datasource-integration/sqlite-initialize.sql
new file mode 100644
index 000000000..dc06fcaba
--- /dev/null
+++ b/src/test/resources/datasource-integration/sqlite-initialize.sql
@@ -0,0 +1,22 @@
+-- Important: separate SQL statements by ; followed directly by a newline. We split the file contents by ";\n"
+
+CREATE TABLE authme (
+ id INTEGER AUTO_INCREMENT,
+ username VARCHAR(255) NOT NULL UNIQUE,
+ password VARCHAR(255) NOT NULL,
+ ip VARCHAR(40) NOT NULL,
+ lastlogin BIGINT,
+ x DOUBLE NOT NULL DEFAULT '0.0',
+ y DOUBLE NOT NULL DEFAULT '0.0',
+ z DOUBLE NOT NULL DEFAULT '0.0',
+ world VARCHAR(255) NOT NULL DEFAULT 'world',
+ email VARCHAR(255) DEFAULT 'your@email.com',
+ isLogged INT DEFAULT '0', realname VARCHAR(255) NOT NULL DEFAULT 'Player',
+ salt varchar(255),
+ CONSTRAINT table_const_prim PRIMARY KEY (id)
+);
+
+INSERT INTO authme (id, username, password, ip, lastlogin, x, y, z, world, email, isLogged, realname, salt)
+VALUES (1,'bobby','$SHA$11aa0706173d7272$dbba96681c2ae4e0bfdf226d70fbbc5e4ee3d8071faa613bc533fe8a64817d10','123.45.67.89',1449136800,1.05,2.1,4.2,'world','your@email.com',0,'Bobby',NULL);
+INSERT INTO authme (id, username, password, ip, lastlogin, x, y, z, world, email, isLogged, realname, salt)
+VALUES (NULL,'user','b28c32f624a4eb161d6adc9acb5bfc5b','34.56.78.90',1453242857,124.1,76.3,-127.8,'nether','user@example.org',0,'user','f750ba32');