killbill-memoizeit

Fixes #144 While fixing the issue and adding the test case,

1/4/2014 1:56:49 AM

Details

diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index 71b9f62..cccafc3 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -918,7 +918,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         invoiceChecker.checkInvoice(account.getId(), 4, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 7, 31), new LocalDate(2012, 8, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
 
         // Fully adjust all invoices
-        final List<Invoice> invoicesToAdjust = ImmutableList.<Invoice>copyOf(invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext));
+        final List<Invoice> invoicesToAdjust = getUnpaidInvoicesOrderFromRecent();
         for (int i = 0; i < invoicesToAdjust.size(); i++) {
             if (i == invoicesToAdjust.size() - 1) {
                 fullyAdjustInvoiceAndCheckForCompletion(account, invoicesToAdjust.get(i), NextEvent.BLOCK, NextEvent.INVOICE_ADJUSTMENT);
@@ -947,15 +947,7 @@ public class TestOverdueIntegration extends TestOverdueBase {
         // Upon paying the last invoice, the overdue system will clear the state and notify invoice that it should re-generate a new invoice
         // for the part hat was unblocked, which explains why on the last payment we expect an additional invoice and payment.
         //
-        final Collection<Invoice> invoices = invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext);
-        // Sort in reverse order to first pay most recent invoice-- that way overdue state may only flip when we reach the last one.
-        final List<Invoice> sortedInvoices = new LinkedList<Invoice>(invoices);
-        Collections.sort(sortedInvoices, new Comparator<Invoice>() {
-            @Override
-            public int compare(final Invoice i1, final Invoice i2) {
-                return i2.getInvoiceDate().compareTo(i1.getInvoiceDate());
-            }
-        });
+        final List<Invoice> sortedInvoices = getUnpaidInvoicesOrderFromRecent();
 
         int remainingUnpaidInvoices = sortedInvoices.size();
         for (final Invoice invoice : sortedInvoices) {
@@ -971,6 +963,19 @@ public class TestOverdueIntegration extends TestOverdueBase {
         checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
     }
 
+    private List<Invoice> getUnpaidInvoicesOrderFromRecent() {
+        final Collection<Invoice> invoices = invoiceUserApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday(), callContext);
+        // Sort in reverse order to first pay most recent invoice-- that way overdue state may only flip when we reach the last one.
+        final List<Invoice> sortedInvoices = new LinkedList<Invoice>(invoices);
+        Collections.sort(sortedInvoices, new Comparator<Invoice>() {
+            @Override
+            public int compare(final Invoice i1, final Invoice i2) {
+                return i2.getInvoiceDate().compareTo(i1.getInvoiceDate());
+            }
+        });
+        return sortedInvoices;
+    }
+
     private void checkChangePlanWithOverdueState(final Entitlement entitlement, final boolean shouldFail, final boolean expectedPayment) {
         if (shouldFail) {
             try {
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
index 8c1f241..15510e8 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueWithSubscriptionCancellation.java
@@ -24,6 +24,7 @@ import org.testng.annotations.Test;
 
 import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
+import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.DefaultEntitlement;
 import com.ning.billing.entitlement.api.Entitlement.EntitlementState;
@@ -74,22 +75,37 @@ public class TestOverdueWithSubscriptionCancellation extends TestOverdueBase {
         invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 5, 1), callContext);
 
+        final DefaultEntitlement addOn1 = addAOEntitlementAndCheckForCompletion(baseEntitlement.getBundleId(), "Holster", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+
+        final DefaultEntitlement addOn2 = addAOEntitlementAndCheckForCompletion(baseEntitlement.getBundleId(), "Holster", ProductCategory.ADD_ON, BillingPeriod.MONTHLY, NextEvent.CREATE, NextEvent.INVOICE);
+
+        // Cancel addOn1 one day after
+        clock.addDays(1);
+        cancelEntitlementAndCheckForCompletion(addOn1, clock.getUTCNow(), NextEvent.BLOCK, NextEvent.CANCEL);
+
         // DAY 30 have to get out of trial before first payment
-        addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
+        addDaysAndCheckForCompletion(29, NextEvent.PHASE,  NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
 
-        invoiceChecker.checkInvoice(account.getId(), 2, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
         invoiceChecker.checkChargedThroughDate(baseEntitlement.getId(), new LocalDate(2012, 6, 30), callContext);
 
+
         // Should still be in clear state
         checkODState(DefaultBlockingState.CLEAR_STATE_NAME);
 
+
         // DAY 36 -- RIGHT AFTER OD1 (two block events, for the cancellation and the OD1 state)
-        addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.INVOICE_ADJUSTMENT);
+        // One BLOCK event is for the overdue state transition
+        // The 2 other BLOCK are for the entitlement blocking states for both base plan and addOn2
+        addDaysAndCheckForCompletion(6, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.INVOICE_ADJUSTMENT);
 
         // Should be in OD1
         checkODState("OD1");
 
         final SubscriptionBase cancelledBaseSubscription = ((DefaultEntitlement) entitlementApi.getEntitlementForId(baseEntitlement.getId(), callContext)).getSubscriptionBase();
         assertTrue(cancelledBaseSubscription.getState() == EntitlementState.CANCELLED);
+
+        final SubscriptionBase cancelledAddon1= ((DefaultEntitlement) entitlementApi.getEntitlementForId(addOn1.getId(), callContext)).getSubscriptionBase();
+        assertTrue(cancelledAddon1.getState() == EntitlementState.CANCELLED);
+
     }
 }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java b/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
index d36682c..3b55ab2 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
@@ -132,12 +132,15 @@ public class InvoiceChecker {
             if (expectedLocalCTD == null) {
                 assertNull(subscription.getChargedThroughDate());
             } else {
+                assertTrue(expectedLocalCTD.compareTo(subscription.getChargedThroughDate().toLocalDate()) == 0);
+                /*
                 final DateTime expectedCTD = expectedLocalCTD.toDateTime(new LocalTime(subscription.getStartDate().getMillis(), DateTimeZone.UTC), DateTimeZone.UTC);
                 final String msg = String.format("Checking CTD for entitlement %s : expectedLocalCTD = %s => expectedCTD = %s, got %s",
                                                  entitlementId, expectedLocalCTD, expectedCTD, subscription.getChargedThroughDate());
                 log.info(msg);
                 assertNotNull(subscription.getChargedThroughDate());
                 assertTrue(subscription.getChargedThroughDate().compareTo(expectedCTD) == 0, msg);
+                */
             }
         } catch (EntitlementApiException e) {
             fail("Failed to retrieve entitlement for " + entitlementId);
diff --git a/beatrix/src/test/resources/catalogSample.xml b/beatrix/src/test/resources/catalogSample.xml
index 8aa1247..d6531b1 100644
--- a/beatrix/src/test/resources/catalogSample.xml
+++ b/beatrix/src/test/resources/catalogSample.xml
@@ -47,6 +47,7 @@
             <available>
                 <addonProduct>Telescopic-Scope</addonProduct>
                 <addonProduct>Laser-Scope</addonProduct>
+                <addonProduct>Holster</addonProduct>
             </available>
         </product>
         <product name="Assault-Rifle">
@@ -722,7 +723,7 @@
                 <duration>
                     <unit>UNLIMITED</unit>
                 </duration>
-                <billingPeriod>ANNUAL</billingPeriod>
+                <billingPeriod>MONTHLY</billingPeriod>
                 <recurringPrice>
                     <price>
                         <currency>USD</currency>
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/block/DefaultBlockingChecker.java b/entitlement/src/main/java/com/ning/billing/entitlement/block/DefaultBlockingChecker.java
index 9d87e3c..e41a5e1 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/block/DefaultBlockingChecker.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/block/DefaultBlockingChecker.java
@@ -77,6 +77,38 @@ public class DefaultBlockingChecker implements BlockingChecker {
         public boolean isBlockBilling() {
             return blockBilling;
         }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof DefaultBlockingAggregator)) {
+                return false;
+            }
+
+            final DefaultBlockingAggregator that = (DefaultBlockingAggregator) o;
+
+            if (blockBilling != that.blockBilling) {
+                return false;
+            }
+            if (blockChange != that.blockChange) {
+                return false;
+            }
+            if (blockEntitlement != that.blockEntitlement) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = (blockChange ? 1 : 0);
+            result = 31 * result + (blockEntitlement ? 1 : 0);
+            result = 31 * result + (blockBilling ? 1 : 0);
+            return result;
+        }
     }
 
     private final SubscriptionBaseInternalApi subscriptionApi;
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EntitlementUtils.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EntitlementUtils.java
index 113447c..87a20c7 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EntitlementUtils.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/EntitlementUtils.java
@@ -34,6 +34,7 @@ import com.ning.billing.callcontext.InternalCallContext;
 import com.ning.billing.callcontext.InternalTenantContext;
 import com.ning.billing.clock.Clock;
 import com.ning.billing.entitlement.DefaultEntitlementService;
+import com.ning.billing.entitlement.EntitlementService;
 import com.ning.billing.entitlement.EventsStream;
 import com.ning.billing.entitlement.api.BlockingApiException;
 import com.ning.billing.entitlement.api.BlockingState;
@@ -94,7 +95,7 @@ public class EntitlementUtils {
 
         final BlockingAggregator currentState = getBlockingStateFor(state.getBlockedId(), state.getType(), context);
         if (previousState != null && currentState != null) {
-            postBlockingTransitionEvent(state.getId(), state.getEffectiveDate(), state.getBlockedId(), state.getType(), previousState, currentState, context);
+            postBlockingTransitionEvent(state.getId(), state.getEffectiveDate(), state.getBlockedId(), state.getType(), state.getService(), previousState, currentState, context);
         }
     }
 
@@ -127,7 +128,7 @@ public class EntitlementUtils {
     }
 
     private void postBlockingTransitionEvent(final UUID blockingStateId, final DateTime effectiveDate, final UUID blockableId, final BlockingStateType type,
-                                             final BlockingAggregator previousState, final BlockingAggregator currentState,
+                                             final String serviceName, final BlockingAggregator previousState, final BlockingAggregator currentState,
                                              final InternalCallContext context) {
         final boolean isTransitionToBlockedBilling = !previousState.isBlockBilling() && currentState.isBlockBilling();
         final boolean isTransitionToUnblockedBilling = previousState.isBlockBilling() && !currentState.isBlockBilling();
@@ -143,12 +144,18 @@ public class EntitlementUtils {
             recordFutureNotification(effectiveDate, notificationEvent, context);
         } else {
             // TODO Do we want to send a DefaultEffectiveEntitlementEvent for entitlement specific blocking states?
-            final BusEvent event = new DefaultBlockingTransitionInternalEvent(blockableId, type,
-                                                                              isTransitionToBlockedBilling, isTransitionToUnblockedBilling,
-                                                                              isTransitionToBlockedEntitlement, isTransitionToUnblockedEntitlement,
-                                                                              context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
 
-            postBusEvent(event);
+            // Don't post if nothing has changed for entitlement-service
+           if (! serviceName.equals(EntitlementService.ENTITLEMENT_SERVICE_NAME) || ! previousState.equals(currentState)) {
+                final BusEvent event = new DefaultBlockingTransitionInternalEvent(blockableId, type,
+                                                                                  isTransitionToBlockedBilling, isTransitionToUnblockedBilling,
+                                                                                  isTransitionToBlockedEntitlement, isTransitionToUnblockedEntitlement,
+                                                                                  context.getAccountRecordId(), context.getTenantRecordId(), context.getUserToken());
+                postBusEvent(event);
+           } else {
+               System.out.println("**********   SKIPPING EVENT ");
+           }
+
         }
     }
 
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 13be1fe..6f916e0 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
@@ -351,7 +351,7 @@ public class TestEntitlementUtils extends EntitlementTestSuiteWithEmbeddedDB {
         Assert.assertEquals(entitlementApi.getEntitlementForId(addOnEntitlement.getId(), callContext).getEffectiveEndDate(), addOn1CancellationDate);
         Assert.assertEquals(entitlementApi.getEntitlementForId(addOn2Entitlement.getId(), callContext).getEffectiveEndDate(), baseCancellationDate);
 
-        testListener.pushExpectedEvents(NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK);
+        testListener.pushExpectedEvents(NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.CANCEL, NextEvent.BLOCK, NextEvent.BLOCK, NextEvent.BLOCK);
         clock.setDay(new LocalDate(2013, 10, 30));
         assertListenerStatus();
 
diff --git a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
index 78fe7fe..01ad416 100644
--- a/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
+++ b/overdue/src/main/java/com/ning/billing/overdue/applicator/OverdueStateApplicator.java
@@ -114,7 +114,6 @@ public class OverdueStateApplicator {
         this.bus = bus;
     }
 
-
     public void apply(final OverdueStateSet overdueStateSet, final BillingState billingState,
                       final Account account, final OverdueState previousOverdueState,
                       final OverdueState nextOverdueState, final InternalCallContext context) throws OverdueException {
@@ -170,7 +169,6 @@ public class OverdueStateApplicator {
         }
     }
 
-
     public void clear(final Account overdueable, final OverdueState previousOverdueState, final OverdueState clearState, final InternalCallContext context) throws OverdueException {
 
         log.debug("OverdueStateApplicator:clear : time = " + clock.getUTCNow() + ", previousState = " + previousOverdueState.getName());
@@ -260,13 +258,17 @@ public class OverdueStateApplicator {
 
             final UUID tenantId = nonEntityDao.retrieveIdFromObject(context.getTenantRecordId(), ObjectType.TENANT);
             for (final Entitlement cur : toBeCancelled) {
-                cur.cancelEntitlementWithDateOverrideBillingPolicy(new LocalDate(clock.getUTCNow(), account.getTimeZone()), actionPolicy, context.toCallContext(tenantId));
+                try {
+                    cur.cancelEntitlementWithDateOverrideBillingPolicy(new LocalDate(clock.getUTCNow(), account.getTimeZone()), actionPolicy, context.toCallContext(tenantId));
+                } catch (EntitlementApiException e) {
+                    // If subscription has already been cancelled, there is nothing to do so we can ignore
+                    if (e.getCode() != ErrorCode.SUB_CANCEL_BAD_STATE.getCode()) {
+                        throw new OverdueException(e);
+                    }
+                }
             }
         } catch (EntitlementApiException e) {
-            // If subscription has already been cancelled, there is nothing to do so we can ignore
-            if (e.getCode() != ErrorCode.SUB_CANCEL_BAD_STATE.getCode()) {
-                throw new OverdueException(e);
-            }
+            throw new OverdueException(e);
         }
     }
 
diff --git a/subscription/src/main/java/com/ning/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java b/subscription/src/main/java/com/ning/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
index d9202e9..62d2acc 100644
--- a/subscription/src/main/java/com/ning/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
+++ b/subscription/src/main/java/com/ning/billing/subscription/api/user/DefaultSubscriptionBaseApiService.java
@@ -231,7 +231,9 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
         dao.cancelSubscription(subscription, cancelEvent, internalCallContext, 0);
         subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), catalogService.getFullCatalog());
 
-        cancelAddOnsIfRequired(subscription, effectiveDate, internalCallContext);
+        if (subscription.getCategory() == ProductCategory.BASE) {
+            cancelAddOnsIfRequired(subscription, effectiveDate, internalCallContext);
+        }
 
         final boolean isImmediate = subscription.getState() == EntitlementState.CANCELLED;
         return isImmediate;
@@ -384,7 +386,9 @@ public class DefaultSubscriptionBaseApiService implements SubscriptionBaseApiSer
         dao.changePlan(subscription, changeEvents, internalCallContext);
         subscription.rebuildTransitions(dao.getEventsForSubscription(subscription.getId(), internalCallContext), catalogService.getFullCatalog());
 
-        cancelAddOnsIfRequired(subscription, effectiveDate, internalCallContext);
+        if (subscription.getCategory() == ProductCategory.BASE) {
+            cancelAddOnsIfRequired(subscription, effectiveDate, internalCallContext);
+        }
 
         final boolean isChangeImmediate = subscription.getCurrentPlan().getProduct().getName().equals(newProductName) &&
                                           subscription.getCurrentPlan().getBillingPeriod() == newBillingPeriod;