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 f32d4bf..27b7116 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
@@ -18,18 +18,15 @@
package org.killbill.billing.beatrix.integration;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.Callable;
-
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-import javax.inject.Named;
-
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDate;
@@ -129,15 +126,16 @@ import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
+import javax.annotation.Nullable;
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Callable;
import static com.jayway.awaitility.Awaitility.await;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -427,19 +425,22 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB {
return new TestPaymentMethodPlugin();
}
- protected AccountData getAccountData(final int billingDay) {
- return new MockAccountBuilder().name(UUID.randomUUID().toString().substring(1, 8))
- .firstNameLength(6)
- .email(UUID.randomUUID().toString().substring(1, 8))
- .phone(UUID.randomUUID().toString().substring(1, 8))
- .migrated(false)
- .isNotifiedForInvoices(false)
- .externalKey(UUID.randomUUID().toString().substring(1, 8))
- .billingCycleDayLocal(billingDay)
- .currency(Currency.USD)
- .paymentMethodId(UUID.randomUUID())
- .timeZone(DateTimeZone.UTC)
- .build();
+ protected AccountData getAccountData(@Nullable final Integer billingDay) {
+ final MockAccountBuilder builder = new MockAccountBuilder()
+ .name(UUID.randomUUID().toString().substring(1, 8))
+ .firstNameLength(6)
+ .email(UUID.randomUUID().toString().substring(1, 8))
+ .phone(UUID.randomUUID().toString().substring(1, 8))
+ .migrated(false)
+ .isNotifiedForInvoices(false)
+ .externalKey(UUID.randomUUID().toString().substring(1, 8))
+ .currency(Currency.USD)
+ .paymentMethodId(UUID.randomUUID())
+ .timeZone(DateTimeZone.UTC);
+ if (billingDay != null) {
+ builder.billingCycleDayLocal(billingDay);
+ }
+ return builder.build();
}
protected AccountData getChildAccountData(final int billingDay, final UUID parentAccountId, final boolean isPaymentDelegatedToParent) {
diff --git a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
index 2402ed2..a697d6c 100644
--- a/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
+++ b/beatrix/src/test/java/org/killbill/billing/beatrix/integration/TestIntegrationInvoice.java
@@ -25,30 +25,39 @@ import java.util.UUID;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
+import org.killbill.billing.ErrorCode;
import org.killbill.billing.ObjectType;
import org.killbill.billing.account.api.Account;
import org.killbill.billing.api.TestApiListener.NextEvent;
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.PlanPhasePriceOverride;
import org.killbill.billing.catalog.api.PlanPhaseSpecifier;
+import org.killbill.billing.catalog.api.PriceListSet;
import org.killbill.billing.catalog.api.ProductCategory;
import org.killbill.billing.entitlement.api.DefaultEntitlement;
import org.killbill.billing.entitlement.api.Entitlement;
+import org.killbill.billing.entitlement.api.EntitlementApiException;
+import org.killbill.billing.entitlement.api.SubscriptionEventType;
import org.killbill.billing.invoice.api.DryRunArguments;
import org.killbill.billing.invoice.api.DryRunType;
import org.killbill.billing.invoice.api.Invoice;
+import org.killbill.billing.invoice.api.InvoiceApiException;
import org.killbill.billing.invoice.api.InvoiceItem;
import org.killbill.billing.invoice.api.InvoiceItemType;
import org.killbill.billing.invoice.model.ExternalChargeInvoiceItem;
import org.killbill.billing.payment.api.Payment;
import org.killbill.billing.payment.api.PluginProperty;
import org.killbill.billing.subscription.api.user.DefaultSubscriptionBase;
+import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
+import static com.tc.util.Assert.fail;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
public class TestIntegrationInvoice extends TestIntegrationBase {
@@ -361,5 +370,78 @@ public class TestIntegrationInvoice extends TestIntegrationBase {
assertListenerStatus();
}
+ @Test(groups = "slow")
+ public void testDryRunWithPendingSubscription() throws Exception {
+
+ final LocalDate initialDate = new LocalDate(2017, 4, 1);
+ clock.setDay(initialDate);
+
+ // Create account with non BCD to force junction BCD logic to activate
+ final Account account = createAccountWithNonOsgiPaymentMethod(getAccountData(null));
+
+ final PlanPhaseSpecifier spec = new PlanPhaseSpecifier("Shotgun", BillingPeriod.ANNUAL, PriceListSet.DEFAULT_PRICELIST_NAME, null);
+
+ final LocalDate futureDate = new LocalDate(2017, 5, 1);
+
+ // No CREATE event as this is set in the future
+ final Entitlement createdEntitlement = entitlementApi.createBaseEntitlement(account.getId(), spec, account.getExternalKey(), null, futureDate, futureDate, false, ImmutableList.<PluginProperty>of(), callContext);
+ assertEquals(createdEntitlement.getState(), Entitlement.EntitlementState.PENDING);
+ assertEquals(createdEntitlement.getEffectiveStartDate().compareTo(futureDate), 0);
+ assertEquals(createdEntitlement.getEffectiveEndDate(), null);
+ assertListenerStatus();
+
+ // Generate a dryRun invoice on the billing startDate
+ final DryRunArguments dryRunArguments1 = new TestDryRunArguments(DryRunType.TARGET_DATE);
+ final Invoice dryRunInvoice1 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, dryRunArguments1, callContext);
+ assertEquals(dryRunInvoice1.getInvoiceItems().size(), 1);
+ assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
+ assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getStartDate(), futureDate);
+ assertEquals(dryRunInvoice1.getInvoiceItems().get(0).getPlanName(), "shotgun-annual");
+
+
+ // Generate a dryRun invoice with a plan change
+ final DryRunArguments dryRunArguments = new TestDryRunArguments(DryRunType.SUBSCRIPTION_ACTION, "Pistol", ProductCategory.BASE, BillingPeriod.MONTHLY, PriceListSet.DEFAULT_PRICELIST_NAME, null,
+ SubscriptionEventType.CHANGE, createdEntitlement.getId(), createdEntitlement.getBundleId(), futureDate, BillingActionPolicy.IMMEDIATE);
+
+ // First one day prior subscription starts
+ try {
+ invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate.minusDays(1), dryRunArguments, callContext);
+ fail("Should fail to trigger dryRun invoice prior subscription starts");
+ } catch (final InvoiceApiException e) {
+ assertEquals(e.getCode(),ErrorCode.INVOICE_NOTHING_TO_DO.getCode());
+ }
+
+ // Second, on the startDate
+ final Invoice dryRunInvoice2 = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, dryRunArguments, callContext);
+ assertEquals(dryRunInvoice2.getInvoiceItems().size(), 1);
+ assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
+ assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getStartDate(), futureDate);
+ assertEquals(dryRunInvoice2.getInvoiceItems().get(0).getPlanName(), "pistol-monthly");
+
+
+ // Check BCD is not yet set
+ final Account refreshedAccount1 = accountUserApi.getAccountById(account.getId(), callContext);
+ assertEquals(refreshedAccount1.getBillCycleDayLocal(), new Integer(0));
+
+
+ busHandler.pushExpectedEvents(NextEvent.INVOICE);
+ final Invoice realInvoice = invoiceUserApi.triggerInvoiceGeneration(createdEntitlement.getAccountId(), futureDate, null, callContext);
+ assertListenerStatus();
+
+ assertEquals(realInvoice.getInvoiceItems().size(), 1);
+ assertEquals(realInvoice.getInvoiceItems().get(0).getInvoiceItemType(), InvoiceItemType.FIXED);
+ assertEquals(realInvoice.getInvoiceItems().get(0).getAmount().compareTo(BigDecimal.ZERO), 0);
+ assertEquals(realInvoice.getInvoiceItems().get(0).getStartDate(), futureDate);
+ assertEquals(realInvoice.getInvoiceItems().get(0).getPlanName(), "shotgun-annual");
+
+ // Check BCD is now set
+ final Account refreshedAccount2 = accountUserApi.getAccountById(account.getId(), callContext);
+ assertEquals(refreshedAccount2.getBillCycleDayLocal(), new Integer(31));
+
+ }
+
+
}