pro.foundev.cassandra.commons.migrations.MigrationRunnerTest.java Source code

Java tutorial

Introduction

Here is the source code for pro.foundev.cassandra.commons.migrations.MigrationRunnerTest.java

Source

/*
 * 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));
    }

}