Add a SQL schema to AllocationToken (#763)

* Add a SQL schema to AllocationToken

* Respond to CR

- rename field in tests
- rename allowed_registrar_ids field
- remove unnecessary db load in GATC

* Add TODO for HistoryEntry vkeys

* Run autoformat

* V48 -> V49
This commit is contained in:
gbrodman 2020-08-20 20:18:34 -04:00 committed by GitHub
parent 8ac30a42ed
commit 876d65e232
23 changed files with 352 additions and 45 deletions

View file

@ -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);

View file

@ -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<HistoryEntry> redemptionHistoryEntry) {
AllocationToken token, VKey<HistoryEntry> 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()

View file

@ -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<TokenStatus, TokenStatus> 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<HistoryEntry> redemptionHistoryEntry;
@Nullable @Index VKey<HistoryEntry> 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<String> allowedClientIds;
@Column(name = "allowedRegistrarIds")
@Nullable
Set<String> allowedClientIds;
/** Allowed TLDs for this token, or null if all TLDs are allowed. */
@Nullable Set<String> 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<Key<HistoryEntry>> getRedemptionHistoryEntry() {
public Optional<VKey<HistoryEntry>> getRedemptionHistoryEntry() {
return Optional.ofNullable(redemptionHistoryEntry);
}
@ -177,7 +197,7 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
return Optional.ofNullable(creationTime.getTimestamp());
}
public ImmutableSet<String> getAllowedClientIds() {
public ImmutableSet<String> getAllowedRegistrarIds() {
return nullToEmptyImmutableCopy(allowedClientIds);
}
@ -260,7 +280,7 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
return this;
}
public Builder setRedemptionHistoryEntry(Key<HistoryEntry> redemptionHistoryEntry) {
public Builder setRedemptionHistoryEntry(VKey<HistoryEntry> 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<String> allowedClientIds) {
getInstance().allowedClientIds = forceEmptyToNull(allowedClientIds);
public Builder setAllowedRegistrarIds(Set<String> allowedRegistrarIds) {
getInstance().allowedClientIds = forceEmptyToNull(allowedRegistrarIds);
return this;
}

View file

@ -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. */

View file

@ -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<TokenStatus, TokenStatusTransition> {
@Override
Map.Entry<String, String> convertToDatabaseMapEntry(
Map.Entry<DateTime, TokenStatusTransition> entry) {
return Maps.immutableEntry(entry.getKey().toString(), entry.getValue().getValue().name());
}
@Override
Map.Entry<DateTime, TokenStatus> convertToEntityMapEntry(Map.Entry<String, String> entry) {
return Maps.immutableEntry(
DateTime.parse(entry.getKey()), TokenStatus.valueOf(entry.getValue()));
}
@Override
Class<TokenStatusTransition> getTimedTransitionSubclass() {
return TokenStatusTransition.class;
}
}

View file

@ -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);

View file

@ -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().<DomainBase>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::<DomainBase>getParent)
.map(key -> tm().load(key))
.map(he -> (Key<DomainBase>) he.getParent())
.collect(toImmutableList());
ImmutableMap.Builder<Key<DomainBase>, DomainBase> domainsBuilder = new ImmutableMap.Builder<>();
for (List<Key<DomainBase>> keys : Lists.partition(domainKeys, BATCH_SIZE)) {

View file

@ -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);

View file

@ -26,6 +26,7 @@
<class>google.registry.model.contact.ContactResource</class>
<class>google.registry.model.domain.DomainBase</class>
<class>google.registry.model.domain.DomainHistory</class>
<class>google.registry.model.domain.token.AllocationToken</class>
<class>google.registry.model.host.HostHistory</class>
<class>google.registry.model.host.HostResource</class>
<class>google.registry.model.registrar.Registrar</class>
@ -46,6 +47,7 @@
<class>google.registry.model.registry.label.ReservedList</class>
<!-- Customized type converters -->
<class>google.registry.persistence.converter.AllocationTokenStatusTransitionConverter</class>
<class>google.registry.persistence.converter.BillingCostTransitionConverter</class>
<class>google.registry.persistence.converter.BillingEventFlagSetConverter</class>
<class>google.registry.persistence.converter.BloomFilterConverter</class>
@ -76,6 +78,7 @@
<class>google.registry.model.host.VKeyConverter_HostResource</class>
<class>google.registry.model.poll.VKeyConverter_Autorenew</class>
<class>google.registry.model.poll.VKeyConverter_OneTime</class>
<class>google.registry.model.reporting.VKeyConverter_HistoryEntry</class>
<!-- TODO(weiminyu): check out application-layer validation. -->
<validation-mode>NONE</validation-mode>

View file

@ -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<DomainCheckFlow, Dom
@Test
void testSuccess_oneExists_allocationTokenIsRedeemed() throws Exception {
setEppInput("domain_check_allocationtoken.xml");
persistActiveDomain("example1.tld");
DomainBase domain = persistActiveDomain("example1.tld");
Key<HistoryEntry> 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 ResourceCheckFlowTestCase<DomainCheckFlow, Dom
.setToken("abc123")
.setTokenType(UNLIMITED_USE)
.setDiscountFraction(0.5)
.setAllowedClientIds(ImmutableSet.of("someOtherClient"))
.setAllowedRegistrarIds(ImmutableSet.of("someOtherClient"))
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()
.put(START_OF_TIME, TokenStatus.NOT_STARTED)

View file

@ -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<DomainCreateFlow, Domain
"domain_create_allocationtoken.xml",
ImmutableMap.of("DOMAIN", "example.tld", "YEARS", "2"));
persistContactsAndHosts();
DomainBase domain = persistActiveDomain("foo.tld");
Key<HistoryEntry> 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 ResourceFlowTestCase<DomainCreateFlow, Domain
HistoryEntry historyEntry =
ofy().load().type(HistoryEntry.class).ancestor(reloadResourceByForeignKey()).first().now();
assertThat(ofy().load().entity(token).now().getRedemptionHistoryEntry())
.hasValue(Key.create(historyEntry));
.hasValue(HistoryEntry.createVKey(Key.create(historyEntry)));
}
@Test
@ -1263,7 +1266,9 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
ofy().load().key(Key.create(AllocationToken.class, token)).now();
assertThat(reloadedToken.isRedeemed()).isTrue();
assertThat(reloadedToken.getRedemptionHistoryEntry())
.hasValue(Key.create(getHistoryEntries(reloadResourceByForeignKey()).get(0)));
.hasValue(
HistoryEntry.createVKey(
Key.create(getHistoryEntries(reloadResourceByForeignKey()).get(0))));
}
private void assertAllocationTokenWasNotRedeemed(String token) {
@ -1498,7 +1503,7 @@ class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow, Domain
new AllocationToken.Builder()
.setToken("abc123")
.setTokenType(UNLIMITED_USE)
.setAllowedClientIds(ImmutableSet.of("someClientId"))
.setAllowedRegistrarIds(ImmutableSet.of("someClientId"))
.setDiscountFraction(0.5)
.setTokenStatusTransitions(
ImmutableSortedMap.<DateTime, TokenStatus>naturalOrder()

View file

@ -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<HistoryEntry> 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

View file

@ -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<HistoryEntry> 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<HistoryEntry> 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()

View file

@ -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<DateTime, TokenStatus> 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<TokenStatus, TokenStatusTransition> 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<TokenStatus, TokenStatusTransition> timedTransitionProperty;
private AllocationTokenStatusTransitionConverterTestEntity() {}
private AllocationTokenStatusTransitionConverterTestEntity(
TimedTransitionProperty<TokenStatus, TokenStatusTransition> timedTransitionProperty) {
this.timedTransitionProperty = timedTransitionProperty;
}
}
}

