/*
* Copyright 2006-2007, Unitils.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.unitils.dbmaintainer;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.unitils.UnitilsJUnit4;
import org.unitils.core.ConfigurationLoader;
import org.unitils.core.UnitilsException;
import org.unitils.core.dbsupport.DbSupport;
import org.unitils.core.dbsupport.DbSupportFactory;
import org.unitils.core.dbsupport.DefaultSQLHandler;
import static org.unitils.core.util.SQLTestUtils.dropTestTables;
import org.unitils.database.SQLUnitils;
import org.unitils.database.annotations.TestDataSource;
import static org.unitils.dbmaintainer.DBMaintainer.PROPKEY_GENERATE_DATA_SET_STRUCTURE_ENABLED;
import static org.unitils.dbmaintainer.DBMaintainer.PROPKEY_KEEP_RETRYING_AFTER_ERROR_ENABLED;
import static org.unitils.dbmaintainer.script.impl.DefaultScriptSource.PROPKEY_SCRIPT_LOCATIONS;
import static org.unitils.dbmaintainer.util.DatabaseModuleConfigUtils.PROPKEY_DATABASE_DIALECT;
import static org.unitils.dbmaintainer.version.impl.DefaultExecutedScriptInfoSource.PROPERTY_AUTO_CREATE_EXECUTED_SCRIPTS_TABLE;
import org.unitils.thirdparty.org.apache.commons.io.FileUtils;
import org.unitils.thirdparty.org.apache.commons.io.IOUtils;
import org.unitils.util.PropertyUtils;
import javax.sql.DataSource;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.junit.Ignore;
import org.unitils.core.Unitils;
import org.unitils.database.DatabaseModule;
/**
* @author Filip Neven
* @author Tim Ducheyne
*/
public class DbMaintainerIntegrationTest extends UnitilsJUnit4 {
private static final String INITIAL_INCREMENTAL_1 = "initial_incremental_1";
private static final String INITIAL_INCREMENTAL_2 = "initial_incremental_2";
private static final String INITIAL_REPEATABLE = "initial_repeatable";
private static final String NEW_INCREMENTAL = "new_incremental";
private static final String NEW_REPEATABLE = "new_repeatable";
private static final String UPDATED_REPEATABLE = "updated_repeatable";
private static final String UPDATED_INCREMENTAL_1 = "updated_incremental_1";
private static final String NEW_INCREMENTAL_LOWER_INDEX = "new_incremental_lower_index";
private static final String SECOND_LOCATION_INCREMENTAL = "second_location_incremental";
private static final String SECOND_LOCATION_REPEATABLE = "second_location_repeatable";
private static final String BEFORE_INITIAL_TABLE = "before_initial";
private static final String dialect = "hsqldb";
/* The logger instance for this class */
private static final Log logger = LogFactory.getLog(DbMaintainerIntegrationTest.class);
private List<String> schemas;
/* DataSource for the test database, is injected */
//@TestDataSource
private DataSource dataSource = null;
private File scriptsLocation1;
private File scriptsLocation2;
private DbSupport dbSupport;
private Properties configuration;
/* True if current test is not for the current dialect */
private boolean disabled;
@Before
public void setUp() throws Exception {
schemas = new ArrayList<String>();
schemas.add("PUBLIC");
configuration = new ConfigurationLoader().loadConfiguration();
configuration.setProperty("org.unitils.dbmaintainer.script.ScriptSource.implClassName", "org.unitils.dbmaintainer.script.impl.DefaultScriptSource");
this.disabled = !"hsqldb".equals(PropertyUtils.getString(PROPKEY_DATABASE_DIALECT, configuration));
if (disabled) {
return;
}
scriptsLocation2 = new File(System.getProperty("java.io.tmpdir") + "/dbmaintain-integrationtest/scripts2");
scriptsLocation1 = new File(System.getProperty("java.io.tmpdir") + "/dbmaintain-integrationtest/scripts1");
logger.info("temp dir created as script location1 : " + scriptsLocation1);
logger.info("temp dir created as script location2 : " + scriptsLocation2);
configuration.put(PROPERTY_AUTO_CREATE_EXECUTED_SCRIPTS_TABLE, "true");
configuration.put(PROPKEY_SCRIPT_LOCATIONS, scriptsLocation1.getAbsolutePath());
configuration.put(PROPKEY_GENERATE_DATA_SET_STRUCTURE_ENABLED, "false");
DatabaseModule databaseModule = Unitils.getInstance().getModulesRepository().getModuleOfType(DatabaseModule.class);
dataSource = databaseModule.getWrapper("").getTransactionalDataSourceAndActivateTransactionIfNeeded(this);
dbSupport = DbSupportFactory.getDefaultDbSupport(configuration, new DefaultSQLHandler(dataSource), dialect, schemas.get(0));
clearScriptsDirectory();
clearTestDatabase();
}
@After
public void cleanup() {
if (disabled) {
return;
}
clearScriptsDirectory();
clearTestDatabase();
}
@Test
public void initial() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
addInitialScripts();
updateDatabase();
assertTablesExist(INITIAL_INCREMENTAL_1, INITIAL_REPEATABLE, INITIAL_INCREMENTAL_2);
}
@Test
public void addIncremental() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
addInitialScripts();
updateDatabase();
assertTablesDontExist(NEW_INCREMENTAL);
newIncrementalScript();
updateDatabase();
assertTablesExist(NEW_INCREMENTAL);
}
@Test
public void addRepeatable() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
addInitialScripts();
updateDatabase();
assertTablesDontExist(NEW_REPEATABLE);
newRepeatableScript();
updateDatabase();
assertTablesExist(NEW_REPEATABLE);
}
@Ignore//Does not work on mac
@Test
public void updateRepeatable() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
addInitialScripts();
updateDatabase();
updateRepeatableScript();
updateDatabase();
assertTablesExist(UPDATED_REPEATABLE);
}
@Ignore //does not work on mac
@Test
public void updateIncremental_fromScratchEnabled() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
enableFromScratch();
addInitialScripts();
updateDatabase();
updateIncrementalScript();
updateDatabase();
assertTablesDontExist(INITIAL_INCREMENTAL_1);
assertTablesExist(UPDATED_INCREMENTAL_1);
}
@Test
public void updateIncremental_fromScratchDisabled() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
addInitialScripts();
updateDatabase();
updateIncrementalScript();
try {
updateDatabase();
} catch (UnitilsException e) {
// TODO
//assertMessageContains(e.getMessage(), "existing", "modified", INITIAL_INCREMENTAL_1 + ".sql");
}
}
@Test
public void addIncrementalWithLowerIndex_fromScratchEnabled() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
enableFromScratch();
addInitialScripts();
updateDatabase();
addIncrementalScriptWithLowerIndex();
updateDatabase();
assertTablesExist(NEW_INCREMENTAL_LOWER_INDEX);
}
@Test
public void addIncrementalWithLowerIndex_fromScratchDisabled() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
addInitialScripts();
updateDatabase();
addIncrementalScriptWithLowerIndex();
try {
updateDatabase();
} catch (UnitilsException e) {
// TODO
//assertMessageContains(e.getMessage(), "added", "lower index", NEW_INCREMENTAL_LOWER_INDEX + ".sql");
}
}
@Test
public void removeExistingIncremental_fromScratchEnabled() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
enableFromScratch();
addInitialScripts();
updateDatabase();
removeIncrementalScript();
updateDatabase();
assertTablesDontExist(INITIAL_INCREMENTAL_1);
}
@Test
public void removeExistingIncremental_fromScratchDisabled() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
addInitialScripts();
updateDatabase();
removeIncrementalScript();
try {
updateDatabase();
} catch (UnitilsException e) {
// TODO
//assertMessageContains(e.getMessage(), "removed", INITIAL_INCREMENTAL_1 + ".sql");
}
}
@Ignore//does not work on mac
@Test
public void errorInIncrementalScript_dontKeepRetrying() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
enableFromScratch();
configuration.put(PROPKEY_KEEP_RETRYING_AFTER_ERROR_ENABLED, "false");
addInitialScripts();
errorInInitialScript();
newIncrementalScript();
// execute the scripts
// the second script will have an error
try {
updateDatabase();
} catch (UnitilsException e) {
assertMessageContains(e.getMessage(), "Error while performing database update");
}
assertTablesDontExist(INITIAL_INCREMENTAL_2, NEW_INCREMENTAL);
// try again
// No script should have been executed, an exception should have been raised that the script that
// caused the error, was not changed.
try {
updateDatabase();
} catch (UnitilsException e) {
assertMessageContains(e.getMessage(), "During a previous database update");
}
assertTablesDontExist(INITIAL_INCREMENTAL_2, NEW_INCREMENTAL);
// change the script and try again
// the database should have been recreated from scratch and all the tables should have been re-created
fixErrorInInitialScript();
updateDatabase();
assertTablesExist(INITIAL_INCREMENTAL_1, INITIAL_REPEATABLE, INITIAL_INCREMENTAL_2, NEW_INCREMENTAL);
}
@Test
public void errorInIncrementalScript_keepRetrying() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
enableFromScratch();
configuration.put(PROPKEY_KEEP_RETRYING_AFTER_ERROR_ENABLED, "true");
addInitialScripts();
errorInInitialScript();
newIncrementalScript();
// execute the scripts
// the second script will have an error
try {
updateDatabase();
} catch (UnitilsException e) {
assertMessageContains(e.getMessage(), "Error while performing database update");
}
assertTablesDontExist(INITIAL_INCREMENTAL_2, NEW_INCREMENTAL);
// try again
// The database should have been recreated from scratch and the second script should have caused the
// same error
try {
updateDatabase();
} catch (UnitilsException e) {
assertMessageContains(e.getMessage(), "Error while performing database update");
}
assertTablesDontExist(INITIAL_INCREMENTAL_2, NEW_INCREMENTAL);
// change the script and try again
// the database should have been recreated from scratch and all the tables should have been re-created
fixErrorInInitialScript();
updateDatabase();
assertTablesExist(INITIAL_INCREMENTAL_1, INITIAL_REPEATABLE, INITIAL_INCREMENTAL_2, NEW_INCREMENTAL);
}
@Test
public void errorInRepeatableScript() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
addInitialScripts();
//createErrorInRepeatableScript();
try {
updateDatabase();
} catch (UnitilsException e) {
// TODO
//assertMessageContains(e.getMessage(), "error", INITIAL_INCREMENTAL_2 + ".sql");
}
try {
updateDatabase();
} catch (UnitilsException e) {
assertMessageContains(e.getMessage(), "previous run", "error", INITIAL_REPEATABLE + ".sql");
}
//fixErrorInRepeatableScript();
updateDatabase();
assertTablesExist(INITIAL_INCREMENTAL_1, INITIAL_REPEATABLE, INITIAL_INCREMENTAL_2);
}
@Test
public void moreThanOneScriptLocation() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
configureSecondScriptLocation();
addInitialScripts();
addSecondLocationScripts();
updateDatabase();
assertTablesExist(INITIAL_INCREMENTAL_1, INITIAL_REPEATABLE, INITIAL_INCREMENTAL_2, SECOND_LOCATION_INCREMENTAL, SECOND_LOCATION_REPEATABLE);
}
/**
* Verifies that, if the dbmaintain_scripts table doesn't exist yet, and the autoCreateExecutedScriptsInfoTable property is set to true,
* we start with a from scratch update
*/
@Test
public void initialFromScratchUpdate() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
createTable(BEFORE_INITIAL_TABLE);
addInitialScripts();
updateDatabase();
assertTablesDontExist(BEFORE_INITIAL_TABLE);
}
/**
* Verifies that, if the dbmaintain_scripts table doesn't exist yet, and the autoCreateExecutedScriptsInfoTable property is set to true,
* we start with a from scratch update
*/
@Test
public void noInitialFromScratchUpdateIfFromScratchDisabled() {
if (disabled) {
logger.warn("Test is not for current dialect. Skipping test.");
return;
}
disableFromScratch();
createTable(BEFORE_INITIAL_TABLE);
addInitialScripts();
updateDatabase();
assertTablesExist(BEFORE_INITIAL_TABLE);
}
private void createTable(String tableName) {
SQLUnitils.executeUpdate("create table " + tableName + " (test varchar(10))", dbSupport.getSQLHandler().getDataSource());
}
private void errorInInitialScript() {
createScript("02_latest/01_" + INITIAL_INCREMENTAL_2 + ".sql", "this is an error;");
}
private void fixErrorInInitialScript() {
createScript("02_latest/01_" + INITIAL_INCREMENTAL_2 + ".sql", "create table " + INITIAL_INCREMENTAL_2 + " (test varchar(10));");
}
private void removeIncrementalScript() {
removeScript("01_base/01_" + INITIAL_INCREMENTAL_1 + ".sql");
}
private void addIncrementalScriptWithLowerIndex() {
createScript("01_base/03_" + NEW_INCREMENTAL_LOWER_INDEX + ".sql", "create table " + NEW_INCREMENTAL_LOWER_INDEX + " (test varchar(10));");
}
private void assertMessageContains(String message, String... subStrings) {
for (String subString : subStrings) {
assertTrue("Expected message to contain substring " + subString + ", but it doesn't.\nMessage was: " + message, message.contains(subString));
}
}
private void enableFromScratch() {
configuration.put(DBMaintainer.PROPKEY_FROM_SCRATCH_ENABLED, "true");
}
private void disableFromScratch() {
configuration.put(DBMaintainer.PROPKEY_FROM_SCRATCH_ENABLED, "false");
}
private void updateIncrementalScript() {
createScript("01_base/01_" + INITIAL_INCREMENTAL_1 + ".sql", "create table " + UPDATED_INCREMENTAL_1 + "(test varchar(10));");
}
private void updateRepeatableScript() {
createScript("01_base/" + INITIAL_REPEATABLE + ".sql", "drop table " + INITIAL_REPEATABLE + " if exists;\n" +
"drop table " + UPDATED_REPEATABLE + " if exists;\n" +
"create table " + UPDATED_REPEATABLE + "(test varchar(10));");
}
private void newIncrementalScript() {
createScript("02_latest/02_" + NEW_INCREMENTAL + ".sql", "create table " + NEW_INCREMENTAL + " (test varchar(10));");
}
private void newRepeatableScript() {
createScript("02_latest/" + NEW_REPEATABLE + ".sql", "drop table " + NEW_REPEATABLE + " if exists;\n" +
"create table " + NEW_REPEATABLE + " (test varchar(10));");
}
private void addInitialScripts() {
createScript("01_base/01_" + INITIAL_INCREMENTAL_1 + ".sql", "create table " + INITIAL_INCREMENTAL_1 + "(test varchar(10));");
createScript("01_base/" + INITIAL_REPEATABLE + ".sql", "drop table " + INITIAL_REPEATABLE + " if exists;\n" +
"create table " + INITIAL_REPEATABLE + "(test varchar(10));");
createScript("02_latest/01_" + INITIAL_INCREMENTAL_2 + ".sql", "create table " + INITIAL_INCREMENTAL_2 + "(test varchar(10));");
}
private void addSecondLocationScripts() {
createScript(scriptsLocation2, "01_base/02_" + SECOND_LOCATION_INCREMENTAL + ".sql", "create table " + SECOND_LOCATION_INCREMENTAL + "(test varchar(10));");
createScript(scriptsLocation2, "01_base/" + SECOND_LOCATION_REPEATABLE + ".sql", "create table " + SECOND_LOCATION_REPEATABLE + "(test varchar(10));");
}
private void assertTablesExist(String... tables) {
Set<String> tableNames = dbSupport.getTableNames();
for (String table : tables) {
assertTrue(table + " does not exist", tableNames.contains(dbSupport.toCorrectCaseIdentifier(table)));
}
}
private void assertTablesDontExist(String... tables) {
Set<String> tableNames = dbSupport.getTableNames();
for (String table : tables) {
assertFalse(table + " exists, while it shouldn't", tableNames.contains(dbSupport.toCorrectCaseIdentifier(table)));
}
}
private void updateDatabase() {
DBMaintainer dbMaintainer = new DBMaintainer(configuration, new DefaultSQLHandler(dataSource), dialect, schemas);
dbMaintainer.updateDatabase(schemas.get(0), true);
}
private void clearTestDatabase() {
dropTestTables(dbSupport, "dbmaintain_scripts", INITIAL_INCREMENTAL_1, INITIAL_INCREMENTAL_2,
INITIAL_REPEATABLE, NEW_INCREMENTAL, NEW_REPEATABLE, UPDATED_INCREMENTAL_1, UPDATED_REPEATABLE,
NEW_INCREMENTAL_LOWER_INDEX, SECOND_LOCATION_INCREMENTAL, SECOND_LOCATION_REPEATABLE, BEFORE_INITIAL_TABLE);
}
private void createScript(String relativePath, String scriptContent) {
createScript(scriptsLocation1, relativePath, scriptContent);
}
private void createScript(File scriptsLocation, String relativePath, String scriptContent) {
Writer fileWriter = null;
try {
File scriptFile = new File(scriptsLocation.getAbsolutePath() + "/" + relativePath);
scriptFile.getParentFile().mkdirs();
fileWriter = new FileWriter(scriptFile);
IOUtils.copy(new StringReader(scriptContent), fileWriter);
} catch (IOException e) {
throw new UnitilsException(e);
} finally {
IOUtils.closeQuietly(fileWriter);
}
}
private void removeScript(String relativePath) {
File scriptFile = new File(scriptsLocation1.getAbsolutePath() + "/" + relativePath);
scriptFile.delete();
}
private void clearScriptsDirectory() {
try {
FileUtils.cleanDirectory(scriptsLocation1);
FileUtils.cleanDirectory(scriptsLocation2);
} catch (IOException e) {
throw new UnitilsException(e);
} catch (IllegalArgumentException e) {
// Ignored
}
}
private void configureSecondScriptLocation() {
configuration.put("dbMaintainer.script.locations", scriptsLocation1.getAbsolutePath() + "," + scriptsLocation2.getAbsolutePath());
}
}