diff --git a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java index 9c8e6ec0e..b514c2e57 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java @@ -370,7 +370,8 @@ public class DomainCreateFlow implements TransactionalFlow { if (allocationToken.isPresent() && TokenType.SINGLE_USE.equals(allocationToken.get().getTokenType())) { entitiesToSave.add( - allocationTokenFlowUtils.redeemToken(allocationToken.get(), Key.create(historyEntry))); + allocationTokenFlowUtils.redeemToken( + allocationToken.get(), HistoryEntry.createVKey(Key.create(historyEntry)))); } enqueueTasks(newDomain, hasSignedMarks, hasClaimsNotice); diff --git a/core/src/main/java/google/registry/flows/domain/token/AllocationTokenFlowUtils.java b/core/src/main/java/google/registry/flows/domain/token/AllocationTokenFlowUtils.java index 848396a43..0111a2a91 100644 --- a/core/src/main/java/google/registry/flows/domain/token/AllocationTokenFlowUtils.java +++ b/core/src/main/java/google/registry/flows/domain/token/AllocationTokenFlowUtils.java @@ -33,6 +33,7 @@ import google.registry.model.domain.token.AllocationToken.TokenStatus; import google.registry.model.domain.token.AllocationToken.TokenType; import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; +import google.registry.persistence.VKey; import java.util.List; import java.util.Optional; import javax.inject.Inject; @@ -107,7 +108,7 @@ public class AllocationTokenFlowUtils { /** Redeems a SINGLE_USE {@link AllocationToken}, returning the redeemed copy. */ public AllocationToken redeemToken( - AllocationToken token, Key redemptionHistoryEntry) { + AllocationToken token, VKey redemptionHistoryEntry) { checkArgument( TokenType.SINGLE_USE.equals(token.getTokenType()), "Only SINGLE_USE tokens can be marked as redeemed"); @@ -124,7 +125,8 @@ public class AllocationTokenFlowUtils { private void validateToken( InternetDomainName domainName, AllocationToken token, String clientId, DateTime now) throws EppException { - if (!token.getAllowedClientIds().isEmpty() && !token.getAllowedClientIds().contains(clientId)) { + if (!token.getAllowedRegistrarIds().isEmpty() + && !token.getAllowedRegistrarIds().contains(clientId)) { throw new AllocationTokenNotValidForRegistrarException(); } if (!token.getAllowedTlds().isEmpty() diff --git a/core/src/main/java/google/registry/model/domain/token/AllocationToken.java b/core/src/main/java/google/registry/model/domain/token/AllocationToken.java index 6cdcde941..b23212da6 100644 --- a/core/src/main/java/google/registry/model/domain/token/AllocationToken.java +++ b/core/src/main/java/google/registry/model/domain/token/AllocationToken.java @@ -49,16 +49,32 @@ import google.registry.model.common.TimedTransitionProperty.TimedTransition; import google.registry.model.reporting.HistoryEntry; import google.registry.persistence.VKey; import google.registry.persistence.WithStringVKey; +import google.registry.schema.replay.DatastoreAndSqlEntity; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; +import javax.persistence.Column; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; import org.joda.time.DateTime; /** An entity representing an allocation token. */ @ReportedOn @Entity @WithStringVKey -public class AllocationToken extends BackupGroupRoot implements Buildable { +@javax.persistence.Entity +@Table( + indexes = { + @javax.persistence.Index( + columnList = "token", + name = "allocation_token_token_idx", + unique = true), + @javax.persistence.Index( + columnList = "domainName", + name = "allocation_token_domain_name_idx"), + }) +public class AllocationToken extends BackupGroupRoot implements Buildable, DatastoreAndSqlEntity { // Promotions should only move forward, and ENDED / CANCELLED are terminal states. private static final ImmutableMultimap VALID_TOKEN_STATUS_TRANSITIONS = @@ -86,19 +102,22 @@ public class AllocationToken extends BackupGroupRoot implements Buildable { } /** The allocation token string. */ - @Id String token; + @javax.persistence.Id @Id String token; /** The key of the history entry for which the token was used. Null if not yet used. */ - @Nullable @Index Key redemptionHistoryEntry; + @Nullable @Index VKey redemptionHistoryEntry; /** The fully-qualified domain name that this token is limited to, if any. */ @Nullable @Index String domainName; /** When this token was created. */ + @Column(nullable = false) CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null); /** Allowed registrar client IDs for this token, or null if all registrars are allowed. */ - @Nullable Set allowedClientIds; + @Column(name = "allowedRegistrarIds") + @Nullable + Set allowedClientIds; /** Allowed TLDs for this token, or null if all TLDs are allowed. */ @Nullable Set allowedTlds; @@ -117,6 +136,7 @@ public class AllocationToken extends BackupGroupRoot implements Buildable { int discountYears = 1; /** The type of the token, either single-use or unlimited-use. */ + @Enumerated(EnumType.STRING) TokenType tokenType; // TODO: Remove onLoad once all allocation tokens are migrated to have a discountYears of 1. @@ -161,7 +181,7 @@ public class AllocationToken extends BackupGroupRoot implements Buildable { return token; } - public Optional> getRedemptionHistoryEntry() { + public Optional> getRedemptionHistoryEntry() { return Optional.ofNullable(redemptionHistoryEntry); } @@ -177,7 +197,7 @@ public class AllocationToken extends BackupGroupRoot implements Buildable { return Optional.ofNullable(creationTime.getTimestamp()); } - public ImmutableSet getAllowedClientIds() { + public ImmutableSet getAllowedRegistrarIds() { return nullToEmptyImmutableCopy(allowedClientIds); } @@ -260,7 +280,7 @@ public class AllocationToken extends BackupGroupRoot implements Buildable { return this; } - public Builder setRedemptionHistoryEntry(Key redemptionHistoryEntry) { + public Builder setRedemptionHistoryEntry(VKey redemptionHistoryEntry) { getInstance().redemptionHistoryEntry = checkArgumentNotNull(redemptionHistoryEntry, "Redemption history entry must not be null"); return this; @@ -279,8 +299,8 @@ public class AllocationToken extends BackupGroupRoot implements Buildable { return this; } - public Builder setAllowedClientIds(Set allowedClientIds) { - getInstance().allowedClientIds = forceEmptyToNull(allowedClientIds); + public Builder setAllowedRegistrarIds(Set allowedRegistrarIds) { + getInstance().allowedClientIds = forceEmptyToNull(allowedRegistrarIds); return this; } diff --git a/core/src/main/java/google/registry/model/reporting/HistoryEntry.java b/core/src/main/java/google/registry/model/reporting/HistoryEntry.java index 1201a9b3b..38b1ee948 100644 --- a/core/src/main/java/google/registry/model/reporting/HistoryEntry.java +++ b/core/src/main/java/google/registry/model/reporting/HistoryEntry.java @@ -31,6 +31,7 @@ import google.registry.model.annotations.ReportedOn; import google.registry.model.domain.Period; import google.registry.model.eppcommon.Trid; import google.registry.persistence.VKey; +import google.registry.persistence.WithStringVKey; import java.util.Set; import javax.annotation.Nullable; import javax.persistence.AttributeOverride; @@ -49,6 +50,7 @@ import org.joda.time.DateTime; @ReportedOn @Entity @MappedSuperclass +@WithStringVKey // TODO(b/162229294): This should be resolved during the course of that bug public class HistoryEntry extends ImmutableObject implements Buildable { /** Represents the type of history entry. */ diff --git a/core/src/main/java/google/registry/persistence/converter/AllocationTokenStatusTransitionConverter.java b/core/src/main/java/google/registry/persistence/converter/AllocationTokenStatusTransitionConverter.java new file mode 100644 index 000000000..1a4bee5a6 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/converter/AllocationTokenStatusTransitionConverter.java @@ -0,0 +1,49 @@ +// 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 com.google.common.collect.Maps; +import google.registry.model.common.TimedTransitionProperty; +import google.registry.model.domain.token.AllocationToken.TokenStatus; +import google.registry.model.domain.token.AllocationToken.TokenStatusTransition; +import java.util.Map; +import javax.persistence.Converter; +import org.joda.time.DateTime; + +/** + * JPA converter for storing/retrieving {@link TimedTransitionProperty} maps referring to {@link + * TokenStatus}es. + */ +@Converter(autoApply = true) +public class AllocationTokenStatusTransitionConverter + extends TimedTransitionPropertyConverterBase { + + @Override + Map.Entry convertToDatabaseMapEntry( + Map.Entry entry) { + return Maps.immutableEntry(entry.getKey().toString(), entry.getValue().getValue().name()); + } + + @Override + Map.Entry convertToEntityMapEntry(Map.Entry entry) { + return Maps.immutableEntry( + DateTime.parse(entry.getKey()), TokenStatus.valueOf(entry.getValue())); + } + + @Override + Class getTimedTransitionSubclass() { + return TokenStatusTransition.class; + } +} diff --git a/core/src/main/java/google/registry/tools/GenerateAllocationTokensCommand.java b/core/src/main/java/google/registry/tools/GenerateAllocationTokensCommand.java index 01e12eb1d..4e25d1f41 100644 --- a/core/src/main/java/google/registry/tools/GenerateAllocationTokensCommand.java +++ b/core/src/main/java/google/registry/tools/GenerateAllocationTokensCommand.java @@ -182,7 +182,8 @@ class GenerateAllocationTokensCommand implements CommandWithRemoteApi { new AllocationToken.Builder() .setToken(t) .setTokenType(tokenType == null ? SINGLE_USE : tokenType) - .setAllowedClientIds(ImmutableSet.copyOf(nullToEmpty(allowedClientIds))) + .setAllowedRegistrarIds( + ImmutableSet.copyOf(nullToEmpty(allowedClientIds))) .setAllowedTlds(ImmutableSet.copyOf(nullToEmpty(allowedTlds))); Optional.ofNullable(discountFraction).ifPresent(token::setDiscountFraction); Optional.ofNullable(discountPremiums).ifPresent(token::setDiscountPremiums); diff --git a/core/src/main/java/google/registry/tools/GetAllocationTokenCommand.java b/core/src/main/java/google/registry/tools/GetAllocationTokenCommand.java index c70c6da4d..35777a897 100644 --- a/core/src/main/java/google/registry/tools/GetAllocationTokenCommand.java +++ b/core/src/main/java/google/registry/tools/GetAllocationTokenCommand.java @@ -16,6 +16,7 @@ package google.registry.tools; import static com.google.common.collect.ImmutableList.toImmutableList; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.persistence.transaction.TransactionManagerFactory.tm; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; @@ -59,7 +60,7 @@ final class GetAllocationTokenCommand implements CommandWithRemoteApi { System.out.printf("Token %s was not redeemed.\n", token); } else { DomainBase domain = - domains.get(loadedToken.getRedemptionHistoryEntry().get().getParent()); + domains.get(loadedToken.getRedemptionHistoryEntry().get().getOfyKey().getParent()); if (domain == null) { System.out.printf("ERROR: Token %s was redeemed but domain can't be loaded.\n", token); } else { @@ -82,7 +83,8 @@ final class GetAllocationTokenCommand implements CommandWithRemoteApi { .map(AllocationToken::getRedemptionHistoryEntry) .filter(Optional::isPresent) .map(Optional::get) - .map(Key::getParent) + .map(key -> tm().load(key)) + .map(he -> (Key) he.getParent()) .collect(toImmutableList()); ImmutableMap.Builder, DomainBase> domainsBuilder = new ImmutableMap.Builder<>(); for (List> keys : Lists.partition(domainKeys, BATCH_SIZE)) { diff --git a/core/src/main/java/google/registry/tools/UpdateAllocationTokensCommand.java b/core/src/main/java/google/registry/tools/UpdateAllocationTokensCommand.java index 48dc11270..345a86549 100644 --- a/core/src/main/java/google/registry/tools/UpdateAllocationTokensCommand.java +++ b/core/src/main/java/google/registry/tools/UpdateAllocationTokensCommand.java @@ -131,7 +131,7 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens private AllocationToken updateToken(AllocationToken original) { AllocationToken.Builder builder = original.asBuilder(); Optional.ofNullable(allowedClientIds) - .ifPresent(clientIds -> builder.setAllowedClientIds(ImmutableSet.copyOf(clientIds))); + .ifPresent(clientIds -> builder.setAllowedRegistrarIds(ImmutableSet.copyOf(clientIds))); Optional.ofNullable(allowedTlds) .ifPresent(tlds -> builder.setAllowedTlds(ImmutableSet.copyOf(tlds))); Optional.ofNullable(discountFraction).ifPresent(builder::setDiscountFraction); diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index 1a1f225ad..015dd176e 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -26,6 +26,7 @@ google.registry.model.contact.ContactResource google.registry.model.domain.DomainBase google.registry.model.domain.DomainHistory + google.registry.model.domain.token.AllocationToken google.registry.model.host.HostHistory google.registry.model.host.HostResource google.registry.model.registrar.Registrar @@ -46,6 +47,7 @@ google.registry.model.registry.label.ReservedList + google.registry.persistence.converter.AllocationTokenStatusTransitionConverter google.registry.persistence.converter.BillingCostTransitionConverter google.registry.persistence.converter.BillingEventFlagSetConverter google.registry.persistence.converter.BloomFilterConverter @@ -76,6 +78,7 @@ google.registry.model.host.VKeyConverter_HostResource google.registry.model.poll.VKeyConverter_Autorenew google.registry.model.poll.VKeyConverter_OneTime + google.registry.model.reporting.VKeyConverter_HistoryEntry NONE diff --git a/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java index 1eb1363b8..547fe5be0 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainCheckFlowTest.java @@ -71,6 +71,7 @@ import google.registry.model.registry.Registry; import google.registry.model.registry.Registry.TldState; import google.registry.model.registry.label.ReservedList; import google.registry.model.reporting.HistoryEntry; +import google.registry.persistence.VKey; import org.joda.money.CurrencyUnit; import org.joda.money.Money; import org.joda.time.DateTime; @@ -162,12 +163,13 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1L); persistResource( new AllocationToken.Builder() .setToken("abc123") .setTokenType(SINGLE_USE) - .setRedemptionHistoryEntry(Key.create(HistoryEntry.class, 1L)) + .setRedemptionHistoryEntry(VKey.create(HistoryEntry.class, 1L, historyEntryKey)) .build()); doCheckTest( create(false, "example1.tld", "In use"), @@ -417,7 +419,7 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCasenaturalOrder() .put(START_OF_TIME, TokenStatus.NOT_STARTED) diff --git a/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java b/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java index ba222b882..9e8b75581 100644 --- a/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java +++ b/core/src/test/java/google/registry/flows/domain/DomainCreateFlowTest.java @@ -162,6 +162,7 @@ import google.registry.model.reporting.DomainTransactionRecord; import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField; import google.registry.model.reporting.HistoryEntry; import google.registry.monitoring.whitebox.EppMetric; +import google.registry.persistence.VKey; import google.registry.testing.TaskQueueHelper.TaskMatcher; import java.math.BigDecimal; import java.util.Map; @@ -492,11 +493,13 @@ class DomainCreateFlowTest extends ResourceFlowTestCase historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 505L); persistResource( new AllocationToken.Builder() .setToken("abc123") .setTokenType(SINGLE_USE) - .setRedemptionHistoryEntry(Key.create(HistoryEntry.class, 505L)) + .setRedemptionHistoryEntry(VKey.create(HistoryEntry.class, 505L, historyEntryKey)) .build()); clock.advanceOneMilli(); EppException thrown = @@ -519,7 +522,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCasenaturalOrder() diff --git a/core/src/test/java/google/registry/flows/domain/token/AllocationTokenFlowUtilsTest.java b/core/src/test/java/google/registry/flows/domain/token/AllocationTokenFlowUtilsTest.java index ea39cfdcd..e9e54cf90 100644 --- a/core/src/test/java/google/registry/flows/domain/token/AllocationTokenFlowUtilsTest.java +++ b/core/src/test/java/google/registry/flows/domain/token/AllocationTokenFlowUtilsTest.java @@ -22,6 +22,7 @@ import static google.registry.model.domain.token.AllocationToken.TokenStatus.VAL import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE; import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.persistActiveDomain; import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions; import static google.registry.util.DateTimeUtils.START_OF_TIME; @@ -42,11 +43,13 @@ import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTok import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForRegistrarException; import google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException; import google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException; +import google.registry.model.domain.DomainBase; import google.registry.model.domain.DomainCommand; import google.registry.model.domain.token.AllocationToken; import google.registry.model.domain.token.AllocationToken.TokenStatus; import google.registry.model.registry.Registry; import google.registry.model.reporting.HistoryEntry; +import google.registry.persistence.VKey; import google.registry.testing.AppEngineExtension; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; @@ -127,7 +130,7 @@ class AllocationTokenFlowUtilsTest { void test_validateToken_invalidForClientId() { persistResource( createOneMonthPromoTokenBuilder(DateTime.now(UTC).minusDays(1)) - .setAllowedClientIds(ImmutableSet.of("NewRegistrar")) + .setAllowedRegistrarIds(ImmutableSet.of("NewRegistrar")) .build()); assertValidateThrowsEppException(AllocationTokenNotValidForRegistrarException.class); } @@ -189,11 +192,13 @@ class AllocationTokenFlowUtilsTest { @Test void test_checkDomainsWithToken_showsFailureMessageForRedeemedToken() { + DomainBase domain = persistActiveDomain("example.tld"); + Key historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1051L); persistResource( new AllocationToken.Builder() .setToken("tokeN") .setTokenType(SINGLE_USE) - .setRedemptionHistoryEntry(Key.create(HistoryEntry.class, 101L)) + .setRedemptionHistoryEntry(VKey.create(HistoryEntry.class, 101L, historyEntryKey)) .build()); assertThat( flowUtils diff --git a/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java b/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java index ce17199c0..f9f43945f 100644 --- a/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java +++ b/core/src/test/java/google/registry/model/domain/token/AllocationTokenTest.java @@ -16,6 +16,7 @@ package google.registry.model.domain.token; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth8.assertThat; +import static google.registry.model.ImmutableObjectSubject.assertAboutImmutableObjects; import static google.registry.model.domain.token.AllocationToken.TokenStatus.CANCELLED; import static google.registry.model.domain.token.AllocationToken.TokenStatus.ENDED; import static google.registry.model.domain.token.AllocationToken.TokenStatus.NOT_STARTED; @@ -23,7 +24,9 @@ import static google.registry.model.domain.token.AllocationToken.TokenStatus.VAL import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE; import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE; import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.persistActiveDomain; import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.util.DateTimeUtils.START_OF_TIME; import static org.joda.time.DateTimeZone.UTC; @@ -33,15 +36,21 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.googlecode.objectify.Key; import google.registry.model.EntityTestCase; +import google.registry.model.domain.DomainBase; import google.registry.model.domain.token.AllocationToken.TokenStatus; import google.registry.model.domain.token.AllocationToken.TokenType; import google.registry.model.reporting.HistoryEntry; +import google.registry.persistence.VKey; import org.joda.time.DateTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** Unit tests for {@link AllocationToken}. */ -class AllocationTokenTest extends EntityTestCase { +public class AllocationTokenTest extends EntityTestCase { + + public AllocationTokenTest() { + super(JpaEntityCoverageCheck.ENABLED); + } @BeforeEach void beforeEach() { @@ -53,11 +62,11 @@ class AllocationTokenTest extends EntityTestCase { AllocationToken unlimitedUseToken = persistResource( new AllocationToken.Builder() - .setToken("abc123") + .setToken("abc123Unlimited") .setTokenType(UNLIMITED_USE) .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")) .setAllowedTlds(ImmutableSet.of("dev", "app")) - .setAllowedClientIds(ImmutableSet.of("TheRegistrar, NewRegistrar")) + .setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar, NewRegistrar")) .setDiscountFraction(0.5) .setDiscountPremiums(true) .setDiscountYears(3) @@ -70,26 +79,52 @@ class AllocationTokenTest extends EntityTestCase { .build()); assertThat(ofy().load().entity(unlimitedUseToken).now()).isEqualTo(unlimitedUseToken); + DomainBase domain = persistActiveDomain("example.foo"); + Key historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1); AllocationToken singleUseToken = persistResource( new AllocationToken.Builder() - .setToken("abc123") - .setRedemptionHistoryEntry(Key.create(HistoryEntry.class, 1L)) + .setToken("abc123Single") + .setRedemptionHistoryEntry(HistoryEntry.createVKey(historyEntryKey)) .setDomainName("example.foo") .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")) .setTokenType(SINGLE_USE) .build()); assertThat(ofy().load().entity(singleUseToken).now()).isEqualTo(singleUseToken); + + jpaTm() + .transact( + () -> { + jpaTm().saveNew(unlimitedUseToken); + jpaTm().saveNew(singleUseToken); + }); + jpaTm() + .transact( + () -> { + assertAboutImmutableObjects() + .that(jpaTm().load(VKey.createSql(AllocationToken.class, "abc123Unlimited"))) + .isEqualExceptFields( + unlimitedUseToken, + "creationTime", + "updateTimestamp", + "redemptionHistoryEntry"); + assertAboutImmutableObjects() + .that(jpaTm().load(VKey.createSql(AllocationToken.class, "abc123Single"))) + .isEqualExceptFields( + singleUseToken, "creationTime", "updateTimestamp", "redemptionHistoryEntry"); + }); } @Test void testIndexing() throws Exception { + DomainBase domain = persistActiveDomain("blahdomain.foo"); + Key historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1); verifyIndexing( persistResource( new AllocationToken.Builder() .setToken("abc123") .setTokenType(SINGLE_USE) - .setRedemptionHistoryEntry(Key.create(HistoryEntry.class, 1L)) + .setRedemptionHistoryEntry(VKey.create(HistoryEntry.class, 1L, historyEntryKey)) .setDomainName("blahdomain.foo") .setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z")) .build()), @@ -193,7 +228,7 @@ class AllocationTokenTest extends EntityTestCase { new AllocationToken.Builder() .setToken("foobar") .setTokenType(TokenType.UNLIMITED_USE) - .setRedemptionHistoryEntry(Key.create(HistoryEntry.class, "hi")); + .setRedemptionHistoryEntry(VKey.create(HistoryEntry.class, 1L)); IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, builder::build); assertThat(thrown) .hasMessageThat() diff --git a/core/src/test/java/google/registry/persistence/converter/AllocationTokenStatusTransitionConverterTest.java b/core/src/test/java/google/registry/persistence/converter/AllocationTokenStatusTransitionConverterTest.java new file mode 100644 index 000000000..4c9cd2b79 --- /dev/null +++ b/core/src/test/java/google/registry/persistence/converter/AllocationTokenStatusTransitionConverterTest.java @@ -0,0 +1,87 @@ +// 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.model.domain.token.AllocationToken.TokenStatus.ENDED; +import static google.registry.model.domain.token.AllocationToken.TokenStatus.NOT_STARTED; +import static google.registry.model.domain.token.AllocationToken.TokenStatus.VALID; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; +import static google.registry.util.DateTimeUtils.START_OF_TIME; + +import com.google.common.collect.ImmutableSortedMap; +import google.registry.model.ImmutableObject; +import google.registry.model.common.TimedTransitionProperty; +import google.registry.model.domain.token.AllocationToken.TokenStatus; +import google.registry.model.domain.token.AllocationToken.TokenStatusTransition; +import google.registry.persistence.transaction.JpaTestRules; +import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestExtension; +import javax.persistence.Entity; +import javax.persistence.Id; +import org.joda.time.DateTime; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** Unit tests for {@link AllocationTokenStatusTransitionConverter}. */ +public class AllocationTokenStatusTransitionConverterTest { + + @RegisterExtension + public final JpaUnitTestExtension jpa = + new JpaTestRules.Builder() + .withInitScript("sql/flyway/V14__load_extension_for_hstore.sql") + .withEntityClass(AllocationTokenStatusTransitionConverterTestEntity.class) + .buildUnitTestRule(); + + private static final ImmutableSortedMap values = + ImmutableSortedMap.of( + START_OF_TIME, + NOT_STARTED, + DateTime.parse("2001-01-01T00:00:00.0Z"), + VALID, + DateTime.parse("2002-01-01T00:00:00.0Z"), + ENDED); + + @Test + void roundTripConversion_returnsSameTimedTransitionProperty() { + TimedTransitionProperty timedTransitionProperty = + TimedTransitionProperty.fromValueMap(values, TokenStatusTransition.class); + AllocationTokenStatusTransitionConverterTestEntity testEntity = + new AllocationTokenStatusTransitionConverterTestEntity(timedTransitionProperty); + jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity)); + AllocationTokenStatusTransitionConverterTestEntity persisted = + jpaTm() + .transact( + () -> + jpaTm() + .getEntityManager() + .find(AllocationTokenStatusTransitionConverterTestEntity.class, "id")); + assertThat(persisted.timedTransitionProperty).containsExactlyEntriesIn(timedTransitionProperty); + } + + @Entity + private static class AllocationTokenStatusTransitionConverterTestEntity extends ImmutableObject { + + @Id String name = "id"; + + TimedTransitionProperty timedTransitionProperty; + + private AllocationTokenStatusTransitionConverterTestEntity() {} + + private AllocationTokenStatusTransitionConverterTestEntity( + TimedTransitionProperty timedTransitionProperty) { + this.timedTransitionProperty = timedTransitionProperty; + } + } +} diff --git a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java index d76184bf5..9c1a3d814 100644 --- a/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java +++ b/core/src/test/java/google/registry/schema/integration/SqlIntegrationTestSuite.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assert_; import google.registry.model.billing.BillingEventTest; import google.registry.model.contact.ContactResourceTest; import google.registry.model.domain.DomainBaseSqlTest; +import google.registry.model.domain.token.AllocationTokenTest; import google.registry.model.history.ContactHistoryTest; import google.registry.model.history.DomainHistoryTest; import google.registry.model.history.HostHistoryTest; @@ -72,6 +73,7 @@ import org.junit.runner.RunWith; @SelectClasses({ // BeforeSuiteTest must be the first entry. See class javadoc for details. BeforeSuiteTest.class, + AllocationTokenTest.class, BillingEventTest.class, ClaimsListDaoTest.class, ContactHistoryTest.class, diff --git a/core/src/test/java/google/registry/tools/DeleteAllocationTokensCommandTest.java b/core/src/test/java/google/registry/tools/DeleteAllocationTokensCommandTest.java index 9c123bd3b..f8553f2f7 100644 --- a/core/src/test/java/google/registry/tools/DeleteAllocationTokensCommandTest.java +++ b/core/src/test/java/google/registry/tools/DeleteAllocationTokensCommandTest.java @@ -18,13 +18,16 @@ import static com.google.common.truth.Truth.assertThat; import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.testing.DatastoreHelper.createTlds; +import static google.registry.testing.DatastoreHelper.persistActiveDomain; import static google.registry.testing.DatastoreHelper.persistResource; import static org.junit.jupiter.api.Assertions.assertThrows; import com.googlecode.objectify.Key; +import google.registry.model.domain.DomainBase; import google.registry.model.domain.token.AllocationToken; import google.registry.model.domain.token.AllocationToken.TokenType; import google.registry.model.reporting.HistoryEntry; +import google.registry.persistence.VKey; import java.util.Collection; import javax.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; @@ -167,12 +170,15 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1051L); + builder.setRedemptionHistoryEntry(VKey.create(HistoryEntry.class, 1051L, historyEntryKey)); } return persistResource(builder.build()); } - private static Collection reloadTokens(AllocationToken ... tokens) { + private static Collection reloadTokens(AllocationToken... tokens) { return ofy().load().entities(tokens).values(); } } diff --git a/core/src/test/java/google/registry/tools/GenerateAllocationTokensCommandTest.java b/core/src/test/java/google/registry/tools/GenerateAllocationTokensCommandTest.java index 0de26760c..b08697d45 100644 --- a/core/src/test/java/google/registry/tools/GenerateAllocationTokensCommandTest.java +++ b/core/src/test/java/google/registry/tools/GenerateAllocationTokensCommandTest.java @@ -37,10 +37,10 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.Iterables; import com.google.common.io.Files; -import com.googlecode.objectify.Key; import google.registry.model.domain.token.AllocationToken; import google.registry.model.domain.token.AllocationToken.TokenStatus; import google.registry.model.reporting.HistoryEntry; +import google.registry.persistence.VKey; import google.registry.testing.DeterministicStringGenerator; import google.registry.testing.DeterministicStringGenerator.Rule; import google.registry.testing.FakeClock; @@ -168,7 +168,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase redemptionHistoryEntry, + @Nullable VKey redemptionHistoryEntry, @Nullable String domainName) { AllocationToken.Builder builder = new AllocationToken.Builder().setToken(token).setTokenType(SINGLE_USE); diff --git a/core/src/test/java/google/registry/tools/GetAllocationTokenCommandTest.java b/core/src/test/java/google/registry/tools/GetAllocationTokenCommandTest.java index 56cecb96a..8838b0f65 100644 --- a/core/src/test/java/google/registry/tools/GetAllocationTokenCommandTest.java +++ b/core/src/test/java/google/registry/tools/GetAllocationTokenCommandTest.java @@ -28,6 +28,7 @@ import com.google.common.collect.ImmutableList; import com.googlecode.objectify.Key; import google.registry.model.domain.DomainBase; import google.registry.model.domain.token.AllocationToken; +import google.registry.model.reporting.HistoryEntry; import org.joda.time.DateTime; import org.junit.jupiter.api.Test; @@ -83,7 +84,8 @@ class GetAllocationTokenCommandTest extends CommandTestCase redemptionHistoryEntry; double discountFraction; google.registry.model.CreateAutoTimestamp creationTime; google.registry.model.UpdateAutoTimestamp updateTimestamp; google.registry.model.common.TimedTransitionProperty tokenStatusTransitions; google.registry.model.domain.token.AllocationToken$TokenType tokenType; + google.registry.persistence.VKey redemptionHistoryEntry; int discountYears; java.lang.String domainName; java.util.Set allowedClientIds; diff --git a/db/src/main/resources/sql/flyway/V49__create_allocation_token.sql b/db/src/main/resources/sql/flyway/V49__create_allocation_token.sql new file mode 100644 index 000000000..39cdebc53 --- /dev/null +++ b/db/src/main/resources/sql/flyway/V49__create_allocation_token.sql @@ -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. + +CREATE TABLE "AllocationToken" ( + token text NOT NULL, + update_timestamp timestamptz, + allowed_registrar_ids text[], + allowed_tlds text[], + creation_time timestamptz NOT NULL, + discount_fraction float8 NOT NULL, + discount_premiums boolean NOT NULL, + discount_years int4 NOT NULL, + domain_name text, + redemption_history_entry text, + token_status_transitions hstore, + token_type text, + PRIMARY KEY (token) +); + +CREATE INDEX allocation_token_domain_name_idx ON "AllocationToken" (domain_name); diff --git a/db/src/main/resources/sql/schema/db-schema.sql.generated b/db/src/main/resources/sql/schema/db-schema.sql.generated index 4c22b0cb4..92ab3ea0d 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -13,6 +13,22 @@ -- limitations under the License. create sequence history_id_sequence start 1 increment 1; + create table "AllocationToken" ( + token text not null, + update_timestamp timestamptz, + allowed_registrar_ids text[], + allowed_tlds text[], + creation_time timestamptz not null, + discount_fraction float8 not null, + discount_premiums boolean not null, + discount_years int4 not null, + domain_name text, + redemption_history_entry text, + token_status_transitions hstore, + token_type text, + primary key (token) + ); + create table "BillingCancellation" ( billing_cancellation_id bigserial not null, registrar_id text not null, @@ -567,6 +583,7 @@ create sequence history_id_sequence start 1 increment 1; contents bytea, primary key (id) ); +create index allocation_token_domain_name_idx on "AllocationToken" (domain_name); create index IDXih4b2tea127p5rb61gje6e1y2 on "BillingCancellation" (registrar_id); create index IDX2exdfbx6oiiwnhr8j6gjpqt2j on "BillingCancellation" (event_time); create index IDXqa3g92jc17e8dtiaviy4fet4x on "BillingCancellation" (billing_time); diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql index 1bdd3124a..0c6e16e6d 100644 --- a/db/src/main/resources/sql/schema/nomulus.golden.sql +++ b/db/src/main/resources/sql/schema/nomulus.golden.sql @@ -34,6 +34,26 @@ SET default_tablespace = ''; SET default_with_oids = false; +-- +-- Name: AllocationToken; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public."AllocationToken" ( + token text NOT NULL, + update_timestamp timestamp with time zone, + allowed_registrar_ids text[], + allowed_tlds text[], + creation_time timestamp with time zone NOT NULL, + discount_fraction double precision NOT NULL, + discount_premiums boolean NOT NULL, + discount_years integer NOT NULL, + domain_name text, + redemption_history_entry text, + token_status_transitions public.hstore, + token_type text +); + + -- -- Name: BillingCancellation; Type: TABLE; Schema: public; Owner: - -- @@ -985,6 +1005,14 @@ ALTER TABLE ONLY public."Spec11ThreatMatch" ALTER COLUMN id SET DEFAULT nextval( ALTER TABLE ONLY public."Transaction" ALTER COLUMN id SET DEFAULT nextval('public."Transaction_id_seq"'::regclass); +-- +-- Name: AllocationToken AllocationToken_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."AllocationToken" + ADD CONSTRAINT "AllocationToken_pkey" PRIMARY KEY (token); + + -- -- Name: BillingCancellation BillingCancellation_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -1185,6 +1213,13 @@ ALTER TABLE ONLY public."RegistryLock" ADD CONSTRAINT idx_registry_lock_repo_id_revision_id UNIQUE (repo_id, revision_id); +-- +-- Name: allocation_token_domain_name_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX allocation_token_domain_name_idx ON public."AllocationToken" USING btree (domain_name); + + -- -- Name: idx1iy7njgb7wjmj9piml4l2g0qi; Type: INDEX; Schema: public; Owner: - --