From 5f0526c07a2bab455e2d65f18e75d2fff2ac7a2f Mon Sep 17 00:00:00 2001 From: Ben McIlwain Date: Thu, 13 Nov 2025 15:09:43 -0500 Subject: [PATCH] Make RDE generation resilient to missing contact rows (#2883) This will prevent RDE from failing once we delete all contacts, just as a fail-safe. BUG= http://b/439636188 --- .../registry/rde/DomainToXjcConverter.java | 30 ++++++++----------- .../rde/DomainToXjcConverterTest.java | 18 +++++++++++ 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/google/registry/rde/DomainToXjcConverter.java b/core/src/main/java/google/registry/rde/DomainToXjcConverter.java index fa7167b63..5e338a32b 100644 --- a/core/src/main/java/google/registry/rde/DomainToXjcConverter.java +++ b/core/src/main/java/google/registry/rde/DomainToXjcConverter.java @@ -168,20 +168,18 @@ final class DomainToXjcConverter { // as the holder of the domain name object. Optional> registrant = model.getRegistrant(); if (registrant.isPresent()) { - Contact registrantContact = tm().transact(() -> tm().loadByKey(registrant.get())); - checkState( - registrantContact != null, - "Registrant contact %s on domain %s does not exist", - registrant, - domainName); - bean.setRegistrant(registrantContact.getContactId()); + Optional registrantContact = + tm().transact(() -> tm().loadByKeyIfPresent(registrant.get())); + registrantContact.ifPresent(c -> bean.setRegistrant(c.getContactId())); } // o Zero or more OPTIONAL elements that contain identifiers // for the human or organizational social information objects // associated with the domain name object. for (DesignatedContact contact : model.getContacts()) { - bean.getContacts().add(convertDesignatedContact(contact, domainName)); + Optional contactType = + convertDesignatedContact(contact, domainName); + contactType.ifPresent(c -> bean.getContacts().add(c)); } // o An OPTIONAL element that contains the public key @@ -292,7 +290,7 @@ final class DomainToXjcConverter { } /** Converts {@link DesignatedContact} to {@link XjcDomainContactType}. */ - private static XjcDomainContactType convertDesignatedContact( + private static Optional convertDesignatedContact( DesignatedContact model, String domainName) { XjcDomainContactType bean = new XjcDomainContactType(); checkState( @@ -300,15 +298,13 @@ final class DomainToXjcConverter { "Contact key for type %s is null on domain %s", model.getType(), domainName); - Contact contact = tm().transact(() -> tm().loadByKey(model.getContactKey())); - checkState( - contact != null, - "Contact %s on domain %s does not exist", - model.getContactKey(), - domainName); + Optional contact = tm().transact(() -> tm().loadByKeyIfPresent(model.getContactKey())); + if (contact.isEmpty()) { + return Optional.empty(); + } bean.setType(XjcDomainContactAttrType.fromValue(Ascii.toLowerCase(model.getType().toString()))); - bean.setValue(contact.getContactId()); - return bean; + bean.setValue(contact.get().getContactId()); + return Optional.of(bean); } private DomainToXjcConverter() {} diff --git a/core/src/test/java/google/registry/rde/DomainToXjcConverterTest.java b/core/src/test/java/google/registry/rde/DomainToXjcConverterTest.java index 620453331..1871f4e69 100644 --- a/core/src/test/java/google/registry/rde/DomainToXjcConverterTest.java +++ b/core/src/test/java/google/registry/rde/DomainToXjcConverterTest.java @@ -14,9 +14,11 @@ package google.registry.rde; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.io.BaseEncoding.base16; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.persistEppResource; import static google.registry.testing.DatabaseHelper.persistResource; @@ -69,6 +71,7 @@ import google.registry.xjc.rdedomain.XjcRdeDomain; import google.registry.xjc.rdedomain.XjcRdeDomainElement; import google.registry.xjc.rgp.XjcRgpStatusType; import google.registry.xjc.secdns.XjcSecdnsDsDataType; +import google.registry.xml.XmlException; import java.io.ByteArrayOutputStream; import java.util.Optional; import org.joda.money.Money; @@ -198,6 +201,21 @@ public class DomainToXjcConverterTest { wrapDeposit(bean).marshal(new ByteArrayOutputStream(), UTF_8); } + @Test + void testConvertAbsentContacts() throws XmlException { + Domain domain = makeDomain(clock); + tm().transact( + () -> + tm().delete( + domain.getAllContacts().stream() + .map(DesignatedContact::getContactKey) + .collect(toImmutableSet()))); + XjcRdeDomain bean = DomainToXjcConverter.convertDomain(domain, RdeMode.FULL); + assertThat(bean.getRegistrant()).isNull(); + assertThat(bean.getContacts()).isEmpty(); + wrapDeposit(bean).marshal(new ByteArrayOutputStream(), UTF_8); + } + XjcRdeDeposit wrapDeposit(XjcRdeDomain domain) { XjcRdeDeposit deposit = new XjcRdeDeposit(); deposit.setId("984302");