killbill-uncached

Details

diff --git a/catalog/src/test/resources/WeaponsHire.xml b/catalog/src/test/resources/WeaponsHire.xml
index 01d7cb4..3d36b9d 100644
--- a/catalog/src/test/resources/WeaponsHire.xml
+++ b/catalog/src/test/resources/WeaponsHire.xml
@@ -51,13 +51,13 @@ Use Cases to do:
 	<products>
 		<product name="Pistol">
 			<category>BASE</category>
-			<available>
-				<addonProduct>Telescopic-Scope</addonProduct>
-				<addonProduct>Laser-Scope</addonProduct>
-			</available>
 		</product>
 		<product name="Shotgun">
 			<category>BASE</category>
+            <available>
+                <addonProduct>Telescopic-Scope</addonProduct>
+                <addonProduct>Laser-Scope</addonProduct>
+            </available>
 		</product>
 		<product name="Assault-Rifle">
 			<category>BASE</category>
@@ -91,33 +91,18 @@ Use Cases to do:
 				<phaseType>TRIAL</phaseType>
 				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
-				<toProduct>Pistol</toProduct>
-				<policy>END_OF_TERM</policy>
-			</changePolicyCase>
+            <changePolicyCase> 
+                <toProduct>Assault-Rifle</toProduct>
+                <policy>IMMEDIATE</policy>
+            </changePolicyCase>
+            <changePolicyCase> 
+                <fromProduct>Pistol</fromProduct>            
+                <toProduct>Shotgun</toProduct>
+                <policy>IMMEDIATE</policy>
+            </changePolicyCase>
 			<changePolicyCase> 
 				<toPriceList>rescue</toPriceList>
 				<policy>END_OF_TERM</policy>
-			</changePolicyCase>		
-			<changePolicyCase> 
-				<fromProduct>Pistol</fromProduct>
-				<toProduct>Shotgun</toProduct>
-				<policy>IMMEDIATE</policy>
-			</changePolicyCase>
-			<changePolicyCase> 
-				<fromProduct>Assault-Rifle</fromProduct>
-				<toProduct>Shotgun</toProduct>
-				<policy>END_OF_TERM</policy>
-			</changePolicyCase>
-			<changePolicyCase> 
-				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
-				<toProduct>Assault-Rifle</toProduct>
-				<toBillingPeriod>MONTHLY</toBillingPeriod>
-				<policy>END_OF_TERM</policy>
-			</changePolicyCase>
-			<changePolicyCase> 
-				<toProduct>Assault-Rifle</toProduct>
-				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
 			<changePolicyCase> 
 				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
@@ -135,9 +120,6 @@ Use Cases to do:
 		</changePolicy>
 		<changeAlignment>
 			<changeAlignmentCase>
-				<alignment>START_OF_SUBSCRIPTION</alignment>
-			</changeAlignmentCase>
-			<changeAlignmentCase>
 				<toPriceList>rescue</toPriceList>
 				<alignment>CHANGE_OF_PLAN</alignment>
 			</changeAlignmentCase>
@@ -146,20 +128,27 @@ Use Cases to do:
 				<toPriceList>rescue</toPriceList>
 				<alignment>CHANGE_OF_PRICELIST</alignment>
 			</changeAlignmentCase>
+            <changeAlignmentCase>
+                <alignment>START_OF_SUBSCRIPTION</alignment>
+            </changeAlignmentCase>
 		</changeAlignment>
 		<cancelPolicy>
 			<cancelPolicyCase>
-				<policy>END_OF_TERM</policy>
-			</cancelPolicyCase>
-			<cancelPolicyCase>
 				<phaseType>TRIAL</phaseType>
 				<policy>IMMEDIATE</policy>
 			</cancelPolicyCase>
+            <cancelPolicyCase>
+                <policy>END_OF_TERM</policy>
+            </cancelPolicyCase>
 		</cancelPolicy>
 		<createAlignment>
-			<createAlignmentCase>
-				<alignment>START_OF_BUNDLE</alignment>
-			</createAlignmentCase>
+		    <createAlignmentCase>
+		        <product>Laser-Scope</product>
+                <alignment>START_OF_SUBSCRIPTION</alignment>
+            </createAlignmentCase>
+            <createAlignmentCase>
+                <alignment>START_OF_BUNDLE</alignment>
+            </createAlignmentCase>
 		</createAlignment>
 		<billingAlignment>
 		<billingAlignmentCase>
