Add syntheticCreationTime to BillingEvent.OneTime

In order to clean up potentially bad BillingEvent.Recurring expansions, we'll need to be able to trace synthetic billing events back to particular runs of the []. This field will be set to the cursor time at the start of the MR (all expansions in one MR job will have the same timestamp).
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=121999938
This commit is contained in:
ctingue 2016-05-10 16:16:04 -07:00 committed by Justine Tunney
parent d62da7bb19
commit 51362722cd
4 changed files with 76 additions and 18 deletions

View file

@ -216,6 +216,15 @@ public abstract class BillingEvent extends ImmutableObject
@IgnoreSave(IfNull.class)
Integer periodYears = null;
/**
* For {@link Flag#SYNTHETIC} events, when this event was persisted to datastore (i.e. the
* cursor position at the time the recurrence expansion job was last run). In the event a job
* needs to be undone, a query on this field will return the complete set of potentially bad
* events.
*/
@Index
DateTime syntheticCreationTime;
public Money getCost() {
return cost;
}
@ -228,6 +237,10 @@ public abstract class BillingEvent extends ImmutableObject
return periodYears;
}
public DateTime getSyntheticCreationTime() {
return syntheticCreationTime;
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@ -259,6 +272,11 @@ public abstract class BillingEvent extends ImmutableObject
return this;
}
public Builder setSyntheticCreationTime(DateTime syntheticCreationTime) {
getInstance().syntheticCreationTime = syntheticCreationTime;
return this;
}
@Override
public OneTime build() {
OneTime instance = getInstance();
@ -270,6 +288,10 @@ public abstract class BillingEvent extends ImmutableObject
checkState(
reasonsWithPeriods.contains(instance.reason) == (instance.periodYears != null),
"Period years must be set if and only if reason is CREATE, RENEW, or TRANSFER.");
checkState(
instance.getFlags().contains(Flag.SYNTHETIC)
== (instance.syntheticCreationTime != null),
"Billing events with SYNTHETIC flag set must have a synthetic creation time.");
return super.build();
}
}

View file

