#792 #814 Create command to remove NOT NULL constraints

- Create command under /authme debug that allows to change the 'nullable' status of MySQL columns (currently last date and email only)
   - We need to offer a default value for forum integrations that have a NOT NULL email column. Offering a command avoids us from force-migrating existing databases while still offering migrations in both directions
- Change in default value handling: lack of values are not handled by setting default values to the PlayerAuth anymore, and reading a default value from the database into a PlayerAuth will be translated into null by the PlayerAuth builder
- When a new database is created, email and lastlogin are now nullable and lack a default a value

Open points:
- Finish MySqlDefaultChangerTest
- Revise purging logic (#792)
- Allow to have more columns nullable (#814)
This commit is contained in:
ljacqu
2017-10-15 12:56:13 +02:00
parent 718c38aa24
commit 1df5308e56
19 changed files with 583 additions and 40 deletions
@@ -111,4 +111,25 @@ public class LastLoginCommandTest {
assertThat(captor.getAllValues().get(2), containsString("123.45.66.77"));
}
@Test
public void shouldHandleNullLastLoginDate() {
// given
String name = "player";
PlayerAuth auth = PlayerAuth.builder()
.name(name)
.lastIp("123.45.67.89")
.build();
given(dataSource.getAuth(name)).willReturn(auth);
CommandSender sender = mock(CommandSender.class);
// when
command.executeCommand(sender, Collections.singletonList(name));
// then
verify(dataSource).getAuth(name);
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(sender, times(2)).sendMessage(captor.capture());
assertThat(captor.getAllValues().get(0), allOf(containsString(name), containsString("never")));
assertThat(captor.getAllValues().get(1), containsString("123.45.67.89"));
}
}
@@ -0,0 +1,69 @@
package fr.xephi.authme.command.executable.authme.debug;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* Consistency test for {@link MySqlDefaultChanger.Columns} enum.
*/
public class MySqlDefaultChangerColumnsTest {
@Test
public void shouldAllHaveDifferentNameProperty() {
// given
Set<String> properties = new HashSet<>();
// when / then
for (MySqlDefaultChanger.Columns col : MySqlDefaultChanger.Columns.values()) {
if (!properties.add(col.columnName.getPath())) {
fail("Column '" + col + "' has a column name property path that was already encountered: "
+ col.columnName.getPath());
}
}
}
@Test
public void shouldHaveMatchingNullableAndNotNullDefinition() {
for (MySqlDefaultChanger.Columns col : MySqlDefaultChanger.Columns.values()) {
verifyHasCorrespondingColumnDefinitions(col);
}
}
@Test
public void shouldHaveMatchingDefaultValueInNotNullDefinition() {
for (MySqlDefaultChanger.Columns col : MySqlDefaultChanger.Columns.values()) {
verifyHasSameDefaultValueInNotNullDefinition(col);
}
}
private void verifyHasCorrespondingColumnDefinitions(MySqlDefaultChanger.Columns column) {
// given / when
String nullable = column.nullableDefinition;
String notNull = column.notNullDefinition;
// then
String expectedNotNull = nullable + " NOT NULL DEFAULT ";
assertThat(column.name(), notNull.startsWith(expectedNotNull), equalTo(true));
// Check that `notNull` length is bigger because we expect a value after DEFAULT
assertThat(column.name(), notNull.length() > expectedNotNull.length(), equalTo(true));
}
private void verifyHasSameDefaultValueInNotNullDefinition(MySqlDefaultChanger.Columns column) {
// given / when
String notNull = column.notNullDefinition;
Object defaultValue = column.defaultValue;
// then
String defaultValueAsString = String.valueOf(defaultValue);
if (!notNull.endsWith("DEFAULT " + defaultValueAsString)
&& !notNull.endsWith("DEFAULT '" + defaultValueAsString + "'")) {
fail("Expected '" + column + "' not-null definition to contain DEFAULT " + defaultValueAsString);
}
}
}
@@ -0,0 +1,60 @@
package fr.xephi.authme.command.executable.authme.debug;
import fr.xephi.authme.ReflectionTestUtils;
import fr.xephi.authme.data.auth.PlayerCache;
import fr.xephi.authme.datasource.CacheDataSource;
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.settings.Settings;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
/**
* Test for {@link MySqlDefaultChanger}.
*/
@RunWith(MockitoJUnitRunner.class)
public class MySqlDefaultChangerTest {
@Mock
private Settings settings;
@Test
public void shouldReturnSameDataSourceInstance() {
// given
DataSource dataSource = mock(DataSource.class);
// when
DataSource result = MySqlDefaultChanger.unwrapSourceFromCacheDataSource(dataSource);
// then
assertThat(result, equalTo(dataSource));
}
@Test
public void shouldUnwrapCacheDataSource() {
// given
DataSource source = mock(DataSource.class);
PlayerCache playerCache = mock(PlayerCache.class);
CacheDataSource cacheDataSource = new CacheDataSource(source, playerCache);
// when
DataSource result = MySqlDefaultChanger.unwrapSourceFromCacheDataSource(cacheDataSource);
// then
assertThat(result, equalTo(source));
}
// TODO #792: Add more tests
private MySqlDefaultChanger createDefaultChanger(DataSource dataSource) {
MySqlDefaultChanger defaultChanger = new MySqlDefaultChanger();
ReflectionTestUtils.setField(defaultChanger, "dataSource", dataSource);
ReflectionTestUtils.setField(defaultChanger, "settings", settings);
return defaultChanger;
}
}