diff --git a/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java b/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java index 9a9f32895..368c0eb5f 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java +++ b/core/src/main/java/google/registry/flows/domain/DomainPricingLogic.java @@ -192,7 +192,7 @@ public final class DomainPricingLogic { new FeesAndCredits.Builder() .setCurrency(tld.getCurrency()) .addFeeOrCredit( - Fee.create(tld.getStandardRestoreCost().getAmount(), FeeType.RESTORE, false)); + Fee.create(tld.getRestoreBillingCost().getAmount(), FeeType.RESTORE, false)); if (isExpired) { feesAndCredits.addFeeOrCredit( Fee.create( diff --git a/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java index 846c0dd71..41223f27b 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java @@ -337,7 +337,7 @@ public final class DomainUpdateFlow implements TransactionalFlow { .setReason(Reason.SERVER_STATUS) .setTargetId(targetId) .setRegistrarId(registrarId) - .setCost(Tld.get(existingDomain.getTld()).getServerStatusChangeCost()) + .setCost(Tld.get(existingDomain.getTld()).getServerStatusChangeBillingCost()) .setEventTime(now) .setBillingTime(now) .setDomainHistory(historyEntry) diff --git a/core/src/main/java/google/registry/model/pricing/StaticPremiumListPricingEngine.java b/core/src/main/java/google/registry/model/pricing/StaticPremiumListPricingEngine.java index a3e40c12c..ca69c6d03 100644 --- a/core/src/main/java/google/registry/model/pricing/StaticPremiumListPricingEngine.java +++ b/core/src/main/java/google/registry/model/pricing/StaticPremiumListPricingEngine.java @@ -42,7 +42,7 @@ public final class StaticPremiumListPricingEngine implements PremiumPricingEngin tld.getPremiumListName().flatMap(pl -> PremiumListDao.getPremiumPrice(pl, label)); return DomainPrices.create( premiumPrice.isPresent(), - premiumPrice.orElse(tld.getStandardCreateCost()), + premiumPrice.orElse(tld.getCreateBillingCost()), premiumPrice.orElse(tld.getStandardRenewCost(priceTime))); } } diff --git a/core/src/main/java/google/registry/model/tld/Tld.java b/core/src/main/java/google/registry/model/tld/Tld.java index 1d3fcf7bf..4178f0e7e 100644 --- a/core/src/main/java/google/registry/model/tld/Tld.java +++ b/core/src/main/java/google/registry/model/tld/Tld.java @@ -26,6 +26,10 @@ import static google.registry.util.DateTimeUtils.START_OF_TIME; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; import static org.joda.money.CurrencyUnit.USD; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.github.benmanes.caffeine.cache.CacheLoader; import com.github.benmanes.caffeine.cache.LoadingCache; import com.google.common.annotations.VisibleForTesting; @@ -48,6 +52,15 @@ import google.registry.model.domain.fee.BaseFee.FeeType; import google.registry.model.domain.fee.Fee; import google.registry.model.domain.token.AllocationToken; import google.registry.model.domain.token.AllocationToken.TokenType; +import google.registry.model.tld.TldYamlUtils.CreateAutoTimestampDeserializer; +import google.registry.model.tld.TldYamlUtils.CurrencyDeserializer; +import google.registry.model.tld.TldYamlUtils.CurrencySerializer; +import google.registry.model.tld.TldYamlUtils.OptionalDurationSerializer; +import google.registry.model.tld.TldYamlUtils.OptionalStringSerializer; +import google.registry.model.tld.TldYamlUtils.TimedTransitionPropertyMoneyDeserializer; +import google.registry.model.tld.TldYamlUtils.TimedTransitionPropertyTldStateDeserializer; +import google.registry.model.tld.TldYamlUtils.TokenVKeyListDeserializer; +import google.registry.model.tld.TldYamlUtils.TokenVKeyListSerializer; import google.registry.model.tld.label.PremiumList; import google.registry.model.tld.label.ReservedList; import google.registry.persistence.VKey; @@ -281,6 +294,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl * *

When this field is null, the "dnsDefaultATtl" value from the config file will be used. */ + @JsonSerialize(using = OptionalDurationSerializer.class) Duration dnsAPlusAaaaTtl; /** @@ -288,6 +302,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl * *

When this field is null, the "dnsDefaultNsTtl" value from the config file will be used. */ + @JsonSerialize(using = OptionalDurationSerializer.class) Duration dnsNsTtl; /** @@ -295,6 +310,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl * *

When this field is null, the "dnsDefaultDsTtl" value from the config file will be used. */ + @JsonSerialize(using = OptionalDurationSerializer.class) Duration dnsDsTtl; /** * The unicode-aware representation of the TLD associated with this {@link Tld}. @@ -328,11 +344,13 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl /** A property that transitions to different {@link TldState}s at different times. */ @Column(nullable = false) + @JsonDeserialize(using = TimedTransitionPropertyTldStateDeserializer.class) TimedTransitionProperty tldStateTransitions = TimedTransitionProperty.withInitialValue(DEFAULT_TLD_STATE); /** An automatically managed creation timestamp. */ @Column(nullable = false) + @JsonDeserialize(using = CreateAutoTimestampDeserializer.class) CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null); /** The set of reserved list names that are applicable to this tld. */ @@ -359,6 +377,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl * the database should be queried for the entity with this name that has the largest revision ID. */ @Column(name = "premium_list_name") + @JsonSerialize(using = OptionalStringSerializer.class) String premiumListName; /** Should RDE upload a nightly escrow deposit for this TLD? */ @@ -408,6 +427,8 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl /** The currency unit for all costs associated with this TLD. */ @Column(nullable = false) + @JsonSerialize(using = CurrencySerializer.class) + @JsonDeserialize(using = CurrencyDeserializer.class) CurrencyUnit currency = DEFAULT_CURRENCY; /** The per-year billing cost for registering a new domain name. */ @@ -454,11 +475,13 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl * renewal to ensure transfers have a cost. */ @Column(nullable = false) + @JsonDeserialize(using = TimedTransitionPropertyMoneyDeserializer.class) TimedTransitionProperty renewBillingCostTransitions = TimedTransitionProperty.withInitialValue(DEFAULT_RENEW_BILLING_COST); /** A property that tracks the EAP fee schedule (if any) for the TLD. */ @Column(nullable = false) + @JsonDeserialize(using = TimedTransitionPropertyMoneyDeserializer.class) TimedTransitionProperty eapFeeSchedule = TimedTransitionProperty.withInitialValue(DEFAULT_EAP_BILLING_COST); @@ -475,6 +498,11 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl /** An allowlist of hosts allowed to be used on domains on this TLD (ignored if empty). */ @Nullable Set allowedFullyQualifiedHostNames; + /** + * Indicates when the TLD is being modified using locally modified files to override the source + * control procedures. This field is ignored in Tld YAML files. + */ + @JsonIgnore @Column(nullable = false) boolean breakglassMode = false; @@ -488,6 +516,8 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl * (ex: add a token to the list or remove a token from the list) should not be allowed without * resetting the entire list contents. */ + @JsonSerialize(using = TokenVKeyListSerializer.class) + @JsonDeserialize(using = TokenVKeyListDeserializer.class) List> defaultPromoTokens; /** A set of allowed {@link IdnTableEnum}s for this TLD, or empty if we should use the default. */ @@ -502,6 +532,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl } /** Retrieve the actual domain name representing the TLD for which this registry operates. */ + @JsonIgnore public InternetDomainName getTld() { return InternetDomainName.from(tldStr); } @@ -511,6 +542,11 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl return tldType; } + /** Retrieve whether invoicing is enabled. */ + public boolean isInvoicingEnabled() { + return invoicingEnabled; + } + /** * Retrieve the TLD state at the given time. Defaults to {@link TldState#PREDELEGATION}. * @@ -588,7 +624,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl * domain create. */ @VisibleForTesting - public Money getStandardCreateCost() { + public Money getCreateBillingCost() { return createBillingCost; } @@ -596,7 +632,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl * Returns the add-on cost of a domain restore (the flat tld-wide fee charged in addition to one * year of renewal for that name). */ - public Money getStandardRestoreCost() { + public Money getRestoreBillingCost() { return restoreBillingCost; } @@ -610,7 +646,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl } /** Returns the cost of a server status change (i.e. lock). */ - public Money getServerStatusChangeCost() { + public Money getServerStatusChangeBillingCost() { return serverStatusChangeBillingCost; } @@ -648,6 +684,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl } @VisibleForTesting + @JsonProperty("eapFeeSchedule") public ImmutableSortedMap getEapFeeScheduleAsMap() { return eapFeeSchedule.toValueMap(); } @@ -660,7 +697,7 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl return claimsPeriodEnd; } - public String getPremiumPricingEngineClassName() { + public String getPricingEngineClassName() { return pricingEngineClassName; } @@ -688,6 +725,11 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl return Optional.ofNullable(dnsDsTtl); } + /** Retrieve the TLD unicode representation. */ + public String getTldUnicode() { + return tldUnicode; + } + public ImmutableSet getAllowedRegistrantContactIds() { return nullToEmptyImmutableCopy(allowedRegistrantContactIds); } @@ -1037,13 +1079,13 @@ public class Tld extends ImmutableObject implements Buildable, UnsafeSerializabl // All costs must be in the expected currency. checkArgumentNotNull(instance.getCurrency(), "Currency must be set"); checkArgument( - instance.getStandardCreateCost().getCurrencyUnit().equals(instance.currency), + instance.getCreateBillingCost().getCurrencyUnit().equals(instance.currency), "Create cost must be in the tld's currency"); checkArgument( - instance.getStandardRestoreCost().getCurrencyUnit().equals(instance.currency), + instance.getRestoreBillingCost().getCurrencyUnit().equals(instance.currency), "Restore cost must be in the TLD's currency"); checkArgument( - instance.getServerStatusChangeCost().getCurrencyUnit().equals(instance.currency), + instance.getServerStatusChangeBillingCost().getCurrencyUnit().equals(instance.currency), "Server status change cost must be in the TLD's currency"); checkArgument( instance.getRegistryLockOrUnlockBillingCost().getCurrencyUnit().equals(instance.currency), diff --git a/core/src/main/java/google/registry/model/tld/TldYamlUtils.java b/core/src/main/java/google/registry/model/tld/TldYamlUtils.java new file mode 100644 index 000000000..d6a969de0 --- /dev/null +++ b/core/src/main/java/google/registry/model/tld/TldYamlUtils.java @@ -0,0 +1,308 @@ +// Copyright 2023 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.tld; + +import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; +import static com.google.common.collect.Ordering.natural; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature; +import google.registry.model.CreateAutoTimestamp; +import google.registry.model.common.TimedTransitionProperty; +import google.registry.model.domain.token.AllocationToken; +import google.registry.model.tld.Tld.TldState; +import google.registry.persistence.VKey; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Optional; +import java.util.SortedMap; +import org.joda.money.CurrencyUnit; +import org.joda.money.Money; +import org.joda.time.DateTime; +import org.joda.time.Duration; + +/** A collection of static utility classes and functions for TLD YAML conversions. */ +public class TldYamlUtils { + + /** + * Returns an {@link ObjectMapper} object that can be used to convert a {@link Tld} object to and + * from YAML. + */ + public static ObjectMapper getObjectMapper() { + SimpleModule module = new SimpleModule(); + module.addSerializer(Money.class, new MoneySerializer()); + module.addDeserializer(Money.class, new MoneyDeserializer()); + ObjectMapper mapper = + new ObjectMapper(new YAMLFactory().disable(Feature.WRITE_DOC_START_MARKER)) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + .registerModule(module); + mapper.findAndRegisterModules(); + return mapper; + } + + /** A custom JSON serializer for {@link Money}. */ + public static class MoneySerializer extends StdSerializer { + + public MoneySerializer() { + this(null); + } + + public MoneySerializer(Class t) { + super(t); + } + + @Override + public void serialize(Money value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeStartObject(); + gen.writeStringField("currency", String.valueOf(value.getCurrencyUnit())); + gen.writeNumberField("amount", value.getAmount()); + gen.writeEndObject(); + } + } + + /** A custom JSON deserializer for {@link Money}. */ + public static class MoneyDeserializer extends StdDeserializer { + public MoneyDeserializer() { + this(null); + } + + public MoneyDeserializer(Class t) { + super(t); + } + + static class MoneyJson { + public String currency; + public BigDecimal amount; + } + + @Override + public Money deserialize(JsonParser jp, DeserializationContext context) throws IOException { + MoneyJson json = jp.readValueAs(MoneyJson.class); + CurrencyUnit currencyUnit = CurrencyUnit.of(json.currency); + return Money.of(currencyUnit, json.amount); + } + } + + /** A custom JSON serializer for {@link CurrencyUnit}. */ + public static class CurrencySerializer extends StdSerializer { + + public CurrencySerializer() { + this(null); + } + + public CurrencySerializer(Class t) { + super(t); + } + + @Override + public void serialize(CurrencyUnit value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeString(value.getCode()); + } + } + + /** A custom JSON deserializer for {@link CurrencyUnit}. */ + public static class CurrencyDeserializer extends StdDeserializer { + public CurrencyDeserializer() { + this(null); + } + + public CurrencyDeserializer(Class t) { + super(t); + } + + @Override + public CurrencyUnit deserialize(JsonParser jp, DeserializationContext context) + throws IOException { + String currencyCode = jp.readValueAs(String.class); + return CurrencyUnit.of(currencyCode); + } + } + + /** A custom JSON serializer for an Optional of a {@link Duration} object. */ + public static class OptionalDurationSerializer extends StdSerializer> { + + public OptionalDurationSerializer() { + this(null); + } + + public OptionalDurationSerializer(Class> t) { + super(t); + } + + @Override + public void serialize(Optional value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + if (value.isPresent()) { + gen.writeNumber(value.get().getMillis()); + } else { + gen.writeNull(); + } + } + } + + /** A custom JSON serializer for an Optional String. */ + public static class OptionalStringSerializer extends StdSerializer> { + + public OptionalStringSerializer() { + this(null); + } + + public OptionalStringSerializer(Class> t) { + super(t); + } + + @Override + public void serialize(Optional value, JsonGenerator gen, SerializerProvider provider) + throws IOException { + if (value.isPresent()) { + gen.writeString(value.get()); + } else { + gen.writeNull(); + } + } + } + + /** A custom JSON serializer for a list of {@link AllocationToken} VKeys. */ + public static class TokenVKeyListSerializer extends StdSerializer>> { + + public TokenVKeyListSerializer() { + this(null); + } + + public TokenVKeyListSerializer(Class>> t) { + super(t); + } + + @Override + public void serialize( + List> list, JsonGenerator gen, SerializerProvider provider) + throws IOException { + gen.writeStartArray(); + for (VKey vkey : list) { + gen.writeString(vkey.getKey().toString()); + } + gen.writeEndArray(); + } + } + + /** A custom JSON deserializer for a list of {@link AllocationToken} VKeys. */ + public static class TokenVKeyListDeserializer + extends StdDeserializer>> { + + public TokenVKeyListDeserializer() { + this(null); + } + + public TokenVKeyListDeserializer(Class> t) { + super(t); + } + + @Override + public List> deserialize(JsonParser jp, DeserializationContext context) + throws IOException { + List> tokens = new ArrayList<>(); + String[] keyStrings = jp.readValueAs(String[].class); + for (String token : keyStrings) { + tokens.add(VKey.create(AllocationToken.class, token)); + } + return tokens; + } + } + + /** A custom JSON deserializer for a {@link TimedTransitionProperty} of {@link TldState}. */ + public static class TimedTransitionPropertyTldStateDeserializer + extends StdDeserializer> { + + public TimedTransitionPropertyTldStateDeserializer() { + this(null); + } + + public TimedTransitionPropertyTldStateDeserializer(Class> t) { + super(t); + } + + @Override + public TimedTransitionProperty deserialize( + JsonParser jp, DeserializationContext context) throws IOException { + SortedMap valueMap = jp.readValueAs(SortedMap.class); + return TimedTransitionProperty.fromValueMap( + valueMap.keySet().stream() + .collect( + toImmutableSortedMap( + natural(), DateTime::parse, key -> TldState.valueOf(valueMap.get(key))))); + } + } + + /** A custom JSON deserializer for a {@link TimedTransitionProperty} of {@link Money}. */ + public static class TimedTransitionPropertyMoneyDeserializer + extends StdDeserializer> { + + public TimedTransitionPropertyMoneyDeserializer() { + this(null); + } + + public TimedTransitionPropertyMoneyDeserializer(Class> t) { + super(t); + } + + @Override + public TimedTransitionProperty deserialize(JsonParser jp, DeserializationContext context) + throws IOException { + SortedMap valueMap = jp.readValueAs(SortedMap.class); + return TimedTransitionProperty.fromValueMap( + valueMap.keySet().stream() + .collect( + toImmutableSortedMap( + natural(), + DateTime::parse, + key -> + Money.of( + CurrencyUnit.of(valueMap.get(key).get("currency").toString()), + (double) valueMap.get(key).get("amount"))))); + } + } + + /** A custom JSON deserializer for a {@link CreateAutoTimestamp}. */ + public static class CreateAutoTimestampDeserializer extends StdDeserializer { + + public CreateAutoTimestampDeserializer() { + this(null); + } + + public CreateAutoTimestampDeserializer(Class t) { + super(t); + } + + @Override + public CreateAutoTimestamp deserialize(JsonParser jp, DeserializationContext context) + throws IOException { + DateTime creationTime = jp.readValueAs(DateTime.class); + return CreateAutoTimestamp.create(creationTime); + } + } +} diff --git a/core/src/main/java/google/registry/pricing/PricingEngineProxy.java b/core/src/main/java/google/registry/pricing/PricingEngineProxy.java index b22a42749..f50a82e5a 100644 --- a/core/src/main/java/google/registry/pricing/PricingEngineProxy.java +++ b/core/src/main/java/google/registry/pricing/PricingEngineProxy.java @@ -58,7 +58,7 @@ public final class PricingEngineProxy { */ public static DomainPrices getPricesForDomainName(String domainName, DateTime priceTime) { String tld = getTldFromDomainName(domainName); - String clazz = Tld.get(tld).getPremiumPricingEngineClassName(); + String clazz = Tld.get(tld).getPricingEngineClassName(); PremiumPricingEngine engine = premiumPricingEngines.get(clazz); checkState(engine != null, "Could not load pricing engine %s for TLD %s", clazz, tld); return engine.getDomainPrices(domainName, priceTime); diff --git a/core/src/test/java/google/registry/beam/common/DatabaseSnapshotTest.java b/core/src/test/java/google/registry/beam/common/DatabaseSnapshotTest.java index dca7108a2..c9eaa3ec8 100644 --- a/core/src/test/java/google/registry/beam/common/DatabaseSnapshotTest.java +++ b/core/src/test/java/google/registry/beam/common/DatabaseSnapshotTest.java @@ -127,7 +127,7 @@ public class DatabaseSnapshotTest { Tld updated = registry .asBuilder() - .setCreateBillingCost(registry.getStandardCreateCost().plus(1)) + .setCreateBillingCost(registry.getCreateBillingCost().plus(1)) .build(); tm().transact(() -> tm().put(updated)); @@ -152,7 +152,7 @@ public class DatabaseSnapshotTest { Tld updated = registry .asBuilder() - .setCreateBillingCost(registry.getStandardCreateCost().plus(1)) + .setCreateBillingCost(registry.getCreateBillingCost().plus(1)) .build(); tm().transact(() -> tm().put(updated)); diff --git a/core/src/test/java/google/registry/model/tld/TldTest.java b/core/src/test/java/google/registry/model/tld/TldTest.java index e29645f03..cd1aa33a7 100644 --- a/core/src/test/java/google/registry/model/tld/TldTest.java +++ b/core/src/test/java/google/registry/model/tld/TldTest.java @@ -17,18 +17,22 @@ package google.registry.model.tld; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.Truth8.assertThat; +import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects; import static google.registry.model.domain.token.AllocationToken.TokenType.DEFAULT_PROMO; import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE; import static google.registry.model.tld.Tld.TldState.GENERAL_AVAILABILITY; import static google.registry.model.tld.Tld.TldState.PREDELEGATION; import static google.registry.model.tld.Tld.TldState.QUIET_PERIOD; import static google.registry.model.tld.Tld.TldState.START_DATE_SUNRISE; +import static google.registry.model.tld.TldYamlUtils.getObjectMapper; import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.newTld; import static google.registry.testing.DatabaseHelper.persistPremiumList; import static google.registry.testing.DatabaseHelper.persistReservedList; import static google.registry.testing.DatabaseHelper.persistResource; +import static google.registry.testing.TestDataHelper.filePath; +import static google.registry.testing.TestDataHelper.loadFile; import static google.registry.util.DateTimeUtils.END_OF_TIME; import static google.registry.util.DateTimeUtils.START_OF_TIME; import static java.math.RoundingMode.UNNECESSARY; @@ -36,6 +40,7 @@ import static org.joda.money.CurrencyUnit.EUR; import static org.joda.money.CurrencyUnit.USD; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; @@ -48,11 +53,14 @@ import google.registry.model.tld.label.PremiumList; import google.registry.model.tld.label.PremiumListDao; import google.registry.model.tld.label.ReservedList; import google.registry.persistence.VKey; +import google.registry.tldconfig.idn.IdnTableEnum; import google.registry.util.SerializeUtils; +import java.io.File; import java.math.BigDecimal; import java.util.Optional; import org.joda.money.Money; import org.joda.time.DateTime; +import org.joda.time.Duration; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -94,6 +102,106 @@ public final class TldTest extends EntityTestCase { assertThat(SerializeUtils.serializeDeserialize(persisted)).isEqualTo(persisted); } + @Test + void testTldToYaml() throws Exception { + fakeClock.setTo(START_OF_TIME); + AllocationToken defaultToken = + persistResource( + new AllocationToken.Builder() + .setToken("bbbbb") + .setTokenType(DEFAULT_PROMO) + .setAllowedTlds(ImmutableSet.of("tld")) + .build()); + Tld existingTld = + createTld("tld") + .asBuilder() + .setDnsAPlusAaaaTtl(Duration.standardHours(1)) + .setDnsWriters(ImmutableSet.of("baz", "bang")) + .setEapFeeSchedule( + ImmutableSortedMap.of( + START_OF_TIME, + Money.of(USD, 0), + DateTime.parse("2000-06-01T00:00:00Z"), + Money.of(USD, 100), + DateTime.parse("2000-06-02T00:00:00Z"), + Money.of(USD, 0))) + .setAllowedFullyQualifiedHostNames(ImmutableSet.of("foo")) + .setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey())) + .setIdnTables(ImmutableSet.of(IdnTableEnum.JA, IdnTableEnum.EXTENDED_LATIN)) + .build(); + + ObjectMapper mapper = getObjectMapper(); + String yaml = mapper.writeValueAsString(existingTld); + assertThat(yaml).isEqualTo(loadFile(getClass(), "tld.yaml")); + } + + @Test + void testYamlToTld() throws Exception { + fakeClock.setTo(START_OF_TIME); + AllocationToken defaultToken = + persistResource( + new AllocationToken.Builder() + .setToken("bbbbb") + .setTokenType(DEFAULT_PROMO) + .setAllowedTlds(ImmutableSet.of("tld")) + .build()); + Tld existingTld = + createTld("tld") + .asBuilder() + .setDnsAPlusAaaaTtl(Duration.standardHours(1)) + .setDnsWriters(ImmutableSet.of("baz", "bang")) + .setEapFeeSchedule( + ImmutableSortedMap.of( + START_OF_TIME, + Money.of(USD, 0), + DateTime.parse("2000-06-01T00:00:00Z"), + Money.of(USD, 100), + DateTime.parse("2000-06-02T00:00:00Z"), + Money.of(USD, 0))) + .setAllowedFullyQualifiedHostNames(ImmutableSet.of("foo")) + .setDefaultPromoTokens(ImmutableList.of(defaultToken.createVKey())) + .setIdnTables(ImmutableSet.of(IdnTableEnum.JA, IdnTableEnum.EXTENDED_LATIN)) + .build(); + + ObjectMapper mapper = getObjectMapper(); + Tld constructedTld = mapper.readValue(new File(filePath(getClass(), "tld.yaml")), Tld.class); + compareTlds(existingTld, constructedTld); + } + + @Test + void testSuccess_tldYamlRoundtrip() throws Exception { + Tld testTld = createTld("test"); + ObjectMapper mapper = getObjectMapper(); + String yaml = mapper.writeValueAsString(testTld); + Tld constructedTld = mapper.readValue(yaml, Tld.class); + compareTlds(testTld, constructedTld); + } + + // On YAML serialization/deserialization some null values may be changed to empty collections + void compareTlds(Tld existingTld, Tld constructedTld) { + assertAboutImmutableObjects() + .that(constructedTld) + .isEqualExceptFields( + existingTld, + "dnsWriters", + "idnTables", + "reservedListNames", + "allowedRegistrantContactIds", + "allowedFullyQualifiedHostNames", + "defaultPromoTokens"); + assertThat(constructedTld.getDnsWriters()) + .containsExactlyElementsIn(existingTld.getDnsWriters()); + assertThat(constructedTld.getIdnTables()).containsExactlyElementsIn(existingTld.getIdnTables()); + assertThat(constructedTld.getReservedListNames()) + .containsExactlyElementsIn(existingTld.getReservedListNames()); + assertThat(constructedTld.getAllowedRegistrantContactIds()) + .containsExactlyElementsIn(existingTld.getAllowedRegistrantContactIds()); + assertThat(constructedTld.getAllowedFullyQualifiedHostNames()) + .containsExactlyElementsIn(existingTld.getAllowedFullyQualifiedHostNames()); + assertThat(constructedTld.getDefaultPromoTokens()) + .containsExactlyElementsIn(existingTld.getDefaultPromoTokens()); + } + @Test void testFailure_registryNotFound() { createTld("foo"); @@ -111,17 +219,17 @@ public final class TldTest extends EntityTestCase { @Test void testSettingCreateBillingCost() { Tld registry = Tld.get("tld").asBuilder().setCreateBillingCost(Money.of(USD, 42)).build(); - assertThat(registry.getStandardCreateCost()).isEqualTo(Money.of(USD, 42)); + assertThat(registry.getCreateBillingCost()).isEqualTo(Money.of(USD, 42)); // The default value of 17 is set in createTld(). - assertThat(registry.getStandardRestoreCost()).isEqualTo(Money.of(USD, 17)); + assertThat(registry.getRestoreBillingCost()).isEqualTo(Money.of(USD, 17)); } @Test void testSettingRestoreBillingCost() { Tld registry = Tld.get("tld").asBuilder().setRestoreBillingCost(Money.of(USD, 42)).build(); // The default value of 13 is set in createTld(). - assertThat(registry.getStandardCreateCost()).isEqualTo(Money.of(USD, 13)); - assertThat(registry.getStandardRestoreCost()).isEqualTo(Money.of(USD, 42)); + assertThat(registry.getCreateBillingCost()).isEqualTo(Money.of(USD, 13)); + assertThat(registry.getRestoreBillingCost()).isEqualTo(Money.of(USD, 42)); } @Test @@ -251,7 +359,7 @@ public final class TldTest extends EntityTestCase { void testSettingServerStatusChangeBillingCost() { Tld registry = Tld.get("tld").asBuilder().setServerStatusChangeBillingCost(Money.of(USD, 42)).build(); - assertThat(registry.getServerStatusChangeCost()).isEqualTo(Money.of(USD, 42)); + assertThat(registry.getServerStatusChangeBillingCost()).isEqualTo(Money.of(USD, 42)); } @Test diff --git a/core/src/test/java/google/registry/tools/CreateTldCommandTest.java b/core/src/test/java/google/registry/tools/CreateTldCommandTest.java index f888d122f..ccfc1af19 100644 --- a/core/src/test/java/google/registry/tools/CreateTldCommandTest.java +++ b/core/src/test/java/google/registry/tools/CreateTldCommandTest.java @@ -225,7 +225,7 @@ class CreateTldCommandTest extends CommandTestCase { "--roid_suffix=Q9JYB4C", "--dns_writers=VoidDnsWriter", "xn--q9jyb4c"); - assertThat(Tld.get("xn--q9jyb4c").getStandardCreateCost()).isEqualTo(Money.of(USD, 42.42)); + assertThat(Tld.get("xn--q9jyb4c").getCreateBillingCost()).isEqualTo(Money.of(USD, 42.42)); } @Test @@ -235,7 +235,7 @@ class CreateTldCommandTest extends CommandTestCase { "--roid_suffix=Q9JYB4C", "--dns_writers=VoidDnsWriter", "xn--q9jyb4c"); - assertThat(Tld.get("xn--q9jyb4c").getStandardRestoreCost()).isEqualTo(Money.of(USD, 42.42)); + assertThat(Tld.get("xn--q9jyb4c").getRestoreBillingCost()).isEqualTo(Money.of(USD, 42.42)); } @Test @@ -245,7 +245,8 @@ class CreateTldCommandTest extends CommandTestCase { "--roid_suffix=Q9JYB4C", "--dns_writers=VoidDnsWriter", "xn--q9jyb4c"); - assertThat(Tld.get("xn--q9jyb4c").getServerStatusChangeCost()).isEqualTo(Money.of(USD, 42.42)); + assertThat(Tld.get("xn--q9jyb4c").getServerStatusChangeBillingCost()) + .isEqualTo(Money.of(USD, 42.42)); } @Test @@ -271,8 +272,8 @@ class CreateTldCommandTest extends CommandTestCase { "--dns_writers=VoidDnsWriter", "xn--q9jyb4c"); Tld registry = Tld.get("xn--q9jyb4c"); - assertThat(registry.getStandardCreateCost()).isEqualTo(Money.ofMajor(JPY, 12345)); - assertThat(registry.getStandardRestoreCost()).isEqualTo(Money.ofMajor(JPY, 67890)); + assertThat(registry.getCreateBillingCost()).isEqualTo(Money.ofMajor(JPY, 12345)); + assertThat(registry.getRestoreBillingCost()).isEqualTo(Money.ofMajor(JPY, 67890)); assertThat(registry.getStandardRenewCost(START_OF_TIME)).isEqualTo(Money.ofMajor(JPY, 101112)); } diff --git a/core/src/test/java/google/registry/tools/UpdateTldCommandTest.java b/core/src/test/java/google/registry/tools/UpdateTldCommandTest.java index e1b512261..30940d826 100644 --- a/core/src/test/java/google/registry/tools/UpdateTldCommandTest.java +++ b/core/src/test/java/google/registry/tools/UpdateTldCommandTest.java @@ -299,13 +299,13 @@ class UpdateTldCommandTest extends CommandTestCase { @Test void testSuccess_createBillingCostFlag() throws Exception { runCommandForced("--create_billing_cost=\"USD 42.42\"", "xn--q9jyb4c"); - assertThat(Tld.get("xn--q9jyb4c").getStandardCreateCost()).isEqualTo(Money.of(USD, 42.42)); + assertThat(Tld.get("xn--q9jyb4c").getCreateBillingCost()).isEqualTo(Money.of(USD, 42.42)); } @Test void testSuccess_restoreBillingCostFlag() throws Exception { runCommandForced("--restore_billing_cost=\"USD 42.42\"", "xn--q9jyb4c"); - assertThat(Tld.get("xn--q9jyb4c").getStandardRestoreCost()).isEqualTo(Money.of(USD, 42.42)); + assertThat(Tld.get("xn--q9jyb4c").getRestoreBillingCost()).isEqualTo(Money.of(USD, 42.42)); } @Test @@ -330,10 +330,10 @@ class UpdateTldCommandTest extends CommandTestCase { "--registry_lock_or_unlock_cost=\"JPY 9001\"", "xn--q9jyb4c"); Tld registry = Tld.get("xn--q9jyb4c"); - assertThat(registry.getStandardCreateCost()).isEqualTo(Money.ofMajor(JPY, 12345)); - assertThat(registry.getStandardRestoreCost()).isEqualTo(Money.ofMajor(JPY, 67890)); + assertThat(registry.getCreateBillingCost()).isEqualTo(Money.ofMajor(JPY, 12345)); + assertThat(registry.getRestoreBillingCost()).isEqualTo(Money.ofMajor(JPY, 67890)); assertThat(registry.getStandardRenewCost(START_OF_TIME)).isEqualTo(Money.ofMajor(JPY, 101112)); - assertThat(registry.getServerStatusChangeCost()).isEqualTo(Money.ofMajor(JPY, 97865)); + assertThat(registry.getServerStatusChangeBillingCost()).isEqualTo(Money.ofMajor(JPY, 97865)); assertThat(registry.getRegistryLockOrUnlockBillingCost()).isEqualTo(Money.ofMajor(JPY, 9001)); } diff --git a/core/src/test/resources/google/registry/model/tld/tld.yaml b/core/src/test/resources/google/registry/model/tld/tld.yaml new file mode 100644 index 000000000..23419904d --- /dev/null +++ b/core/src/test/resources/google/registry/model/tld/tld.yaml @@ -0,0 +1,66 @@ +tldStr: "tld" +roidSuffix: "TLD" +pricingEngineClassName: "google.registry.model.pricing.StaticPremiumListPricingEngine" +dnsWriters: +- "baz" +- "bang" +numDnsPublishLocks: 1 +dnsAPlusAaaaTtl: 3600000 +dnsNsTtl: null +dnsDsTtl: null +tldUnicode: "tld" +driveFolderId: null +tldType: "REAL" +invoicingEnabled: false +tldStateTransitions: + "1970-01-01T00:00:00.000Z": "GENERAL_AVAILABILITY" +creationTime: "1970-01-01T00:00:00.000Z" +reservedListNames: [] +premiumListName: "tld" +escrowEnabled: false +dnsPaused: false +addGracePeriodLength: 432000000 +anchorTenantAddGracePeriodLength: 2592000000 +autoRenewGracePeriodLength: 3888000000 +redemptionGracePeriodLength: 2592000000 +renewGracePeriodLength: 432000000 +transferGracePeriodLength: 432000000 +automaticTransferLength: 432000000 +pendingDeleteLength: 432000000 +currency: "USD" +createBillingCost: + currency: "USD" + amount: 13.00 +restoreBillingCost: + currency: "USD" + amount: 17.00 +serverStatusChangeBillingCost: + currency: "USD" + amount: 19.00 +registryLockOrUnlockBillingCost: + currency: "USD" + amount: 0.00 +renewBillingCostTransitions: + "1970-01-01T00:00:00.000Z": + currency: "USD" + amount: 11.00 +lordnUsername: null +claimsPeriodEnd: "294247-01-10T04:00:54.775Z" +allowedRegistrantContactIds: [] +allowedFullyQualifiedHostNames: +- "foo" +defaultPromoTokens: +- "bbbbb" +idnTables: +- "JA" +- "EXTENDED_LATIN" +eapFeeSchedule: + "1970-01-01T00:00:00.000Z": + currency: "USD" + amount: 0.00 + "2000-06-01T00:00:00.000Z": + currency: "USD" + amount: 100.00 + "2000-06-02T00:00:00.000Z": + currency: "USD" + amount: 0.00