Use new renew cost calculation in handleFeeRequest() (#1694)

* Resolve conflict

* Fix setup for existing test cases in info and check flow

* Revise info flow test cases

* Fix lint

* Merge branch 'master' into handlefeerequest-renew

* Address code review comments myself

* Merge branch 'master' into handlefeerequest-renew

* Get test passing

* Add check flow tests

* Format, consolidate test helpers

* Don't unnecessarily specify XML name
This commit is contained in:
Ben McIlwain 2022-07-07 17:28:45 -04:00 committed by GitHub
parent d5c65bf8d7
commit bda8961ea7
8 changed files with 349 additions and 41 deletions

View file

@ -17,6 +17,7 @@ package google.registry.flows.domain;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.flows.FlowUtils.validateRegistrarIsLoggedIn;
import static google.registry.flows.ResourceFlowUtils.verifyTargetIdCount;
import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
@ -31,6 +32,7 @@ import static google.registry.flows.domain.DomainFlowUtils.validateDomainNameWit
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPredelegation;
import static google.registry.model.tld.Registry.TldState.START_DATE_SUNRISE;
import static google.registry.model.tld.label.ReservationType.getTypeOfHighestSeverity;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@ -51,6 +53,7 @@ import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponseRet
import google.registry.flows.domain.token.AllocationTokenDomainCheckResults;
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
import google.registry.model.EppResource;
import google.registry.model.billing.BillingEvent;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainCommand.Check;
import google.registry.model.domain.fee.FeeCheckCommandExtension;
@ -261,22 +264,26 @@ public final class DomainCheckFlow implements Flow {
FeeCheckCommandExtension<?, ?> feeCheck = feeCheckOpt.get();
ImmutableList.Builder<FeeCheckResponseExtensionItem> responseItems =
new ImmutableList.Builder<>();
ImmutableMap<String, EppResource> domainObjs =
ImmutableMap<String, DomainBase> domainObjs =
loadDomainsForRestoreChecks(feeCheck, domainNames, existingDomains);
ImmutableMap<String, BillingEvent.Recurring> recurrences =
loadRecurrencesForDomains(domainObjs);
for (FeeCheckCommandExtensionItem feeCheckItem : feeCheck.getItems()) {
for (String domainName : getDomainNamesToCheckForFee(feeCheckItem, domainNames.keySet())) {
FeeCheckResponseExtensionItem.Builder<?> builder = feeCheckItem.createResponseBuilder();
Optional<DomainBase> domainBase = Optional.ofNullable(domainObjs.get(domainName));
handleFeeRequest(
feeCheckItem,
builder,
domainNames.get(domainName),
Optional.ofNullable((DomainBase) domainObjs.get(domainName)),
domainBase,
feeCheck.getCurrency(),
now,
pricingLogic,
allocationToken,
availableDomains.contains(domainName));
availableDomains.contains(domainName),
recurrences.getOrDefault(domainName, null));
responseItems.add(builder.setDomainNameIfSupported(domainName).build());
}
}
@ -294,7 +301,7 @@ public final class DomainCheckFlow implements Flow {
* nicer in Cloud SQL when we can SELECT just the fields we want rather than having to load the
* entire entity.
*/
private ImmutableMap<String, EppResource> loadDomainsForRestoreChecks(
private ImmutableMap<String, DomainBase> loadDomainsForRestoreChecks(
FeeCheckCommandExtension<?, ?> feeCheck,
ImmutableMap<String, InternetDomainName> domainNames,
ImmutableMap<String, ForeignKeyIndex<DomainBase>> existingDomains) {
@ -326,7 +333,23 @@ public final class DomainCheckFlow implements Flow {
ImmutableMap<VKey<? extends EppResource>, EppResource> loadedDomains =
EppResource.loadCached(ImmutableList.copyOf(existingDomainsToLoad.values()));
return ImmutableMap.copyOf(
Maps.transformEntries(existingDomainsToLoad, (k, v) -> loadedDomains.get(v)));
Maps.transformEntries(existingDomainsToLoad, (k, v) -> (DomainBase) loadedDomains.get(v)));
}
private ImmutableMap<String, BillingEvent.Recurring> loadRecurrencesForDomains(
ImmutableMap<String, DomainBase> domainObjs) {
return tm().transact(
() -> {
ImmutableMap<VKey<? extends BillingEvent.Recurring>, BillingEvent.Recurring>
recurrences =
tm().loadByKeys(
domainObjs.values().stream()
.map(DomainBase::getAutorenewBillingEvent)
.collect(toImmutableSet()));
return ImmutableMap.copyOf(
Maps.transformValues(
domainObjs, d -> recurrences.get(d.getAutorenewBillingEvent())));
});
}
/**

View file

@ -633,7 +633,8 @@ public class DomainFlowUtils {
DateTime currentDate,
DomainPricingLogic pricingLogic,
Optional<AllocationToken> allocationToken,
boolean isAvailable)
boolean isAvailable,
@Nullable Recurring recurringBillingEvent)
throws EppException {
DateTime now = currentDate;
// Use the custom effective date specified in the fee check request, if there is one.
@ -680,7 +681,10 @@ public class DomainFlowUtils {
break;
case RENEW:
builder.setAvailIfSupported(true);
fees = pricingLogic.getRenewPrice(registry, domainNameString, now, years, null).getFees();
fees =
pricingLogic
.getRenewPrice(registry, domainNameString, now, years, recurringBillingEvent)
.getFees();
break;
case RESTORE:
// The minimum allowable period per the EPP spec is 1, so, strangely, 1 year still has to be

View file

@ -163,7 +163,8 @@ public final class DomainInfoFlow implements Flow {
now,
pricingLogic,
Optional.empty(),
false);
false,
tm().transact(() -> tm().loadByKey(domain.getAutorenewBillingEvent())));
extensions.add(builder.build());
}
return extensions.build();

View file

@ -14,6 +14,9 @@
package google.registry.flows.domain;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.DEFAULT;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.NONPREMIUM;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.SPECIFIED;
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.eppoutput.CheckData.DomainCheck.create;
@ -24,6 +27,7 @@ import static google.registry.testing.DatabaseHelper.createTlds;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.newDomainBase;
import static google.registry.testing.DatabaseHelper.persistActiveDomain;
import static google.registry.testing.DatabaseHelper.persistBillingRecurrenceForDomain;
import static google.registry.testing.DatabaseHelper.persistDeletedDomain;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistReservedList;
@ -66,7 +70,11 @@ import google.registry.flows.domain.DomainFlowUtils.TrailingDashException;
import google.registry.flows.domain.DomainFlowUtils.TransfersAreAlwaysForOneYearException;
import google.registry.flows.domain.DomainFlowUtils.UnknownFeeCommandException;
import google.registry.flows.exceptions.TooManyResourceChecksException;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.eppcommon.StatusValue;
@ -75,6 +83,7 @@ import google.registry.model.tld.Registry;
import google.registry.model.tld.Registry.TldState;
import google.registry.model.tld.label.ReservedList;
import google.registry.testing.SetClockExtension;
import java.math.BigDecimal;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
import org.joda.time.DateTime;
@ -806,6 +815,29 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
runFlowAssertResponse(loadFile("domain_check_fee_premium_response_v06.xml"));
}
@Test
void testFeeExtension_existingPremiumDomain_withNonPremiumRenewalBehavior() throws Exception {
createTld("example");
persistBillingRecurrenceForDomain(persistActiveDomain("rich.example"), NONPREMIUM, null);
setEppInput("domain_check_fee_premium_v06.xml");
runFlowAssertResponse(
loadFile(
"domain_check_fee_response_domain_exists_v06.xml",
ImmutableMap.of("RENEWPRICE", "11.00")));
}
@Test
void testFeeExtension_existingPremiumDomain_withSpecifiedRenewalBehavior() throws Exception {
createTld("example");
persistBillingRecurrenceForDomain(
persistActiveDomain("rich.example"), SPECIFIED, Money.of(USD, new BigDecimal("15.55")));
setEppInput("domain_check_fee_premium_v06.xml");
runFlowAssertResponse(
loadFile(
"domain_check_fee_response_domain_exists_v06.xml",
ImmutableMap.of("RENEWPRICE", "15.55")));
}
@Test
void testFeeExtension_premium_eap_v06() throws Exception {
createTld("example");
@ -829,21 +861,28 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
@Test
void testFeeExtension_premium_eap_v06_withRenewalOnRestore() throws Exception {
createTld("example");
setEppInput("domain_check_fee_premium_v06.xml");
clock.setTo(DateTime.parse("2010-01-01T10:00:00Z"));
DateTime startTime = DateTime.parse("2010-01-01T10:00:00Z");
clock.setTo(startTime);
persistResource(
persistActiveDomain("rich.example")
.asBuilder()
.setDeletionTime(clock.nowUtc().plusDays(25))
.setRegistrationExpirationTime(clock.nowUtc().minusDays(1))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.build());
persistPendingDeleteDomain("rich.example");
setEppInput("domain_check_fee_premium_v06.xml");
persistResource(
Registry.get("example")
.asBuilder()
.setEapFeeSchedule(
new ImmutableSortedMap.Builder<DateTime, Money>(Ordering.natural())
.put(START_OF_TIME, Money.of(USD, 0))
.put(clock.nowUtc().minusDays(1), Money.of(USD, 100))
.put(clock.nowUtc().plusDays(1), Money.of(USD, 50))
.put(clock.nowUtc().plusDays(2), Money.of(USD, 0))
.put(startTime.minusDays(1), Money.of(USD, 100))
.put(startTime.plusDays(1), Money.of(USD, 50))
.put(startTime.plusDays(2), Money.of(USD, 0))
.build())
.build());
runFlowAssertResponse(loadFile("domain_check_fee_premium_eap_response_v06_with_renewal.xml"));
}
@ -942,7 +981,7 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
.setPremiumList(persistPremiumList("tld", USD, "premiumcollision,USD 70"))
.build());
// The domain needs to exist in order for it to be loaded to check for restore fee.
persistActiveDomain("allowedinsunrise.tld");
persistBillingRecurrenceForDomain(persistActiveDomain("allowedinsunrise.tld"), DEFAULT, null);
setEppInput("domain_check_fee_reserved_dupes_v06.xml");
runFlowAssertResponse(loadFile("domain_check_fee_reserved_response_dupes_v06.xml"));
}
@ -1034,8 +1073,8 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
.setPremiumList(persistPremiumList("tld", USD, "premiumcollision,USD 70"))
.build());
// The domain needs to exist in order for it to be loaded to check for restore fee.
persistActiveDomain("allowedinsunrise.tld");
setEppInput("domain_check_fee_reserved_dupes_v12.xml");
persistBillingRecurrenceForDomain(persistActiveDomain("allowedinsunrise.tld"), DEFAULT, null);
runFlowAssertResponse(loadFile("domain_check_fee_reserved_dupes_response_v12.xml"));
}
@ -1375,7 +1414,8 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
assertTldsFieldLogged("com", "net", "org");
}
private void persistPendingDeleteDomain(String domainName) {
private DomainBase persistPendingDeleteDomain(String domainName) {
DomainBase existingDomain =
persistResource(
newDomainBase(domainName)
.asBuilder()
@ -1383,5 +1423,26 @@ class DomainCheckFlowTest extends ResourceCheckFlowTestCase<DomainCheckFlow, Dom
.setRegistrationExpirationTime(clock.nowUtc().minusDays(1))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.build());
DomainHistory historyEntry =
persistResource(
new DomainHistory.Builder()
.setDomain(existingDomain)
.setType(HistoryEntry.Type.DOMAIN_DELETE)
.setModificationTime(existingDomain.getCreationTime())
.setRegistrarId(existingDomain.getCreationRegistrarId())
.build());
BillingEvent.Recurring renewEvent =
persistResource(
new BillingEvent.Recurring.Builder()
.setReason(Reason.RENEW)
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setTargetId(existingDomain.getDomainName())
.setRegistrarId("TheRegistrar")
.setEventTime(existingDomain.getCreationTime())
.setRecurrenceEndTime(clock.nowUtc())
.setParent(historyEntry)
.build());
return persistResource(
existingDomain.asBuilder().setAutorenewBillingEvent(renewEvent.createVKey()).build());
}
}

View file

@ -16,17 +16,23 @@ package google.registry.flows.domain;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.DEFAULT;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.NONPREMIUM;
import static google.registry.model.billing.BillingEvent.RenewalPriceBehavior.SPECIFIED;
import static google.registry.model.tld.Registry.TldState.QUIET_PERIOD;
import static google.registry.testing.DatabaseHelper.assertNoBillingEvents;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.newDomainBase;
import static google.registry.testing.DatabaseHelper.persistActiveContact;
import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static google.registry.testing.DatabaseHelper.persistBillingRecurrenceForDomain;
import static google.registry.testing.DatabaseHelper.persistPremiumList;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
import static google.registry.testing.TestDataHelper.updateSubstitutions;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.DateTimeUtils.START_OF_TIME;
import static org.joda.money.CurrencyUnit.USD;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableMap;
@ -46,6 +52,7 @@ import google.registry.flows.domain.DomainFlowUtils.TransfersAreAlwaysForOneYear
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DesignatedContact;
@ -64,6 +71,8 @@ import google.registry.model.tld.Registry;
import google.registry.persistence.VKey;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.SetClockExtension;
import javax.annotation.Nullable;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
@ -175,12 +184,6 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
}
}
private void doSuccessfulTest(
String expectedXmlFilename, boolean inactive, ImmutableMap<String, String> substitutions)
throws Exception {
doSuccessfulTest(expectedXmlFilename, inactive, substitutions, false);
}
private void doSuccessfulTest(String expectedXmlFilename, boolean inactive) throws Exception {
doSuccessfulTest(expectedXmlFilename, inactive, ImmutableMap.of(), false);
}
@ -195,6 +198,16 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
doSuccessfulTest(expectedXmlFilename, true);
}
/** sets up a sample recurring billing event as part of the domain creation process. */
private void setUpBillingEventForExistingDomain() {
setUpBillingEventForExistingDomain(DEFAULT, null);
}
private void setUpBillingEventForExistingDomain(
RenewalPriceBehavior renewalPriceBehavior, @Nullable Money renewalPrice) {
domain = persistBillingRecurrenceForDomain(domain, renewalPriceBehavior, renewalPrice);
}
@Test
void testNotLoggedIn() {
sessionMetadata.setRegistrarId(null);
@ -685,6 +698,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"COMMAND", "create",
"PERIOD", "2"));
persistTestEntities(false);
setUpBillingEventForExistingDomain();
doSuccessfulTest(
"domain_info_fee_response.xml",
false,
@ -692,7 +706,8 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"COMMAND", "create",
"DESCRIPTION", "create",
"PERIOD", "2",
"FEE", "26.00"));
"FEE", "26.00"),
true);
}
/** Test renew command. */
@ -705,6 +720,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"COMMAND", "renew",
"PERIOD", "2"));
persistTestEntities(false);
setUpBillingEventForExistingDomain();
doSuccessfulTest(
"domain_info_fee_response.xml",
false,
@ -712,7 +728,8 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"COMMAND", "renew",
"DESCRIPTION", "renew",
"PERIOD", "2",
"FEE", "22.00"));
"FEE", "22.00"),
true);
}
/** Test transfer command. */
@ -725,6 +742,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"COMMAND", "transfer",
"PERIOD", "1"));
persistTestEntities(false);
setUpBillingEventForExistingDomain();
doSuccessfulTest(
"domain_info_fee_response.xml",
false,
@ -732,7 +750,8 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"COMMAND", "transfer",
"DESCRIPTION", "renew",
"PERIOD", "1",
"FEE", "11.00"));
"FEE", "11.00"),
true);
}
/** Test restore command. */
@ -745,7 +764,8 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"COMMAND", "restore",
"PERIOD", "1"));
persistTestEntities(false);
doSuccessfulTest("domain_info_fee_restore_response.xml", false);
setUpBillingEventForExistingDomain();
doSuccessfulTest("domain_info_fee_restore_response.xml", false, ImmutableMap.of(), true);
}
@Test
@ -754,13 +774,15 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"domain_info_fee.xml",
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "restore", "PERIOD", "1"));
persistTestEntities(false);
setUpBillingEventForExistingDomain();
persistResource(
domain
.asBuilder()
.setDeletionTime(clock.nowUtc().plusDays(25))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.build());
doSuccessfulTest("domain_info_fee_restore_response_no_renewal.xml", false);
doSuccessfulTest(
"domain_info_fee_restore_response_no_renewal.xml", false, ImmutableMap.of(), true);
}
@Test
@ -771,6 +793,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
updateSubstitutions(
SUBSTITUTION_BASE, "NAME", "rich.example", "COMMAND", "restore", "PERIOD", "1"));
persistTestEntities("rich.example", false);
setUpBillingEventForExistingDomain();
persistResource(
domain
.asBuilder()
@ -778,7 +801,8 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
.setRegistrationExpirationTime(clock.nowUtc().minusDays(1))
.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
.build());
doSuccessfulTest("domain_info_fee_restore_response_with_renewal.xml", false);
doSuccessfulTest(
"domain_info_fee_restore_response_with_renewal.xml", false, ImmutableMap.of(), true);
}
/** Test create command on a premium label. */
@ -793,10 +817,12 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"COMMAND", "create",
"PERIOD", "1"));
persistTestEntities("rich.example", false);
setUpBillingEventForExistingDomain();
doSuccessfulTest(
"domain_info_fee_premium_response.xml",
false,
ImmutableMap.of("COMMAND", "create", "DESCRIPTION", "create"));
ImmutableMap.of("COMMAND", "create", "DESCRIPTION", "create"),
true);
}
/** Test renew command on a premium label. */
@ -811,10 +837,107 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"COMMAND", "renew",
"PERIOD", "1"));
persistTestEntities("rich.example", false);
setUpBillingEventForExistingDomain();
doSuccessfulTest(
"domain_info_fee_premium_response.xml",
false,
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew"));
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew"),
true);
}
@Test
void testFeeExtension_renewCommandPremium_anchorTenant() throws Exception {
createTld("tld");
persistResource(
Registry.get("tld")
.asBuilder()
.setPremiumList(persistPremiumList("tld", USD, "example,USD 70"))
.build());
setEppInput(
"domain_info_fee.xml",
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "1"));
persistTestEntities("example.tld", false);
setUpBillingEventForExistingDomain(NONPREMIUM, null);
doSuccessfulTest(
"domain_info_fee_response.xml",
false,
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "11.0", "PERIOD", "1"),
true);
}
@Test
void testFeeExtension_renewCommandPremium_internalRegistration() throws Exception {
createTld("tld");
persistResource(
Registry.get("tld")
.asBuilder()
.setPremiumList(persistPremiumList("tld", USD, "example,USD 70"))
.build());
setEppInput(
"domain_info_fee.xml",
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "1"));
persistTestEntities("example.tld", false);
setUpBillingEventForExistingDomain(SPECIFIED, Money.of(USD, 3));
doSuccessfulTest(
"domain_info_fee_response.xml",
false,
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "3.0", "PERIOD", "1"),
true);
}
@Test
void testFeeExtension_renewCommandPremium_anchorTenant_multiYear() throws Exception {
createTld("tld");
persistResource(
Registry.get("tld")
.asBuilder()
.setPremiumList(persistPremiumList("tld", USD, "example,USD 70"))
.build());
setEppInput(
"domain_info_fee.xml",
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "3"));
persistTestEntities("example.tld", false);
setUpBillingEventForExistingDomain(NONPREMIUM, null);
doSuccessfulTest(
"domain_info_fee_response.xml",
false,
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "33.0", "PERIOD", "3"),
true);
}
@Test
void testFeeExtension_renewCommandPremium_internalRegistration_multiYear() throws Exception {
createTld("tld");
persistResource(
Registry.get("tld")
.asBuilder()
.setPremiumList(persistPremiumList("tld", USD, "example,USD 70"))
.build());
setEppInput(
"domain_info_fee.xml",
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "3"));
persistTestEntities("example.tld", false);
setUpBillingEventForExistingDomain(SPECIFIED, Money.of(USD, 3));
doSuccessfulTest(
"domain_info_fee_response.xml",
false,
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "9.0", "PERIOD", "3"),
true);
}
@Test
void testFeeExtension_renewCommandStandard_internalRegistration() throws Exception {
createTld("tld");
setEppInput(
"domain_info_fee.xml",
updateSubstitutions(SUBSTITUTION_BASE, "COMMAND", "renew", "PERIOD", "1"));
persistTestEntities("example.tld", false);
setUpBillingEventForExistingDomain(SPECIFIED, Money.of(USD, 3));
doSuccessfulTest(
"domain_info_fee_response.xml",
false,
ImmutableMap.of("COMMAND", "renew", "DESCRIPTION", "renew", "FEE", "3.0", "PERIOD", "1"),
true);
}
/** Test transfer command on a premium label. */
@ -829,10 +952,12 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"COMMAND", "transfer",
"PERIOD", "1"));
persistTestEntities("rich.example", false);
setUpBillingEventForExistingDomain();
doSuccessfulTest(
"domain_info_fee_premium_response.xml",
false,
ImmutableMap.of("COMMAND", "transfer", "DESCRIPTION", "renew"));
ImmutableMap.of("COMMAND", "transfer", "DESCRIPTION", "renew"),
true);
}
/** Test restore command on a premium label. */
@ -847,7 +972,9 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"COMMAND", "restore",
"PERIOD", "1"));
persistTestEntities("rich.example", false);
doSuccessfulTest("domain_info_fee_restore_premium_response.xml", false);
setUpBillingEventForExistingDomain();
doSuccessfulTest(
"domain_info_fee_restore_premium_response.xml", false, ImmutableMap.of(), true);
}
/** Test setting the currency explicitly to a wrong value. */
@ -861,6 +988,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"CURRENCY", "EUR",
"PERIOD", "1"));
persistTestEntities(false);
setUpBillingEventForExistingDomain();
EppException thrown = assertThrows(CurrencyUnitMismatchException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@ -889,6 +1017,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"PERIOD", "2",
"UNIT", "m"));
persistTestEntities(false);
setUpBillingEventForExistingDomain();
EppException thrown = assertThrows(BadPeriodUnitException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@ -898,6 +1027,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
void testFeeExtension_commandPhase() {
setEppInput("domain_info_fee_command_phase.xml");
persistTestEntities(false);
setUpBillingEventForExistingDomain();
EppException thrown = assertThrows(FeeChecksDontSupportPhasesException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@ -907,6 +1037,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
void testFeeExtension_commandSubphase() {
setEppInput("domain_info_fee_command_subphase.xml");
persistTestEntities(false);
setUpBillingEventForExistingDomain();
EppException thrown = assertThrows(FeeChecksDontSupportPhasesException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@ -921,6 +1052,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"COMMAND", "restore",
"PERIOD", "2"));
persistTestEntities(false);
setUpBillingEventForExistingDomain();
EppException thrown = assertThrows(RestoresAreAlwaysForOneYearException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}
@ -935,6 +1067,7 @@ class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, DomainBase
"COMMAND", "transfer",
"PERIOD", "2"));
persistTestEntities(false);
setUpBillingEventForExistingDomain();
EppException thrown = assertThrows(TransfersAreAlwaysForOneYearException.class, this::runFlow);
assertAboutEppExceptions().that(thrown).marshalsToXml();
}

