diff --git a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
index 00af476..31b4556 100644
--- a/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
+++ b/invoice/src/main/java/org/killbill/billing/invoice/InvoiceDispatcher.java
@@ -903,16 +903,19 @@ public class InvoiceDispatcher {
public FutureAccountNotificationsBuilder() {
}
- public void setNotificationListForTrigger(final Map<LocalDate, Set<UUID>> notificationListForTrigger) {
+ public FutureAccountNotificationsBuilder setNotificationListForTrigger(final Map<LocalDate, Set<UUID>> notificationListForTrigger) {
this.notificationListForTrigger = notificationListForTrigger;
+ return this;
}
- public void setNotificationListForDryRun(final Map<LocalDate, Set<UUID>> notificationListForDryRun) {
+ public FutureAccountNotificationsBuilder setNotificationListForDryRun(final Map<LocalDate, Set<UUID>> notificationListForDryRun) {
this.notificationListForDryRun = notificationListForDryRun;
+ return this;
}
- public void setRescheduled(final boolean rescheduled) {
+ public FutureAccountNotificationsBuilder setRescheduled(final boolean rescheduled) {
isRescheduled = rescheduled;
+ return this;
}
public Map<LocalDate, Set<UUID>> getNotificationListForTrigger() {
diff --git a/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDatePoster.java b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDatePoster.java
new file mode 100644
index 0000000..b9bf095
--- /dev/null
+++ b/invoice/src/test/java/org/killbill/billing/invoice/notification/TestNextBillingDatePoster.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 The Billing Project, LLC
+ *
+ * The Billing Project licenses this file to you under the Apache License, version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.killbill.billing.invoice.notification;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+import org.killbill.billing.ObjectType;
+import org.killbill.billing.account.api.Account;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications;
+import org.killbill.billing.invoice.InvoiceDispatcher.FutureAccountNotifications.FutureAccountNotificationsBuilder;
+import org.killbill.billing.invoice.InvoiceTestSuiteWithEmbeddedDB;
+import org.killbill.billing.invoice.api.DefaultInvoiceService;
+import org.killbill.billing.platform.api.KillbillConfigSource;
+import org.killbill.billing.subscription.api.SubscriptionBase;
+import org.killbill.notificationq.api.NotificationEventWithMetadata;
+import org.killbill.notificationq.api.NotificationQueue;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+
+public class TestNextBillingDatePoster extends InvoiceTestSuiteWithEmbeddedDB {
+
+ protected KillbillConfigSource getConfigSource() {
+ final ImmutableMap<String, String> extraProperties = ImmutableMap.<String, String>builder()
+ .put("org.killbill.invoice.dryRunNotificationSchedule", "48h")
+ .build();
+ return getConfigSource("/resource.properties", extraProperties);
+ }
+
+ @Test(groups = "slow")
+ public void testDryRunReInsertion() throws Exception {
+ final Account account = invoiceUtil.createAccount(callContext);
+ final Long accountRecordId = nonEntityDao.retrieveAccountRecordIdFromObject(account.getId(), ObjectType.ACCOUNT, null);
+
+ final DateTime now = clock.getUTCNow();
+ final LocalDate nowLocal = internalCallContext.toLocalDate(now);
+
+ final SubscriptionBase subscription = invoiceUtil.createSubscription();
+ final UUID subscriptionId = subscription.getId();
+
+ final FutureAccountNotifications futureAccountNotifications = createFutureAccountNotifications(subscriptionId, nowLocal);
+
+ // Add 3 seconds to make it more interesting
+ clock.addDeltaFromReality(3000);
+
+ invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications, internalCallContext);
+
+
+ // Add 3 seconds to make it more interesting
+ clock.addDeltaFromReality(3000);
+ invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications, internalCallContext);
+
+ final NotificationQueue nextBillingQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+ DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
+ final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = nextBillingQueue.getFutureNotificationForSearchKeys(accountRecordId, internalCallContext.getTenantRecordId());
+ final ImmutableList<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotificationsList = ImmutableList.copyOf(futureNotifications);
+ Assert.assertEquals(futureNotificationsList.size(), 1);
+
+ // We expect only one notification for which effectiveDate matches our original effectiveDate (conversion DateTime -> LocalDate -> DateTime)
+ final NotificationEventWithMetadata<NextBillingDateNotificationKey> notification = futureNotificationsList.get(0);
+ Assert.assertEquals(notification.getEffectiveDate(), internalCallContext.toUTCDateTime(nowLocal));
+
+ final Iterable<UUID> uuidKeys = notification.getEvent().getUuidKeys();
+ Assert.assertFalse(Iterables.isEmpty(uuidKeys));
+ final List<UUID> uuidKeysList = ImmutableList.copyOf(uuidKeys);
+ Assert.assertEquals(uuidKeysList.size(), 1);
+ Assert.assertEquals(uuidKeysList.get(0), subscriptionId);
+ }
+
+ @Test(groups = "slow")
+ public void testDryRunUpdateWithNewSubscription() throws Exception {
+ final Account account = invoiceUtil.createAccount(callContext);
+ final Long accountRecordId = nonEntityDao.retrieveAccountRecordIdFromObject(account.getId(), ObjectType.ACCOUNT, null);
+
+ final DateTime now = clock.getUTCNow();
+ final LocalDate nowLocal = internalCallContext.toLocalDate(now);
+
+ final SubscriptionBase subscription1 = invoiceUtil.createSubscription();
+ final UUID subscriptionId1 = subscription1.getId();
+
+ final FutureAccountNotifications futureAccountNotifications1 = createFutureAccountNotifications(subscriptionId1, nowLocal);
+ invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications1, internalCallContext);
+
+ final SubscriptionBase subscription2 = invoiceUtil.createSubscription();
+ final UUID subscriptionId2 = subscription2.getId();
+
+ final FutureAccountNotifications futureAccountNotifications2 = createFutureAccountNotifications(subscriptionId2, nowLocal);
+ invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications2, internalCallContext);
+
+
+ final NotificationQueue nextBillingQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+ DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
+
+ final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = nextBillingQueue.getFutureNotificationForSearchKeys(accountRecordId, internalCallContext.getTenantRecordId());
+ final ImmutableList<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotificationsList = ImmutableList.copyOf(futureNotifications);
+ Assert.assertEquals(futureNotificationsList.size(), 1);
+
+ // We expect only one notification but this time we should see a list with both subscriptionIds.
+ final NotificationEventWithMetadata<NextBillingDateNotificationKey> notification = futureNotificationsList.get(0);
+ Assert.assertEquals(notification.getEffectiveDate(), internalCallContext.toUTCDateTime(nowLocal));
+
+ final Iterable<UUID> uuidKeys = notification.getEvent().getUuidKeys();
+ Assert.assertFalse(Iterables.isEmpty(uuidKeys));
+ final List<UUID> uuidKeysList = ImmutableList.copyOf(uuidKeys);
+ Assert.assertEquals(uuidKeysList.size(), 2);
+ Assert.assertEquals(uuidKeysList.get(0), subscriptionId1);
+ Assert.assertEquals(uuidKeysList.get(1), subscriptionId2);
+ }
+
+
+ @Test(groups = "slow")
+ public void testDryRunWithSameSubscriptionLater() throws Exception {
+ final Account account = invoiceUtil.createAccount(callContext);
+ final Long accountRecordId = nonEntityDao.retrieveAccountRecordIdFromObject(account.getId(), ObjectType.ACCOUNT, null);
+
+ final DateTime now = clock.getUTCNow();
+ final LocalDate nowLocal = internalCallContext.toLocalDate(now);
+
+ final SubscriptionBase subscription = invoiceUtil.createSubscription();
+ final UUID subscriptionId = subscription.getId();
+
+ final FutureAccountNotifications futureAccountNotifications1 = createFutureAccountNotifications(subscriptionId, nowLocal);
+
+ // Add 3 seconds to make it more interesting
+ clock.addDeltaFromReality(3000);
+
+ invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications1, internalCallContext);
+
+
+ clock.addDays(1);
+ final DateTime newNow = clock.getUTCNow();
+ final LocalDate newNowLocal = internalCallContext.toLocalDate(newNow);
+
+ final FutureAccountNotifications futureAccountNotifications2 = createFutureAccountNotifications(subscriptionId, newNowLocal);
+
+
+ invoiceDao.setFutureAccountNotificationsForEmptyInvoice(account.getId(), futureAccountNotifications2, internalCallContext);
+
+ final NotificationQueue nextBillingQueue = notificationQueueService.getNotificationQueue(DefaultInvoiceService.INVOICE_SERVICE_NAME,
+ DefaultNextBillingDateNotifier.NEXT_BILLING_DATE_NOTIFIER_QUEUE);
+ final Iterable<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotifications = nextBillingQueue.getFutureNotificationForSearchKeys(accountRecordId, internalCallContext.getTenantRecordId());
+ final ImmutableList<NotificationEventWithMetadata<NextBillingDateNotificationKey>> futureNotificationsList = ImmutableList.copyOf(futureNotifications);
+ // We expect only two notifications, one for each date
+ Assert.assertEquals(futureNotificationsList.size(), 2);
+
+ final NotificationEventWithMetadata<NextBillingDateNotificationKey> notification1 = futureNotificationsList.get(0);
+ Assert.assertEquals(notification1.getEffectiveDate(), internalCallContext.toUTCDateTime(nowLocal));
+
+ final Iterable<UUID> uuidKeys1 = notification1.getEvent().getUuidKeys();
+ Assert.assertFalse(Iterables.isEmpty(uuidKeys1));
+ final List<UUID> uuidKeysList1 = ImmutableList.copyOf(uuidKeys1);
+ Assert.assertEquals(uuidKeysList1.size(), 1);
+ Assert.assertEquals(uuidKeysList1.get(0), subscriptionId);
+
+
+ final NotificationEventWithMetadata<NextBillingDateNotificationKey> notification2 = futureNotificationsList.get(1);
+ Assert.assertEquals(notification2.getEffectiveDate(), internalCallContext.toUTCDateTime(newNowLocal));
+
+ final Iterable<UUID> uuidKeys2 = notification2.getEvent().getUuidKeys();
+ Assert.assertFalse(Iterables.isEmpty(uuidKeys2));
+ final List<UUID> uuidKeysList2 = ImmutableList.copyOf(uuidKeys2);
+ Assert.assertEquals(uuidKeysList2.size(), 1);
+ Assert.assertEquals(uuidKeysList2.get(0), subscriptionId);
+
+ }
+
+ private FutureAccountNotifications createFutureAccountNotifications(final UUID subscriptionId, final LocalDate notificationDate) {
+ final Map<LocalDate, Set<UUID>> notificationListForDryRun = new HashMap<LocalDate, Set<UUID>>();
+ notificationListForDryRun.put(notificationDate, ImmutableSet.<UUID>of(subscriptionId));
+
+ return new FutureAccountNotificationsBuilder()
+ .setNotificationListForDryRun(notificationListForDryRun)
+ .build();
+
+ }
+
+}