From f4725af15dfe1d85d1b3bb679ec24fe6d52ade79 Mon Sep 17 00:00:00 2001 From: Gus Brodman Date: Fri, 17 Jul 2026 17:30:29 -0400 Subject: [PATCH] Run Address validation on GSON deserialization too we should run these checks when we deserialize from json objects, not just when we use the builder to construct it. We did something similar for TLD recently. --- .../registry/model/eppcommon/Address.java | 31 ++++-- .../model/registrar/RegistrarAddress.java | 17 ++-- .../registry/model/eppcommon/AddressTest.java | 33 +++++++ .../model/registrar/RegistrarAddressTest.java | 94 +++++++++++++++++++ 4 files changed, 157 insertions(+), 18 deletions(-) create mode 100644 core/src/test/java/google/registry/model/registrar/RegistrarAddressTest.java diff --git a/core/src/main/java/google/registry/model/eppcommon/Address.java b/core/src/main/java/google/registry/model/eppcommon/Address.java index 423c71de93e..ddb98101ca0 100644 --- a/core/src/main/java/google/registry/model/eppcommon/Address.java +++ b/core/src/main/java/google/registry/model/eppcommon/Address.java @@ -155,6 +155,7 @@ public Builder asBuilder() { @Override public void postProcess() { + validateState(); if (street == null || street.isEmpty()) { return; } @@ -163,6 +164,21 @@ public void postProcess() { streetLine3 = street.size() >= 3 ? street.get(2) : null; } + public void validateState() { + checkArgument( + street == null || (!street.isEmpty() && street.size() <= 3), + "Street address must have [1-3] lines: %s", + street); + //noinspection ConstantConditions + checkArgument( + street == null || street.stream().noneMatch(String::isEmpty), + "Street address cannot contain empty string: %s", + street); + checkArgument( + countryCode == null || countryCode.length() == 2, + "Country code should be a 2 character string"); + } + /** A builder for constructing {@link Address}. */ public static class Builder extends Buildable.Builder { @@ -172,16 +188,13 @@ protected Builder(T instance) { super(instance); } + @Override + public T build() { + getInstance().validateState(); + return super.build(); + } + public Builder setStreet(ImmutableList street) { - checkArgument( - street == null || (!street.isEmpty() && street.size() <= 3), - "Street address must have [1-3] lines: %s", - street); - //noinspection ConstantConditions - checkArgument( - street.stream().noneMatch(String::isEmpty), - "Street address cannot contain empty string: %s", - street); getInstance().street = street; getInstance().streetLine1 = street.get(0); getInstance().streetLine2 = street.size() >= 2 ? street.get(1) : null; diff --git a/core/src/main/java/google/registry/model/registrar/RegistrarAddress.java b/core/src/main/java/google/registry/model/registrar/RegistrarAddress.java index 521a382643f..eddb1027c6c 100644 --- a/core/src/main/java/google/registry/model/registrar/RegistrarAddress.java +++ b/core/src/main/java/google/registry/model/registrar/RegistrarAddress.java @@ -37,6 +37,14 @@ public Builder asBuilder() { return new Builder(clone(this)); } + @Override + public void validateState() { + super.validateState(); + checkNotNull(forceEmptyToNull(getStreet()), "Missing street"); + checkNotNull(getCity(), "Missing city"); + checkNotNull(getCountryCode(), "Missing country code"); + } + /** Builder for {@link RegistrarAddress}. */ public static class Builder extends Address.Builder { public Builder() {} @@ -44,14 +52,5 @@ public Builder() {} private Builder(RegistrarAddress instance) { super(instance); } - - @Override - public RegistrarAddress build() { - RegistrarAddress instance = getInstance(); - checkNotNull(forceEmptyToNull(instance.getStreet()), "Missing street"); - checkNotNull(instance.getCity(), "Missing city"); - checkNotNull(instance.getCountryCode(), "Missing country code"); - return super.build(); - } } } diff --git a/core/src/test/java/google/registry/model/eppcommon/AddressTest.java b/core/src/test/java/google/registry/model/eppcommon/AddressTest.java index 96ca1466d90..f9c92818c98 100644 --- a/core/src/test/java/google/registry/model/eppcommon/AddressTest.java +++ b/core/src/test/java/google/registry/model/eppcommon/AddressTest.java @@ -25,6 +25,7 @@ import google.registry.model.eppcommon.AddressTest.TestEntity.TestAddress; import google.registry.persistence.transaction.JpaTestExtensions; import google.registry.persistence.transaction.JpaTestExtensions.JpaUnitTestExtension; +import google.registry.tools.GsonUtils; import jakarta.persistence.Embeddable; import jakarta.persistence.Entity; import jakarta.persistence.Id; @@ -124,6 +125,38 @@ void testSuccess_pojoToAndFromXml() throws Exception { assertAddress(unmarshalledEntity.address, "123 W 14th St", "8th Fl", "Rm 8"); } + @Test + void testSuccess_gsonInstantiation() { + String json = "{\"street\":[\"123 W 14th St\",\"8th Fl\"],\"countryCode\":\"US\"}"; + TestAddress address = GsonUtils.provideGson().fromJson(json, TestAddress.class); + assertThat(address.getStreet()).containsExactly("123 W 14th St", "8th Fl"); + assertThat(address.getCountryCode()).isEqualTo("US"); + } + + @Test + void testFailure_gsonInstantiation_tooManyStreetLines() { + String json = "{\"street\":[\"123 W 14th St\",\"8th Fl\",\"Rm 8\",\"4th Line\"]}"; + assertThrows( + IllegalArgumentException.class, + () -> GsonUtils.provideGson().fromJson(json, TestAddress.class)); + } + + @Test + void testFailure_gsonInstantiation_emptyStreetLine() { + String json = "{\"street\":[\"123 W 14th St\",\"\"]}"; + assertThrows( + IllegalArgumentException.class, + () -> GsonUtils.provideGson().fromJson(json, TestAddress.class)); + } + + @Test + void testFailure_gsonInstantiation_invalidCountryCode() { + String json = "{\"countryCode\":\"USA\"}"; + assertThrows( + IllegalArgumentException.class, + () -> GsonUtils.provideGson().fromJson(json, TestAddress.class)); + } + private static TestAddress createAddress(String... streetList) { return new TestAddress.Builder() .setStreet(ImmutableList.copyOf(streetList)) diff --git a/core/src/test/java/google/registry/model/registrar/RegistrarAddressTest.java b/core/src/test/java/google/registry/model/registrar/RegistrarAddressTest.java new file mode 100644 index 00000000000..097be618634 --- /dev/null +++ b/core/src/test/java/google/registry/model/registrar/RegistrarAddressTest.java @@ -0,0 +1,94 @@ +// Copyright 2026 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.registrar; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.common.collect.ImmutableList; +import google.registry.tools.GsonUtils; +import org.junit.jupiter.api.Test; + +/** Unit tests for {@link RegistrarAddress}. */ +class RegistrarAddressTest { + + @Test + void testSuccess_gsonInstantiation() { + String json = "{\"street\":[\"123 W 14th St\"],\"city\":\"New York\",\"countryCode\":\"US\"}"; + RegistrarAddress address = GsonUtils.provideGson().fromJson(json, RegistrarAddress.class); + assertThat(address.getStreet()).containsExactly("123 W 14th St"); + assertThat(address.getCity()).isEqualTo("New York"); + assertThat(address.getCountryCode()).isEqualTo("US"); + } + + @Test + void testFailure_gsonInstantiation_missingStreet() { + String json = "{\"city\":\"New York\",\"countryCode\":\"US\"}"; + assertThrows( + NullPointerException.class, + () -> GsonUtils.provideGson().fromJson(json, RegistrarAddress.class)); + } + + @Test + void testFailure_gsonInstantiation_missingCity() { + String json = "{\"street\":[\"123 W 14th St\"],\"countryCode\":\"US\"}"; + assertThrows( + NullPointerException.class, + () -> GsonUtils.provideGson().fromJson(json, RegistrarAddress.class)); + } + + @Test + void testFailure_gsonInstantiation_missingCountryCode() { + String json = "{\"street\":[\"123 W 14th St\"],\"city\":\"New York\"}"; + assertThrows( + NullPointerException.class, + () -> GsonUtils.provideGson().fromJson(json, RegistrarAddress.class)); + } + + @Test + void testFailure_gsonInstantiation_invalidCountryCode() { + String json = "{\"street\":[\"123 W 14th St\"],\"city\":\"New York\",\"countryCode\":\"USA\"}"; + assertThrows( + IllegalArgumentException.class, + () -> GsonUtils.provideGson().fromJson(json, RegistrarAddress.class)); + } + + @Test + void testFailure_gsonInstantiation_tooManyStreetLines() { + String json = + "{\"street\":[\"1\",\"2\",\"3\",\"4\"],\"city\":\"New York\",\"countryCode\":\"US\"}"; + assertThrows( + IllegalArgumentException.class, + () -> GsonUtils.provideGson().fromJson(json, RegistrarAddress.class)); + } + + @Test + void testSuccess_builder() { + RegistrarAddress address = + new RegistrarAddress.Builder() + .setStreet(ImmutableList.of("123 W 14th St")) + .setCity("New York") + .setCountryCode("US") + .build(); + assertThat(address.getStreet()).containsExactly("123 W 14th St"); + } + + @Test + void testFailure_builder_missingStreet() { + assertThrows( + NullPointerException.class, + () -> new RegistrarAddress.Builder().setCity("New York").setCountryCode("US").build()); + } +}