View file

@ -69,6 +69,8 @@ import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingEvent;
import google.registry.model.billing.BillingEvent.Flag;
import google.registry.model.billing.BillingEvent.Reason;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
import google.registry.model.contact.ContactAuthInfo;
import google.registry.model.contact.ContactHistory;
import google.registry.model.contact.ContactResource;
@ -330,6 +332,35 @@ public class DatabaseHelper {
return persistedDomain;
}
/** Persists a {@link Recurring} and {@link HistoryEntry} for a domain that already exists. */
public static DomainBase persistBillingRecurrenceForDomain(
DomainBase domain, RenewalPriceBehavior renewalPriceBehavior, @Nullable Money renewalPrice) {
DomainHistory historyEntry =
persistResource(
new DomainHistory.Builder()
.setRegistrarId(domain.getCreationRegistrarId())
.setType(HistoryEntry.Type.DOMAIN_CREATE)
.setModificationTime(domain.getCreationTime())
.setDomain(domain)
.build());
Recurring recurring =
persistResource(
new BillingEvent.Recurring.Builder()
.setParent(historyEntry)
.setRenewalPrice(renewalPrice)
.setRenewalPriceBehavior(renewalPriceBehavior)
.setRegistrarId(domain.getCreationRegistrarId())
.setEventTime(domain.getCreationTime())
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
.setId(2L)
.setReason(Reason.RENEW)
.setRecurrenceEndTime(END_OF_TIME)
.setTargetId(domain.getDomainName())
.build());
return persistResource(
domain.asBuilder().setAutorenewBillingEvent(recurring.createVKey()).build());
}
public static ReservedList persistReservedList(String listName, String... lines) {
return persistReservedList(listName, true, lines);
}

