killbill-memoizeit

Details

diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java b/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
index 267ad7a..8f13369 100644
--- a/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/glue/BeatrixModule.java
@@ -17,7 +17,6 @@
 package com.ning.billing.beatrix.glue;
 
 import org.skife.config.ConfigSource;
-import org.skife.config.ConfigurationObjectFactory;
 
 import com.ning.billing.beatrix.DefaultBeatrixService;
 import com.ning.billing.beatrix.bus.api.BeatrixService;
@@ -28,7 +27,6 @@ import com.ning.billing.bus.api.PersistentBus;
 import com.ning.billing.bus.api.PersistentBusConfig;
 import com.ning.billing.util.glue.BusProvider;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.inject.AbstractModule;
 import com.google.inject.Key;
 import com.google.inject.name.Names;
@@ -57,8 +55,8 @@ public class BeatrixModule extends AbstractModule {
         bind(BeatrixService.class).to(DefaultBeatrixService.class);
         bind(DefaultBeatrixService.class).asEagerSingleton();
 
-        final PersistentBusConfig extBusConfig = new ConfigurationObjectFactory(configSource).buildWithReplacements(PersistentBusConfig.class,
-                                                                                                                    ImmutableMap.<String, String>of("instanceName", "external"));
+        final PersistentBusConfig extBusConfig = new ExternalPersistentBusConfig(configSource);
+
         bind(BusProvider.class).annotatedWith(Names.named(EXTERNAL_BUS)).toInstance(new BusProvider(extBusConfig));
         bind(PersistentBus.class).annotatedWith(Names.named(EXTERNAL_BUS)).toProvider(Key.get(BusProvider.class, Names.named(EXTERNAL_BUS))).asEagerSingleton();
 
diff --git a/beatrix/src/main/java/com/ning/billing/beatrix/glue/ExternalPersistentBusConfig.java b/beatrix/src/main/java/com/ning/billing/beatrix/glue/ExternalPersistentBusConfig.java
new file mode 100644
index 0000000..3ccc80c
--- /dev/null
+++ b/beatrix/src/main/java/com/ning/billing/beatrix/glue/ExternalPersistentBusConfig.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2010-2013 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.beatrix.glue;
+
+import org.skife.config.ConfigSource;
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.config.TimeSpan;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.ning.billing.bus.api.PersistentBusConfig;
+
+import com.google.common.collect.ImmutableMap;
+
+// Hack to make sure the external and internal buses don't share the same tables names.
+// See discussion https://groups.google.com/forum/#!msg/killbilling-users/x3o1-EjR3V0/ZJ-PJYFM_M0J
+public class ExternalPersistentBusConfig extends PersistentBusConfig {
+
+    private static final Logger logger = LoggerFactory.getLogger(ExternalPersistentBusConfig.class);
+
+    private static final String TABLE_NAME_DEFAULT_VALUE = "bus_events";
+    private static final String TABLE_NAME_ALTERNATE_DEFAULT_VALUE = "bus_ext_events";
+
+    private static final String HISTORY_TABLE_NAME_DEFAULT_VALUE = "bus_events_history";
+    private static final String HISTORY_TABLE_NAME_ALTERNATE_DEFAULT_VALUE = "bus_ext_events_history";
+
+    private final PersistentBusConfig internalPersistentBusConfig;
+    private final PersistentBusConfig externalPersistentBusConfig;
+
+    public ExternalPersistentBusConfig(final ConfigSource configSource) {
+        // See com.ning.billing.util.glue.BusModule
+        internalPersistentBusConfig = new ConfigurationObjectFactory(configSource).buildWithReplacements(PersistentBusConfig.class,
+                                                                                                         ImmutableMap.<String, String>of("instanceName", "main"));
+        externalPersistentBusConfig = new ConfigurationObjectFactory(configSource).buildWithReplacements(PersistentBusConfig.class,
+                                                                                                         ImmutableMap.<String, String>of("instanceName", "external"));
+    }
+
+    @Override
+    public int getMaxEntriesClaimed() {
+        return externalPersistentBusConfig.getMaxEntriesClaimed();
+    }
+
+    @Override
+    public TimeSpan getClaimedTime() {
+        return externalPersistentBusConfig.getClaimedTime();
+    }
+
+    @Override
+    public long getSleepTimeMs() {
+        return externalPersistentBusConfig.getSleepTimeMs();
+    }
+
+    @Override
+    public boolean isProcessingOff() {
+        return externalPersistentBusConfig.isProcessingOff();
+    }
+
+    @Override
+    public int getNbThreads() {
+        return externalPersistentBusConfig.getNbThreads();
+    }
+
+    @Override
+    public boolean isUsingInflightQueue() {
+        return externalPersistentBusConfig.isUsingInflightQueue();
+    }
+
+    @Override
+    public int getQueueCapacity() {
+        return externalPersistentBusConfig.getQueueCapacity();
+    }
+
+    @Override
+    public int getPrefetchEntries() {
+        return externalPersistentBusConfig.getPrefetchEntries();
+    }
+
+    @Override
+    public String getTableName() {
+        if (internalPersistentBusConfig.getTableName().equals(externalPersistentBusConfig.getTableName())) {
+            if (TABLE_NAME_DEFAULT_VALUE.equals(externalPersistentBusConfig.getTableName())) {
+                logger.debug("Overriding default value for the external bus table name");
+                return TABLE_NAME_ALTERNATE_DEFAULT_VALUE;
+            } else {
+                // Overridden by the user?
+                throw new IllegalArgumentException("The external and internal buses cannot share the same table name " + externalPersistentBusConfig.getTableName());
+            }
+        } else {
+            return externalPersistentBusConfig.getTableName();
+        }
+    }
+
+    @Override
+    public String getHistoryTableName() {
+        if (internalPersistentBusConfig.getHistoryTableName().equals(externalPersistentBusConfig.getHistoryTableName())) {
+            if (HISTORY_TABLE_NAME_DEFAULT_VALUE.equals(externalPersistentBusConfig.getHistoryTableName())) {
+                logger.debug("Overriding default value for the external bus history table name");
+                return HISTORY_TABLE_NAME_ALTERNATE_DEFAULT_VALUE;
+            } else {
+                // Overridden by the user?
+                throw new IllegalArgumentException("The external and internal buses cannot share the same history table name " + externalPersistentBusConfig.getHistoryTableName());
+            }
+        } else {
+            return externalPersistentBusConfig.getHistoryTableName();
+        }
+    }
+}
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
index 4792c0e..fd409c6 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultSubscriptionBundleTimeline.java
@@ -33,6 +33,8 @@ import java.util.UUID;
 import org.joda.time.DateTime;
 import org.joda.time.DateTimeZone;
 import org.joda.time.LocalDate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Plan;
@@ -42,10 +44,10 @@ import com.ning.billing.catalog.api.Product;
 import com.ning.billing.entitlement.DefaultEntitlementService;
 import com.ning.billing.entitlement.block.BlockingChecker.BlockingAggregator;
 import com.ning.billing.entitlement.block.DefaultBlockingChecker.DefaultBlockingAggregator;
+import com.ning.billing.junction.DefaultBlockingState;
 import com.ning.billing.subscription.api.SubscriptionBase;
 import com.ning.billing.subscription.api.SubscriptionBaseTransitionType;
 import com.ning.billing.subscription.api.user.SubscriptionBaseTransition;
-import com.ning.billing.junction.DefaultBlockingState;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Collections2;
@@ -53,6 +55,9 @@ import com.google.common.collect.ImmutableList;
 
 public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTimeline {
 
+
+    private final Logger logger = LoggerFactory.getLogger(DefaultSubscriptionBundleTimeline.class);
+
     public static final String BILLING_SERVICE_NAME = "billing-service";
     public static final String ENT_BILLING_SERVICE_NAME = "entitlement+billing-service";
 
@@ -96,23 +101,30 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
                 if (effectivedComp != 0) {
                     return effectivedComp;
                 }
-                final int createdDateComp = o1.getCreatedDate().compareTo(o2.getCreatedDate());
-                if (createdDateComp != 0) {
-                    return createdDateComp;
+                // For the same effectiveDate we want to first return ENTITLEMENT events
+                final int serviceNameComp = o1.getService().compareTo(o2.getService());
+                if (serviceNameComp != 0) {
+                    if (o1.getService().equals(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME)) {
+                        return -1;
+                    } else if (o2.getService().equals(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME)) {
+                        return 1;
+                    } else {
+                        return serviceNameComp;
+                    }
                 }
-                final int uuidComp = o1.getId().compareTo(o2.getId());
+                final int uuidComp = o1.getBlockedId().compareTo(o2.getBlockedId());
                 if (uuidComp != 0) {
                     return uuidComp;
                 }
                 // Same effectiveDate, createdDate and for the same object, we sort first by serviceName and then serviceState
-                final int serviceNameComp = o1.getService().compareTo(o2.getService());
-                if (serviceNameComp != 0) {
-                    return serviceNameComp;
-                }
                 final int serviceStateComp = o1.getStateName().compareTo(o2.getStateName());
                 if (serviceStateComp != 0) {
                     return serviceStateComp;
                 }
+                final int createdDateComp = o1.getCreatedDate().compareTo(o2.getCreatedDate());
+                if (createdDateComp != 0) {
+                    return createdDateComp;
+                }
                 // Underministic-- not sure that will ever happen.
                 return 0;
             }
@@ -122,11 +134,48 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
 
             final List<SubscriptionEvent> newEvents = new ArrayList<SubscriptionEvent>();
             int index = insertFromBlockingEvent(accountTimeZone, allEntitlementUUIDs, result, bs, bs.getEffectiveDate(), newEvents);
-            result.addAll(index, newEvents);
+            insertAfterIndex(result, newEvents, index);
+        }
+        return reOrderSubscriptionEventsOnSameDateByType(result);
+    }
+
+    private LinkedList<SubscriptionEvent> reOrderSubscriptionEventsOnSameDateByType(final LinkedList<SubscriptionEvent> events) {
+
+        final LinkedList<SubscriptionEvent> result = new LinkedList<SubscriptionEvent>();
+        for (final SubscriptionEvent e : events) {
+            final DefaultSubscriptionEvent cur = (DefaultSubscriptionEvent) e;
+            final DefaultSubscriptionEvent prev = result.size() > 0 ? (DefaultSubscriptionEvent) result.getLast() : null;
+            // If we already inserted an event for that subscription at that specific time, reorder so it follows enum SubscriptionEventType
+            if (prev != null &&
+                prev.getEffectiveDateTime().compareTo(cur.getEffectiveDateTime()) == 0 &&
+                prev.getEntitlementId().equals(cur.getEntitlementId()) &&
+                prev.getSubscriptionEventType().ordinal() > cur.getSubscriptionEventType().ordinal()) {
+                result.add(result.size() - 1, cur);
+            } else {
+                result.add(cur);
+            }
         }
         return result;
     }
 
