#392 Start integration test for SQLite

This commit is contained in:
ljacqu 2016-02-21 10:46:13 +01:00
parent dfa3921740
commit e8d627c0e1
7 changed files with 324 additions and 59 deletions

View File

@ -376,6 +376,13 @@
<scope>compile</scope>
<optional>true</optional>
</dependency>
<!-- JDBC driver for datasource integration tests -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.8.11.2</version>
<scope>test</scope>
</dependency>
<!-- Log4J Logger (required by the console filter) -->
<dependency>

View File

@ -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,7 +118,7 @@ public class PlayerAuth {
* @param email String
* @param realName String
*/
public PlayerAuth(String nickname, HashedPassword password, int groupId, String ip, long lastLogin,
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;

View File

@ -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");

View File

@ -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;
}
}

View File

@ -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<HashedPassword>() {
@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<PlayerAuth>() {
@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<PlayerAuth>() {
@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));
}
};
}
}

View File

@ -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 <T> void set(Property<T> 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
}
}
}
}

View File

@ -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');