@@ -447,6 +436,20 @@ Use Cases to do:
 		</plan>
 		<plan name="laser-scope-monthly">
 		<product>Laser-Scope</product>
+           <initialPhases>
+              <phase type="DISCOUNT">
+                 <duration>
+                    <unit>MONTHS</unit>
+                    <number>1</number>
+                  </duration>
+                 <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                      <price><currency>USD</currency><value>999.95</value></price>                             
+                      <price><currency>EUR</currency><value>499.95</value></price>
+                      <price><currency>GBP</currency><value>999.95</value></price>
+                      </recurringPrice>
+                </phase>
+            </initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
 					<unit>UNLIMITED</unit>
@@ -461,6 +464,20 @@ Use Cases to do:
 		</plan>
 		<plan name="telescopic-scope-monthly">
 			<product>Telescopic-Scope</product>
+			<initialPhases>
+              <phase type="DISCOUNT">
+                 <duration>
+                    <unit>MONTHS</unit>
+                    <number>1</number>
+                  </duration>
+                 <billingPeriod>MONTHLY</billingPeriod>
+                    <recurringPrice>
+                      <price><currency>USD</currency><value>399.95</value></price>                             
+                      <price><currency>EUR</currency><value>299.95</value></price>
+                      <price><currency>GBP</currency><value>399.95</value></price>
+                      </recurringPrice>
+                </phase>
+            </initialPhases>
 			<finalPhase type="EVERGREEN">
 				<duration>
 					<unit>UNLIMITED</unit>
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
index ebee20e..51010db 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/api/user/DefaultEntitlementUserApi.java
@@ -122,7 +122,7 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
             }
 
             DateTime bundleStartDate = null;
-            Subscription baseSubscription = dao.getBaseSubscription(bundleId);
+            SubscriptionData baseSubscription = (SubscriptionData) dao.getBaseSubscription(bundleId);
             switch(plan.getProduct().getCategory()) {
             case BASE:
                 if (baseSubscription != null) {
@@ -143,11 +143,11 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
             }
 
             SubscriptionData subscription = apiService.createPlan(new SubscriptionBuilder()
-            .setId(UUID.randomUUID())
-            .setBundleId(bundleId)
-            .setCategory(plan.getProduct().getCategory())
-            .setBundleStartDate(bundleStartDate)
-            .setStartDate(effectiveDate),
+                .setId(UUID.randomUUID())
+                .setBundleId(bundleId)
+                .setCategory(plan.getProduct().getCategory())
+                .setBundleStartDate(bundleStartDate)
+                .setStartDate(effectiveDate),
             plan, spec.getPhaseType(), realPriceList, requestedDate, effectiveDate, now);
 
             return subscription;
@@ -156,7 +156,7 @@ public class DefaultEntitlementUserApi implements EntitlementUserApi {
         }
     }
 
