package liquibase;
import liquibase.changelog.ChangeLogIterator;
import liquibase.changelog.ChangeLogParameters;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.changelog.filter.ContextChangeSetFilter;
import liquibase.changelog.filter.DbmsChangeSetFilter;
import liquibase.changelog.filter.LabelChangeSetFilter;
import liquibase.changelog.filter.ShouldRunChangeSetFilter;
import liquibase.database.Database;
import liquibase.database.DatabaseConnection;
import liquibase.database.DatabaseFactory;
import liquibase.database.ObjectQuotingStrategy;
import liquibase.sdk.database.MockDatabase;
import liquibase.exception.ChangeLogParseException;
import liquibase.exception.LiquibaseException;
import liquibase.exception.LockException;
import liquibase.lockservice.LockService;
import liquibase.lockservice.LockServiceFactory;
import liquibase.logging.LogFactory;
import liquibase.logging.Logger;
import liquibase.parser.ChangeLogParser;
import liquibase.parser.ChangeLogParserFactory;
import liquibase.resource.ResourceAccessor;
import liquibase.test.Assert;
import liquibase.sdk.resource.MockResourceAccessor;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.HashMap;
import java.util.Map;
import static liquibase.test.Assert.assertListsEqual;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
public class LiquibaseTest {
private MockResourceAccessor mockResourceAccessor;
private Database mockDatabase;
private LockServiceFactory mockLockServiceFactory;
private LockService mockLockService;
private ChangeLogParserFactory mockChangeLogParserFactory;
private ChangeLogParser mockChangeLogParser;
private DatabaseChangeLog mockChangeLog;
private ChangeLogIterator mockChangeLogIterator;
private Logger mockLogger;
// private TestLiquibase testLiquibase;
// private DatabaseConnection connectionForConstructor;
@Before
public void before() throws Exception {
// if (connectionForConstructor != null) {
// reset(connectionForConstructor);
// }
// connectionForConstructor = createMock(DatabaseConnection.class);
// connectionForConstructor.setAutoCommit(false);
// expectLastCall().atLeastOnce();
//
// DatabaseMetaData metaData = createMock(DatabaseMetaData.class);
// expect(metaData.getDatabaseProductName()).andReturn("Oracle");
// replay(metaData);
//
//// expect(connectionForConstructor.getMetaData()).andReturn(metaData);
// replay(connectionForConstructor);
//
// testLiquibase = new TestLiquibase();
mockResourceAccessor = new MockResourceAccessor();
mockDatabase = mock(Database.class);
mockLockService = mock(LockService.class);
mockLockServiceFactory = mock(LockServiceFactory.class);
mockChangeLogParserFactory = mock(ChangeLogParserFactory.class);
mockChangeLogParser = mock(ChangeLogParser.class);
mockChangeLog = mock(DatabaseChangeLog.class);
mockChangeLogIterator = mock(ChangeLogIterator.class);
mockLogger = mock(Logger.class);
LockServiceFactory.setInstance(mockLockServiceFactory);
when(mockLockServiceFactory.getLockService(any(Database.class))).thenReturn(mockLockService);
ChangeLogParserFactory.setInstance(mockChangeLogParserFactory);
when(mockChangeLogParserFactory.getParser(anyString(), Mockito.isA(ResourceAccessor.class))).thenReturn(mockChangeLogParser);
when(mockChangeLogParser.parse(anyString(), any(ChangeLogParameters.class), Mockito.isA(ResourceAccessor.class))).thenReturn(mockChangeLog);
LogFactory.setInstance(new LogFactory() {
@Override
public Logger getLog(String name) {
return mockLogger;
}
});
}
@After
public void after() {
verifyNoMoreInteractions(mockLockService, mockChangeLogParser, mockChangeLog, mockChangeLogIterator); //for no other interactions of normal use objects. Not automatically checking mockDatabase and the *Factory mocks
Mockito.reset(mockDatabase, mockLockServiceFactory, mockLockService, mockChangeLogParserFactory, mockChangeLogParser, mockChangeLog, mockChangeLogIterator);
LockServiceFactory.reset();
ChangeLogParserFactory.reset();
LogFactory.reset();
}
@Test
public void constructor() throws Exception {
LogFactory.reset(); //going to test log setup
MockResourceAccessor resourceAccessor = this.mockResourceAccessor;
MockDatabase database = new MockDatabase();
Liquibase liquibase = new Liquibase("com/example/test.xml", resourceAccessor, database);
assertNotNull(liquibase.getLog());
assertEquals("com/example/test.xml", liquibase.getChangeLogFile());
assertSame(resourceAccessor, liquibase.getResourceAccessor());
assertNotNull(liquibase.getChangeLogParameters());
assertEquals("Standard database changelog parameters were not set", "DATABASECHANGELOGLOCK", liquibase.getChangeLogParameters().getValue("database.databaseChangeLogLockTableName"));
assertSame(database, liquibase.getDatabase());
}
@Test
public void constructor_changelogPathsStandardize() throws Exception {
Liquibase liquibase = new Liquibase("path\\with\\windows\\separators.xml", mockResourceAccessor, new MockDatabase());
assertEquals("path/with/windows/separators.xml", liquibase.getChangeLogFile());
liquibase = new Liquibase("path/with/unix/separators.xml", mockResourceAccessor, new MockDatabase());
assertEquals("path/with/unix/separators.xml", liquibase.getChangeLogFile());
liquibase = new Liquibase("/absolute/path/remains.xml", mockResourceAccessor, new MockDatabase());
assertEquals("/absolute/path/remains.xml", liquibase.getChangeLogFile());
}
@Test
public void constructor_createDatabaseInstanceFromConnection() throws LiquibaseException {
DatabaseConnection databaseConnection = mock(DatabaseConnection.class);
Database database = mockDatabase;
try {
DatabaseFactory.setInstance(mock(DatabaseFactory.class));
when(DatabaseFactory.getInstance().findCorrectDatabaseImplementation(databaseConnection)).thenReturn(database);
Liquibase liquibase = new Liquibase("com/example/test.xml", mockResourceAccessor, databaseConnection);
assertSame("Liquibase constructor passing connection did not find the correct database implementation", database, liquibase.getDatabase());
} finally {
DatabaseFactory.reset();
}
}
@Test
public void getFileOpener() throws LiquibaseException {
Liquibase liquibase = new Liquibase("com/example/test.xml", mockResourceAccessor, mockDatabase);
assertSame(liquibase.getResourceAccessor(), liquibase.getFileOpener());
}
@Test
public void setCurrentDateTimeFunction() throws LiquibaseException {
Database database = mockDatabase;
String testFunction = "GetMyTime";
new Liquibase("com/example/test.xml", mockResourceAccessor, database).setCurrentDateTimeFunction(testFunction);
verify(database).setCurrentDateTimeFunction(testFunction);
}
@Test
public void update_passedStringContext() throws LiquibaseException {
LiquibaseDelegate liquibase = new LiquibaseDelegate() {
@Override
public void update(Contexts contexts) throws LiquibaseException {
objectToVerify = contexts;
}
};
liquibase.update("test");
assertEquals("test", liquibase.objectToVerify.toString());
liquibase.reset();
liquibase.update("");
assertEquals("", liquibase.objectToVerify.toString());
liquibase.reset();
liquibase.update((String) null);
assertEquals("", liquibase.objectToVerify.toString());
liquibase.reset();
liquibase.update("test1, test2");
assertEquals("test1,test2", liquibase.objectToVerify.toString());
liquibase.reset();
}
// @Test
// public void update() throws LiquibaseException {
// Contexts contexts = new Contexts("a,b");
//
// Liquibase liquibase = new Liquibase("com/example/test.xml", mockResourceAccessor, mockDatabase) {
// @Override
// protected ChangeLogIterator getStandardChangelogIterator(Contexts contexts, DatabaseChangeLog changeLog) throws DatabaseException {
// return mockChangeLogIterator;
// }
// };
//
// liquibase.update(contexts);
//
// verify(mockLockService).waitForLock();
//// verify(mockDatabase).checkDatabaseChangeLogTable(true, mockChangeLog, contexts);
//// verify(mockDatabase).checkDatabaseChangeLogLockTable();
// verify(mockChangeLog).validate(mockDatabase, contexts);
// verify(mockChangeLogParser).parse("com/example/test.xml", liquibase.getChangeLogParameters(), mockResourceAccessor);
// verify(mockChangeLogIterator).run(any(UpdateVisitor.class), eq(mockDatabase));
// verify(mockLockService).releaseLock();
// verify(mockDatabase).setObjectQuotingStrategy(ObjectQuotingStrategy.LEGACY); //quoting strategy needs to be set back in case it changed during the update
//
//
// assertEquals("Passed contexts were not set on the changelog parameters object", "a,b", liquibase.getChangeLogParameters().getContexts().toString());
// }
// @Test
// public void update_nullContexts() throws LiquibaseException {
// Liquibase liquibase = new Liquibase("com/example/test.xml", mockResourceAccessor, mockDatabase) {
// @Override
// protected ChangeLogIterator getStandardChangelogIterator(Contexts contexts, DatabaseChangeLog changeLog) throws DatabaseException {
// return mockChangeLogIterator;
// }
// };
//
// liquibase.update((Contexts) null);
//
// verify(mockLockService).waitForLock();
//// verify(mockDatabase).checkDatabaseChangeLogTable(true, mockChangeLog, (Contexts) null);
//// verify(mockDatabase).checkDatabaseChangeLogLockTable();
// verify(mockChangeLog).validate(mockDatabase, (Contexts) null);
// verify(mockChangeLogParser).parse("com/example/test.xml", liquibase.getChangeLogParameters(), mockResourceAccessor);
// verify(mockChangeLogIterator).run(any(UpdateVisitor.class), eq(mockDatabase));
// verify(mockLockService).releaseLock();
// verify(mockDatabase).setObjectQuotingStrategy(ObjectQuotingStrategy.LEGACY); //quoting strategy needs to be set back in case it changed during the update
//
//
// assertNull(liquibase.getChangeLogParameters().getContexts());
// }
@Test(expected = LockException.class)
public void update_exceptionGettingLock() throws LiquibaseException {
doThrow(LockException.class).when(mockLockService).waitForLock();
Liquibase liquibase = new Liquibase("com/example/test.xml", mockResourceAccessor, mockDatabase);
try {
liquibase.update((Contexts) null);
} finally {
verify(mockLockService).waitForLock();
//should not call anything else, even releaseLock()
}
}
@Test(expected = ChangeLogParseException.class)
public void update_exceptionDoingUpdate() throws LiquibaseException {
Contexts contexts = new Contexts("a,b");
Liquibase liquibase = new Liquibase("com/example/test.xml", mockResourceAccessor, mockDatabase);
doThrow(ChangeLogParseException.class).when(mockChangeLogParser).parse("com/example/test.xml", liquibase.getChangeLogParameters(), mockResourceAccessor);
try {
liquibase.update(contexts);
} finally {
verify(mockLockService).waitForLock();
verify(mockLockService).releaseLock(); //should still call
verify(mockDatabase).setObjectQuotingStrategy(ObjectQuotingStrategy.LEGACY); //should still call
verify(mockChangeLogParser).parse("com/example/test.xml", liquibase.getChangeLogParameters(), mockResourceAccessor);
}
}
// @Test
// public void update_exceptionReleasingLock() throws LiquibaseException {
// doThrow(LockException.class).when(mockLockService).releaseLock();
//
// update(); //works like normal, just logs error
// verify(mockLogger).severe(eq("Could not release lock"), any(Exception.class));
// }
@Test
public void getStandardChangelogIterator() throws LiquibaseException {
ChangeLogIterator iterator = new Liquibase("com/example/changelog.xml", mockResourceAccessor, mockDatabase).getStandardChangelogIterator(new Contexts("a", "b"), new LabelExpression("x", "y"), mockChangeLog);
assertListsEqual(new Class[] {ShouldRunChangeSetFilter.class,
ContextChangeSetFilter.class,
LabelChangeSetFilter.class,
DbmsChangeSetFilter.class},
iterator.getChangeSetFilters(), new Assert.AssertFunction() {
@Override
public void check(String message, Object expected, Object actual) {
assertEquals(message, expected, actual.getClass());
}
});
}
//todo: reintroduce @Test
// public void isSaveToRunMigration() throws Exception {
// TestLiquibase liquibase = testLiquibase;
//
// // curiously setting the database of mock liquibase
// Database database = testLiquibase.getDatabase();
//
// liquibase.setUrl("jdbc:oracle:thin:@localhost:1521:latest");
// assertTrue(liquibase.isSafeToRunUpdate());
//
// liquibase.setUrl("jdbc:oracle:thin:@liquibase:1521:latest");
// assertFalse(liquibase.isSafeToRunUpdate());
//
// ExecutorService.getInstance().setWriteExecutor(database, new LoggingExecutor(new PrintWriter(System.out), database));
// assertTrue("Safe to run if outputing sql, even if non-localhost URL", liquibase.isSafeToRunUpdate());
//
// }
/*
@Test
public void testBlosDocumentation() throws Exception {
testLiquibase.generateDocumentation(".");
}
*/
// @Test
// public void getImplementedDatabases() throws Exception {
// List<Database> databases = DatabaseFactory.getInstance().getImplementedDatabases();
// assertTrue(databases.size() > 15);
//
// boolean foundOracle = false;
// boolean foundPostgres = false;
// boolean foundMSSQL = false;
//
// for (Database db : databases) {
// if (db instanceof OracleDatabase) {
// foundOracle = true;
// } else if (db instanceof PostgresDatabase) {
// foundPostgres = true;
// } else if (db instanceof MSSQLDatabase) {
// foundMSSQL = true;
// }
// }
//
// assertTrue("Oracle not in Implemented Databases", foundOracle);
// assertTrue("MSSQL not in Implemented Databases", foundMSSQL);
// assertTrue("Postgres not in Implemented Databases", foundPostgres);
// }
// private class TestLiquibase extends Liquibase {
// private String url;
// // instead use super.database
// //private Database database;
// private InputStream inputStream;
//
// public TestLiquibase() throws LiquibaseException {
// super("liquibase/test.xml", new ClassLoaderResourceAccessor(), ((Database) null));
// inputStream = createMock(InputStream.class);
// replay(inputStream);
// }
//
// @Override
// public Database getDatabase() {
// if (database == null) {
// database = new OracleDatabase() {
//
// };
// }
// return database;
// }
//
// public void setDatabase(Database database) {
// this.database = database;
// }
//
//
// @SuppressWarnings("unused")
// public Database[] getImplementedDatabases() {
// Database mockDatabase = createMock(Database.class);
// try {
//
// expect(mockDatabase.isCorrectDatabaseImplementation(null)).andReturn(true).atLeastOnce();
// mockDatabase.setConnection((DatabaseConnection) null);
// expectLastCall();
// expect(mockDatabase.getConnection()).andReturn(connectionForConstructor);
// replay(mockDatabase);
//
// return new Database[]{
// mockDatabase,
// };
// } catch (DatabaseException e) {
// throw new RuntimeException(e);
// }
// }
//
// public void setUrl(String url) {
// this.url = url;
// }
//
// @Override
// public ResourceAccessor getFileOpener() {
// return new ResourceAccessor() {
// public InputStream getResourceAsStream(String file) {
// return inputStream;
// }
//
// public Enumeration<URL> getResources(String packageName) {
// return null;
// }
//
// public ClassLoader toClassLoader() {
// return null;
// }
// };
// }
// }
/**
* Convenience helper class for testing Liquibase methods that simply delegate to another.
* To use, create a subclass that overrides the method delegated to with an implementation that stores whatever params are being passed.
* After calling the delegating method in your test, assert against the objectToVerify
*/
private static class LiquibaseDelegate extends Liquibase {
/**
* If using a single parameter, store in here
*/
protected Object objectToVerify;
/**
* If using multiple parameters, store them here
*/
protected Map<String, Object> objectsToVerify = new HashMap<String, Object>();
private LiquibaseDelegate() throws LiquibaseException {
super("com/example/test.xml", new MockResourceAccessor(), mock(Database.class));
}
/**
* Resets the object(s)ToVerify so this delegate can be reused in a test.
*/
public void reset() {
objectToVerify = null;
objectsToVerify.clear();
}
}
}