killbill-aplcache

More addon functionality (incomplete)

12/30/2011 10:49:28 PM

Details

diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java
index dce8c99..541c57c 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/alignment/PlanAligner.java
@@ -135,7 +135,7 @@ public class PlanAligner  {
                         lastPlanTransition.getNextPhase().getPhaseType(),
                         lastPlanTransition.getNextPriceList());
                 return getTimedPhase(timedPhases, effectiveDate, WhichPhase.NEXT);
-            // If we went through Plan changes, borrow the logics for chnagePlan alignement
+            // If we went through Plan changes, borrow the logics for changePlan alignement
             case CHANGE:
                 return getTimedPhaseOnChange(subscription.getStartDate(),
                         subscription.getBundleStartDate(),
@@ -149,7 +149,7 @@ public class PlanAligner  {
                 throw new EntitlementError(String.format("Unexpectd initial transition %s for current plan %s on subscription %s",
                         lastPlanTransition.getTransitionType(), subscription.getCurrentPlan(), subscription.getId()));
             }
-        } catch (Exception /*EntitlementUserApiException, CatalogApiException */ e) {
+        } catch (Exception /* EntitlementUserApiException, CatalogApiException */ e) {
             throw new EntitlementError(String.format("Could not compute next phase change for subscription %s", subscription.getId()), e);
         }
     }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
index d252278..ebee20e 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
@@ -30,6 +30,7 @@ import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
+import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.util.clock.Clock;
@@ -41,14 +42,17 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
     private final EntitlementDao dao;
     private final CatalogService catalogService;
     private final SubscriptionApiService apiService;
+    private final AddonUtils addonUtils;
 
     @Inject
-    public DefaultEntitlementUserApi(Clock clock, EntitlementDao dao, CatalogService catalogService, SubscriptionApiService apiService) {
+    public DefaultEntitlementUserApi(Clock clock, EntitlementDao dao, CatalogService catalogService,
+            SubscriptionApiService apiService, AddonUtils addonUtils) {
         super();
         this.clock = clock;
         this.apiService = apiService;
         this.dao = dao;
         this.catalogService = catalogService;
+        this.addonUtils = addonUtils;
     }
 
     @Override
@@ -159,21 +163,14 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
             throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_BP_NON_ACTIVE, targetAddOnPlan.getName());
         }
 
-        Product targetAddonProduct = targetAddOnPlan.getProduct();
-        Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
-
-        Product [] includedAddOns = baseProduct.getIncluded();
-        for (Product curInc : includedAddOns) {
-            if (curInc.getName().equals(targetAddonProduct.getName())) {
-                throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_ALREADY_INCLUDED, targetAddOnPlan.getName(), baseProduct.getName());
-            }
+        if (addonUtils.isAddonIncluded(baseSubscription, targetAddOnPlan)) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_ALREADY_INCLUDED,
+                    targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
         }
-        Product[] availableAddOns = baseProduct.getAvailable();
-        for (Product curAv : availableAddOns) {
-            if (curAv.getName().equals(targetAddonProduct.getName())) {
-                return;
-            }
+
+        if (!addonUtils.isAddonAvailable(baseSubscription, targetAddOnPlan)) {
+            throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_NOT_AVAILABLE,
+                    targetAddOnPlan.getName(), baseSubscription.getCurrentPlan().getProduct().getName());
         }
-        throw new EntitlementUserApiException(ErrorCode.ENT_CREATE_AO_NOT_AVAILABLE, targetAddOnPlan.getName(), baseProduct.getName());
     }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java
index 8bdd584..8cc2573 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionBundleData.java
@@ -54,6 +54,7 @@ public class SubscriptionBundleData implements SubscriptionBundle {
         return accountId;
     }
 
