killbill-memoizeit

Fixing issues with future events when cancelling/ chnaging

9/5/2012 2:12:53 PM

Details

diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java
index 4fef890..b3f5277 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/transfer/DefaultEntitlementTransferApi.java
@@ -224,17 +224,17 @@ public class DefaultEntitlementTransferApi implements EntitlementTransferApi {
                         effectiveTransferDate.isBefore(oldSubscription.getChargedThroughDate()) ?
                             oldSubscription.getChargedThroughDate() : effectiveTransferDate;
 
-                            final EntitlementEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder()
-                            .setSubscriptionId(cur.getId())
-                            .setActiveVersion(cur.getActiveVersion())
-                            .setProcessedDate(clock.getUTCNow())
-                            .setEffectiveDate(effectiveCancelDate)
-                            .setRequestedDate(effectiveTransferDate)
-                            .setUserToken(context.getUserToken())
-                            .setFromDisk(true));
-
-                            TransferCancelData cancelData =  new TransferCancelData(oldSubscription, cancelEvent);
-                            transferCancelDataList.add(cancelData);
+                    final EntitlementEvent cancelEvent = new ApiEventCancel(new ApiEventBuilder()
+                    .setSubscriptionId(cur.getId())
+                    .setActiveVersion(cur.getActiveVersion())
+                    .setProcessedDate(clock.getUTCNow())
+                    .setEffectiveDate(effectiveCancelDate)
+                    .setRequestedDate(effectiveTransferDate)
+                    .setUserToken(context.getUserToken())
+                    .setFromDisk(true));
+
+                    TransferCancelData cancelData =  new TransferCancelData(oldSubscription, cancelEvent);
+                    transferCancelDataList.add(cancelData);
                 }
 
                 // We Align with the original subscription
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/AuditedEntitlementDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/AuditedEntitlementDao.java
index d2cd173..eb31743 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/AuditedEntitlementDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/dao/AuditedEntitlementDao.java
@@ -405,8 +405,7 @@ public class AuditedEntitlementDao implements EntitlementDao {
             @Override
             public Void inTransaction(final EntitlementEventSqlDao transactional, final TransactionStatus status) throws Exception {
                 final UUID subscriptionId = subscription.getId();
-                cancelNextChangeEventFromTransaction(subscriptionId, transactional, context);
-                cancelNextPhaseEventFromTransaction(subscriptionId, transactional, context);
+                cancelFutureEventsFromTransaction(subscriptionId, transactional, context);
 
                 final List<EntityAudit> eventAudits = new ArrayList<EntityAudit>();
                 for (final EntitlementEvent cur : changeEvents) {
@@ -432,9 +431,7 @@ public class AuditedEntitlementDao implements EntitlementDao {
 
     private void cancelSubscriptionFromTransaction(final SubscriptionData subscription, final EntitlementEvent cancelEvent, final EntitlementEventSqlDao transactional, final CallContext context, final int seqId) {
         final UUID subscriptionId = subscription.getId();
-        cancelNextCancelEventFromTransaction(subscriptionId, transactional, context);
-        cancelNextChangeEventFromTransaction(subscriptionId, transactional, context);
-        cancelNextPhaseEventFromTransaction(subscriptionId, transactional, context);
+        cancelFutureEventsFromTransaction(subscriptionId, transactional, context);
         transactional.insertEvent(cancelEvent, context);
         final String cancelEventId = cancelEvent.getId().toString();
 
@@ -456,12 +453,13 @@ public class AuditedEntitlementDao implements EntitlementDao {
         cancelFutureEventFromTransaction(subscriptionId, dao, EventType.PHASE, null, context);
     }
 
-    private void cancelNextChangeEventFromTransaction(final UUID subscriptionId, final EntitlementEventSqlDao dao, final CallContext context) {
-        cancelFutureEventFromTransaction(subscriptionId, dao, EventType.API_USER, ApiEventType.CHANGE, context);
-    }
 
-    private void cancelNextCancelEventFromTransaction(final UUID subscriptionId, final EntitlementEventSqlDao dao, final CallContext context) {
-        cancelFutureEventFromTransaction(subscriptionId, dao, EventType.API_USER, ApiEventType.CANCEL, context);
+    private void cancelFutureEventsFromTransaction(final UUID subscriptionId, final EntitlementEventSqlDao dao, final CallContext context) {
+        final Date now = clock.getUTCNow().toDate();
+        final List<EntitlementEvent> events = dao.getFutureActiveEventForSubscription(subscriptionId.toString(), now);
+        for (final EntitlementEvent cur : events) {
+            unactivateEventFromTransaction(cur, dao, context);
+        }
     }
 
     private void cancelFutureEventFromTransaction(final UUID subscriptionId, final EntitlementEventSqlDao dao, final EventType type,
@@ -479,9 +477,12 @@ public class AuditedEntitlementDao implements EntitlementDao {
                 futureEvent = cur;
             }
         }
+        unactivateEventFromTransaction(futureEvent, dao, context);
+    }
 
-        if (futureEvent != null) {
-            final String eventId = futureEvent.getId().toString();
+    private void unactivateEventFromTransaction(final EntitlementEvent event, final EntitlementEventSqlDao dao, final CallContext context) {
+        if (event != null) {
+            final String eventId = event.getId().toString();
             dao.unactiveEvent(eventId, context);
             final Long recordId = dao.getRecordId(eventId);
             final EntityAudit audit = new EntityAudit(TableName.SUBSCRIPTION_EVENTS, recordId, ChangeType.UPDATE);
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
index a15cd77..2d2a451 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/migration/TestMigration.java
@@ -50,7 +50,7 @@ public abstract class TestMigration extends TestApiBase {
         try {
             final DateTime startDate = clock.getUTCNow().minusMonths(2);
             final DateTime beforeMigration = clock.getUTCNow();
-            final EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlan(startDate);
+            final EntitlementAccountMigration toBeMigrated = createAccountForMigrationWithRegularBasePlan(startDate);
             final DateTime afterMigration = clock.getUTCNow();
 
             testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
@@ -83,7 +83,7 @@ public abstract class TestMigration extends TestApiBase {
             final DateTime beforeMigration = clock.getUTCNow();
             final DateTime initalBPStart = clock.getUTCNow().minusMonths(3);
             final DateTime initalAddonStart = clock.getUTCNow().minusMonths(1).plusDays(7);
-            final EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlanAndAddons(initalBPStart, initalAddonStart);
+            final EntitlementAccountMigration toBeMigrated = createAccountForMigrationWithRegularBasePlanAndAddons(initalBPStart, initalAddonStart);
             final DateTime afterMigration = clock.getUTCNow();
 
             testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
@@ -111,7 +111,7 @@ public abstract class TestMigration extends TestApiBase {
             final Subscription aoSubscription = (subscriptions.get(0).getCurrentPlan().getProduct().getCategory() == ProductCategory.ADD_ON) ?
                     subscriptions.get(0) : subscriptions.get(1);
             // initalAddonStart.plusMonths(1).minusMonths(1) may be different from initalAddonStart, depending on exact date
-            // e.g : March 31 + 1 month => April 30 and April 30 - 1 month = March 30 which is != March 31 !!!! 
+            // e.g : March 31 + 1 month => April 30 and April 30 - 1 month = March 30 which is != March 31 !!!!
             assertEquals(aoSubscription.getStartDate(), initalAddonStart.plusMonths(1).minusMonths(1));
             assertEquals(aoSubscription.getEndDate(), null);
             assertEquals(aoSubscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
@@ -131,7 +131,7 @@ public abstract class TestMigration extends TestApiBase {
         try {
             final DateTime startDate = clock.getUTCNow().minusMonths(1);
             final DateTime beforeMigration = clock.getUTCNow();
-            final EntitlementAccountMigration toBeMigrated = createAccountWithRegularBasePlanFutreCancelled(startDate);
+            final EntitlementAccountMigration toBeMigrated = createAccountForMigrationWithRegularBasePlanFutreCancelled(startDate);
             final DateTime afterMigration = clock.getUTCNow();
 
             testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
@@ -177,7 +177,7 @@ public abstract class TestMigration extends TestApiBase {
     public void testSingleBasePlanWithPendingPhase() {
         try {
             final DateTime trialDate = clock.getUTCNow().minusDays(10);
-            final EntitlementAccountMigration toBeMigrated = createAccountFuturePendingPhase(trialDate);
+            final EntitlementAccountMigration toBeMigrated = createAccountForMigrationFuturePendingPhase(trialDate);
 
             testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
             migrationApi.migrate(toBeMigrated, context);
@@ -223,7 +223,7 @@ public abstract class TestMigration extends TestApiBase {
     public void testSingleBasePlanWithPendingChange() {
         try {
             final DateTime beforeMigration = clock.getUTCNow();
-            final EntitlementAccountMigration toBeMigrated = createAccountFuturePendingChange();
+            final EntitlementAccountMigration toBeMigrated = createAccountForMigrationFuturePendingChange();
             final DateTime afterMigration = clock.getUTCNow();
 
             testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
@@ -264,179 +264,4 @@ public abstract class TestMigration extends TestApiBase {
             Assert.fail("", e);
         }
     }
-
-    private EntitlementAccountMigration createAccountTest(final List<List<EntitlementSubscriptionMigrationCaseWithCTD>> cases) {
-        return new EntitlementAccountMigration() {
-            private final UUID accountId = UUID.randomUUID();
-
-            @Override
-            public EntitlementBundleMigration[] getBundles() {
-                final List<EntitlementBundleMigration> bundles = new ArrayList<EntitlementBundleMigration>();
-                final EntitlementBundleMigration bundle0 = new EntitlementBundleMigration() {
-                    @Override
-                    public EntitlementSubscriptionMigration[] getSubscriptions() {
-                        final EntitlementSubscriptionMigration[] result = new EntitlementSubscriptionMigration[cases.size()];
-
-                        for (int i = 0; i < cases.size(); i++) {
-                            final List<EntitlementSubscriptionMigrationCaseWithCTD> curCases = cases.get(i);
-                            final EntitlementSubscriptionMigration subscription = new EntitlementSubscriptionMigration() {
-                                @Override
-                                public EntitlementSubscriptionMigrationCaseWithCTD[] getSubscriptionCases() {
-                                    return curCases.toArray(new EntitlementSubscriptionMigrationCaseWithCTD[curCases.size()]);
-                                }
-
-                                @Override
-                                public ProductCategory getCategory() {
-                                    return curCases.get(0).getPlanPhaseSpecifier().getProductCategory();
-                                }
-
-                                @Override
-                                public DateTime getChargedThroughDate() {
-                                    for (final EntitlementSubscriptionMigrationCaseWithCTD cur : curCases) {
-                                        if (cur.getChargedThroughDate() != null) {
-                                            return cur.getChargedThroughDate();
-                                        }
-                                    }
-                                    return null;
-                                }
-                            };
-                            result[i] = subscription;
-                        }
-                        return result;
-                    }
-
-                    @Override
-                    public String getBundleKey() {
-                        return "12345";
-                    }
-                };
-                bundles.add(bundle0);
-                return bundles.toArray(new EntitlementBundleMigration[bundles.size()]);
-            }
-
-            @Override
-            public UUID getAccountKey() {
-                return accountId;
-            }
-        };
-    }
-
-    private EntitlementAccountMigration createAccountWithRegularBasePlanAndAddons(final DateTime initialBPstart, final DateTime initalAddonStart) {
-
-        final List<EntitlementSubscriptionMigrationCaseWithCTD> cases = new LinkedList<EntitlementSubscriptionMigrationCaseWithCTD>();
-        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
-                new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
-                initialBPstart,
-                null,
-                initialBPstart.plusYears(1)));
-
-        final List<EntitlementSubscriptionMigrationCaseWithCTD> firstAddOnCases = new LinkedList<EntitlementSubscriptionMigrationCaseWithCTD>();
-        firstAddOnCases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
-                new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.DISCOUNT),
-                initalAddonStart,
-                initalAddonStart.plusMonths(1),
-                initalAddonStart.plusMonths(1)));
-        firstAddOnCases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
-                new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
-                initalAddonStart.plusMonths(1),
-                null,
-                null));
-
-        final List<List<EntitlementSubscriptionMigrationCaseWithCTD>> input = new ArrayList<List<EntitlementSubscriptionMigrationCaseWithCTD>>();
-        input.add(cases);
-        input.add(firstAddOnCases);
-        return createAccountTest(input);
-    }
-
-    private EntitlementAccountMigration createAccountWithRegularBasePlan(final DateTime startDate) {
-        final List<EntitlementSubscriptionMigrationCaseWithCTD> cases = new LinkedList<EntitlementSubscriptionMigrationCaseWithCTD>();
-        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
-                new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
-                startDate,
-                null,
-                startDate.plusYears(1)));
-        final List<List<EntitlementSubscriptionMigrationCaseWithCTD>> input = new ArrayList<List<EntitlementSubscriptionMigrationCaseWithCTD>>();
-        input.add(cases);
-        return createAccountTest(input);
-    }
-
-    private EntitlementAccountMigration createAccountWithRegularBasePlanFutreCancelled(final DateTime startDate) {
-        final List<EntitlementSubscriptionMigrationCaseWithCTD> cases = new LinkedList<EntitlementSubscriptionMigrationCaseWithCTD>();
-        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
-                new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
-                startDate,
-                startDate.plusYears(1),
-                startDate.plusYears(1)));
-        final List<List<EntitlementSubscriptionMigrationCaseWithCTD>> input = new ArrayList<List<EntitlementSubscriptionMigrationCaseWithCTD>>();
-        input.add(cases);
-        return createAccountTest(input);
-    }
-
-    private EntitlementAccountMigration createAccountFuturePendingPhase(final DateTime trialDate) {
-        final List<EntitlementSubscriptionMigrationCaseWithCTD> cases = new LinkedList<EntitlementSubscriptionMigrationCaseWithCTD>();
-        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
-                new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL),
-                trialDate,
-                trialDate.plusDays(30),
-                trialDate.plusDays(30)));
-        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
-                new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
-                trialDate.plusDays(30),
-                null,
-                null));
-        final List<List<EntitlementSubscriptionMigrationCaseWithCTD>> input = new ArrayList<List<EntitlementSubscriptionMigrationCaseWithCTD>>();
-        input.add(cases);
-        return createAccountTest(input);
-    }
-
-    private EntitlementAccountMigration createAccountFuturePendingChange() {
-        final List<EntitlementSubscriptionMigrationCaseWithCTD> cases = new LinkedList<EntitlementSubscriptionMigrationCaseWithCTD>();
-        final DateTime effectiveDate = clock.getUTCNow().minusDays(10);
-        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
-                new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
-                effectiveDate,
-                effectiveDate.plusMonths(1),
-                effectiveDate.plusMonths(1)));
-        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
-                new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
-                effectiveDate.plusMonths(1).plusDays(1),
-                null,
-                null));
-        final List<List<EntitlementSubscriptionMigrationCaseWithCTD>> input = new ArrayList<List<EntitlementSubscriptionMigrationCaseWithCTD>>();
-        input.add(cases);
-        return createAccountTest(input);
-    }
-
-    public static class EntitlementSubscriptionMigrationCaseWithCTD implements EntitlementSubscriptionMigrationCase {
-        private final PlanPhaseSpecifier pps;
-        private final DateTime effDt;
-        private final DateTime cancelDt;
-        private final DateTime ctd;
-
-        public EntitlementSubscriptionMigrationCaseWithCTD(final PlanPhaseSpecifier pps, final DateTime effDt, final DateTime cancelDt, final DateTime ctd) {
-            this.pps = pps;
-            this.cancelDt = cancelDt;
-            this.effDt = effDt;
-            this.ctd = ctd;
-        }
-
-        @Override
-        public PlanPhaseSpecifier getPlanPhaseSpecifier() {
-            return pps;
-        }
-
-        @Override
-        public DateTime getEffectiveDate() {
-            return effDt;
-        }
-
-        @Override
-        public DateTime getCancelledDate() {
-            return cancelDt;
-        }
-
-        public DateTime getChargedThroughDate() {
-            return ctd;
-        }
-    }
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
index 88b24b1..eb52dec 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestApiBase.java
@@ -19,6 +19,8 @@ package com.ning.billing.entitlement.api;
 import javax.annotation.Nullable;
 import java.io.IOException;
 import java.net.URL;
