killbill-memoizeit

Fixes #138

12/5/2013 10:09:34 PM

Details

diff --git a/api/src/main/java/com/ning/billing/entitlement/EventsStream.java b/api/src/main/java/com/ning/billing/entitlement/EventsStream.java
index b570fd3..8cb930f 100644
--- a/api/src/main/java/com/ning/billing/entitlement/EventsStream.java
+++ b/api/src/main/java/com/ning/billing/entitlement/EventsStream.java
@@ -60,6 +60,8 @@ public interface EventsStream {
 
     Collection<BlockingState> getPendingEntitlementCancellationEvents();
 
+    BlockingState getEntitlementCancellationEvent();
+
     // All blocking states for the account, associated bundle or subscription
     Collection<BlockingState> getBlockingStates();
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
index ac479f7..5576a98 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/DefaultEntitlement.java
@@ -289,15 +289,28 @@ public class DefaultEntitlement extends EntityBase implements Entitlement {
         // Get the latest state from disk
         refresh(callContext);
 
-        if (eventsStream.isEntitlementCancelled() || eventsStream.isSubscriptionCancelled()) {
+        if (eventsStream.isSubscriptionCancelled()) {
             throw new EntitlementApiException(ErrorCode.SUB_CANCEL_BAD_STATE, getId(), EntitlementState.CANCELLED);
         }
 
-        // Reactivate entitlements
-        // See also https://github.com/killbill/killbill/issues/111
         final InternalCallContext contextWithValidAccountRecordId = internalCallContextFactory.createInternalCallContext(getAccountId(), callContext);
-        for (final BlockingState futureCancellation : eventsStream.getPendingEntitlementCancellationEvents()) {
-            blockingStateDao.unactiveBlockingState(futureCancellation.getId(), contextWithValidAccountRecordId);
+        final Collection<BlockingState> pendingEntitlementCancellationEvents = eventsStream.getPendingEntitlementCancellationEvents();
+        if (eventsStream.isEntitlementCancelled()) {
+            final BlockingState cancellationEvent = eventsStream.getEntitlementCancellationEvent();
+            blockingStateDao.unactiveBlockingState(cancellationEvent.getId(), contextWithValidAccountRecordId);
+        } else if (pendingEntitlementCancellationEvents.size() > 0) {
+            // Reactivate entitlements
+            // See also https://github.com/killbill/killbill/issues/111
+            //
+            // Today we only support cancellation at SUBSCRIPTION level (Not ACCOUNT or BUNDLE), so we should really have only
+            // one future event in the list
+            //
+            for (final BlockingState futureCancellation : pendingEntitlementCancellationEvents) {
+                blockingStateDao.unactiveBlockingState(futureCancellation.getId(), contextWithValidAccountRecordId);
+            }
+        } else {
+            // Entitlement is NOT cancelled (or future cancelled), there is nothing to do
+            throw new EntitlementApiException(ErrorCode.SUB_CANCEL_BAD_STATE, getId(), EntitlementState.CANCELLED);
         }
 
         // If billing was previously cancelled, reactivate
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/DefaultEventsStream.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/DefaultEventsStream.java
index 7a735cb..1b28830 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/DefaultEventsStream.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/DefaultEventsStream.java
@@ -190,6 +190,11 @@ public class DefaultEventsStream implements EventsStream {
         return getPendingEntitlementEvents(DefaultEntitlementApi.ENT_STATE_CANCELLED);
     }
 
+    @Override
+    public BlockingState getEntitlementCancellationEvent() {
+        return entitlementCancelEvent;
+    }
+
     public Collection<BlockingState> getPendingEntitlementEvents(final String... types) {
         final List<String> typeList = ImmutableList.<String>copyOf(types);
         return Collections2.<BlockingState>filter(subscriptionEntitlementStates,
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java
index e2ff2d8..caa7178 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/TestDefaultEntitlementApi.java
@@ -62,15 +62,12 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
         testListener.pushExpectedEvent(NextEvent.CREATE);
         final Entitlement addOnEntitlement = entitlementApi.addEntitlement(entitlement.getBundleId(), addOnSpec, initialDate, callContext);
         assertListenerStatus();
-
-        /*
-        // TODO It looks like we don't check if there is a future cancellation. Maybe we should?
         try {
             entitlement.uncancelEntitlement(callContext);
             Assert.fail("Entitlement hasn't been cancelled yet");
         } catch (final EntitlementApiException e) {
             Assert.assertEquals(e.getCode(), ErrorCode.SUB_CANCEL_BAD_STATE.getCode());
-        }*/
+        }
 
         clock.addDays(3);
 
@@ -108,6 +105,40 @@ public class TestDefaultEntitlementApi extends EntitlementTestSuiteWithEmbeddedD
         }
     }
 
+
+    @Test(groups = "slow")
+    public void testUncancelEffectiveCancelledEntitlement() throws AccountApiException, EntitlementApiException {
+        final LocalDate initialDate = new LocalDate(2013, 8, 7);
+        clock.setDay(initialDate);
+
+        final Account account = accountApi.createAccount(getAccountData(7), callContext);
+
+        final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+        // Keep the same object for the whole test, to make sure we refresh its state before r/w calls
+        testListener.pushExpectedEvent(NextEvent.CREATE);
+        final Entitlement entitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), initialDate, callContext);
+        assertListenerStatus();
+
+        testListener.pushExpectedEvent(NextEvent.PHASE);
+        clock.addDays(30);
+        assertListenerStatus();
+        subscriptionInternalApi.setChargedThroughDate(entitlement.getId(), clock.getUTCNow().plusMonths(1), internalCallContext);
+
+        final LocalDate entitlementCancelledDate = clock.getToday(account.getTimeZone());
+        testListener.pushExpectedEvent(NextEvent.BLOCK);
+        final Entitlement cancelledEntitlement = entitlement.cancelEntitlementWithDateOverrideBillingPolicy(clock.getToday(account.getTimeZone()), BillingActionPolicy.END_OF_TERM, callContext);
+        assertListenerStatus();
+        Assert.assertEquals(cancelledEntitlement.getEffectiveEndDate(), entitlementCancelledDate);
+
+        testListener.pushExpectedEvent(NextEvent.UNCANCEL);
+        cancelledEntitlement.uncancelEntitlement(callContext);
+        assertListenerStatus();
+
+        final Entitlement reactivatedEntitlement = entitlementApi.getEntitlementForId(cancelledEntitlement.getId(), callContext);
+        Assert.assertNull(reactivatedEntitlement.getEffectiveEndDate());
+    }
+
     @Test(groups = "slow")
     public void testCreateEntitlementWithCheck() throws AccountApiException, EntitlementApiException {
         final LocalDate initialDate = new LocalDate(2013, 8, 7);