diff --git a/db/build.gradle b/db/build.gradle index c7e9f9ea4..0a90411da 100644 --- a/db/build.gradle +++ b/db/build.gradle @@ -87,10 +87,20 @@ flyway { } dependencies { - runtimeOnly 'org.flywaydb:flyway-core:5.2.4' + def deps = rootProject.dependencyMap - runtimeOnly 'com.google.cloud.sql:postgres-socket-factory:1.0.12' - runtimeOnly 'org.postgresql:postgresql:42.2.5' + compile deps['org.flywaydb:flyway-core'] + + runtimeOnly deps['com.google.cloud.sql:postgres-socket-factory'] + runtimeOnly deps['org.postgresql:postgresql'] + + testCompile deps['com.google.flogger:flogger'] + testRuntime deps['com.google.flogger:flogger-system-backend'] + testCompile deps['com.google.truth:truth'] + testCompile deps['io.github.java-diff-utils:java-diff-utils'] + testCompile deps['org.testcontainers:postgresql'] + testCompile deps['junit:junit'] + testCompile project(':third_party') } // Ensure that resources are rebuilt before running Flyway tasks diff --git a/db/src/main/resources/sql/flyway/V1__new_claims_list_and_entry.sql b/db/src/main/resources/sql/flyway/V1__create_claims_list_and_entry.sql similarity index 96% rename from db/src/main/resources/sql/flyway/V1__new_claims_list_and_entry.sql rename to db/src/main/resources/sql/flyway/V1__create_claims_list_and_entry.sql index 87c69723f..f4132bbdf 100644 --- a/db/src/main/resources/sql/flyway/V1__new_claims_list_and_entry.sql +++ b/db/src/main/resources/sql/flyway/V1__create_claims_list_and_entry.sql @@ -25,7 +25,7 @@ primary key (revision_id) ); - alter table "ClaimsEntry" + alter table if exists "ClaimsEntry" add constraint FKlugn0q07ayrtar87dqi3vs3c8 foreign key (revision_id) references "ClaimsList"; diff --git a/db/src/main/resources/sql/schema/claims_list.sql b/db/src/main/resources/sql/flyway/V2__create_premium_list_and_entry.sql similarity index 52% rename from db/src/main/resources/sql/schema/claims_list.sql rename to db/src/main/resources/sql/flyway/V2__create_premium_list_and_entry.sql index 85e3504ee..e911d7201 100644 --- a/db/src/main/resources/sql/schema/claims_list.sql +++ b/db/src/main/resources/sql/flyway/V2__create_premium_list_and_entry.sql @@ -12,16 +12,21 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -CREATE TABLE `ClaimsList` ( - revision_id BIGSERIAL NOT NULL, - creation_timestamp TIMESTAMPTZ NOT NULL, - PRIMARY KEY (revision_id) -); + create table "PremiumEntry" ( + revision_id int8 not null, + price numeric(19, 2) not null, + domain_label text not null, + primary key (revision_id, domain_label) + ); -CREATE TABLE `ClaimsEntry` ( - revision_id int8 NOT NULL, - claim_key TEXT NOT NULL, - domain_label TEXT NOT NULL, - PRIMARY KEY (revision_id, domain_label), - FOREIGN KEY (revision_id) REFERENCES `ClaimsList`(revision_id) -); + create table "PremiumList" ( + revision_id bigserial not null, + creation_timestamp timestamptz not null, + currency bytea not null, + primary key (revision_id) + ); + + alter table if exists "PremiumEntry" + add constraint FKqebdja3jkx9c9cnqnrw9g9ocu + foreign key (revision_id) + references "PremiumList"; diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql index 5ec31dd06..6a3fcff30 100644 --- a/db/src/main/resources/sql/schema/nomulus.golden.sql +++ b/db/src/main/resources/sql/schema/nomulus.golden.sql @@ -1,18 +1,182 @@ +-- +-- PostgreSQL database dump +-- - create table "ClaimsEntry" ( - revision_id int8 not null, - claim_key text not null, - domain_label text not null, - primary key (revision_id, domain_label) - ); +-- Dumped from database version 9.6.12 +-- Dumped by pg_dump version 9.6.12 - create table "ClaimsList" ( - revision_id bigserial not null, - creation_timestamp timestamptz not null, - primary key (revision_id) - ); +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; + + +-- +-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; + + +SET default_tablespace = ''; + +SET default_with_oids = false; + +-- +-- Name: ClaimsEntry; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public."ClaimsEntry" ( + revision_id bigint NOT NULL, + claim_key text NOT NULL, + domain_label text NOT NULL +); + + +-- +-- Name: ClaimsList; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public."ClaimsList" ( + revision_id bigint NOT NULL, + creation_timestamp timestamp with time zone NOT NULL +); + + +-- +-- Name: ClaimsList_revision_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public."ClaimsList_revision_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: ClaimsList_revision_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public."ClaimsList_revision_id_seq" OWNED BY public."ClaimsList".revision_id; + + +-- +-- Name: PremiumEntry; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public."PremiumEntry" ( + revision_id bigint NOT NULL, + price numeric(19,2) NOT NULL, + domain_label text NOT NULL +); + + +-- +-- Name: PremiumList; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public."PremiumList" ( + revision_id bigint NOT NULL, + creation_timestamp timestamp with time zone NOT NULL, + currency bytea NOT NULL +); + + +-- +-- Name: PremiumList_revision_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public."PremiumList_revision_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: PremiumList_revision_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public."PremiumList_revision_id_seq" OWNED BY public."PremiumList".revision_id; + + +-- +-- Name: ClaimsList revision_id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."ClaimsList" ALTER COLUMN revision_id SET DEFAULT nextval('public."ClaimsList_revision_id_seq"'::regclass); + + +-- +-- Name: PremiumList revision_id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."PremiumList" ALTER COLUMN revision_id SET DEFAULT nextval('public."PremiumList_revision_id_seq"'::regclass); + + +-- +-- Name: ClaimsEntry ClaimsEntry_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."ClaimsEntry" + ADD CONSTRAINT "ClaimsEntry_pkey" PRIMARY KEY (revision_id, domain_label); + + +-- +-- Name: ClaimsList ClaimsList_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."ClaimsList" + ADD CONSTRAINT "ClaimsList_pkey" PRIMARY KEY (revision_id); + + +-- +-- Name: PremiumEntry PremiumEntry_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."PremiumEntry" + ADD CONSTRAINT "PremiumEntry_pkey" PRIMARY KEY (revision_id, domain_label); + + +-- +-- Name: PremiumList PremiumList_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."PremiumList" + ADD CONSTRAINT "PremiumList_pkey" PRIMARY KEY (revision_id); + + +-- +-- Name: ClaimsEntry fklugn0q07ayrtar87dqi3vs3c8; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."ClaimsEntry" + ADD CONSTRAINT fklugn0q07ayrtar87dqi3vs3c8 FOREIGN KEY (revision_id) REFERENCES public."ClaimsList"(revision_id); + + +-- +-- Name: PremiumEntry fkqebdja3jkx9c9cnqnrw9g9ocu; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."PremiumEntry" + ADD CONSTRAINT fkqebdja3jkx9c9cnqnrw9g9ocu FOREIGN KEY (revision_id) REFERENCES public."PremiumList"(revision_id); + + +-- +-- PostgreSQL database dump complete +-- - alter table "ClaimsEntry" - add constraint FKlugn0q07ayrtar87dqi3vs3c8 - foreign key (revision_id) - references "ClaimsList"; diff --git a/db/src/main/resources/sql/schema/premium_list.sql b/db/src/main/resources/sql/schema/premium_list.sql deleted file mode 100644 index 2cc1b7862..000000000 --- a/db/src/main/resources/sql/schema/premium_list.sql +++ /dev/null @@ -1,28 +0,0 @@ --- 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. - -CREATE TABLE "PremiumList" ( - revision_id BIGSERIAL NOT NULL, - creation_timestamp TIMESTAMPTZ NOT NULL, - currency TEXT NOT NULL, - PRIMARY KEY (revision_id) -); - -CREATE TABLE "PremiumEntry" ( - revision_id BIGSERIAL NOT NULL, - price NUMERIC(12, 2) NOT NULL, - domain_label TEXT NOT NULL, - primary key (revision_id, domain_label), - FOREIGN KEY (revision_id) REFERENCES "PremiumList"(revision_id) -); diff --git a/db/src/test/java/google/registry/sql/flyway/SchemaTest.java b/db/src/test/java/google/registry/sql/flyway/SchemaTest.java new file mode 100644 index 000000000..c0c4bd08b --- /dev/null +++ b/db/src/test/java/google/registry/sql/flyway/SchemaTest.java @@ -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. + * + *

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 this link 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 + }; + } +} diff --git a/db/src/test/java/google/registry/testing/TextDiffSubject.java b/db/src/test/java/google/registry/testing/TextDiffSubject.java new file mode 100644 index 000000000..8d90b79b2 --- /dev/null +++ b/db/src/test/java/google/registry/testing/TextDiffSubject.java @@ -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. + * + *