+import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.UUID;
 
@@ -49,6 +51,7 @@ import com.ning.billing.catalog.api.Currency;
 import com.ning.billing.catalog.api.Duration;
 import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.catalog.api.TimeUnit;
 import com.ning.billing.config.EntitlementConfig;
@@ -56,9 +59,12 @@ import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.EntitlementTestSuiteWithEmbeddedDB;
 import com.ning.billing.entitlement.api.billing.ChargeThruApi;
 import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementAccountMigration;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementBundleMigration;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementSubscriptionMigration;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementSubscriptionMigrationCase;
 import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
 import com.ning.billing.entitlement.api.transfer.EntitlementTransferApi;
-import com.ning.billing.entitlement.api.transfer.EntitlementTransferApiException;
 import com.ning.billing.entitlement.api.user.EffectiveSubscriptionEvent;
 import com.ning.billing.entitlement.api.user.EntitlementUserApi;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
@@ -400,4 +406,184 @@ public abstract class TestApiBase extends EntitlementTestSuiteWithEmbeddedDB imp
             log.debug("Transition " + cur);
         }
     }
+
+
+    /**************************************************************
+        Utilities for migration tests
+    ***************************************************************/
+
+    protected EntitlementAccountMigration createAccountForMigrationTest(final List<List<EntitlementSubscriptionMigrationCaseWithCTD>> cases) {
+        return new EntitlementAccountMigration() {
+            private final UUID accountId = UUID.randomUUID();
+
+            @Override
+            public EntitlementBundleMigration[] getBundles() {
+                final List<EntitlementBundleMigration> bundles = new ArrayList<EntitlementBundleMigration>();
+                final EntitlementBundleMigration bundle0 = new EntitlementBundleMigration() {
+                    @Override
+                    public EntitlementSubscriptionMigration[] getSubscriptions() {
+                        final EntitlementSubscriptionMigration[] result = new EntitlementSubscriptionMigration[cases.size()];
+
+                        for (int i = 0; i < cases.size(); i++) {
+                            final List<EntitlementSubscriptionMigrationCaseWithCTD> curCases = cases.get(i);
+                            final EntitlementSubscriptionMigration subscription = new EntitlementSubscriptionMigration() {
+                                @Override
+                                public EntitlementSubscriptionMigrationCaseWithCTD[] getSubscriptionCases() {
+                                    return curCases.toArray(new EntitlementSubscriptionMigrationCaseWithCTD[curCases.size()]);
+                                }
+
+                                @Override
+                                public ProductCategory getCategory() {
+                                    return curCases.get(0).getPlanPhaseSpecifier().getProductCategory();
+                                }
+
+                                @Override
+                                public DateTime getChargedThroughDate() {
+                                    for (final EntitlementSubscriptionMigrationCaseWithCTD cur : curCases) {
+                                        if (cur.getChargedThroughDate() != null) {
+                                            return cur.getChargedThroughDate();
+                                        }
+                                    }
+                                    return null;
+                                }
+                            };
+                            result[i] = subscription;
+                        }
+                        return result;
+                    }
+
+                    @Override
+                    public String getBundleKey() {
+                        return "12345";
+                    }
+                };
+                bundles.add(bundle0);
+                return bundles.toArray(new EntitlementBundleMigration[bundles.size()]);
+            }
+
+            @Override
+            public UUID getAccountKey() {
+                return accountId;
+            }
+        };
+    }
+
+    protected EntitlementAccountMigration createAccountForMigrationWithRegularBasePlanAndAddons(final DateTime initialBPstart, final DateTime initalAddonStart) {
+
+        final List<EntitlementSubscriptionMigrationCaseWithCTD> cases = new LinkedList<EntitlementSubscriptionMigrationCaseWithCTD>();
+        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
+                new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
+                initialBPstart,
+                null,
+                initialBPstart.plusYears(1)));
+
+        final List<EntitlementSubscriptionMigrationCaseWithCTD> firstAddOnCases = new LinkedList<EntitlementSubscriptionMigrationCaseWithCTD>();
+        firstAddOnCases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
+                new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.DISCOUNT),
+                initalAddonStart,
+                initalAddonStart.plusMonths(1),
+                initalAddonStart.plusMonths(1)));
+        firstAddOnCases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
+                new PlanPhaseSpecifier("Telescopic-Scope", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
+                initalAddonStart.plusMonths(1),
+                null,
+                null));
+
+        final List<List<EntitlementSubscriptionMigrationCaseWithCTD>> input = new ArrayList<List<EntitlementSubscriptionMigrationCaseWithCTD>>();
+        input.add(cases);
+        input.add(firstAddOnCases);
+        return createAccountForMigrationTest(input);
+    }
+
+    protected EntitlementAccountMigration createAccountForMigrationWithRegularBasePlan(final DateTime startDate) {
+        final List<EntitlementSubscriptionMigrationCaseWithCTD> cases = new LinkedList<EntitlementSubscriptionMigrationCaseWithCTD>();
+        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
+                new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
+                startDate,
+                null,
+                startDate.plusYears(1)));
+        final List<List<EntitlementSubscriptionMigrationCaseWithCTD>> input = new ArrayList<List<EntitlementSubscriptionMigrationCaseWithCTD>>();
+        input.add(cases);
+        return createAccountForMigrationTest(input);
+    }
+
+    protected EntitlementAccountMigration createAccountForMigrationWithRegularBasePlanFutreCancelled(final DateTime startDate) {
+        final List<EntitlementSubscriptionMigrationCaseWithCTD> cases = new LinkedList<EntitlementSubscriptionMigrationCaseWithCTD>();
+        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
+                new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
+                startDate,
+                startDate.plusYears(1),
+                startDate.plusYears(1)));
+        final List<List<EntitlementSubscriptionMigrationCaseWithCTD>> input = new ArrayList<List<EntitlementSubscriptionMigrationCaseWithCTD>>();
+        input.add(cases);
+        return createAccountForMigrationTest(input);
+    }
+
+    protected EntitlementAccountMigration createAccountForMigrationFuturePendingPhase(final DateTime trialDate) {
+        final List<EntitlementSubscriptionMigrationCaseWithCTD> cases = new LinkedList<EntitlementSubscriptionMigrationCaseWithCTD>();
+        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
+                new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.TRIAL),
+                trialDate,
+                trialDate.plusDays(30),
+                trialDate.plusDays(30)));
+        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
+                new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
+                trialDate.plusDays(30),
+                null,
+                null));
+        final List<List<EntitlementSubscriptionMigrationCaseWithCTD>> input = new ArrayList<List<EntitlementSubscriptionMigrationCaseWithCTD>>();
+        input.add(cases);
+        return createAccountForMigrationTest(input);
+    }
+
+    protected EntitlementAccountMigration createAccountForMigrationFuturePendingChange() {
+        final List<EntitlementSubscriptionMigrationCaseWithCTD> cases = new LinkedList<EntitlementSubscriptionMigrationCaseWithCTD>();
+        final DateTime effectiveDate = clock.getUTCNow().minusDays(10);
+        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
+                new PlanPhaseSpecifier("Assault-Rifle", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
+                effectiveDate,
+                effectiveDate.plusMonths(1),
+                effectiveDate.plusMonths(1)));
+        cases.add(new EntitlementSubscriptionMigrationCaseWithCTD(
+                new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, PhaseType.EVERGREEN),
+                effectiveDate.plusMonths(1).plusDays(1),
+                null,
+                null));
+        final List<List<EntitlementSubscriptionMigrationCaseWithCTD>> input = new ArrayList<List<EntitlementSubscriptionMigrationCaseWithCTD>>();
+        input.add(cases);
+        return createAccountForMigrationTest(input);
+    }
+
+    public static class EntitlementSubscriptionMigrationCaseWithCTD implements EntitlementSubscriptionMigrationCase {
+        private final PlanPhaseSpecifier pps;
+        private final DateTime effDt;
+        private final DateTime cancelDt;
+        private final DateTime ctd;
+
+        public EntitlementSubscriptionMigrationCaseWithCTD(final PlanPhaseSpecifier pps, final DateTime effDt, final DateTime cancelDt, final DateTime ctd) {
+            this.pps = pps;
+            this.cancelDt = cancelDt;
+            this.effDt = effDt;
+            this.ctd = ctd;
+        }
+
+        @Override
+        public PlanPhaseSpecifier getPlanPhaseSpecifier() {
+            return pps;
+        }
+
+        @Override
+        public DateTime getEffectiveDate() {
+            return effDt;
+        }
+
+        @Override
+        public DateTime getCancelledDate() {
+            return cancelDt;
+        }
+
+        public DateTime getChargedThroughDate() {
+            return ctd;
+        }
+    }
 }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestTransfer.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestTransfer.java
