killbill-aplcache

entitlement: bug fixes in DefaultSubscriptionBundleTimeline This

12/16/2013 3:10:08 PM

Details

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 99af794..255f8e9 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
@@ -334,6 +334,9 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
                     break;
                 case PAUSE_BILLING:
                 case PAUSE_ENTITLEMENT:
+                case RESUME_ENTITLEMENT:
+                case RESUME_BILLING:
+                case SERVICE_STATE_CHANGE:
                     curTargetState.addEntitlementEvent(cur);
                     break;
                 case STOP_BILLING:
@@ -507,12 +510,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
         }
 
         // See https://github.com/killbill/killbill/issues/135
-        final String serviceName;
-        if (DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(in.getService())) {
-            serviceName = getServiceName(eventType);
-        } else {
-            serviceName = in.getService();
-        }
+        final String serviceName = getRealServiceNameForEntitlementOrExternalServiceName(in.getService(), eventType);
 
         return new DefaultSubscriptionEvent(in.getId(),
                                             entitlementId,
@@ -537,6 +535,16 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
                                             accountTimeZone);
     }
 
+    private static String getRealServiceNameForEntitlementOrExternalServiceName(final String originalServiceName, final SubscriptionEventType eventType) {
+        final String serviceName;
+        if (DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME.equals(originalServiceName)) {
+            serviceName = getServiceName(eventType);
+        } else {
+            serviceName = originalServiceName;
+        }
+        return serviceName;
+    }
+
     private SubscriptionEvent toSubscriptionEvent(final SubscriptionBaseTransition in, final SubscriptionEventType eventType, final DateTimeZone accountTimeZone) {
         return new DefaultSubscriptionEvent(in.getId(),
                                             in.getSubscriptionId(),
@@ -663,8 +671,22 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
         }
 
         public void addEntitlementEvent(final SubscriptionEvent e) {
-            final BlockingState converted = new DefaultBlockingState(e.getEntitlementId(), BlockingStateType.SUBSCRIPTION,
-                                                                     e.getServiceStateName(), e.getServiceName(), false, e.isBlockedEntitlement(), e.isBlockedBilling(),
+            final String serviceName = getRealServiceNameForEntitlementOrExternalServiceName(e.getServiceName(), e.getSubscriptionEventType());
+            final BlockingState lastBlockingStateForService = perServiceBlockingState.get(serviceName);
+
+            // Assume the event has no impact on changes - TODO this is wrong for SERVICE_STATE_CHANGE
+            final boolean blockChange = lastBlockingStateForService != null && lastBlockingStateForService.isBlockChange();
+            // For block entitlement or billing, override the previous state
+            final boolean blockedEntitlement = e.isBlockedEntitlement();
+            final boolean blockedBilling = e.isBlockedBilling();
+
+            final BlockingState converted = new DefaultBlockingState(e.getEntitlementId(),
+                                                                     BlockingStateType.SUBSCRIPTION,
+                                                                     e.getServiceStateName(),
+                                                                     serviceName,
+                                                                     blockChange,
+                                                                     blockedEntitlement,
+                                                                     blockedBilling,
                                                                      ((DefaultSubscriptionEvent) e).getEffectiveDateTime());
             perServiceBlockingState.put(converted.getService(), converted);
         }
@@ -724,7 +746,7 @@ public class DefaultSubscriptionBundleTimeline implements SubscriptionBundleTime
                 result.add(SubscriptionEventType.PAUSE_BILLING);
             }
 
-            if (!shouldResumeEntitlement && !shouldBlockEntitlement && !shouldBlockEntitlement && !shouldBlockBilling && !fixedBlockingState.getService().equals(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME)) {
+            if (!shouldResumeEntitlement && !shouldResumeBilling && !shouldBlockEntitlement && !shouldBlockBilling && !fixedBlockingState.getService().equals(DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME)) {
                 result.add(SubscriptionEventType.SERVICE_STATE_CHANGE);
             }
             return result;
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 9932e3f..3f89870 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
@@ -368,7 +368,7 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
 
         effectiveDate = effectiveDate.plusDays(15);
         clock.addDays(15);
-        final String service = "boo";
+        final String service = "boo-service-which-will-pause-billing";
         final BlockingState bs3 = new DefaultBlockingState(UUID.randomUUID(), entitlementId, BlockingStateType.SUBSCRIPTION,
                                                            "NothingUseful3", service,
                                                            false, false, true, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
@@ -416,8 +416,8 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
         assertEquals(events.get(5).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
         assertEquals(events.get(6).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
 
-        assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
-        assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+        assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+        assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
 
         assertEquals(events.get(0).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
         assertEquals(events.get(1).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
@@ -1081,6 +1081,143 @@ public class TestDefaultSubscriptionBundleTimeline extends EntitlementTestSuiteN
         assertNull(events.get(3).getNextPhase());
     }
 
+    @Test(groups = "fast", description = "Test for https://github.com/killbill/killbill/issues/149")
+    public void testVariousBlockingStatesAtTheSameEffectiveDate() 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, 15, 43, 25, 0, DateTimeZone.UTC);
+        final SubscriptionBaseTransition tr1 = createTransition(entitlementId, EventType.API_USER, ApiEventType.CREATE, requestedDate, effectiveDate, clock.getUTCNow(), null, "trial");
+        allTransitions.add(tr1);
+
+        // 2013-02-10
+        effectiveDate = effectiveDate.plusDays(40);
+        clock.addDays(40);
+        final BlockingState bs1 = new DefaultBlockingState(UUID.randomUUID(), bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE,
+                                                           DefaultEntitlementApi.ENT_STATE_BLOCKED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+                                                           true, true, true, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+        blockingStates.add(bs1);
+        // Same timestamp on purpose
+        final BlockingState bs2 = new DefaultBlockingState(UUID.randomUUID(), bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE,
+                                                           DefaultEntitlementApi.ENT_STATE_CLEAR, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+                                                           false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+        blockingStates.add(bs2);
+
+        // 2013-02-20
+        effectiveDate = effectiveDate.plusDays(10);
+        clock.addDays(10);
+        final BlockingState bs3 = new DefaultBlockingState(UUID.randomUUID(), bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE,
+                                                           DefaultEntitlementApi.ENT_STATE_BLOCKED, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+                                                           true, true, true, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+        blockingStates.add(bs3);
+
+        // 2013-03-02
+        effectiveDate = effectiveDate.plusDays(10);
+        clock.addDays(10);
+        final BlockingState bs4 = new DefaultBlockingState(UUID.randomUUID(), bundleId, BlockingStateType.SUBSCRIPTION_BUNDLE,
+                                                           DefaultEntitlementApi.ENT_STATE_CLEAR, DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME,
+                                                           false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+        blockingStates.add(bs4);
+
+        final String overdueService = "overdue-service";
+        // 2013-03-04
+        effectiveDate = effectiveDate.plusDays(2);
+        clock.addDays(2);
+        final BlockingState bs5 = new DefaultBlockingState(UUID.randomUUID(), accountId, BlockingStateType.ACCOUNT,
+                                                           "OD1", overdueService,
+                                                           false, false, false, effectiveDate, clock.getUTCNow(), clock.getUTCNow());
+        blockingStates.add(bs5);
+
+        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);
+
+        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(bs1.getEffectiveDate(), accountTimeZone)), 0);
+        assertEquals(events.get(3).getEffectiveDate().compareTo(new LocalDate(bs1.getEffectiveDate(), accountTimeZone)), 0);
+        assertEquals(events.get(4).getEffectiveDate().compareTo(new LocalDate(bs2.getEffectiveDate(), accountTimeZone)), 0);
+        assertEquals(events.get(5).getEffectiveDate().compareTo(new LocalDate(bs2.getEffectiveDate(), accountTimeZone)), 0);
+        assertEquals(events.get(6).getEffectiveDate().compareTo(new LocalDate(bs3.getEffectiveDate(), accountTimeZone)), 0);
+        assertEquals(events.get(7).getEffectiveDate().compareTo(new LocalDate(bs3.getEffectiveDate(), accountTimeZone)), 0);
+        assertEquals(events.get(8).getEffectiveDate().compareTo(new LocalDate(bs4.getEffectiveDate(), accountTimeZone)), 0);
+        assertEquals(events.get(9).getEffectiveDate().compareTo(new LocalDate(bs4.getEffectiveDate(), accountTimeZone)), 0);
+
+        assertEquals(events.get(10).getEffectiveDate().compareTo(new LocalDate(bs5.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.PAUSE_ENTITLEMENT);
+        assertEquals(events.get(3).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+        assertEquals(events.get(4).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
+        assertEquals(events.get(5).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
+
+        assertEquals(events.get(6).getSubscriptionEventType(), SubscriptionEventType.PAUSE_ENTITLEMENT);
+        assertEquals(events.get(7).getSubscriptionEventType(), SubscriptionEventType.PAUSE_BILLING);
+        assertEquals(events.get(8).getSubscriptionEventType(), SubscriptionEventType.RESUME_ENTITLEMENT);
+        assertEquals(events.get(9).getSubscriptionEventType(), SubscriptionEventType.RESUME_BILLING);
+
+        assertEquals(events.get(10).getSubscriptionEventType(), SubscriptionEventType.SERVICE_STATE_CHANGE);
+
+        assertEquals(events.get(0).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+        assertEquals(events.get(1).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+
+        assertEquals(events.get(2).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+        assertEquals(events.get(3).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+        assertEquals(events.get(4).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+        assertEquals(events.get(5).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+
+        assertEquals(events.get(6).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+        assertEquals(events.get(7).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+        assertEquals(events.get(8).getServiceName(), DefaultEntitlementService.ENTITLEMENT_SERVICE_NAME);
+        assertEquals(events.get(9).getServiceName(), DefaultSubscriptionBundleTimeline.BILLING_SERVICE_NAME);
+
+        assertEquals(events.get(10).getServiceName(), overdueService);
+
+        assertNull(events.get(0).getPrevPhase());
+        assertEquals(events.get(0).getNextPhase().getName(), "trial");
+        assertNull(events.get(1).getPrevPhase());
+        assertEquals(events.get(1).getNextPhase().getName(), "trial");
+
+        assertEquals(events.get(2).getPrevPhase().getName(), "trial");
+        assertEquals(events.get(2).getNextPhase().getName(), "trial");
+        assertEquals(events.get(3).getPrevPhase().getName(), "trial");
+        assertEquals(events.get(3).getNextPhase().getName(), "trial");
+        assertEquals(events.get(4).getPrevPhase().getName(), "trial");
+        assertEquals(events.get(4).getNextPhase().getName(), "trial");
+        assertEquals(events.get(5).getPrevPhase().getName(), "trial");
+        assertEquals(events.get(5).getNextPhase().getName(), "trial");
+
+        assertEquals(events.get(6).getPrevPhase().getName(), "trial");
+        assertEquals(events.get(6).getNextPhase().getName(), "trial");
+        assertEquals(events.get(7).getPrevPhase().getName(), "trial");
+        assertEquals(events.get(7).getNextPhase().getName(), "trial");
+        assertEquals(events.get(8).getPrevPhase().getName(), "trial");
+        assertEquals(events.get(8).getNextPhase().getName(), "trial");
+        assertEquals(events.get(9).getPrevPhase().getName(), "trial");
+        assertEquals(events.get(9).getNextPhase().getName(), "trial");
+
+        assertEquals(events.get(10).getPrevPhase().getName(), "trial");
+        assertEquals(events.get(10).getNextPhase().getName(), "trial");
+    }
+
     private Entitlement createEntitlement(final UUID entitlementId, final List<SubscriptionBaseTransition> allTransitions) {
         final DefaultEntitlement result = Mockito.mock(DefaultEntitlement.class);
         Mockito.when(result.getId()).thenReturn(entitlementId);