+
     // STEPH do we need it ? and should we return that and when is that populated/updated?
     @Override
     public DateTime getStartDate() {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
index 40132b8..8789537 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/SubscriptionData.java
@@ -18,6 +18,7 @@ package com.ning.billing.entitlement.api.user;
 
 import com.ning.billing.ErrorCode;
 import com.ning.billing.catalog.api.*;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.entitlement.api.user.SubscriptionFactory.SubscriptionBuilder;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
@@ -231,15 +232,6 @@ public class SubscriptionData implements Subscription {
         return paidThroughDate;
     }
 
-    /*
-    public DateTime getCurrentPlanStart() {
-        return getInitialTransitionForCurrentPlan().getEffectiveTransitionTime();
-    }
-
-    public PlanPhase getInitialPhaseOnCurrentPlan() {
-        return getInitialTransitionForCurrentPlan().getNextPhase();
-    }
-*/
     public SubscriptionTransitionData getInitialTransitionForCurrentPlan() {
         if (transitions == null) {
             throw new EntitlementError(String.format("No transitions for subscription %s", getId()));
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
new file mode 100644
index 0000000..66635c3
--- /dev/null
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at:
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.ning.billing.entitlement.engine.addon;
+
+import com.google.inject.Inject;
+import com.ning.billing.ErrorCode;
+import com.ning.billing.catalog.api.CatalogApiException;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.Product;
+import com.ning.billing.entitlement.api.user.Subscription;
+
+public class AddonUtils {
+
+
+    private final CatalogService catalogService;
+
+    @Inject
+    public AddonUtils(CatalogService catalogService) {
+        this.catalogService = catalogService;
+    }
+
+    public boolean isAddonAvailable(Subscription baseSubscription, Plan targetAddOnPlan) {
+
+        Product targetAddonProduct = targetAddOnPlan.getProduct();
+        Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
+        Product[] availableAddOns = baseProduct.getAvailable();
+
+        for (Product curAv : availableAddOns) {
+            if (curAv.getName().equals(targetAddonProduct.getName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean isAddonIncluded(Subscription baseSubscription, Plan targetAddOnPlan) {
+        Product targetAddonProduct = targetAddOnPlan.getProduct();
+        Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
+
+        Product[] includedAddOns = baseProduct.getIncluded();
+        for (Product curAv : includedAddOns) {
+            if (curAv.getName().equals(targetAddonProduct.getName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
index e6f61e7..f03bcbc 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
@@ -16,11 +16,17 @@
 
 package com.ning.billing.entitlement.engine.core;
 
+import java.util.Iterator;
+import java.util.List;
+
 import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
+import com.ning.billing.catalog.api.CatalogService;
+import com.ning.billing.catalog.api.Plan;
+import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.config.EntitlementConfig;
 import com.ning.billing.entitlement.alignment.PlanAligner;
 import com.ning.billing.entitlement.alignment.TimedPhase;
@@ -33,12 +39,18 @@ import com.ning.billing.entitlement.api.test.DefaultEntitlementTestApi;
 import com.ning.billing.entitlement.api.test.EntitlementTestApi;
 import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
+import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.entitlement.engine.dao.EntitlementDao;
 import com.ning.billing.entitlement.events.EntitlementEvent;
 import com.ning.billing.entitlement.events.EntitlementEvent.EventType;
 import com.ning.billing.entitlement.events.phase.PhaseEvent;
 import com.ning.billing.entitlement.events.phase.PhaseEventData;
+import com.ning.billing.entitlement.events.user.ApiEvent;
+import com.ning.billing.entitlement.events.user.ApiEventBuilder;
+import com.ning.billing.entitlement.events.user.ApiEventCancel;
 import com.ning.billing.entitlement.exceptions.EntitlementError;
 import com.ning.billing.lifecycle.LifecycleHandlerType;
 import com.ning.billing.lifecycle.LifecycleHandlerType.LifecycleLevel;
@@ -64,6 +76,8 @@ public class Engine implements EventListener, EntitlementService {
     private final EntitlementBillingApi billingApi;
     private final EntitlementTestApi testApi;
     private final EntitlementMigrationApi migrationApi;
+    private final CatalogService catalogService;
+    private final AddonUtils addonUtils;
     private final EventBus eventBus;
 
     private boolean startedNotificationThread;
@@ -72,7 +86,8 @@ public class Engine implements EventListener, EntitlementService {
     public Engine(Clock clock, EntitlementDao dao, EventNotifier apiEventProcessor,
             PlanAligner planAligner, EntitlementConfig config, DefaultEntitlementUserApi userApi,
             DefaultEntitlementBillingApi billingApi, DefaultEntitlementTestApi testApi,
-            DefaultEntitlementMigrationApi migrationApi, EventBus eventBus) {
+            DefaultEntitlementMigrationApi migrationApi, CatalogService catalogService,
+            AddonUtils addonUtils, EventBus eventBus) {
         super();
         this.clock = clock;
         this.dao = dao;
@@ -82,6 +97,8 @@ public class Engine implements EventListener, EntitlementService {
         this.testApi = testApi;
         this.billingApi = billingApi;
         this.migrationApi = migrationApi;
+        this.catalogService = catalogService;
+        this.addonUtils = addonUtils;
         this.eventBus = eventBus;
 
         this.startedNotificationThread = false;
@@ -138,8 +155,14 @@ public class Engine implements EventListener, EntitlementService {
             log.warn("Failed to retrieve subscription for id %s", event.getSubscriptionId());
             return;
         }
+        //
+        // Do any internal processing on that event before we send the event to the bus
+        //
         if (event.getType() == EventType.PHASE) {
-            insertNextPhaseEvent(subscription);
+            onPhaseEvent(subscription);
+        } else if (event.getType() == EventType.API_USER &&
+                subscription.getCategory() == ProductCategory.BASE) {
+            onBasePlanEvent(subscription, event);
         }
         try {
             eventBus.post(subscription.getTransitionFromEvent(event));
@@ -184,7 +207,7 @@ public class Engine implements EventListener, EntitlementService {
         }
     }
 
-    private void insertNextPhaseEvent(SubscriptionData subscription) {
+    private void onPhaseEvent(SubscriptionData subscription) {
         try {
             DateTime now = clock.getUTCNow();
             TimedPhase nextTimedPhase = planAligner.getNextTimedPhase(subscription, now);
@@ -199,4 +222,32 @@ public class Engine implements EventListener, EntitlementService {
         }
     }
 
+    private void onBasePlanEvent(SubscriptionData baseSubscription, EntitlementEvent event) {
+
+        DateTime now = clock.getUTCNow();
+
+        List<Subscription> subscriptions = dao.getSubscriptions(baseSubscription.getId());
+        Iterator<Subscription> it = subscriptions.iterator();
+        while (it.hasNext()) {
+            SubscriptionData cur = (SubscriptionData) it.next();
+            if (cur.getState() == SubscriptionState.CANCELLED ||
+                    cur.getCategory() != ProductCategory.ADD_ON) {
+                continue;
+            }
+            Plan addonCurrentPlan = cur.getCurrentPlan();
+            if (addonUtils.isAddonIncluded(baseSubscription, addonCurrentPlan) ||
+                    ! addonUtils.isAddonAvailable(baseSubscription, addonCurrentPlan)) {
+                //
+                // Perform AO cancellation using the effectiveDate of the BP
+                //
+                EntitlementEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder()
+                .setSubscriptionId(cur.getId())
+                .setActiveVersion(cur.getActiveVersion())
+                .setProcessedDate(now)
+                .setEffectiveDate(event.getEffectiveDate())
+                .setRequestedDate(now));
+                dao.cancelSubscription(cur.getId(), cancelEvent);
+            }
+        }
+    }
 }
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
index 016f005..a987fe6 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/EntitlementSqlDao.java
@@ -243,6 +243,7 @@ public class EntitlementSqlDao implements EntitlementDao {
             @Override
             public Void inTransaction(EventSqlDao dao,
                     TransactionStatus status) throws Exception {
+                // STEPH what about future cancel events : seems like this is missing?
                 cancelNextChangeEventFromTransaction(subscriptionId, dao);
                 cancelNextPhaseEventFromTransaction(subscriptionId, dao);
                 dao.insertEvent(cancelEvent);
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
index ab04700..1098c15 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/glue/EntitlementModule.java
@@ -30,6 +30,7 @@ import com.ning.billing.entitlement.api.test.EntitlementTestApi;
 import com.ning.billing.entitlement.api.user.DefaultEntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.SubscriptionApiService;
+import com.ning.billing.entitlement.engine.addon.AddonUtils;
 import com.ning.billing.entitlement.engine.core.DefaultApiEventProcessor;
 import com.ning.billing.entitlement.engine.core.Engine;
 import com.ning.billing.entitlement.engine.core.EventNotifier;
@@ -66,6 +67,7 @@ public class EntitlementModule extends AbstractModule {
         bind(EntitlementService.class).to(Engine.class).asEagerSingleton();
         bind(Engine.class).asEagerSingleton();
         bind(PlanAligner.class).asEagerSingleton();
+        bind(AddonUtils.class).asEagerSingleton();
         bind(MigrationPlanAligner.class).asEagerSingleton();
         bind(EntitlementTestApi.class).to(DefaultEntitlementTestApi.class).asEagerSingleton();
         bind(EntitlementUserApi.class).to(DefaultEntitlementUserApi.class).asEagerSingleton();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
index a2f3532..a6536f4 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
@@ -40,7 +40,10 @@ import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.TestApiBase;
 import com.ning.billing.entitlement.api.ApiTestListener.NextEvent;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition.SubscriptionTransitionType;
 import com.ning.billing.entitlement.glue.MockEngineModuleSql;
+import com.ning.billing.util.clock.DefaultClock;
 
 public class TestUserApiAddOn extends TestApiBase {
 
@@ -49,6 +52,76 @@ public class TestUserApiAddOn extends TestApiBase {
         return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
     }
 
+
+    @Test(enabled=true, groups={"sql"})
+    public void testCancelBPWthAddon() {
+        try {
+
+            String baseProduct = "Shotgun";
+            BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+            String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+            // CREATE BP
+            SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+            String aoProduct = "Telescopic-Scope";
+            BillingPeriod aoTerm = BillingPeriod.MONTHLY;
+            String aoPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+            DateTime beforeAOCreation = clock.getUTCNow();
+            SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
+            DateTime afterAOCreation = clock.getUTCNow();
+
+            testListener.reset();
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+
+            // MOVE CLOCK AFTER TRIAL + AO DISCOUNT
+            Duration twoMonths = getDurationMonth(2);
+            clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
+            assertTrue(testListener.isCompleted(5000));
+
+            // SET CTD TO CANCEL IN FUTURE
+            DateTime now = clock.getUTCNow();
+            Duration ctd = getDurationMonth(1);
+            DateTime newChargedThroughDate = DefaultClock.addDuration(now, ctd);
+            billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate);
+            baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+            // FUTURE CANCELLATION
+            baseSubscription.cancel(now, false);
+
+            // REFETCH AO SUBSCRIPTION AND CHECK THIS IS FUTURE CANCELLED
+            aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+            assertEquals(aoSubscription.getState(), SubscriptionState.ACTIVE);
+
+
+            /*
+             * STEPH not true because this will only happen when CANCEL event is being processed
+             * => isFutureCancelled is broken for AO
+                SubscriptionTransition aaPendingSubscription = aoSubscription.getPendingTransition();
+                assertNotNull(aaPendingSubscription);
+                assertEquals(aaPendingSubscription.getTransitionType(), SubscriptionTransitionType.CANCEL);
+             */
+
+            // MOVE AFTER CANCELLATION
+            testListener.reset();
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            clock.addDeltaFromReality(ctd);
+            now = clock.getUTCNow();
+            assertTrue(testListener.isCompleted(5000));
+
+            // REFETCH AO SUBSCRIPTION AND CHECK THIS IS FUTURE CANCELLED
+            aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+            assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
+
+
+        } catch (Exception e) {
+            Assert.fail(e.getMessage());
+        }
+    }
+
+
     @Test(enabled=true, groups={"sql"})
     public void testAddonCreateWithBundleAlign() {
         try {
@@ -118,7 +191,7 @@ public class TestUserApiAddOn extends TestApiBase {
             Duration someTimeLater = getDurationDay(13);
             clock.setDeltaFromReality(someTimeLater, DAY_IN_MS);
 
-            // CREATE ADDON (ALIGN BUNDLE)
+            // CREATE ADDON
             DateTime beforeAOCreation = clock.getUTCNow();
             SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
             DateTime afterAOCreation = clock.getUTCNow();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
index fef6e3c..9917228 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiError.java
@@ -36,6 +36,7 @@ import java.util.UUID;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
 
 public class TestUserApiError extends TestApiBase {
 
@@ -157,13 +158,21 @@ public class TestUserApiError extends TestApiBase {
     public void testChangeSubscriptionFutureCancelled() {
         try {
             Subscription subscription = createSubscription("Shotgun", BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME);
+            PlanPhase trialPhase = subscription.getCurrentPhase();
+
+            // MOVE TO NEXT PHASE
+            PlanPhase currentPhase = subscription.getCurrentPhase();
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            clock.setDeltaFromReality(currentPhase.getDuration(), DAY_IN_MS);
+            assertTrue(testListener.isCompleted(3000));
+
 
             // SET CTD TO CANCEL IN FUTURE
-            PlanPhase trialPhase = subscription.getCurrentPhase();
             DateTime expectedPhaseTrialChange = DefaultClock.addDuration(subscription.getStartDate(), trialPhase.getDuration());
             Duration ctd = getDurationMonth(1);
             DateTime newChargedThroughDate = DefaultClock.addDuration(expectedPhaseTrialChange, ctd);
             billingApi.setChargedThroughDate(subscription.getId(), newChargedThroughDate);
+
             subscription = entitlementApi.getSubscriptionFromId(subscription.getId());
 
             subscription.cancel(clock.getUTCNow(), false);