View file

@ -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,

View file

@ -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<DeleteAllocation
.setTokenType(SINGLE_USE)
.setDomainName(domainName);
if (redeemed) {
builder.setRedemptionHistoryEntry(Key.create(HistoryEntry.class, 1051L));
String domainToPersist = domainName != null ? domainName : "example.foo";
DomainBase domain = persistActiveDomain(domainToPersist);
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1051L);
builder.setRedemptionHistoryEntry(VKey.create(HistoryEntry.class, 1051L, historyEntryKey));
}
return persistResource(builder.build());
}
private static Collection<AllocationToken> reloadTokens(AllocationToken ... tokens) {
private static Collection<AllocationToken> reloadTokens(AllocationToken... tokens) {
return ofy().load().entities(tokens).values();
}
}

View file

@ -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<GenerateAlloca
new AllocationToken.Builder()
.setToken("promo123456789ABCDEFG")
.setTokenType(UNLIMITED_USE)
.setAllowedClientIds(ImmutableSet.of("TheRegistrar", "NewRegistrar"))
.setAllowedRegistrarIds(ImmutableSet.of("TheRegistrar", "NewRegistrar"))
.setAllowedTlds(ImmutableSet.of("tld", "example"))
.setDiscountFraction(0.5)
.setDiscountPremiums(true)
@ -314,7 +314,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
private AllocationToken createToken(
String token,
@Nullable Key<HistoryEntry> redemptionHistoryEntry,
@Nullable VKey<HistoryEntry> redemptionHistoryEntry,
@Nullable String domainName) {
AllocationToken.Builder builder =
new AllocationToken.Builder().setToken(token).setTokenType(SINGLE_USE);

View file

@ -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<GetAllocationTokenCo
.setToken("foo")
.setTokenType(SINGLE_USE)
.setDomainName("fqqdn.tld")
.setRedemptionHistoryEntry(Key.create(createHistoryEntryForEppResource(domain)))
.setRedemptionHistoryEntry(
HistoryEntry.createVKey(Key.create(createHistoryEntryForEppResource(domain))))
.build());
runCommand("foo");
assertInStdout(

View file

@ -55,9 +55,9 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
void testUpdateClientIds_setClientIds() throws Exception {
AllocationToken token =
persistResource(
builderWithPromo().setAllowedClientIds(ImmutableSet.of("toRemove")).build());
builderWithPromo().setAllowedRegistrarIds(ImmutableSet.of("toRemove")).build());
runCommandForced("--prefix", "token", "--allowed_client_ids", "clientone,clienttwo");
assertThat(reloadResource(token).getAllowedClientIds())
assertThat(reloadResource(token).getAllowedRegistrarIds())
.containsExactly("clientone", "clienttwo");
}
@ -65,9 +65,9 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
void testUpdateClientIds_clearClientIds() throws Exception {
AllocationToken token =
persistResource(
builderWithPromo().setAllowedClientIds(ImmutableSet.of("toRemove")).build());
builderWithPromo().setAllowedRegistrarIds(ImmutableSet.of("toRemove")).build());
runCommandForced("--prefix", "token", "--allowed_client_ids", "");
assertThat(reloadResource(token).getAllowedClientIds()).isEmpty();
assertThat(reloadResource(token).getAllowedRegistrarIds()).isEmpty();
}
@Test
@ -175,14 +175,14 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
AllocationToken token =
persistResource(
builderWithPromo()
.setAllowedClientIds(ImmutableSet.of("clientid"))
.setAllowedRegistrarIds(ImmutableSet.of("clientid"))
.setAllowedTlds(ImmutableSet.of("tld"))
.setDiscountFraction(0.15)
.build());
runCommandForced("--prefix", "token");
AllocationToken reloaded = reloadResource(token);
assertThat(reloaded.getAllowedTlds()).isEqualTo(token.getAllowedTlds());
assertThat(reloaded.getAllowedClientIds()).isEqualTo(token.getAllowedClientIds());
assertThat(reloaded.getAllowedRegistrarIds()).isEqualTo(token.getAllowedRegistrarIds());
assertThat(reloaded.getDiscountFraction()).isEqualTo(token.getDiscountFraction());
}

View file

@ -233,12 +233,12 @@ class google.registry.model.domain.secdns.DelegationSignerData {
class google.registry.model.domain.token.AllocationToken {
@Id java.lang.String token;
boolean discountPremiums;
com.googlecode.objectify.Key<google.registry.model.reporting.HistoryEntry> redemptionHistoryEntry;
double discountFraction;
google.registry.model.CreateAutoTimestamp creationTime;
google.registry.model.UpdateAutoTimestamp updateTimestamp;
google.registry.model.common.TimedTransitionProperty<google.registry.model.domain.token.AllocationToken$TokenStatus, google.registry.model.domain.token.AllocationToken$TokenStatusTransition> tokenStatusTransitions;
google.registry.model.domain.token.AllocationToken$TokenType tokenType;
google.registry.persistence.VKey<google.registry.model.reporting.HistoryEntry> redemptionHistoryEntry;
int discountYears;
java.lang.String domainName;
java.util.Set<java.lang.String> allowedClientIds;

View file

@ -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);

View file

@ -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);

View file

@ -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: -
--