Allow explicitly for null EPP resources in History objects (#790)

* Allow explicitly for null EPP resources in History objects

* Repo IDs should always be nonnull

* Add a test to verify loading / comparison of legacy HistoryEntry objects

* Format javadoc + annotations

* More javadoc changes

* V52 -> V56

* V56 -> V57

* saveNew -> insert in new tests
This commit is contained in:
gbrodman 2020-09-21 15:50:15 -04:00 committed by GitHub
parent 597f5746a4
commit 2a5d9c8ef5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 503 additions and 100 deletions

View file

@ -32,17 +32,25 @@ import javax.annotation.Nullable;
/** Truth subject for asserting things about ImmutableObjects that are not built in. */
public final class ImmutableObjectSubject extends Subject {
private final ImmutableObject actual;
@Nullable private final ImmutableObject actual;
protected ImmutableObjectSubject(FailureMetadata failureMetadata, ImmutableObject actual) {
protected ImmutableObjectSubject(
FailureMetadata failureMetadata, @Nullable ImmutableObject actual) {
super(failureMetadata, actual);
this.actual = actual;
}
public void isEqualExceptFields(ImmutableObject expected, String... ignoredFields) {
Map<Field, Object> actualFields = filterFields(actual, ignoredFields);
Map<Field, Object> expectedFields = filterFields(expected, ignoredFields);
assertThat(actualFields).containsExactlyEntriesIn(expectedFields);
public void isEqualExceptFields(@Nullable ImmutableObject expected, String... ignoredFields) {
if (actual == null) {
assertThat(expected).isNull();
} else {
assertThat(expected).isNotNull();
}
if (actual != null) {
Map<Field, Object> actualFields = filterFields(actual, ignoredFields);
Map<Field, Object> expectedFields = filterFields(expected, ignoredFields);
assertThat(actualFields).containsExactlyEntriesIn(expectedFields);
}
}
public static Correspondence<ImmutableObject, ImmutableObject> immutableObjectCorrespondence(

View file

@ -53,7 +53,32 @@ public class ContactHistoryTest extends EntityTestCase {
jpaTm()
.transact(
() -> {
ContactHistory fromDatabase = jpaTm().load(VKey.createSql(ContactHistory.class, 1L));
ContactHistory fromDatabase =
jpaTm().load(VKey.createSql(ContactHistory.class, contactHistory.getId()));
assertContactHistoriesEqual(fromDatabase, contactHistory);
assertThat(fromDatabase.getContactRepoId().getSqlKey())
.isEqualTo(contactHistory.getContactRepoId().getSqlKey());
});
}
@Test
void testLegacyPersistence_nullContactBase() {
saveRegistrar("TheRegistrar");
ContactResource contact = newContactResourceWithRoid("contactId", "contact1");
jpaTm().transact(() -> jpaTm().insert(contact));
VKey<ContactResource> contactVKey = contact.createVKey();
ContactResource contactFromDb = jpaTm().transact(() -> jpaTm().load(contactVKey));
ContactHistory contactHistory =
createContactHistory(contactFromDb, contactVKey).asBuilder().setContactBase(null).build();
contactHistory.id = null;
jpaTm().transact(() -> jpaTm().insert(contactHistory));
jpaTm()
.transact(
() -> {
ContactHistory fromDatabase =
jpaTm().load(VKey.createSql(ContactHistory.class, contactHistory.getId()));
assertContactHistoriesEqual(fromDatabase, contactHistory);
assertThat(fromDatabase.getContactRepoId().getSqlKey())
.isEqualTo(contactHistory.getContactRepoId().getSqlKey());
@ -106,7 +131,7 @@ public class ContactHistoryTest extends EntityTestCase {
.that(one)
.isEqualExceptFields(two, "contactBase", "contactRepoId", "parent");
assertAboutImmutableObjects()
.that(one.getContactBase())
.isEqualExceptFields(two.getContactBase(), "repoId");
.that(one.getContactBase().orElse(null))
.isEqualExceptFields(two.getContactBase().orElse(null), "repoId");
}
}

View file

@ -40,6 +40,7 @@ import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Tests for {@link DomainHistory}. */
@ -49,28 +50,33 @@ public class DomainHistoryTest extends EntityTestCase {
super(JpaEntityCoverageCheck.ENABLED);
}
@BeforeEach
void beforeEach() {
saveRegistrar("TheRegistrar");
}
@Test
void testPersistence() {
saveRegistrar("TheRegistrar");
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");
ContactResource contact = newContactResourceWithRoid("contactId", "contact1");
DomainBase domain = createDomainWithContactsAndHosts();
DomainHistory domainHistory = createDomainHistory(domain);
domainHistory.id = null;
jpaTm().transact(() -> jpaTm().insert(domainHistory));
jpaTm()
.transact(
() -> {
jpaTm().insert(host);
jpaTm().insert(contact);
DomainHistory fromDatabase = jpaTm().load(domainHistory.createVKey());
assertDomainHistoriesEqual(fromDatabase, domainHistory);
assertThat(fromDatabase.getDomainRepoId().getSqlKey())
.isEqualTo(domainHistory.getDomainRepoId().getSqlKey());
});
}
DomainBase domain =
newDomainBase("example.tld", "domainRepoId", contact)
.asBuilder()
.setNameservers(host.createVKey())
.build();
jpaTm().transact(() -> jpaTm().insert(domain));
DomainHistory domainHistory = createDomainHistory(domain);
@Test
void testLegacyPersistence_nullResource() {
DomainBase domain = createDomainWithContactsAndHosts();
DomainHistory domainHistory =
createDomainHistory(domain).asBuilder().setDomainContent(null).build();
domainHistory.id = null;
jpaTm().transact(() -> jpaTm().insert(domainHistory));
@ -91,8 +97,6 @@ public class DomainHistoryTest extends EntityTestCase {
@Test
void testOfyPersistence() {
saveRegistrar("TheRegistrar");
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");
ContactResource contact = newContactResourceWithRoid("contactId", "contact1");
@ -115,7 +119,7 @@ public class DomainHistoryTest extends EntityTestCase {
tm().transact(() -> tm().insert(domainHistory));
// retrieving a HistoryEntry or a DomainHistory with the same key should return the same object
// note: due to the @EntitySubclass annotation. all Keys for ContactHistory objects will have
// note: due to the @EntitySubclass annotation. all Keys for DomainHistory objects will have
// type HistoryEntry
VKey<DomainHistory> domainHistoryVKey =
VKey.createOfy(DomainHistory.class, Key.create(domainHistory));
@ -127,11 +131,33 @@ public class DomainHistoryTest extends EntityTestCase {
assertThat(domainHistoryFromDb).isEqualTo(historyEntryFromDb);
}
static DomainBase createDomainWithContactsAndHosts() {
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");
ContactResource contact = newContactResourceWithRoid("contactId", "contact1");
jpaTm()
.transact(
() -> {
jpaTm().insert(host);
jpaTm().insert(contact);
});
DomainBase domain =
newDomainBase("example.tld", "domainRepoId", contact)
.asBuilder()
.setNameservers(host.createVKey())
.build();
jpaTm().transact(() -> jpaTm().insert(domain));
return domain;
}
static void assertDomainHistoriesEqual(DomainHistory one, DomainHistory two) {
assertAboutImmutableObjects()
.that(one)
.isEqualExceptFields(
two, "domainContent", "domainRepoId", "parent", "nsHosts", "domainTransactionRecords");
assertThat(one.getDomainContent().map(DomainContent::getDomainName))
.isEqualTo(two.getDomainContent().map(DomainContent::getDomainName));
// NB: the record's ID gets reset by Hibernate, causing the hash code to differ so we have to
// compare it separately
assertThat(one.getDomainTransactionRecords())

View file

@ -1,4 +1,4 @@
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
// 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.
@ -45,7 +45,8 @@ public class HostHistoryTest extends EntityTestCase {
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");
jpaTm().transact(() -> jpaTm().insert(host));
VKey<HostResource> hostVKey = VKey.createSql(HostResource.class, "host1");
VKey<HostResource> hostVKey =
VKey.create(HostResource.class, "host1", Key.create(HostResource.class, "host1"));
HostResource hostFromDb = jpaTm().transact(() -> jpaTm().load(hostVKey));
HostHistory hostHistory = createHostHistory(hostFromDb, hostVKey);
hostHistory.id = null;
@ -62,19 +63,46 @@ public class HostHistoryTest extends EntityTestCase {
}
@Test
public void testOfySave() {
void testLegacyPersistence_nullHostBase() {
saveRegistrar("TheRegistrar");
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");
jpaTm().transact(() -> jpaTm().insert(host));
VKey<HostResource> hostVKey =
VKey.create(HostResource.class, "host1", Key.create(HostResource.class, "host1"));
HostResource hostFromDb = jpaTm().transact(() -> jpaTm().load(hostVKey));
HostHistory hostHistory =
createHostHistory(hostFromDb, hostVKey).asBuilder().setHostBase(null).build();
hostHistory.id = null;
jpaTm().transact(() -> jpaTm().insert(hostHistory));
jpaTm()
.transact(
() -> {
HostHistory fromDatabase =
jpaTm().load(VKey.createSql(HostHistory.class, hostHistory.getId()));
assertHostHistoriesEqual(fromDatabase, hostHistory);
assertThat(fromDatabase.getHostRepoId().getSqlKey())
.isEqualTo(hostHistory.getHostRepoId().getSqlKey());
});
}
@Test
void testOfySave() {
saveRegistrar("registrar1");
HostResource host = newHostResourceWithRoid("ns1.example.com", "host1");
tm().transact(() -> tm().insert(host));
VKey<HostResource> hostVKey = VKey.create(HostResource.class, "host1", Key.create(host));
VKey<HostResource> hostVKey =
VKey.create(HostResource.class, "host1", Key.create(HostResource.class, "host1"));
HostResource hostFromDb = tm().transact(() -> tm().load(hostVKey));
HostHistory hostHistory = createHostHistory(hostFromDb, hostVKey);
fakeClock.advanceOneMilli();
tm().transact(() -> tm().insert(hostHistory));
// retrieving a HistoryEntry or a HostHistory with the same key should return the same object
// note: due to the @EntitySubclass annotation. all Keys for ContactHistory objects will have
// note: due to the @EntitySubclass annotation. all Keys for HostHistory objects will have
// type HistoryEntry
VKey<HostHistory> hostHistoryVKey = VKey.createOfy(HostHistory.class, Key.create(hostHistory));
VKey<HistoryEntry> historyEntryVKey =
@ -88,8 +116,8 @@ public class HostHistoryTest extends EntityTestCase {
private void assertHostHistoriesEqual(HostHistory one, HostHistory two) {
assertAboutImmutableObjects().that(one).isEqualExceptFields(two, "hostBase");
assertAboutImmutableObjects()
.that(one.getHostBase())
.isEqualExceptFields(two.getHostBase(), "repoId");
.that(one.getHostBase().orElse(null))
.isEqualExceptFields(two.getHostBase().orElse(null), "repoId");
}
private HostHistory createHostHistory(HostBase hostBase, VKey<HostResource> hostVKey) {

View file

@ -0,0 +1,211 @@
// 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.model.history;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatastoreHelper.newContactResourceWithRoid;
import static google.registry.testing.DatastoreHelper.newHostResourceWithRoid;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static google.registry.util.CollectionUtils.nullToEmpty;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableSet;
import com.googlecode.objectify.Key;
import google.registry.model.EntityTestCase;
import google.registry.model.EppResource;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.Period;
import google.registry.model.eppcommon.Trid;
import google.registry.model.host.HostHistory;
import google.registry.model.host.HostResource;
import google.registry.model.reporting.DomainTransactionRecord;
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Tests to check {@link HistoryEntry} + its subclasses' transitions to/from Datastore/SQL. */
public class LegacyHistoryObjectTest extends EntityTestCase {
public LegacyHistoryObjectTest() {
super(JpaEntityCoverageCheck.ENABLED);
}
@BeforeEach
void beforeEach() {
saveRegistrar("TheRegistrar");
}
@Test
void testFullConversion_contact() {
// Create+save an old contact HistoryEntry, reload it, and verify it's a proper ContactHistory
ContactResource contact = newContactResourceWithRoid("contactId", "contact1");
HistoryEntry legacyHistoryEntry = historyEntryBuilderFor(contact).build();
tm().transact(() -> tm().insert(legacyHistoryEntry));
// In Datastore, we will save it as HistoryEntry but retrieve it as ContactHistory
long historyEntryId = legacyHistoryEntry.getId();
HistoryEntry fromObjectify =
tm().transact(
() ->
tm().load(
VKey.create(
HistoryEntry.class,
historyEntryId,
Key.create(legacyHistoryEntry))));
// The objects will be mostly the same, but the ContactHistory object has a couple extra fields
assertAboutImmutableObjects()
.that(legacyHistoryEntry)
.isEqualExceptFields(fromObjectify, "contactBase", "contactRepoId");
assertThat(fromObjectify instanceof ContactHistory).isTrue();
ContactHistory legacyContactHistory = (ContactHistory) fromObjectify;
// Next, save that from-Datastore object in SQL and verify we can load it back in
legacyContactHistory.id = null;
jpaTm()
.transact(
() -> {
jpaTm().insert(contact);
jpaTm().insert(legacyContactHistory);
});
ContactHistory legacyHistoryFromSql =
jpaTm()
.transact(
() ->
jpaTm()
.load(VKey.createSql(ContactHistory.class, legacyContactHistory.getId())));
assertAboutImmutableObjects()
.that(legacyContactHistory)
.isEqualExceptFields(legacyHistoryFromSql);
// can't compare contactRepoId directly since it doesn't save the ofy key
assertThat(legacyContactHistory.getContactRepoId().getSqlKey())
.isEqualTo(legacyHistoryFromSql.getContactRepoId().getSqlKey());
}
@Test
void testFullConversion_domain() {
// Create+save an old domain HistoryEntry, reload it, and verify it's a proper DomainHistory
DomainBase domain = DomainHistoryTest.createDomainWithContactsAndHosts();
HistoryEntry legacyHistoryEntry = historyEntryForDomain(domain);
tm().transact(() -> tm().insert(legacyHistoryEntry));
// In Datastore, we will save it as HistoryEntry but retrieve it as DomainHistory
long historyEntryId = legacyHistoryEntry.getId();
HistoryEntry fromObjectify =
tm().transact(
() ->
tm().load(
VKey.create(
HistoryEntry.class,
historyEntryId,
Key.create(legacyHistoryEntry))));
// The objects will be mostly the same, but the DomainHistory object has a couple extra fields
assertAboutImmutableObjects()
.that(legacyHistoryEntry)
.isEqualExceptFields(fromObjectify, "domainContent", "domainRepoId", "nsHosts");
assertThat(fromObjectify instanceof DomainHistory).isTrue();
DomainHistory legacyDomainHistory = (DomainHistory) fromObjectify;
// Next, save that from-Datastore object in SQL and verify we can load it back in
jpaTm().transact(() -> jpaTm().insert(legacyDomainHistory));
DomainHistory legacyHistoryFromSql =
jpaTm().transact(() -> jpaTm().load(legacyDomainHistory.createVKey()));
// Don't compare nsHosts directly because one is null and the other is empty
assertAboutImmutableObjects()
.that(legacyDomainHistory)
.isEqualExceptFields(
// NB: period, transaction records, and other client ID are added in #794
legacyHistoryFromSql, "period", "domainTransactionRecords", "otherClientId", "nsHosts");
assertThat(nullToEmpty(legacyDomainHistory.getNsHosts()))
.isEqualTo(nullToEmpty(legacyHistoryFromSql.getNsHosts()));
}
@Test
void testFullConversion_host() {
// Create+save an old host HistoryEntry, reload it, and verify it's a proper HostHistory
HostResource host = newHostResourceWithRoid("hs1.example.com", "host1");
HistoryEntry legacyHistoryEntry = historyEntryBuilderFor(host).build();
tm().transact(() -> tm().insert(legacyHistoryEntry));
// In Datastore, we will save it as HistoryEntry but retrieve it as HostHistory
long historyEntryId = legacyHistoryEntry.getId();
HistoryEntry fromObjectify =
tm().transact(
() ->
tm().load(
VKey.create(
HistoryEntry.class,
historyEntryId,
Key.create(legacyHistoryEntry))));
// The objects will be mostly the same, but the HostHistory object has a couple extra fields
assertAboutImmutableObjects()
.that(legacyHistoryEntry)
.isEqualExceptFields(fromObjectify, "hostBase", "hostRepoId");
assertThat(fromObjectify instanceof HostHistory).isTrue();
HostHistory legacyHostHistory = (HostHistory) fromObjectify;
// Next, save that from-Datastore object in SQL and verify we can load it back in
legacyHostHistory.id = null;
jpaTm()
.transact(
() -> {
jpaTm().insert(host);
jpaTm().insert(legacyHostHistory);
});
HostHistory legacyHistoryFromSql =
jpaTm()
.transact(
() -> jpaTm().load(VKey.createSql(HostHistory.class, legacyHostHistory.getId())));
assertAboutImmutableObjects().that(legacyHostHistory).isEqualExceptFields(legacyHistoryFromSql);
// can't compare hostRepoId directly since it doesn't save the ofy key in SQL
assertThat(legacyHostHistory.getHostRepoId().getSqlKey())
.isEqualTo(legacyHistoryFromSql.getHostRepoId().getSqlKey());
}
private HistoryEntry historyEntryForDomain(DomainBase domain) {
DomainTransactionRecord transactionRecord =
new DomainTransactionRecord.Builder()
.setTld("foobar")
.setReportingTime(fakeClock.nowUtc())
.setReportField(TransactionReportField.NET_ADDS_1_YR)
.setReportAmount(1)
.build();
return historyEntryBuilderFor(domain)
.setPeriod(Period.create(1, Period.Unit.YEARS))
.setDomainTransactionRecords(ImmutableSet.of(transactionRecord))
.setOtherClientId("TheRegistrar")
.build();
}
private HistoryEntry.Builder historyEntryBuilderFor(EppResource parent) {
return new HistoryEntry.Builder()
.setParent(parent)
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setXmlBytes("<xml></xml>".getBytes(UTF_8))
.setModificationTime(fakeClock.nowUtc())
.setClientId("TheRegistrar")
.setTrid(Trid.create("ABC-123", "server-trid"))
.setBySuperuser(false)
.setReason("reason")
.setRequestedByRegistrar(false);
}
}