mirror of
https://github.com/google/nomulus.git
synced 2025-07-09 04:33:28 +02:00
Convert AllocationToken-related classes to tm() (#909)
* Convert AllocationToken-related classes to tm() For the most part this is a fairly simple converstion -- changing Key references to VKey references, using JPA transactions when necessary, and using the TransactionManager interface. There's a bit of cleanup too in related code
This commit is contained in:
parent
6e2bbd1a7e
commit
e550c94cbc
12 changed files with 201 additions and 178 deletions
|
@ -15,14 +15,13 @@
|
||||||
package google.registry.flows.domain.token;
|
package google.registry.flows.domain.token;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.net.InternetDomainName;
|
import com.google.common.net.InternetDomainName;
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import google.registry.flows.EppException;
|
import google.registry.flows.EppException;
|
||||||
import google.registry.flows.EppException.AssociationProhibitsOperationException;
|
import google.registry.flows.EppException.AssociationProhibitsOperationException;
|
||||||
import google.registry.flows.EppException.AuthorizationErrorException;
|
import google.registry.flows.EppException.AuthorizationErrorException;
|
||||||
|
@ -153,14 +152,15 @@ public class AllocationTokenFlowUtils {
|
||||||
// See https://tools.ietf.org/html/draft-ietf-regext-allocation-token-04#section-2.1
|
// See https://tools.ietf.org/html/draft-ietf-regext-allocation-token-04#section-2.1
|
||||||
throw new InvalidAllocationTokenException();
|
throw new InvalidAllocationTokenException();
|
||||||
}
|
}
|
||||||
AllocationToken tokenEntity = ofy().load().key(Key.create(AllocationToken.class, token)).now();
|
Optional<AllocationToken> maybeTokenEntity =
|
||||||
if (tokenEntity == null) {
|
tm().maybeLoad(VKey.create(AllocationToken.class, token));
|
||||||
|
if (maybeTokenEntity.isEmpty()) {
|
||||||
throw new InvalidAllocationTokenException();
|
throw new InvalidAllocationTokenException();
|
||||||
}
|
}
|
||||||
if (tokenEntity.isRedeemed()) {
|
if (maybeTokenEntity.get().isRedeemed()) {
|
||||||
throw new AlreadyRedeemedAllocationTokenException();
|
throw new AlreadyRedeemedAllocationTokenException();
|
||||||
}
|
}
|
||||||
return tokenEntity;
|
return maybeTokenEntity.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: exception messages should be <= 32 characters long for domain check results
|
// Note: exception messages should be <= 32 characters long for domain check results
|
||||||
|
|
|
@ -67,7 +67,6 @@ public class Spec11RegistrarThreatMatchesParser {
|
||||||
if (!gcsUtils.existsAndNotEmpty(spec11ReportFilename)) {
|
if (!gcsUtils.existsAndNotEmpty(spec11ReportFilename)) {
|
||||||
return ImmutableSet.of();
|
return ImmutableSet.of();
|
||||||
}
|
}
|
||||||
ImmutableSet.Builder<RegistrarThreatMatches> builder = ImmutableSet.builder();
|
|
||||||
try (InputStream in = gcsUtils.openInputStream(spec11ReportFilename);
|
try (InputStream in = gcsUtils.openInputStream(spec11ReportFilename);
|
||||||
InputStreamReader isr = new InputStreamReader(in, UTF_8)) {
|
InputStreamReader isr = new InputStreamReader(in, UTF_8)) {
|
||||||
// Skip the header at line 0
|
// Skip the header at line 0
|
||||||
|
|
|
@ -14,19 +14,19 @@
|
||||||
|
|
||||||
package google.registry.tools;
|
package google.registry.tools;
|
||||||
|
|
||||||
|
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||||
import static com.google.common.collect.Iterables.partition;
|
import static com.google.common.collect.Iterables.partition;
|
||||||
import static com.google.common.collect.Streams.stream;
|
import static com.google.common.collect.Streams.stream;
|
||||||
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
|
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
|
||||||
import com.beust.jcommander.Parameter;
|
import com.beust.jcommander.Parameter;
|
||||||
import com.beust.jcommander.Parameters;
|
import com.beust.jcommander.Parameters;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import google.registry.model.domain.token.AllocationToken;
|
import google.registry.model.domain.token.AllocationToken;
|
||||||
|
import google.registry.persistence.VKey;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,7 +48,7 @@ final class DeleteAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
|
||||||
private static final int BATCH_SIZE = 20;
|
private static final int BATCH_SIZE = 20;
|
||||||
private static final Joiner JOINER = Joiner.on(", ");
|
private static final Joiner JOINER = Joiner.on(", ");
|
||||||
|
|
||||||
private ImmutableSet<Key<AllocationToken>> tokensToDelete;
|
private ImmutableSet<VKey<AllocationToken>> tokensToDelete;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init() {
|
public void init() {
|
||||||
|
@ -71,26 +71,24 @@ final class DeleteAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Deletes a (filtered) batch of AllocationTokens and returns how many were deleted. */
|
/** Deletes a (filtered) batch of AllocationTokens and returns how many were deleted. */
|
||||||
private long deleteBatch(List<Key<AllocationToken>> batch) {
|
private long deleteBatch(List<VKey<AllocationToken>> batch) {
|
||||||
// Load the tokens in the same transaction as they are deleted to verify they weren't redeemed
|
// Load the tokens in the same transaction as they are deleted to verify they weren't redeemed
|
||||||
// since the query ran. This also filters out per-domain tokens if they're not to be deleted.
|
// since the query ran. This also filters out per-domain tokens if they're not to be deleted.
|
||||||
ImmutableSet<AllocationToken> tokensToDelete =
|
ImmutableSet<VKey<AllocationToken>> tokensToDelete =
|
||||||
ofy().load().keys(batch).values().stream()
|
tm().load(batch).values().stream()
|
||||||
.filter(t -> withDomains || !t.getDomainName().isPresent())
|
.filter(t -> withDomains || t.getDomainName().isEmpty())
|
||||||
.filter(t -> SINGLE_USE.equals(t.getTokenType()))
|
.filter(t -> SINGLE_USE.equals(t.getTokenType()))
|
||||||
.filter(t -> !t.isRedeemed())
|
.filter(t -> !t.isRedeemed())
|
||||||
|
.map(AllocationToken::createVKey)
|
||||||
.collect(toImmutableSet());
|
.collect(toImmutableSet());
|
||||||
if (!dryRun) {
|
if (!dryRun) {
|
||||||
ofy().delete().entities(tokensToDelete);
|
tm().delete(tokensToDelete);
|
||||||
}
|
}
|
||||||
System.out.printf(
|
System.out.printf(
|
||||||
"%s tokens: %s\n",
|
"%s tokens: %s\n",
|
||||||
dryRun ? "Would delete" : "Deleted",
|
dryRun ? "Would delete" : "Deleted",
|
||||||
JOINER.join(
|
JOINER.join(
|
||||||
tokensToDelete.stream()
|
tokensToDelete.stream().map(VKey::getSqlKey).sorted().collect(toImmutableList())));
|
||||||
.map(AllocationToken::getToken)
|
|
||||||
.sorted()
|
|
||||||
.collect(toImmutableSet())));
|
|
||||||
return tokensToDelete.size();
|
return tokensToDelete.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@ import static com.google.common.collect.Queues.newArrayDeque;
|
||||||
import static com.google.common.collect.Sets.difference;
|
import static com.google.common.collect.Sets.difference;
|
||||||
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
|
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.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||||
import static google.registry.util.StringGenerator.DEFAULT_PASSWORD_LENGTH;
|
import static google.registry.util.StringGenerator.DEFAULT_PASSWORD_LENGTH;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
@ -38,10 +38,10 @@ import com.google.common.collect.ImmutableSortedMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Streams;
|
import com.google.common.collect.Streams;
|
||||||
import com.google.common.io.Files;
|
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;
|
||||||
import google.registry.model.domain.token.AllocationToken.TokenStatus;
|
import google.registry.model.domain.token.AllocationToken.TokenStatus;
|
||||||
import google.registry.model.domain.token.AllocationToken.TokenType;
|
import google.registry.model.domain.token.AllocationToken.TokenType;
|
||||||
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.tools.params.TransitionListParameter.TokenStatusTransitions;
|
import google.registry.tools.params.TransitionListParameter.TokenStatusTransitions;
|
||||||
import google.registry.util.CollectionUtils;
|
import google.registry.util.CollectionUtils;
|
||||||
import google.registry.util.NonFinalForTesting;
|
import google.registry.util.NonFinalForTesting;
|
||||||
|
@ -258,8 +258,13 @@ class GenerateAllocationTokensCommand implements CommandWithRemoteApi {
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
int saveTokens(final ImmutableSet<AllocationToken> tokens) {
|
int saveTokens(final ImmutableSet<AllocationToken> tokens) {
|
||||||
Collection<AllocationToken> savedTokens =
|
Collection<AllocationToken> savedTokens;
|
||||||
dryRun ? tokens : tm().transact(() -> ofy().save().entities(tokens).now().values());
|
if (dryRun) {
|
||||||
|
savedTokens = tokens;
|
||||||
|
} else {
|
||||||
|
transactIfJpaTm(() -> tm().transact(() -> tm().putAll(tokens)));
|
||||||
|
savedTokens = tm().transact(() -> tm().loadAll(tokens));
|
||||||
|
}
|
||||||
savedTokens.forEach(
|
savedTokens.forEach(
|
||||||
t -> System.out.println(SKIP_NULLS.join(t.getDomainName().orElse(null), t.getToken())));
|
t -> System.out.println(SKIP_NULLS.join(t.getDomainName().orElse(null), t.getToken())));
|
||||||
return savedTokens.size();
|
return savedTokens.size();
|
||||||
|
@ -282,12 +287,14 @@ class GenerateAllocationTokensCommand implements CommandWithRemoteApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImmutableSet<String> getExistingTokenStrings(ImmutableSet<String> candidates) {
|
private ImmutableSet<String> getExistingTokenStrings(ImmutableSet<String> candidates) {
|
||||||
ImmutableSet<Key<AllocationToken>> existingTokenKeys =
|
ImmutableSet<VKey<AllocationToken>> existingTokenKeys =
|
||||||
candidates.stream()
|
candidates.stream()
|
||||||
.map(input -> Key.create(AllocationToken.class, input))
|
.map(input -> VKey.create(AllocationToken.class, input))
|
||||||
.collect(toImmutableSet());
|
.collect(toImmutableSet());
|
||||||
return ofy().load().keys(existingTokenKeys).values().stream()
|
return transactIfJpaTm(
|
||||||
|
() ->
|
||||||
|
tm().load(existingTokenKeys).values().stream()
|
||||||
.map(AllocationToken::getToken)
|
.map(AllocationToken::getToken)
|
||||||
.collect(toImmutableSet());
|
.collect(toImmutableSet()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
package google.registry.tools;
|
package google.registry.tools;
|
||||||
|
|
||||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
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 static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
|
||||||
import com.beust.jcommander.Parameter;
|
import com.beust.jcommander.Parameter;
|
||||||
|
@ -26,6 +25,7 @@ import com.google.common.collect.Lists;
|
||||||
import com.googlecode.objectify.Key;
|
import com.googlecode.objectify.Key;
|
||||||
import google.registry.model.domain.DomainBase;
|
import google.registry.model.domain.DomainBase;
|
||||||
import google.registry.model.domain.token.AllocationToken;
|
import google.registry.model.domain.token.AllocationToken;
|
||||||
|
import google.registry.persistence.VKey;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -45,22 +45,26 @@ final class GetAllocationTokenCommand implements CommandWithRemoteApi {
|
||||||
public void run() {
|
public void run() {
|
||||||
ImmutableMap.Builder<String, AllocationToken> builder = new ImmutableMap.Builder<>();
|
ImmutableMap.Builder<String, AllocationToken> builder = new ImmutableMap.Builder<>();
|
||||||
for (List<String> tokens : Lists.partition(mainParameters, BATCH_SIZE)) {
|
for (List<String> tokens : Lists.partition(mainParameters, BATCH_SIZE)) {
|
||||||
ImmutableList<Key<AllocationToken>> tokenKeys =
|
ImmutableList<VKey<AllocationToken>> tokenKeys =
|
||||||
tokens.stream().map(t -> Key.create(AllocationToken.class, t)).collect(toImmutableList());
|
tokens.stream()
|
||||||
ofy().load().keys(tokenKeys).forEach((k, v) -> builder.put(k.getName(), v));
|
.map(t -> VKey.create(AllocationToken.class, t))
|
||||||
|
.collect(toImmutableList());
|
||||||
|
tm().load(tokenKeys).forEach((k, v) -> builder.put(k.getSqlKey().toString(), v));
|
||||||
}
|
}
|
||||||
ImmutableMap<String, AllocationToken> loadedTokens = builder.build();
|
ImmutableMap<String, AllocationToken> loadedTokens = builder.build();
|
||||||
ImmutableMap<Key<DomainBase>, DomainBase> domains = loadRedeemedDomains(loadedTokens.values());
|
ImmutableMap<VKey<DomainBase>, DomainBase> domains = loadRedeemedDomains(loadedTokens.values());
|
||||||
|
|
||||||
for (String token : mainParameters) {
|
for (String token : mainParameters) {
|
||||||
if (loadedTokens.containsKey(token)) {
|
if (loadedTokens.containsKey(token)) {
|
||||||
AllocationToken loadedToken = loadedTokens.get(token);
|
AllocationToken loadedToken = loadedTokens.get(token);
|
||||||
System.out.println(loadedToken.toString());
|
System.out.println(loadedToken.toString());
|
||||||
if (!loadedToken.getRedemptionHistoryEntry().isPresent()) {
|
if (loadedToken.getRedemptionHistoryEntry().isEmpty()) {
|
||||||
System.out.printf("Token %s was not redeemed.\n", token);
|
System.out.printf("Token %s was not redeemed.\n", token);
|
||||||
} else {
|
} else {
|
||||||
|
Key<DomainBase> domainOfyKey =
|
||||||
|
loadedToken.getRedemptionHistoryEntry().get().getOfyKey().getParent();
|
||||||
DomainBase domain =
|
DomainBase domain =
|
||||||
domains.get(loadedToken.getRedemptionHistoryEntry().get().getOfyKey().getParent());
|
domains.get(VKey.create(DomainBase.class, domainOfyKey.getName(), domainOfyKey));
|
||||||
if (domain == null) {
|
if (domain == null) {
|
||||||
System.out.printf("ERROR: Token %s was redeemed but domain can't be loaded.\n", token);
|
System.out.printf("ERROR: Token %s was redeemed but domain can't be loaded.\n", token);
|
||||||
} else {
|
} else {
|
||||||
|
@ -76,19 +80,23 @@ final class GetAllocationTokenCommand implements CommandWithRemoteApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImmutableMap<Key<DomainBase>, DomainBase> loadRedeemedDomains(
|
@SuppressWarnings("unchecked")
|
||||||
|
private static ImmutableMap<VKey<DomainBase>, DomainBase> loadRedeemedDomains(
|
||||||
Collection<AllocationToken> tokens) {
|
Collection<AllocationToken> tokens) {
|
||||||
ImmutableList<Key<DomainBase>> domainKeys =
|
ImmutableList<VKey<DomainBase>> domainKeys =
|
||||||
tokens.stream()
|
tokens.stream()
|
||||||
.map(AllocationToken::getRedemptionHistoryEntry)
|
.map(AllocationToken::getRedemptionHistoryEntry)
|
||||||
.filter(Optional::isPresent)
|
.filter(Optional::isPresent)
|
||||||
.map(Optional::get)
|
.map(Optional::get)
|
||||||
.map(key -> tm().load(key))
|
.map(key -> tm().load(key))
|
||||||
.map(he -> (Key<DomainBase>) he.getParent())
|
.map(he -> (Key<DomainBase>) he.getParent())
|
||||||
|
.map(key -> VKey.create(DomainBase.class, key.getName(), key))
|
||||||
.collect(toImmutableList());
|
.collect(toImmutableList());
|
||||||
ImmutableMap.Builder<Key<DomainBase>, DomainBase> domainsBuilder = new ImmutableMap.Builder<>();
|
ImmutableMap.Builder<VKey<DomainBase>, DomainBase> domainsBuilder =
|
||||||
for (List<Key<DomainBase>> keys : Lists.partition(domainKeys, BATCH_SIZE)) {
|
new ImmutableMap.Builder<>();
|
||||||
domainsBuilder.putAll(ofy().load().keys(keys));
|
for (List<VKey<DomainBase>> keys : Lists.partition(domainKeys, BATCH_SIZE)) {
|
||||||
|
tm().load(ImmutableList.copyOf(keys))
|
||||||
|
.forEach((k, v) -> domainsBuilder.put((VKey<DomainBase>) k, v));
|
||||||
}
|
}
|
||||||
return domainsBuilder.build();
|
return domainsBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||||
import static com.google.common.collect.Iterables.partition;
|
import static com.google.common.collect.Iterables.partition;
|
||||||
import static com.google.common.collect.Streams.stream;
|
import static com.google.common.collect.Streams.stream;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||||
|
|
||||||
import com.beust.jcommander.Parameter;
|
import com.beust.jcommander.Parameter;
|
||||||
import com.beust.jcommander.Parameters;
|
import com.beust.jcommander.Parameters;
|
||||||
|
@ -105,13 +105,17 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
|
||||||
}
|
}
|
||||||
|
|
||||||
tokensToSave =
|
tokensToSave =
|
||||||
ofy().load().keys(getTokenKeys()).values().stream()
|
transactIfJpaTm(
|
||||||
|
() ->
|
||||||
|
tm().load(getTokenKeys()).values().stream()
|
||||||
.collect(toImmutableMap(Function.identity(), this::updateToken))
|
.collect(toImmutableMap(Function.identity(), this::updateToken))
|
||||||
.entrySet()
|
.entrySet()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(entry -> !entry.getKey().equals(entry.getValue())) // only update changed tokens
|
.filter(
|
||||||
|
entry ->
|
||||||
|
!entry.getKey().equals(entry.getValue())) // only update changed tokens
|
||||||
.map(Map.Entry::getValue)
|
.map(Map.Entry::getValue)
|
||||||
.collect(toImmutableSet());
|
.collect(toImmutableSet()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -123,7 +127,7 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
|
||||||
protected String execute() {
|
protected String execute() {
|
||||||
long numUpdated =
|
long numUpdated =
|
||||||
stream(partition(tokensToSave, BATCH_SIZE))
|
stream(partition(tokensToSave, BATCH_SIZE))
|
||||||
.mapToLong(batch -> tm().transact(() -> saveBatch(batch)))
|
.mapToLong(batch -> tm().transact(() -> saveBatch(ImmutableList.copyOf(batch))))
|
||||||
.sum();
|
.sum();
|
||||||
return String.format("Updated %d tokens in total.", numUpdated);
|
return String.format("Updated %d tokens in total.", numUpdated);
|
||||||
}
|
}
|
||||||
|
@ -141,9 +145,9 @@ final class UpdateAllocationTokensCommand extends UpdateOrDeleteAllocationTokens
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private long saveBatch(List<AllocationToken> batch) {
|
private long saveBatch(ImmutableList<AllocationToken> batch) {
|
||||||
if (!dryRun) {
|
if (!dryRun) {
|
||||||
ofy().save().entities(batch);
|
tm().putAll(batch);
|
||||||
}
|
}
|
||||||
System.out.printf(
|
System.out.printf(
|
||||||
"%s tokens: %s\n",
|
"%s tokens: %s\n",
|
||||||
|
|
|
@ -15,13 +15,15 @@
|
||||||
package google.registry.tools;
|
package google.registry.tools;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||||
|
|
||||||
import com.beust.jcommander.Parameter;
|
import com.beust.jcommander.Parameter;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import google.registry.model.domain.token.AllocationToken;
|
import google.registry.model.domain.token.AllocationToken;
|
||||||
|
import google.registry.persistence.VKey;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** Shared base class for commands to update or delete allocation tokens. */
|
/** Shared base class for commands to update or delete allocation tokens. */
|
||||||
|
@ -47,19 +49,28 @@ abstract class UpdateOrDeleteAllocationTokensCommand extends ConfirmingCommand
|
||||||
description = "Do not actually update or delete the tokens; defaults to false")
|
description = "Do not actually update or delete the tokens; defaults to false")
|
||||||
protected boolean dryRun;
|
protected boolean dryRun;
|
||||||
|
|
||||||
protected ImmutableSet<Key<AllocationToken>> getTokenKeys() {
|
protected ImmutableSet<VKey<AllocationToken>> getTokenKeys() {
|
||||||
checkArgument(
|
checkArgument(
|
||||||
tokens == null ^ prefix == null,
|
tokens == null ^ prefix == null,
|
||||||
"Must provide one of --tokens or --prefix, not both / neither");
|
"Must provide one of --tokens or --prefix, not both / neither");
|
||||||
if (tokens != null) {
|
if (tokens != null) {
|
||||||
return tokens.stream()
|
ImmutableSet<VKey<AllocationToken>> keys =
|
||||||
.map(token -> Key.create(AllocationToken.class, token))
|
tokens.stream()
|
||||||
|
.map(token -> VKey.create(AllocationToken.class, token))
|
||||||
.collect(toImmutableSet());
|
.collect(toImmutableSet());
|
||||||
|
ImmutableSet<VKey<AllocationToken>> nonexistentKeys =
|
||||||
|
transactIfJpaTm(
|
||||||
|
() -> keys.stream().filter(key -> !tm().exists(key)).collect(toImmutableSet()));
|
||||||
|
checkState(nonexistentKeys.isEmpty(), "Tokens with keys %s did not exist.", nonexistentKeys);
|
||||||
|
return keys;
|
||||||
} else {
|
} else {
|
||||||
checkArgument(!prefix.isEmpty(), "Provided prefix should not be blank");
|
checkArgument(!prefix.isEmpty(), "Provided prefix should not be blank");
|
||||||
return ofy().load().type(AllocationToken.class).keys().list().stream()
|
return transactIfJpaTm(
|
||||||
.filter(key -> key.getName().startsWith(prefix))
|
() ->
|
||||||
.collect(toImmutableSet());
|
tm().loadAll(AllocationToken.class).stream()
|
||||||
|
.filter(token -> token.getToken().startsWith(prefix))
|
||||||
|
.map(AllocationToken::createVKey)
|
||||||
|
.collect(toImmutableSet()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,14 @@ package google.registry.model.domain.token;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static com.google.common.truth.Truth8.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.CANCELLED;
|
||||||
import static google.registry.model.domain.token.AllocationToken.TokenStatus.ENDED;
|
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.NOT_STARTED;
|
||||||
import static google.registry.model.domain.token.AllocationToken.TokenStatus.VALID;
|
import static google.registry.model.domain.token.AllocationToken.TokenStatus.VALID;
|
||||||
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
|
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.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||||
import static google.registry.testing.DatabaseHelper.createTld;
|
import static google.registry.testing.DatabaseHelper.createTld;
|
||||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||||
|
@ -40,12 +39,14 @@ import google.registry.model.domain.DomainBase;
|
||||||
import google.registry.model.domain.token.AllocationToken.TokenStatus;
|
import google.registry.model.domain.token.AllocationToken.TokenStatus;
|
||||||
import google.registry.model.domain.token.AllocationToken.TokenType;
|
import google.registry.model.domain.token.AllocationToken.TokenType;
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.testing.DualDatabaseTest;
|
||||||
|
import google.registry.testing.TestOfyAndSql;
|
||||||
|
import google.registry.testing.TestOfyOnly;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
/** Unit tests for {@link AllocationToken}. */
|
/** Unit tests for {@link AllocationToken}. */
|
||||||
|
@DualDatabaseTest
|
||||||
public class AllocationTokenTest extends EntityTestCase {
|
public class AllocationTokenTest extends EntityTestCase {
|
||||||
|
|
||||||
public AllocationTokenTest() {
|
public AllocationTokenTest() {
|
||||||
|
@ -57,7 +58,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
createTld("foo");
|
createTld("foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testPersistence() {
|
void testPersistence() {
|
||||||
AllocationToken unlimitedUseToken =
|
AllocationToken unlimitedUseToken =
|
||||||
persistResource(
|
persistResource(
|
||||||
|
@ -77,7 +78,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
.put(DateTime.now(UTC).plusWeeks(8), TokenStatus.ENDED)
|
.put(DateTime.now(UTC).plusWeeks(8), TokenStatus.ENDED)
|
||||||
.build())
|
.build())
|
||||||
.build());
|
.build());
|
||||||
assertThat(ofy().load().entity(unlimitedUseToken).now()).isEqualTo(unlimitedUseToken);
|
assertThat(transactIfJpaTm(() -> tm().load(unlimitedUseToken))).isEqualTo(unlimitedUseToken);
|
||||||
|
|
||||||
DomainBase domain = persistActiveDomain("example.foo");
|
DomainBase domain = persistActiveDomain("example.foo");
|
||||||
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1);
|
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1);
|
||||||
|
@ -90,32 +91,10 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
|
.setCreationTimeForTest(DateTime.parse("2010-11-12T05:00:00Z"))
|
||||||
.setTokenType(SINGLE_USE)
|
.setTokenType(SINGLE_USE)
|
||||||
.build());
|
.build());
|
||||||
assertThat(ofy().load().entity(singleUseToken).now()).isEqualTo(singleUseToken);
|
assertThat(transactIfJpaTm(() -> tm().load(singleUseToken))).isEqualTo(singleUseToken);
|
||||||
|
|
||||||
jpaTm()
|
|
||||||
.transact(
|
|
||||||
() -> {
|
|
||||||
jpaTm().insert(unlimitedUseToken);
|
|
||||||
jpaTm().insert(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
|
@TestOfyOnly
|
||||||
void testIndexing() throws Exception {
|
void testIndexing() throws Exception {
|
||||||
DomainBase domain = persistActiveDomain("blahdomain.foo");
|
DomainBase domain = persistActiveDomain("blahdomain.foo");
|
||||||
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1);
|
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1);
|
||||||
|
@ -133,7 +112,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
"domainName");
|
"domainName");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testCreationTime_autoPopulates() {
|
void testCreationTime_autoPopulates() {
|
||||||
AllocationToken tokenBeforePersisting =
|
AllocationToken tokenBeforePersisting =
|
||||||
new AllocationToken.Builder().setToken("abc123").setTokenType(SINGLE_USE).build();
|
new AllocationToken.Builder().setToken("abc123").setTokenType(SINGLE_USE).build();
|
||||||
|
@ -142,7 +121,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
assertThat(tokenAfterPersisting.getCreationTime()).hasValue(fakeClock.nowUtc());
|
assertThat(tokenAfterPersisting.getCreationTime()).hasValue(fakeClock.nowUtc());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSetCreationTime_cantCallMoreThanOnce() {
|
void testSetCreationTime_cantCallMoreThanOnce() {
|
||||||
AllocationToken.Builder builder =
|
AllocationToken.Builder builder =
|
||||||
new AllocationToken.Builder()
|
new AllocationToken.Builder()
|
||||||
|
@ -156,7 +135,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
assertThat(thrown).hasMessageThat().isEqualTo("Creation time can only be set once");
|
assertThat(thrown).hasMessageThat().isEqualTo("Creation time can only be set once");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSetToken_cantCallMoreThanOnce() {
|
void testSetToken_cantCallMoreThanOnce() {
|
||||||
AllocationToken.Builder builder = new AllocationToken.Builder().setToken("foobar");
|
AllocationToken.Builder builder = new AllocationToken.Builder().setToken("foobar");
|
||||||
IllegalStateException thrown =
|
IllegalStateException thrown =
|
||||||
|
@ -164,7 +143,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
assertThat(thrown).hasMessageThat().isEqualTo("Token can only be set once");
|
assertThat(thrown).hasMessageThat().isEqualTo("Token can only be set once");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSetTokenType_cantCallMoreThanOnce() {
|
void testSetTokenType_cantCallMoreThanOnce() {
|
||||||
AllocationToken.Builder builder =
|
AllocationToken.Builder builder =
|
||||||
new AllocationToken.Builder().setTokenType(TokenType.UNLIMITED_USE);
|
new AllocationToken.Builder().setTokenType(TokenType.UNLIMITED_USE);
|
||||||
|
@ -173,7 +152,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
assertThat(thrown).hasMessageThat().isEqualTo("Token type can only be set once");
|
assertThat(thrown).hasMessageThat().isEqualTo("Token type can only be set once");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testBuild_DomainNameWithLessThanTwoParts() {
|
void testBuild_DomainNameWithLessThanTwoParts() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -191,7 +170,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
assertThat(thrown).hasMessageThat().isEqualTo("Invalid domain name: example");
|
assertThat(thrown).hasMessageThat().isEqualTo("Invalid domain name: example");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testBuild_invalidTld() {
|
void testBuild_invalidTld() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -209,7 +188,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
assertThat(thrown).hasMessageThat().isEqualTo("Invalid domain name: example.nosuchtld");
|
assertThat(thrown).hasMessageThat().isEqualTo("Invalid domain name: example.nosuchtld");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testBuild_domainNameOnlyOnSingleUse() {
|
void testBuild_domainNameOnlyOnSingleUse() {
|
||||||
AllocationToken.Builder builder =
|
AllocationToken.Builder builder =
|
||||||
new AllocationToken.Builder()
|
new AllocationToken.Builder()
|
||||||
|
@ -222,7 +201,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
.isEqualTo("Domain name can only be specified for SINGLE_USE tokens");
|
.isEqualTo("Domain name can only be specified for SINGLE_USE tokens");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testBuild_redemptionHistoryEntryOnlyInSingleUse() {
|
void testBuild_redemptionHistoryEntryOnlyInSingleUse() {
|
||||||
DomainBase domain = persistActiveDomain("blahdomain.foo");
|
DomainBase domain = persistActiveDomain("blahdomain.foo");
|
||||||
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1);
|
Key<HistoryEntry> historyEntryKey = Key.create(Key.create(domain), HistoryEntry.class, 1);
|
||||||
|
@ -237,7 +216,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
.isEqualTo("Redemption history entry can only be specified for SINGLE_USE tokens");
|
.isEqualTo("Redemption history entry can only be specified for SINGLE_USE tokens");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSetTransitions_notStartOfTime() {
|
void testSetTransitions_notStartOfTime() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -255,7 +234,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
.isEqualTo("tokenStatusTransitions map must start at START_OF_TIME.");
|
.isEqualTo("tokenStatusTransitions map must start at START_OF_TIME.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSetTransitions_badInitialValue() {
|
void testSetTransitions_badInitialValue() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -272,14 +251,14 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
.isEqualTo("tokenStatusTransitions must start with NOT_STARTED");
|
.isEqualTo("tokenStatusTransitions must start with NOT_STARTED");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSetTransitions_invalidInitialTransitions() {
|
void testSetTransitions_invalidInitialTransitions() {
|
||||||
// NOT_STARTED can only go to VALID or CANCELLED
|
// NOT_STARTED can only go to VALID or CANCELLED
|
||||||
assertBadInitialTransition(NOT_STARTED);
|
assertBadInitialTransition(NOT_STARTED);
|
||||||
assertBadInitialTransition(ENDED);
|
assertBadInitialTransition(ENDED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSetTransitions_badTransitionsFromValid() {
|
void testSetTransitions_badTransitionsFromValid() {
|
||||||
// VALID can only go to ENDED or CANCELLED
|
// VALID can only go to ENDED or CANCELLED
|
||||||
assertBadTransition(
|
assertBadTransition(
|
||||||
|
@ -300,14 +279,14 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
NOT_STARTED);
|
NOT_STARTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSetTransitions_terminalTransitions() {
|
void testSetTransitions_terminalTransitions() {
|
||||||
// both ENDED and CANCELLED are terminal
|
// both ENDED and CANCELLED are terminal
|
||||||
assertTerminal(ENDED);
|
assertTerminal(ENDED);
|
||||||
assertTerminal(CANCELLED);
|
assertTerminal(CANCELLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSetDiscountFractionTooHigh() {
|
void testSetDiscountFractionTooHigh() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -318,7 +297,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
.isEqualTo("Discount fraction must be between 0 and 1 inclusive");
|
.isEqualTo("Discount fraction must be between 0 and 1 inclusive");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSetDiscountFractionTooLow() {
|
void testSetDiscountFractionTooLow() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -329,7 +308,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
.isEqualTo("Discount fraction must be between 0 and 1 inclusive");
|
.isEqualTo("Discount fraction must be between 0 and 1 inclusive");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSetDiscountYearsTooHigh() {
|
void testSetDiscountYearsTooHigh() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -340,7 +319,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
.isEqualTo("Discount years must be between 1 and 10 inclusive");
|
.isEqualTo("Discount years must be between 1 and 10 inclusive");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSetDiscountYearsTooLow() {
|
void testSetDiscountYearsTooLow() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -351,7 +330,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
.isEqualTo("Discount years must be between 1 and 10 inclusive");
|
.isEqualTo("Discount years must be between 1 and 10 inclusive");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testBuild_noTokenType() {
|
void testBuild_noTokenType() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -360,7 +339,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
assertThat(thrown).hasMessageThat().isEqualTo("Token type must be specified");
|
assertThat(thrown).hasMessageThat().isEqualTo("Token type must be specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testBuild_noToken() {
|
void testBuild_noToken() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -369,7 +348,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
assertThat(thrown).hasMessageThat().isEqualTo("Token must not be null or empty");
|
assertThat(thrown).hasMessageThat().isEqualTo("Token must not be null or empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testBuild_emptyToken() {
|
void testBuild_emptyToken() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -378,7 +357,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
assertThat(thrown).hasMessageThat().isEqualTo("Token must not be blank");
|
assertThat(thrown).hasMessageThat().isEqualTo("Token must not be blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testBuild_discountPremiumsRequiresDiscountFraction() {
|
void testBuild_discountPremiumsRequiresDiscountFraction() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -394,7 +373,7 @@ public class AllocationTokenTest extends EntityTestCase {
|
||||||
.isEqualTo("Discount premiums can only be specified along with a discount fraction");
|
.isEqualTo("Discount premiums can only be specified along with a discount fraction");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testBuild_discountYearsRequiresDiscountFraction() {
|
void testBuild_discountYearsRequiresDiscountFraction() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
|
|
@ -18,6 +18,8 @@ import static com.google.common.collect.Iterables.concat;
|
||||||
import static com.google.common.collect.Iterables.toArray;
|
import static com.google.common.collect.Iterables.toArray;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.joda.time.DateTimeZone.UTC;
|
import static org.joda.time.DateTimeZone.UTC;
|
||||||
|
|
||||||
|
@ -185,7 +187,7 @@ public abstract class CommandTestCase<C extends Command> {
|
||||||
|
|
||||||
/** Reloads the given resource from Datastore. */
|
/** Reloads the given resource from Datastore. */
|
||||||
<T> T reloadResource(T resource) {
|
<T> T reloadResource(T resource) {
|
||||||
return ofy().load().entity(resource).now();
|
return transactIfJpaTm(() -> tm().load(resource));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns count of all poll messages in Datastore. */
|
/** Returns count of all poll messages in Datastore. */
|
||||||
|
|
|
@ -16,23 +16,28 @@ package google.registry.tools;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
|
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||||
import static google.registry.testing.DatabaseHelper.createTlds;
|
import static google.registry.testing.DatabaseHelper.createTlds;
|
||||||
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
|
||||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.googlecode.objectify.Key;
|
import com.googlecode.objectify.Key;
|
||||||
import google.registry.model.domain.DomainBase;
|
import google.registry.model.domain.DomainBase;
|
||||||
import google.registry.model.domain.token.AllocationToken;
|
import google.registry.model.domain.token.AllocationToken;
|
||||||
import google.registry.model.domain.token.AllocationToken.TokenType;
|
import google.registry.model.domain.token.AllocationToken.TokenType;
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
import java.util.Collection;
|
import google.registry.testing.DualDatabaseTest;
|
||||||
|
import google.registry.testing.TestOfyAndSql;
|
||||||
|
import java.util.Arrays;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
/** Unit tests for {@link DeleteAllocationTokensCommand}. */
|
/** Unit tests for {@link DeleteAllocationTokensCommand}. */
|
||||||
|
@DualDatabaseTest
|
||||||
class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocationTokensCommand> {
|
class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocationTokensCommand> {
|
||||||
|
|
||||||
private AllocationToken preRed1;
|
private AllocationToken preRed1;
|
||||||
|
@ -53,38 +58,38 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
|
||||||
othrNot = persistToken("asdgfho7HASDS", null, false);
|
othrNot = persistToken("asdgfho7HASDS", null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void test_deleteOnlyUnredeemedTokensWithPrefix() throws Exception {
|
void test_deleteOnlyUnredeemedTokensWithPrefix() throws Exception {
|
||||||
runCommandForced("--prefix", "prefix");
|
runCommandForced("--prefix", "prefix");
|
||||||
assertThat(reloadTokens(preNot1, preNot2)).isEmpty();
|
assertNonexistent(preNot1, preNot2);
|
||||||
assertThat(reloadTokens(preRed1, preRed2, othrRed, othrNot))
|
assertThat(reloadTokens(preRed1, preRed2, othrRed, othrNot))
|
||||||
.containsExactly(preRed1, preRed2, othrRed, othrNot);
|
.containsExactly(preRed1, preRed2, othrRed, othrNot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void test_deleteSingleAllocationToken() throws Exception {
|
void test_deleteSingleAllocationToken() throws Exception {
|
||||||
runCommandForced("--prefix", "asdgfho7HASDS");
|
runCommandForced("--prefix", "asdgfho7HASDS");
|
||||||
assertThat(reloadTokens(othrNot)).isEmpty();
|
assertNonexistent(othrNot);
|
||||||
assertThat(reloadTokens(preRed1, preRed2, preNot1, preNot2, othrRed))
|
assertThat(reloadTokens(preRed1, preRed2, preNot1, preNot2, othrRed))
|
||||||
.containsExactly(preRed1, preRed2, preNot1, preNot2, othrRed);
|
.containsExactly(preRed1, preRed2, preNot1, preNot2, othrRed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void test_deleteParticularTokens() throws Exception {
|
void test_deleteParticularTokens() throws Exception {
|
||||||
runCommandForced("--tokens", "prefix2978204,asdgfho7HASDS");
|
runCommandForced("--tokens", "prefix2978204,asdgfho7HASDS");
|
||||||
assertThat(reloadTokens(preNot1, othrNot)).isEmpty();
|
assertNonexistent(preNot1, othrNot);
|
||||||
assertThat(reloadTokens(preRed1, preRed2, preNot2, othrRed))
|
assertThat(reloadTokens(preRed1, preRed2, preNot2, othrRed))
|
||||||
.containsExactly(preRed1, preRed2, preNot2, othrRed);
|
.containsExactly(preRed1, preRed2, preNot2, othrRed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void test_deleteTokensWithNonExistentPrefix_doesNothing() throws Exception {
|
void test_deleteTokensWithNonExistentPrefix_doesNothing() throws Exception {
|
||||||
runCommandForced("--prefix", "nonexistent");
|
runCommandForced("--prefix", "nonexistent");
|
||||||
assertThat(reloadTokens(preRed1, preRed2, preNot1, preNot2, othrRed, othrNot))
|
assertThat(reloadTokens(preRed1, preRed2, preNot1, preNot2, othrRed, othrNot))
|
||||||
.containsExactly(preRed1, preRed2, preNot1, preNot2, othrRed, othrNot);
|
.containsExactly(preRed1, preRed2, preNot1, preNot2, othrRed, othrNot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void test_dryRun_deletesNothing() throws Exception {
|
void test_dryRun_deletesNothing() throws Exception {
|
||||||
runCommandForced("--prefix", "prefix", "--dry_run");
|
runCommandForced("--prefix", "prefix", "--dry_run");
|
||||||
assertThat(reloadTokens(preRed1, preRed2, preNot1, preNot2, othrRed, othrNot))
|
assertThat(reloadTokens(preRed1, preRed2, preNot1, preNot2, othrRed, othrNot))
|
||||||
|
@ -92,27 +97,27 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
|
||||||
assertInStdout("Would delete tokens: prefix2978204, prefix8ZZZhs8");
|
assertInStdout("Would delete tokens: prefix2978204, prefix8ZZZhs8");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void test_defaultOptions_doesntDeletePerDomainTokens() throws Exception {
|
void test_defaultOptions_doesntDeletePerDomainTokens() throws Exception {
|
||||||
AllocationToken preDom1 = persistToken("prefixasdfg897as", "foo.bar", false);
|
AllocationToken preDom1 = persistToken("prefixasdfg897as", "foo.bar", false);
|
||||||
AllocationToken preDom2 = persistToken("prefix98HAZXadbn", "foo.bar", true);
|
AllocationToken preDom2 = persistToken("prefix98HAZXadbn", "foo.bar", true);
|
||||||
runCommandForced("--prefix", "prefix");
|
runCommandForced("--prefix", "prefix");
|
||||||
assertThat(reloadTokens(preNot1, preNot2)).isEmpty();
|
assertNonexistent(preNot1, preNot2);
|
||||||
assertThat(reloadTokens(preRed1, preRed2, preDom1, preDom2, othrRed, othrNot))
|
assertThat(reloadTokens(preRed1, preRed2, preDom1, preDom2, othrRed, othrNot))
|
||||||
.containsExactly(preRed1, preRed2, preDom1, preDom2, othrRed, othrNot);
|
.containsExactly(preRed1, preRed2, preDom1, preDom2, othrRed, othrNot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void test_withDomains_doesDeletePerDomainTokens() throws Exception {
|
void test_withDomains_doesDeletePerDomainTokens() throws Exception {
|
||||||
AllocationToken preDom1 = persistToken("prefixasdfg897as", "foo.bar", false);
|
AllocationToken preDom1 = persistToken("prefixasdfg897as", "foo.bar", false);
|
||||||
AllocationToken preDom2 = persistToken("prefix98HAZXadbn", "foo.bar", true);
|
AllocationToken preDom2 = persistToken("prefix98HAZXadbn", "foo.bar", true);
|
||||||
runCommandForced("--prefix", "prefix", "--with_domains");
|
runCommandForced("--prefix", "prefix", "--with_domains");
|
||||||
assertThat(reloadTokens(preNot1, preNot2, preDom1)).isEmpty();
|
assertNonexistent(preNot1, preNot2, preDom1);
|
||||||
assertThat(reloadTokens(preRed1, preRed2, preDom2, othrRed, othrNot))
|
assertThat(reloadTokens(preRed1, preRed2, preDom2, othrRed, othrNot))
|
||||||
.containsExactly(preRed1, preRed2, preDom2, othrRed, othrNot);
|
.containsExactly(preRed1, preRed2, preDom2, othrRed, othrNot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSkipUnlimitedUseTokens() throws Exception {
|
void testSkipUnlimitedUseTokens() throws Exception {
|
||||||
AllocationToken unlimitedUseToken =
|
AllocationToken unlimitedUseToken =
|
||||||
persistResource(
|
persistResource(
|
||||||
|
@ -124,17 +129,18 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
|
||||||
assertThat(reloadTokens(unlimitedUseToken)).containsExactly(unlimitedUseToken);
|
assertThat(reloadTokens(unlimitedUseToken)).containsExactly(unlimitedUseToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void test_batching() throws Exception {
|
void test_batching() throws Exception {
|
||||||
for (int i = 0; i < 50; i++) {
|
for (int i = 0; i < 50; i++) {
|
||||||
persistToken(String.format("batch%2d", i), null, i % 2 == 0);
|
persistToken(String.format("batch%2d", i), null, i % 2 == 0);
|
||||||
}
|
}
|
||||||
assertThat(ofy().load().type(AllocationToken.class).count()).isEqualTo(56);
|
assertThat(transactIfJpaTm(() -> tm().loadAll(AllocationToken.class).size())).isEqualTo(56);
|
||||||
runCommandForced("--prefix", "batch");
|
runCommandForced("--prefix", "batch");
|
||||||
assertThat(ofy().load().type(AllocationToken.class).count()).isEqualTo(56 - 25);
|
assertThat(transactIfJpaTm(() -> tm().loadAll(AllocationToken.class).size()))
|
||||||
|
.isEqualTo(56 - 25);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void test_prefixIsRequired() {
|
void test_prefixIsRequired() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(IllegalArgumentException.class, this::runCommandForced);
|
assertThrows(IllegalArgumentException.class, this::runCommandForced);
|
||||||
|
@ -143,7 +149,7 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
|
||||||
.isEqualTo("Must provide one of --tokens or --prefix, not both / neither");
|
.isEqualTo("Must provide one of --tokens or --prefix, not both / neither");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_bothPrefixAndTokens() {
|
void testFailure_bothPrefixAndTokens() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -154,7 +160,7 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
|
||||||
.isEqualTo("Must provide one of --tokens or --prefix, not both / neither");
|
.isEqualTo("Must provide one of --tokens or --prefix, not both / neither");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_emptyPrefix() {
|
void testFailure_emptyPrefix() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(IllegalArgumentException.class, () -> runCommandForced("--prefix", ""));
|
assertThrows(IllegalArgumentException.class, () -> runCommandForced("--prefix", ""));
|
||||||
|
@ -177,7 +183,11 @@ class DeleteAllocationTokensCommandTest extends CommandTestCase<DeleteAllocation
|
||||||
return persistResource(builder.build());
|
return persistResource(builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Collection<AllocationToken> reloadTokens(AllocationToken... tokens) {
|
private static ImmutableList<AllocationToken> reloadTokens(AllocationToken... tokens) {
|
||||||
return ofy().load().entities(tokens).values();
|
return transactIfJpaTm(() -> tm().loadAll(ImmutableSet.copyOf(tokens)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertNonexistent(AllocationToken... tokens) {
|
||||||
|
Arrays.stream(tokens).forEach(t -> transactIfJpaTm(() -> assertThat(tm().exists(t)).isFalse()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@ package google.registry.tools;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.model.domain.token.AllocationToken.TokenType.SINGLE_USE;
|
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.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
|
||||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
import static google.registry.persistence.transaction.TransactionManagerUtil.transactIfJpaTm;
|
||||||
import static google.registry.testing.DatabaseHelper.assertAllocationTokens;
|
import static google.registry.testing.DatabaseHelper.assertAllocationTokens;
|
||||||
import static google.registry.testing.DatabaseHelper.createTld;
|
import static google.registry.testing.DatabaseHelper.createTld;
|
||||||
import static google.registry.testing.DatabaseHelper.persistResource;
|
import static google.registry.testing.DatabaseHelper.persistResource;
|
||||||
|
@ -43,8 +44,10 @@ import google.registry.model.reporting.HistoryEntry;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.testing.DeterministicStringGenerator;
|
import google.registry.testing.DeterministicStringGenerator;
|
||||||
import google.registry.testing.DeterministicStringGenerator.Rule;
|
import google.registry.testing.DeterministicStringGenerator.Rule;
|
||||||
|
import google.registry.testing.DualDatabaseTest;
|
||||||
import google.registry.testing.FakeClock;
|
import google.registry.testing.FakeClock;
|
||||||
import google.registry.testing.FakeSleeper;
|
import google.registry.testing.FakeSleeper;
|
||||||
|
import google.registry.testing.TestOfyAndSql;
|
||||||
import google.registry.util.Retrier;
|
import google.registry.util.Retrier;
|
||||||
import google.registry.util.StringGenerator.Alphabets;
|
import google.registry.util.StringGenerator.Alphabets;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -52,10 +55,10 @@ import java.util.Collection;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.mockito.ArgumentMatchers;
|
import org.mockito.ArgumentMatchers;
|
||||||
|
|
||||||
/** Unit tests for {@link GenerateAllocationTokensCommand}. */
|
/** Unit tests for {@link GenerateAllocationTokensCommand}. */
|
||||||
|
@DualDatabaseTest
|
||||||
class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAllocationTokensCommand> {
|
class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAllocationTokensCommand> {
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
|
@ -65,14 +68,14 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
new Retrier(new FakeSleeper(new FakeClock(DateTime.parse("2000-01-01TZ"))), 3);
|
new Retrier(new FakeSleeper(new FakeClock(DateTime.parse("2000-01-01TZ"))), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_oneToken() throws Exception {
|
void testSuccess_oneToken() throws Exception {
|
||||||
runCommand("--prefix", "blah", "--number", "1", "--length", "9");
|
runCommand("--prefix", "blah", "--number", "1", "--length", "9");
|
||||||
assertAllocationTokens(createToken("blah123456789", null, null));
|
assertAllocationTokens(createToken("blah123456789", null, null));
|
||||||
assertInStdout("blah123456789");
|
assertInStdout("blah123456789");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_threeTokens() throws Exception {
|
void testSuccess_threeTokens() throws Exception {
|
||||||
runCommand("--prefix", "foo", "--number", "3", "--length", "10");
|
runCommand("--prefix", "foo", "--number", "3", "--length", "10");
|
||||||
assertAllocationTokens(
|
assertAllocationTokens(
|
||||||
|
@ -82,14 +85,14 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
assertInStdout("foo123456789A\nfooBCDEFGHJKL\nfooMNPQRSTUVW");
|
assertInStdout("foo123456789A\nfooBCDEFGHJKL\nfooMNPQRSTUVW");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_defaults() throws Exception {
|
void testSuccess_defaults() throws Exception {
|
||||||
runCommand("--number", "1");
|
runCommand("--number", "1");
|
||||||
assertAllocationTokens(createToken("123456789ABCDEFG", null, null));
|
assertAllocationTokens(createToken("123456789ABCDEFG", null, null));
|
||||||
assertInStdout("123456789ABCDEFG");
|
assertInStdout("123456789ABCDEFG");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_retry() throws Exception {
|
void testSuccess_retry() throws Exception {
|
||||||
command = spy(command);
|
command = spy(command);
|
||||||
RemoteApiException fakeException = new RemoteApiException("foo", "foo", "foo", new Exception());
|
RemoteApiException fakeException = new RemoteApiException("foo", "foo", "foo", new Exception());
|
||||||
|
@ -104,7 +107,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
verify(command, times(3)).saveTokens(ArgumentMatchers.any());
|
verify(command, times(3)).saveTokens(ArgumentMatchers.any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_tokenCollision() throws Exception {
|
void testSuccess_tokenCollision() throws Exception {
|
||||||
AllocationToken existingToken =
|
AllocationToken existingToken =
|
||||||
persistResource(
|
persistResource(
|
||||||
|
@ -117,24 +120,24 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
assertInStdout("DEADBEEFDEFGHJKLMNPQ");
|
assertInStdout("DEADBEEFDEFGHJKLMNPQ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_dryRun_outputsButDoesntSave() throws Exception {
|
void testSuccess_dryRun_outputsButDoesntSave() throws Exception {
|
||||||
runCommand("--prefix", "foo", "--number", "2", "--length", "10", "--dry_run");
|
runCommand("--prefix", "foo", "--number", "2", "--length", "10", "--dry_run");
|
||||||
assertAllocationTokens();
|
assertAllocationTokens();
|
||||||
assertInStdout("foo123456789A\nfooBCDEFGHJKL");
|
assertInStdout("foo123456789A\nfooBCDEFGHJKL");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_largeNumberOfTokens() throws Exception {
|
void testSuccess_largeNumberOfTokens() throws Exception {
|
||||||
command.stringGenerator =
|
command.stringGenerator =
|
||||||
new DeterministicStringGenerator(Alphabets.BASE_58, Rule.PREPEND_COUNTER);
|
new DeterministicStringGenerator(Alphabets.BASE_58, Rule.PREPEND_COUNTER);
|
||||||
runCommand("--prefix", "ooo", "--number", "100", "--length", "16");
|
runCommand("--prefix", "ooo", "--number", "100", "--length", "16");
|
||||||
// The deterministic string generator makes it too much hassle to assert about each token, so
|
// The deterministic string generator makes it too much hassle to assert about each token, so
|
||||||
// just assert total number.
|
// just assert total number.
|
||||||
assertThat(ofy().load().type(AllocationToken.class).count()).isEqualTo(100);
|
assertThat(transactIfJpaTm(() -> tm().loadAll(AllocationToken.class).size())).isEqualTo(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_domainNames() throws Exception {
|
void testSuccess_domainNames() throws Exception {
|
||||||
createTld("tld");
|
createTld("tld");
|
||||||
File domainNamesFile = tmpDir.resolve("domain_names.txt").toFile();
|
File domainNamesFile = tmpDir.resolve("domain_names.txt").toFile();
|
||||||
|
@ -148,7 +151,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
"foo1.tld,123456789ABCDEFG\nboo2.tld,HJKLMNPQRSTUVWXY\nbaz9.tld,Zabcdefghijkmnop");
|
"foo1.tld,123456789ABCDEFG\nboo2.tld,HJKLMNPQRSTUVWXY\nbaz9.tld,Zabcdefghijkmnop");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_promotionToken() throws Exception {
|
void testSuccess_promotionToken() throws Exception {
|
||||||
DateTime promoStart = DateTime.now(UTC);
|
DateTime promoStart = DateTime.now(UTC);
|
||||||
DateTime promoEnd = promoStart.plusMonths(1);
|
DateTime promoEnd = promoStart.plusMonths(1);
|
||||||
|
@ -182,24 +185,24 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_specifyTokens() throws Exception {
|
void testSuccess_specifyTokens() throws Exception {
|
||||||
runCommand("--tokens", "foobar,foobaz");
|
runCommand("--tokens", "foobar,foobaz");
|
||||||
assertAllocationTokens(createToken("foobar", null, null), createToken("foobaz", null, null));
|
assertAllocationTokens(createToken("foobar", null, null), createToken("foobaz", null, null));
|
||||||
assertInStdout("foobar", "foobaz");
|
assertInStdout("foobar", "foobaz");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testSuccess_specifyManyTokens() throws Exception {
|
void testSuccess_specifyManyTokens() throws Exception {
|
||||||
command.stringGenerator =
|
command.stringGenerator =
|
||||||
new DeterministicStringGenerator(Alphabets.BASE_58, Rule.PREPEND_COUNTER);
|
new DeterministicStringGenerator(Alphabets.BASE_58, Rule.PREPEND_COUNTER);
|
||||||
Collection<String> sampleTokens = command.stringGenerator.createStrings(13, 100);
|
Collection<String> sampleTokens = command.stringGenerator.createStrings(13, 100);
|
||||||
runCommand("--tokens", Joiner.on(",").join(sampleTokens));
|
runCommand("--tokens", Joiner.on(",").join(sampleTokens));
|
||||||
assertInStdout(Iterables.toArray(sampleTokens, String.class));
|
assertInStdout(Iterables.toArray(sampleTokens, String.class));
|
||||||
assertThat(ofy().load().type(AllocationToken.class).count()).isEqualTo(100);
|
assertThat(transactIfJpaTm(() -> tm().loadAll(AllocationToken.class).size())).isEqualTo(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_mustSpecifyNumberOfTokensOrDomainsFile() {
|
void testFailure_mustSpecifyNumberOfTokensOrDomainsFile() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(IllegalArgumentException.class, () -> runCommand("--prefix", "FEET"));
|
assertThrows(IllegalArgumentException.class, () -> runCommand("--prefix", "FEET"));
|
||||||
|
@ -208,7 +211,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
.isEqualTo("Must specify exactly one of '--number', '--domain_names_file', and '--tokens'");
|
.isEqualTo("Must specify exactly one of '--number', '--domain_names_file', and '--tokens'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_mustNotSpecifyBothNumberOfTokensAndDomainsFile() {
|
void testFailure_mustNotSpecifyBothNumberOfTokensAndDomainsFile() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -223,7 +226,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
.isEqualTo("Must specify exactly one of '--number', '--domain_names_file', and '--tokens'");
|
.isEqualTo("Must specify exactly one of '--number', '--domain_names_file', and '--tokens'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_mustNotSpecifyBothNumberOfTokensAndTokenStrings() {
|
void testFailure_mustNotSpecifyBothNumberOfTokensAndTokenStrings() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -238,7 +241,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
.isEqualTo("Must specify exactly one of '--number', '--domain_names_file', and '--tokens'");
|
.isEqualTo("Must specify exactly one of '--number', '--domain_names_file', and '--tokens'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_mustNotSpecifyBothTokenStringsAndDomainsFile() {
|
void testFailure_mustNotSpecifyBothTokenStringsAndDomainsFile() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -253,7 +256,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
.isEqualTo("Must specify exactly one of '--number', '--domain_names_file', and '--tokens'");
|
.isEqualTo("Must specify exactly one of '--number', '--domain_names_file', and '--tokens'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_specifiesAlreadyExistingToken() throws Exception {
|
void testFailure_specifiesAlreadyExistingToken() throws Exception {
|
||||||
runCommand("--tokens", "foobar");
|
runCommand("--tokens", "foobar");
|
||||||
beforeEachCommandTestCase(); // reset the command variables
|
beforeEachCommandTestCase(); // reset the command variables
|
||||||
|
@ -264,7 +267,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
.isEqualTo("Cannot create specified tokens; the following tokens already exist: [foobar]");
|
.isEqualTo("Cannot create specified tokens; the following tokens already exist: [foobar]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_invalidTokenType() {
|
void testFailure_invalidTokenType() {
|
||||||
ParameterException thrown =
|
ParameterException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -275,7 +278,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
.isEqualTo("Invalid value for -t parameter. Allowed values:[SINGLE_USE, UNLIMITED_USE]");
|
.isEqualTo("Invalid value for -t parameter. Allowed values:[SINGLE_USE, UNLIMITED_USE]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_invalidTokenStatusTransition() {
|
void testFailure_invalidTokenStatusTransition() {
|
||||||
assertThat(
|
assertThat(
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -290,7 +293,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
.isInstanceOf(IllegalArgumentException.class);
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_lengthOfZero() {
|
void testFailure_lengthOfZero() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -302,7 +305,7 @@ class GenerateAllocationTokensCommandTest extends CommandTestCase<GenerateAlloca
|
||||||
"Token length should not be 0. To generate exact tokens, use the --tokens parameter.");
|
"Token length should not be 0. To generate exact tokens, use the --tokens parameter.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_unlimitedUseMustHaveTransitions() {
|
void testFailure_unlimitedUseMustHaveTransitions() {
|
||||||
assertThat(
|
assertThat(
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
|
|
@ -30,12 +30,14 @@ import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.ImmutableSortedMap;
|
import com.google.common.collect.ImmutableSortedMap;
|
||||||
import google.registry.model.domain.token.AllocationToken;
|
import google.registry.model.domain.token.AllocationToken;
|
||||||
import google.registry.model.domain.token.AllocationToken.TokenStatus;
|
import google.registry.model.domain.token.AllocationToken.TokenStatus;
|
||||||
|
import google.registry.testing.DualDatabaseTest;
|
||||||
|
import google.registry.testing.TestOfyAndSql;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
|
@DualDatabaseTest
|
||||||
class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocationTokensCommand> {
|
class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocationTokensCommand> {
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testUpdateTlds_setTlds() throws Exception {
|
void testUpdateTlds_setTlds() throws Exception {
|
||||||
AllocationToken token =
|
AllocationToken token =
|
||||||
persistResource(builderWithPromo().setAllowedTlds(ImmutableSet.of("toRemove")).build());
|
persistResource(builderWithPromo().setAllowedTlds(ImmutableSet.of("toRemove")).build());
|
||||||
|
@ -43,7 +45,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
|
||||||
assertThat(reloadResource(token).getAllowedTlds()).containsExactly("tld", "example");
|
assertThat(reloadResource(token).getAllowedTlds()).containsExactly("tld", "example");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testUpdateTlds_clearTlds() throws Exception {
|
void testUpdateTlds_clearTlds() throws Exception {
|
||||||
AllocationToken token =
|
AllocationToken token =
|
||||||
persistResource(builderWithPromo().setAllowedTlds(ImmutableSet.of("toRemove")).build());
|
persistResource(builderWithPromo().setAllowedTlds(ImmutableSet.of("toRemove")).build());
|
||||||
|
@ -51,7 +53,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
|
||||||
assertThat(reloadResource(token).getAllowedTlds()).isEmpty();
|
assertThat(reloadResource(token).getAllowedTlds()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testUpdateClientIds_setClientIds() throws Exception {
|
void testUpdateClientIds_setClientIds() throws Exception {
|
||||||
AllocationToken token =
|
AllocationToken token =
|
||||||
persistResource(
|
persistResource(
|
||||||
|
@ -61,7 +63,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
|
||||||
.containsExactly("clientone", "clienttwo");
|
.containsExactly("clientone", "clienttwo");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testUpdateClientIds_clearClientIds() throws Exception {
|
void testUpdateClientIds_clearClientIds() throws Exception {
|
||||||
AllocationToken token =
|
AllocationToken token =
|
||||||
persistResource(
|
persistResource(
|
||||||
|
@ -70,14 +72,14 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
|
||||||
assertThat(reloadResource(token).getAllowedRegistrarIds()).isEmpty();
|
assertThat(reloadResource(token).getAllowedRegistrarIds()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testUpdateDiscountFraction() throws Exception {
|
void testUpdateDiscountFraction() throws Exception {
|
||||||
AllocationToken token = persistResource(builderWithPromo().setDiscountFraction(0.5).build());
|
AllocationToken token = persistResource(builderWithPromo().setDiscountFraction(0.5).build());
|
||||||
runCommandForced("--prefix", "token", "--discount_fraction", "0.15");
|
runCommandForced("--prefix", "token", "--discount_fraction", "0.15");
|
||||||
assertThat(reloadResource(token).getDiscountFraction()).isEqualTo(0.15);
|
assertThat(reloadResource(token).getDiscountFraction()).isEqualTo(0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testUpdateDiscountPremiums() throws Exception {
|
void testUpdateDiscountPremiums() throws Exception {
|
||||||
AllocationToken token =
|
AllocationToken token =
|
||||||
persistResource(
|
persistResource(
|
||||||
|
@ -88,14 +90,14 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
|
||||||
assertThat(reloadResource(token).shouldDiscountPremiums()).isFalse();
|
assertThat(reloadResource(token).shouldDiscountPremiums()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testUpdateDiscountYears() throws Exception {
|
void testUpdateDiscountYears() throws Exception {
|
||||||
AllocationToken token = persistResource(builderWithPromo().setDiscountFraction(0.5).build());
|
AllocationToken token = persistResource(builderWithPromo().setDiscountFraction(0.5).build());
|
||||||
runCommandForced("--prefix", "token", "--discount_years", "4");
|
runCommandForced("--prefix", "token", "--discount_years", "4");
|
||||||
assertThat(reloadResource(token).getDiscountYears()).isEqualTo(4);
|
assertThat(reloadResource(token).getDiscountYears()).isEqualTo(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testUpdateStatusTransitions() throws Exception {
|
void testUpdateStatusTransitions() throws Exception {
|
||||||
DateTime now = DateTime.now(UTC);
|
DateTime now = DateTime.now(UTC);
|
||||||
AllocationToken token = persistResource(builderWithPromo().build());
|
AllocationToken token = persistResource(builderWithPromo().build());
|
||||||
|
@ -110,7 +112,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
|
||||||
.containsExactly(START_OF_TIME, NOT_STARTED, now.minusDays(1), VALID, now, CANCELLED);
|
.containsExactly(START_OF_TIME, NOT_STARTED, now.minusDays(1), VALID, now, CANCELLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testUpdateStatusTransitions_badTransitions() {
|
void testUpdateStatusTransitions_badTransitions() {
|
||||||
DateTime now = DateTime.now(UTC);
|
DateTime now = DateTime.now(UTC);
|
||||||
persistResource(builderWithPromo().build());
|
persistResource(builderWithPromo().build());
|
||||||
|
@ -130,7 +132,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
|
||||||
.isEqualTo("tokenStatusTransitions map cannot transition from NOT_STARTED to ENDED.");
|
.isEqualTo("tokenStatusTransitions map cannot transition from NOT_STARTED to ENDED.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testUpdate_onlyWithPrefix() throws Exception {
|
void testUpdate_onlyWithPrefix() throws Exception {
|
||||||
AllocationToken token =
|
AllocationToken token =
|
||||||
persistResource(builderWithPromo().setAllowedTlds(ImmutableSet.of("tld")).build());
|
persistResource(builderWithPromo().setAllowedTlds(ImmutableSet.of("tld")).build());
|
||||||
|
@ -146,7 +148,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
|
||||||
assertThat(reloadResource(otherToken).getAllowedTlds()).isEmpty();
|
assertThat(reloadResource(otherToken).getAllowedTlds()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testUpdate_onlyTokensProvided() throws Exception {
|
void testUpdate_onlyTokensProvided() throws Exception {
|
||||||
AllocationToken firstToken =
|
AllocationToken firstToken =
|
||||||
persistResource(builderWithPromo().setAllowedTlds(ImmutableSet.of("tld")).build());
|
persistResource(builderWithPromo().setAllowedTlds(ImmutableSet.of("tld")).build());
|
||||||
|
@ -170,7 +172,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
|
||||||
assertThat(reloadResource(thirdToken).getAllowedTlds()).isEmpty();
|
assertThat(reloadResource(thirdToken).getAllowedTlds()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testDoNothing() throws Exception {
|
void testDoNothing() throws Exception {
|
||||||
AllocationToken token =
|
AllocationToken token =
|
||||||
persistResource(
|
persistResource(
|
||||||
|
@ -186,7 +188,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
|
||||||
assertThat(reloaded.getDiscountFraction()).isEqualTo(token.getDiscountFraction());
|
assertThat(reloaded.getDiscountFraction()).isEqualTo(token.getDiscountFraction());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_bothTokensAndPrefix() {
|
void testFailure_bothTokensAndPrefix() {
|
||||||
assertThat(
|
assertThat(
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -196,7 +198,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
|
||||||
.isEqualTo("Must provide one of --tokens or --prefix, not both / neither");
|
.isEqualTo("Must provide one of --tokens or --prefix, not both / neither");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_neitherTokensNorPrefix() {
|
void testFailure_neitherTokensNorPrefix() {
|
||||||
assertThat(
|
assertThat(
|
||||||
assertThrows(
|
assertThrows(
|
||||||
|
@ -205,7 +207,7 @@ class UpdateAllocationTokensCommandTest extends CommandTestCase<UpdateAllocation
|
||||||
.isEqualTo("Must provide one of --tokens or --prefix, not both / neither");
|
.isEqualTo("Must provide one of --tokens or --prefix, not both / neither");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@TestOfyAndSql
|
||||||
void testFailure_emptyPrefix() {
|
void testFailure_emptyPrefix() {
|
||||||
IllegalArgumentException thrown =
|
IllegalArgumentException thrown =
|
||||||
assertThrows(IllegalArgumentException.class, () -> runCommandForced("--prefix", ""));
|
assertThrows(IllegalArgumentException.class, () -> runCommandForced("--prefix", ""));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue