Java tutorial
/* * Copyright 2015 Ryan Svihla * * 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 pro.foundev.cassandra.commons.migrations; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; import com.datastax.driver.core.exceptions.InvalidQueryException; import com.datastax.driver.core.exceptions.SyntaxError; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.Mockito; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import pro.foundev.cassandra.commons.core.CassandraConfiguration; import pro.foundev.cassandra.commons.migrations.testSupport.CqlFile; import pro.foundev.cassandra.commons.test.spring.CassandraTableCleanupListener; import pro.foundev.cassandra.commons.test.spring.CassandraTestContext; import pro.foundev.cassandra.commons.test.CassandraTestDB; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; import java.util.Arrays; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = CassandraTestContext.class) @TestExecutionListeners(CassandraTableCleanupListener.class) public class MigrationRunnerTest { private Path scriptDirectory; @Autowired private CassandraTestDB testDB; private static final String keyspaceName = "my_keyspace"; private MigrationRunnerImpl migrationRunner; private CqlFile cqlFile; private static CassandraConfiguration getConfiguration() { CassandraConfiguration configuration = new CassandraConfiguration(); configuration.setHosts(Arrays.asList("127.0.0.1")); configuration.setKeyspace(keyspaceName); configuration.setPort(9042); return configuration; } private Logger log; @Before public void setUp() throws IOException { migrationRunner = new MigrationRunnerImpl(getConfiguration()); log = Mockito.spy(Logger.class); migrationRunner.setLog(log); cqlFile = new CqlFile(); scriptDirectory = cqlFile.getScriptDirectory(); testDB.getSession().execute("DROP KEYSPACE IF EXISTS " + keyspaceName); testDB.getSession().execute("CREATE KEYSPACE " + keyspaceName + " with replication " + "= {'class': 'SimpleStrategy', 'replication_factor':1 }"); } @After public void tearDown() throws IOException { testDB.getSession().execute("DROP KEYSPACE IF EXISTS " + keyspaceName); cqlFile.cleanup(); } private Row findTable(String tableName) { ResultSet results = testDB.getSession() .execute("SELECT * FROM system.schema_columnfamilies WHERE keyspace_name='my_keyspace' AND " + "columnfamily_name='" + tableName + "'"); Row row = results.one(); return row; } private void assertTableExists(String tableName) { assertNotNull(findTable(tableName)); } private void assertTableDoesNotExist(String tableName) { assertNull(findTable(tableName)); } @Rule public ExpectedException exception = ExpectedException.none(); @Test public void whenScriptDirectoryIsNotThereItShouldHaveCleanException() throws FileNotFoundException { exception.expect(FileNotFoundException.class); migrationRunner.runScriptDirectory(null); } @Test public void whenYamlFileIsNotThereItShouldHaveCleanException() throws IOException { exception.expect(FileNotFoundException.class); String yamlFile = null; new MigrationRunnerImpl(yamlFile).runScriptDirectory(scriptDirectory); } @Test public void whenInvalidCQLShouldRaiseException() throws FileNotFoundException { cqlFile.createCQLFile("1_create_table.cql", "CREATE TABLE my_table (id DOESNOTEXIST, value text, PRIMARY KEY(id))"); exception.expect(Exception.class); migrationRunner.runScriptDirectory(scriptDirectory); assertTableDoesNotExist("my_table"); assertThat(testDB.count("migrations"), is(0L)); } @Test public void whenParsingYamlFileItShouldStillBeAbleToRunMigrations() throws IOException { cqlFile.createCQLFile("1_create_table_1.cql", "CREATE TABLE my_table (id int, value text," + "PRIMARY KEY(id))"); Path yamlFile = cqlFile.createYamlFile(keyspaceName); migrationRunner = new MigrationRunnerImpl(yamlFile.toString()); migrationRunner.runScriptDirectory(scriptDirectory); assertTableExists("my_table"); } @Test public void whenNoExistingMigrationsAndOneScriptItShouldRunCQLInScript() throws FileNotFoundException { cqlFile.createCQLFile("1_create_table_1.cql", "CREATE TABLE my_table (id int, value text," + "PRIMARY KEY(id))"); migrationRunner.runScriptDirectory(scriptDirectory); assertTableExists("my_table"); } @Test public void whenNoExistingMigrationsAndTwoScriptsItShouldRunCQLInScriptsInOrder() throws FileNotFoundException { cqlFile.createCQLFile("1_create_table_1.cql", "CREATE TABLE my_table (id int, value text," + "PRIMARY KEY(id))"); cqlFile.createCQLFile("2_create_table_2.cql", "CREATE TABLE my_table_2 (id int, value text," + "PRIMARY KEY(id))"); migrationRunner.runScriptDirectory(scriptDirectory); assertTableExists("my_table"); assertTableExists("my_table_2"); } @Test public void whenExistingMigrationAndOneScriptItShouldRunCQLInScriptsInOrderAndNotConflict() throws FileNotFoundException { cqlFile.createCQLFile("1_create_table.cql", "CREATE TABLE my_table (id int, value text," + "PRIMARY KEY(id))"); migrationRunner.runScriptDirectory(scriptDirectory); cqlFile.createCQLFile("2_drop_table.cql", "DROP TABLE my_table"); migrationRunner.runScriptDirectory(scriptDirectory); assertTableDoesNotExist("my_table"); } @Test public void whenMigrationAlreadyRunCanSafelyRunAgainWithoutReruningPrevious() throws FileNotFoundException { cqlFile.createCQLFile("201510231455_create_table.cql", "CREATE TABLE my_table (id int, value text," + "PRIMARY KEY(id))"); cqlFile.createCQLFile("201510231456_drop_table.cql", "DROP TABLE my_table"); migrationRunner.runScriptDirectory(scriptDirectory); migrationRunner.runScriptDirectory(scriptDirectory); assertTableDoesNotExist("my_table"); } @Test public void whenVersionIsDateTimeStampItCanParseVersion() throws FileNotFoundException { cqlFile.createCQLFile("201510220938_create_table.cql", "CREATE TABLE my_table (id int, value text," + "PRIMARY KEY(id))"); migrationRunner.runScriptDirectory(scriptDirectory); Row row = testDB.getSession().execute("SELECT * FROM " + keyspaceName + ".migrations").one(); Long version = row.getLong("version"); assertEquals(version, new Long(201510220938L)); } @Test public void whenKeyspaceDoesNotAlreadyExistItCreatesWithSimpleStrategyAndRF1() { CassandraConfiguration configuration = getConfiguration(); configuration.setKeyspace("no_keyspace"); MigrationRunner migrationRunner = new MigrationRunnerImpl(configuration); try { assertNull(testDB.getSession() .execute("SELECT * FROM system.schema_keyspaces " + "where keyspace_name='no_keyspace'").one()); migrationRunner.runScriptDirectory(scriptDirectory); assertNotNull(testDB.getSession() .execute("SELECT * FROM system.schema_keyspaces " + "where keyspace_name='no_keyspace'").one()); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { testDB.getSession().execute("DROP KEYSPACE IF EXISTS no_keyspace"); } } @Test public void whenNonCQLFileInDirectoryItWillIgnore() throws FileNotFoundException { cqlFile.createCQLFile("201510220938_create_table.cql", "CREATE TABLE my_table (id int, value text," + "PRIMARY KEY(id))"); cqlFile.createCQLFile(".gitkeep", "DROP TABLE my_table"); migrationRunner.runScriptDirectory(scriptDirectory); Row row = testDB.getSession().execute("SELECT * FROM " + keyspaceName + ".migrations").one(); Long version = row.getLong("version"); assertEquals(version, new Long(201510220938L)); assertTrue(testDB.tableExists(keyspaceName, "my_table")); } @Test public void itWillLogEachMigration() throws FileNotFoundException { cqlFile.createCQLFile("201510220938_create_table.cql", "CREATE TABLE my_table (id int, value text, ts timestamp, " + "PRIMARY KEY(id))"); migrationRunner.runScriptDirectory(scriptDirectory); InOrder inOrder = inOrder(log); inOrder.verify(log, times(1)).info("----------------------------------------------------------------"); inOrder.verify(log, times(1)).info("-- running migration: create_table with version: 201510220938 --"); inOrder.verify(log, times(1)).info("----------------------------------------------------------------"); inOrder.verify(log, times(1)) .info("-------------------------------------------------------------------------------"); inOrder.verify(log, times(1)) .info("-- CREATE TABLE my_table (id int, value text, ts timestamp, PRIMARY KEY(id)) --"); inOrder.verify(log, times(1)) .info("-------------------------------------------------------------------------------"); inOrder.verify(log, times(1)).info("-----------------------------------------------------------------"); inOrder.verify(log, times(1)).info("-- migration complete: create_table with version: 201510220938 --"); inOrder.verify(log, times(1)).info("-----------------------------------------------------------------"); } @Test public void whenSeveralLineMigrationFileItWillLogEachStatementSeparately() throws FileNotFoundException { cqlFile.createCQLFile("201510220938_create_table.cql", "CREATE TABLE my_table (id int, value text, ts timestamp, " + "PRIMARY KEY(id));\n" + "CREATE TABLE my_table_2 (id int, value text, ts timestamp, " + "PRIMARY KEY(id));"); migrationRunner.runScriptDirectory(scriptDirectory); InOrder inOrder = inOrder(log); inOrder.verify(log, times(1)).info("----------------------------------------------------------------"); inOrder.verify(log, times(1)).info("-- running migration: create_table with version: 201510220938 --"); inOrder.verify(log, times(1)).info("----------------------------------------------------------------"); inOrder.verify(log, times(1)) .info("--------------------------------------------------------------------------------"); inOrder.verify(log, times(1)) .info("-- CREATE TABLE my_table (id int, value text, ts timestamp, PRIMARY KEY(id)); --"); inOrder.verify(log, times(1)) .info("--------------------------------------------------------------------------------"); inOrder.verify(log, times(1)) .info("----------------------------------------------------------------------------------"); inOrder.verify(log, times(1)) .info("-- CREATE TABLE my_table_2 (id int, value text, ts timestamp, PRIMARY KEY(id)); --"); inOrder.verify(log, times(1)) .info("----------------------------------------------------------------------------------"); inOrder.verify(log, times(1)).info("-----------------------------------------------------------------"); inOrder.verify(log, times(1)).info("-- migration complete: create_table with version: 201510220938 --"); inOrder.verify(log, times(1)).info("-----------------------------------------------------------------"); } @Test public void whenCriticalErrorHappensOn2ndStatementItWillLogMigrationError() { cqlFile.createCQLFile("201510220938_create_table.cql", "CREATE TABLE my_table (id int, value text, ts timestamp, " + "PRIMARY KEY(id));\n" + "CREATE TABLE my_table (id int, value text, ts timestamp, " + "PRIMARY KEY(id));"); try { migrationRunner.runScriptDirectory(scriptDirectory); } catch (Exception ex) { InOrder inOrder = inOrder(log); inOrder.verify(log, times(1)).info("----------------------------------------------------------------"); inOrder.verify(log, times(1)).info("-- running migration: create_table with version: 201510220938 --"); inOrder.verify(log, times(1)).info("----------------------------------------------------------------"); inOrder.verify(log, times(1)) .info("--------------------------------------------------------------------------------"); inOrder.verify(log, times(1)) .info("-- CREATE TABLE my_table (id int, value text, ts timestamp, PRIMARY KEY(id)); --"); inOrder.verify(log, times(1)) .info("--------------------------------------------------------------------------------"); inOrder.verify(log, times(1)) .info("--------------------------------------------------------------------------------"); inOrder.verify(log, times(1)).error(eq("-- critical error: migration step #2 failed miserably. " + "We do not know how to recover. Please review the exception and figure out the error in your migration " + "script, then do the following:\n" + "1. Make a new script and a new migration\n" + "2. Fix your script and then add it to the new migration\n" + "3. Save the migration that was successful with the following CQL: INSERT INTO migrations (migrations_set, version, name, cql) values\" +\n" + " \" (0, 201510220938, 'create_table', 'CREATE TABLE my_table (id int, value text, cql text, PRIMARY KEY(id));');" + "Keep an eye out for https://github.com/rssvihla/cassandra-commons/issues/45 which will bring resumable migrations" + " The CQL that failed was 'CREATE TABLE my_table (id int, value text, ts timestamp, " + "PRIMARY KEY(id))"), any(Exception.class)); inOrder.verify(log, times(1)).info("-----------------------------------------------------------------"); inOrder.verify(log, times(1)).info("-- failed migration: create_table with version: 201510220938 --"); inOrder.verify(log, times(1)).info("-----------------------------------------------------------------"); } } @Test public void whenCriticalErrorHappensOn2ndStatementItWillNotSaveMigration() { cqlFile.createCQLFile("201510220938_create_table.cql", "CREATE TABLE my_table (id int, value text, ts timestamp, " + "PRIMARY KEY(id));\n" + "CREATE TABLE BAD_CQL (id int value text ts timestamp, " + "PRIMARY KEY(id));"); try { migrationRunner.runScriptDirectory(scriptDirectory); } catch (Exception ex) { Row migration = testDB.getSession() .execute( "SELECT * FROM migrations where migrations_set = 0 " + "AND version = " + 201510220938L) .one(); assertNull(migration); assertThat(testDB.count("migrations"), is(0L)); } } @Test public void whenManyMigrationsItWillLogEachMigration() throws FileNotFoundException { cqlFile.createCQLFile("201510220938_create_table.cql", "CREATE TABLE my_table (id int, value text," + "PRIMARY KEY(id))"); cqlFile.createCQLFile("201510220945_create_table_2.cql", "CREATE TABLE my_table_2 (id int, value text," + "PRIMARY KEY(id))"); migrationRunner.runScriptDirectory(scriptDirectory); InOrder inOrder = inOrder(log); inOrder.verify(log, times(1)).info("-- running migration: create_table with version: 201510220938 --"); inOrder.verify(log, times(1)).info("-- migration complete: create_table with version: 201510220938 --"); inOrder.verify(log, times(1)).info("-- running migration: create_table_2 with version: 201510220945 --"); inOrder.verify(log, times(1)).info("-- migration complete: create_table_2 with version: 201510220945 --"); } @Test public void itWillLogStartup() throws FileNotFoundException { InOrder inOrder = inOrder(log); migrationRunner.runScriptDirectory(scriptDirectory); inOrder.verify(log).info("-------------------------"); inOrder.verify(log).info("-- migrations starting --"); inOrder.verify(log).info("-------------------------"); } @Test public void whenItFailsItWillLogFailure() throws FileNotFoundException { cqlFile.createCQLFile("201510220945_create_table_2.cql", "BAD CQL"); try { migrationRunner.runScriptDirectory(scriptDirectory); } catch (SyntaxError ex) { verify(log).error(eq("-- failed migration: create_table_2 with version: 201510220945 --"), any(Exception.class)); } } @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void whenItFailsItWillBubbleUpException() throws FileNotFoundException { cqlFile.createCQLFile("201510220945_create_table_2.cql", "BAD CQL"); thrown.expect(SyntaxError.class); migrationRunner.runScriptDirectory(scriptDirectory); } @Test public void whenItFailsItDoesNotLogSuccess() throws FileNotFoundException { cqlFile.createCQLFile("201510220945_create_table_2.cql", "BAD CQL"); try { migrationRunner.runScriptDirectory(scriptDirectory); } catch (SyntaxError ex) { verify(log, times(0)).info("-- migrations successfully completed --"); } } @Test public void whenItFailsItWillNotRunOtherMigrations() throws FileNotFoundException { cqlFile.createCQLFile("201510220945_create_table_2.cql", "BAD CQL"); cqlFile.createCQLFile("201510220946_create_table_2.cql", "CREATE TABLE my_table_2 (id int, value text," + "PRIMARY KEY(id))"); try { migrationRunner.runScriptDirectory(scriptDirectory); } catch (SyntaxError ex) { assertFalse(testDB.tableExists(keyspaceName, "my_table_2")); } } @Test public void itWillLogCompletionStatus() throws FileNotFoundException { cqlFile.createCQLFile("201510220938_create_table.cql", "CREATE TABLE my_table (id int, value text," + "PRIMARY KEY(id))"); cqlFile.createCQLFile("201510220945_create_table_2.cql", "CREATE TABLE my_table_2 (id int, value text," + "PRIMARY KEY(id))"); migrationRunner.runScriptDirectory(scriptDirectory); verify(log).info("-- migrations successfully completed --"); } @Test public void whenErrorOnMigrationSaveItWillLogCorrectionSuggestion() throws FileNotFoundException { cqlFile.createCQLFile("201510220938_create_table.cql", "CREATE TABLE my_table (id int, value text," + "PRIMARY KEY(id));"); testDB.getSession().execute("CREATE TABLE " + keyspaceName + ".migrations (migration_set int, version int, PRIMARY KEY(migration_set, version))"); try { migrationRunner.runScriptDirectory(scriptDirectory); } catch (InvalidQueryException ex) { verify(log).error(eq( "applied migration: create_table with version: 201510220938 but it failed writing the migration " + "record to the migration table. Please review the error fix and manually insert the following CQL in the " + "keyspace you are running migrations against " + "to resolve the problem:\nINSERT INTO migrations (migrations_set, version, name, cql) values" + " (0, 201510220938, 'create_table', 'CREATE TABLE my_table (id int, value text, cql text, PRIMARY KEY(id));'"), any(Exception.class)); } } @Test public void whenResettingWillCompleteDropKeyspace() throws FileNotFoundException { cqlFile.createCQLFile("201510220938_create_table.cql", "CREATE TABLE my_table (id int, value text," + "PRIMARY KEY(id));"); migrationRunner.runScriptDirectory(scriptDirectory); migrationRunner.resetKeyspace(); assertThat(testDB.tableExists(keyspaceName, "migrations"), is(false)); } }