User may choose one of the following diff formats: + * + *

+ * + *

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 actual; + private DiffFormat diffFormat = DiffFormat.SIDE_BY_SIDE_MARKDOWN; + + protected TextDiffSubject(FailureMetadata metadata, List actual) { + super(metadata, actual); + this.actual = ImmutableList.copyOf(actual); + } + + public TextDiffSubject withDiffFormat(DiffFormat format) { + this.diffFormat = format; + return this; + } + + public void hasSameContentAs(List expectedContent) { + checkNotNull(expectedContent, "expectedContent"); + ImmutableList 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 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> + TEXT_DIFF_SUBJECT_TEXT_FACTORY = TextDiffSubject::new; + + public static Subject.Factory> textFactory() { + return TEXT_DIFF_SUBJECT_TEXT_FACTORY; + } + + static String generateUnifiedDiff( + ImmutableList expectedContent, ImmutableList actualContent) { + Patch diff; + try { + diff = DiffUtils.diff(expectedContent, actualContent); + } catch (DiffException e) { + throw new RuntimeException(e); + } + List unifiedDiff = + UnifiedDiffUtils.generateUnifiedDiff("expected", "actual", expectedContent, diff, 0); + + return Joiner.on('\n').join(unifiedDiff); + } + + static String generateSideBySideDiff( + ImmutableList expectedContent, ImmutableList actualContent) { + DiffRowGenerator generator = + DiffRowGenerator.create() + .showInlineDiffs(true) + .inlineDiffByWord(true) + .oldTag(f -> "~") + .newTag(f -> "**") + .build(); + List 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 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 expected, ImmutableList actual) { + return generateUnifiedDiff(expected, actual); + } + }, + SIDE_BY_SIDE_MARKDOWN { + @Override + String generateDiff(ImmutableList expected, ImmutableList actual) { + return generateSideBySideDiff(expected, actual); + } + }; + + abstract String generateDiff(ImmutableList expected, ImmutableList actual); + } +} diff --git a/db/src/test/java/google/registry/testing/TextDiffSubjectTest.java b/db/src/test/java/google/registry/testing/TextDiffSubjectTest.java new file mode 100644 index 000000000..ef9b73509 --- /dev/null +++ b/db/src/test/java/google/registry/testing/TextDiffSubjectTest.java @@ -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 actual = readAllLinesFromResource(ACTUAL_RESOURCE); + assertThat(TextDiffSubject.generateUnifiedDiff(actual, actual)).isEqualTo(""); + } + + @Test + public void displayed_unifiedDiff_hasDiff() throws IOException { + ImmutableList actual = readAllLinesFromResource(ACTUAL_RESOURCE); + ImmutableList 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 actual = readAllLinesFromResource(ACTUAL_RESOURCE); + ImmutableList 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 readAllLinesFromResource(String resourceName) + throws IOException { + return ImmutableList.copyOf( + Resources.readLines(getResource(resourceName), StandardCharsets.UTF_8)); + } +} diff --git a/db/src/test/resources/google/registry/testing/text-diff-actual.txt b/db/src/test/resources/google/registry/testing/text-diff-actual.txt new file mode 100644 index 000000000..bef69f31a --- /dev/null +++ b/db/src/test/resources/google/registry/testing/text-diff-actual.txt @@ -0,0 +1,2 @@ +This is a random file, +with two lines and terminates with a newline. diff --git a/db/src/test/resources/google/registry/testing/text-diff-expected.txt b/db/src/test/resources/google/registry/testing/text-diff-expected.txt new file mode 100644 index 000000000..d09d06e30 --- /dev/null +++ b/db/src/test/resources/google/registry/testing/text-diff-expected.txt @@ -0,0 +1,3 @@ +This is a random file, + +with three lines and terminates without a newline. \ No newline at end of file diff --git a/db/src/test/resources/google/registry/testing/text-sidebyside-diff.txt b/db/src/test/resources/google/registry/testing/text-sidebyside-diff.txt new file mode 100644 index 000000000..1d9ce26a3 --- /dev/null +++ b/db/src/test/resources/google/registry/testing/text-sidebyside-diff.txt @@ -0,0 +1,5 @@ +|Expected |Actual | +|------------------------------------------------------|-----------------------------------------------------| +|This is a random file, |This is a random file, | +| |with **two** lines and terminates **with** a newline.| +|with ~three~ lines and terminates ~without~ a newline.| | \ No newline at end of file diff --git a/db/src/test/resources/google/registry/testing/text-unified-diff.txt b/db/src/test/resources/google/registry/testing/text-unified-diff.txt new file mode 100644 index 000000000..bd7041090 --- /dev/null +++ b/db/src/test/resources/google/registry/testing/text-unified-diff.txt @@ -0,0 +1,6 @@ +--- expected ++++ actual +@@ -2,2 +2,1 @@ +- +-with three lines and terminates without a newline. ++with two lines and terminates with a newline. \ No newline at end of file diff --git a/db/src/test/resources/testcontainer/mount/README.md b/db/src/test/resources/testcontainer/mount/README.md new file mode 100644 index 000000000..aa31c048d --- /dev/null +++ b/db/src/test/resources/testcontainer/mount/README.md @@ -0,0 +1,4 @@ +## Summary + +This folder may be mapped to a testcontainer instance as a volume. Please refer +to SchemaTest.java for context. \ No newline at end of file diff --git a/dependencies.gradle b/dependencies.gradle index e46608106..5649ec576 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -45,6 +45,7 @@ ext { 'com.google.closure-stylesheets:closure-stylesheets:1.5.0', 'com.google.cloud:google-cloud-core:1.59.0', 'com.google.cloud:google-cloud-storage:1.59.0', + 'com.google.cloud.sql:postgres-socket-factory:1.0.12', 'com.google.code.findbugs:jsr305:3.0.2', 'com.google.code.gson:gson:2.8.5', 'com.googlecode.json-simple:json-simple:1.1.1', @@ -78,6 +79,7 @@ ext { 'com.sun.xml.bind:jaxb-xjc:2.2.11', 'com.thoughtworks.qdox:qdox:1.12.1', 'dnsjava:dnsjava:2.1.7', + 'io.github.java-diff-utils:java-diff-utils:4.0', 'io.netty:netty-buffer:4.1.31.Final', 'io.netty:netty-codec:4.1.31.Final', 'io.netty:netty-codec-http:4.1.31.Final', @@ -111,6 +113,7 @@ ext { 'org.bouncycastle:bcpg-jdk15on:1.61', 'org.bouncycastle:bcpkix-jdk15on:1.61', 'org.bouncycastle:bcprov-jdk15on:1.61', + 'org.flywaydb:flyway-core:5.2.4', 'org.glassfish.jaxb:jaxb-runtime:2.3.0', 'org.hamcrest:hamcrest-all:1.3', 'org.hamcrest:hamcrest-core:1.3', @@ -125,8 +128,8 @@ ext { 'org.seleniumhq.selenium:selenium-chrome-driver:3.141.59', 'org.seleniumhq.selenium:selenium-java:3.141.59', 'org.seleniumhq.selenium:selenium-remote-driver:3.141.59', - 'org.testcontainers:postgresql:1.11.3', - 'org.testcontainers:selenium:1.10.7', + 'org.testcontainers:postgresql:1.12.1', + 'org.testcontainers:selenium:1.12.1', 'org.yaml:snakeyaml:1.17', 'xerces:xmlParserAPIs:2.6.2', 'xpp3:xpp3:1.1.4c'