killbill-aplcache

entitlement: fix bug in ProxyBlockingStateDao If an entitlement

11/16/2013 8:49:50 AM

Details

diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java b/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java
index 9150555..bb9431c 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/dao/ProxyBlockingStateDao.java
@@ -35,8 +35,10 @@ import com.ning.billing.callcontext.InternalCallContext;
 import com.ning.billing.callcontext.InternalTenantContext;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.clock.Clock;
+import com.ning.billing.entitlement.EntitlementService;
 import com.ning.billing.entitlement.api.BlockingState;
 import com.ning.billing.entitlement.api.BlockingStateType;
+import com.ning.billing.entitlement.api.DefaultEntitlementApi;
 import com.ning.billing.entitlement.api.Entitlement.EntitlementState;
 import com.ning.billing.entitlement.api.EntitlementApiException;
 import com.ning.billing.entitlement.engine.core.EventsStream;
@@ -228,10 +230,12 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
 
             // Inject the extra blocking states into the stream if needed
             for (final BlockingState blockingState : blockingStatesNotOnDisk) {
-                // In case we're coming from getBlockingHistoryForService / getBlockingAll, make sure we don't add
-                // blocking states for other add-ons on that base subscription
-                if (blockingStateType == null ||
-                    (BlockingStateType.SUBSCRIPTION.equals(blockingStateType) && blockingState.getBlockedId().equals(blockableId))) {
+                if ((blockingStateType == null ||
+                     // In case we're coming from getBlockingHistoryForService / getBlockingAll, make sure we don't add
+                     // blocking states for other add-ons on that base subscription
+                     (BlockingStateType.SUBSCRIPTION.equals(blockingStateType) && blockingState.getBlockedId().equals(blockableId))) &&
+                    // If this entitlement is actually already cancelled, don't add the cancellation event we computed from subscription events (wrong)
+                    !isEntitlementCancelled(blockingState.getBlockedId(), blockingStatesOnDiskCopy)) {
                     final BlockingStateModelDao blockingStateModelDao = new BlockingStateModelDao(blockingState, now, now);
                     blockingStatesOnDiskCopy.add(BlockingStateModelDao.toBlockingState(blockingStateModelDao));
                 }
@@ -241,4 +245,19 @@ public class ProxyBlockingStateDao implements BlockingStateDao {
         // Return the sorted list
         return BLOCKING_STATE_ORDERING.immutableSortedCopy(blockingStatesOnDiskCopy);
     }
+
+    private boolean isEntitlementCancelled(final UUID blockedId, final Iterable<BlockingState> blockingStates) {
+        // If this entitlement is already cancelled, there is nothing to do
+        return Iterables.<BlockingState>tryFind(blockingStates,
+                                                new Predicate<BlockingState>() {
+                                                    @Override
+                                                    public boolean apply(final BlockingState input) {
+                                                        return input.getBlockedId().equals(blockedId) &&
+                                                               BlockingStateType.SUBSCRIPTION.equals(input.getType()) &&
+                                                               EntitlementService.ENTITLEMENT_SERVICE_NAME.equals(input.getService()) &&
+                                                               DefaultEntitlementApi.ENT_STATE_CANCELLED.equals(input.getStateName());
+                                                    }
+                                                })
+                        .orNull() != null;
+    }
 }
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 5398bce..5bf86ae 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
@@ -40,7 +40,6 @@ import com.ning.billing.entitlement.api.BlockingState;
 import com.ning.billing.entitlement.api.BlockingStateType;
 import com.ning.billing.entitlement.api.DefaultEntitlement;
 import com.ning.billing.entitlement.api.DefaultEntitlementApi;
-import com.ning.billing.entitlement.api.Entitlement;
 import com.ning.billing.entitlement.api.Entitlement.EntitlementActionPolicy;
 import com.ning.billing.entitlement.api.EntitlementApiException;
 import com.ning.billing.entitlement.dao.BlockingStateSqlDao;
@@ -124,6 +123,22 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
         checkBlockingStatesDAO(cancelledBaseEntitlement, cancelledAddOnEntitlement, baseEffectiveCancellationOrChangeDate, true);
     }
 