index 6074c5e..146db74 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestTransfer.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/transfer/TestTransfer.java
@@ -38,7 +38,10 @@ import com.ning.billing.catalog.api.PhaseType;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.Product;
+import com.ning.billing.entitlement.api.SubscriptionTransitionType;
 import com.ning.billing.entitlement.api.TestApiBase;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApiException;
+import com.ning.billing.entitlement.api.migration.EntitlementMigrationApi.EntitlementAccountMigration;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
 import com.ning.billing.entitlement.api.user.SubscriptionData;
@@ -55,6 +58,67 @@ public class TestTransfer extends TestApiBase {
         return Guice.createInjector(Stage.DEVELOPMENT, new MockEngineModuleSql());
     }
 
+
+
+    @Test(groups = "slow")
+    public void testTransferMigratedSubscriptionWithCTDInFuture() throws Exception {
+
+        final UUID newAccountId = UUID.randomUUID();
+
+        try {
+            final DateTime startDate = clock.getUTCNow().minusMonths(2);
+            final DateTime beforeMigration = clock.getUTCNow();
+            final EntitlementAccountMigration toBeMigrated = createAccountForMigrationWithRegularBasePlan(startDate);
+            final DateTime afterMigration = clock.getUTCNow();
+
+            testListener.pushExpectedEvent(NextEvent.MIGRATE_ENTITLEMENT);
+            migrationApi.migrate(toBeMigrated, context);
+            assertTrue(testListener.isCompleted(5000));
+
+            final List<SubscriptionBundle> bundles = entitlementApi.getBundlesForAccount(toBeMigrated.getAccountKey());
+            assertEquals(bundles.size(), 1);
+            final SubscriptionBundle bundle = bundles.get(0);
+
+            final List<Subscription> subscriptions = entitlementApi.getSubscriptionsForBundle(bundle.getId());
+            assertEquals(subscriptions.size(), 1);
+            final Subscription subscription = subscriptions.get(0);
+            assertDateWithin(subscription.getStartDate(), beforeMigration, afterMigration);
+            assertEquals(subscription.getEndDate(), null);
+            assertEquals(subscription.getCurrentPriceList().getName(), PriceListSet.DEFAULT_PRICELIST_NAME);
+            assertEquals(subscription.getCurrentPhase().getPhaseType(), PhaseType.EVERGREEN);
+            assertEquals(subscription.getState(), SubscriptionState.ACTIVE);
+            assertEquals(subscription.getCurrentPlan().getName(), "assault-rifle-annual");
+            assertEquals(subscription.getChargedThroughDate(), startDate.plusYears(1));
+            // WE should see MIGRATE_ENTITLEMENT and then MIGRATE_BILLING in the future
+            assertEquals(subscription.getBillingTransitions().size(), 1);
+            assertEquals(subscription.getBillingTransitions().get(0).getTransitionType(), SubscriptionTransitionType.MIGRATE_BILLING);
+            assertTrue(subscription.getBillingTransitions().get(0).getEffectiveTransitionTime().compareTo(clock.getUTCNow()) > 0);
+            assertListenerStatus();
+
+
+            // MOVE A LITTLE, STILL IN TRIAL
+            clock.addDays(20);
+
+            final DateTime transferRequestedDate = clock.getUTCNow();
+
+            testListener.pushExpectedEvent(NextEvent.TRANSFER);
+            testListener.pushExpectedEvent(NextEvent.CANCEL);
+            transferApi.transferBundle(bundle.getAccountId(), newAccountId, bundle.getKey(), transferRequestedDate, false, true, context);
+            assertTrue(testListener.isCompleted(3000));
+
+            final Subscription oldBaseSubscription = entitlementApi.getBaseSubscription(bundle.getId());
+            assertTrue(oldBaseSubscription.getState() == SubscriptionState.CANCELLED);
+            // The MIGRATE_BILLING event should have been invalidated
+            assertEquals(oldBaseSubscription.getBillingTransitions().size(), 1);
+            assertEquals(oldBaseSubscription.getBillingTransitions().get(0).getTransitionType(), SubscriptionTransitionType.CANCEL);
+
+        } catch (EntitlementMigrationApiException e) {
+            Assert.fail("", e);
+        }
+
+
+    }
+
     @Test(groups = "slow")
     public void testTransferBPInTrialWithNoCTD() throws Exception {