killbill-memoizeit

Details

diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
index 0d7a9ca..1fa3c3a 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationBase.java
@@ -723,6 +723,15 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
                                                                        final ProductCategory productCategory,
                                                                        final BillingPeriod billingPeriod,
                                                                        final NextEvent... events) {
+        return addAOEntitlementAndCheckForCompletion(bundleId, productName, productCategory, billingPeriod, null, events);
+    }
+
+    protected DefaultEntitlement addAOEntitlementAndCheckForCompletion(final UUID bundleId,
+                                                                       final String productName,
+                                                                       final ProductCategory productCategory,
+                                                                       final BillingPeriod billingPeriod,
+                                                                       final LocalDate effectiveDate,
+                                                                       final NextEvent... events) {
         if (productCategory != ProductCategory.ADD_ON) {
             throw new RuntimeException("Unexpected Call for creating a productCategory " + productCategory);
         }
@@ -732,7 +741,7 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
             public Entitlement apply(@Nullable final Void dontcare) {
                 try {
                     final PlanPhaseSpecifier spec = new PlanPhaseSpecifier(productName, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null);
-                    final UUID entitlementId = entitlementApi.addEntitlement(bundleId, new DefaultEntitlementSpecifier(spec), null, null, false, ImmutableList.<PluginProperty>of(), callContext);
+                    final UUID entitlementId = entitlementApi.addEntitlement(bundleId, new DefaultEntitlementSpecifier(spec), effectiveDate, effectiveDate, false, ImmutableList.<PluginProperty>of(), callContext);
                     assertNotNull(entitlementId);
                     return entitlementApi.getEntitlementForId(entitlementId, callContext);
                 } catch (final EntitlementApiException e) {
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
index 6e05ff3..03fc958 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/usage/TestConsumableInArrear.java
@@ -30,8 +30,10 @@ import org.killbill.billing.beatrix.integration.TestIntegrationBase;
 import org.killbill.billing.beatrix.util.InvoiceChecker.ExpectedInvoiceItemCheck;
 import org.killbill.billing.catalog.api.BillingActionPolicy;
 import org.killbill.billing.catalog.api.BillingPeriod;
+import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
 import org.killbill.billing.catalog.api.ProductCategory;
 import org.killbill.billing.entitlement.api.DefaultEntitlement;
+import org.killbill.billing.entitlement.api.DefaultEntitlementSpecifier;
 import org.killbill.billing.invoice.api.Invoice;
 import org.killbill.billing.invoice.api.InvoiceItemType;
 import org.killbill.billing.payment.api.PluginProperty;
@@ -149,6 +151,81 @@ public class TestConsumableInArrear extends TestIntegrationBase {
     }
 
     @Test(groups = "slow")
+    public void testWithChange() throws Exception {
+        // We take april as it has 30 days (easier to play with BCD)
+        // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
+        clock.setDay(new LocalDate(2012, 4, 1));
+
+        final AccountData accountData = getAccountData(1);
+        final Account account = createAccountWithNonOsgiPaymentMethod(accountData);
+        accountChecker.checkAccount(account.getId(), accountData, callContext);
+
+        //
+        // CREATE SUBSCRIPTION AND EXPECT BOTH EVENTS: NextEvent.CREATE, NextEvent.BLOCK NextEvent.INVOICE
+        //
+        final DefaultEntitlement bpSubscription = createBaseEntitlementAndCheckForCompletion(account.getId(), "bundleKey", "Shotgun", ProductCategory.BASE, BillingPeriod.ANNUAL, NextEvent.CREATE, NextEvent.BLOCK, NextEvent.INVOICE);
+        // Check bundle after BP got created otherwise we get an error from auditApi.
+        subscriptionChecker.checkSubscriptionCreated(bpSubscription.getId(), internalCallContext);
+        invoiceChecker.checkInvoice(account.getId(), 1, callContext, new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
+
+        //
+        // ADD ADD_ON ON THE SAME DAY
+        //
+        final DefaultEntitlement aoSubscription = addAOEntitlementAndCheckForCompletion(bpSubscription.getBundleId(),
+                                                                                        "Bullets",
+                                                                                        ProductCategory.ADD_ON,
+                                                                                        BillingPeriod.NO_BILLING_PERIOD,
+                                                                                        new LocalDate(2012, 4, 1),
+                                                                                        NextEvent.CREATE, NextEvent.BLOCK, NextEvent.NULL_INVOICE);
+
+        recordUsageData(aoSubscription.getId(), "t1", "bullets", new LocalDate(2012, 4, 1), 99L, callContext);
+        recordUsageData(aoSubscription.getId(), "t2", "bullets", new LocalDate(2012, 4, 15), 100L, callContext);
+
+        // Trigger future invoice
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        invoiceUserApi.triggerInvoiceGeneration(account.getId(),
+                                                new LocalDate(2012, 5, 1),
+                                                callContext);
+        assertListenerStatus();
+        final Invoice secondInvoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+                                                                  new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+                                                                  new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("5.90")));
+        invoiceChecker.checkTrackingIds(secondInvoice, ImmutableSet.of("t1", "t2"), internalCallContext);
+
+        // Change to the Slugs plan
+        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE);
+        aoSubscription.changePlanWithDate(new DefaultEntitlementSpecifier(new PlanPhaseSpecifier("slugs-usage-in-arrear")),
+                                          new LocalDate(2012, 4, 1),
+                                          ImmutableList.<PluginProperty>of(),
+                                          callContext);
+        assertListenerStatus();
+
+        // Verify invoices (second invoice is unchanged)
+        final Invoice updatedSecondInvoice = invoiceChecker.checkInvoice(account.getId(), 2, callContext,
+                                                                        new ExpectedInvoiceItemCheck(new LocalDate(2012, 5, 1), new LocalDate(2013, 5, 1), InvoiceItemType.RECURRING, new BigDecimal("2399.95")),
+                                                                        new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("5.90")));
+        invoiceChecker.checkTrackingIds(updatedSecondInvoice, ImmutableSet.of("t1", "t2"), internalCallContext);
+        final Invoice thirdInvoice = invoiceChecker.checkInvoice(account.getId(), 3, callContext,
+                                                                  new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, BigDecimal.ZERO));
+        invoiceChecker.checkTrackingIds(thirdInvoice, ImmutableSet.of(), internalCallContext);
+
+        // Add usage data
+        recordUsageData(aoSubscription.getId(), "u1", "slugs", new LocalDate(2012, 4, 1), 99L, callContext);
+        recordUsageData(aoSubscription.getId(), "u2", "slugs", new LocalDate(2012, 4, 15), 100L, callContext);
+
+        // Trigger future invoice
+        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT, NextEvent.INVOICE_PAYMENT);
+        invoiceUserApi.triggerInvoiceGeneration(account.getId(),
+                                                new LocalDate(2012, 5, 1),
+                                                callContext);
+        assertListenerStatus();
+
+        final Invoice fourthInvoice = invoiceChecker.checkInvoice(account.getId(), 4, callContext,
+                                                                  new ExpectedInvoiceItemCheck(new LocalDate(2012, 4, 1), new LocalDate(2012, 5, 1), InvoiceItemType.USAGE, new BigDecimal("4")));
+        invoiceChecker.checkTrackingIds(fourthInvoice, ImmutableSet.of("u1", "u2"), internalCallContext);
+    }
+
+    @Test(groups = "slow")
     public void testWithCancellation() throws Exception {
         // We take april as it has 30 days (easier to play with BCD)
         // Set clock to the initial start date - we implicitly assume here that the account timezone is UTC
diff --git a/beatrix/src/test/resources/catalogs/default/catalogTest.xml b/beatrix/src/test/resources/catalogs/default/catalogTest.xml
index 77a6048..ff134c7 100644
--- a/beatrix/src/test/resources/catalogs/default/catalogTest.xml
+++ b/beatrix/src/test/resources/catalogs/default/catalogTest.xml
@@ -41,6 +41,7 @@
 
     <units>
         <unit name="bullets"/>
+        <unit name="slugs"/>
         <unit name="stones"/>
     </units>
 
@@ -69,6 +70,7 @@
                 <addonProduct>Laser-Scope</addonProduct>
                 <addonProduct>Holster</addonProduct>
                 <addonProduct>Bullets</addonProduct>
+                <addonProduct>Slugs</addonProduct>
             </available>
         </product>
         <product name="Assault-Rifle">
@@ -106,6 +108,9 @@
         <product name="Bullets">
             <category>ADD_ON</category>
         </product>
+        <product name="Slugs">
+            <category>ADD_ON</category>
+        </product>
     </products>
 
     <rules>
@@ -1358,6 +1363,69 @@
             <plansAllowedInBundle>-1</plansAllowedInBundle>
             <!-- arbitrary number of these (multipack) -->
         </plan>
+        <plan name="slugs-usage-in-arrear" prettyName="Slug Monthly Plan">
+            <product>Slugs</product>
+            <finalPhase type="EVERGREEN" prettyName="Slug Monthly Plan Evergreen">
+                <duration>
+                    <unit>UNLIMITED</unit>
+                </duration>
+                <usages>
+                    <usage name="slugs-usage-in-arrear-usage" prettyName="Slug Usage In Arrear" billingMode="IN_ARREAR" usageType="CONSUMABLE">
+                        <billingPeriod>MONTHLY</billingPeriod>
+                        <tiers>
+                            <tier>
+                                <blocks>
+                                    <tieredBlock>
+                                        <unit>slugs</unit>
+                                        <size>100</size>
+                                        <prices>
+                                            <price>
+                                                <currency>USD</currency>
+                                                <value>2</value>
+                                            </price>
+                                            <price>
+                                                <currency>EUR</currency>
+                                                <value>1</value>
+                                            </price>
+                                            <price>
+                                                <currency>GBP</currency>
+                                                <value>0.1</value>
+                                            </price>
+                                        </prices>
+                                        <max>10</max>
+                                    </tieredBlock>
+                                </blocks>
+                            </tier>
+                            <tier>
+                                <blocks>
+                                    <tieredBlock>
+                                        <unit>slugs</unit>
+                                        <size>1000</size>
+                                        <prices>
+                                            <price>
+                                                <currency>USD</currency>
+                                                <value>5</value>
+                                            </price>
+                                            <price>
+                                                <currency>EUR</currency>
+                                                <value>4</value>
+                                            </price>
+                                            <price>
+                                                <currency>GBP</currency>
+                                                <value>3</value>
+                                            </price>
+                                        </prices>
+                                        <max>100</max>
+                                    </tieredBlock>
+                                </blocks>
+                            </tier>
+                        </tiers>
+                    </usage>
+                </usages>
+            </finalPhase>
+            <plansAllowedInBundle>-1</plansAllowedInBundle>
+            <!-- arbitrary number of these (multipack) -->
+        </plan>
         <plan name="trebuchet-usage-in-arrear" prettyName="Trebuchet Monthly Plan">
             <product>Trebuchet</product>
             <finalPhase type="EVERGREEN">
@@ -1423,6 +1491,7 @@
                 <plan>holster-monthly-regular</plan>
                 <plan>refurbish-maintenance</plan>
                 <plan>bullets-usage-in-arrear</plan>
+                <plan>slugs-usage-in-arrear</plan>
                 <plan>holster-monthly-special</plan>
             </plans>
         </defaultPriceList>
diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java
index 5e7f5c2..a99dc62 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/UsageInvoiceItemGenerator.java
@@ -196,7 +196,7 @@ public class UsageInvoiceItemGenerator extends InvoiceItemGenerator {
                     public boolean apply(final InvoiceItem input) {
                         if (input.getInvoiceItemType() == InvoiceItemType.USAGE) {
                             final Usage usage = knownUsage.get(input.getUsageName());
-                            return usage.getBillingMode() == BillingMode.IN_ARREAR;
+                            return usage != null && usage.getBillingMode() == BillingMode.IN_ARREAR;
                         }
                         return false;
                     }