killbill-memoizeit

entitlement: respect the entitlement date when computing

11/15/2013 7:20:15 PM

Details

diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventsStream.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventsStream.java
index 9d07620..785e644 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventsStream.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EventsStream.java
@@ -123,6 +123,10 @@ public class EventsStream {
         return entitlementCancelEvent != null && entitlementCancelEvent.getEffectiveDate().isAfter(utcNow);
     }
 
+    public boolean isEntitlementFutureChanged() {
+        return getPendingSubscriptionEvents(utcNow, SubscriptionBaseTransitionType.CHANGE).iterator().hasNext();
+    }
+
     public boolean isEntitlementActive() {
         return entitlementState == EntitlementState.ACTIVE;
     }
@@ -190,14 +194,7 @@ public class EventsStream {
     }
 
     public Collection<BlockingState> computeAddonsBlockingStatesForNextSubscriptionBaseEvent(final DateTime effectiveDate) {
-        // Compute the transition trigger
-        final Iterable<SubscriptionBaseTransition> pendingSubscriptionBaseTransitions = getPendingSubscriptionEvents(effectiveDate, SubscriptionBaseTransitionType.CHANGE, SubscriptionBaseTransitionType.CANCEL);
-        if (!pendingSubscriptionBaseTransitions.iterator().hasNext()) {
-            return ImmutableList.<BlockingState>of();
-        }
-
-        final SubscriptionBaseTransition subscriptionBaseTransitionTrigger = pendingSubscriptionBaseTransitions.iterator().next();
-        return computeAddonsBlockingStatesForSubscriptionBaseEvent(subscriptionBaseTransitionTrigger);
+        return computeAddonsBlockingStatesForNextSubscriptionBaseEvent(effectiveDate, false);
     }
 
     // Compute future blocking states not on disk for add-ons associated to this (base) events stream
@@ -213,15 +210,31 @@ public class EventsStream {
             // Note that in theory we could always only look subscription base as we assume entitlement cancel means subscription base cancel
             // but we want to use the effective date of the entitlement cancel event to create the add-on cancel event
             final BlockingState futureEntitlementCancelEvent = getEntitlementCancellationEvent(subscription.getId());
-            return computeAddonsBlockingStatesForNextSubscriptionBaseEvent(futureEntitlementCancelEvent.getEffectiveDate());
-        } else {
+            return computeAddonsBlockingStatesForNextSubscriptionBaseEvent(futureEntitlementCancelEvent.getEffectiveDate(), false);
+        } else if (isEntitlementFutureChanged()) {
             // ...or a subscription change (i.e. a change plan where the new plan has an impact on the existing add-on).
             // We need to go back to subscription base as entitlement doesn't know about these
-            return computeAddonsBlockingStatesForNextSubscriptionBaseEvent(utcNow);
+            return computeAddonsBlockingStatesForNextSubscriptionBaseEvent(utcNow, true);
+        } else {
+            return ImmutableList.of();
+        }
+    }
+
+    private Collection<BlockingState> computeAddonsBlockingStatesForNextSubscriptionBaseEvent(final DateTime effectiveDate,
+                                                                                              final boolean useBillingEffectiveDate) {
+        // Compute the transition trigger
+        final Iterable<SubscriptionBaseTransition> pendingSubscriptionBaseTransitions = getPendingSubscriptionEvents(effectiveDate, SubscriptionBaseTransitionType.CHANGE, SubscriptionBaseTransitionType.CANCEL);
+        if (!pendingSubscriptionBaseTransitions.iterator().hasNext()) {
+            return ImmutableList.<BlockingState>of();
         }
+
+        final SubscriptionBaseTransition subscriptionBaseTransitionTrigger = pendingSubscriptionBaseTransitions.iterator().next();
+        return computeAddonsBlockingStatesForSubscriptionBaseEvent(subscriptionBaseTransitionTrigger,
+                                                                   useBillingEffectiveDate ? subscriptionBaseTransitionTrigger.getEffectiveTransitionTime() : effectiveDate);
     }
 
