mirror of
https://github.com/google/nomulus.git
synced 2025-08-26 19:13:48 +02:00
Add schema deployment tests (#265)
* Add schema deployment tests Updated flyway schema script files so that they reflect what is currently deployed in alpha: ClaimsList and PremiumList related elements. Put post-schema-push pg_dump output in nomulus.golden.sql as the authoritative schema. Also added test to verify that the schema pushed by flyway will result in exactly the golden schema. Upgraded testcontainers to 1.12.1. Added a custom Truth subject for better diffing of multi-line text blocks. Removed claims_list.sql and premium_list.sql, as we do not have use for them. * Add schema deployment tests Updated flyway schema script files so that they reflect what is currently deployed in alpha: ClaimsList and PremiumList related elements. Put post-schema-push pg_dump output in nomulus.golden.sql as the authoritative schema. Also added test to verify that the schema pushed by flyway will result in exactly the golden schema. Upgraded testcontainers to 1.12.1. Added a custom Truth subject for better diffing of multi-line text blocks. Removed claims_list.sql and premium_list.sql, as we do not have use for them. * Add schema deployment tests Updated flyway schema script files so that they reflect what is currently deployed in alpha: ClaimsList and PremiumList related elements. Put post-schema-push pg_dump output in nomulus.golden.sql as the authoritative schema. Also added test to verify that the schema pushed by flyway will result in exactly the golden schema. Upgraded testcontainers to 1.12.1. Added a custom Truth subject for better diffing of multi-line text blocks. Removed claims_list.sql and premium_list.sql, as we do not have use for them.
This commit is contained in:
parent
49777a6caa
commit
005e059d33
14 changed files with 651 additions and 61 deletions
108
db/src/test/java/google/registry/sql/flyway/SchemaTest.java
Normal file
108
db/src/test/java/google/registry/sql/flyway/SchemaTest.java
Normal file
|
@ -0,0 +1,108 @@
|
|||
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 google.registry.sql.flyway;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.TextDiffSubject.assertThat;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.io.Resources;
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import org.flywaydb.core.Flyway;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.testcontainers.containers.BindMode;
|
||||
import org.testcontainers.containers.Container;
|
||||
import org.testcontainers.containers.PostgreSQLContainer;
|
||||
|
||||
/** Unit tests about Cloud SQL schema. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class SchemaTest {
|
||||
|
||||
// Resource path that is mapped to the testcontainer instance.
|
||||
private static final String MOUNTED_RESOURCE_PATH = "testcontainer/mount";
|
||||
// The mount point in the container.
|
||||
private static final String CONTAINER_MOUNT_POINT = "/tmp/pg_dump_out";
|
||||
// pg_dump output file name.
|
||||
private static final String DUMP_OUTPUT_FILE = "dump.txt";
|
||||
|
||||
/**
|
||||
* The target database for schema deployment.
|
||||
*
|
||||
* <p>A resource path is mapped to this container in READ_WRITE mode to retrieve the deployed
|
||||
* schema generated by the 'pg_dump' command. We do not communicate over stdout because
|
||||
* testcontainer adds spurious newlines. See <a
|
||||
* href=https://github.com/testcontainers/testcontainers-java/issues/1854>this link</a> for more
|
||||
* information.
|
||||
*/
|
||||
@Rule
|
||||
public PostgreSQLContainer sqlContainer =
|
||||
new PostgreSQLContainer<>("postgres:9.6.12")
|
||||
.withClasspathResourceMapping(
|
||||
MOUNTED_RESOURCE_PATH, CONTAINER_MOUNT_POINT, BindMode.READ_WRITE);
|
||||
|
||||
@Test
|
||||
public void deploySchema_success() throws Exception {
|
||||
Flyway flyway =
|
||||
Flyway.configure()
|
||||
.locations("sql/flyway")
|
||||
.dataSource(
|
||||
sqlContainer.getJdbcUrl(), sqlContainer.getUsername(), sqlContainer.getPassword())
|
||||
.load();
|
||||
|
||||
// flyway.migrate() returns the number of newly pushed scripts. This is a variable
|
||||
// number as our schema evolves.
|
||||
assertThat(flyway.migrate()).isGreaterThan(0);
|
||||
flyway.validate();
|
||||
|
||||
Container.ExecResult execResult =
|
||||
sqlContainer.execInContainer(
|
||||
StandardCharsets.UTF_8,
|
||||
getSchemaDumpCommand(sqlContainer.getUsername(), sqlContainer.getDatabaseName()));
|
||||
if (execResult.getExitCode() != 0) {
|
||||
throw new RuntimeException(execResult.toString());
|
||||
}
|
||||
|
||||
URL dumpedSchema =
|
||||
Resources.getResource(
|
||||
Joiner.on(File.separatorChar).join(MOUNTED_RESOURCE_PATH, DUMP_OUTPUT_FILE));
|
||||
|
||||
assertThat(dumpedSchema)
|
||||
.hasSameContentAs(Resources.getResource("sql/schema/nomulus.golden.sql"));
|
||||
}
|
||||
|
||||
private static String[] getSchemaDumpCommand(String username, String dbName) {
|
||||
return new String[] {
|
||||
"pg_dump",
|
||||
"-h",
|
||||
"localhost",
|
||||
"-U",
|
||||
username,
|
||||
"-f",
|
||||
Paths.get(CONTAINER_MOUNT_POINT, DUMP_OUTPUT_FILE).toString(),
|
||||
"--schema-only",
|
||||
"--no-owner",
|
||||
"--no-privileges",
|
||||
"--exclude-table",
|
||||
"flyway_schema_history",
|
||||
dbName
|
||||
};
|
||||
}
|
||||
}
|
201
db/src/test/java/google/registry/testing/TextDiffSubject.java
Normal file
201
db/src/test/java/google/registry/testing/TextDiffSubject.java
Normal file
|
@ -0,0 +1,201 @@
|
|||
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 google.registry.testing;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.truth.Truth.assertAbout;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.github.difflib.DiffUtils;
|
||||
import com.github.difflib.UnifiedDiffUtils;
|
||||
import com.github.difflib.algorithm.DiffException;
|
||||
import com.github.difflib.patch.Patch;
|
||||
import com.github.difflib.text.DiffRow;
|
||||
import com.github.difflib.text.DiffRowGenerator;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.common.truth.Fact;
|
||||
import com.google.common.truth.FailureMetadata;
|
||||
import com.google.common.truth.Subject;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Compares two multi-line text blocks, and displays their diffs in readable formats.
|
||||
*
|
||||
* <p>User may choose one of the following diff formats:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link DiffFormat#UNIFIED_DIFF} displays the differences in the unified-diff format
|
||||
* <li>{@link DiffFormat#SIDE_BY_SIDE_MARKDOWN} displays the two text blocks side by side, with
|
||||
* markdown annotations to highlight the differences.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Note that if one text block has one trailing newline at the end while another has none, this
|
||||
* difference will not be shown in the generated diffs. This is the case where two texts may be
|
||||
* reported as unequal but the diffs appear equal. Fixing this requires special treatment of the
|
||||
* last line of text. The fix would not be useful in our environment, where all important files are
|
||||
* covered by a style checker that ensures the presence of a trailing newline.
|
||||
*/
|
||||
// TODO(weiminyu): move this class and test to a standalone 'testing' project. Note that the util
|
||||
// project is not good since it depends back to core.
|
||||
@SuppressWarnings("unchecked") // On behalf of Raw type Subject; remove after Truth 1.0 upgrade.
|
||||
public class TextDiffSubject extends Subject {
|
||||
|
||||
private final ImmutableList<String> actual;
|
||||
private DiffFormat diffFormat = DiffFormat.SIDE_BY_SIDE_MARKDOWN;
|
||||
|
||||
protected TextDiffSubject(FailureMetadata metadata, List<String> actual) {
|
||||
super(metadata, actual);
|
||||
this.actual = ImmutableList.copyOf(actual);
|
||||
}
|
||||
|
||||
public TextDiffSubject withDiffFormat(DiffFormat format) {
|
||||
this.diffFormat = format;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void hasSameContentAs(List<String> expectedContent) {
|
||||
checkNotNull(expectedContent, "expectedContent");
|
||||
ImmutableList<String> expected = ImmutableList.copyOf(expectedContent);
|
||||
if (expected.equals(actual)) {
|
||||
return;
|
||||
}
|
||||
String diffString = diffFormat.generateDiff(expected, actual);
|
||||
failWithoutActual(
|
||||
Fact.simpleFact(
|
||||
Joiner.on('\n')
|
||||
.join(
|
||||
"Files differ in content. Displaying " + Ascii.toLowerCase(diffFormat.name()),
|
||||
diffString)));
|
||||
}
|
||||
|
||||
public void hasSameContentAs(URL resourceUrl) throws IOException {
|
||||
hasSameContentAs(Resources.asCharSource(resourceUrl, UTF_8).readLines());
|
||||
}
|
||||
|
||||
public static TextDiffSubject assertThat(List<String> actual) {
|
||||
return assertAbout(textFactory()).that(ImmutableList.copyOf(checkNotNull(actual, "actual")));
|
||||
}
|
||||
|
||||
public static TextDiffSubject assertThat(URL resourceUrl) throws IOException {
|
||||
return assertThat(Resources.asCharSource(resourceUrl, UTF_8).readLines());
|
||||
}
|
||||
|
||||
private static final Subject.Factory<TextDiffSubject, ImmutableList<String>>
|
||||
TEXT_DIFF_SUBJECT_TEXT_FACTORY = TextDiffSubject::new;
|
||||
|
||||
public static Subject.Factory<TextDiffSubject, ImmutableList<String>> textFactory() {
|
||||
return TEXT_DIFF_SUBJECT_TEXT_FACTORY;
|
||||
}
|
||||
|
||||
static String generateUnifiedDiff(
|
||||
ImmutableList<String> expectedContent, ImmutableList<String> actualContent) {
|
||||
Patch<String> diff;
|
||||
try {
|
||||
diff = DiffUtils.diff(expectedContent, actualContent);
|
||||
} catch (DiffException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
List<String> unifiedDiff =
|
||||
UnifiedDiffUtils.generateUnifiedDiff("expected", "actual", expectedContent, diff, 0);
|
||||
|
||||
return Joiner.on('\n').join(unifiedDiff);
|
||||
}
|
||||
|
||||
static String generateSideBySideDiff(
|
||||
ImmutableList<String> expectedContent, ImmutableList<String> actualContent) {
|
||||
DiffRowGenerator generator =
|
||||
DiffRowGenerator.create()
|
||||
.showInlineDiffs(true)
|
||||
.inlineDiffByWord(true)
|
||||
.oldTag(f -> "~")
|
||||
.newTag(f -> "**")
|
||||
.build();
|
||||
List<DiffRow> rows;
|
||||
try {
|
||||
rows = generator.generateDiffRows(expectedContent, actualContent);
|
||||
} catch (DiffException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
int maxExpectedLineLength =
|
||||
findMaxLineLength(rows.stream().map(DiffRow::getOldLine).collect(Collectors.toList()));
|
||||
int maxActualLineLength =
|
||||
findMaxLineLength(rows.stream().map(DiffRow::getNewLine).collect(Collectors.toList()));
|
||||
|
||||
SideBySideRowFormatter sideBySideRowFormatter =
|
||||
new SideBySideRowFormatter(maxExpectedLineLength, maxActualLineLength);
|
||||
|
||||
return Joiner.on('\n')
|
||||
.join(
|
||||
sideBySideRowFormatter.formatRow("Expected", "Actual", ' '),
|
||||
sideBySideRowFormatter.formatRow("", "", '-'),
|
||||
rows.stream()
|
||||
.map(
|
||||
row ->
|
||||
sideBySideRowFormatter.formatRow(row.getOldLine(), row.getNewLine(), ' '))
|
||||
.toArray());
|
||||
}
|
||||
|
||||
private static int findMaxLineLength(Collection<String> lines) {
|
||||
return lines.stream()
|
||||
.max(Comparator.comparingInt(String::length))
|
||||
.map(String::length)
|
||||
.orElse(0);
|
||||
}
|
||||
|
||||
private static class SideBySideRowFormatter {
|
||||
private final int maxExpectedLineLength;
|
||||
private final int maxActualLineLength;
|
||||
|
||||
private SideBySideRowFormatter(int maxExpectedLineLength, int maxActualLineLength) {
|
||||
this.maxExpectedLineLength = maxExpectedLineLength;
|
||||
this.maxActualLineLength = maxActualLineLength;
|
||||
}
|
||||
|
||||
public String formatRow(String expected, String actual, char padChar) {
|
||||
return String.format(
|
||||
"|%s|%s|",
|
||||
Strings.padEnd(expected, maxExpectedLineLength, padChar),
|
||||
Strings.padEnd(actual, maxActualLineLength, padChar));
|
||||
}
|
||||
}
|
||||
|
||||
/** The format used to display diffs when two text blocks are different. */
|
||||
public enum DiffFormat {
|
||||
UNIFIED_DIFF {
|
||||
@Override
|
||||
String generateDiff(ImmutableList<String> expected, ImmutableList<String> actual) {
|
||||
return generateUnifiedDiff(expected, actual);
|
||||
}
|
||||
},
|
||||
SIDE_BY_SIDE_MARKDOWN {
|
||||
@Override
|
||||
String generateDiff(ImmutableList<String> expected, ImmutableList<String> actual) {
|
||||
return generateSideBySideDiff(expected, actual);
|
||||
}
|
||||
};
|
||||
|
||||
abstract String generateDiff(ImmutableList<String> expected, ImmutableList<String> actual);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright 2019 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// 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 google.registry.testing;
|
||||
|
||||
import static com.google.common.io.Resources.getResource;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.JUnitBackports.assertThrows;
|
||||
import static google.registry.testing.TextDiffSubject.assertThat;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.io.Resources;
|
||||
import google.registry.testing.TextDiffSubject.DiffFormat;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/** Unit tests for {@link TextDiffSubject}. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class TextDiffSubjectTest {
|
||||
|
||||
// Resources for input data.
|
||||
private static final String ACTUAL_RESOURCE = "google/registry/testing/text-diff-actual.txt";
|
||||
private static final String EXPECTED_RESOURCE = "google/registry/testing/text-diff-expected.txt";
|
||||
|
||||
// Resources for expected diff texts.
|
||||
private static final String UNIFIED_DIFF_RESOURCE =
|
||||
"google/registry/testing/text-unified-diff.txt";
|
||||
private static final String SIDE_BY_SIDE_DIFF_RESOURCE =
|
||||
"google/registry/testing/text-sidebyside-diff.txt";
|
||||
|
||||
@Test
|
||||
public void unifiedDiff_equal() throws IOException {
|
||||
assertThat(getResource(ACTUAL_RESOURCE))
|
||||
.withDiffFormat(DiffFormat.UNIFIED_DIFF)
|
||||
.hasSameContentAs(getResource(ACTUAL_RESOURCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sideBySideDiff_equal() throws IOException {
|
||||
assertThat(getResource(ACTUAL_RESOURCE))
|
||||
.withDiffFormat(DiffFormat.SIDE_BY_SIDE_MARKDOWN)
|
||||
.hasSameContentAs(getResource(ACTUAL_RESOURCE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unifedDiff_notEqual() throws IOException {
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertThat(getResource(ACTUAL_RESOURCE))
|
||||
.withDiffFormat(DiffFormat.UNIFIED_DIFF)
|
||||
.hasSameContentAs(getResource(EXPECTED_RESOURCE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sideBySideDiff_notEqual() throws IOException {
|
||||
assertThrows(
|
||||
AssertionError.class,
|
||||
() ->
|
||||
assertThat(getResource(ACTUAL_RESOURCE))
|
||||
.withDiffFormat(DiffFormat.SIDE_BY_SIDE_MARKDOWN)
|
||||
.hasSameContentAs(getResource(EXPECTED_RESOURCE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayed_unifiedDiff_noDiff() throws IOException {
|
||||
ImmutableList<String> actual = readAllLinesFromResource(ACTUAL_RESOURCE);
|
||||
assertThat(TextDiffSubject.generateUnifiedDiff(actual, actual)).isEqualTo("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayed_unifiedDiff_hasDiff() throws IOException {
|
||||
ImmutableList<String> actual = readAllLinesFromResource(ACTUAL_RESOURCE);
|
||||
ImmutableList<String> expected = readAllLinesFromResource(EXPECTED_RESOURCE);
|
||||
String diff = Joiner.on('\n').join(readAllLinesFromResource(UNIFIED_DIFF_RESOURCE));
|
||||
assertThat(TextDiffSubject.generateUnifiedDiff(expected, actual)).isEqualTo(diff);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void displayed_sideBySideDiff_hasDiff() throws IOException {
|
||||
ImmutableList<String> actual = readAllLinesFromResource(ACTUAL_RESOURCE);
|
||||
ImmutableList<String> expected = readAllLinesFromResource(EXPECTED_RESOURCE);
|
||||
String diff = Joiner.on('\n').join(readAllLinesFromResource(SIDE_BY_SIDE_DIFF_RESOURCE));
|
||||
assertThat(TextDiffSubject.generateSideBySideDiff(expected, actual)).isEqualTo(diff);
|
||||
}
|
||||
|
||||
private static ImmutableList<String> readAllLinesFromResource(String resourceName)
|
||||
throws IOException {
|
||||
return ImmutableList.copyOf(
|
||||
Resources.readLines(getResource(resourceName), StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue