killbill-memoizeit

entitlement: fix bug in EventsStream In some scenarii (e.g.

11/18/2013 11:44:36 AM

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 785e644..6f40045 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
@@ -20,6 +20,8 @@ import java.util.Collection;
 import java.util.List;
 import java.util.UUID;
 
+import javax.annotation.Nullable;
+
 import org.joda.time.DateTime;
 import org.joda.time.LocalDate;
 
@@ -222,25 +224,36 @@ public class EventsStream {
 
     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();
+        SubscriptionBaseTransition subscriptionBaseTransitionTrigger = null;
+        if (!isEntitlementFutureCancelled()) {
+            // Compute the transition trigger (either subscription cancel or change)
+            final Iterable<SubscriptionBaseTransition> pendingSubscriptionBaseTransitions = getPendingSubscriptionEvents(effectiveDate, SubscriptionBaseTransitionType.CHANGE, SubscriptionBaseTransitionType.CANCEL);
+            if (!pendingSubscriptionBaseTransitions.iterator().hasNext()) {
+                return ImmutableList.<BlockingState>of();
+            }
+
+            subscriptionBaseTransitionTrigger = pendingSubscriptionBaseTransitions.iterator().next();
+        }
+
+        final Product baseTransitionTriggerNextProduct;
+        final DateTime blockingStateEffectiveDate;
+        if (subscriptionBaseTransitionTrigger == null) {
+            baseTransitionTriggerNextProduct = null;
+            blockingStateEffectiveDate = effectiveDate;
+        } else {
+            baseTransitionTriggerNextProduct = (EntitlementState.CANCELLED.equals(subscriptionBaseTransitionTrigger.getNextState()) ? null : subscriptionBaseTransitionTrigger.getNextPlan().getProduct());
+            blockingStateEffectiveDate = useBillingEffectiveDate ? subscriptionBaseTransitionTrigger.getEffectiveTransitionTime() : effectiveDate;
         }
 
-        final SubscriptionBaseTransition subscriptionBaseTransitionTrigger = pendingSubscriptionBaseTransitions.iterator().next();
-        return computeAddonsBlockingStatesForSubscriptionBaseEvent(subscriptionBaseTransitionTrigger,
-                                                                   useBillingEffectiveDate ? subscriptionBaseTransitionTrigger.getEffectiveTransitionTime() : effectiveDate);
+        return computeAddonsBlockingStatesForSubscriptionBaseEvent(baseTransitionTriggerNextProduct, blockingStateEffectiveDate);
     }
 
-    private Collection<BlockingState> computeAddonsBlockingStatesForSubscriptionBaseEvent(final SubscriptionBaseTransition subscriptionBaseTransitionTrigger,
+    private Collection<BlockingState> computeAddonsBlockingStatesForSubscriptionBaseEvent(@Nullable final Product baseTransitionTriggerNextProduct,
                                                                                           final DateTime blockingStateEffectiveDate) {
         if (baseSubscription == null || baseSubscription.getLastActivePlan() == null || !ProductCategory.BASE.equals(baseSubscription.getLastActivePlan().getProduct().getCategory())) {
             return ImmutableList.<BlockingState>of();
         }
 
-        final Product baseTransitionTriggerNextProduct = EntitlementState.CANCELLED.equals(subscriptionBaseTransitionTrigger.getNextState()) ? null : subscriptionBaseTransitionTrigger.getNextPlan().getProduct();
-
         // Compute included and available addons for the new product
         final Collection<String> includedAddonsForProduct;
         final Collection<String> availableAddonsForProduct;
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 5bf86ae..d30cc83 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
@@ -44,6 +44,8 @@ import com.ning.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
 import com.ning.billing.entitlement.api.EntitlementApiException;
 import com.ning.billing.entitlement.dao.BlockingStateSqlDao;
 
+import com.google.common.base.Objects;
+
 public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
 
     private BlockingStateSqlDao sqlDao;
@@ -53,11 +55,12 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
     private DateTime baseEffectiveEOTCancellationOrChangeDateTime;
     private LocalDate baseEffectiveCancellationOrChangeDate;
 
+    private final LocalDate initialDate = new LocalDate(2013, 8, 8);
+
     @BeforeMethod(groups = "slow")
     public void setUp() throws Exception {
         sqlDao = dbi.onDemand(BlockingStateSqlDao.class);
 
-        final LocalDate initialDate = new LocalDate(2013, 8, 8);
         clock.setDay(initialDate);
         final Account account = accountApi.createAccount(getAccountData(7), callContext);
 
@@ -100,8 +103,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
         checkFutureBlockingStatesToCancel(addOnEntitlement, null, null);
         checkFutureBlockingStatesToCancel(cancelledBaseEntitlement, addOnEntitlement, baseEffectiveEOTCancellationOrChangeDateTime);
         // and for the "write" path (which will be exercised when the future notification kicks in).
-        // Note that no event are computed because the add-on is not cancelled yet
-        checkActualBlockingStatesToCancel(cancelledBaseEntitlement, addOnEntitlement, null, false);
+        checkActualBlockingStatesToCancel(cancelledBaseEntitlement, addOnEntitlement, baseEffectiveEOTCancellationOrChangeDateTime, false);
         // Verify also the blocking states DAO adds events not on disk
         checkBlockingStatesDAO(baseEntitlement, addOnEntitlement, baseEffectiveCancellationOrChangeDate, true);
 
@@ -139,6 +141,17 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
         checkBlockingStatesDAO(cancelledBaseEntitlement, cancelledAddOnEntitlement, baseEffectiveCancellationOrChangeDate, clock.getUTCToday(), true);
     }
 
+    @Test(groups = "slow", description = "Verify add-ons blocking states are added for IMM billing / EOT entitlement cancellations")
+    public void testCancellationBillingIMMEntitlementEOT() throws Exception {
+        // Cancel the base plan
+        testListener.pushExpectedEvents(NextEvent.CANCEL, NextEvent.CANCEL);
+        final DefaultEntitlement cancelledBaseEntitlement = (DefaultEntitlement) baseEntitlement.cancelEntitlementWithPolicyOverrideBillingPolicy(EntitlementActionPolicy.END_OF_TERM, BillingActionPolicy.IMMEDIATE, callContext);
+        assertListenerStatus();
+
+        // Verify the blocking states API sees the EOT cancellation (add-on blocking state not on disk)
+        checkBlockingStatesDAO(cancelledBaseEntitlement, addOnEntitlement, baseEffectiveCancellationOrChangeDate, true);
+    }
+
     @Test(groups = "slow", description = "Verify add-ons blocking states are not impacted by IMM cancellations")
     public void testCancellationIMM() throws Exception {
         // Approximate check, as the blocking state check (checkBlockingStatesDAO) could be a bit off
@@ -219,8 +232,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
         checkFutureBlockingStatesToCancel(addOnEntitlement, null, null);
         checkFutureBlockingStatesToCancel(changedBaseEntitlement, addOnEntitlement, baseEffectiveEOTCancellationOrChangeDateTime);
         // ...and for the "write" path (which will be exercised when the future notification kicks in).
-        // Note that no event are computed because the add-on is not cancelled yet
-        checkActualBlockingStatesToCancel(changedBaseEntitlement, addOnEntitlement, null, false);
+        checkActualBlockingStatesToCancel(changedBaseEntitlement, addOnEntitlement, baseEffectiveEOTCancellationOrChangeDateTime, false);
         // Verify also the blocking states DAO adds events not on disk
         checkBlockingStatesDAO(changedBaseEntitlement, addOnEntitlement, baseEffectiveCancellationOrChangeDate, false);
 
@@ -314,7 +326,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
 
     // Test the "write" path
     private void checkActualBlockingStatesToCancel(final DefaultEntitlement baseEntitlement, final DefaultEntitlement addOnEntitlement, @Nullable final DateTime effectiveCancellationDateTime, final boolean approximateDateCheck) throws EntitlementApiException {
-        final Collection<BlockingState> blockingStatesForCancellation = computeBlockingStatesForAssociatedAddons(baseEntitlement, effectiveCancellationDateTime);
+        final Collection<BlockingState> blockingStatesForCancellation = computeBlockingStatesForAssociatedAddons(baseEntitlement, Objects.firstNonNull(effectiveCancellationDateTime, initialDate.toDateTimeAtStartOfDay()));
         if (effectiveCancellationDateTime == null) {
             Assert.assertEquals(blockingStatesForCancellation.size(), 0);
         } else {