+
+    private void insertAfterIndex(final LinkedList<SubscriptionEvent> original, final List<SubscriptionEvent> newEvents, int index) {
+
+        final boolean firstPosition = (index == -1);
+        final boolean lastPosition = (index == original.size() - 1);
+        if (lastPosition || firstPosition) {
+            for (final SubscriptionEvent cur : newEvents) {
+                if (lastPosition) {
+                    original.addLast(cur);
+                } else {
+                    original.addFirst(cur);
+                }
+            }
+        } else {
+            original.addAll(index + 1, newEvents);
+        }
+    }
+
     private int insertFromBlockingEvent(final DateTimeZone accountTimeZone, final Set<UUID> allEntitlementUUIDs, final LinkedList<SubscriptionEvent> result, final BlockingState bs, final DateTime bsEffectiveDate, final List<SubscriptionEvent> newEvents) {
 
 
@@ -145,14 +194,12 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
         DefaultSubscriptionEvent curInsertion = null;
         while (it.hasNext()) {
             DefaultSubscriptionEvent cur = (DefaultSubscriptionEvent) it.next();
-            index++;
-
             final int compEffectiveDate = bsEffectiveDate.compareTo(cur.getEffectiveDateTime());
-            final boolean shouldContinue = (compEffectiveDate > 0 ||
-                                            (compEffectiveDate == 0 && bs.getCreatedDate().compareTo(cur.getCreatedDate()) >= 0));
+            final boolean shouldContinue = (compEffectiveDate >= 0);
             if (!shouldContinue) {
                 break;
             }
+            index++;
 
             final TargetState curTargetState = targetStates.get(cur.getEntitlementId());
             switch (cur.getSubscriptionEventType()) {
@@ -278,20 +325,23 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
                 break;
             } else if (compEffectiveDate == 0) {
 
-                int compCreatedDate = ((DefaultSubscriptionEvent) event).getCreatedDate().compareTo(((DefaultSubscriptionEvent) cur).getCreatedDate());
-                if (compCreatedDate < 0) {
-                    // Same EffectiveDate but CreatedDate is less than cur -> insert here
+                int compUUID = event.getEntitlementId().compareTo(cur.getEntitlementId());
+                if (compUUID < 0) {
+                    // Same EffectiveDate but then order by subscriptionId;
                     break;
-                } else if (compCreatedDate == 0) {
-                    int compUUID = event.getId().compareTo(cur.getId());
-                    if (compUUID < 0) {
-                        // Same EffectiveDate and CreatedDate but order by ID
+                } else if (compUUID == 0) {
+
+                    int eventOrder = event.getSubscriptionEventType().ordinal() - cur.getSubscriptionEventType().ordinal();
+                    if (eventOrder < 0) {
+                        // Same EffectiveDate but same subscription, order by eventId;
+                        break;
+                    }
+
+                    // Two identical event for the same subscription at the same time, this sounds like some data issue
+                    if (eventOrder == 0) {
+                        logger.warn("Detected identical events type = "  + event.getSubscriptionEventType() + " ids = " +
+                                    event.getId() + ", " + cur.getId() + " for subscription " + cur.getEntitlementId());
                         break;
-                    } else if (compUUID == 0) {
-                        if (event.getSubscriptionEventType().ordinal() < cur.getSubscriptionEventType().ordinal()) {
-                            // Same EffectiveDate, CreatedDate and ID, but event type is lower -- as described in enum
-                            break;
-                        }
                     }
                 }
             }
@@ -326,7 +376,6 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
     }
 
 
-
     private SubscriptionEvent toSubscriptionEvent(final SubscriptionBaseTransition in, final SubscriptionEventType eventType, final DateTimeZone accountTimeZone) {
         return new DefaultSubscriptionEvent(in.getId(),
                                             in.getSubscriptionId(),
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
index 05d9adc..a8aeb16 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultSubscriptionBundleTimeline.java
@@ -467,6 +467,122 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
     }
 
 
+
+    @Test(groups = "fast")
+    public void testWithOverdueOfflineAndClear() throws CatalogApiException {
+        clock.setDay(new LocalDate(2013, 1, 1));
+
+        final DateTimeZone accountTimeZone = DateTimeZone.UTC;
+        final UUID accountId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+        final String externalKey = "foo";
+
+        final UUID entitlementId = UUID.randomUUID();
+
+        final List<SubscriptionBaseTransition> allTransitions = new ArrayList<SubscriptionBaseTransition>();
+
+        final List<BlockingState> blockingStates = new ArrayList<BlockingState>();
+
+        final DateTime requestedDate = new DateTime();
+        DateTime effectiveDate = new DateTime(2013, 1, 1, 23, 11, 8, 0, DateTimeZone.UTC);
+        final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+        allTransitions.add(tr1);
+
+        effectiveDate = effectiveDate.plusDays(30);
+        clock.addDays(30);
+        final SubscriptionBaseTransition tr2 = createTransition(entitlementId, EventType.PHASE, null, requestedDate, effectiveDate, clock.getUTCNow(), "trial", "phase");
+        allTransitions.add(tr2);
+
+        effectiveDate = effectiveDate.plusDays(6); // 2013-02-06
+        clock.addDays(6);
+        final SubscriptionBaseTransition tr3 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase", null);
+        allTransitions.add(tr3);
+
+        effectiveDate = effectiveDate.plusDays(22);// 2013-02-28
+        clock.addDays(22);
+        final SubscriptionBaseTransition tr4 = createTransition(entitlementId, EventType.API_USER, ApiEventType.RE_CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "phase");
+        allTransitions.add(tr4);
+
+        effectiveDate = effectiveDate.plusDays(12); // 2013-03-12
+        clock.addDays(12);
+        final SubscriptionBaseTransition tr5 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CANCEL, requestedDate, effectiveDate, clock.getUTCNow(), "phase", null);
+        allTransitions.add(tr5);
+
+        final BlockingState bs1 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.ACCOUNT,
+                                                           "OFFLINE", "overdue-service",
+                                                           true, true, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+
+        blockingStates.add(bs1);
+
+
+        final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
+                                                           DefaultEntitlementApi.ENT_STATE_CANCELLED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+                                                           true, true, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+
+        blockingStates.add(bs2);
+
+
+        effectiveDate = effectiveDate.plusDays(12); // 2013-03-24
+        clock.addDays(12);
+
+        final BlockingState bs3 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.ACCOUNT,
+                                                           "__KILLBILL__CLEAR__OVERDUE__STATE__", "overdue-service",
+                                                           false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+
+        blockingStates.add(bs3);
+
+
+        final List<Entitlement> entitlements = new ArrayList<Entitlement>();
+        final Entitlement entitlement = createEntitlement(entitlementId, allTransitions);
+        entitlements.add(entitlement);
+
+        final SubscriptionBundleTimeline timeline = new DefaultSubscriptionBundleTimeline(accountTimeZone, accountId, bundleId, externalKey, entitlements, blockingStates);
+
+        assertEquals(timeline.getAccountId(), accountId);
+        assertEquals(timeline.getBundleId(), bundleId);
+        assertEquals(timeline.getExternalKey(), externalKey);
+
+        final List<SubscriptionEvent> events = timeline.getSubscriptionEvents();
+        assertEquals(events.size(), 11);
+
+        assertEquals(events.get(0).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(1).getEffectiveDate().compareTo(new LocalDate(tr1.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(2).getEffectiveDate().compareTo(new LocalDate(tr2.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(3).getEffectiveDate().compareTo(new LocalDate(tr3.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(4).getEffectiveDate().compareTo(new LocalDate(tr3.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(5).getEffectiveDate().compareTo(new LocalDate(tr4.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(6).getEffectiveDate().compareTo(new LocalDate(tr4.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(7).getEffectiveDate().compareTo(new LocalDate(tr5.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(8).getEffectiveDate().compareTo(new LocalDate(tr5.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(9).getEffectiveDate().compareTo(new LocalDate(tr5.getEffectiveTransitionTime(), accountTimeZone)), 0);
+        assertEquals(events.get(10).getEffectiveDate().compareTo(new LocalDate(bs3.getEffectiveDate(), accountTimeZone)), 0);
+
+        assertEquals(events.get(0).getSubscriptionEventType(), SubscriptionEventType.START_ENTITLEMENT);
+        assertEquals(events.get(1).getSubscriptionEventType(), SubscriptionEventType.START_BILLING);
+        assertEquals(events.get(2).getSubscriptionEventType(), SubscriptionEventType.PHASE);
+        assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+        assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+        assertEquals(events.get(5).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
+        assertEquals(events.get(6).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
+        assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.STOP_ENTITLEMENT);
+        assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.STOP_BILLING);
+        assertEquals(events.get(9).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+        assertEquals(events.get(10).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+
+        assertEquals(events.get(0).getNextPhase().getName(), "trial");
+        assertEquals(events.get(1).getNextPhase().getName(), "trial");
+        assertEquals(events.get(2).getNextPhase().getName(), "phase");
+        assertEquals(events.get(3).getNextPhase(), null);
+        assertEquals(events.get(4).getNextPhase(), null);
+        assertEquals(events.get(5).getNextPhase().getName(), "phase");
+        assertEquals(events.get(6).getNextPhase().getName(), "phase");
+        assertEquals(events.get(7).getNextPhase(), null);
+        assertEquals(events.get(8).getNextPhase(), null);
+        assertEquals(events.get(9).getNextPhase(), null);
+        assertEquals(events.get(10).getNextPhase(), null);
+    }
+
+
     private DefaultEntitlement createEntitlement(final UUID entitlementId, final List<SubscriptionBaseTransition> allTransitions) {
 
         final DefaultEntitlement result = Mockito.mock(DefaultEntitlement.class);