/*
* Copyright 2008, 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 org.junit.Assert.fail;
import org.junit.Before;
import org.junit.Test;
import org.unitils.UnitilsJUnit4;
import org.unitils.core.UnitilsException;
import org.unitils.dbmaintainer.clean.DBClearer;
import org.unitils.dbmaintainer.script.ExecutedScript;
import org.unitils.dbmaintainer.script.Script;
import org.unitils.dbmaintainer.script.ScriptContentHandle;
import org.unitils.dbmaintainer.script.ScriptSource;
import org.unitils.dbmaintainer.script.impl.DefaultScriptRunner;
import org.unitils.dbmaintainer.structure.ConstraintsDisabler;
import org.unitils.dbmaintainer.structure.DataSetStructureGenerator;
import org.unitils.dbmaintainer.structure.SequenceUpdater;
import org.unitils.dbmaintainer.version.ExecutedScriptInfoSource;
import org.unitils.inject.annotation.InjectIntoByType;
import org.unitils.inject.annotation.TestedObject;
import org.unitils.mock.ArgumentMatchers;
import org.unitils.mock.Mock;
import org.unitils.mock.MockUnitils;
import static org.unitils.mock.MockUnitils.assertNoMoreInvocations;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
/**
* Tests the main algorithm of the DBMaintainer, using mocks for all implementation classes.
*
* @author Filip Neven
* @author Tim Ducheyne
*/
public class DBMaintainerTest extends UnitilsJUnit4 {
@InjectIntoByType
private Mock<ExecutedScriptInfoSource> mockExecutedScriptInfoSource;
@InjectIntoByType
private Mock<ScriptSource> mockScriptSource;
@InjectIntoByType
private Mock<DefaultScriptRunner> mockScriptRunner;
@InjectIntoByType
private Mock<DBClearer> mockDbClearer;
@InjectIntoByType
private Mock<ConstraintsDisabler> mockConstraintsDisabler;
@InjectIntoByType
private Mock<SequenceUpdater> mockSequenceUpdater;
@InjectIntoByType
private Mock<DataSetStructureGenerator> mockDataSetStructureGenerator;
@TestedObject
private DBMaintainer dbMaintainer;
/* Test database update scripts */
private List<Script> scripts, postProcessingScripts;
private List<ExecutedScript> alreadyExecutedScripts;
private ScriptContentHandle sciptContentHandle1, sciptContentHandle2, postProcessingSciptContentHandle1, postProcessingSciptContentHandle2;
private String dialect;
private String schema;
/**
* Create an instance of DBMaintainer
*
* @throws Exception
*/
@Before
public void setUp() throws Exception {
dialect = "hsqldb";
schema = "public";
dbMaintainer = new DBMaintainer();
dbMaintainer.setDialect(dialect);
dbMaintainer.fromScratchEnabled = true;
dbMaintainer.keepRetryingAfterError = true;
dbMaintainer.disableConstraintsEnabled = true;
scripts = new ArrayList<Script>();
sciptContentHandle1 = MockUnitils.createDummy(ScriptContentHandle.class);
Script script1 = new Script("01_script1.sql", 0L, sciptContentHandle1);
scripts.add(script1);
sciptContentHandle2 = MockUnitils.createDummy(ScriptContentHandle.class);
Script script2 = new Script("02_script2.sql", 0L, sciptContentHandle2);
scripts.add(script2);
alreadyExecutedScripts = new ArrayList<ExecutedScript>();
alreadyExecutedScripts.add(new ExecutedScript(script1, null, true));
alreadyExecutedScripts.add(new ExecutedScript(script2, null, true));
postProcessingScripts = new ArrayList<Script>();
postProcessingSciptContentHandle1 = MockUnitils.createDummy(ScriptContentHandle.class);
postProcessingScripts.add(new Script("post-script1.sql", 0L, postProcessingSciptContentHandle1));
postProcessingSciptContentHandle2 = MockUnitils.createDummy(ScriptContentHandle.class);
postProcessingScripts.add(new Script("post-script2.sql", 0L, postProcessingSciptContentHandle2));
HashSet<ExecutedScript> hashSet = new HashSet<ExecutedScript>(alreadyExecutedScripts);
mockExecutedScriptInfoSource.returns(hashSet).getExecutedScripts();
}
@Test
public void testNoUpdateNeeded() {
// Set database version and available script expectations
expectNoScriptModifications();
expectPostProcessingScripts(postProcessingScripts);
dbMaintainer.updateDatabase(schema, true);
assertNoMoreInvocations();
}
/**
* Tests incremental update of a database: No existing scripts are modified, but new ones are added. The database
* is not cleared but the new scripts are executed on by one, incrementing the database version each time.
*/
@Test
@SuppressWarnings("unchecked")
public void testUpdateDatabase_Incremental() throws Exception {
expectNewScriptsAdded();
expectPostProcessingScripts(postProcessingScripts);
dbMaintainer.updateDatabase(schema, true);
assertScriptsExecutedAndDbVersionSet();
}
/**
* Tests updating the database from scratch: Existing scripts have been modified. The database is cleared first
* and all scripts are executed.
*/
@Test
@SuppressWarnings("unchecked")
public void testUpdateDatabase_FromScratch() throws Exception {
expectExistingScriptModified();
expectPostProcessingScripts(postProcessingScripts);
dbMaintainer.updateDatabase(schema, true);
mockDbClearer.assertInvoked().clearSchemas();
mockExecutedScriptInfoSource.assertInvoked().clearAllExecutedScripts();
assertScriptsExecutedAndDbVersionSet();
}
@Test
public void testUpdateDatabase_LastUpdateFailed() {
expectLastUpdateFailed();
expectPostProcessingScripts(postProcessingScripts);
dbMaintainer.updateDatabase(schema, true);
mockDbClearer.assertInvoked().clearSchemas();
mockExecutedScriptInfoSource.assertInvoked().clearAllExecutedScripts();
assertScriptsExecutedAndDbVersionSet();
}
/**
* Tests the behavior in case there is an error in a script supplied by the ScriptSource. In this case, the
* database version must not org incremented and a StatementHandlerException must be thrown.
*/
@Test
public void testUpdateDatabase_ErrorInScript() throws Exception {
// Set database version and available script expectations
expectNewScriptsAdded();
expectNoPostProcessingCodeScripts();
mockScriptRunner.raises(UnitilsException.class).execute(scripts.get(0).getScriptContentHandle());
try {
dbMaintainer.updateDatabase(schema, true);
fail("A UnitilsException should have been thrown");
} catch (UnitilsException e) {
// expected
}
mockExecutedScriptInfoSource.assertInvoked().registerExecutedScript(new ExecutedScript(scripts.get(0), null, false));
}
@Test
public void testUpdateDatabase_ErrorInPostProcessingCodeScripts() {
// Set database version and available script expectations
expectNewScriptsAdded();
expectPostProcessingScripts(postProcessingScripts);
mockScriptRunner.raises(UnitilsException.class).execute(ArgumentMatchers.same(postProcessingScripts.get(1).getScriptContentHandle()));
try {
dbMaintainer.updateDatabase(schema, true);
fail("A UnitilsException should have been thrown");
} catch (UnitilsException e) {
// Expected
}
assertScriptsExecutedAndDbVersionSet();
}
@Test
public void testUpdateDatabase_isInitialFromScratchUpdate() {
expectFromScratchUpdateRecommended();
expectPostProcessingScripts(postProcessingScripts);
dbMaintainer.updateDatabase(schema, true);
mockDbClearer.assertInvoked().clearSchemas();
mockExecutedScriptInfoSource.assertInvoked().clearAllExecutedScripts();
assertScriptsExecutedAndDbVersionSet();
}
private void expectFromScratchUpdateRecommended() {
mockExecutedScriptInfoSource.returns(true).isFromScratchUpdateRecommended();
expectModifiedScripts(false);
expectAllScripts(scripts);
}
@SuppressWarnings({"unchecked"})
private void expectNoScriptModifications() {
expectModifiedScripts(false);
expectNewScripts(Collections.EMPTY_LIST);
}
private void expectNewScriptsAdded() {
expectModifiedScripts(false);
expectNewScripts(scripts);
}
private void expectLastUpdateFailed() {
expectErrorInExistingIndexedScript();
expectModifiedScripts(false);
expectAllScripts(scripts);
}
@SuppressWarnings({"unchecked"})
private void expectNoPostProcessingCodeScripts() {
expectPostProcessingScripts(Collections.EMPTY_LIST);
}
private void expectExistingScriptModified() {
expectModifiedScripts(true);
expectAllScripts(scripts);
}
private void assertScriptsExecutedAndDbVersionSet() {
mockExecutedScriptInfoSource.assertInvokedInSequence().registerExecutedScript(new ExecutedScript(scripts.get(0), null, null));
mockScriptRunner.assertInvokedInSequence().execute(scripts.get(0).getScriptContentHandle());
mockExecutedScriptInfoSource.assertInvokedInSequence().updateExecutedScript(new ExecutedScript(scripts.get(0), null, null));
mockExecutedScriptInfoSource.assertInvokedInSequence().registerExecutedScript(new ExecutedScript(scripts.get(1), null, null));
mockScriptRunner.assertInvokedInSequence().execute(scripts.get(1).getScriptContentHandle());
mockExecutedScriptInfoSource.assertInvokedInSequence().updateExecutedScript(new ExecutedScript(scripts.get(1), null, null));
mockScriptRunner.assertInvokedInSequence().execute(postProcessingScripts.get(0).getScriptContentHandle());
mockScriptRunner.assertInvokedInSequence().execute(postProcessingScripts.get(1).getScriptContentHandle());
}
private void expectNewScripts(List<Script> scripts) {
mockScriptSource.returns(scripts).getNewScripts(null, null, dialect, schema, true);
}
private void expectErrorInExistingIndexedScript() {
alreadyExecutedScripts.get(0).setSuccessful(false);
}
private void expectModifiedScripts(boolean modifiedScripts) {
mockScriptSource.returns(modifiedScripts).isExistingIndexedScriptModified(null, null, dialect, schema, true);
}
private void expectPostProcessingScripts(List<Script> postProcessingCodeScripts) {
mockScriptSource.returns(postProcessingCodeScripts).getPostProcessingScripts(dialect, schema, true);
}
private void expectAllScripts(List<Script> scripts) {
mockScriptSource.returns(scripts).getAllUpdateScripts(dialect, schema, true);
}
}