package io.dropwizard.migrations;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.io.Files;
import com.google.common.io.Resources;
import net.jcip.annotations.NotThreadSafe;
import net.sourceforge.argparse4j.inf.Namespace;
import org.apache.commons.lang3.StringUtils;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
@NotThreadSafe
public class DbDumpCommandTest extends AbstractMigrationTest {
private static DocumentBuilder xmlParser;
private static List<String> attributeNames;
private final DbDumpCommand<TestMigrationConfiguration> dumpCommand =
new DbDumpCommand<>(new TestMigrationDatabaseConfiguration(), TestMigrationConfiguration.class, "migrations.xml");
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
private TestMigrationConfiguration existedDbConf;
@BeforeClass
public static void initXmlParser() throws Exception {
xmlParser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
attributeNames = ImmutableList.of("columns", "foreign-keys", "indexes", "primary-keys", "sequences",
"tables", "unique-constraints", "views");
}
@Before
public void setUp() throws Exception {
final String existedDbPath = new File(Resources.getResource("test-db.mv.db").toURI()).getAbsolutePath();
final String existedDbUrl = "jdbc:h2:" + StringUtils.removeEnd(existedDbPath, ".mv.db");
existedDbConf = createConfiguration(existedDbUrl);
dumpCommand.setOutputStream(new PrintStream(baos));
}
@Test
public void testDumpSchema() throws Exception {
final Map<String, Object> attributes = new HashMap<>();
for (String name : attributeNames) {
attributes.put(name, true);
}
dumpCommand.run(null, new Namespace(attributes), existedDbConf);
final Element changeSet = getFirstElement(toXmlDocument(baos).getDocumentElement(), "changeSet");
assertCreateTable(changeSet);
}
@Test
public void testDumpSchemaAndData() throws Exception {
final Map<String, Object> attributes = new HashMap<>();
for (String name : Iterables.concat(attributeNames, ImmutableList.of("data"))) {
attributes.put(name, true);
}
dumpCommand.run(null, new Namespace(attributes), existedDbConf);
final NodeList changeSets = toXmlDocument(baos).getDocumentElement().getElementsByTagName("changeSet");
assertCreateTable((Element) changeSets.item(0));
assertInsertData((Element) changeSets.item(1));
}
@Test
public void testDumpOnlyData() throws Exception {
dumpCommand.run(null, new Namespace(ImmutableMap.of("data", (Object) true)), existedDbConf);
final Element changeSet = getFirstElement(toXmlDocument(baos).getDocumentElement(), "changeSet");
assertInsertData(changeSet);
}
@Test
public void testWriteToFile() throws Exception {
final File file = File.createTempFile("migration", ".xml");
final Map<String, Object> attributes = ImmutableMap.of("output", file.getAbsolutePath());
dumpCommand.run(null, new Namespace(attributes), existedDbConf);
// Check that file is exist, and has some XML content (no reason to make a full-blown XML assertion)
assertThat(Files.toString(file, StandardCharsets.UTF_8)).startsWith("<?xml version=\"1.1\" encoding=\"UTF-8\" standalone=\"no\"?>");
}
@Test
public void testHelpPage() throws Exception {
createSubparser(dumpCommand).printHelp(new PrintWriter(new OutputStreamWriter(baos, UTF_8), true));
assertThat(baos.toString(UTF_8)).isEqualTo(String.format(
"usage: db dump [-h] [--migrations MIGRATIONS-FILE] [--catalog CATALOG]%n" +
" [--schema SCHEMA] [-o OUTPUT] [--tables] [--ignore-tables]%n" +
" [--columns] [--ignore-columns] [--views] [--ignore-views]%n" +
" [--primary-keys] [--ignore-primary-keys] [--unique-constraints]%n" +
" [--ignore-unique-constraints] [--indexes] [--ignore-indexes]%n" +
" [--foreign-keys] [--ignore-foreign-keys] [--sequences]%n" +
" [--ignore-sequences] [--data] [--ignore-data] [file]%n" +
"%n" +
"Generate a dump of the existing database state.%n" +
"%n" +
"positional arguments:%n" +
" file application configuration file%n" +
"%n" +
"optional arguments:%n" +
" -h, --help show this help message and exit%n" +
" --migrations MIGRATIONS-FILE%n" +
" the file containing the Liquibase migrations for%n" +
" the application%n" +
" --catalog CATALOG Specify the database catalog (use database%n" +
" default if omitted)%n" +
" --schema SCHEMA Specify the database schema (use database default%n" +
" if omitted)%n" +
" -o OUTPUT, --output OUTPUT%n" +
" Write output to <file> instead of stdout%n" +
"%n" +
"Tables:%n" +
" --tables Check for added or removed tables (default)%n" +
" --ignore-tables Ignore tables%n" +
"%n" +
"Columns:%n" +
" --columns Check for added, removed, or modified columns%n" +
" (default)%n" +
" --ignore-columns Ignore columns%n" +
"%n" +
"Views:%n" +
" --views Check for added, removed, or modified views%n" +
" (default)%n" +
" --ignore-views Ignore views%n" +
"%n" +
"Primary Keys:%n" +
" --primary-keys Check for changed primary keys (default)%n" +
" --ignore-primary-keys Ignore primary keys%n" +
"%n" +
"Unique Constraints:%n" +
" --unique-constraints Check for changed unique constraints (default)%n" +
" --ignore-unique-constraints%n" +
" Ignore unique constraints%n" +
"%n" +
"Indexes:%n" +
" --indexes Check for changed indexes (default)%n" +
" --ignore-indexes Ignore indexes%n" +
"%n" +
"Foreign Keys:%n" +
" --foreign-keys Check for changed foreign keys (default)%n" +
" --ignore-foreign-keys Ignore foreign keys%n" +
"%n" +
"Sequences:%n" +
" --sequences Check for changed sequences (default)%n" +
" --ignore-sequences Ignore sequences%n" +
"%n" +
"Data:%n" +
" --data Check for changed data%n" +
" --ignore-data Ignore data (default)%n"));
}
private static Document toXmlDocument(ByteArrayOutputStream baos) throws SAXException, IOException {
return xmlParser.parse(new ByteArrayInputStream(baos.toByteArray()));
}
/**
* Assert correctness of a change set with creation of a table
*
* @param changeSet actual XML element
*/
private static void assertCreateTable(Element changeSet) {
final Element createTable = getFirstElement(changeSet, "createTable");
assertThat(createTable.getAttribute("catalogName")).isEqualTo("TEST-DB");
assertThat(createTable.getAttribute("schemaName")).isEqualTo("PUBLIC");
assertThat(createTable.getAttribute("tableName")).isEqualTo("PERSONS");
final NodeList columns = createTable.getElementsByTagName("column");
final Element idColumn = (Element) columns.item(0);
assertThat(idColumn.getAttribute("autoIncrement")).isEqualTo("true");
assertThat(idColumn.getAttribute("name")).isEqualTo("ID");
assertThat(idColumn.getAttribute("type")).isEqualTo("INT(10)");
final Element idColumnConstraints = getFirstElement(idColumn, "constraints");
assertThat(idColumnConstraints.getAttribute("primaryKey")).isEqualTo("true");
assertThat(idColumnConstraints.getAttribute("primaryKeyName")).isEqualTo("PK_PERSONS");
final Element nameColumn = (Element) columns.item(1);
assertThat(nameColumn.getAttribute("name")).isEqualTo("NAME");
assertThat(nameColumn.getAttribute("type")).isEqualTo("VARCHAR(256)");
final Element nameColumnConstraints = getFirstElement(nameColumn, "constraints");
assertThat(nameColumnConstraints.getAttribute("nullable")).isEqualTo("false");
final Element emailColumn = (Element) columns.item(2);
assertThat(emailColumn.getAttribute("name")).isEqualTo("EMAIL");
assertThat(emailColumn.getAttribute("type")).isEqualTo("VARCHAR(128)");
}
/**
* Assert a correctness of a change set with insertion data into a table
*
* @param changeSet actual XML element
*/
private static void assertInsertData(Element changeSet) {
final Element insert = getFirstElement(changeSet, "insert");
assertThat(insert.getAttribute("catalogName")).isEqualTo("TEST-DB");
assertThat(insert.getAttribute("schemaName")).isEqualTo("PUBLIC");
assertThat(insert.getAttribute("tableName")).isEqualTo("PERSONS");
final NodeList columns = insert.getElementsByTagName("column");
final Element idColumn = (Element) columns.item(0);
assertThat(idColumn.getAttribute("name")).isEqualTo("ID");
assertThat(idColumn.getAttribute("valueNumeric")).isEqualTo("1");
final Element nameColumn = (Element) columns.item(1);
assertThat(nameColumn.getAttribute("name")).isEqualTo("NAME");
assertThat(nameColumn.getAttribute("value")).isEqualTo("Bill Smith");
final Element emailColumn = (Element) columns.item(2);
assertThat(emailColumn.getAttribute("name")).isEqualTo("EMAIL");
assertThat(emailColumn.getAttribute("value")).isEqualTo("bill@smith.me");
}
private static Element getFirstElement(Element root, String tagName) {
return (Element) root.getElementsByTagName(tagName).item(0);
}
}