@ -55,6 +55,7 @@ public class BillingEventTest extends EntityTestCase {
HistoryEntry historyEntry2;
DomainResource domain;
BillingEvent.OneTime oneTime;
BillingEvent.OneTime oneTimeSynthetic;
BillingEvent.Recurring recurring;
BillingEvent.Cancellation cancellationOneTime;
BillingEvent.Cancellation cancellationRecurring;
@ -84,6 +85,16 @@ public class BillingEventTest extends EntityTestCase {
.setCost(Money.of(USD, 1))
.setEventTime(now)
.setBillingTime(now.plusDays(5))));
oneTimeSynthetic = persistResource(commonInit(
new BillingEvent.OneTime.Builder()
.setParent(historyEntry)
.setReason(Reason.CREATE)
.setFlags(ImmutableSet.of(BillingEvent.Flag.ANCHOR_TENANT, BillingEvent.Flag.SYNTHETIC))
.setSyntheticCreationTime(now.plusDays(10))
.setPeriodYears(2)
.setCost(Money.of(USD, 1))
.setEventTime(now)
.setBillingTime(now.plusDays(5))));
recurring = persistResource(commonInit(
new BillingEvent.Recurring.Builder()
.setParent(historyEntry)
@ -136,7 +147,7 @@ public class BillingEventTest extends EntityTestCase {
// Note that these are all tested separately because BillingEvent is an abstract base class that
// lacks the @Entity annotation, and thus we cannot call .type(BillingEvent.class)
assertThat(ofy().load().type(BillingEvent.OneTime.class).ancestor(domain).list())
.containsExactly(oneTime);
.containsExactly(oneTime, oneTimeSynthetic);
assertThat(ofy().load().type(BillingEvent.Recurring.class).ancestor(domain).list())
.containsExactly(recurring);
assertThat(ofy().load().type(BillingEvent.Cancellation.class).ancestor(domain).list())
@ -144,7 +155,7 @@ public class BillingEventTest extends EntityTestCase {
assertThat(ofy().load().type(BillingEvent.Modification.class).ancestor(domain).list())
.containsExactly(modification);
assertThat(ofy().load().type(BillingEvent.OneTime.class).ancestor(historyEntry).list())
.containsExactly(oneTime);
.containsExactly(oneTime, oneTimeSynthetic);
assertThat(ofy().load().type(BillingEvent.Recurring.class).ancestor(historyEntry).list())
.containsExactly(recurring);
assertThat(ofy().load().type(BillingEvent.Cancellation.class).ancestor(historyEntry2).list())
@ -155,13 +166,35 @@ public class BillingEventTest extends EntityTestCase {
@Test
public void testIndexing() throws Exception {
verifyIndexing(oneTime, "clientId", "eventTime", "billingTime");
verifyIndexing(oneTime, "clientId", "eventTime", "billingTime", "syntheticCreationTime");
verifyIndexing(
oneTimeSynthetic, "clientId", "eventTime", "billingTime", "syntheticCreationTime");
verifyIndexing(
recurring, "clientId", "eventTime", "recurrenceEndTime", "recurrenceTimeOfYear.timeString");
verifyIndexing(cancellationOneTime, "clientId", "eventTime", "billingTime");
verifyIndexing(modification, "clientId", "eventTime");
}
@Test
public void testFailure_syntheticFlagWithoutCreationTime() {
thrown.expect(
IllegalStateException.class,
"Billing events with SYNTHETIC flag set must have a synthetic creation time");
oneTime.asBuilder()
.setFlags(ImmutableSet.of(BillingEvent.Flag.SYNTHETIC))
.build();
}
@Test
public void testFailure_syntheticCreationTimeWithoutFlag() {
thrown.expect(
IllegalStateException.class,
"Billing events with SYNTHETIC flag set must have a synthetic creation time");
oneTime.asBuilder()
.setSyntheticCreationTime(now.plusDays(10))
.build();
}
@Test
public void testSuccess_cancellation_forGracePeriod_withOneTime() {
BillingEvent.Cancellation newCancellation = BillingEvent.Cancellation.forGracePeriod(

View file

@ -25,28 +25,28 @@ public class DeleteDomainCommandTest extends EppToolCommandTestCase<DeleteDomain
public void testSuccess() throws Exception {
runCommand("--client=NewRegistrar", "--domain_name=example.tld", "--force",
"--reason=Test");
new EppVerifier().verifySent("testdata/domain_delete.xml");
eppVerifier().verifySent("testdata/domain_delete.xml");
}
@Test
public void testSuccess_multipleWordReason() throws Exception {
runCommand("--client=NewRegistrar", "--domain_name=example.tld", "--force",
"--reason=\"Test test\"");
new EppVerifier().verifySent("testdata/domain_delete_multiple_word_reason.xml");
eppVerifier().verifySent("testdata/domain_delete_multiple_word_reason.xml");
}
@Test
public void testSuccess_requestedByRegistrarFalse() throws Exception {
runCommand("--client=NewRegistrar", "--domain_name=example.tld", "--force",
"--reason=Test", "--registrar_request=false");
new EppVerifier().verifySent("testdata/domain_delete.xml");
eppVerifier().verifySent("testdata/domain_delete.xml");
}
@Test
public void testSuccess_requestedByRegistrarTrue() throws Exception {
runCommand("--client=NewRegistrar", "--domain_name=example.tld", "--force",
"--reason=Test", "--registrar_request=true");
new EppVerifier().verifySent("testdata/domain_delete_by_registrar.xml");
eppVerifier().verifySent("testdata/domain_delete_by_registrar.xml");
}
@Test

View file

@ -67,29 +67,32 @@ public abstract class EppToolCommandTestCase<C extends EppToolCommand> extends C
/** Helper to get a new {@link EppVerifier} instance. */
EppVerifier eppVerifier() {
return new EppVerifier();
return new EppVerifier("NewRegistrar", false, false);
}
/** Builder pattern class for verifying EPP commands sent to the server. */
/** Class for verifying EPP commands sent to the server. */
class EppVerifier {
String clientIdentifier = "NewRegistrar";
boolean superuser = false;
boolean dryRun = false;
private final String clientIdentifier;
private final boolean superuser;
private final boolean dryRun;
private EppVerifier(String clientIdentifier, boolean superuser, boolean dryRun) {
this.clientIdentifier = clientIdentifier;
this.superuser = superuser;
this.dryRun = dryRun;
}
EppVerifier setClientIdentifier(String clientIdentifier) {
this.clientIdentifier = clientIdentifier;
return this;
return new EppVerifier(clientIdentifier, superuser, dryRun);
}
EppVerifier asSuperuser() {
this.superuser = true;
return this;
return new EppVerifier(clientIdentifier, true, dryRun);
}
EppVerifier asDryRun() {
this.dryRun = true;
return this;
return new EppVerifier(clientIdentifier, superuser, true);
}
void verifySent(String... filesToMatch) throws Exception {