View file

@ -19,7 +19,7 @@
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="create">100.00</fee:fee>
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.002Z">100.00
<fee:fee description="Early Access Period, fee expires: 2010-01-02T10:00:00.000Z">100.00
</fee:fee>
<fee:class>premium</fee:class>
</fee:cd>

View file

@ -0,0 +1,55 @@
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<response>
<result code="1000">
<msg>Command completed successfully</msg>
</result>
<resData>
<domain:chkData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:cd>
<domain:name avail="0">rich.example</domain:name>
<domain:reason>In use</domain:reason>
</domain:cd>
</domain:chkData>
</resData>
<extension>
<fee:chkData xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>rich.example</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="create">100.00</fee:fee>
<fee:class>premium</fee:class>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>rich.example</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>renew</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="renew">%RENEWPRICE%</fee:fee>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>rich.example</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>transfer</fee:command>
<fee:period unit="y">1</fee:period>
<!-- TODO(mcilwain): This should be non-premium once transfer flow
changes are made. -->
<fee:fee description="renew">100.00</fee:fee>
<fee:class>premium</fee:class>
</fee:cd>
<fee:cd xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:name>rich.example</fee:name>
<fee:currency>USD</fee:currency>
<fee:command>restore</fee:command>
<fee:period unit="y">1</fee:period>
<fee:fee description="restore">17.00</fee:fee>
</fee:cd>
</fee:chkData>
</extension>
<trID>
<clTRID>ABC-12345</clTRID>
<svTRID>server-trid</svTRID>
</trID>
</response>
</epp>