-    private Collection<BlockingState> computeAddonsBlockingStatesForSubscriptionBaseEvent(final SubscriptionBaseTransition subscriptionBaseTransitionTrigger) {
+    private Collection<BlockingState> computeAddonsBlockingStatesForSubscriptionBaseEvent(final SubscriptionBaseTransition subscriptionBaseTransitionTrigger,
+                                                                                          final DateTime blockingStateEffectiveDate) {
         if (baseSubscription == null || baseSubscription.getLastActivePlan() == null || !ProductCategory.BASE.equals(baseSubscription.getLastActivePlan().getProduct().getCategory())) {
             return ImmutableList.<BlockingState>of();
         }
@@ -284,7 +297,7 @@ public class EventsStream {
                                                                                                                true,
                                                                                                                true,
                                                                                                                false,
-                                                                                                               subscriptionBaseTransitionTrigger.getEffectiveTransitionTime());
+                                                                                                               blockingStateEffectiveDate);
                                                                            }
                                                                        });
     }
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java
index abef851..498448f 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/engine/core/TestEntitlementUtils.java
@@ -158,6 +158,41 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
         checkBlockingStatesDAO(cancelledBaseEntitlement, cancelledAddOnEntitlement, cancellationDate, true);
     }
 
+    // See https://github.com/killbill/killbill/issues/121
+    @Test(groups = "slow", description = "Verify add-ons blocking states are not impacted by EOT billing cancellations")
+    public void testCancellationIMMBillingEOT() throws Exception {
+        // Approximate check, as the blocking state check (checkBlockingStatesDAO) could be a bit off
+        final DateTime cancellationDateTime = clock.getUTCNow();
+        final LocalDate cancellationDate = clock.getUTCToday();
+
+        // Cancel the base plan
+        testListener.pushExpectedEvents(NextEvent.BLOCK, NextEvent.BLOCK);
+        final DefaultEntitlement cancelledBaseEntitlement = (DefaultEntitlement) baseEntitlement.cancelEntitlementWithPolicyOverrideBillingPolicy(EntitlementActionPolicy.IMMEDIATE, BillingActionPolicy.END_OF_TERM, callContext);
+        assertListenerStatus();
+
+        // Refresh the add-on state
+        final DefaultEntitlement cancelledAddOnEntitlement = (DefaultEntitlement) entitlementApi.getEntitlementForId(addOnEntitlement.getId(), callContext);
+
+        // Verify we compute the right blocking states for the "read" path...
+        checkFutureBlockingStatesToCancel(cancelledBaseEntitlement, null, null);
+        checkFutureBlockingStatesToCancel(cancelledAddOnEntitlement, null, null);
+        checkFutureBlockingStatesToCancel(cancelledBaseEntitlement, cancelledAddOnEntitlement, null);
+        // ...and for the "write" path (which has been exercised in the cancel call above).
+        checkActualBlockingStatesToCancel(cancelledBaseEntitlement, cancelledAddOnEntitlement, cancellationDateTime, true);
+        // Verify also the blocking states DAO doesn't add too many events (all on disk)
+        checkBlockingStatesDAO(cancelledBaseEntitlement, cancelledAddOnEntitlement, cancellationDate, true);
+
+        testListener.pushExpectedEvents(NextEvent.CANCEL, NextEvent.CANCEL);
+        clock.addDays(30);
+        assertListenerStatus();
+
+        checkFutureBlockingStatesToCancel(cancelledBaseEntitlement, null, null);
+        checkFutureBlockingStatesToCancel(cancelledAddOnEntitlement, null, null);
+        checkFutureBlockingStatesToCancel(cancelledBaseEntitlement, cancelledAddOnEntitlement, null);
+        checkActualBlockingStatesToCancel(cancelledBaseEntitlement, cancelledAddOnEntitlement, cancellationDateTime, true);
+        checkBlockingStatesDAO(cancelledBaseEntitlement, cancelledAddOnEntitlement, cancellationDate, true);
+    }
+
     @Test(groups = "slow", description = "Verify add-ons blocking states are added for EOT change plans")
     public void testChangePlanEOT() throws Exception {
         // Change plan EOT to Assault-Rifle (Telescopic-Scope is included)