-    private void checkAddonCreationRights(Subscription baseSubscription, Plan targetAddOnPlan)
+    private void checkAddonCreationRights(SubscriptionData baseSubscription, Plan targetAddOnPlan)
         throws EntitlementUserApiException, CatalogApiException {
 
         if (baseSubscription.getState() != SubscriptionState.ACTIVE) {
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
index 66635c3..94c35f9 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/addon/AddonUtils.java
@@ -23,6 +23,9 @@ import com.ning.billing.catalog.api.CatalogService;
 import com.ning.billing.catalog.api.Plan;
 import com.ning.billing.catalog.api.Product;
 import com.ning.billing.entitlement.api.user.Subscription;
+import com.ning.billing.entitlement.api.user.Subscription.SubscriptionState;
+import com.ning.billing.entitlement.api.user.SubscriptionData;
+import com.ning.billing.entitlement.api.user.SubscriptionTransition;
 
 public class AddonUtils {
 
@@ -34,7 +37,11 @@ public class AddonUtils {
         this.catalogService = catalogService;
     }
 
-    public boolean isAddonAvailable(Subscription baseSubscription, Plan targetAddOnPlan) {
+    public boolean isAddonAvailable(SubscriptionData baseSubscription, Plan targetAddOnPlan) {
+
+        if (baseSubscription.getState() == SubscriptionState.CANCELLED) {
+            return false;
+        }
 
         Product targetAddonProduct = targetAddOnPlan.getProduct();
         Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
@@ -48,7 +55,12 @@ public class AddonUtils {
         return false;
     }
 
-    public boolean isAddonIncluded(Subscription baseSubscription, Plan targetAddOnPlan) {
+    public boolean isAddonIncluded(SubscriptionData baseSubscription, Plan targetAddOnPlan) {
+
+        if (baseSubscription.getState() == SubscriptionState.CANCELLED) {
+            return false;
+        }
+
         Product targetAddonProduct = targetAddOnPlan.getProduct();
         Product baseProduct = baseSubscription.getCurrentPlan().getProduct();
 
diff --git a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
index f03bcbc..73e488b 100644
--- a/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
+++ b/entitlement/src/main/java/com/ning/billing/entitlement/engine/core/Engine.java
@@ -76,7 +76,6 @@ public class Engine implements EventListener, EntitlementService {
     private final EntitlementBillingApi billingApi;
     private final EntitlementTestApi testApi;
     private final EntitlementMigrationApi migrationApi;
-    private final CatalogService catalogService;
     private final AddonUtils addonUtils;
     private final EventBus eventBus;
 
@@ -86,8 +85,8 @@ public class Engine implements EventListener, EntitlementService {
     public Engine(Clock clock, EntitlementDao dao, EventNotifier apiEventProcessor,
             PlanAligner planAligner, EntitlementConfig config, DefaultEntitlementUserApi userApi,
             DefaultEntitlementBillingApi billingApi, DefaultEntitlementTestApi testApi,
-            DefaultEntitlementMigrationApi migrationApi, CatalogService catalogService,
-            AddonUtils addonUtils, EventBus eventBus) {
+            DefaultEntitlementMigrationApi migrationApi, AddonUtils addonUtils, EventBus eventBus) {
+
         super();
         this.clock = clock;
         this.dao = dao;
@@ -97,7 +96,6 @@ public class Engine implements EventListener, EntitlementService {
         this.testApi = testApi;
         this.billingApi = billingApi;
         this.migrationApi = migrationApi;
-        this.catalogService = catalogService;
         this.addonUtils = addonUtils;
         this.eventBus = eventBus;
 
@@ -162,7 +160,7 @@ public class Engine implements EventListener, EntitlementService {
             onPhaseEvent(subscription);
         } else if (event.getType() == EventType.API_USER &&
                 subscription.getCategory() == ProductCategory.BASE) {
-            onBasePlanEvent(subscription, event);
+            onBasePlanEvent(subscription, (ApiEvent) event);
         }
         try {
             eventBus.post(subscription.getTransitionFromEvent(event));
@@ -222,11 +220,11 @@ public class Engine implements EventListener, EntitlementService {
         }
     }
 
-    private void onBasePlanEvent(SubscriptionData baseSubscription, EntitlementEvent event) {
+    private void onBasePlanEvent(SubscriptionData baseSubscription, ApiEvent event) {
 
         DateTime now = clock.getUTCNow();
 
-        List<Subscription> subscriptions = dao.getSubscriptions(baseSubscription.getId());
+        List<Subscription> subscriptions = dao.getSubscriptions(baseSubscription.getBundleId());
         Iterator<Subscription> it = subscriptions.iterator();
         while (it.hasNext()) {
             SubscriptionData cur = (SubscriptionData) it.next();
diff --git a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
index a6536f4..d7b9bf4 100644
--- a/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
+++ b/entitlement/src/test/java/com/ning/billing/entitlement/api/user/TestUserApiAddOn.java
@@ -68,9 +68,7 @@ public class TestUserApiAddOn extends TestApiBase {
             BillingPeriod aoTerm = BillingPeriod.MONTHLY;
             String aoPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
 
-            DateTime beforeAOCreation = clock.getUTCNow();
             SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
-            DateTime afterAOCreation = clock.getUTCNow();
 
             testListener.reset();
             testListener.pushExpectedEvent(NextEvent.PHASE);
@@ -91,19 +89,10 @@ public class TestUserApiAddOn extends TestApiBase {
             // FUTURE CANCELLATION
             baseSubscription.cancel(now, false);
 
-            // REFETCH AO SUBSCRIPTION AND CHECK THIS IS FUTURE CANCELLED
+            // REFETCH AO SUBSCRIPTION AND CHECK THIS IS ACTIVE
             aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
             assertEquals(aoSubscription.getState(), SubscriptionState.ACTIVE);
 
-
-            /*
-             * STEPH not true because this will only happen when CANCEL event is being processed
-             * => isFutureCancelled is broken for AO
-                SubscriptionTransition aaPendingSubscription = aoSubscription.getPendingTransition();
-                assertNotNull(aaPendingSubscription);
-                assertEquals(aaPendingSubscription.getTransitionType(), SubscriptionTransitionType.CANCEL);
-             */
-
             // MOVE AFTER CANCELLATION
             testListener.reset();
             testListener.pushExpectedEvent(NextEvent.CANCEL);
@@ -111,11 +100,10 @@ public class TestUserApiAddOn extends TestApiBase {
             now = clock.getUTCNow();
             assertTrue(testListener.isCompleted(5000));
 
-            // REFETCH AO SUBSCRIPTION AND CHECK THIS IS FUTURE CANCELLED
+            // REFETCH AO SUBSCRIPTION AND CHECK THIS IS CANCELLED
             aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
             assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
 
-
         } catch (Exception e) {
             Assert.fail(e.getMessage());
         }
@@ -123,6 +111,121 @@ public class TestUserApiAddOn extends TestApiBase {
 
 
     @Test(enabled=true, groups={"sql"})
+    public void testChangeBPWthAddonNonIncluded() {
+        try {
+
+            String baseProduct = "Shotgun";
+            BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+            String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+            // CREATE BP
+            SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+            String aoProduct = "Telescopic-Scope";
+            BillingPeriod aoTerm = BillingPeriod.MONTHLY;
+            String aoPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+            SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
+
+            testListener.reset();
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+
+            // MOVE CLOCK AFTER TRIAL + AO DISCOUNT
+            Duration twoMonths = getDurationMonth(2);
+            clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
+            assertTrue(testListener.isCompleted(5000));
+
+            // SET CTD TO CANCEL IN FUTURE
+            DateTime now = clock.getUTCNow();
+            Duration ctd = getDurationMonth(1);
+            DateTime newChargedThroughDate = DefaultClock.addDuration(now, ctd);
+            billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate);
+            baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+            // CHANGE IMMEDIATELY WITH TO BP WITH NON INCLUDED ADDON
+            String newBaseProduct = "Assault-Rifle";
+            BillingPeriod newBaseTerm = BillingPeriod.MONTHLY;
+            String newBasePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+            testListener.reset();
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            baseSubscription.changePlan(newBaseProduct, newBaseTerm, newBasePriceList, now);
+            assertTrue(testListener.isCompleted(5000));
+
+            // REFETCH AO SUBSCRIPTION AND CHECK THIS CANCELLED
+            aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+            assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
+
+        } catch (Exception e) {
+            Assert.fail(e.getMessage());
+        }
+    }
+
+    @Test(enabled=true, groups={"sql"})
+    public void testChangeBPWthAddonNonAvailable() {
+        try {
+
+            String baseProduct = "Shotgun";
+            BillingPeriod baseTerm = BillingPeriod.MONTHLY;
+            String basePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+            // CREATE BP
+            SubscriptionData baseSubscription = createSubscription(baseProduct, baseTerm, basePriceList);
+
+            String aoProduct = "Telescopic-Scope";
+            BillingPeriod aoTerm = BillingPeriod.MONTHLY;
+            String aoPriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+            SubscriptionData aoSubscription = createSubscription(aoProduct, aoTerm, aoPriceList);
+
+            testListener.reset();
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+            testListener.pushExpectedEvent(NextEvent.PHASE);
+
+            // MOVE CLOCK AFTER TRIAL + AO DISCOUNT
+            Duration twoMonths = getDurationMonth(2);
+            clock.setDeltaFromReality(twoMonths, DAY_IN_MS);
+            assertTrue(testListener.isCompleted(5000));
+
+            // SET CTD TO CANCEL IN FUTURE
+            DateTime now = clock.getUTCNow();
+            Duration ctd = getDurationMonth(1);
+            DateTime newChargedThroughDate = DefaultClock.addDuration(now, ctd);
+            billingApi.setChargedThroughDate(baseSubscription.getId(), newChargedThroughDate);
+            baseSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(baseSubscription.getId());
+
+            // CHANGE IMMEDIATELY WITH TO BP WITH NON AVAILABLE ADDON
+            String newBaseProduct = "Pistol";
+            BillingPeriod newBaseTerm = BillingPeriod.MONTHLY;
+            String newBasePriceList = PriceListSet.DEFAULT_PRICELIST_NAME;
+
+            baseSubscription.changePlan(newBaseProduct, newBaseTerm, newBasePriceList, now);
+
+
+            // REFETCH AO SUBSCRIPTION AND CHECK THIS IS ACTIVE
+            aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+            assertEquals(aoSubscription.getState(), SubscriptionState.ACTIVE);
+
+            // MOVE AFTER CHANGE
+            testListener.reset();
+            testListener.pushExpectedEvent(NextEvent.CHANGE);
+            clock.addDeltaFromReality(ctd);
+            now = clock.getUTCNow();
+            assertTrue(testListener.isCompleted(5000));
+
+
+            // REFETCH AO SUBSCRIPTION AND CHECK THIS CANCELLED
+            aoSubscription = (SubscriptionData) entitlementApi.getSubscriptionFromId(aoSubscription.getId());
+            assertEquals(aoSubscription.getState(), SubscriptionState.CANCELLED);
+
+        } catch (Exception e) {
+            Assert.fail(e.getMessage());
+        }
+    }
+
+
+    @Test(enabled=false, groups={"sql"})
     public void testAddonCreateWithBundleAlign() {
         try {
             String aoProduct = "Telescopic-Scope";
diff --git a/entitlement/src/test/resources/testInput.xml b/entitlement/src/test/resources/testInput.xml
index 7174893..3d36b9d 100644
--- a/entitlement/src/test/resources/testInput.xml
+++ b/entitlement/src/test/resources/testInput.xml
@@ -91,33 +91,18 @@ Use Cases to do:
 				<phaseType>TRIAL</phaseType>
 				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
-			<changePolicyCase> 
-				<toProduct>Pistol</toProduct>
-				<policy>END_OF_TERM</policy>
-			</changePolicyCase>
+            <changePolicyCase> 
+                <toProduct>Assault-Rifle</toProduct>
+                <policy>IMMEDIATE</policy>
+            </changePolicyCase>
+            <changePolicyCase> 
+                <fromProduct>Pistol</fromProduct>            
+                <toProduct>Shotgun</toProduct>
+                <policy>IMMEDIATE</policy>
+            </changePolicyCase>
 			<changePolicyCase> 
 				<toPriceList>rescue</toPriceList>
 				<policy>END_OF_TERM</policy>
-			</changePolicyCase>		
-			<changePolicyCase> 
-				<fromProduct>Pistol</fromProduct>
-				<toProduct>Shotgun</toProduct>
-				<policy>IMMEDIATE</policy>
-			</changePolicyCase>
-			<changePolicyCase> 
-				<fromProduct>Assault-Rifle</fromProduct>
-				<toProduct>Shotgun</toProduct>
-				<policy>END_OF_TERM</policy>
-			</changePolicyCase>
-			<changePolicyCase> 
-				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
-				<toProduct>Assault-Rifle</toProduct>
-				<toBillingPeriod>MONTHLY</toBillingPeriod>
-				<policy>END_OF_TERM</policy>
-			</changePolicyCase>
-			<changePolicyCase> 
-				<toProduct>Assault-Rifle</toProduct>
-				<policy>IMMEDIATE</policy>
 			</changePolicyCase>
 			<changePolicyCase> 
 				<fromBillingPeriod>MONTHLY</fromBillingPeriod>
@@ -157,6 +142,10 @@ Use Cases to do:
             </cancelPolicyCase>
 		</cancelPolicy>
 		<createAlignment>
+		    <createAlignmentCase>
+		        <product>Laser-Scope</product>
+                <alignment>START_OF_SUBSCRIPTION</alignment>
+            </createAlignmentCase>
             <createAlignmentCase>
                 <alignment>START_OF_BUNDLE</alignment>
             </createAlignmentCase>