1
0
mirror of https://github.com/google/nomulus synced 2026-06-09 16:33:02 +00:00

Compare commits

...

25 Commits

Author SHA1 Message Date
Weimin Yu aa0dcea537 Fix flaky tests due to Entity name conflicts (#569)
* Fix flaky tests due to Entity name conflicts

Objectify siliently replaces current registration of a given kind
when another class is registered for this kind. There are
several TestObject classes in the current code base, which by
default are all mapped to the same kind.

Tests have only been flaky because impacted tests need to run
in specific orders for failures to happen. Using multiple executors
in Gradle also reduced the likely hood of errors. To reproduce the
problem run the following tests in order (e.g., by putting them in
a test suite):
1. ExportCommitLogDiffActionTest
2. CreateAutoTimestampTest
3. RestoreCommitLogsActionTest

In this PR, we
- Made sure all entities have unique kinds.
- Made all test entities register with AppEngineRule instead of directly
  with ObjectifyService.
- Added code in AppEngineRule to check for re-registrations.
- Added presumit check for forbidden direct registration.
2020-04-28 15:32:42 -04:00
sarahcaseybot e920e4d201 Remove Lock Dual Read and Dual Write (#568) 2020-04-27 17:30:51 -04:00
Ben McIlwain cd13f6c5d3 Allow the nomulus renew_domain command to specify the client ID (#567)
* Allow the `nomulus renew_domain` command to specify the client ID

This means that a superuser can renew a domain and have the associated history
entry, one time billing event, and renewal grace period be recorded against a
specified registrar rather than the owning registrar of the domain.  This is
useful to e.g. renew a domain for free by "charging" the renewal to the
registry's fake registrar.  Since the grace period is written to the specified
cliend id as well, if the actual registrar deletes the domain, they don't get
back the money that they didn't pay in the first place.
2020-04-24 18:06:27 -04:00
Ben McIlwain 210de9340e Don't NPE when nomulus tool is run without a subcommand (#564)
* Don't NPE when nomulus tool is run without a subcommand

This occurred when an environment was specified but without a subcommand. Now,
the list of valid subcommands is outputted instead of seeing a generic NPE.

This also makes some formatting changes in other files that were causing the
incremental format check to fail.

* Try AppEngineRule
2020-04-24 17:32:58 -04:00
Michael Muller 5d58be6f0a Remove separate deployment of persistence.xml (#563)
* Remove separate deployment of persistence.xml

We added a step to explicitly copy persistence.xml because for some reason it
wasn't originally getting deployed to app-engine, resulting in failures on
startup.  However, this file is now included in core.jar and we are now
getting a warning about multiple persistence units with the same name as it
reads the files from both the filesystem and core.jar.
2020-04-23 13:35:37 -04:00
Shicong Huang d8066ca752 Remove minimumIdle config in HikariCP (#557)
* Remove minimumIdle config for HikariCP

* Add comment

* Resolve comment
2020-04-22 19:35:02 -04:00
gbrodman ca3ae9b0e4 Add SqlEntity and DatastoreEntity interfaces (#562)
* Add SqlEntity and DatastoreEntity interfaces

These will be used when replaying transactions from either the Datastore
commit logs or the SQL Transaction objects.

When Datastore is the primary database, we will read in the
Datastore commit logs, convert each saved entity to however many SQL
entities, then save those SQL entities in SQL.

When SQL is the primary database, we will read in the SQL objects from a
yet-to-be-created SQL table, convert them to however many Datastore
entities, then save those Datastore entities in Datastore.

This PR includes a couple simple examples of how this will work for entities that are
saveable in both SQL and Datastore (the simple case).

* Add 1-1 mapping between entity annotations and interfaces
2020-04-21 17:28:49 -04:00
Shicong Huang 295251ee78 Add JPA annotations to ContactResource and generate schema (#547)
* Add JPA annotations to ContactResource and generate schema

* Resolve comments

* Resolve comments

* Manually add foreign key constraints

* Run with junit5

* Rebase on HEAD

* Fix DomainBaseSqlTest
2020-04-21 15:40:16 -04:00
Michael Muller 7ca0e9387c Persist DomainBase.nsHosts VKeys to SQL (#541)
Persist nsHosts in Cloud SQL

Persist the VKey based nameserver hosts field of DomainBase in Cloud SQL with
foreign key constraints.
2020-04-20 13:03:12 -04:00
Weimin Yu 4f988d42c7 Allow Entity instantiation without AppEngineRule (#559)
* Allow Entity instantiation without AppEngineRule

Defined an extension that sets up a fake AppEngine environment
so that Datastore entities can be instantiated.

* Allow Entity instantiation without AppEngineRule

Defined an extension that sets up a fake AppEngine environment
so that Datastore entities can be instantiated.
2020-04-16 17:03:27 -04:00
Weimin Yu 9b47a6cfee Hack to call setup and teardown in JUnit5 suite (#560)
* Hack to call setup and teardown in JUnit5 suite

JUnit 5 runner does not support @BeforeAll and @AfterAll declared
in the Suite class (as opposed to the member classes). However,
staying with the JUnit 4 suite runner would prevent any member
classes from migrating to JUnit 5.

We use a hack to invoke suite-level set up and teardown from tests.
This change is safe in that if the JUnit 5 runner implementation changes
behavior, we will only see false alarms.
2020-04-16 14:46:08 -04:00
Shicong Huang 9db4d1a082 Add a listener to invoke entity callbacks (#551)
* Add a listener to invoke entity callbacks

* Resolve comments

* Add test
2020-04-16 14:30:43 -04:00
Michael Muller ec22d4d1a0 Implement VKeyConverter (#538)
* Implement VKeyConverter

Implement a SQL converter that can be used for VKey objects.

Caveats:

- This only works with string columns (there's an excellent chance that all of
  our VKeys will use SQL string columns).
- Using this dpesn't establish a foreign key constraint between the referenced
  type (the "T" in VKey<T>) and the entity itself: this needs to be
  defined manually in the schema.
2020-04-16 09:45:23 -04:00
Weimin Yu 0fcf26def0 Exclude proxy configs from the FOSS jar (#558)
* Exclude proxy configs from the FOSS jar

No sensitve data exposed.

Added a todo to modify the release process and stop
building the foss jar on the merged repo.
2020-04-15 12:21:41 -04:00
gbrodman 3d88ba4e1b Add verification that domain labels aren't multi-level domains (#553)
* Add verification that domain labels aren't multi-level domains

In addition, I did a bit of test refactoring because previously, the
CreateOrUpdateReserveListCommandTestCase test cases weren't actually
testing the proper things -- they were failing with
IllegalArgumentExceptions, but not the right ones.

* Change test name and use IDN library

* Handle numeric labels

String like "0" or "2018" are valid labels but not valid domain names

* Use IDN validation with a dummy TLD
2020-04-15 11:54:40 -04:00
Weimin Yu 580a3b6981 Disable JpaEntityCoverageCheck by default (#555)
* Disable JpaEntityCoverageCheck by default

Only members of SqlIntegrationTestSuite should enable the check,
which incurs per-test overhead.
2020-04-14 12:48:21 -04:00
gbrodman 6990d6058f Allow a --token option when checking a domain (#556)
* Allow a --token option when checking a domain
2020-04-14 10:20:27 -04:00
gbrodman dfeed63c40 Run automated NPM update (#554) 2020-04-11 11:47:25 -04:00
Lai Jiang 9eac9621cb Add a Test workaround for certain Linux distro (#552)
On Arch Linux, DumpGoldenSchemaCommandTest failed due to the follow
error:

java.lang.RuntimeException: Container.ExecResult(exitCode=1, stdout=, stderr=pg_dump: [archiver] could not open output file "/tmp/pg_dump.out": Is a directory)

However I cannot figure out why this permission error happens, as the
docker command is executed as root. Saving the pg_dump output to a
temporary file and copy it over the mapped file works, so I don't
know...
2020-04-10 12:44:36 -04:00
Weimin Yu b7efc5dd25 Migrate SqlIntegrationTestSuite members to Junit5 (#550)
* Migrate SqlIntegrationTestSuite members  to Junit5

Made InjectRule and EntityTestCase work with both JUnit4 and 5.

Note that "@RunWith(JUnit4.class)" is no longer needed on
JUnit4 test classes. Therefore, its removal from EntityTestCase
has no impact on child classes. All of them are still included in
tests.

Migrated remaining member classes in SqlIntegrationTestSuite to JUnit5.
2020-04-09 12:54:16 -04:00
Weimin Yu 1911c11623 Add Test suite support for JUnit 5 classes (#549)
* Add Test suite support for JUnit 5 classes

Added Gradle dependencies and updated lockfiles.

Updated SqlInegrationTestSuite to use new annotations.

Migrated one member class in SqlIntegrationTestSuite (CursorDaoTest)
to JUnit 5, and verified that the new Suite runner can handle a
mixture of JUnit 4 and 5 tests in one suite.

Note that Gradle tests that run TestSuites must choose JUnit 4.
Updated core/build.gradle and integration/build.gradle.
2020-04-07 21:06:49 -04:00
Weimin Yu b8df0bac24 Make AppEngineRule work with JUnit 5 (#548)
* Make AppEngineRule work with JUnit 5

Made AppEngineRule work with both JUnit4 and JUnit 5 and applied
it to PremiumListTest.

Next step is to convert SqlIntegrationTestSuite.java to JUnit5.
2020-04-07 14:59:25 -04:00
Lai Jiang 0561c7754e Upgrade to Gradle 6.3 (#546) 2020-04-06 22:14:14 -04:00
Weimin Yu 904f16c8b5 Actually run JUnit 5 tests (#545)
* Actually run JUnit 5 tests

In Gradle, JUnit 5 must be explicitly enabled with a call to
test.useJUnitPlatform().

The FilteringTest used in :core must also enable JUnit5 separately.

Fixed AppEngineRule to work with the few tests that have migrated
to JUnit5.

More work is needed with AppEngine before we can migrate tests
that actually use Cloud SQL.

For context, with @EnableRuleMigrationSupport, JUnit 5 runner calls
an external resource's before() and after() methods. TestRule.apply()
is not called, therefore any setup done their will be bypassed with
JUnit 5.
2020-04-06 13:26:38 -04:00
Weimin Yu 3a7d71e411 Upgrade CompareDbBackup for Datastore V3 (#543)
* Upgrade CompareDbBackup for Datastore V3

Upgrade the CompareDbBackup class to work with latest
Datastore backup directory structure.

Also fixed a few unrelated minor issues:
- Remaining cases of improper use of System.setOut
- Wrong import order in one class
2020-04-06 10:50:38 -04:00
128 changed files with 3262 additions and 1005 deletions
-4
View File
@@ -31,10 +31,6 @@ def coreResourcesDir = "${rootDir}/core/build/resources/main"
war {
webInf {
from "../../core/src/main/java/google/registry/env/common/${project.name}/WEB-INF"
from("${coreResourcesDir}/META-INF/persistence.xml") {
into "classes/META-INF"
}
}
}
@@ -61,12 +61,12 @@ org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:2.10.0
org.hamcrest:hamcrest-core:1.3
org.json:json:20160212
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.mockito:mockito-core:2.25.0
org.objenesis:objenesis:2.6
org.opentest4j:opentest4j:1.2.0
@@ -61,12 +61,12 @@ org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:2.10.0
org.hamcrest:hamcrest-core:1.3
org.json:json:20160212
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.mockito:mockito-core:2.25.0
org.objenesis:objenesis:2.6
org.opentest4j:opentest4j:1.2.0
@@ -61,12 +61,12 @@ org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:2.10.0
org.hamcrest:hamcrest-core:1.3
org.json:json:20160212
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.mockito:mockito-core:2.25.0
org.objenesis:objenesis:2.6
org.opentest4j:opentest4j:1.2.0
@@ -61,12 +61,12 @@ org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:2.10.0
org.hamcrest:hamcrest-core:1.3
org.json:json:20160212
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.mockito:mockito-core:2.25.0
org.objenesis:objenesis:2.6
org.opentest4j:opentest4j:1.2.0
@@ -20,10 +20,10 @@ org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:2.10.0
org.eclipse.jgit:org.eclipse.jgit:4.4.1.201607150455-r
org.hamcrest:hamcrest-core:1.3
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.opentest4j:opentest4j:1.2.0
@@ -20,10 +20,10 @@ org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:2.10.0
org.eclipse.jgit:org.eclipse.jgit:4.4.1.201607150455-r
org.hamcrest:hamcrest-core:1.3
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.opentest4j:opentest4j:1.2.0
@@ -21,10 +21,10 @@ org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:2.10.0
org.eclipse.jgit:org.eclipse.jgit:4.4.1.201607150455-r
org.hamcrest:hamcrest-core:1.3
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.opentest4j:opentest4j:1.2.0
@@ -21,10 +21,10 @@ org.checkerframework:checker-compat-qual:2.5.5
org.checkerframework:checker-qual:2.10.0
org.eclipse.jgit:org.eclipse.jgit:4.4.1.201607150455-r
org.hamcrest:hamcrest-core:1.3
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.opentest4j:opentest4j:1.2.0
+9
View File
@@ -99,6 +99,15 @@ PRESUBMITS = {
"System.(out|err).println is only allowed in tools/ packages. Please "
"use a logger instead.",
# ObjectifyService.register is restricted to main/ or AppEngineRule.
PresubmitCheck(
r".*\bObjectifyService\.register", "java", {
"/build/", "/generated/", "node_modules/", "src/main/",
"AppEngineRule.java"
}):
"ObjectifyService.register is not allowed in tests. Please use "
"AppengineRule.register instead.",
# PostgreSQLContainer instantiation must specify docker tag
PresubmitCheck(
r"[\s\S]*new\s+PostgreSQLContainer(<[\s\S]*>)?\(\s*\)[\s\S]*",
+15
View File
@@ -309,6 +309,8 @@ dependencies {
testCompile deps['org.junit.jupiter:junit-jupiter-api']
testCompile deps['org.junit.jupiter:junit-jupiter-engine']
testCompile deps['org.junit.jupiter:junit-jupiter-migrationsupport']
testCompile deps['org.junit.platform:junit-platform-runner']
testCompile deps['org.junit.platform:junit-platform-suite-api']
testCompile deps['org.junit.vintage:junit-vintage-engine']
testCompile deps['org.mockito:mockito-core']
runtime deps['org.postgresql:postgresql']
@@ -646,6 +648,10 @@ artifacts {
*/
class FilteringTest extends Test {
FilteringTest() {
useJUnitPlatform();
}
private void applyTestFilter() {
if (project.testFilter) {
testNameIncludePatterns = project.testFilter.split(',')
@@ -733,6 +739,10 @@ task registryToolIntegrationTest {
// Dedicated test suite for schema-dependent tests.
task sqlIntegrationTest(type: FilteringTest) {
// TestSuite still requires a JUnit 4 runner, which knows how to handle JUnit 5 tests.
// Here we need to override parent's choice of JUnit 5. If changing this, remember to
// change :integration:sqlIntegrationTest too.
useJUnit()
excludeTestCases = false
tests = ['google/registry/schema/integration/SqlIntegrationTestSuite.*']
}
@@ -852,6 +862,8 @@ task standardTest(type: FilteringTest) {
includeAllTests()
exclude fragileTestPatterns
exclude outcastTestPatterns
// See SqlIntegrationTestSuite.java
exclude '**/*BeforeSuiteTest.*', '**/*AfterSuiteTest.*'
if (rootProject.findProperty("skipDockerIncompatibleTests") == "true") {
exclude dockerIncompatibleTestPatterns
@@ -884,11 +896,14 @@ createUberJar('nomulus', 'nomulus', 'google.registry.tools.RegistryTool')
// A jar with classes and resources from main sourceSet, excluding internal
// data. See comments on configurations.nomulus_test above for details.
// TODO(weiminyu): release process should build this using the public repo to eliminate the need
// for excludes.
task nomulusFossJar (type: Jar) {
archiveBaseName = 'nomulus'
archiveClassifier = 'public'
from (project.sourceSets.main.output) {
exclude 'google/registry/config/files/**'
exclude 'google/registry/proxy/config/**'
}
from (project.sourceSets.main.output) {
include 'google/registry/config/files/default-config.yaml'
@@ -243,13 +243,16 @@ org.jboss:jandex:2.0.5.Final
org.jetbrains:annotations:17.0.0
org.joda:joda-money:1.0.1
org.json:json:20160810
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.jupiter:junit-jupiter-migrationsupport:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.jupiter:junit-jupiter-migrationsupport:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.platform:junit-platform-launcher:1.6.1
org.junit.platform:junit-platform-runner:1.6.1
org.junit.platform:junit-platform-suite-api:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.jvnet.staxex:stax-ex:1.8
org.mockito:mockito-core:2.25.0
org.mortbay.jetty:jetty-util:6.1.26
@@ -241,13 +241,16 @@ org.jboss:jandex:2.0.5.Final
org.jetbrains:annotations:17.0.0
org.joda:joda-money:1.0.1
org.json:json:20160810
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.jupiter:junit-jupiter-migrationsupport:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.jupiter:junit-jupiter-migrationsupport:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.platform:junit-platform-launcher:1.6.1
org.junit.platform:junit-platform-runner:1.6.1
org.junit.platform:junit-platform-suite-api:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.jvnet.staxex:stax-ex:1.8
org.mockito:mockito-core:2.25.0
org.mortbay.jetty:jetty-util:6.1.26
@@ -246,13 +246,16 @@ org.jboss:jandex:2.0.5.Final
org.jetbrains:annotations:17.0.0
org.joda:joda-money:1.0.1
org.json:json:20160810
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.jupiter:junit-jupiter-migrationsupport:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.jupiter:junit-jupiter-migrationsupport:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.platform:junit-platform-launcher:1.6.1
org.junit.platform:junit-platform-runner:1.6.1
org.junit.platform:junit-platform-suite-api:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.jvnet.staxex:stax-ex:1.8
org.mockito:mockito-core:2.25.0
org.mortbay.jetty:jetty-util:6.1.26
@@ -246,13 +246,16 @@ org.jboss:jandex:2.0.5.Final
org.jetbrains:annotations:17.0.0
org.joda:joda-money:1.0.1
org.json:json:20160810
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.jupiter:junit-jupiter-migrationsupport:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.jupiter:junit-jupiter-migrationsupport:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.platform:junit-platform-launcher:1.6.1
org.junit.platform:junit-platform-runner:1.6.1
org.junit.platform:junit-platform-suite-api:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.jvnet.staxex:stax-ex:1.8
org.mockito:mockito-core:2.25.0
org.mortbay.jetty:jetty-util:6.1.26
@@ -1512,21 +1512,11 @@ public final class RegistryConfig {
return CONFIG_SETTINGS.get().hibernate.hikariConnectionTimeout;
}
/** Returns the minimum idle connections for HikariCP. */
public static String getHibernateHikariMinimumIdle() {
return CONFIG_SETTINGS.get().hibernate.hikariMinimumIdle;
}
/** Returns the maximum pool size for HikariCP. */
public static String getHibernateHikariMaximumPoolSize() {
return CONFIG_SETTINGS.get().hibernate.hikariMaximumPoolSize;
}
/** Returns the idle timeout for HikariCP. */
public static String getHibernateHikariIdleTimeout() {
return CONFIG_SETTINGS.get().hibernate.hikariIdleTimeout;
}
/** Returns the roid suffix to be used for the roids of all contacts and hosts. */
public static String getContactAndHostRoidSuffix() {
return CONFIG_SETTINGS.get().registryPolicy.contactAndHostRoidSuffix;
@@ -112,9 +112,7 @@ public class RegistryConfigSettings {
public String connectionIsolation;
public String logSqlQueries;
public String hikariConnectionTimeout;
public String hikariMinimumIdle;
public String hikariMaximumPoolSize;
public String hikariIdleTimeout;
}
/** Configuration for Cloud SQL. */
@@ -207,9 +207,17 @@ hibernate:
# Connection pool configurations.
hikariConnectionTimeout: 20000
hikariMinimumIdle: 0
# We occasionally received "Connection is not available, request timed out"
# exception when setting minimumIdle to 0 and it turned out it is a bug (See
# https://github.com/brettwooldridge/HikariCP/issues/1212) in HikariCP and
# the workaround is to use a fixed size connection pool.
#
# HikariCP also recommends not setting minimumIdle for maximum performance
# and responsiveness to spike demands (See
# https://github.com/brettwooldridge/HikariCP).
# TODO(b/154720215): Experiment with a smaller pool size.
hikariMaximumPoolSize: 20
hikariIdleTimeout: 300000
cloudSql:
# jdbc url for the Cloud SQL database.
@@ -70,9 +70,11 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
/** The ID of the registrar that is currently sponsoring this resource. */
@Index
@Column(nullable = false)
String currentSponsorClientId;
/** The ID of the registrar that created this resource. */
@Column(nullable = false)
String creationClientId;
/**
@@ -88,7 +90,9 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
// Map the method to XML, not the field, because if we map the field (with an adaptor class) it
// will never be omitted from the xml even if the timestamp inside creationTime is null and we
// return null from the adaptor. (Instead it gets written as an empty tag.)
@Index CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
@Column(nullable = false)
@Index
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
/**
* The time when this resource was or will be deleted.
@@ -16,13 +16,14 @@ package google.registry.model.contact;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.eppcommon.Address;
import javax.persistence.Embeddable;
/**
* EPP Contact Address
*
* <p>This class is embedded inside the {@link PostalInfo} of an EPP contact to hold its
* address. The fields are all defined in parent class {@link Address}, but the subclass is still
* necessary to pick up the contact namespace.
* <p>This class is embedded inside the {@link PostalInfo} of an EPP contact to hold its address.
* The fields are all defined in parent class {@link Address}, but the subclass is still necessary
* to pick up the contact namespace.
*
* <p>This does not implement {@code Overlayable} because it is intended to be bulk replaced on
* update.
@@ -30,6 +31,7 @@ import google.registry.model.eppcommon.Address;
* @see PostalInfo
*/
@Embed
@Embeddable
public class ContactAddress extends Address {
/** Builder for {@link ContactAddress}. */
@@ -20,6 +20,7 @@ import javax.xml.bind.annotation.XmlType;
/** A version of authInfo specifically for contacts. */
@Embed
@javax.persistence.Embeddable
@XmlType(namespace = "urn:ietf:params:xml:ns:contact-1.0")
public class ContactAuthInfo extends AuthInfo {
public static ContactAuthInfo create(PasswordAuth pw) {
@@ -16,17 +16,19 @@ package google.registry.model.contact;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.eppcommon.PhoneNumber;
import javax.persistence.Embeddable;
/**
* EPP Contact Phone Number
*
* <p>This class is embedded inside a {@link ContactResource} hold the phone number of an EPP
* contact. The fields are all defined in the parent class {@link PhoneNumber}, but the subclass is
* contact. The fields are all defined in the parent class {@link PhoneNumber}, but the subclass is
* still necessary to pick up the contact namespace.
*
* @see ContactResource
*/
@Embed
@Embeddable
public class ContactPhoneNumber extends PhoneNumber {
/** Builder for {@link ContactPhoneNumber}. */
@@ -33,6 +33,11 @@ import google.registry.model.transfer.TransferData;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlElement;
import org.joda.time.DateTime;
@@ -43,9 +48,19 @@ import org.joda.time.DateTime;
*/
@ReportedOn
@Entity
@javax.persistence.Entity(name = "Contact")
@javax.persistence.Table(
name = "Contact",
indexes = {
@javax.persistence.Index(columnList = "creationTime"),
@javax.persistence.Index(columnList = "currentSponsorClientId"),
@javax.persistence.Index(columnList = "deletionTime"),
@javax.persistence.Index(columnList = "contactId", unique = true),
@javax.persistence.Index(columnList = "searchName")
})
@ExternalMessagingName("contact")
public class ContactResource extends EppResource implements
ForeignKeyedEppResource, ResourceWithTransferData {
public class ContactResource extends EppResource
implements ForeignKeyedEppResource, ResourceWithTransferData {
/**
* Unique identifier for this contact.
@@ -61,13 +76,55 @@ public class ContactResource extends EppResource implements
* US-ASCII character set. Personal info; cleared by {@link Builder#wipeOut}.
*/
@IgnoreSave(IfNull.class)
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "name", column = @Column(name = "addr_local_name")),
@AttributeOverride(name = "org", column = @Column(name = "addr_local_org")),
@AttributeOverride(name = "type", column = @Column(name = "addr_local_type")),
@AttributeOverride(
name = "address.streetLine1",
column = @Column(name = "addr_local_street_line1")),
@AttributeOverride(
name = "address.streetLine2",
column = @Column(name = "addr_local_street_line2")),
@AttributeOverride(
name = "address.streetLine3",
column = @Column(name = "addr_local_street_line3")),
@AttributeOverride(name = "address.city", column = @Column(name = "addr_local_city")),
@AttributeOverride(name = "address.state", column = @Column(name = "addr_local_state")),
@AttributeOverride(name = "address.zip", column = @Column(name = "addr_local_zip")),
@AttributeOverride(
name = "address.countryCode",
column = @Column(name = "addr_local_country_code"))
})
PostalInfo localizedPostalInfo;
/**
* Internationalized postal info for the contact. Personal info; cleared by
* {@link Builder#wipeOut}.
* Internationalized postal info for the contact. Personal info; cleared by {@link
* Builder#wipeOut}.
*/
@IgnoreSave(IfNull.class)
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "name", column = @Column(name = "addr_i18n_name")),
@AttributeOverride(name = "org", column = @Column(name = "addr_i18n_org")),
@AttributeOverride(name = "type", column = @Column(name = "addr_i18n_type")),
@AttributeOverride(
name = "address.streetLine1",
column = @Column(name = "addr_i18n_street_line1")),
@AttributeOverride(
name = "address.streetLine2",
column = @Column(name = "addr_i18n_street_line2")),
@AttributeOverride(
name = "address.streetLine3",
column = @Column(name = "addr_i18n_street_line3")),
@AttributeOverride(name = "address.city", column = @Column(name = "addr_i18n_city")),
@AttributeOverride(name = "address.state", column = @Column(name = "addr_i18n_state")),
@AttributeOverride(name = "address.zip", column = @Column(name = "addr_i18n_zip")),
@AttributeOverride(
name = "address.countryCode",
column = @Column(name = "addr_i18n_country_code"))
})
PostalInfo internationalizedPostalInfo;
/**
@@ -80,10 +137,20 @@ public class ContactResource extends EppResource implements
/** Contacts voice number. Personal info; cleared by {@link Builder#wipeOut}. */
@IgnoreSave(IfNull.class)
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "phoneNumber", column = @Column(name = "voice_phone_number")),
@AttributeOverride(name = "extension", column = @Column(name = "voice_phone_extension")),
})
ContactPhoneNumber voice;
/** Contacts fax number. Personal info; cleared by {@link Builder#wipeOut}. */
@IgnoreSave(IfNull.class)
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "phoneNumber", column = @Column(name = "fax_phone_number")),
@AttributeOverride(name = "extension", column = @Column(name = "fax_phone_extension")),
})
ContactPhoneNumber fax;
/** Contacts email address. Personal info; cleared by {@link Builder#wipeOut}. */
@@ -91,10 +158,16 @@ public class ContactResource extends EppResource implements
String email;
/** Authorization info (aka transfer secret) of the contact. */
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "pw.value", column = @Column(name = "auth_info_value")),
@AttributeOverride(name = "pw.repoId", column = @Column(name = "auth_info_repo_id")),
})
ContactAuthInfo authInfo;
/** Data about any pending or past transfers on this contact. */
TransferData transferData;
// TODO(b/153363295): Figure out how to persist transfer data
@Transient TransferData transferData;
/**
* The time that this resource was last transferred.
@@ -107,6 +180,16 @@ public class ContactResource extends EppResource implements
// the wipeOut() function, so that data is not kept around for deleted contacts.
/** Disclosure policy. */
@Embedded
@AttributeOverrides({
@AttributeOverride(name = "name", column = @Column(name = "disclose_types_name")),
@AttributeOverride(name = "org", column = @Column(name = "disclose_types_org")),
@AttributeOverride(name = "addr", column = @Column(name = "disclose_types_addr")),
@AttributeOverride(name = "flag", column = @Column(name = "disclose_mode_flag")),
@AttributeOverride(name = "voice.marked", column = @Column(name = "disclose_show_voice")),
@AttributeOverride(name = "fax.marked", column = @Column(name = "disclose_show_fax")),
@AttributeOverride(name = "email.marked", column = @Column(name = "disclose_show_email"))
})
Disclose disclose;
public String getContactId() {
@@ -22,11 +22,14 @@ import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.eppcommon.PresenceMarker;
import java.util.List;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
/** The "discloseType" from {@link "http://tools.ietf.org/html/rfc5733"}. */
@Embed
@Embeddable
@XmlType(propOrder = {"name", "org", "addr", "voice", "fax", "email"})
public class Disclose extends ImmutableObject {
@@ -36,11 +39,11 @@ public class Disclose extends ImmutableObject {
List<PostalInfoChoice> addr;
PresenceMarker voice;
@Embedded PresenceMarker voice;
PresenceMarker fax;
@Embedded PresenceMarker fax;
PresenceMarker email;
@Embedded PresenceMarker email;
@XmlAttribute
Boolean flag;
@@ -21,6 +21,9 @@ import google.registry.model.Buildable;
import google.registry.model.Buildable.Overlayable;
import google.registry.model.ImmutableObject;
import java.util.Optional;
import javax.persistence.Embeddable;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlEnumValue;
@@ -29,10 +32,11 @@ import javax.xml.bind.annotation.adapters.NormalizedStringAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/**
* Implementation of both "postalInfoType" and "chgPostalInfoType" from
* {@link "http://tools.ietf.org/html/rfc5733"}.
* Implementation of both "postalInfoType" and "chgPostalInfoType" from {@link
* "http://tools.ietf.org/html/rfc5733"}.
*/
@Embed
@Embeddable
@XmlType(propOrder = {"name", "org", "address", "type"})
public class PostalInfo extends ImmutableObject implements Overlayable<PostalInfo> {
@@ -53,6 +57,7 @@ public class PostalInfo extends ImmutableObject implements Overlayable<PostalInf
@XmlElement(name = "addr")
ContactAddress address;
@Enumerated(EnumType.STRING)
@XmlAttribute
Type type;
@@ -66,6 +66,7 @@ import google.registry.model.registry.Registry;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferStatus;
import google.registry.persistence.VKey;
import google.registry.schema.replay.DatastoreAndSqlEntity;
import google.registry.util.CollectionUtils;
import java.util.HashSet;
import java.util.Objects;
@@ -76,8 +77,10 @@ import javax.annotation.Nullable;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.ElementCollection;
import javax.persistence.Embedded;
import javax.persistence.JoinTable;
import javax.persistence.Transient;
import org.joda.time.DateTime;
import org.joda.time.Interval;
@@ -105,7 +108,7 @@ import org.joda.time.Interval;
})
@ExternalMessagingName("domain")
public class DomainBase extends EppResource
implements ForeignKeyedEppResource, ResourceWithTransferData {
implements ForeignKeyedEppResource, ResourceWithTransferData, DatastoreAndSqlEntity {
/** The max number of years that a domain can be registered for, as set by ICANN policy. */
public static final int MAX_REGISTRATION_YEARS = 10;
@@ -141,7 +144,11 @@ public class DomainBase extends EppResource
*/
@Index @ElementCollection @Transient Set<Key<HostResource>> nsHosts;
@Ignore @Transient Set<VKey<HostResource>> nsHostVKeys;
@Ignore
@ElementCollection
@JoinTable(name = "DomainHost")
@Convert(converter = HostResource.VKeyHostResourceConverter.class)
Set<VKey<HostResource>> nsHostVKeys;
/**
* The union of the contacts visible via {@link #getContacts} and {@link #getRegistrant}.
@@ -18,6 +18,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import javax.persistence.Embeddable;
import javax.persistence.MappedSuperclass;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlValue;
@@ -31,17 +33,21 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
* "e164Type" type from {@link "http://tools.ietf.org/html/draft-lozano-tmch-smd"}.
*
* <blockquote>
*
* <p>"Contact telephone number structure is derived from structures defined in [ITU.E164.2005].
* Telephone numbers described in this mapping are character strings that MUST begin with a plus
* sign ("+", ASCII value 0x002B), followed by a country code defined in [ITU.E164.2005], followed
* by a dot (".", ASCII value 0x002E), followed by a sequence of digits representing the telephone
* number. An optional "x" attribute is provided to note telephone extension information."
*
* </blockquote>
*
* @see google.registry.model.contact.ContactPhoneNumber
* @see google.registry.model.mark.MarkPhoneNumber
*/
@XmlTransient
@Embeddable
@MappedSuperclass
public class PhoneNumber extends ImmutableObject {
@XmlValue
@@ -17,6 +17,7 @@ package google.registry.model.eppcommon;
import com.googlecode.objectify.annotation.Embed;
import google.registry.model.ImmutableObject;
import java.io.Serializable;
import javax.persistence.Embeddable;
import javax.xml.bind.annotation.XmlTransient;
/**
@@ -26,6 +27,7 @@ import javax.xml.bind.annotation.XmlTransient;
* {@code <foo></foo>}, and will unmarshal always to {@code <foo/>}.
*/
@Embed
@Embeddable
public class PresenceMarker extends ImmutableObject implements Serializable {
@XmlTransient
boolean marked = true;
@@ -34,22 +34,25 @@ import google.registry.model.annotations.ReportedOn;
import google.registry.model.domain.DomainBase;
import google.registry.model.transfer.TransferData;
import google.registry.persistence.VKey;
import google.registry.persistence.converter.VKeyConverter;
import java.net.InetAddress;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.ElementCollection;
import org.joda.time.DateTime;
/**
* A persistable Host resource including mutable and non-mutable fields.
*
* <p>A host's {@link TransferData} is stored on the superordinate domain. Non-subordinate hosts
* <p>A host's {@link TransferData} is stored on the superordinate domain. Non-subordinate hosts
* don't carry a full set of TransferData; all they have is lastTransferTime.
*
* @see <a href="https://tools.ietf.org/html/rfc5732">RFC 5732</a>
*/
@ReportedOn
@Entity
@javax.persistence.Entity
@ExternalMessagingName("host")
public class HostResource extends EppResource implements ForeignKeyedEppResource {
@@ -64,8 +67,7 @@ public class HostResource extends EppResource implements ForeignKeyedEppResource
String fullyQualifiedHostName;
/** IP Addresses for this host. Can be null if this is an external host. */
@Index
Set<InetAddress> inetAddresses;
@Index @ElementCollection Set<InetAddress> inetAddresses;
/** The superordinate domain of this host, or null if this is an external host. */
@Index
@@ -210,4 +212,11 @@ public class HostResource extends EppResource implements ForeignKeyedEppResource
return this;
}
}
public static class VKeyHostResourceConverter extends VKeyConverter<HostResource> {
@Override
protected Class<HostResource> getAttributeClass() {
return HostResource.class;
}
}
}
@@ -151,11 +151,14 @@ public class ObjectifyService {
String kind = Key.getKind(clazz);
boolean registered = factory().getMetadata(kind) != null;
if (clazz.isAnnotationPresent(Entity.class)) {
// Objectify silently ignores re-registrations for a given kind string, even if the classes
// being registered are distinct. Throw an exception if that would happen here.
checkState(!registered,
// Objectify silently replaces current registration for a given kind string when a different
// class is registered again for this kind. For simplicity's sake, throw an exception on any
// re-registration.
checkState(
!registered,
"Kind '%s' already registered, cannot register new @Entity %s",
kind, clazz.getCanonicalName());
kind,
clazz.getCanonicalName());
} else if (clazz.isAnnotationPresent(EntitySubclass.class)) {
// Ensure that any @EntitySubclass classes have also had their parent @Entity registered,
// which Objectify nominally requires but doesn't enforce in 4.x (though it may in 5.x).
@@ -73,6 +73,7 @@ import google.registry.model.annotations.ReportedOn;
import google.registry.model.common.EntityGroupRoot;
import google.registry.model.registrar.Registrar.BillingAccountEntry.CurrencyMapper;
import google.registry.model.registry.Registry;
import google.registry.schema.replay.DatastoreAndSqlEntity;
import google.registry.util.CidrAddressBlock;
import java.security.cert.CertificateParsingException;
import java.util.Comparator;
@@ -107,11 +108,12 @@ import org.joda.time.DateTime;
columnList = "ianaIdentifier",
name = "registrar_iana_identifier_idx"),
})
public class Registrar extends ImmutableObject implements Buildable, Jsonifiable {
public class Registrar extends ImmutableObject
implements Buildable, Jsonifiable, DatastoreAndSqlEntity {
/** Represents the type of a registrar entity. */
public enum Type {
/** A real-world, third-party registrar. Should have non-null IANA and billing IDs. */
/** A real-world, third-party registrar. Should have non-null IANA and billing IDs. */
REAL(Objects::nonNull),
/**
@@ -19,6 +19,7 @@ import static com.google.common.base.Strings.emptyToNull;
import static google.registry.util.DomainNameUtils.canonicalizeDomainName;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.net.InternetDomainName;
import com.googlecode.objectify.annotation.Id;
import google.registry.model.Buildable.GenericBuilder;
import google.registry.model.ImmutableObject;
@@ -83,6 +84,13 @@ public abstract class DomainLabelEntry<T extends Comparable<?>, D extends Domain
"Label '%s' must be in puny-coded, lower-case form",
getInstance().label);
checkArgumentNotNull(getInstance().getValue(), "Value must be specified");
// Verify that the label creates a valid SLD if we add a TLD to the end of it.
// We require that the label is not already a full domain name including a dot.
// Domain name validation is tricky, so let InternetDomainName handle it for us.
checkArgument(
InternetDomainName.from(getInstance().label + ".tld").parts().size() == 2,
"Label %s must not be a multi-level domain name",
getInstance().label);
return super.build();
}
}
@@ -16,7 +16,6 @@ package google.registry.model.server;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.DateTimeUtils.isAtOrAfter;
@@ -29,7 +28,6 @@ import com.googlecode.objectify.annotation.Id;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.NotBackedUp;
import google.registry.model.annotations.NotBackedUp.Reason;
import google.registry.schema.server.LockDao;
import google.registry.util.RequestStatusChecker;
import google.registry.util.RequestStatusCheckerImpl;
import java.io.Serializable;
@@ -197,22 +195,6 @@ public class Lock extends ImmutableObject implements Serializable {
// Checking if an unexpired lock still exists - if so, the lock can't be acquired.
Lock lock = ofy().load().type(Lock.class).id(lockId).now();
try {
jpaTm()
.transact(
() -> {
Optional<google.registry.schema.server.Lock> cloudSqlLockOptional;
if (tld == null) {
cloudSqlLockOptional = LockDao.load(resourceName);
} else {
cloudSqlLockOptional = LockDao.load(resourceName, tld);
}
LockDao.compare(Optional.ofNullable(lock), cloudSqlLockOptional);
});
} catch (Exception e) {
logger.atSevere().withCause(e).log(
"Issue loading and comparing lock from Cloud SQL");
}
if (lock != null) {
logger.atInfo().log(
"Loaded existing lock: %s for request: %s", lock.lockId, lock.requestLogId);
@@ -237,35 +219,6 @@ public class Lock extends ImmutableObject implements Serializable {
// don't need to be backed up.
ofy().saveWithoutBackup().entity(newLock);
// create and save the lock to Cloud SQL
try {
jpaTm()
.transact(
() -> {
google.registry.schema.server.Lock cloudSqlLock;
if (tld == null) {
cloudSqlLock =
google.registry.schema.server.Lock.createGlobal(
resourceName,
requestStatusChecker.getLogId(),
now,
leaseLength);
} else {
cloudSqlLock =
google.registry.schema.server.Lock.create(
resourceName,
tld,
requestStatusChecker.getLogId(),
now,
leaseLength);
}
LockDao.save(cloudSqlLock);
});
} catch (Exception e) {
logger.atSevere().withCause(e).log(
"Error saving lock to Cloud SQL: %s", newLock);
}
return AcquireResult.create(now, lock, newLock, lockState);
});
@@ -284,44 +237,12 @@ public class Lock extends ImmutableObject implements Serializable {
// this can happen if release() is called around the expiration time and the lock
// expires underneath us.
Lock loadedLock = ofy().load().type(Lock.class).id(lockId).now();
try {
jpaTm()
.transact(
() -> {
Optional<google.registry.schema.server.Lock> cloudSqlLockOptional;
if (tld == null) {
cloudSqlLockOptional = LockDao.load(resourceName);
} else {
cloudSqlLockOptional = LockDao.load(resourceName, tld);
}
LockDao.compare(Optional.ofNullable(loadedLock), cloudSqlLockOptional);
});
} catch (Exception e) {
logger.atSevere().withCause(e).log(
"Issue loading and comparing lock from Cloud SQL");
}
if (Lock.this.equals(loadedLock)) {
// Use noBackupOfy() so that we don't create a commit log entry for deleting the
// lock.
logger.atInfo().log("Deleting lock: %s", lockId);
ofy().deleteWithoutBackup().entity(Lock.this);
// Remove the lock from Cloud SQL
try {
jpaTm()
.transact(
() -> {
if (tld == null) {
LockDao.delete(resourceName);
} else {
LockDao.delete(resourceName, tld);
}
});
} catch (Exception e) {
logger.atSevere().withCause(e).log(
"Error deleting lock from Cloud SQL: %s", loadedLock);
}
lockMetrics.recordRelease(
resourceName, tld, new Duration(acquiredTime, tm().getTransactionTime()));
} else {
@@ -0,0 +1,200 @@
// Copyright 2020 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.persistence;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.Stream;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.MappedSuperclass;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
import javax.persistence.Transient;
/**
* A listener class to invoke entity callbacks in cases where Hibernate doesn't invoke the callback
* as expected.
*
* <p>JPA defines a few annotations, e.g. {@link PostLoad}, that we can use for the application to
* react to certain events that occur inside the persistence mechanism. However, Hibernate only
* supports a few basic use cases, e.g. defining a {@link PostLoad} method directly in an {@link
* javax.persistence.Entity} class or in an {@link Embeddable} class. If the annotated method is
* defined in an {@link Embeddable} class that is a property of another {@link Embeddable} class, or
* it is defined in a parent class of the {@link Embeddable} class, Hibernate doesn't invoke it.
*
* <p>This listener is added in core/src/main/resources/META-INF/orm.xml as a default entity
* listener whose annotated methods will be invoked by Hibernate when corresponding events happen.
* For example, {@link EntityCallbacksListener#prePersist} will be invoked before the entity is
* persisted to the database, then it will recursively invoke any other {@link PrePersist} method
* that should be invoked but not handled by Hibernate due to the bug.
*
* @see <a
* href="https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#events-jpa-callbacks">JPA
* Callbacks</a>
* @see <a href="https://hibernate.atlassian.net/browse/HHH-13316">HHH-13316</a>
*/
public class EntityCallbacksListener {
@PrePersist
void prePersist(Object entity) {
EntityCallbackExecutor.create(PrePersist.class).execute(entity, entity.getClass());
}
@PreRemove
void preRemove(Object entity) {
EntityCallbackExecutor.create(PreRemove.class).execute(entity, entity.getClass());
}
@PostPersist
void postPersist(Object entity) {
EntityCallbackExecutor.create(PostPersist.class).execute(entity, entity.getClass());
}
@PostRemove
void postRemove(Object entity) {
EntityCallbackExecutor.create(PostRemove.class).execute(entity, entity.getClass());
}
@PreUpdate
void preUpdate(Object entity) {
EntityCallbackExecutor.create(PreUpdate.class).execute(entity, entity.getClass());
}
@PostUpdate
void postUpdate(Object entity) {
EntityCallbackExecutor.create(PostUpdate.class).execute(entity, entity.getClass());
}
@PostLoad
void postLoad(Object entity) {
EntityCallbackExecutor.create(PostLoad.class).execute(entity, entity.getClass());
}
private static class EntityCallbackExecutor {
Class<? extends Annotation> callbackType;
private EntityCallbackExecutor(Class<? extends Annotation> callbackType) {
this.callbackType = callbackType;
}
private static EntityCallbackExecutor create(Class<? extends Annotation> callbackType) {
return new EntityCallbackExecutor(callbackType);
}
/**
* Executes eligible callbacks in {@link Embedded} properties recursively.
*
* @param entity the Java object of the entity class
* @param entityType either the type of the entity or an ancestor type
*/
private void execute(Object entity, Class<?> entityType) {
Class<?> parentType = entityType.getSuperclass();
if (parentType != null && parentType.isAnnotationPresent(MappedSuperclass.class)) {
execute(entity, parentType);
}
findEmbeddedProperties(entity, entityType)
.forEach(
normalEmbedded -> {
// For each normal embedded property, we don't execute its callback method because
// it is handled by Hibernate. However, for the embedded property defined in the
// entity's parent class, we need to treat it as a nested embedded property and
// invoke its callback function.
if (entity.getClass().equals(entityType)) {
executeCallbackForNormalEmbeddedProperty(
normalEmbedded, normalEmbedded.getClass());
} else {
executeCallbackForNestedEmbeddedProperty(
normalEmbedded, normalEmbedded.getClass());
}
});
}
private void executeCallbackForNestedEmbeddedProperty(
Object nestedEmbeddedObject, Class<?> nestedEmbeddedType) {
Class<?> parentType = nestedEmbeddedType.getSuperclass();
if (parentType != null && parentType.isAnnotationPresent(MappedSuperclass.class)) {
executeCallbackForNestedEmbeddedProperty(nestedEmbeddedObject, parentType);
}
findEmbeddedProperties(nestedEmbeddedObject, nestedEmbeddedType)
.forEach(
embeddedProperty ->
executeCallbackForNestedEmbeddedProperty(
embeddedProperty, embeddedProperty.getClass()));
for (Method method : nestedEmbeddedType.getDeclaredMethods()) {
if (method.isAnnotationPresent(callbackType)) {
invokeMethod(method, nestedEmbeddedObject);
}
}
}
private void executeCallbackForNormalEmbeddedProperty(
Object normalEmbeddedObject, Class<?> normalEmbeddedType) {
Class<?> parentType = normalEmbeddedType.getSuperclass();
if (parentType != null && parentType.isAnnotationPresent(MappedSuperclass.class)) {
executeCallbackForNormalEmbeddedProperty(normalEmbeddedObject, parentType);
}
findEmbeddedProperties(normalEmbeddedObject, normalEmbeddedType)
.forEach(
embeddedProperty ->
executeCallbackForNestedEmbeddedProperty(
embeddedProperty, embeddedProperty.getClass()));
}
private Stream<Object> findEmbeddedProperties(Object object, Class<?> clazz) {
return Arrays.stream(clazz.getDeclaredFields())
.filter(field -> !field.isAnnotationPresent(Transient.class))
.filter(
field ->
field.isAnnotationPresent(Embedded.class)
|| field.getType().isAnnotationPresent(Embeddable.class))
.filter(field -> !Modifier.isStatic(field.getModifiers()))
.map(field -> getFieldObject(field, object))
.filter(Objects::nonNull);
}
private static Object getFieldObject(Field field, Object object) {
field.setAccessible(true);
try {
return field.get(object);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private static void invokeMethod(Method method, Object object) {
method.setAccessible(true);
try {
method.invoke(object);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}
@@ -17,9 +17,7 @@ package google.registry.persistence;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.config.RegistryConfig.getHibernateConnectionIsolation;
import static google.registry.config.RegistryConfig.getHibernateHikariConnectionTimeout;
import static google.registry.config.RegistryConfig.getHibernateHikariIdleTimeout;
import static google.registry.config.RegistryConfig.getHibernateHikariMaximumPoolSize;
import static google.registry.config.RegistryConfig.getHibernateHikariMinimumIdle;
import static google.registry.config.RegistryConfig.getHibernateLogSqlQueries;
import com.google.api.client.auth.oauth2.Credential;
@@ -50,10 +48,7 @@ public class PersistenceModule {
// This name must be the same as the one defined in persistence.xml.
public static final String PERSISTENCE_UNIT_NAME = "nomulus";
public static final String HIKARI_CONNECTION_TIMEOUT = "hibernate.hikari.connectionTimeout";
public static final String HIKARI_MINIMUM_IDLE = "hibernate.hikari.minimumIdle";
public static final String HIKARI_MAXIMUM_POOL_SIZE = "hibernate.hikari.maximumPoolSize";
public static final String HIKARI_IDLE_TIMEOUT = "hibernate.hikari.idleTimeout";
public static final String HIKARI_DS_SOCKET_FACTORY = "hibernate.hikari.dataSource.socketFactory";
public static final String HIKARI_DS_CLOUD_SQL_INSTANCE =
"hibernate.hikari.dataSource.cloudSqlInstance";
@@ -79,9 +74,7 @@ public class PersistenceModule {
properties.put(Environment.ISOLATION, getHibernateConnectionIsolation());
properties.put(Environment.SHOW_SQL, getHibernateLogSqlQueries());
properties.put(HIKARI_CONNECTION_TIMEOUT, getHibernateHikariConnectionTimeout());
properties.put(HIKARI_MINIMUM_IDLE, getHibernateHikariMinimumIdle());
properties.put(HIKARI_MAXIMUM_POOL_SIZE, getHibernateHikariMaximumPoolSize());
properties.put(HIKARI_IDLE_TIMEOUT, getHibernateHikariIdleTimeout());
properties.put(Environment.DIALECT, NomulusPostgreSQLDialect.class.getName());
return properties.build();
}
@@ -0,0 +1,36 @@
// Copyright 2020 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.persistence.converter;
import google.registry.model.contact.Disclose.PostalInfoChoice;
import google.registry.model.contact.PostalInfo;
import java.util.List;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/** JPA {@link AttributeConverter} for storing/retrieving {@link List < PostalInfoChoice >}. */
@Converter(autoApply = true)
public class PostalInfoChoiceListConverter extends StringListConverterBase<PostalInfoChoice> {
@Override
String toString(PostalInfoChoice element) {
return element.getType().name();
}
@Override
PostalInfoChoice fromString(String value) {
return PostalInfoChoice.create(PostalInfo.Type.valueOf(value));
}
}
@@ -0,0 +1,37 @@
// Copyright 2020 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.persistence.converter;
import google.registry.persistence.VKey;
import javax.annotation.Nullable;
import javax.persistence.AttributeConverter;
/** Converts VKey to a string column. */
public abstract class VKeyConverter<T> implements AttributeConverter<VKey<T>, String> {
@Override
@Nullable
public String convertToDatabaseColumn(@Nullable VKey<T> attribute) {
return attribute == null ? null : (String) attribute.getSqlKey();
}
@Override
@Nullable
public VKey<T> convertToEntityAttribute(@Nullable String dbData) {
return dbData == null ? null : VKey.createSql(getAttributeClass(), dbData);
}
/** Returns the class of the attribute. */
protected abstract Class<T> getAttributeClass();
}
@@ -0,0 +1,31 @@
// Copyright 2020 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.schema.replay;
import com.google.common.collect.ImmutableList;
/** An entity that has the same Java object representation in SQL and Datastore. */
public interface DatastoreAndSqlEntity extends DatastoreEntity, SqlEntity {
@Override
default ImmutableList<DatastoreEntity> toDatastoreEntities() {
return ImmutableList.of(this);
}
@Override
default ImmutableList<SqlEntity> toSqlEntities() {
return ImmutableList.of(this);
}
}
@@ -0,0 +1,30 @@
// Copyright 2020 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.schema.replay;
import com.google.common.collect.ImmutableList;
/**
* An object that can be stored in Datastore and serialized using Objectify's {@link
* com.googlecode.objectify.cmd.Saver#toEntity(Object)} code.
*
* <p>This is used when replaying {@link google.registry.model.ofy.CommitLogManifest}s to import
* transactions and data into the secondary SQL store during the first, Datastore-primary, phase of
* the migration.
*/
public interface DatastoreEntity {
ImmutableList<SqlEntity> toSqlEntities();
}
@@ -0,0 +1,29 @@
// Copyright 2020 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.schema.replay;
import com.google.common.collect.ImmutableList;
/**
* An object that can be stored in Cloud SQL using {@link
* javax.persistence.EntityManager#persist(Object)}
*
* <p>This will be used when replaying SQL transactions into Datastore, during the second,
* SQL-primary, phase of the migration from Datastore to SQL.
*/
public interface SqlEntity {
ImmutableList<DatastoreEntity> toDatastoreEntities();
}
@@ -14,6 +14,8 @@
package google.registry.tools;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.Multimap;
@@ -39,6 +41,11 @@ final class CheckDomainCommand extends NonMutatingEppToolCommand {
required = true)
private List<String> mainParameters;
@Parameter(
names = {"-t", "--token"},
description = "Allocation token to use in the command, if desired")
private String allocationToken;
@Inject
@Config("registryAdminClientId")
String registryAdminClientId;
@@ -53,7 +60,11 @@ final class CheckDomainCommand extends NonMutatingEppToolCommand {
Multimap<String, String> domainNameMap = validateAndGroupDomainNamesByTld(mainParameters);
for (Collection<String> values : domainNameMap.asMap().values()) {
setSoyTemplate(DomainCheckSoyInfo.getInstance(), DomainCheckSoyInfo.DOMAINCHECK);
addSoyRecord(clientId, new SoyMapData("domainNames", values));
SoyMapData soyMapData = new SoyMapData("domainNames", values);
if (!isNullOrEmpty(allocationToken)) {
soyMapData.put("allocationToken", allocationToken);
}
addSoyRecord(clientId, soyMapData);
}
}
}
@@ -18,9 +18,20 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import java.io.File;
import java.util.function.Predicate;
/** Compare two database backups. */
/**
* Compares two Datastore backups in V3 format on local file system. This is for use in tests and
* experiments with small data sizes.
*
* <p>This utility only supports the current Datastore backup format (version 3). A backup is a
* two-level directory hierarchy with data files in level-db format (output-*) and Datastore
* metadata files (*.export_metadata).
*/
class CompareDbBackups {
private static final String DS_V3_BACKUP_FILE_PREFIX = "output-";
private static final Predicate<File> DATA_FILE_MATCHER =
file -> file.isFile() && file.getName().startsWith(DS_V3_BACKUP_FILE_PREFIX);
public static void main(String[] args) {
if (args.length != 2) {
@@ -29,9 +40,13 @@ class CompareDbBackups {
}
ImmutableSet<ComparableEntity> entities1 =
new RecordAccumulator().readDirectory(new File(args[0])).getComparableEntitySet();
new RecordAccumulator()
.readDirectory(new File(args[0]), DATA_FILE_MATCHER)
.getComparableEntitySet();
ImmutableSet<ComparableEntity> entities2 =
new RecordAccumulator().readDirectory(new File(args[1])).getComparableEntitySet();
new RecordAccumulator()
.readDirectory(new File(args[1]), DATA_FILE_MATCHER)
.getComparableEntitySet();
// Calculate the entities added and removed.
SetView<ComparableEntity> added = Sets.difference(entities2, entities1);
@@ -54,6 +69,10 @@ class CompareDbBackups {
System.out.println(entity);
}
}
if (added.isEmpty() && removed.isEmpty()) {
System.out.printf("\nBoth sets have the same %d entities.\n", entities1.size());
}
}
/** Print out multi-line text in a pretty ASCII header frame. */
@@ -153,8 +153,8 @@ public final class DomainLockUtils {
/**
* Creates and applies a lock in one step.
*
* <p>This should only be used for admin actions, e.g. Nomulus tool commands or relocks.
* Note: in the case of relocks, isAdmin is determined by the previous lock.
* <p>This should only be used for admin actions, e.g. Nomulus tool commands or relocks. Note: in
* the case of relocks, isAdmin is determined by the previous lock.
*/
public RegistryLock administrativelyApplyLock(
String domainName, String registrarId, @Nullable String registrarPocId, boolean isAdmin) {
@@ -175,7 +175,7 @@ public final class DomainLockUtils {
/**
* Creates and applies an unlock in one step.
*
*
* <p>This should only be used for admin actions, e.g. Nomulus tool commands.
*/
public RegistryLock administrativelyApplyUnlock(
@@ -20,17 +20,18 @@ import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.function.Predicate;
/** Utility class that accumulates Entity records from level db files. */
/** Accumulates Entity records from level db files under a directory hierarchy. */
class RecordAccumulator {
private final LevelDbLogReader reader = new LevelDbLogReader();
/** Recursively reads all records in the directory. */
public final RecordAccumulator readDirectory(File dir) {
public final RecordAccumulator readDirectory(File dir, Predicate<File> fileMatcher) {
for (File child : dir.listFiles()) {
if (child.isDirectory()) {
readDirectory(child);
} else if (child.isFile()) {
readDirectory(child, fileMatcher);
} else if (fileMatcher.test(child)) {
try {
reader.readFrom(new FileInputStream(child));
} catch (IOException e) {
@@ -132,10 +132,8 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
jcommander.parse(args);
} catch (ParameterException e) {
// If we failed to fully parse the command but at least found a valid command name, show only
// the usage for that command. Otherwise, show full usage. Either way, rethrow the error.
if (jcommander.getParsedCommand() == null) {
jcommander.usage();
} else {
// the usage for that command.
if (jcommander.getParsedCommand() != null) {
jcommander.usage(jcommander.getParsedCommand());
}
// Don't rethrow if we said: nomulus command --help
@@ -144,8 +142,13 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
}
throw e;
}
if (showAllCommands) {
String parsedCommand = jcommander.getParsedCommand();
// Show the list of all commands either if requested or if no subcommand name was specified
// (which does not throw a ParameterException parse error above).
if (showAllCommands || parsedCommand == null) {
if (parsedCommand == null) {
System.out.println("The list of available subcommands is:");
}
commands.keySet().forEach(System.out::println);
return;
}
@@ -161,8 +164,7 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
// retrieving the first (and, by virtue of our usage, only) object from it.
Command command =
(Command)
Iterables.getOnlyElement(
jcommander.getCommands().get(jcommander.getParsedCommand()).getObjects());
Iterables.getOnlyElement(jcommander.getCommands().get(parsedCommand).getObjects());
loggingParams.configureLogging(); // Must be called after parameters are parsed.
try {
@@ -15,6 +15,7 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.util.CollectionUtils.findDuplicates;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
@@ -37,6 +38,13 @@ import org.joda.time.format.DateTimeFormatter;
@Parameters(separators = " =", commandDescription = "Renew domain(s) via EPP.")
final class RenewDomainCommand extends MutatingEppToolCommand {
@Parameter(
names = {"-c", "--client"},
description =
"The registrar to execute as and bill the renewal to; otherwise each domain's sponsoring"
+ " registrar. Renewals by non-sponsoring registrars require --superuser as well.")
String clientId;
@Parameter(
names = {"-p", "--period"},
description = "Number of years to renew the registration for (defaults to 1).")
@@ -63,7 +71,7 @@ final class RenewDomainCommand extends MutatingEppToolCommand {
setSoyTemplate(RenewDomainSoyInfo.getInstance(), RenewDomainSoyInfo.RENEWDOMAIN);
DomainBase domain = domainOptional.get();
addSoyRecord(
domain.getCurrentSponsorClientId(),
isNullOrEmpty(clientId) ? domain.getCurrentSponsorClientId() : clientId,
new SoyMapData(
"domainName", domain.getFullyQualifiedDomainName(),
"expirationDate", domain.getRegistrationExpirationTime().toString(DATE_FORMATTER),
@@ -186,9 +186,7 @@ public final class RegistrarFormFields {
.build();
public static final FormField<String, String> REGISTRY_LOCK_EMAIL_ADDRESS_FIELD =
FormFields.EMAIL
.asBuilderNamed("registryLockEmailAddress")
.build();
FormFields.EMAIL.asBuilderNamed("registryLockEmailAddress").build();
public static final FormField<Boolean, Boolean> CONTACT_VISIBLE_IN_WHOIS_AS_ADMIN_FIELD =
FormField.named("visibleInWhoisAsAdmin", Boolean.class)
+7
View File
@@ -10,4 +10,11 @@
<basic name="amount" access="FIELD"/>
</attributes>
</embeddable>
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener class="google.registry.persistence.EntityCallbacksListener" />
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>
@@ -19,7 +19,9 @@
* Move tests to another (sub)project. This is not a big problem, but feels unnatural.
* Use Hibernate's ServiceRegistry for bootstrapping (not JPA-compliant)
-->
<class>google.registry.model.contact.ContactResource</class>
<class>google.registry.model.domain.DomainBase</class>
<class>google.registry.model.host.HostResource</class>
<class>google.registry.model.registrar.Registrar</class>
<class>google.registry.model.registrar.RegistrarContact</class>
<class>google.registry.schema.domain.RegistryLock</class>
@@ -40,11 +42,13 @@
<class>google.registry.persistence.converter.CurrencyUnitConverter</class>
<class>google.registry.persistence.converter.DateTimeConverter</class>
<class>google.registry.persistence.converter.DurationConverter</class>
<class>google.registry.persistence.converter.PostalInfoChoiceListConverter</class>
<class>google.registry.persistence.converter.RegistrarPocSetConverter</class>
<class>google.registry.persistence.converter.StatusValueSetConverter</class>
<class>google.registry.persistence.converter.StringListConverter</class>
<class>google.registry.persistence.converter.StringSetConverter</class>
<class>google.registry.persistence.converter.UpdateAutoTimestampConverter</class>
<class>google.registry.persistence.converter.VKeyConverter</class>
<class>google.registry.persistence.converter.ZonedDateTimeConverter</class>
<!-- TODO(weiminyu): check out application-layer validation. -->
@@ -19,6 +19,7 @@
*/
{template .domaincheck stricthtml="false"}
{@param domainNames: list<string>}
{@param? allocationToken: string|null}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
@@ -39,6 +40,12 @@
</fee:domain>
{/for}
</fee:check>
{if isNonnull($allocationToken)}
<allocationToken:allocationToken
xmlns:allocationToken="urn:ietf:params:xml:ns:allocationToken-1.0">
{$allocationToken}
</allocationToken:allocationToken>
{/if}
</extension>
<clTRID>RegistryTool</clTRID>
</command>
@@ -36,6 +36,9 @@ public class DumpGoldenSchemaCommand extends PostgresqlCommand {
// The mount point in the container.
private static final String CONTAINER_MOUNT_POINT = "/tmp/pg_dump.out";
// Temporary workaround to fix permission issues on certain Linux distro (e. g. Arch Linux).
private static final String CONTAINER_MOUNT_POINT_TMP = "/tmp/pg_dump.tmp";
@Parameter(
names = {"--output", "-o"},
description = "Output file",
@@ -61,6 +64,11 @@ public class DumpGoldenSchemaCommand extends PostgresqlCommand {
if (result.getExitCode() != 0) {
throw new RuntimeException(result.toString());
}
result =
postgresContainer.execInContainer("cp", CONTAINER_MOUNT_POINT_TMP, CONTAINER_MOUNT_POINT);
if (result.getExitCode() != 0) {
throw new RuntimeException(result.toString());
}
}
@Override
@@ -79,7 +87,7 @@ public class DumpGoldenSchemaCommand extends PostgresqlCommand {
"-U",
username,
"-f",
CONTAINER_MOUNT_POINT,
CONTAINER_MOUNT_POINT_TMP,
"--schema-only",
"--no-owner",
"--no-privileges",
@@ -50,7 +50,11 @@ import org.junit.runners.JUnit4;
public class ExportCommitLogDiffActionTest {
@Rule
public final AppEngineRule appEngine = AppEngineRule.builder().withDatastoreAndCloudSql().build();
public final AppEngineRule appEngine =
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.withOfyTestEntities(TestObject.class)
.build();
/** Local GCS service available for testing. */
private final GcsService gcsService = GcsServiceFactory.createGcsService();
@@ -71,7 +71,11 @@ public class RestoreCommitLogsActionTest {
final GcsService gcsService = createGcsService();
@Rule
public final AppEngineRule appEngine = AppEngineRule.builder().withDatastoreAndCloudSql().build();
public final AppEngineRule appEngine =
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.withOfyTestEntities(TestObject.class)
.build();
@Before
public void init() {
@@ -153,6 +153,8 @@ public class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, D
doSuccessfulTest(
responseFilename,
renewalYears,
"TheRegistrar",
UserPrivileges.NORMAL,
substitutions,
Money.of(USD, 11).multipliedBy(renewalYears));
}
@@ -160,13 +162,16 @@ public class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, D
private void doSuccessfulTest(
String responseFilename,
int renewalYears,
String renewalClientId,
UserPrivileges userPrivileges,
Map<String, String> substitutions,
Money totalRenewCost)
throws Exception {
assertTransactionalFlow(true);
DateTime currentExpiration = reloadResourceByForeignKey().getRegistrationExpirationTime();
DateTime newExpiration = currentExpiration.plusYears(renewalYears);
runFlowAssertResponse(loadFile(responseFilename, substitutions));
runFlowAssertResponse(
CommitMode.LIVE, userPrivileges, loadFile(responseFilename, substitutions));
DomainBase domain = reloadResourceByForeignKey();
HistoryEntry historyEntryDomainRenew =
getOnlyHistoryEntryOfType(domain, HistoryEntry.Type.DOMAIN_RENEW);
@@ -183,13 +188,13 @@ public class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, D
.and()
.hasLastEppUpdateTime(clock.nowUtc())
.and()
.hasLastEppUpdateClientId("TheRegistrar");
.hasLastEppUpdateClientId(renewalClientId);
assertAboutHistoryEntries().that(historyEntryDomainRenew).hasPeriodYears(renewalYears);
BillingEvent.OneTime renewBillingEvent =
new BillingEvent.OneTime.Builder()
.setReason(Reason.RENEW)
.setTargetId(getUniqueIdFromCommand())
.setClientId("TheRegistrar")
.setClientId(renewalClientId)
.setCost(totalRenewCost)
.setPeriodYears(renewalYears)
.setEventTime(clock.nowUtc())
@@ -233,7 +238,7 @@ public class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, D
GracePeriod.create(
GracePeriodStatus.RENEW,
clock.nowUtc().plus(Registry.get("tld").getRenewGracePeriodLength()),
"TheRegistrar",
renewalClientId,
null),
renewBillingEvent));
}
@@ -256,6 +261,19 @@ public class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, D
ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2005-04-03T22:00:00.0Z"));
}
@Test
public void testSuccess_recurringClientIdIsSame_whenSuperuserOverridesRenewal() throws Exception {
persistDomain();
setClientIdForFlow("NewRegistrar");
doSuccessfulTest(
"domain_renew_response.xml",
5,
"NewRegistrar",
UserPrivileges.SUPERUSER,
ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2005-04-03T22:00:00.0Z"),
Money.of(USD, 55));
}
@Test
public void testSuccess_customLogicFee() throws Exception {
// The "costly-renew" domain has an additional RENEW fee of 100 from custom logic on top of the
@@ -269,7 +287,13 @@ public class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, D
"FEE", "111.00");
setEppInput("domain_renew_fee.xml", customFeeMap);
persistDomain();
doSuccessfulTest("domain_renew_response_fee.xml", 1, customFeeMap, Money.of(USD, 111));
doSuccessfulTest(
"domain_renew_response_fee.xml",
1,
"TheRegistrar",
UserPrivileges.NORMAL,
customFeeMap,
Money.of(USD, 111));
}
@Test
@@ -687,7 +711,7 @@ public class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, D
@Test
public void testFailure_unauthorizedClient() throws Exception {
sessionMetadata.setClientId("NewRegistrar");
setClientIdForFlow("NewRegistrar");
persistActiveDomain(getUniqueIdFromCommand());
EppException thrown = assertThrows(ResourceNotOwnedException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
@@ -695,7 +719,7 @@ public class DomainRenewFlowTest extends ResourceFlowTestCase<DomainRenewFlow, D
@Test
public void testSuccess_superuserUnauthorizedClient() throws Exception {
sessionMetadata.setClientId("NewRegistrar");
setClientIdForFlow("NewRegistrar");
persistDomain();
runFlowAssertResponse(
CommitMode.LIVE,
@@ -19,12 +19,10 @@ import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.joda.time.DateTimeZone.UTC;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.annotation.Entity;
import google.registry.model.common.CrossTldSingleton;
import google.registry.testing.AppEngineRule;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -35,19 +33,18 @@ import org.junit.runners.JUnit4;
public class CreateAutoTimestampTest {
@Rule
public final AppEngineRule appEngine = AppEngineRule.builder().withDatastoreAndCloudSql().build();
public final AppEngineRule appEngine =
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.withOfyTestEntities(TestObject.class)
.build();
/** Timestamped class. */
@Entity
@Entity(name = "CatTestEntity")
public static class TestObject extends CrossTldSingleton {
CreateAutoTimestamp createTime = CreateAutoTimestamp.create(null);
}
@Before
public void before() {
ObjectifyService.register(TestObject.class);
}
private TestObject reload() {
return ofy().load().entity(new TestObject()).now();
}
@@ -42,22 +42,33 @@ import java.util.Set;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Rule;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Base class of all unit tests for entities which are persisted to Datastore via Objectify. */
@RunWith(JUnit4.class)
public abstract class EntityTestCase {
protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC));
@Rule
public final AppEngineRule appEngine =
AppEngineRule.builder().withDatastoreAndCloudSql().withClock(fakeClock).build();
@Rule @RegisterExtension public final AppEngineRule appEngine;
@Rule public InjectRule inject = new InjectRule();
@Rule @RegisterExtension public InjectRule inject = new InjectRule();
protected EntityTestCase() {
this(false);
}
protected EntityTestCase(boolean enableJpaEntityCheck) {
appEngine =
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.enableJpaEntityCoverageCheck(enableJpaEntityCheck)
.withClock(fakeClock)
.build();
}
@Before
@BeforeEach
public void injectClock() {
inject.setStaticField(Ofy.class, "clock", fakeClock);
}
@@ -27,7 +27,6 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import google.registry.testing.AppEngineRule;
@@ -39,7 +38,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -50,12 +48,11 @@ import org.junit.runners.JUnit4;
public class ImmutableObjectTest {
@Rule
public final AppEngineRule appEngine = AppEngineRule.builder().withDatastoreAndCloudSql().build();
@Before
public void register() {
ObjectifyService.register(ValueObject.class);
}
public final AppEngineRule appEngine =
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.withOfyTestEntities(ValueObject.class)
.build();
/** Simple subclass of ImmutableObject. */
public static class SimpleObject extends ImmutableObject {
@@ -19,12 +19,10 @@ import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static org.joda.time.DateTimeZone.UTC;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.annotation.Entity;
import google.registry.model.common.CrossTldSingleton;
import google.registry.testing.AppEngineRule;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -35,19 +33,18 @@ import org.junit.runners.JUnit4;
public class UpdateAutoTimestampTest {
@Rule
public final AppEngineRule appEngine = AppEngineRule.builder().withDatastoreAndCloudSql().build();
public final AppEngineRule appEngine =
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.withOfyTestEntities(TestObject.class)
.build();
/** Timestamped class. */
@Entity
@Entity(name = "UatTestEntity")
public static class TestObject extends CrossTldSingleton {
UpdateAutoTimestamp updateTime = UpdateAutoTimestamp.create(null);
}
@Before
public void before() {
ObjectifyService.register(TestObject.class);
}
private TestObject reload() {
return ofy().load().entity(new TestObject()).now();
}
@@ -17,10 +17,13 @@ package google.registry.model.contact;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.ContactResourceSubject.assertAboutContacts;
import static google.registry.testing.DatastoreHelper.cloneAndSetAutoTimestamps;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.SqlHelper.assertThrowForeignKeyViolation;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static org.junit.Assert.assertThrows;
@@ -37,87 +40,119 @@ import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppcommon.Trid;
import google.registry.model.transfer.TransferData;
import google.registry.model.transfer.TransferStatus;
import org.junit.Before;
import org.junit.Test;
import google.registry.persistence.VKey;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link ContactResource}. */
public class ContactResourceTest extends EntityTestCase {
ContactResource originalContact;
ContactResource contactResource;
@Before
public ContactResourceTest() {
super(true);
}
@BeforeEach
public void setUp() {
createTld("foobar");
originalContact =
new ContactResource.Builder()
.setContactId("contact_id")
.setRepoId("1-FOOBAR")
.setCreationClientId("registrar1")
.setLastEppUpdateTime(fakeClock.nowUtc())
.setLastEppUpdateClientId("registrar2")
.setLastTransferTime(fakeClock.nowUtc())
.setPersistedCurrentSponsorClientId("registrar3")
.setLocalizedPostalInfo(
new PostalInfo.Builder()
.setType(Type.LOCALIZED)
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("111 8th Ave", "4th Floor"))
.setCity("New York")
.setState("NY")
.setZip("10011")
.setCountryCode("US")
.build())
.build())
.setInternationalizedPostalInfo(
new PostalInfo.Builder()
.setType(Type.INTERNATIONALIZED)
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("111 8th Ave", "4th Floor"))
.setCity("New York")
.setState("NY")
.setZip("10011")
.setCountryCode("US")
.build())
.build())
.setVoiceNumber(new ContactPhoneNumber.Builder().setPhoneNumber("867-5309").build())
.setFaxNumber(
new ContactPhoneNumber.Builder()
.setPhoneNumber("867-5309")
.setExtension("1000")
.build())
.setEmailAddress("jenny@example.com")
.setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("passw0rd")))
.setDisclose(
new Disclose.Builder()
.setVoice(new PresenceMarker())
.setEmail(new PresenceMarker())
.setFax(new PresenceMarker())
.setFlag(true)
.setAddrs(ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED)))
.setNames(ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED)))
.setOrgs(ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED)))
.build())
.setStatusValues(ImmutableSet.of(StatusValue.OK))
.setTransferData(
new TransferData.Builder()
.setGainingClientId("gaining")
.setLosingClientId("losing")
.setPendingTransferExpirationTime(fakeClock.nowUtc())
.setServerApproveEntities(
ImmutableSet.of(Key.create(BillingEvent.OneTime.class, 1)))
.setTransferRequestTime(fakeClock.nowUtc())
.setTransferStatus(TransferStatus.SERVER_APPROVED)
.setTransferRequestTrid(Trid.create("client-trid", "server-trid"))
.build())
.build();
// Set up a new persisted ContactResource entity.
contactResource =
persistResource(
cloneAndSetAutoTimestamps(
new ContactResource.Builder()
.setContactId("contact_id")
.setRepoId("1-FOOBAR")
.setCreationClientId("a registrar")
.setLastEppUpdateTime(fakeClock.nowUtc())
.setLastEppUpdateClientId("another registrar")
.setLastTransferTime(fakeClock.nowUtc())
.setPersistedCurrentSponsorClientId("a third registrar")
.setLocalizedPostalInfo(
new PostalInfo.Builder()
.setType(Type.LOCALIZED)
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("111 8th Ave", "4th Floor"))
.setCity("New York")
.setState("NY")
.setZip("10011")
.setCountryCode("US")
.build())
.build())
.setInternationalizedPostalInfo(
new PostalInfo.Builder()
.setType(Type.INTERNATIONALIZED)
.setAddress(
new ContactAddress.Builder()
.setStreet(ImmutableList.of("111 8th Ave", "4th Floor"))
.setCity("New York")
.setState("NY")
.setZip("10011")
.setCountryCode("US")
.build())
.build())
.setVoiceNumber(
new ContactPhoneNumber.Builder().setPhoneNumber("867-5309").build())
.setFaxNumber(
new ContactPhoneNumber.Builder()
.setPhoneNumber("867-5309")
.setExtension("1000")
.build())
.setEmailAddress("jenny@example.com")
.setAuthInfo(ContactAuthInfo.create(PasswordAuth.create("passw0rd")))
.setDisclose(
new Disclose.Builder()
.setVoice(new PresenceMarker())
.setEmail(new PresenceMarker())
.setFax(new PresenceMarker())
.setFlag(true)
.setAddrs(
ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED)))
.setNames(
ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED)))
.setOrgs(
ImmutableList.of(PostalInfoChoice.create(Type.INTERNATIONALIZED)))
.build())
.setStatusValues(ImmutableSet.of(StatusValue.OK))
.setTransferData(
new TransferData.Builder()
.setGainingClientId("gaining")
.setLosingClientId("losing")
.setPendingTransferExpirationTime(fakeClock.nowUtc())
.setServerApproveEntities(
ImmutableSet.of(Key.create(BillingEvent.OneTime.class, 1)))
.setTransferRequestTime(fakeClock.nowUtc())
.setTransferStatus(TransferStatus.SERVER_APPROVED)
.setTransferRequestTrid(Trid.create("client-trid", "server-trid"))
.build())
.build()));
contactResource = persistResource(cloneAndSetAutoTimestamps(originalContact));
}
@Test
public void testCloudSqlPersistence_failWhenViolateForeignKeyConstraint() {
assertThrowForeignKeyViolation(() -> jpaTm().transact(() -> jpaTm().saveNew(originalContact)));
}
@Test
public void testCloudSqlPersistence_succeed() {
saveRegistrar("registrar1");
saveRegistrar("registrar2");
saveRegistrar("registrar3");
jpaTm().transact(() -> jpaTm().saveNew(originalContact));
ContactResource persisted =
jpaTm()
.transact(
() ->
jpaTm()
.load(VKey.createSql(ContactResource.class, originalContact.getRepoId())))
.get();
// TODO(b/153378849): Remove the hard code for postal info after resolving the issue that
// @PostLoad doesn't work in Address
ContactResource fixed =
originalContact
.asBuilder()
.setCreationTime(persisted.getCreationTime())
.setInternationalizedPostalInfo(persisted.getInternationalizedPostalInfo())
.setLocalizedPostalInfo(persisted.getLocalizedPostalInfo())
.setTransferData(null)
.build();
assertThat(persisted).isEqualTo(fixed);
}
@Test
@@ -16,44 +16,67 @@ package google.registry.model.domain;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.SqlHelper.assertThrowForeignKeyViolation;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.time.DateTimeZone.UTC;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.model.EntityTestCase;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DesignatedContact.Type;
import google.registry.model.domain.launch.LaunchNotice;
import google.registry.model.domain.secdns.DelegationSignerData;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.host.HostResource;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.FakeClock;
import javax.persistence.EntityManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Verify that we can store/retrieve DomainBase objects from a SQL database. */
@RunWith(JUnit4.class)
public class DomainBaseSqlTest extends EntityTestCase {
public class DomainBaseSqlTest {
protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC));
@RegisterExtension
@Order(value = 1)
DatastoreEntityExtension datastoreEntityExtension = new DatastoreEntityExtension();
@RegisterExtension
JpaIntegrationWithCoverageExtension jpa =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
DomainBase domain;
Key<ContactResource> contactKey;
Key<ContactResource> contact2Key;
VKey<HostResource> host1VKey;
HostResource host;
@Before
@BeforeEach
public void setUp() {
contactKey = Key.create(ContactResource.class, "contact_id1");
contact2Key = Key.create(ContactResource.class, "contact_id2");
host1VKey = VKey.createSql(HostResource.class, "host1");
domain =
new DomainBase.Builder()
.setFullyQualifiedDomainName("example.com")
.setRepoId("4-COM")
.setCreationClientId("a registrar")
.setCreationClientId("registrar1")
.setLastEppUpdateTime(fakeClock.nowUtc())
.setLastEppUpdateClientId("AnotherRegistrar")
.setLastEppUpdateClientId("registrar2")
.setLastTransferTime(fakeClock.nowUtc())
.setNameservers(host1VKey)
.setStatusValues(
ImmutableSet.of(
StatusValue.CLIENT_DELETE_PROHIBITED,
@@ -65,7 +88,7 @@ public class DomainBaseSqlTest extends EntityTestCase {
.setRegistrant(contactKey)
.setContacts(ImmutableSet.of(DesignatedContact.create(Type.ADMIN, contact2Key)))
.setSubordinateHosts(ImmutableSet.of("ns1.example.com"))
.setPersistedCurrentSponsorClientId("losing")
.setPersistedCurrentSponsorClientId("registrar3")
.setRegistrationExpirationTime(fakeClock.nowUtc().plusYears(1))
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("password")))
.setDsData(ImmutableSet.of(DelegationSignerData.create(1, 2, 3, new byte[] {0, 1, 2})))
@@ -73,16 +96,31 @@ public class DomainBaseSqlTest extends EntityTestCase {
LaunchNotice.create("tcnid", "validatorId", START_OF_TIME, START_OF_TIME))
.setSmdId("smdid")
.build();
host =
new HostResource.Builder()
.setRepoId("host1")
.setFullyQualifiedHostName("ns1.example.com")
.setCreationClientId("registrar1")
.setPersistedCurrentSponsorClientId("registrar2")
.build();
}
@Test
public void testDomainBasePersistence() {
saveRegistrar("registrar1");
saveRegistrar("registrar2");
saveRegistrar("registrar3");
jpaTm()
.transact(
() -> {
// Persist the domain.
EntityManager em = jpaTm().getEntityManager();
em.persist(domain);
// Persist the host.
em.persist(host);
});
jpaTm()
@@ -111,4 +149,18 @@ public class DomainBaseSqlTest extends EntityTestCase {
assertThat(result).isEqualTo(org);
});
}
@Test
public void testForeignKeyConstraints() {
assertThrowForeignKeyViolation(
() -> {
jpaTm()
.transact(
() -> {
// Persist the domain without the associated host object.
EntityManager em = jpaTm().getEntityManager();
em.persist(domain);
});
});
}
}
@@ -16,7 +16,6 @@ package google.registry.model.ofy;
import static com.google.appengine.api.datastore.EntityTranslator.convertToPb;
import static com.google.common.truth.Truth.assertThat;
import static com.googlecode.objectify.ObjectifyService.register;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.CommitLogBucket.getBucketKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
@@ -47,7 +46,11 @@ import org.junit.runners.JUnit4;
public class OfyCommitLogTest {
@Rule
public final AppEngineRule appEngine = AppEngineRule.builder().withDatastoreAndCloudSql().build();
public final AppEngineRule appEngine =
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.withOfyTestEntities(TestVirtualObject.class, Root.class, Child.class)
.build();
@Rule
public final InjectRule inject = new InjectRule();
@@ -56,8 +59,6 @@ public class OfyCommitLogTest {
@Before
public void before() {
register(Root.class);
register(Child.class);
inject.setStaticField(Ofy.class, "clock", clock);
}
@@ -31,20 +31,21 @@ import google.registry.testing.AppEngineRule;
import google.registry.testing.FakeClock;
import java.util.Optional;
import org.joda.time.Duration;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link RegistryLockDao}. */
@RunWith(JUnit4.class)
public final class RegistryLockDaoTest {
private final FakeClock fakeClock = new FakeClock();
@Rule
@RegisterExtension
public final AppEngineRule appEngine =
AppEngineRule.builder().withDatastoreAndCloudSql().withClock(fakeClock).build();
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.enableJpaEntityCoverageCheck(true)
.withClock(fakeClock)
.build();
@Test
public void testSaveAndLoad_success() {
@@ -29,16 +29,14 @@ import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumList.PremiumListRevision;
import google.registry.testing.AppEngineRule;
import org.joda.money.Money;
import org.junit.Rule;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link PremiumList}. */
@EnableRuleMigrationSupport
public class PremiumListTest {
@Rule
@RegisterExtension
public final AppEngineRule appEngine = AppEngineRule.builder().withDatastoreAndCloudSql().build();
@BeforeEach
@@ -28,7 +28,6 @@ import static org.mockito.Mockito.when;
import google.registry.model.ofy.Ofy;
import google.registry.model.server.Lock.LockState;
import google.registry.schema.server.LockDao;
import google.registry.testing.AppEngineRule;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectRule;
@@ -65,19 +64,16 @@ public class LockTest {
Optional<Lock> lock = Lock.acquire(RESOURCE_NAME, tld, leaseLength, requestStatusChecker, true);
verify(Lock.lockMetrics).recordAcquire(RESOURCE_NAME, tld, expectedLockState);
verifyNoMoreInteractions(Lock.lockMetrics);
assertThat(LockDao.load(RESOURCE_NAME, tld)).isPresent();
Lock.lockMetrics = null;
return lock;
}
private void release(Lock lock, String expectedTld, long expectedMillis) {
assertThat(LockDao.load(RESOURCE_NAME, expectedTld)).isPresent();
Lock.lockMetrics = mock(LockMetrics.class);
lock.release();
verify(Lock.lockMetrics)
.recordRelease(RESOURCE_NAME, expectedTld, Duration.millis(expectedMillis));
verifyNoMoreInteractions(Lock.lockMetrics);
assertThat(LockDao.load(RESOURCE_NAME, expectedTld)).isEmpty();
Lock.lockMetrics = null;
}
@@ -23,7 +23,6 @@ import static org.joda.time.Duration.standardHours;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedMap;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.annotation.Entity;
import google.registry.model.common.CrossTldSingleton;
import google.registry.model.ofy.CommitLogManifest;
@@ -45,13 +44,17 @@ public class CommitLogRevisionsTranslatorFactoryTest {
private static final DateTime START_TIME = DateTime.parse("2000-01-01TZ");
@Entity
@Entity(name = "ClrtfTestEntity")
public static class TestObject extends CrossTldSingleton {
ImmutableSortedMap<DateTime, Key<CommitLogManifest>> revisions = ImmutableSortedMap.of();
}
@Rule
public final AppEngineRule appEngine = AppEngineRule.builder().withDatastoreAndCloudSql().build();
public final AppEngineRule appEngine =
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.withOfyTestEntities(TestObject.class)
.build();
@Rule
public final InjectRule inject = new InjectRule();
@@ -60,7 +63,6 @@ public class CommitLogRevisionsTranslatorFactoryTest {
@Before
public void before() {
ObjectifyService.register(TestObject.class);
inject.setStaticField(Ofy.class, "clock", clock);
}
@@ -0,0 +1,302 @@
// Copyright 2020 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.persistence;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import com.google.common.collect.ImmutableSet;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
import java.lang.reflect.Method;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
import javax.persistence.Transient;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link EntityCallbacksListener}. */
@RunWith(JUnit4.class)
public class EntityCallbacksListenerTest {
@Rule
public final JpaUnitTestRule jpaRule =
new JpaTestRules.Builder().withEntityClass(TestEntity.class).buildUnitTestRule();
@Test
public void verifyAllCallbacks_executedExpectedTimes() {
TestEntity testPersist = new TestEntity();
jpaTm().transact(() -> jpaTm().saveNew(testPersist));
checkAll(testPersist, 1, 0, 0, 0);
TestEntity testUpdate = new TestEntity();
TestEntity updated =
jpaTm()
.transact(
() -> {
TestEntity merged = jpaTm().getEntityManager().merge(testUpdate);
merged.foo++;
jpaTm().getEntityManager().flush();
return merged;
});
// Note that when we get the merged entity, its @PostLoad callbacks are also invoked
checkAll(updated, 0, 1, 0, 1);
TestEntity testLoad =
jpaTm().transact(() -> jpaTm().load(VKey.createSql(TestEntity.class, "id"))).get();
checkAll(testLoad, 0, 0, 0, 1);
TestEntity testRemove =
jpaTm()
.transact(
() -> {
TestEntity removed = jpaTm().load(VKey.createSql(TestEntity.class, "id")).get();
jpaTm().getEntityManager().remove(removed);
return removed;
});
checkAll(testRemove, 0, 0, 1, 1);
}
@Test
public void verifyAllManagedEntities_haveNoMethodWithEmbedded() {
ImmutableSet<Class> violations =
PersistenceXmlUtility.getManagedClasses().stream()
.filter(clazz -> clazz.isAnnotationPresent(Entity.class))
.filter(EntityCallbacksListenerTest::hasMethodAnnotatedWithEmbedded)
.collect(toImmutableSet());
assertWithMessage(
"Found entity classes having methods annotated with @Embedded. EntityCallbacksListener"
+ " only supports annotating fields with @Embedded.")
.that(violations)
.isEmpty();
}
@Test
public void verifyHasMethodAnnotatedWithEmbedded_work() {
assertThat(hasMethodAnnotatedWithEmbedded(ViolationEntity.class)).isTrue();
}
private static boolean hasMethodAnnotatedWithEmbedded(Class<?> entityType) {
boolean result = false;
Class<?> parentType = entityType.getSuperclass();
if (parentType != null && parentType.isAnnotationPresent(MappedSuperclass.class)) {
result = hasMethodAnnotatedWithEmbedded(parentType);
}
for (Method method : entityType.getDeclaredMethods()) {
if (method.isAnnotationPresent(Embedded.class)) {
result = true;
break;
}
}
return result;
}
private static void checkAll(
TestEntity testEntity,
int expectedPersist,
int expectedUpdate,
int expectedRemove,
int expectedLoad) {
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPostPersist)
.isEqualTo(expectedPersist);
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPrePersist)
.isEqualTo(expectedPersist);
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPreUpdate)
.isEqualTo(expectedUpdate);
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPostUpdate)
.isEqualTo(expectedUpdate);
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPreRemove)
.isEqualTo(expectedRemove);
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPostRemove)
.isEqualTo(expectedRemove);
assertThat(testEntity.entityPostLoad).isEqualTo(expectedLoad);
assertThat(testEntity.entityEmbedded.entityEmbeddedPostLoad).isEqualTo(expectedLoad);
assertThat(testEntity.entityEmbedded.entityEmbeddedNested.entityEmbeddedNestedPostLoad)
.isEqualTo(expectedLoad);
assertThat(testEntity.entityEmbedded.entityEmbeddedParentPostLoad).isEqualTo(expectedLoad);
assertThat(testEntity.parentPostLoad).isEqualTo(expectedLoad);
assertThat(testEntity.parentEmbedded.parentEmbeddedPostLoad).isEqualTo(expectedLoad);
assertThat(testEntity.parentEmbedded.parentEmbeddedNested.parentEmbeddedNestedPostLoad)
.isEqualTo(expectedLoad);
assertThat(testEntity.parentEmbedded.parentEmbeddedParentPostLoad).isEqualTo(expectedLoad);
}
@Entity(name = "TestEntity")
private static class TestEntity extends ParentEntity {
@Id String name = "id";
int foo = 0;
@Transient int entityPostLoad = 0;
@Embedded EntityEmbedded entityEmbedded = new EntityEmbedded();
@PostLoad
void entityPostLoad() {
entityPostLoad++;
}
}
@Embeddable
private static class EntityEmbedded extends EntityEmbeddedParent {
@Embedded EntityEmbeddedNested entityEmbeddedNested = new EntityEmbeddedNested();
@Transient int entityEmbeddedPostLoad = 0;
String entityEmbedded = "placeholder";
@PostLoad
void entityEmbeddedPrePersist() {
entityEmbeddedPostLoad++;
}
}
@MappedSuperclass
private static class EntityEmbeddedParent {
@Transient int entityEmbeddedParentPostLoad = 0;
String entityEmbeddedParent = "placeholder";
@PostLoad
void entityEmbeddedParentPostLoad() {
entityEmbeddedParentPostLoad++;
}
}
@Embeddable
private static class EntityEmbeddedNested {
@Transient int entityEmbeddedNestedPrePersist = 0;
@Transient int entityEmbeddedNestedPreRemove = 0;
@Transient int entityEmbeddedNestedPostPersist = 0;
@Transient int entityEmbeddedNestedPostRemove = 0;
@Transient int entityEmbeddedNestedPreUpdate = 0;
@Transient int entityEmbeddedNestedPostUpdate = 0;
@Transient int entityEmbeddedNestedPostLoad = 0;
String entityEmbeddedNested = "placeholder";
@PrePersist
void entityEmbeddedNestedPrePersist() {
entityEmbeddedNestedPrePersist++;
}
@PreRemove
void entityEmbeddedNestedPreRemove() {
entityEmbeddedNestedPreRemove++;
}
@PostPersist
void entityEmbeddedNestedPostPersist() {
entityEmbeddedNestedPostPersist++;
}
@PostRemove
void entityEmbeddedNestedPostRemove() {
entityEmbeddedNestedPostRemove++;
}
@PreUpdate
void entityEmbeddedNestedPreUpdate() {
entityEmbeddedNestedPreUpdate++;
}
@PostUpdate
void entityEmbeddedNestedPostUpdate() {
entityEmbeddedNestedPostUpdate++;
}
@PostLoad
void entityEmbeddedNestedPostLoad() {
entityEmbeddedNestedPostLoad++;
}
}
@MappedSuperclass
private static class ParentEntity {
@Embedded ParentEmbedded parentEmbedded = new ParentEmbedded();
@Transient int parentPostLoad = 0;
String parentEntity = "placeholder";
@PostLoad
void parentPostLoad() {
parentPostLoad++;
}
}
@Embeddable
private static class ParentEmbedded extends ParentEmbeddedParent {
@Transient int parentEmbeddedPostLoad = 0;
String parentEmbedded = "placeholder";
@Embedded ParentEmbeddedNested parentEmbeddedNested = new ParentEmbeddedNested();
@PostLoad
void parentEmbeddedPostLoad() {
parentEmbeddedPostLoad++;
}
}
@Embeddable
private static class ParentEmbeddedNested {
@Transient int parentEmbeddedNestedPostLoad = 0;
String parentEmbeddedNested = "placeholder";
@PostLoad
void parentEmbeddedNestedPostLoad() {
parentEmbeddedNestedPostLoad++;
}
}
@MappedSuperclass
private static class ParentEmbeddedParent {
@Transient int parentEmbeddedParentPostLoad = 0;
String parentEmbeddedParent = "placeholder";
@PostLoad
void parentEmbeddedParentPostLoad() {
parentEmbeddedParentPostLoad++;
}
}
@Entity
private static class ViolationEntity {
@Embedded
EntityEmbedded getEntityEmbedded() {
return new EntityEmbedded();
}
}
}
@@ -17,18 +17,21 @@ import static com.google.common.truth.Truth.assertThat;
import com.googlecode.objectify.Key;
import google.registry.testing.AppEngineRule;
import google.registry.testing.TestObject;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import google.registry.testing.TestObject;
@RunWith(JUnit4.class)
public class VKeyTest {
@Rule
public final AppEngineRule appEngineRule =
AppEngineRule.builder().withDatastoreAndCloudSql().build();
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.withOfyTestEntities(TestObject.class)
.build();
public VKeyTest() {}
@@ -0,0 +1,78 @@
// Copyright 2020 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.persistence.converter;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Test SQL persistence of VKey. */
@RunWith(JUnit4.class)
public class VKeyConverterTest {
@Rule
public final JpaUnitTestRule jpaRule =
new JpaTestRules.Builder().withEntityClass(TestEntity.class).buildUnitTestRule();
public VKeyConverterTest() {}
@Test
public void testRoundTrip() {
TestEntity original =
new TestEntity("TheRealSpartacus", VKey.createSql(TestEntity.class, "ImSpartacus!"));
jpaTm().transact(() -> jpaTm().getEntityManager().persist(original));
TestEntity retrieved =
jpaTm()
.transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "TheRealSpartacus"));
assertThat(retrieved.other.getSqlKey()).isEqualTo("ImSpartacus!");
}
static class TestEntityVKeyConverter extends VKeyConverter<TestEntity> {
@Override
protected Class<TestEntity> getAttributeClass() {
return TestEntity.class;
}
}
@Entity(name = "TestEntity")
static class TestEntity {
@Id String id;
// Specifying "@Converter(autoApply = true) on TestEntityVKeyConverter this doesn't seem to
// work.
@Convert(converter = TestEntityVKeyConverter.class)
VKey<TestEntity> other;
TestEntity(String id, VKey<TestEntity> other) {
this.id = id;
this.other = other;
}
/** Default constructor, needed for hibernate. */
public TestEntity() {}
}
}
@@ -27,6 +27,7 @@ import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import javax.persistence.Entity;
import org.junit.rules.ExternalResource;
@@ -51,15 +52,15 @@ public class JpaEntityCoverage extends ExternalResource {
private static final Map<String, Boolean> testsJpaEntities = Maps.newHashMap();
// Provides class name of the test being executed.
private final TestCaseWatcher watcher;
private final Supplier<String> currTestClassNameSupplier;
public JpaEntityCoverage(TestCaseWatcher watcher) {
this.watcher = watcher;
public JpaEntityCoverage(Supplier<String> currTestClassNameSupplier) {
this.currTestClassNameSupplier = currTestClassNameSupplier;
}
@Override
public void before() {
testsJpaEntities.putIfAbsent(watcher.getTestClass(), false);
testsJpaEntities.putIfAbsent(currTestClassNameSupplier.get(), false);
}
@Override
@@ -69,7 +70,7 @@ public class JpaEntityCoverage extends ExternalResource {
.forEach(
entity -> {
allCoveredJpaEntities.add(entity);
testsJpaEntities.put(watcher.getTestClass(), true);
testsJpaEntities.put(currTestClassNameSupplier.get(), true);
});
}
@@ -29,6 +29,9 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.joda.time.DateTime;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -88,7 +91,7 @@ public class JpaTestRules {
this.ruleChain =
RuleChain.outerRule(watcher)
.around(integrationTestRule)
.around(new JpaEntityCoverage(watcher));
.around(new JpaEntityCoverage(watcher::getTestClass));
}
@Override
@@ -97,6 +100,40 @@ public class JpaTestRules {
}
}
/**
* JUnit extension for member classes of {@link
* google.registry.schema.integration.SqlIntegrationTestSuite}. In addition to providing a
* database through {@link JpaIntegrationTestRule}, it also keeps track of the test coverage of
* the declared JPA entities (in persistence.xml). Per-class statistics are stored in static
* variables. The SqlIntegrationTestSuite inspects the cumulative statistics after all test
* classes have run.
*/
public static final class JpaIntegrationWithCoverageExtension
implements BeforeEachCallback, AfterEachCallback {
private String currentTestClassName = null;
private final JpaEntityCoverage jpaEntityCoverage =
new JpaEntityCoverage(() -> this.currentTestClassName);
private final JpaIntegrationTestRule integrationTestRule;
JpaIntegrationWithCoverageExtension(JpaIntegrationTestRule integrationTestRule) {
this.integrationTestRule = integrationTestRule;
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
this.currentTestClassName = context.getRequiredTestClass().getName();
integrationTestRule.before();
jpaEntityCoverage.before();
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
jpaEntityCoverage.after();
integrationTestRule.after();
this.currentTestClassName = null;
}
}
/** Builder of test rules that provide {@link JpaTransactionManager}. */
public static class Builder {
private String initScript;
@@ -149,6 +186,15 @@ public class JpaTestRules {
return new JpaIntegrationWithCoverageRule(buildIntegrationTestRule());
}
/**
* JUnit extension that adapts {@link JpaIntegrationTestRule} for JUnit 5 and also checks test
* coverage of JPA entity classes.
*/
public JpaIntegrationWithCoverageExtension buildIntegrationWithCoverageExtension() {
checkState(initScript == null, "Integration tests do not accept initScript");
return new JpaIntegrationWithCoverageExtension(buildIntegrationTestRule());
}
/** Builds a {@link JpaUnitTestRule} instance. */
public JpaUnitTestRule buildUnitTestRule() {
checkState(
@@ -41,6 +41,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
@@ -76,7 +77,7 @@ abstract class JpaTransactionManagerRule extends ExternalResource {
private final Clock clock;
private final Optional<String> initScriptPath;
private final ImmutableList<Class> extraEntityClasses;
private final ImmutableMap userProperties;
private final ImmutableMap<String, String> userProperties;
private static final JdbcDatabaseContainer database = create();
private static final HibernateSchemaExporter exporter =
@@ -143,15 +144,18 @@ abstract class JpaTransactionManagerRule extends ExternalResource {
new String(Files.readAllBytes(tempSqlFile.toPath()), StandardCharsets.UTF_8));
}
ImmutableMap properties = PersistenceModule.providesDefaultDatabaseConfigs();
Map<String, String> properties =
Maps.newHashMap(PersistenceModule.providesDefaultDatabaseConfigs());
// Set the minimumIdle to 0 so assertReasonableNumDbConnections() can check if there is
// connection leak after each test.
properties.put("hibernate.hikari.minimumIdle", "0");
properties.put("hibernate.hikari.idleTimeout", "300000");
if (!userProperties.isEmpty()) {
// If there are user properties, create a new properties object with these added.
ImmutableMap.Builder builder = properties.builder();
builder.putAll(userProperties);
properties.putAll(userProperties);
// Forbid Hibernate push to stay consistent with flyway-based schema management.
builder.put(Environment.HBM2DDL_AUTO, "none");
builder.put(Environment.SHOW_SQL, "true");
properties = builder.build();
properties.put(Environment.HBM2DDL_AUTO, "none");
properties.put(Environment.SHOW_SQL, "true");
}
assertReasonableNumDbConnections();
emf =
@@ -159,7 +163,7 @@ abstract class JpaTransactionManagerRule extends ExternalResource {
getJdbcUrl(),
database.getUsername(),
database.getPassword(),
properties,
ImmutableMap.copyOf(properties),
extraEntityClasses);
emfEntityHash = entityHash;
}
@@ -30,13 +30,10 @@ import google.registry.testing.FakeClock;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link Cursor}. */
@RunWith(JUnit4.class)
public class CursorDaoTest {
private final FakeClock fakeClock = new FakeClock();
@@ -44,9 +41,13 @@ public class CursorDaoTest {
private final TestLogHandler logHandler = new TestLogHandler();
private final Logger loggerToIntercept = Logger.getLogger(CursorDao.class.getCanonicalName());
@Rule
@RegisterExtension
public final AppEngineRule appEngine =
AppEngineRule.builder().withDatastoreAndCloudSql().withClock(fakeClock).build();
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.enableJpaEntityCoverageCheck(true)
.withClock(fakeClock)
.build();
@Test
public void save_worksSuccessfullyOnNewCursor() {
@@ -14,22 +14,26 @@
package google.registry.schema.integration;
import com.google.common.truth.Expect;
import static com.google.common.truth.Truth.assert_;
import google.registry.model.contact.ContactResourceTest;
import google.registry.model.domain.DomainBaseSqlTest;
import google.registry.model.registry.RegistryLockDaoTest;
import google.registry.persistence.transaction.JpaEntityCoverage;
import google.registry.schema.cursor.CursorDaoTest;
import google.registry.schema.integration.SqlIntegrationTestSuite.AfterSuiteTest;
import google.registry.schema.integration.SqlIntegrationTestSuite.BeforeSuiteTest;
import google.registry.schema.registrar.RegistrarDaoTest;
import google.registry.schema.server.LockDaoTest;
import google.registry.schema.tld.PremiumListDaoTest;
import google.registry.schema.tld.ReservedListDaoTest;
import google.registry.schema.tmch.ClaimsListDaoTest;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
/**
* Groups all JPA entity tests in one suite for easy invocation. This suite is used for
@@ -47,37 +51,81 @@ import org.junit.runners.Suite.SuiteClasses;
* <p>Note that with {@code JpaIntegrationWithCoverageRule}, each method starts with an empty
* database. Therefore this is not the right place for verifying backward data compatibility in
* end-to-end functional tests.
*
* <p>As of April 2020, none of the before/after annotations ({@code BeforeClass} and {@code
* AfterClass} in JUnit 4, or {@code BeforeAll} and {@code AfterAll} in JUnit5) work in a test suite
* run with {@link JUnitPlatform the current JUnit 5 runner}. However, staying with the JUnit 4
* runner would prevent any member tests from migrating to JUnit 5.
*
* <p>This class uses a hack to work with the current JUnit 5 runner. {@link BeforeSuiteTest} is
* added to the front of the suite class list and invokes the suite's setup method, and {@link
* AfterSuiteTest} is added to the tail of the suite class list and invokes the suite's teardown
* method. This works because the member tests are run in the order they are declared (See {@code
* org.junit.platform.engine.support.descriptor.AbstractTestDescriptor#addChild}). Should the
* ordering changes in the future, we will only get false alarms.
*/
@RunWith(Suite.class)
@SuiteClasses({
@RunWith(JUnitPlatform.class)
@SelectClasses({
// BeforeSuiteTest must be the first entry. See class javadoc for details.
BeforeSuiteTest.class,
ClaimsListDaoTest.class,
ContactResourceTest.class,
CursorDaoTest.class,
DomainBaseSqlTest.class,
LockDaoTest.class,
PremiumListDaoTest.class,
RegistrarDaoTest.class,
RegistryLockDaoTest.class,
ReservedListDaoTest.class
ReservedListDaoTest.class,
// AfterSuiteTest must be the last entry. See class javadoc for details.
AfterSuiteTest.class
})
public class SqlIntegrationTestSuite {
@ClassRule public static final Expect expect = Expect.create();
@BeforeClass
@BeforeAll // Not yet supported in JUnit 5. Called through BeforeSuiteTest.
public static void initJpaEntityCoverage() {
JpaEntityCoverage.init();
}
@AfterClass
@AfterAll // Not yet supported in JUnit 5. Called through AfterSuiteTest.
public static void checkJpaEntityCoverage() {
expect
// TODO(weiminyu): collect both assertion errors like Truth's Expect does in JUnit 4.
assert_()
.withMessage("Tests are missing for the following JPA entities:")
.that(JpaEntityCoverage.getUncoveredEntities())
.isEmpty();
expect
assert_()
.withMessage(
"The following classes do not test JPA entities. Please remove them from this suite")
.that(JpaEntityCoverage.getIrrelevantTestClasses())
.isEmpty();
}
/**
* Hack for calling {@link SqlIntegrationTestSuite#initJpaEntityCoverage()} before all real tests
* in suite. See outer class javadoc for details.
*
* <p>The 'Test' suffix in class name is required.
*/
static class BeforeSuiteTest {
@Test
void beforeAll() {
initJpaEntityCoverage();
}
}
/**
* Hack for invoking {@link SqlIntegrationTestSuite#checkJpaEntityCoverage()} after all real tests
* in suite. See outer class javadoc for details.
*
* <p>The 'Test' suffix in class name is required.
*/
static class AfterSuiteTest {
@Test
void afterSuite() {
checkJpaEntityCoverage();
}
}
}
@@ -16,27 +16,41 @@ package google.registry.schema.registrar;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static org.joda.time.DateTimeZone.UTC;
import static org.junit.Assert.assertThrows;
import com.google.common.collect.ImmutableList;
import google.registry.model.EntityTestCase;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.persistence.VKey;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.testing.FakeClock;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link RegistrarDao}. */
@RunWith(JUnit4.class)
public class RegistrarDaoTest extends EntityTestCase {
/** Unit tests for persisting {@link Registrar} entities. */
public class RegistrarDaoTest {
protected FakeClock fakeClock = new FakeClock(DateTime.now(UTC));
@RegisterExtension
@Order(value = 1)
DatastoreEntityExtension datastoreEntityExtension = new DatastoreEntityExtension();
@RegisterExtension
JpaIntegrationWithCoverageExtension jpa =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
private final VKey<Registrar> registrarKey = VKey.createSql(Registrar.class, "registrarId");
private Registrar testRegistrar;
@Before
@BeforeEach
public void setUp() {
testRegistrar =
new Registrar.Builder()
@@ -0,0 +1,65 @@
// Copyright 2020 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.schema.replay;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableSet;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;
import org.junit.Ignore;
import org.junit.Test;
/**
* Test to verify classes implement {@link SqlEntity} and {@link DatastoreEntity} when they should.
*/
public class EntityTest {
@Test
@Ignore("This won't be done until b/152410794 is done, since it requires many entity changes")
public void testSqlEntityPersistence() {
try (ScanResult scanResult =
new ClassGraph().enableAnnotationInfo().whitelistPackages("google.registry").scan()) {
// All javax.persistence entities must implement SqlEntity and vice versa
ImmutableSet<String> javaxPersistenceClasses =
getClassNames(
scanResult.getClassesWithAnnotation(javax.persistence.Entity.class.getName()));
ImmutableSet<String> sqlEntityClasses =
getClassNames(scanResult.getClassesImplementing(SqlEntity.class.getName()));
assertThat(javaxPersistenceClasses).isEqualTo(sqlEntityClasses);
// All com.googlecode.objectify.annotation.Entity classes must implement DatastoreEntity and
// vice versa
ImmutableSet<String> objectifyClasses =
getClassNames(
scanResult.getClassesWithAnnotation(
com.googlecode.objectify.annotation.Entity.class.getName()));
ImmutableSet<String> datastoreEntityClasses =
getClassNames(scanResult.getClassesImplementing(DatastoreEntity.class.getName()));
assertThat(objectifyClasses).isEqualTo(datastoreEntityClasses);
}
}
private ImmutableSet<String> getClassNames(ClassInfoList classInfoList) {
return classInfoList.stream()
.filter(ClassInfo::isStandardClass)
.map(ClassInfo::loadClass)
.map(Class::getName)
.collect(toImmutableSet());
}
}
@@ -19,28 +19,25 @@ import static google.registry.testing.LogsSubject.assertAboutLogs;
import com.google.common.testing.TestLogHandler;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
import google.registry.testing.FakeClock;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.joda.time.Duration;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link Lock}. */
@RunWith(JUnit4.class)
public class LockDaoTest {
private final FakeClock fakeClock = new FakeClock();
private final TestLogHandler logHandler = new TestLogHandler();
private final Logger loggerToIntercept = Logger.getLogger(LockDao.class.getCanonicalName());
@Rule
public final JpaIntegrationWithCoverageRule jpaRule =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageRule();
@RegisterExtension
public final JpaIntegrationWithCoverageExtension jpa =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
@Test
public void save_worksSuccessfully() {
@@ -34,21 +34,22 @@ import java.math.BigDecimal;
import java.util.Optional;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link PremiumListDao}. */
@RunWith(JUnit4.class)
public class PremiumListDaoTest {
private final FakeClock fakeClock = new FakeClock();
@Rule
@RegisterExtension
public final AppEngineRule appEngine =
AppEngineRule.builder().withDatastoreAndCloudSql().withClock(fakeClock).build();
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.enableJpaEntityCoverageCheck(true)
.withClock(fakeClock)
.build();
private static final ImmutableMap<String, BigDecimal> TEST_PRICES =
ImmutableMap.of(
@@ -124,7 +125,7 @@ public class PremiumListDaoTest {
// TODO(b/147246613): Un-ignore this.
@Test
@Ignore
@Disabled
public void update_throwsWhenListDoesntExist() {
IllegalArgumentException thrown =
assertThrows(
@@ -20,23 +20,20 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
import com.google.common.collect.ImmutableMap;
import google.registry.model.registry.label.ReservationType;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
import google.registry.schema.tld.ReservedList.ReservedEntry;
import google.registry.testing.FakeClock;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ReservedListDao}. */
@RunWith(JUnit4.class)
public class ReservedListDaoTest {
private final FakeClock fakeClock = new FakeClock();
@Rule
public final JpaIntegrationWithCoverageRule jpaRule =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageRule();
@RegisterExtension
public final JpaIntegrationWithCoverageExtension jpa =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
private static final ImmutableMap<String, ReservedEntry> TEST_RESERVATIONS =
ImmutableMap.of(
@@ -18,22 +18,19 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableMap;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
import google.registry.testing.FakeClock;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link ClaimsListDao}. */
@RunWith(JUnit4.class)
public class ClaimsListDaoTest {
private final FakeClock fakeClock = new FakeClock();
@Rule
public final JpaIntegrationWithCoverageRule jpaRule =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageRule();
@RegisterExtension
public final JpaIntegrationWithCoverageExtension jpa =
new JpaTestRules.Builder().withClock(fakeClock).buildIntegrationWithCoverageExtension();
@Test
public void trySave_insertsClaimsListSuccessfully() {
@@ -14,6 +14,7 @@
package google.registry.testing;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assertWithMessage;
import static google.registry.testing.DatastoreHelper.persistSimpleResources;
import static google.registry.util.ResourceUtils.readResourceUtf8;
@@ -33,6 +34,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyFilter;
import google.registry.model.ofy.ObjectifyService;
import google.registry.model.registrar.Registrar;
@@ -40,18 +42,25 @@ import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarContact;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationTestRule;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule;
import google.registry.util.Clock;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.logging.LogManager;
import javax.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.Description;
@@ -65,9 +74,15 @@ import org.junit.runners.model.Statement;
*
* <p>This rule also resets global Objectify for the current thread.
*
* <p>This class works with both JUnit 4 and JUnit 5. With JUnit 4, the test runner calls {@link
* #apply(Statement, Description)}, which in turns calls {@link #before()} on entry and {@link
* #after()} on exit. With JUnit 5, the test runner calls {@link #beforeEach(ExtensionContext)} and
* {@link #afterEach(ExtensionContext)}.
*
* @see org.junit.rules.ExternalResource
*/
public final class AppEngineRule extends ExternalResource {
public final class AppEngineRule extends ExternalResource
implements BeforeEachCallback, AfterEachCallback {
public static final String NEW_REGISTRAR_GAE_USER_ID = "666";
public static final String THE_REGISTRAR_GAE_USER_ID = "31337";
@@ -92,8 +107,19 @@ public final class AppEngineRule extends ExternalResource {
/** A rule-within-a-rule to provide a temporary folder for AppEngineRule's internal temp files. */
TemporaryFolder temporaryFolder = new TemporaryFolder();
/**
* Sets up a SQL database when running on JUnit 5. This is for test classes that are not member of
* the {@code SqlIntegrationTestSuite}.
*/
JpaIntegrationTestRule jpaIntegrationTestRule = null;
/**
* Sets up a SQL database when running on JUnit 5 and records the JPA entities tested by each test
* class. This is for {@code SqlIntegrationTestSuite} members.
*/
JpaIntegrationWithCoverageExtension jpaIntegrationWithCoverageExtension = null;
private boolean withDatastoreAndCloudSql;
private boolean enableJpaEntityCoverageCheck;
private boolean withLocalModules;
private boolean withTaskQueue;
private boolean withUserService;
@@ -103,16 +129,28 @@ public final class AppEngineRule extends ExternalResource {
private String taskQueueXml;
private UserInfo userInfo;
// Test Objectify entity classes to be used with this AppEngineRule instance.
private ImmutableList<Class<?>> ofyTestEntities;
/** Builder for {@link AppEngineRule}. */
public static class Builder {
private AppEngineRule rule = new AppEngineRule();
private ImmutableList.Builder<Class<?>> ofyTestEntities = new ImmutableList.Builder();
/** Turn on the Datastore service and the Cloud SQL service. */
public Builder withDatastoreAndCloudSql() {
rule.withDatastoreAndCloudSql = true;
return this;
}
/**
* Enables JPA entity coverage check if {@code enabled} is true. This should only be enabled for
* members of SqlIntegrationTestSuite.
*/
public Builder enableJpaEntityCoverageCheck(boolean enabled) {
rule.enableJpaEntityCoverageCheck = enabled;
return this;
}
/** Turn on the use of local modules. */
public Builder withLocalModules() {
@@ -149,7 +187,29 @@ public final class AppEngineRule extends ExternalResource {
return this;
}
/**
* Declares test-only entities to be registered with {@code ObjectifyService}.
*
* <p>Note that {@code ObjectifyService} silently replaces the current registration for a given
* kind when a different class is registered for this kind. Since {@code ObjectifyService} does
* not support de-registration, each test entity class must be of a unique kind across the
* entire code base. Although this requirement can be worked around by using different {@code
* ObjectifyService} instances for each test (class), the setup overhead would rise
* significantly.
*
* @see AppEngineRule#register(Class)
*/
@SafeVarargs
public final Builder withOfyTestEntities(Class<?>... entities) {
ofyTestEntities.add(entities);
return this;
}
public AppEngineRule build() {
checkState(
!rule.enableJpaEntityCoverageCheck || rule.withDatastoreAndCloudSql,
"withJpaEntityCoverageCheck enabled without Cloud SQL");
rule.ofyTestEntities = this.ofyTestEntities.build();
return rule;
}
}
@@ -256,7 +316,45 @@ public final class AppEngineRule extends ExternalResource {
.build();
}
/** Hack to make sure AppEngineRule is always wrapped in a TemporaryFolder rule. */
/** Called before every test method. JUnit 5 only. */
@Override
public void beforeEach(ExtensionContext context) throws Exception {
before();
if (withDatastoreAndCloudSql) {
JpaTestRules.Builder builder = new JpaTestRules.Builder();
if (clock != null) {
builder.withClock(clock);
}
if (enableJpaEntityCoverageCheck) {
jpaIntegrationWithCoverageExtension = builder.buildIntegrationWithCoverageExtension();
jpaIntegrationWithCoverageExtension.beforeEach(context);
} else {
jpaIntegrationTestRule = builder.buildIntegrationTestRule();
jpaIntegrationTestRule.before();
}
}
}
/** Called after each test method. JUnit 5 only. */
@Override
public void afterEach(ExtensionContext context) throws Exception {
if (withDatastoreAndCloudSql) {
if (enableJpaEntityCoverageCheck) {
jpaIntegrationWithCoverageExtension.afterEach(context);
} else {
jpaIntegrationTestRule.after();
}
}
after();
}
/**
* Hack to make sure AppEngineRule is always wrapped in a {@link JpaIntegrationWithCoverageRule}.
* JUnit 4 only.
*/
// Note: Even with @EnableRuleMigrationSupport, JUnit5 runner does not call this method.
// Note 2: Do not migrate members of SqlIntegrationTestSuite to JUnit5 individually.
// TODO(weiminyu): migrate SqlIntegrationTestSuite in one go.
@Override
public Statement apply(Statement base, Description description) {
Statement statement = base;
@@ -265,14 +363,18 @@ public final class AppEngineRule extends ExternalResource {
if (clock != null) {
builder.withClock(clock);
}
statement = builder.buildIntegrationWithCoverageRule().apply(base, description);
checkState(
!enableJpaEntityCoverageCheck,
"JUnit4 tests must not enable withJpaEntityCoverageCheck.");
statement = builder.buildIntegrationTestRule().apply(base, description);
}
return temporaryFolder.apply(super.apply(statement, description), description);
return super.apply(statement, description);
}
@Override
protected void before() throws IOException {
setupLogging();
temporaryFolder.create();
Set<LocalServiceTestConfig> configs = new HashSet<>();
if (withUrlFetch) {
configs.add(new LocalURLFetchServiceTestConfig());
@@ -334,6 +436,7 @@ public final class AppEngineRule extends ExternalResource {
// Reset id allocation in ObjectifyService so that ids are deterministic in tests.
ObjectifyService.resetNextTestId();
loadInitialData();
this.ofyTestEntities.forEach(AppEngineRule::register);
}
}
@@ -346,10 +449,10 @@ public final class AppEngineRule extends ExternalResource {
helper = null;
// Test that Datastore didn't need any indexes we don't have listed in our index file.
File indexFile = new File(temporaryFolder.getRoot(), "datastore-indexes-auto.xml");
if (!indexFile.exists()) {
return;
}
try {
if (!indexFile.exists()) {
return;
}
String indexFileContent = Files.asCharSource(indexFile, UTF_8).read();
if (indexFileContent.trim().isEmpty()) {
return;
@@ -361,9 +464,29 @@ public final class AppEngineRule extends ExternalResource {
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
temporaryFolder.delete();
}
}
/**
* Registers test-only Objectify entities and checks for re-registrations for the same kind by
* different classes.
*/
private static void register(Class<?> entityClass) {
String kind = Key.getKind(entityClass);
Optional.ofNullable(com.googlecode.objectify.ObjectifyService.factory().getMetadata(kind))
.ifPresent(
meta ->
checkState(
meta.getEntityClass() == entityClass,
"Cannot register %s. The Kind %s is already registered with %s.",
entityClass.getName(),
kind,
meta.getEntityClass().getName()));
com.googlecode.objectify.ObjectifyService.register(entityClass);
}
/** Install {@code testing/logging.properties} so logging is less noisy. */
private static void setupLogging() throws IOException {
LogManager.getLogManager()
@@ -18,6 +18,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertThrows;
import com.google.common.base.Joiner;
import com.googlecode.objectify.annotation.Entity;
import java.io.File;
import java.io.IOException;
import org.junit.Before;
@@ -88,9 +89,29 @@ public class AppEngineRuleTest {
assertThrows(AssertionError.class, () -> appEngineRule.after());
}
@Test
public void testRegisterOfyEntities_failure() {
AppEngineRule appEngineRule =
AppEngineRule.builder()
.withDatastoreAndCloudSql()
.withOfyTestEntities(google.registry.testing.TestObject.class, TestObject.class)
.build();
String expectedErrorMessage =
String.format(
"Cannot register %s. The Kind %s is already registered with %s",
TestObject.class.getName(),
"TestObject",
google.registry.testing.TestObject.class.getName());
assertThrows(expectedErrorMessage, IllegalStateException.class, appEngineRule::before);
appEngineRule.after();
}
private void writeAutoIndexFile(String content) throws IOException {
com.google.common.io.Files.asCharSink(
new File(temporaryFolder.getRoot(), "datastore-indexes-auto.xml"), UTF_8)
.write(content);
}
@Entity
private static final class TestObject {}
}
@@ -0,0 +1,104 @@
// Copyright 2020 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 com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.Environment;
import java.util.Map;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.testcontainers.shaded.com.google.common.collect.ImmutableMap;
/**
* Allows instantiation of Datastore {@code Entity entities} without the heavyweight {@code
* AppEngineRule}.
*
* <p>When used together with {@code JpaIntegrationWithCoverageExtension}, this extension must be
* registered first. For consistency's sake, it is recommended that the field for this extension be
* annotated with {@code @org.junit.jupiter.api.Order(value = 1)}. Please refer to {@link
* google.registry.model.domain.DomainBaseSqlTest} for example, and to <a
* href="https://junit.org/junit5/docs/current/user-guide/#extensions-registration-programmatic">
* JUnit 5 User Guide</a> for details of extension ordering.
*/
public class DatastoreEntityExtension implements BeforeEachCallback, AfterEachCallback {
private static final Environment PLACEHOLDER_ENV = new PlaceholderEnvironment();
@Override
public void beforeEach(ExtensionContext context) {
ApiProxy.setEnvironmentForCurrentThread(PLACEHOLDER_ENV);
}
@Override
public void afterEach(ExtensionContext context) {
// Clear the cached instance.
ApiProxy.setEnvironmentForCurrentThread(null);
}
private static final class PlaceholderEnvironment implements Environment {
@Override
public String getAppId() {
return "PlaceholderAppId";
}
@Override
public Map<String, Object> getAttributes() {
return ImmutableMap.of();
}
@Override
public String getModuleId() {
throw new UnsupportedOperationException();
}
@Override
public String getVersionId() {
throw new UnsupportedOperationException();
}
@Override
public String getEmail() {
throw new UnsupportedOperationException();
}
@Override
public boolean isLoggedIn() {
throw new UnsupportedOperationException();
}
@Override
public boolean isAdmin() {
throw new UnsupportedOperationException();
}
@Override
public String getAuthDomain() {
throw new UnsupportedOperationException();
}
@SuppressWarnings("deprecation")
@Override
public String getRequestNamespace() {
throw new UnsupportedOperationException();
}
@Override
public long getRemainingMillis() {
throw new UnsupportedOperationException();
}
}
}
@@ -22,6 +22,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.Nullable;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.rules.ExternalResource;
/**
@@ -44,9 +46,10 @@ import org.junit.rules.ExternalResource;
* to be evil; but hopefully one day we'll be able to delete this class and do things
* <i>properly</i> with <a href="http://square.github.io/dagger/">Dagger</a> dependency injection.
*
* <p>You use this class by declaring it as a {@link org.junit.Rule &#064;Rule} field and then
* calling {@link #setStaticField} from either your {@link org.junit.Test &#064;Test} or {@link
* org.junit.Before &#064;Before} methods. For example:
* <p>This class temporarily supports both JUnit4 and JUnit5. You use this class in JUnit4 by
* declaring it as a {@link org.junit.Rule &#064;Rule} field and then calling {@link
* #setStaticField} from either your {@link org.junit.Test &#064;Test} or {@link org.junit.Before
* &#064;Before} methods. For example:
*
* <pre>
* // Doomsday.java
@@ -85,7 +88,7 @@ import org.junit.rules.ExternalResource;
* @see google.registry.util.NonFinalForTesting
* @see org.junit.rules.ExternalResource
*/
public class InjectRule extends ExternalResource {
public class InjectRule extends ExternalResource implements AfterEachCallback {
private static class Change {
private final Field field;
@@ -140,6 +143,11 @@ public class InjectRule extends ExternalResource {
injected.add(field);
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
after();
}
@Override
protected void after() {
RuntimeException thrown = null;
@@ -14,12 +14,19 @@
package google.registry.testing;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.AppEngineRule.makeRegistrar1;
import static org.junit.Assert.assertThrows;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import google.registry.model.registry.RegistryLockDao;
import google.registry.schema.domain.RegistryLock;
import java.sql.SQLException;
import java.util.Optional;
import javax.persistence.RollbackException;
import org.junit.function.ThrowingRunnable;
/** Static utils for setting up and retrieving test resources from the SQL database. */
public class SqlHelper {
@@ -52,5 +59,19 @@ public class SqlHelper {
return jpaTm().transact(() -> RegistryLockDao.getByRevisionId(revisionId));
}
public static void saveRegistrar(String clientId) {
jpaTm()
.transact(
() -> jpaTm().saveNew(makeRegistrar1().asBuilder().setClientId(clientId).build()));
}
public static void assertThrowForeignKeyViolation(ThrowingRunnable runnable) {
RollbackException thrown = assertThrows(RollbackException.class, runnable);
assertThat(Throwables.getRootCause(thrown)).isInstanceOf(SQLException.class);
assertThat(Throwables.getRootCause(thrown))
.hasMessageThat()
.contains("violates foreign key constraint");
}
private SqlHelper() {}
}
@@ -17,7 +17,6 @@ package google.registry.testing;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Parent;
@@ -30,9 +29,6 @@ import google.registry.model.common.EntityGroupRoot;
*/
@Entity
public class TestObject extends ImmutableObject {
static {
ObjectifyService.register(TestObject.class); // Register this kind on first reference.
}
@Parent
Key<EntityGroupRoot> parent;
@@ -70,9 +66,6 @@ public class TestObject extends ImmutableObject {
@Entity
@VirtualEntity
public static class TestVirtualObject extends ImmutableObject {
static {
ObjectifyService.register(TestVirtualObject.class); // Register this kind on first reference.
}
@Id
String id;
@@ -76,6 +76,12 @@ public class CheckDomainCommandTest extends EppToolCommandTestCase<CheckDomainCo
eppVerifier.expectDryRun().expectClientId("adminreg").verifySent("domain_check_fee.xml");
}
@Test
public void testSuccess_allocationToken_reserved() throws Exception {
runCommand("--client=NewRegistrar", "--token=abc123", "example.tld");
eppVerifier.expectDryRun().verifySent("domain_check_fee_allocationtoken.xml");
}
@Test
public void testFailure_NoMainParameter() {
assertThrows(ParameterException.class, () -> runCommand("--client=NewRegistrar"));
@@ -17,36 +17,60 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import google.registry.testing.AppEngineRule;
import com.google.common.io.Resources;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.tools.LevelDbFileBuilder.Property;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import org.junit.Rule;
import org.junit.Test;
import java.net.URL;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class CompareDbBackupsTest {
private static final int BASE_ID = 1001;
// Capture standard output.
private final ByteArrayOutputStream stdout = new ByteArrayOutputStream();
private PrintStream orgStdout;
@Rule public final TemporaryFolder tempFs = new TemporaryFolder();
public final TemporaryFolder tempFs = new TemporaryFolder();
@Rule
public final AppEngineRule appEngine = AppEngineRule.builder().withDatastoreAndCloudSql().build();
@RegisterExtension
public DatastoreEntityExtension datastoreEntityExtension = new DatastoreEntityExtension();
@BeforeEach
public void before() throws IOException {
orgStdout = System.out;
System.setOut(new PrintStream(stdout));
tempFs.create();
}
@AfterEach
public void after() {
System.setOut(orgStdout);
tempFs.delete();
}
@Test
public void testCommand() throws Exception {
public void testLoadBackup() {
URL backupRootFolder = Resources.getResource("google/registry/tools/datastore-export");
CompareDbBackups.main(new String[] {backupRootFolder.getPath(), backupRootFolder.getPath()});
String output = new String(stdout.toByteArray(), UTF_8);
assertThat(output).containsMatch("Both sets have the same 41 entities");
}
@Test
public void testCompareBackups() throws Exception {
// Create two directories corresponding to data dumps.
File dump1 = tempFs.newFolder("dump1");
LevelDbFileBuilder builder = new LevelDbFileBuilder(new File(dump1, "data1"));
LevelDbFileBuilder builder = new LevelDbFileBuilder(new File(dump1, "output-data1"));
builder.addEntityProto(
BASE_ID,
Property.create("eeny", 100L),
@@ -60,7 +84,7 @@ public class CompareDbBackupsTest {
builder.build();
File dump2 = tempFs.newFolder("dump2");
builder = new LevelDbFileBuilder(new File(dump2, "data2"));
builder = new LevelDbFileBuilder(new File(dump2, "output-data2"));
builder.addEntityProto(
BASE_ID + 1,
Property.create("moxey", 100L),
@@ -73,7 +97,6 @@ public class CompareDbBackupsTest {
Property.create("strutz", 300L));
builder.build();
System.setOut(new PrintStream(stdout));
CompareDbBackups.main(new String[] {dump1.getCanonicalPath(), dump2.getCanonicalPath()});
String output = new String(stdout.toByteArray(), UTF_8);
assertThat(output)
@@ -61,18 +61,43 @@ public abstract class CreateOrUpdateReservedListCommandTestCase<
@Test
public void testFailure_fileDoesntExist() {
assertThrows(
ParameterException.class,
() ->
runCommandForced(
"--name=xn--q9jyb4c-blah", "--input=" + reservedTermsPath + "-nonexistent"));
assertThat(
assertThrows(
ParameterException.class,
() ->
runCommandForced(
"--name=xn--q9jyb4c_common-reserved",
"--input=" + reservedTermsPath + "-nonexistent")))
.hasMessageThat()
.contains("-i not found");
}
@Test
public void testFailure_fileDoesntParse() {
assertThrows(
IllegalArgumentException.class,
() -> runCommandForced("--name=xn--q9jyb4c-blork", "--input=" + invalidReservedTermsPath));
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--name=xn--q9jyb4c_common-reserved",
"--input=" + invalidReservedTermsPath)))
.hasMessageThat()
.contains("No enum constant");
}
@Test
public void testFailure_invalidLabel_includesFullDomainName() throws Exception {
Files.asCharSink(new File(invalidReservedTermsPath), UTF_8)
.write("example.tld,FULLY_BLOCKED\n\n");
assertThat(
assertThrows(
IllegalArgumentException.class,
() ->
runCommandForced(
"--name=xn--q9jyb4c_common-reserved",
"--input=" + invalidReservedTermsPath)))
.hasMessageThat()
.isEqualTo("Label example.tld must not be a multi-level domain name");
}
google.registry.schema.tld.ReservedList createCloudSqlReservedList(
@@ -23,11 +23,8 @@ import google.registry.rde.Ghostryde;
import google.registry.testing.BouncyCastleProviderRule;
import google.registry.testing.FakeKeyringModule;
import google.registry.testing.InjectRule;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -61,19 +58,12 @@ public class GhostrydeCommandTest extends CommandTestCase<GhostrydeCommand> {
public final BouncyCastleProviderRule bouncy = new BouncyCastleProviderRule();
private Keyring keyring;
private PrintStream orgStdout;
@Before
public void before() {
keyring = new FakeKeyringModule().get();
command.rdeStagingDecryptionKey = keyring::getRdeStagingDecryptionKey;
command.rdeStagingEncryptionKey = keyring::getRdeStagingEncryptionKey;
orgStdout = System.out;
}
@After
public void after() {
System.setOut(orgStdout);
}
@Test
@@ -153,9 +143,7 @@ public class GhostrydeCommandTest extends CommandTestCase<GhostrydeCommand> {
Path inFile = tmpDir.newFolder().toPath().resolve("atrain.ghostryde");
Files.write(
inFile, Ghostryde.encode(SONG_BY_CHRISTINA_ROSSETTI, keyring.getRdeStagingEncryptionKey()));
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
runCommand("--decrypt", "--input=" + inFile);
assertThat(out.toByteArray()).isEqualTo(SONG_BY_CHRISTINA_ROSSETTI);
assertThat(getStdoutAsString().getBytes(UTF_8)).isEqualTo(SONG_BY_CHRISTINA_ROSSETTI);
}
}
@@ -76,7 +76,7 @@ public class RecordAccumulatorTest {
builder.build();
ImmutableSet<ComparableEntity> entities =
new RecordAccumulator().readDirectory(subdir).getComparableEntitySet();
new RecordAccumulator().readDirectory(subdir, any -> true).getComparableEntitySet();
assertThat(entities).containsExactly(e1, e2, e3);
}
}
@@ -98,19 +98,20 @@ public class RegistrarContactCommandTest extends CommandTestCase<RegistrarContac
"--visible_in_domain_whois_as_abuse=false",
"NewRegistrar");
RegistrarContact registrarContact = loadRegistrar("NewRegistrar").getContacts().asList().get(1);
assertThat(registrarContact).isEqualTo(
new RegistrarContact.Builder()
.setParent(registrar)
.setName("Judith Registrar")
.setEmailAddress("judith.doe@example.com")
.setRegistryLockEmailAddress("judith.doe@external.com")
.setPhoneNumber("+1.2125650000")
.setFaxNumber("+1.2125650001")
.setTypes(ImmutableSet.of(WHOIS))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.setVisibleInDomainWhoisAsAbuse(false)
.build());
assertThat(registrarContact)
.isEqualTo(
new RegistrarContact.Builder()
.setParent(registrar)
.setName("Judith Registrar")
.setEmailAddress("judith.doe@example.com")
.setRegistryLockEmailAddress("judith.doe@external.com")
.setPhoneNumber("+1.2125650000")
.setFaxNumber("+1.2125650001")
.setTypes(ImmutableSet.of(WHOIS))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.setVisibleInDomainWhoisAsAbuse(false)
.build());
}
@Test
@@ -305,17 +306,18 @@ public class RegistrarContactCommandTest extends CommandTestCase<RegistrarContac
"--visible_in_domain_whois_as_abuse=true",
"NewRegistrar");
RegistrarContact registrarContact = loadRegistrar("NewRegistrar").getContacts().asList().get(1);
assertThat(registrarContact).isEqualTo(
new RegistrarContact.Builder()
.setParent(registrar)
.setName("Jim Doe")
.setEmailAddress("jim.doe@example.com")
.setRegistryLockEmailAddress("jim.doe@external.com")
.setTypes(ImmutableSet.of(ADMIN, ABUSE))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.setVisibleInDomainWhoisAsAbuse(true)
.build());
assertThat(registrarContact)
.isEqualTo(
new RegistrarContact.Builder()
.setParent(registrar)
.setName("Jim Doe")
.setEmailAddress("jim.doe@example.com")
.setRegistryLockEmailAddress("jim.doe@external.com")
.setTypes(ImmutableSet.of(ADMIN, ABUSE))
.setVisibleInWhoisAsAdmin(true)
.setVisibleInWhoisAsTech(false)
.setVisibleInDomainWhoisAsAbuse(true)
.build());
assertThat(registrarContact.getGaeUserId()).isNull();
}
@@ -0,0 +1,88 @@
// Copyright 2020 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.tools;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertThrows;
import google.registry.testing.AppEngineRule;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.concurrent.locks.ReentrantLock;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link google.registry.tools.RegistryTool}. */
public class RegistryToolTest {
@RegisterExtension
public final AppEngineRule appEngine = AppEngineRule.builder().withDatastoreAndCloudSql().build();
// Lock for stdout/stderr. Note that this is static: since we're dealing with globals, we need
// to lock for the entire JVM.
private static final ReentrantLock stdoutLock = new ReentrantLock();
private PrintStream oldStdout;
private final ByteArrayOutputStream stdout = new ByteArrayOutputStream();
@BeforeEach
void beforeEach() {
// Capture standard output/error. This is problematic because gradle tests run in parallel in
// the same JVM. So first lock out any other tests in this JVM that are trying to do this
// trick.
// TODO(mcilwain): Turn this into a JUnit 5 extension.
stdoutLock.lock();
oldStdout = System.out;
System.setOut(new PrintStream(stdout));
}
@AfterEach
final void afterEach() {
System.setOut(oldStdout);
stdoutLock.unlock();
}
@Test
void test_displayAvailableCommands() throws Exception {
RegistryTool.main(new String[] {"-e", "unittest", "--commands"});
// Check for the existence of a few common commands.
assertThat(getStdout()).contains("login");
assertThat(getStdout()).contains("check_domain");
assertThat(getStdout()).contains("get_tld");
}
@Test
void test_noSubcommandSpecified_displaysAvailableCommands() throws Exception {
RegistryTool.main(new String[] {"-e", "unittest"});
assertThat(getStdout()).contains("The list of available subcommands is:");
assertThat(getStdout()).contains("login");
assertThat(getStdout()).contains("check_domain");
assertThat(getStdout()).contains("get_tld");
}
@Test
void test_noEnvironmentSpecified_throwsCorrectError() {
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> RegistryTool.main(new String[] {}));
assertThat(thrown).hasMessageThat().contains("Please specify the environment flag");
}
private String getStdout() {
return new String(stdout.toByteArray(), UTF_8);
}
}
@@ -15,21 +15,27 @@
package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.testing.DatastoreHelper.newDomainBase;
import static google.registry.testing.DatastoreHelper.persistActiveDomain;
import static google.registry.testing.DatastoreHelper.persistDeletedDomain;
import static google.registry.testing.DatastoreHelper.persistNewRegistrar;
import static google.registry.testing.DatastoreHelper.persistResource;
import static org.junit.Assert.assertThrows;
import com.beust.jcommander.ParameterException;
import com.google.common.collect.ImmutableMap;
import google.registry.model.domain.DomainBase;
import google.registry.model.ofy.Ofy;
import google.registry.model.registrar.Registrar;
import google.registry.testing.FakeClock;
import google.registry.testing.InjectRule;
import google.registry.util.Clock;
import java.util.List;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
/** Unit tests for {@link RenewDomainCommand}. */
public class RenewDomainCommandTest extends EppToolCommandTestCase<RenewDomainCommand> {
@@ -63,23 +69,57 @@ public class RenewDomainCommandTest extends EppToolCommandTestCase<RenewDomainCo
.verifyNoMoreSent();
}
private static List<DomainBase> persistThreeDomains() {
ImmutableList.Builder<DomainBase> domains = new ImmutableList.Builder<>();
domains.add(
persistActiveDomain(
"domain1.tld",
DateTime.parse("2014-09-05T05:05:05Z"),
DateTime.parse("2015-09-05T05:05:05Z")));
domains.add(
persistActiveDomain(
"domain2.tld",
DateTime.parse("2014-11-05T05:05:05Z"),
DateTime.parse("2015-11-05T05:05:05Z")));
// The third domain is owned by a different registrar.
domains.add(
persistResource(
newDomainBase("domain3.tld")
.asBuilder()
.setCreationTimeForTest(DateTime.parse("2015-01-05T05:05:05Z"))
.setRegistrationExpirationTime(DateTime.parse("2016-01-05T05:05:05Z"))
.setPersistedCurrentSponsorClientId("NewRegistrar")
.build()));
return domains.build();
}
@Test
public void testSuccess_multipleDomains() throws Exception {
persistActiveDomain(
"domain1.tld",
DateTime.parse("2014-09-05T05:05:05Z"),
DateTime.parse("2015-09-05T05:05:05Z"));
persistActiveDomain(
"domain2.tld",
DateTime.parse("2014-11-05T05:05:05Z"),
DateTime.parse("2015-11-05T05:05:05Z"));
persistActiveDomain(
"domain3.tld",
DateTime.parse("2015-01-05T05:05:05Z"),
DateTime.parse("2016-01-05T05:05:05Z"));
public void testSuccess_multipleDomains_renewsAndUsesEachDomainsRegistrar() throws Exception {
persistThreeDomains();
runCommandForced("--period 3", "domain1.tld", "domain2.tld", "domain3.tld");
eppVerifier
.expectClientId("TheRegistrar")
.verifySent(
"domain_renew.xml",
ImmutableMap.of("DOMAIN", "domain1.tld", "EXPDATE", "2015-09-05", "YEARS", "3"))
.verifySent(
"domain_renew.xml",
ImmutableMap.of("DOMAIN", "domain2.tld", "EXPDATE", "2015-11-05", "YEARS", "3"))
.expectClientId("NewRegistrar")
.verifySent(
"domain_renew.xml",
ImmutableMap.of("DOMAIN", "domain3.tld", "EXPDATE", "2016-01-05", "YEARS", "3"))
.verifyNoMoreSent();
}
@Test
public void testSuccess_multipleDomains_renewsAndUsesSpecifiedRegistrar() throws Exception {
persistThreeDomains();
persistNewRegistrar("reg3", "Registrar 3", Registrar.Type.REAL, 9783L);
runCommandForced("--period 3", "domain1.tld", "domain2.tld", "domain3.tld", "-u", "-c reg3");
eppVerifier
.expectClientId("reg3")
.expectSuperuser()
.verifySent(
"domain_renew.xml",
ImmutableMap.of("DOMAIN", "domain1.tld", "EXPDATE", "2015-09-05", "YEARS", "3"))
@@ -106,9 +146,7 @@ public class RenewDomainCommandTest extends EppToolCommandTestCase<RenewDomainCo
persistDeletedDomain("deleted.tld", DateTime.parse("2012-10-05T05:05:05Z"));
IllegalArgumentException e =
assertThrows(IllegalArgumentException.class, () -> runCommandForced("deleted.tld"));
assertThat(e)
.hasMessageThat()
.isEqualTo("Domain 'deleted.tld' does not exist or is deleted");
assertThat(e).hasMessageThat().isEqualTo("Domain 'deleted.tld' does not exist or is deleted");
}
@Test
@@ -128,9 +166,7 @@ public class RenewDomainCommandTest extends EppToolCommandTestCase<RenewDomainCo
IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class, () -> runCommandForced("domain.tld", "--period 10"));
assertThat(e)
.hasMessageThat()
.isEqualTo("Cannot renew domains for 10 or more years");
assertThat(e).hasMessageThat().isEqualTo("Cannot renew domains for 10 or more years");
}
@Test
@@ -26,12 +26,18 @@ import com.google.common.collect.ImmutableMap;
import google.registry.model.registry.label.ReservedList;
import google.registry.schema.tld.ReservedList.ReservedEntry;
import google.registry.schema.tld.ReservedListDao;
import org.junit.Before;
import org.junit.Test;
/** Unit tests for {@link UpdateReservedListCommand}. */
public class UpdateReservedListCommandTest extends
CreateOrUpdateReservedListCommandTestCase<UpdateReservedListCommand> {
@Before
public void setup() {
populateInitialReservedListInDatastore(true);
}
private void populateInitialReservedListInDatastore(boolean shouldPublish) {
persistResource(
new ReservedList.Builder()
@@ -63,7 +69,6 @@ public class UpdateReservedListCommandTest extends
@Test
public void testSuccess_lastUpdateTime_updatedCorrectly() throws Exception {
populateInitialReservedListInDatastore(true);
ReservedList original = ReservedList.get("xn--q9jyb4c_common-reserved").get();
runCommandForced("--input=" + reservedTermsPath);
ReservedList updated = ReservedList.get("xn--q9jyb4c_common-reserved").get();
@@ -90,7 +95,6 @@ public class UpdateReservedListCommandTest extends
}
private void runSuccessfulUpdateTest(String... args) throws Exception {
populateInitialReservedListInDatastore(true);
runCommandForced(args);
assertThat(ReservedList.get("xn--q9jyb4c_common-reserved")).isPresent();
ReservedList reservedList = ReservedList.get("xn--q9jyb4c_common-reserved").get();
@@ -114,7 +118,6 @@ public class UpdateReservedListCommandTest extends
@Test
public void testSaveToCloudSql_succeeds() throws Exception {
populateInitialReservedListInDatastore(true);
populateInitialReservedListInCloudSql(true);
runCommandForced("--name=xn--q9jyb4c_common-reserved", "--input=" + reservedTermsPath);
verifyXnq9jyb4cInDatastore();
@@ -126,7 +129,6 @@ public class UpdateReservedListCommandTest extends
// Note that, during the dual-write phase, we always save the reserved list to Cloud SQL without
// checking if there is a list with same name. This is to backfill the existing list in Cloud
// Datastore when we update it.
populateInitialReservedListInDatastore(true);
runCommandForced("--name=xn--q9jyb4c_common-reserved", "--input=" + reservedTermsPath);
verifyXnq9jyb4cInDatastore();
assertThat(ReservedListDao.checkExists("xn--q9jyb4c_common-reserved")).isTrue();
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<check>
<domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
</domain:check>
</check>
<extension>
<fee:check xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:domain>
<fee:name>example.tld</fee:name>
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
</fee:check>
<allocationToken:allocationToken
xmlns:allocationToken="urn:ietf:params:xml:ns:allocationToken-1.0">
abc123
</allocationToken:allocationToken>
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>
@@ -26,12 +26,12 @@ org.checkerframework:checker-qual:2.10.0
org.flywaydb:flyway-core:5.2.4
org.hamcrest:hamcrest-core:1.3
org.jetbrains:annotations:17.0.0
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.opentest4j:opentest4j:1.2.0
org.rnorth.duct-tape:duct-tape:1.0.8
org.rnorth.visible-assertions:visible-assertions:2.1.2
@@ -26,12 +26,12 @@ org.checkerframework:checker-qual:2.10.0
org.flywaydb:flyway-core:5.2.4
org.hamcrest:hamcrest-core:1.3
org.jetbrains:annotations:17.0.0
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.vintage:junit-vintage-engine:5.6.0
org.junit:junit-bom:5.6.0
org.junit.jupiter:junit-jupiter-api:5.6.1
org.junit.jupiter:junit-jupiter-engine:5.6.1
org.junit.platform:junit-platform-commons:1.6.1
org.junit.platform:junit-platform-engine:1.6.1
org.junit.vintage:junit-vintage-engine:5.6.1
org.junit:junit-bom:5.6.1
org.opentest4j:opentest4j:1.2.0
org.rnorth.duct-tape:duct-tape:1.0.8
org.rnorth.visible-assertions:visible-assertions:2.1.2

Some files were not shown because too many files have changed in this diff Show More