killbill-uncached

invoice: CR for safety bounds * Fix per-day check * Add ability

11/7/2016 12:38:07 AM

Details

diff --git a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java
index 259c570..0f22e05 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/generator/FixedAndRecurringInvoiceItemGenerator.java
@@ -19,6 +19,7 @@ package org.killbill.billing.invoice.generator;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -399,25 +400,39 @@ public class FixedAndRecurringInvoiceItemGenerator extends InvoiceItemGenerator 
 
     // Trigger an exception if we create too many subscriptions for a subscription on a given day
     private void safetyBound(final Iterable<InvoiceItem> resultingItems, final Multimap<UUID, LocalDate> createdItemsPerDayPerSubscription, final InternalTenantContext internalCallContext) throws InvoiceApiException {
+        if (config.getMaxDailyNumberOfItemsSafetyBound(internalCallContext) == -1) {
+            // Safety bound disabled
+            return;
+        }
+
         for (final InvoiceItem invoiceItem : resultingItems) {
             if (invoiceItem.getSubscriptionId() != null) {
-                trackInvoiceItemCreatedDay(invoiceItem, createdItemsPerDayPerSubscription, internalCallContext);
+                final LocalDate resultingItemCreationDay = trackInvoiceItemCreatedDay(invoiceItem, createdItemsPerDayPerSubscription, internalCallContext);
+
+                final Collection<LocalDate> creationDaysForSubscription = createdItemsPerDayPerSubscription.get(invoiceItem.getSubscriptionId());
+                int i = 0;
+                for (final LocalDate creationDayForSubscription : creationDaysForSubscription) {
+                    if (creationDayForSubscription.compareTo(resultingItemCreationDay) == 0) {
+                        i++;
+                        if (i > config.getMaxDailyNumberOfItemsSafetyBound(internalCallContext)) {
+                            // Proposed items have already been logged
+                            throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR, String.format("SAFETY BOUND TRIGGERED subscriptionId='%s', resultingItem=%s", invoiceItem.getSubscriptionId(), invoiceItem));
+                        }
 
-                if (createdItemsPerDayPerSubscription.get(invoiceItem.getSubscriptionId()).size() > config.getMaxDailyNumberOfItemsSafetyBound(internalCallContext)) {
-                    // Proposed items have already been logged
-                    throw new InvoiceApiException(ErrorCode.UNEXPECTED_ERROR, String.format("SAFETY BOUND TRIGGERED subscriptionId='%s', resultingItem=%s", invoiceItem.getSubscriptionId(), invoiceItem));
+                    }
                 }
             }
         }
     }
 
-    private void trackInvoiceItemCreatedDay(final InvoiceItem invoiceItem, final Multimap<UUID, LocalDate> createdItemsPerDayPerSubscription, final InternalTenantContext internalCallContext) {
+    private LocalDate trackInvoiceItemCreatedDay(final InvoiceItem invoiceItem, final Multimap<UUID, LocalDate> createdItemsPerDayPerSubscription, final InternalTenantContext internalCallContext) {
         final UUID subscriptionId = invoiceItem.getSubscriptionId();
         if (subscriptionId == null) {
-            return;
+            return null;
         }
 
         final LocalDate createdDay = internalCallContext.toLocalDate(invoiceItem.getCreatedDate());
         createdItemsPerDayPerSubscription.put(subscriptionId, createdDay);
+        return createdDay;
     }
 }
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestFixedAndRecurringInvoiceItemGenerator.java b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestFixedAndRecurringInvoiceItemGenerator.java
index ca56b1f..5fbfc79 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/generator/TestFixedAndRecurringInvoiceItemGenerator.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/generator/TestFixedAndRecurringInvoiceItemGenerator.java
@@ -315,7 +315,35 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
         for (int i = 0; i < threshold; i++) {
             final Invoice invoice = new DefaultInvoice(account.getId(), clock.getUTCToday(), startDate.plusMonths(i), account.getCurrency());
             invoice.addInvoiceItem(new RecurringInvoiceItem(UUID.randomUUID(),
-                                                            clock.getUTCNow(),
+                                                            startDate.plusMonths(i).toDateTimeAtStartOfDay(), // Different days - should not trigger the safety bounds
+                                                            invoice.getId(),
+                                                            account.getId(),
+                                                            subscription.getBundleId(),
+                                                            subscription.getId(),
+                                                            event.getPlan().getName(),
+                                                            event.getPlanPhase().getName(),
+                                                            startDate.plusMonths(i),
+                                                            startDate.plusMonths(1 + i),
+                                                            amount,
+                                                            amount,
+                                                            account.getCurrency()));
+            existingInvoices.add(invoice);
+        }
+
+        assertEquals(fixedAndRecurringInvoiceItemGenerator.generateItems(account,
+                                                                         UUID.randomUUID(),
+                                                                         events,
+                                                                         existingInvoices,
+                                                                         startDate.plusMonths(threshold),
+                                                                         account.getCurrency(),
+                                                                         new HashMap<UUID, SubscriptionFutureNotificationDates>(),
+                                                                         internalCallContext).size(), 1);
+
+        // Simulate a big catch-up on that day
+        for (int i = threshold; i < 2 * threshold; i++) {
+            final Invoice invoice = new DefaultInvoice(account.getId(), clock.getUTCToday(), startDate.plusMonths(i), account.getCurrency());
+            invoice.addInvoiceItem(new RecurringInvoiceItem(UUID.randomUUID(),
+                                                            clock.getUTCNow(), // Same day
                                                             invoice.getId(),
                                                             account.getId(),
                                                             subscription.getBundleId(),
@@ -335,7 +363,7 @@ public class TestFixedAndRecurringInvoiceItemGenerator extends InvoiceTestSuiteN
                                                                                                          UUID.randomUUID(),
                                                                                                          events,
                                                                                                          existingInvoices,
-                                                                                                         startDate.plusMonths(threshold),
+                                                                                                         startDate.plusMonths(2 * threshold),
                                                                                                          account.getCurrency(),
                                                                                                          new HashMap<UUID, SubscriptionFutureNotificationDates>(),
                                                                                                          internalCallContext);
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
index 2f6bf60..487fd07 100644
--- a/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
+++ b/invoice/src/test/java/org/killbill/billing/invoice/InvoiceTestSuiteWithEmbeddedDB.java
@@ -42,6 +42,7 @@ import org.killbill.billing.util.config.definition.InvoiceConfig;
 import org.killbill.billing.util.dao.NonEntityDao;
 import org.killbill.bus.api.PersistentBus;
 import org.killbill.clock.Clock;
+import org.killbill.clock.ClockMock;
 import org.killbill.commons.locker.GlobalLocker;
 import org.killbill.notificationq.api.NotificationQueueService;
 import org.slf4j.Logger;
@@ -91,7 +92,7 @@ public abstract class InvoiceTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
     @Inject
     protected GlobalLocker locker;
     @Inject
-    protected Clock clock;
+    protected ClockMock clock;
     @Inject
     protected InternalCallContextFactory internalCallContextFactory;
     @Inject
@@ -127,6 +128,7 @@ public abstract class InvoiceTestSuiteWithEmbeddedDB extends GuicyKillbillTestSu
         controllerDispatcher.clearAll();
         bus.start();
         restartInvoiceService(invoiceService);
+        clock.resetDeltaFromReality();
     }
 
     private void restartInvoiceService(final InvoiceService invoiceService) throws Exception {