+    @Test(groups = "slow", description = "Verify add-ons blocking states are not impacted for add-on IMM cancellations")
+    public void testCancellationBaseEOTAddOnIMM() throws Exception {
+        // Cancel the base plan
+        final DefaultEntitlement cancelledBaseEntitlement = (DefaultEntitlement) baseEntitlement.cancelEntitlementWithPolicyOverrideBillingPolicy(EntitlementActionPolicy.END_OF_TERM, BillingActionPolicy.END_OF_TERM, callContext);
+        // No blocking event (EOT)
+        assertListenerStatus();
+
+        // Cancel the add-on
+        testListener.pushExpectedEvents(NextEvent.CANCEL, NextEvent.BLOCK);
+        final DefaultEntitlement cancelledAddOnEntitlement = (DefaultEntitlement) addOnEntitlement.cancelEntitlementWithPolicyOverrideBillingPolicy(EntitlementActionPolicy.IMMEDIATE, BillingActionPolicy.IMMEDIATE, callContext);
+        assertListenerStatus();
+
+        // Verify the blocking states API doesn't mix the dates (all blocking states are on disk)
+        checkBlockingStatesDAO(cancelledBaseEntitlement, cancelledAddOnEntitlement, baseEffectiveCancellationOrChangeDate, clock.getUTCToday(), 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
@@ -319,12 +334,17 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
     }
 
     // Test the DAO
-    private void checkBlockingStatesDAO(final Entitlement baseEntitlement, final Entitlement addOnEntitlement, final LocalDate effectiveCancellationDate, final boolean isBaseCancelled) {
+    private void checkBlockingStatesDAO(final DefaultEntitlement baseEntitlement, final DefaultEntitlement addOnEntitlement, final LocalDate effectiveCancellationDate, final boolean isBaseCancelled) {
+        checkBlockingStatesDAO(baseEntitlement, addOnEntitlement, effectiveCancellationDate, effectiveCancellationDate, isBaseCancelled);
+    }
+
+    // Test the DAO
+    private void checkBlockingStatesDAO(final DefaultEntitlement baseEntitlement, final DefaultEntitlement addOnEntitlement, final LocalDate effectiveBaseCancellationDate, final LocalDate effectiveAddOnCancellationDate, final boolean isBaseCancelled) {
         final List<BlockingState> blockingStatesForBaseEntitlement = blockingStateDao.getBlockingAll(baseEntitlement.getId(), BlockingStateType.SUBSCRIPTION, internalCallContext);
         Assert.assertEquals(blockingStatesForBaseEntitlement.size(), isBaseCancelled ? 1 : 0);
         if (isBaseCancelled) {
             Assert.assertEquals(blockingStatesForBaseEntitlement.get(0).getBlockedId(), baseEntitlement.getId());
-            Assert.assertEquals(blockingStatesForBaseEntitlement.get(0).getEffectiveDate().toLocalDate(), effectiveCancellationDate);
+            Assert.assertEquals(blockingStatesForBaseEntitlement.get(0).getEffectiveDate().toLocalDate(), effectiveBaseCancellationDate);
             Assert.assertEquals(blockingStatesForBaseEntitlement.get(0).getType(), BlockingStateType.SUBSCRIPTION);
             Assert.assertEquals(blockingStatesForBaseEntitlement.get(0).getService(), EntitlementService.ENTITLEMENT_SERVICE_NAME);
             Assert.assertEquals(blockingStatesForBaseEntitlement.get(0).getStateName(), DefaultEntitlementApi.ENT_STATE_CANCELLED);
@@ -333,7 +353,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
         final List<BlockingState> blockingStatesForAddOn = blockingStateDao.getBlockingAll(addOnEntitlement.getId(), BlockingStateType.SUBSCRIPTION, internalCallContext);
         Assert.assertEquals(blockingStatesForAddOn.size(), 1);
         Assert.assertEquals(blockingStatesForAddOn.get(0).getBlockedId(), addOnEntitlement.getId());
-        Assert.assertEquals(blockingStatesForAddOn.get(0).getEffectiveDate().toLocalDate(), effectiveCancellationDate);
+        Assert.assertEquals(blockingStatesForAddOn.get(0).getEffectiveDate().toLocalDate(), effectiveAddOnCancellationDate);
         Assert.assertEquals(blockingStatesForAddOn.get(0).getType(), BlockingStateType.SUBSCRIPTION);
         Assert.assertEquals(blockingStatesForAddOn.get(0).getService(), EntitlementService.ENTITLEMENT_SERVICE_NAME);
         Assert.assertEquals(blockingStatesForAddOn.get(0).getStateName(), DefaultEntitlementApi.ENT_STATE_CANCELLED);