mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 12:07:51 +02:00
Add new columns to BillingEvent (#1573)
* Add new columns to BillingEvent.java * Improve PR and modifyJodaMoneyType to handle null currency in override * Add test cases for edge cases of nullSafeGet in JodaMoneyType * Improve assertions
This commit is contained in:
parent
430e136920
commit
f79e4740f6
6 changed files with 712 additions and 13 deletions
|
@ -126,7 +126,40 @@ public abstract class BillingEvent extends ImmutableObject
|
||||||
* This flag will be added to any {@link OneTime} events that are created via, e.g., an
|
* This flag will be added to any {@link OneTime} events that are created via, e.g., an
|
||||||
* automated process to expand {@link Recurring} events.
|
* automated process to expand {@link Recurring} events.
|
||||||
*/
|
*/
|
||||||
SYNTHETIC
|
SYNTHETIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets of renewal price behaviors that can be applied to billing recurrences.
|
||||||
|
*
|
||||||
|
* <p>When a client renews a domain, they could be charged differently, depending on factors such
|
||||||
|
* as the client type and the domain itself.
|
||||||
|
*/
|
||||||
|
public enum RenewalPriceBehavior {
|
||||||
|
/**
|
||||||
|
* This indicates the renewal price is the default price.
|
||||||
|
*
|
||||||
|
* <p>By default, if the domain is premium, then premium price will be used. Otherwise, the
|
||||||
|
* standard price of the TLD will be used.
|
||||||
|
*/
|
||||||
|
DEFAULT,
|
||||||
|
/**
|
||||||
|
* This indicates the domain will be renewed at standard price even if it's a premium domain.
|
||||||
|
*
|
||||||
|
* <p>We chose to name this "NONPREMIUM" rather than simply "STANDARD" to avoid confusion
|
||||||
|
* between "STANDARD" and "DEFAULT".
|
||||||
|
*
|
||||||
|
* <p>This price behavior is used with anchor tenants.
|
||||||
|
*/
|
||||||
|
NONPREMIUM,
|
||||||
|
/**
|
||||||
|
* This indicates that the renewalPrice in {@link BillingEvent.Recurring} will be used for
|
||||||
|
* domain renewal.
|
||||||
|
*
|
||||||
|
* <p>The renewalPrice has a non-null value iff the price behavior is set to "SPECIFIED". This
|
||||||
|
* behavior is used with internal registrations.
|
||||||
|
*/
|
||||||
|
SPECIFIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Entity id. */
|
/** Entity id. */
|
||||||
|
@ -555,6 +588,22 @@ public abstract class BillingEvent extends ImmutableObject
|
||||||
})
|
})
|
||||||
TimeOfYear recurrenceTimeOfYear;
|
TimeOfYear recurrenceTimeOfYear;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The renewal price for domain renewal if and only if it's specified.
|
||||||
|
*
|
||||||
|
* <p>This price column remains null except when the renewal price behavior of the billing is
|
||||||
|
* SPECIFIED. This column is used for internal registrations.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Type(type = JodaMoneyType.TYPE_NAME)
|
||||||
|
@Columns(
|
||||||
|
columns = {@Column(name = "renewalPriceAmount"), @Column(name = "renewalPriceCurrency")})
|
||||||
|
Money renewalPrice;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "renewalPriceBehavior", nullable = false)
|
||||||
|
RenewalPriceBehavior renewalPriceBehavior = RenewalPriceBehavior.DEFAULT;
|
||||||
|
|
||||||
public DateTime getRecurrenceEndTime() {
|
public DateTime getRecurrenceEndTime() {
|
||||||
return recurrenceEndTime;
|
return recurrenceEndTime;
|
||||||
}
|
}
|
||||||
|
@ -563,6 +612,14 @@ public abstract class BillingEvent extends ImmutableObject
|
||||||
return recurrenceTimeOfYear;
|
return recurrenceTimeOfYear;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RenewalPriceBehavior getRenewalPriceBehavior() {
|
||||||
|
return renewalPriceBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Money> getRenewalPrice() {
|
||||||
|
return Optional.ofNullable(renewalPrice);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public VKey<Recurring> createVKey() {
|
public VKey<Recurring> createVKey() {
|
||||||
return VKey.create(Recurring.class, getId(), Key.create(this));
|
return VKey.create(Recurring.class, getId(), Key.create(this));
|
||||||
|
@ -591,11 +648,26 @@ public abstract class BillingEvent extends ImmutableObject
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder setRenewalPriceBehavior(RenewalPriceBehavior renewalPriceBehavior) {
|
||||||
|
getInstance().renewalPriceBehavior = renewalPriceBehavior;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setRenewalPrice(@Nullable Money renewalPrice) {
|
||||||
|
getInstance().renewalPrice = renewalPrice;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Recurring build() {
|
public Recurring build() {
|
||||||
Recurring instance = getInstance();
|
Recurring instance = getInstance();
|
||||||
checkNotNull(instance.eventTime);
|
checkNotNull(instance.eventTime);
|
||||||
checkNotNull(instance.reason);
|
checkNotNull(instance.reason);
|
||||||
|
checkArgument(
|
||||||
|
(instance.renewalPriceBehavior == RenewalPriceBehavior.SPECIFIED)
|
||||||
|
^ (instance.renewalPrice == null),
|
||||||
|
"Renewal price can have a value if and only if the renewal price behavior is"
|
||||||
|
+ " SPECIFIED");
|
||||||
instance.recurrenceTimeOfYear = TimeOfYear.fromDateTime(instance.eventTime);
|
instance.recurrenceTimeOfYear = TimeOfYear.fromDateTime(instance.eventTime);
|
||||||
instance.recurrenceEndTime =
|
instance.recurrenceEndTime =
|
||||||
Optional.ofNullable(instance.recurrenceEndTime).orElse(END_OF_TIME);
|
Optional.ofNullable(instance.recurrenceEndTime).orElse(END_OF_TIME);
|
||||||
|
|
|
@ -21,6 +21,7 @@ import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.type.StandardBasicTypes;
|
import org.hibernate.type.StandardBasicTypes;
|
||||||
|
@ -116,20 +117,28 @@ public class JodaMoneyType implements CompositeUserType {
|
||||||
return Objects.hashCode(x);
|
return Objects.hashCode(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Object nullSafeGet(
|
public Object nullSafeGet(
|
||||||
ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
|
ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
|
||||||
throws HibernateException, SQLException {
|
throws HibernateException, SQLException {
|
||||||
BigDecimal amount = StandardBasicTypes.BIG_DECIMAL.nullSafeGet(rs, names[AMOUNT_ID], session);
|
BigDecimal amount = StandardBasicTypes.BIG_DECIMAL.nullSafeGet(rs, names[AMOUNT_ID], session);
|
||||||
CurrencyUnit currencyUnit =
|
String currencyUnitString =
|
||||||
CurrencyUnit.of(StandardBasicTypes.STRING.nullSafeGet(rs, names[CURRENCY_ID], session));
|
StandardBasicTypes.STRING.nullSafeGet(rs, names[CURRENCY_ID], session);
|
||||||
if (amount != null && currencyUnit != null) {
|
// It is allowable for a Money object to be null, but only if both the currency unit and the
|
||||||
return Money.of(currencyUnit, amount.stripTrailingZeros());
|
// amount are null
|
||||||
}
|
if (amount == null && currencyUnitString == null) {
|
||||||
if (amount == null && currencyUnit == null) {
|
|
||||||
return null;
|
return null;
|
||||||
|
} else if (amount != null && currencyUnitString != null) {
|
||||||
|
// CurrencyUnit.of() throws an IllegalCurrencyException for unknown currency, which means the
|
||||||
|
// currency is valid if it returns a value
|
||||||
|
return Money.of(CurrencyUnit.of(currencyUnitString), amount.stripTrailingZeros());
|
||||||
|
} else {
|
||||||
|
throw new HibernateException(
|
||||||
|
String.format(
|
||||||
|
"Mismatching null state between currency '%s' and amount '%s'",
|
||||||
|
currencyUnitString, amount));
|
||||||
}
|
}
|
||||||
throw new HibernateException("Mismatching null state between currency and amount.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -140,7 +149,7 @@ public class JodaMoneyType implements CompositeUserType {
|
||||||
String currencyUnit = value == null ? null : ((Money) value).getCurrencyUnit().getCode();
|
String currencyUnit = value == null ? null : ((Money) value).getCurrencyUnit().getCode();
|
||||||
|
|
||||||
if ((amount == null && currencyUnit != null) || (amount != null && currencyUnit == null)) {
|
if ((amount == null && currencyUnit != null) || (amount != null && currencyUnit == null)) {
|
||||||
throw new HibernateException("Mismatching null state between currency and amount.");
|
throw new HibernateException("Mismatching null state between currency and amount");
|
||||||
}
|
}
|
||||||
StandardBasicTypes.BIG_DECIMAL.nullSafeSet(st, amount, index, session);
|
StandardBasicTypes.BIG_DECIMAL.nullSafeSet(st, amount, index, session);
|
||||||
StandardBasicTypes.STRING.nullSafeSet(st, currencyUnit, index + 1, session);
|
StandardBasicTypes.STRING.nullSafeSet(st, currencyUnit, index + 1, session);
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package google.registry.model.billing;
|
package google.registry.model.billing;
|
||||||
|
|
||||||
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 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.auditedOfy;
|
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||||
|
@ -36,6 +37,7 @@ import com.googlecode.objectify.Key;
|
||||||
import google.registry.model.EntityTestCase;
|
import google.registry.model.EntityTestCase;
|
||||||
import google.registry.model.billing.BillingEvent.Flag;
|
import google.registry.model.billing.BillingEvent.Flag;
|
||||||
import google.registry.model.billing.BillingEvent.Reason;
|
import google.registry.model.billing.BillingEvent.Reason;
|
||||||
|
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
|
||||||
import google.registry.model.domain.DomainBase;
|
import google.registry.model.domain.DomainBase;
|
||||||
import google.registry.model.domain.DomainHistory;
|
import google.registry.model.domain.DomainHistory;
|
||||||
import google.registry.model.domain.GracePeriod;
|
import google.registry.model.domain.GracePeriod;
|
||||||
|
@ -49,6 +51,7 @@ import google.registry.testing.TestOfyAndSql;
|
||||||
import google.registry.testing.TestOfyOnly;
|
import google.registry.testing.TestOfyOnly;
|
||||||
import google.registry.testing.TestSqlOnly;
|
import google.registry.testing.TestSqlOnly;
|
||||||
import google.registry.util.DateTimeUtils;
|
import google.registry.util.DateTimeUtils;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import org.joda.money.Money;
|
import org.joda.money.Money;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
@ -474,4 +477,487 @@ public class BillingEventTest extends EntityTestCase {
|
||||||
.setParent(domainHistory)
|
.setParent(domainHistory)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testSuccess_defaultRenewalPriceBehavior_assertsIsDefault() {
|
||||||
|
assertThat(recurring.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.DEFAULT);
|
||||||
|
assertThat(recurring.getRenewalPrice()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testSuccess_getRenewalPriceBehavior_returnsRightBehavior() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.NONPREMIUM);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testSuccess_setRenewalPriceBehaviorThenBuild_defaultToSpecified() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.DEFAULT);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
|
||||||
|
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
|
||||||
|
assertThat(loadedEntity).isEqualTo(recurringEvent);
|
||||||
|
persistResource(
|
||||||
|
loadedEntity
|
||||||
|
.asBuilder()
|
||||||
|
.setRenewalPrice(Money.of(USD, 100))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
|
||||||
|
.build());
|
||||||
|
assertThat(loadByEntity(recurringEvent).getRenewalPriceBehavior())
|
||||||
|
.isEqualTo(RenewalPriceBehavior.SPECIFIED);
|
||||||
|
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).hasValue(Money.of(USD, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testSuccess_setRenewalPriceBehaviorThenBuild_defaultToNonPremium() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.DEFAULT);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
|
||||||
|
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
|
||||||
|
assertThat(loadedEntity).isEqualTo(recurringEvent);
|
||||||
|
persistResource(
|
||||||
|
loadedEntity.asBuilder().setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM).build());
|
||||||
|
assertThat(loadByEntity(recurringEvent).getRenewalPriceBehavior())
|
||||||
|
.isEqualTo(RenewalPriceBehavior.NONPREMIUM);
|
||||||
|
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testSuccess_setRenewalPriceBehaviorThenBuild_nonPremiumToSpecified() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.NONPREMIUM);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
|
||||||
|
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
|
||||||
|
assertThat(loadedEntity).isEqualTo(recurringEvent);
|
||||||
|
persistResource(
|
||||||
|
loadedEntity
|
||||||
|
.asBuilder()
|
||||||
|
.setRenewalPrice(Money.of(USD, 100))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
|
||||||
|
.build());
|
||||||
|
assertThat(loadByEntity(recurringEvent).getRenewalPriceBehavior())
|
||||||
|
.isEqualTo(RenewalPriceBehavior.SPECIFIED);
|
||||||
|
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).hasValue(Money.of(USD, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testSuccess_setRenewalPriceBehaviorThenBuild_nonPremiumToDefault() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.NONPREMIUM);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
|
||||||
|
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
|
||||||
|
assertThat(loadedEntity).isEqualTo(recurringEvent);
|
||||||
|
persistResource(
|
||||||
|
loadedEntity.asBuilder().setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT).build());
|
||||||
|
assertThat(loadByEntity(recurringEvent).getRenewalPriceBehavior())
|
||||||
|
.isEqualTo(RenewalPriceBehavior.DEFAULT);
|
||||||
|
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testSuccess_setRenewalPriceBehaviorThenBuild_specifiedToDefault() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
|
||||||
|
.setRenewalPrice(Money.of(USD, 100))
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.SPECIFIED);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).hasValue(Money.of(USD, 100));
|
||||||
|
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
|
||||||
|
assertThat(loadedEntity).isEqualTo(recurringEvent);
|
||||||
|
persistResource(
|
||||||
|
loadedEntity
|
||||||
|
.asBuilder()
|
||||||
|
.setRenewalPrice(null)
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
|
||||||
|
.build());
|
||||||
|
assertThat(loadByEntity(recurringEvent).getRenewalPriceBehavior())
|
||||||
|
.isEqualTo(RenewalPriceBehavior.DEFAULT);
|
||||||
|
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testSuccess_setRenewalPriceBehaviorThenBuild_specifiedToNonPremium() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
|
||||||
|
.setRenewalPrice(Money.of(USD, 100))
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.SPECIFIED);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).hasValue(Money.of(USD, 100));
|
||||||
|
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
|
||||||
|
assertThat(loadedEntity).isEqualTo(recurringEvent);
|
||||||
|
persistResource(
|
||||||
|
loadedEntity
|
||||||
|
.asBuilder()
|
||||||
|
.setRenewalPrice(null)
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
|
||||||
|
.build());
|
||||||
|
assertThat(loadByEntity(recurringEvent).getRenewalPriceBehavior())
|
||||||
|
.isEqualTo(RenewalPriceBehavior.NONPREMIUM);
|
||||||
|
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testFailure_setRenewalPriceBehaviorThenBuild_defaultToSpecified_needRenewalPrice() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.DEFAULT);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
|
||||||
|
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
|
||||||
|
assertThat(loadedEntity).isEqualTo(recurringEvent);
|
||||||
|
IllegalArgumentException thrown =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() ->
|
||||||
|
loadedEntity
|
||||||
|
.asBuilder()
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
|
||||||
|
.build());
|
||||||
|
assertThat(thrown)
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo(
|
||||||
|
"Renewal price can have a value if and only if the "
|
||||||
|
+ "renewal price behavior is SPECIFIED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testFailure_setRenewalPriceBehaviorThenBuild_defaultToPremium_noNeedToAddRenewalPrice() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.DEFAULT);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
|
||||||
|
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
|
||||||
|
assertThat(loadedEntity).isEqualTo(recurringEvent);
|
||||||
|
IllegalArgumentException thrown =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() ->
|
||||||
|
loadedEntity
|
||||||
|
.asBuilder()
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
|
||||||
|
.setRenewalPrice(Money.of(USD, 100))
|
||||||
|
.build());
|
||||||
|
assertThat(thrown)
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo(
|
||||||
|
"Renewal price can have a value if and only if the "
|
||||||
|
+ "renewal price behavior is SPECIFIED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testFailure_setRenewalPriceBehaviorThenBuild_nonPremiumToDefault_noNeedToAddRenewalPrice() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.NONPREMIUM);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
|
||||||
|
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
|
||||||
|
assertThat(loadedEntity).isEqualTo(recurringEvent);
|
||||||
|
IllegalArgumentException thrown =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() ->
|
||||||
|
loadedEntity
|
||||||
|
.asBuilder()
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
|
||||||
|
.setRenewalPrice(Money.of(USD, 100))
|
||||||
|
.build());
|
||||||
|
assertThat(thrown)
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo(
|
||||||
|
"Renewal price can have a value if and only if the "
|
||||||
|
+ "renewal price behavior is SPECIFIED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testFailure_setRenewalPriceBehaviorThenBuild_nonPremiumToSpecified_needRenewalPrice() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.NONPREMIUM);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).isEmpty();
|
||||||
|
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
|
||||||
|
assertThat(loadedEntity).isEqualTo(recurringEvent);
|
||||||
|
IllegalArgumentException thrown =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() ->
|
||||||
|
loadedEntity
|
||||||
|
.asBuilder()
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
|
||||||
|
.build());
|
||||||
|
assertThat(thrown)
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo(
|
||||||
|
"Renewal price can have a value if and only if the "
|
||||||
|
+ "renewal price behavior is SPECIFIED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testFailure_setRenewalPriceBehaviorThenBuild_specifiedToNonPremium_removeRenewalPrice() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
|
||||||
|
.setRenewalPrice(Money.of(USD, 100))
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.SPECIFIED);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).hasValue(Money.of(USD, 100));
|
||||||
|
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
|
||||||
|
assertThat(loadedEntity).isEqualTo(recurringEvent);
|
||||||
|
IllegalArgumentException thrown =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() ->
|
||||||
|
loadedEntity
|
||||||
|
.asBuilder()
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
|
||||||
|
.build());
|
||||||
|
assertThat(thrown)
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo(
|
||||||
|
"Renewal price can have a value if and only if the "
|
||||||
|
+ "renewal price behavior is SPECIFIED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testFailure_setRenewalPriceBehaviorThenBuild_specifiedToDefault_removeRenewalPrice() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
|
||||||
|
.setRenewalPrice(Money.of(USD, 100))
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.SPECIFIED);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).hasValue(Money.of(USD, 100));
|
||||||
|
BillingEvent.Recurring loadedEntity = loadByEntity(recurringEvent);
|
||||||
|
assertThat(loadedEntity).isEqualTo(recurringEvent);
|
||||||
|
IllegalArgumentException thrown =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() ->
|
||||||
|
loadedEntity
|
||||||
|
.asBuilder()
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
|
||||||
|
.build());
|
||||||
|
assertThat(thrown)
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo(
|
||||||
|
"Renewal price can have a value if and only if the "
|
||||||
|
+ "renewal price behavior is SPECIFIED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testSuccess_buildWithDefaultRenewalBehavior() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
|
||||||
|
.setRenewalPrice(Money.of(USD, BigDecimal.valueOf(100)))
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.SPECIFIED);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).hasValue(Money.of(USD, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testSuccess_buildWithNonPremiumRenewalBehavior() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.NONPREMIUM);
|
||||||
|
assertThat(loadByEntity(recurringEvent).getRenewalPrice()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testSuccess_buildWithSpecifiedRenewalBehavior() {
|
||||||
|
BillingEvent.Recurring recurringEvent =
|
||||||
|
persistResource(
|
||||||
|
commonInit(
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
|
||||||
|
.setRenewalPrice(Money.of(USD, BigDecimal.valueOf(100)))
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)));
|
||||||
|
assertThat(recurringEvent.getRenewalPriceBehavior()).isEqualTo(RenewalPriceBehavior.SPECIFIED);
|
||||||
|
assertThat(recurringEvent.getRenewalPrice()).hasValue(Money.of(USD, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testFailure_buildWithSpecifiedRenewalBehavior_requiresNonNullRenewalPrice() {
|
||||||
|
IllegalArgumentException thrown =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() ->
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.SPECIFIED)
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)
|
||||||
|
.build());
|
||||||
|
assertThat(thrown)
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo(
|
||||||
|
"Renewal price can have a value if and only if the "
|
||||||
|
+ "renewal price behavior is SPECIFIED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testFailure_buildWithNonPremiumRenewalBehavior_requiresNullRenewalPrice() {
|
||||||
|
IllegalArgumentException thrown =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() ->
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.NONPREMIUM)
|
||||||
|
.setRenewalPrice(Money.of(USD, BigDecimal.valueOf(100)))
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)
|
||||||
|
.build());
|
||||||
|
assertThat(thrown)
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo(
|
||||||
|
"Renewal price can have a value if and only if the "
|
||||||
|
+ "renewal price behavior is SPECIFIED");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOfyAndSql
|
||||||
|
void testFailure_buildWithDefaultRenewalBehavior_requiresNullRenewalPrice() {
|
||||||
|
IllegalArgumentException thrown =
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() ->
|
||||||
|
new BillingEvent.Recurring.Builder()
|
||||||
|
.setParent(domainHistory)
|
||||||
|
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||||
|
.setReason(Reason.RENEW)
|
||||||
|
.setEventTime(now.plusYears(1))
|
||||||
|
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
|
||||||
|
.setRenewalPrice(Money.of(USD, BigDecimal.valueOf(100)))
|
||||||
|
.setRecurrenceEndTime(END_OF_TIME)
|
||||||
|
.build());
|
||||||
|
assertThat(thrown)
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo(
|
||||||
|
"Renewal price can have a value if and only if the "
|
||||||
|
+ "renewal price behavior is SPECIFIED");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ package google.registry.persistence.converter;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||||
import static google.registry.testing.DatabaseHelper.insertInDb;
|
import static google.registry.testing.DatabaseHelper.insertInDb;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import google.registry.model.ImmutableObject;
|
import google.registry.model.ImmutableObject;
|
||||||
|
@ -23,6 +24,7 @@ import google.registry.model.replay.EntityTest.EntityForTesting;
|
||||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
import google.registry.persistence.transaction.JpaTestExtensions;
|
||||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaUnitTestExtension;
|
import google.registry.persistence.transaction.JpaTestExtensions.JpaUnitTestExtension;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -34,9 +36,11 @@ import javax.persistence.FetchType;
|
||||||
import javax.persistence.Id;
|
import javax.persistence.Id;
|
||||||
import javax.persistence.JoinColumn;
|
import javax.persistence.JoinColumn;
|
||||||
import javax.persistence.MapKeyColumn;
|
import javax.persistence.MapKeyColumn;
|
||||||
|
import javax.persistence.PersistenceException;
|
||||||
import org.hibernate.annotations.Columns;
|
import org.hibernate.annotations.Columns;
|
||||||
import org.hibernate.annotations.Type;
|
import org.hibernate.annotations.Type;
|
||||||
import org.joda.money.CurrencyUnit;
|
import org.joda.money.CurrencyUnit;
|
||||||
|
import org.joda.money.IllegalCurrencyException;
|
||||||
import org.joda.money.Money;
|
import org.joda.money.Money;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
@ -65,7 +69,7 @@ public class JodaMoneyConverterTest {
|
||||||
.createNativeQuery(
|
.createNativeQuery(
|
||||||
"SELECT amount, currency FROM \"TestEntity\" WHERE name = 'id'")
|
"SELECT amount, currency FROM \"TestEntity\" WHERE name = 'id'")
|
||||||
.getResultList());
|
.getResultList());
|
||||||
assertThat(result.size()).isEqualTo(1);
|
assertThat(result).hasSize(1);
|
||||||
// The amount property, when loaded as a raw value, has the same scale as the table column,
|
// The amount property, when loaded as a raw value, has the same scale as the table column,
|
||||||
// which is 2.
|
// which is 2.
|
||||||
assertThat(Arrays.asList((Object[]) result.get(0)))
|
assertThat(Arrays.asList((Object[]) result.get(0)))
|
||||||
|
@ -91,7 +95,7 @@ public class JodaMoneyConverterTest {
|
||||||
.createNativeQuery(
|
.createNativeQuery(
|
||||||
"SELECT amount, currency FROM \"TestEntity\" WHERE name = 'id'")
|
"SELECT amount, currency FROM \"TestEntity\" WHERE name = 'id'")
|
||||||
.getResultList());
|
.getResultList());
|
||||||
assertThat(result.size()).isEqualTo(1);
|
assertThat(result).hasSize(1);
|
||||||
/* The amount property, when loaded as a raw value, has the same scale as the table column,
|
/* The amount property, when loaded as a raw value, has the same scale as the table column,
|
||||||
which is 2. */
|
which is 2. */
|
||||||
assertThat(Arrays.asList((Object[]) result.get(0)))
|
assertThat(Arrays.asList((Object[]) result.get(0)))
|
||||||
|
@ -136,7 +140,7 @@ public class JodaMoneyConverterTest {
|
||||||
"SELECT my_amount, my_currency, your_amount, your_currency FROM"
|
"SELECT my_amount, my_currency, your_amount, your_currency FROM"
|
||||||
+ " \"ComplexTestEntity\" WHERE name = 'id'")
|
+ " \"ComplexTestEntity\" WHERE name = 'id'")
|
||||||
.getResultList());
|
.getResultList());
|
||||||
assertThat(result.size()).isEqualTo(1);
|
assertThat(result).hasSize(1);
|
||||||
assertThat(Arrays.asList((Object[]) result.get(0)))
|
assertThat(Arrays.asList((Object[]) result.get(0)))
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
BigDecimal.valueOf(100).setScale(2), "USD", BigDecimal.valueOf(80).setScale(2), "GBP")
|
BigDecimal.valueOf(100).setScale(2), "USD", BigDecimal.valueOf(80).setScale(2), "GBP")
|
||||||
|
@ -153,7 +157,7 @@ public class JodaMoneyConverterTest {
|
||||||
.getResultList());
|
.getResultList());
|
||||||
ComplexTestEntity persisted =
|
ComplexTestEntity persisted =
|
||||||
jpaTm().transact(() -> jpaTm().getEntityManager().find(ComplexTestEntity.class, "id"));
|
jpaTm().transact(() -> jpaTm().getEntityManager().find(ComplexTestEntity.class, "id"));
|
||||||
assertThat(result.size()).isEqualTo(1);
|
assertThat(result).hasSize(1);
|
||||||
|
|
||||||
assertThat(Arrays.asList((Object[]) result.get(0)))
|
assertThat(Arrays.asList((Object[]) result.get(0)))
|
||||||
.containsExactly(BigDecimal.valueOf(2000).setScale(2), "JPY")
|
.containsExactly(BigDecimal.valueOf(2000).setScale(2), "JPY")
|
||||||
|
@ -164,6 +168,124 @@ public class JodaMoneyConverterTest {
|
||||||
assertThat(persisted.moneyMap).containsExactlyEntriesIn(moneyMap);
|
assertThat(persisted.moneyMap).containsExactlyEntriesIn(moneyMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implicit test cases for @override method @nullSafeGet when constructing {@link Money} object
|
||||||
|
* with null/invalid column(s).
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testNullSafeGet_nullAmountNullCurrency_returnsNull() throws SQLException {
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() ->
|
||||||
|
jpaTm()
|
||||||
|
.getEntityManager()
|
||||||
|
.createNativeQuery(
|
||||||
|
"INSERT INTO \"TestEntity\" (name, amount, currency) VALUES('id', null,"
|
||||||
|
+ " null)")
|
||||||
|
.executeUpdate());
|
||||||
|
List<?> result =
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() ->
|
||||||
|
jpaTm()
|
||||||
|
.getEntityManager()
|
||||||
|
.createNativeQuery(
|
||||||
|
"SELECT amount, currency FROM \"TestEntity\" WHERE name = 'id'")
|
||||||
|
.getResultList());
|
||||||
|
assertThat(result).hasSize(1);
|
||||||
|
assertThat(Arrays.asList((Object[]) result.get(0))).containsExactly(null, null).inOrder();
|
||||||
|
assertThat(
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() ->
|
||||||
|
jpaTm()
|
||||||
|
.getEntityManager()
|
||||||
|
.createQuery("SELECT money FROM TestEntity WHERE name = 'id'")
|
||||||
|
.getResultList())
|
||||||
|
.get(0))
|
||||||
|
.isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNullSafeGet_nullAMountValidCurrency_throwsHibernateException() throws SQLException {
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() ->
|
||||||
|
jpaTm()
|
||||||
|
.getEntityManager()
|
||||||
|
.createNativeQuery(
|
||||||
|
"INSERT INTO \"TestEntity\" (name, amount, currency) VALUES('id', null,"
|
||||||
|
+ " 'USD')")
|
||||||
|
.executeUpdate());
|
||||||
|
List<?> result =
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() ->
|
||||||
|
jpaTm()
|
||||||
|
.getEntityManager()
|
||||||
|
.createNativeQuery(
|
||||||
|
"SELECT amount, currency FROM \"TestEntity\" WHERE name = 'id'")
|
||||||
|
.getResultList());
|
||||||
|
assertThat(Arrays.asList((Object[]) result.get(0))).containsExactly(null, "USD");
|
||||||
|
// CurrencyUnit.of() throws HibernateException for invalid currency which leads to persistance
|
||||||
|
// error
|
||||||
|
PersistenceException thrown =
|
||||||
|
assertThrows(
|
||||||
|
PersistenceException.class,
|
||||||
|
() ->
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() ->
|
||||||
|
jpaTm()
|
||||||
|
.getEntityManager()
|
||||||
|
.createQuery("SELECT money FROM TestEntity WHERE name = 'id'")
|
||||||
|
.getResultList()));
|
||||||
|
assertThat(thrown)
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo(
|
||||||
|
"org.hibernate.HibernateException: Mismatching null state between currency 'USD' and"
|
||||||
|
+ " amount 'null'");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNullSafeGet_nullAMountInValidCurrency_throwsIllegalCurrencyException()
|
||||||
|
throws SQLException {
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() ->
|
||||||
|
jpaTm()
|
||||||
|
.getEntityManager()
|
||||||
|
.createNativeQuery(
|
||||||
|
"INSERT INTO \"TestEntity\" (name, amount, currency) VALUES('id', 100,"
|
||||||
|
+ " 'INVALIDCURRENCY')")
|
||||||
|
.executeUpdate());
|
||||||
|
List<?> result =
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() ->
|
||||||
|
jpaTm()
|
||||||
|
.getEntityManager()
|
||||||
|
.createNativeQuery(
|
||||||
|
"SELECT amount, currency FROM \"TestEntity\" WHERE name = 'id'")
|
||||||
|
.getResultList());
|
||||||
|
assertThat(result).hasSize(1);
|
||||||
|
assertThat(Arrays.asList((Object[]) result.get(0)))
|
||||||
|
.containsExactly(BigDecimal.valueOf(100).setScale(2), "INVALIDCURRENCY")
|
||||||
|
.inOrder();
|
||||||
|
IllegalCurrencyException thrown =
|
||||||
|
assertThrows(
|
||||||
|
IllegalCurrencyException.class,
|
||||||
|
() ->
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() ->
|
||||||
|
jpaTm()
|
||||||
|
.getEntityManager()
|
||||||
|
.createQuery("SELECT money FROM TestEntity WHERE name = 'id'")
|
||||||
|
.getResultList()));
|
||||||
|
assertThat(thrown).hasMessageThat().isEqualTo("Unknown currency 'INVALIDCURRENCY'");
|
||||||
|
}
|
||||||
|
|
||||||
// Override entity name to exclude outer-class name in table name. Not necessary if class is not
|
// Override entity name to exclude outer-class name in table name. Not necessary if class is not
|
||||||
// inner class.
|
// inner class.
|
||||||
@Entity(name = "TestEntity")
|
@Entity(name = "TestEntity")
|
||||||
|
|
|
@ -65,13 +65,20 @@ class google.registry.model.billing.BillingEvent$Recurring {
|
||||||
@Id java.lang.Long id;
|
@Id java.lang.Long id;
|
||||||
@Parent com.googlecode.objectify.Key<google.registry.model.domain.DomainHistory> parent;
|
@Parent com.googlecode.objectify.Key<google.registry.model.domain.DomainHistory> parent;
|
||||||
google.registry.model.billing.BillingEvent$Reason reason;
|
google.registry.model.billing.BillingEvent$Reason reason;
|
||||||
|
google.registry.model.billing.BillingEvent$RenewalPriceBehavior renewalPriceBehavior;
|
||||||
google.registry.model.common.TimeOfYear recurrenceTimeOfYear;
|
google.registry.model.common.TimeOfYear recurrenceTimeOfYear;
|
||||||
java.lang.String clientId;
|
java.lang.String clientId;
|
||||||
java.lang.String targetId;
|
java.lang.String targetId;
|
||||||
java.util.Set<google.registry.model.billing.BillingEvent$Flag> flags;
|
java.util.Set<google.registry.model.billing.BillingEvent$Flag> flags;
|
||||||
|
org.joda.money.Money renewalPrice;
|
||||||
org.joda.time.DateTime eventTime;
|
org.joda.time.DateTime eventTime;
|
||||||
org.joda.time.DateTime recurrenceEndTime;
|
org.joda.time.DateTime recurrenceEndTime;
|
||||||
}
|
}
|
||||||
|
enum google.registry.model.billing.BillingEvent$RenewalPriceBehavior {
|
||||||
|
DEFAULT;
|
||||||
|
NONPREMIUM;
|
||||||
|
SPECIFIED;
|
||||||
|
}
|
||||||
class google.registry.model.common.Cursor {
|
class google.registry.model.common.Cursor {
|
||||||
@Id java.lang.String id;
|
@Id java.lang.String id;
|
||||||
@Parent com.googlecode.objectify.Key<google.registry.model.common.EntityGroupRoot> parent;
|
@Parent com.googlecode.objectify.Key<google.registry.model.common.EntityGroupRoot> parent;
|
||||||
|
|
|
@ -79,6 +79,9 @@
|
||||||
domain_name text not null,
|
domain_name text not null,
|
||||||
recurrence_end_time timestamptz,
|
recurrence_end_time timestamptz,
|
||||||
recurrence_time_of_year text,
|
recurrence_time_of_year text,
|
||||||
|
renewal_price_amount numeric(19, 2),
|
||||||
|
renewal_price_currency text,
|
||||||
|
renewal_price_behavior text not null,
|
||||||
primary key (billing_recurrence_id)
|
primary key (billing_recurrence_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue