killbill-memoizeit
Changes
entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java 10(+8 -2)
subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java 75(+74 -1)
subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionDataIterator.java 2(+0 -2)
subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java 7(+6 -1)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java 25(+17 -8)
subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java 5(+5 -0)
subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg 13(+13 -0)
subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java 12(+9 -3)
Details
diff --git a/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java b/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java
index 301d6f0..2f98acc 100644
--- a/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java
+++ b/entitlement/src/test/java/org/killbill/billing/entitlement/engine/core/TestEntitlementUtils.java
@@ -354,8 +354,14 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
Assert.assertEquals(entitlementApi.getEntitlementForId(addOnEntitlement.getId(), callContext).getEffectiveEndDate(), addOn1CancellationDate);
Assert.assertEquals(entitlementApi.getEntitlementForId(addOn2Entitlement.getId(), callContext).getEffectiveEndDate(), baseCancellationDate);
- testListener.pushExpectedEvents(NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK);
- clock.setDay(new LocalDate(2013, 10, 30));
+ // Move to addOn1CancellationDate
+ testListener.pushExpectedEvents(NextEvent.CANCEL, NextEvent.BLOCK);
+ clock.setDay(new LocalDate(2013, 9, 9));
+ assertListenerStatus();
+
+
+ testListener.pushExpectedEvents(NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.BLOCK, NextEvent.BLOCK);
+ clock.setDay(new LocalDate(2013, 10, 10));
assertListenerStatus();
// Verify the cancellation dates
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
index 3db0cd5..90a581e 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/DefaultSubscriptionBase.java
@@ -20,6 +20,8 @@ package org.killbill.billing.subscription.api.user;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
@@ -60,6 +62,9 @@ import org.killbill.clock.Clock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+
public class DefaultSubscriptionBase extends EntityBase implements SubscriptionBase {
private static final Logger log = LoggerFactory.getLogger(DefaultSubscriptionBase.class);
@@ -343,6 +348,7 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
final SubscriptionBaseTransitionDataIterator it = new SubscriptionBaseTransitionDataIterator(
clock, transitions, Order.DESC_FROM_FUTURE,
Visibility.FROM_DISK_ONLY, TimeLimit.PAST_OR_PRESENT_ONLY);
+
return it.hasNext() ? it.next() : null;
}
@@ -574,6 +580,8 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
this.events = inputEvents;
+ filterOutDuplicateCancelEvents(events);
+
UUID nextUserToken = null;
UUID nextEventId = null;
@@ -599,8 +607,8 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
continue;
}
- ApiEventType apiEventType = null;
+ ApiEventType apiEventType = null;
boolean isFromDisk = true;
nextEventId = cur.getId();
@@ -690,6 +698,71 @@ public class DefaultSubscriptionBase extends EntityBase implements SubscriptionB
prevEventId = nextEventId;
prevCreatedDate = nextCreatedDate;
previousBillingCycleDayLocal = nextBillingCycleDayLocal;
+
+ }
+ }
+
+ //
+ // Hardening against data integrity issues where we have multiple active CANCEL (See #619):
+ // We skip any cancel events after the first one (subscription cannot be cancelled multiple times).
+ // The code should prevent such cases from happening but because of #619, some invalid data could be there so to be safe we added this code
+ //
+ // Also we remove !onDisk cancel events when there is an onDisk cancel event (can happen during the path where we process the base plan cancel notification, and are
+ // in the process of adding the new cancel events for the AO)
+ //
+ private void filterOutDuplicateCancelEvents(final List<SubscriptionBaseEvent> inputEvents) {
+
+ Collections.sort(inputEvents, new Comparator<SubscriptionBaseEvent>() {
+ @Override
+ public int compare(final SubscriptionBaseEvent o1, final SubscriptionBaseEvent o2) {
+ int res = o1.getEffectiveDate().compareTo(o2.getEffectiveDate());
+ if (res == 0) {
+ res = o1.getTotalOrdering() < (o2.getTotalOrdering()) ? -1 : 1;
+ }
+ return res;
+ }
+ });
+
+ final boolean isCancelled = Iterables.any(inputEvents, new Predicate<SubscriptionBaseEvent>() {
+ @Override
+ public boolean apply(final SubscriptionBaseEvent input) {
+ if (input.isActive() && input.getType() == EventType.API_USER) {
+ final ApiEvent userEV = (ApiEvent) input;
+ if (userEV.getApiEventType() == ApiEventType.CANCEL && userEV.isFromDisk()) {
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+
+ if (!isCancelled) {
+ return;
+ }
+
+
+ boolean foundFirstOnDiskCancel = false;
+ final Iterator<SubscriptionBaseEvent> it = inputEvents.iterator();
+ while(it.hasNext()) {
+ final SubscriptionBaseEvent input = it.next();
+ if (!input.isActive()) {
+ continue;
+ }
+
+ if (input.getType() == EventType.API_USER) {
+ final ApiEvent userEV = (ApiEvent) input;
+ if (userEV.getApiEventType() == ApiEventType.CANCEL) {
+ if (userEV.isFromDisk()) {
+ if (!foundFirstOnDiskCancel) {
+ foundFirstOnDiskCancel = true;
+ } else {
+ it.remove();
+ }
+ } else {
+ it.remove();
+ }
+ }
+ }
}
}
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionDataIterator.java b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionDataIterator.java
index 4871f6a..e7acad6 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionDataIterator.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/api/user/SubscriptionBaseTransitionDataIterator.java
@@ -29,7 +29,6 @@ public class SubscriptionBaseTransitionDataIterator implements Iterator<Subscrip
private final Iterator<SubscriptionBaseTransition> it;
private final TimeLimit timeLimit;
private final Visibility visibility;
- private final Order order;
private SubscriptionBaseTransition next;
@@ -55,7 +54,6 @@ public class SubscriptionBaseTransitionDataIterator implements Iterator<Subscrip
this.clock = clock;
this.timeLimit = timeLimit;
this.visibility = visibility;
- this.order = order;
this.next = null;
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
index 820c07f..fd0e273 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/core/DefaultSubscriptionBaseService.java
@@ -153,6 +153,12 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
return;
}
+ final SubscriptionBaseTransitionData transition = subscription.getTransitionFromEvent(event, seqId);
+ if (transition == null) {
+ log.warn("Skipping event ='{}', no matching transition was built", event.getType());
+ return;
+ }
+
boolean eventSent = false;
if (event.getType() == EventType.PHASE) {
eventSent = onPhaseEvent(subscription, event, context);
@@ -165,7 +171,6 @@ public class DefaultSubscriptionBaseService implements EventListener, Subscripti
if (!eventSent) {
// Methods above invoking the DAO will send this event directly from the transaction
- final SubscriptionBaseTransitionData transition = subscription.getTransitionFromEvent(event, seqId);
final BusEvent busEvent = new DefaultEffectiveSubscriptionEvent(transition,
subscription.getAlignStartDate(),
context.getUserToken(),
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
index b39f487..75eabb5 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/DefaultSubscriptionDao.java
@@ -688,8 +688,14 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
}
private void cancelFutureEventsFromTransaction(final UUID subscriptionId, final DateTime effectiveDate, final EntitySqlDaoWrapperFactory entitySqlDaoWrapperFactory, final boolean includingBCDChange, final InternalCallContext context) {
- final List<SubscriptionEventModelDao> eventModels = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class).getFutureActiveEventForSubscription(subscriptionId.toString(), effectiveDate.toDate(), context);
+ final List<SubscriptionEventModelDao> eventModels = entitySqlDaoWrapperFactory.become(SubscriptionEventSqlDao.class).getFutureOrPresentActiveEventForSubscription(subscriptionId.toString(), effectiveDate.toDate(), context);
for (final SubscriptionEventModelDao cur : eventModels) {
+
+ // Skip CREATE event (because of date equality in the query and we don't want to invalidate CREATE event that match a CANCEL event)
+ if (cur.getEventType() == EventType.API_USER && cur.getUserType()== ApiEventType.CREATE) {
+ continue;
+ }
+
if (includingBCDChange || cur.getEventType() != EventType.BCD_UPDATE) {
unactivateEventFromTransaction(cur, entitySqlDaoWrapperFactory, context);
}
@@ -707,6 +713,7 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
SubscriptionEventModelDao futureEvent = null;
final Date now = clock.getUTCNow().toDate();
+
final List<SubscriptionEventModelDao> eventModels = dao.become(SubscriptionEventSqlDao.class).getFutureActiveEventForSubscription(subscriptionId.toString(), now, context);
for (final SubscriptionEventModelDao cur : eventModels) {
if (cur.getEventType() == type &&
@@ -985,13 +992,15 @@ public class DefaultSubscriptionDao extends EntityDaoBase<SubscriptionBundleMode
final SubscriptionBaseEvent immediateEvent, final int seqId, final InternalCallContext context) {
try {
final SubscriptionBaseTransitionData transition = subscription.getTransitionFromEvent(immediateEvent, seqId);
- final BusEvent busEvent = new DefaultEffectiveSubscriptionEvent(transition,
- subscription.getAlignStartDate(),
- context.getUserToken(),
- context.getAccountRecordId(),
- context.getTenantRecordId());
-
- eventBus.postFromTransaction(busEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
+ if (transition != null) {
+ final BusEvent busEvent = new DefaultEffectiveSubscriptionEvent(transition,
+ subscription.getAlignStartDate(),
+ context.getUserToken(),
+ context.getAccountRecordId(),
+ context.getTenantRecordId());
+
+ eventBus.postFromTransaction(busEvent, entitySqlDaoWrapperFactory.getHandle().getConnection());
+ }
} catch (final EventBusException e) {
log.warn("Failed to post effective event for subscriptionId='{}'", subscription.getId(), e);
}
diff --git a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
index 4ead49c..0e2a8ab 100644
--- a/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
+++ b/subscription/src/main/java/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.java
@@ -50,6 +50,11 @@ public interface SubscriptionEventSqlDao extends EntitySqlDao<SubscriptionEventM
@BindBean final InternalTenantContext context);
@SqlQuery
+ public List<SubscriptionEventModelDao> getFutureOrPresentActiveEventForSubscription(@Bind("subscriptionId") String subscriptionId,
+ @Bind("now") Date now,
+ @BindBean final InternalTenantContext context);
+
+ @SqlQuery
public List<SubscriptionEventModelDao> getEventsForSubscription(@Bind("subscriptionId") String subscriptionId,
@BindBean final InternalTenantContext context);
diff --git a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
index 49cca79..4b0aa5a 100644
--- a/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
+++ b/subscription/src/main/resources/org/killbill/billing/subscription/engine/dao/SubscriptionEventSqlDao.sql.stg
@@ -70,6 +70,19 @@ and effective_date > :now
;
>>
+getFutureOrPresentActiveEventForSubscription() ::= <<
+select <allTableFields()>
+, record_id as total_ordering
+from <tableName()>
+where
+subscription_id = :subscriptionId
+and is_active = true
+and effective_date >= :now
+<AND_CHECK_TENANT()>
+<defaultOrderBy()>
+;
+>>
+
getEventsForSubscription() ::= <<
select <allTableFields()>
, record_id as total_ordering
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java b/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java
index 2ed95c3..869e886 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/alignment/TestPlanAligner.java
@@ -16,6 +16,7 @@
package org.killbill.billing.subscription.alignment;
+import java.util.ArrayList;
import java.util.List;
import org.joda.time.DateTime;
@@ -38,7 +39,6 @@ import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
-import com.google.common.collect.ImmutableList;
public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
@@ -159,7 +159,9 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
phaseType,
ApiEventType.CREATE
);
- defaultSubscriptionBase.rebuildTransitions(ImmutableList.<SubscriptionBaseEvent>of(event), catalogService.getFullCatalog(true, true, internalCallContext));
+ final List<SubscriptionBaseEvent> events = new ArrayList<SubscriptionBaseEvent>();
+ events.add(event);
+ defaultSubscriptionBase.rebuildTransitions(events, catalogService.getFullCatalog(true, true, internalCallContext));
Assert.assertEquals(defaultSubscriptionBase.getAllTransitions().size(), 1);
Assert.assertNull(defaultSubscriptionBase.getAllTransitions().get(0).getPreviousPhase());
@@ -184,7 +186,11 @@ public class TestPlanAligner extends SubscriptionTestSuiteNoDB {
ApiEventType.CHANGE
);
- defaultSubscriptionBase.rebuildTransitions(ImmutableList.<SubscriptionBaseEvent>of(previousEvent, event), catalogService.getFullCatalog(true, true, internalCallContext));
+ final List<SubscriptionBaseEvent> events = new ArrayList<SubscriptionBaseEvent>();
+ events.add(previousEvent);
+ events.add(event);
+
+ defaultSubscriptionBase.rebuildTransitions(events, catalogService.getFullCatalog(true, true, internalCallContext));
final List<SubscriptionBaseTransition> newTransitions = defaultSubscriptionBase.getAllTransitions();
Assert.assertEquals(newTransitions.size(), 2);
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
index a3acb50..056208c 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiCancel.java
@@ -16,8 +16,16 @@
package org.killbill.billing.subscription.api.user;
+import java.util.UUID;
+
import org.joda.time.DateTime;
import org.joda.time.Interval;
+import org.killbill.billing.entitlement.api.Entitlement.EntitlementState;
+import org.killbill.billing.entity.EntityPersistenceException;
+import org.killbill.billing.subscription.engine.dao.SubscriptionEventSqlDao;
+import org.killbill.billing.subscription.engine.dao.model.SubscriptionEventModelDao;
+import org.killbill.billing.subscription.events.SubscriptionBaseEvent;
+import org.skife.jdbi.v2.Handle;
import org.testng.Assert;
import org.testng.annotations.Test;
@@ -118,6 +126,10 @@ public class TestUserApiCancel extends SubscriptionTestSuiteWithEmbeddedDB {
subscription.cancel(callContext);
assertListenerStatus();
+ // CANCEL a second time (first pending CANCEL should be made inactive)
+ subscription.cancel(callContext);
+ assertListenerStatus();
+
assertEquals(subscription.getLastActiveProduct().getName(), prod);
assertEquals(subscription.getLastActivePriceList().getName(), planSet);
assertEquals(subscription.getLastActiveBillingPeriod(), term);
@@ -127,10 +139,9 @@ public class TestUserApiCancel extends SubscriptionTestSuiteWithEmbeddedDB {
Assert.assertNotNull(futureEndDate);
// MOVE TO EOT + RECHECK
- testListener.pushExpectedEvent(NextEvent.CANCEL);
+ testListener.pushExpectedEvents(NextEvent.CANCEL);
it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
clock.addDeltaFromReality(it.toDurationMillis());
- final DateTime future = clock.getUTCNow();
assertListenerStatus();
assertTrue(futureEndDate.compareTo(subscription.getEndDate()) == 0);
@@ -258,4 +269,81 @@ public class TestUserApiCancel extends SubscriptionTestSuiteWithEmbeddedDB {
// CANCEL in EVERGREEN period with an invalid Date (prior to the Creation Date)
subscription.cancelWithDate(invalidDate, callContext);
}
+
+
+
+ @Test(groups = "slow")
+ public void testWithMultipleCancellationEvent() throws SubscriptionBillingApiException, SubscriptionBaseApiException {
+ final String prod = "Shotgun";
+ final BillingPeriod term = BillingPeriod.MONTHLY;
+ final String planSet = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+ // CREATE
+ DefaultSubscriptionBase subscription = testUtil.createSubscription(bundle, prod, term, planSet);
+ PlanPhase trialPhase = subscription.getCurrentPhase();
+ assertEquals(trialPhase.getPhaseType(), PhaseType.TRIAL);
+
+ // NEXT PHASE
+ final DateTime expectedPhaseTrialChange = TestSubscriptionHelper.addDuration(subscription.getStartDate(), trialPhase.getDuration());
+ testUtil.checkNextPhaseChange(subscription, 1, expectedPhaseTrialChange);
+
+ // MOVE TO NEXT PHASE
+ testListener.pushExpectedEvent(NextEvent.PHASE);
+ Interval it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusDays(31));
+ clock.addDeltaFromReality(it.toDurationMillis());
+
+ assertListenerStatus();
+ trialPhase = subscription.getCurrentPhase();
+ assertEquals(trialPhase.getPhaseType(), PhaseType.EVERGREEN);
+
+ // SET CTD + RE READ SUBSCRIPTION + CHANGE PLAN
+ final Duration ctd = testUtil.getDurationMonth(1);
+ final DateTime newChargedThroughDate = TestSubscriptionHelper.addDuration(expectedPhaseTrialChange, ctd);
+ subscriptionInternalApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate, internalCallContext);
+ subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+
+ assertEquals(subscription.getLastActiveProduct().getName(), prod);
+ assertEquals(subscription.getLastActivePriceList().getName(), planSet);
+ assertEquals(subscription.getLastActiveBillingPeriod(), term);
+ assertEquals(subscription.getLastActiveCategory(), ProductCategory.BASE);
+
+ // CANCEL
+ subscription.cancel(callContext);
+ assertListenerStatus();
+
+ subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ Assert.assertEquals(subscription.getAllTransitions().size(), 3);
+
+
+ // Manually add a CANCEL event on the same EOT date as the previous one to verify the code is resilient enough to ignore it
+ final SubscriptionBaseEvent cancelEvent = subscription.getEvents().get(subscription.getEvents().size() - 1);
+ final SubscriptionEventModelDao newCancelEvent = new SubscriptionEventModelDao(cancelEvent);
+ newCancelEvent.setId(UUID.randomUUID());
+
+ final Handle handle = dbi.open();
+ final SubscriptionEventSqlDao sqlDao = handle.attach(SubscriptionEventSqlDao.class);
+ try {
+ sqlDao.create(newCancelEvent, internalCallContext);
+ } catch (EntityPersistenceException e) {
+ Assert.fail(e.getMessage());
+ }
+
+ subscription = (DefaultSubscriptionBase) subscriptionInternalApi.getSubscriptionFromId(subscription.getId(), internalCallContext);
+ // The extra cancel event is being ignored
+ Assert.assertEquals(subscription.getEvents().size(), 3);
+ Assert.assertEquals(subscription.getAllTransitions().size(), 3);
+
+
+ // We expect only one CANCEL event, this other one is skipped
+ testListener.pushExpectedEvents(NextEvent.CANCEL);
+ it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
+ clock.addDeltaFromReality(it.toDurationMillis());
+ assertListenerStatus();
+
+ // Our previous transition should be a CANCEL with a valid previous plan
+ final SubscriptionBaseTransition previousTransition = subscription.getPreviousTransition();
+ Assert.assertEquals(previousTransition.getPreviousState(), EntitlementState.ACTIVE);
+ Assert.assertNotNull(previousTransition.getPreviousPlan());
+
+ }
}
diff --git a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
index 26eb6ff..58fe498 100644
--- a/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
+++ b/subscription/src/test/java/org/killbill/billing/subscription/api/user/TestUserApiChangePlan.java
@@ -350,7 +350,7 @@ public class TestUserApiChangePlan extends SubscriptionTestSuiteWithEmbeddedDB {
assertEquals(currentPhase.getPhaseType(), PhaseType.DISCOUNT);
// ACTIVATE CHANGE BY MOVING AFTER CTD
- testListener.pushExpectedEvents(NextEvent.CHANGE, NextEvent.CHANGE);
+ testListener.pushExpectedEvents(NextEvent.CHANGE);
it = new Interval(clock.getUTCNow(), clock.getUTCNow().plusMonths(1));
clock.addDeltaFromReality(it.toDurationMillis());
assertListenerStatus();