killbill-memoizeit

beatrix: add more checks in TestOverdueIntegration Signed-off-by:

8/22/2012 9:22:00 PM

Details

diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
index 06ea733..0531696 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/overdue/TestOverdueIntegration.java
@@ -19,34 +19,32 @@ package com.ning.billing.beatrix.integration.overdue;
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.math.BigDecimal;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.Callable;
 
 import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import com.google.inject.Inject;
-import com.google.inject.name.Named;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.beatrix.integration.BeatrixModule;
 import com.ning.billing.beatrix.integration.TestIntegrationBase;
+import com.ning.billing.beatrix.util.InvoiceChecker.ExpectedItemCheck;
 import com.ning.billing.catalog.api.BillingPeriod;
-import com.ning.billing.catalog.api.PlanPhaseSpecifier;
 import com.ning.billing.catalog.api.PriceListSet;
 import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.entitlement.api.user.EntitlementUserApiException;
 import com.ning.billing.entitlement.api.user.Subscription;
 import com.ning.billing.entitlement.api.user.SubscriptionBundle;
-import com.ning.billing.entitlement.api.user.SubscriptionData;
 import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItemType;
 import com.ning.billing.invoice.api.InvoiceUserApi;
 import com.ning.billing.junction.api.BlockingApi;
 import com.ning.billing.junction.api.BlockingApiException;
@@ -56,15 +54,16 @@ import com.ning.billing.overdue.wrapper.OverdueWrapperFactory;
 import com.ning.billing.payment.api.PaymentApi;
 import com.ning.billing.payment.api.PaymentMethodPlugin;
 import com.ning.billing.payment.provider.MockPaymentProviderPlugin;
-import com.ning.billing.util.callcontext.DefaultCallContext;
 import com.ning.billing.util.clock.ClockMock;
 import com.ning.billing.util.config.XMLLoader;
 
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
 import static com.jayway.awaitility.Awaitility.await;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.fail;
 
 @Test(groups = "slow")
 @Guice(modules = {BeatrixModule.class})
@@ -97,58 +96,57 @@ public class TestOverdueIntegration extends TestIntegrationBase {
     private SubscriptionBundle bundle;
     private String productName;
     private BillingPeriod term;
-    private String planSetName;
 
     @BeforeMethod(groups = "slow")
     public void setupOverdue() throws Exception {
         final String configXml = "<overdueConfig>" +
-                "   <bundleOverdueStates>" +
-                "       <state name=\"OD3\">" +
-                "           <condition>" +
-                "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
-                "                   <unit>DAYS</unit><number>50</number>" +
-                "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
-                "           </condition>" +
-                "           <externalMessage>Reached OD3</externalMessage>" +
-                "           <blockChanges>true</blockChanges>" +
-                "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
-                "           <autoReevaluationInterval>" +
-                "               <unit>DAYS</unit><number>5</number>" +
-                "           </autoReevaluationInterval>" +
-                "       </state>" +
-                "       <state name=\"OD2\">" +
-                "           <condition>" +
-                "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
-                "                   <unit>DAYS</unit><number>40</number>" +
-                "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
-                "           </condition>" +
-                "           <externalMessage>Reached OD2</externalMessage>" +
-                "           <blockChanges>true</blockChanges>" +
-                "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
-                "           <autoReevaluationInterval>" +
-                "               <unit>DAYS</unit><number>5</number>" +
-                "           </autoReevaluationInterval>" +
-                "       </state>" +
-                "       <state name=\"OD1\">" +
-                "           <condition>" +
-                "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
-                "                   <unit>DAYS</unit><number>30</number>" +
-                "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
-                "           </condition>" +
-                "           <externalMessage>Reached OD1</externalMessage>" +
-                "           <blockChanges>true</blockChanges>" +
-                "           <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
-                "           <autoReevaluationInterval>" +
-                "               <unit>DAYS</unit><number>100</number>" + // this number is intentionally too high
-                "           </autoReevaluationInterval>" +
-                "       </state>" +
-                "   </bundleOverdueStates>" +
-                "</overdueConfig>";
+                                 "   <bundleOverdueStates>" +
+                                 "       <state name=\"OD3\">" +
+                                 "           <condition>" +
+                                 "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "                   <unit>DAYS</unit><number>50</number>" +
+                                 "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "           </condition>" +
+                                 "           <externalMessage>Reached OD3</externalMessage>" +
+                                 "           <blockChanges>true</blockChanges>" +
+                                 "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
+                                 "           <autoReevaluationInterval>" +
+                                 "               <unit>DAYS</unit><number>5</number>" +
+                                 "           </autoReevaluationInterval>" +
+                                 "       </state>" +
+                                 "       <state name=\"OD2\">" +
+                                 "           <condition>" +
+                                 "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "                   <unit>DAYS</unit><number>40</number>" +
+                                 "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "           </condition>" +
+                                 "           <externalMessage>Reached OD2</externalMessage>" +
+                                 "           <blockChanges>true</blockChanges>" +
+                                 "           <disableEntitlementAndChangesBlocked>true</disableEntitlementAndChangesBlocked>" +
+                                 "           <autoReevaluationInterval>" +
+                                 "               <unit>DAYS</unit><number>5</number>" +
+                                 "           </autoReevaluationInterval>" +
+                                 "       </state>" +
+                                 "       <state name=\"OD1\">" +
+                                 "           <condition>" +
+                                 "               <timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "                   <unit>DAYS</unit><number>30</number>" +
+                                 "               </timeSinceEarliestUnpaidInvoiceEqualsOrExceeds>" +
+                                 "           </condition>" +
+                                 "           <externalMessage>Reached OD1</externalMessage>" +
+                                 "           <blockChanges>true</blockChanges>" +
+                                 "           <disableEntitlementAndChangesBlocked>false</disableEntitlementAndChangesBlocked>" +
+                                 "           <autoReevaluationInterval>" +
+                                 "               <unit>DAYS</unit><number>100</number>" + // this number is intentionally too high
+                                 "           </autoReevaluationInterval>" +
+                                 "       </state>" +
+                                 "   </bundleOverdueStates>" +
+                                 "</overdueConfig>";
         final InputStream is = new ByteArrayInputStream(configXml.getBytes());
         final OverdueConfig config = XMLLoader.getObjectFromStreamNoValidation(is, OverdueConfig.class);
         overdueWrapperFactory.setOverdueConfig(config);
 
-        account = createAccountWithPaymentMethod(getAccountData(25));
+        account = createAccountWithPaymentMethod(getAccountData(0));
         assertNotNull(account);
 
         final PaymentMethodPlugin info = new PaymentMethodPlugin() {
@@ -178,7 +176,6 @@ public class TestOverdueIntegration extends TestIntegrationBase {
 
         productName = "Shotgun";
         term = BillingPeriod.MONTHLY;
-        planSetName = PriceListSet.DEFAULT_PRICELIST_NAME;
 
         // create account
         // set mock payments to fail
@@ -191,91 +188,86 @@ public class TestOverdueIntegration extends TestIntegrationBase {
         // Clear databases
     }
 
-    // We set the the property killbill.payment.retry.days=8,8,8,8,8,8,8,8 so that Payment retry logics does not end with an ABORTED state
+    // We set the the property killbill.payment.retry.days=8,8,8,8,8,8,8,8 so that Payment retry logic does not end with an ABORTED state
     // preventing final instant payment to succeed.
-    //
     @Test(groups = "slow")
     public void testBasicOverdueState() throws Exception {
         clock.setTime(new DateTime(2012, 5, 1, 0, 3, 42, 0));
         paymentPlugin.makeAllInvoicesFailWithError(true);
 
-        // set next invoice to fail and create subscription
-        busHandler.pushExpectedEvents(NextEvent.CREATE, NextEvent.INVOICE);
-        final Subscription baseSubscription = entitlementUserApi.createSubscription(bundle.getId(), new PlanPhaseSpecifier(productName, ProductCategory.BASE, term, planSetName, null), null, context);
+        // Set next invoice to fail and create subscription
+        final Subscription baseSubscription = createSubscriptionAndCheckForCompletion(bundle.getId(), productName, ProductCategory.BASE, term, NextEvent.CREATE, NextEvent.INVOICE);
 
-        assertNotNull(baseSubscription);
-        assertTrue(busHandler.isCompleted(DELAY));
+        invoiceChecker.checkInvoice(account.getId(), 1, new ExpectedItemCheck(new LocalDate(2012, 5, 1), null, InvoiceItemType.FIXED, new BigDecimal("0")));
 
-        busHandler.pushExpectedEvents(NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
-        clock.addDays(30); // DAY 30 have to get out of trial before first payment
+        // DAY 30 have to get out of trial before first payment
+        addDaysAndCheckForCompletion(30, NextEvent.PHASE, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
 
-        assertTrue(busHandler.isCompleted(DELAY));
+        invoiceChecker.checkInvoice(account.getId(), 2, new ExpectedItemCheck(new LocalDate(2012, 5, 31), new LocalDate(2012, 6, 30), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
 
-        // should still be in clear state
+        // Should still be in clear state
         checkODState(BlockingApi.CLEAR_STATE_NAME);
 
-        clock.addDays(15); // DAY 45 - 15 days after invoice
-        assertTrue(busHandler.isCompleted(DELAY));
-        //should still be in clear state
+        // DAY 45 - 15 days after invoice
+        addDaysAndCheckForCompletion(15, NextEvent.PAYMENT_ERROR);
+
+        // Should still be in clear state
         checkODState(BlockingApi.CLEAR_STATE_NAME);
 
-        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
-        clock.addDays(20); // DAY 65 - 35 days after invoice
-        assertTrue(busHandler.isCompleted(DELAY));
+        // Note: we have two tracks of payment retries because of the invoice generated at the phase change
+
+        // DAY 65 - 35 days after invoice
+        addDaysAndCheckForCompletion(20, NextEvent.INVOICE, NextEvent.PAYMENT_ERROR, NextEvent.PAYMENT_ERROR);
 
-        //Now we should be in OD1
+        invoiceChecker.checkInvoice(account.getId(), 3, new ExpectedItemCheck(new LocalDate(2012, 6, 30), new LocalDate(2012, 7, 31), InvoiceItemType.RECURRING, new BigDecimal("249.95")));
+
+        // Now we should be in OD1
         checkODState("OD1");
         checkChangePlanWithOverdueState(baseSubscription, true);
 
-        clock.addDays(2); //DAY 67 - 37 days after invoice
-        assertTrue(busHandler.isCompleted(DELAY));
-        // should still be in OD1
+        // DAY 67 - 37 days after invoice
+        addDaysAndCheckForCompletion(2);
+
+        // Should still be in OD1
         checkODState("OD1");
         checkChangePlanWithOverdueState(baseSubscription, true);
 
-        //busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
-        clock.addDays(8); //DAY 75 - 45 days after invoice
-        assertTrue(busHandler.isCompleted(DELAY));
-        // should still be in OD1
+        // DAY 75 - 45 days after invoice
+        addDaysAndCheckForCompletion(8, NextEvent.PAYMENT_ERROR, NextEvent.PAYMENT_ERROR);
+
+        // Should still be in OD1
         checkODState("OD2");
         checkChangePlanWithOverdueState(baseSubscription, true);
 
-        busHandler.pushExpectedEvents(NextEvent.INVOICE, NextEvent.PAYMENT_ERROR);
-        clock.addDays(10); //DAY 85 - 55 days after invoice
-        assertTrue(busHandler.isCompleted(DELAY));
-        // should now be in OD2 state once the update is processed
+        // DAY 85 - 55 days after invoice
+        addDaysAndCheckForCompletion(10, NextEvent.PAYMENT_ERROR, NextEvent.PAYMENT_ERROR);
+
+        // Should now be in OD2 state once the update is processed
         checkODState("OD3");
         checkChangePlanWithOverdueState(baseSubscription, true);
 
         paymentPlugin.makeAllInvoicesFailWithError(false);
         final Collection<Invoice> invoices = invoiceApi.getUnpaidInvoicesByAccountId(account.getId(), clock.getUTCToday());
-        final List<String> invoiceIds = new ArrayList<String>();
         for (final Invoice invoice : invoices) {
-            invoiceIds.add(invoice.getId().toString());
             if (invoice.getBalance().compareTo(BigDecimal.ZERO) > 0) {
-                busHandler.pushExpectedEvent(NextEvent.PAYMENT);
-                paymentApi.createPayment(account, invoice.getId(), invoice.getBalance(), new DefaultCallContext("test", null, null, clock));
-                assertTrue(busHandler.isCompleted(DELAY));
+                createPaymentAndCheckForCompletion(account, invoice, NextEvent.PAYMENT);
             }
         }
 
         checkODState(BlockingApi.CLEAR_STATE_NAME);
         checkChangePlanWithOverdueState(baseSubscription, false);
-
     }
 
     private void checkChangePlanWithOverdueState(final Subscription subscription, final boolean shouldFail) {
-        try {
-            subscription.changePlan("Pistol", term, planSetName, clock.getUTCNow(), context);
-            if (shouldFail) {
-                fail("Expected change plan to fail because of OD1 state");
-            }
-        } catch(EntitlementUserApiException expected) {
-            if (shouldFail) {
-                assertTrue(expected.getCause() instanceof BlockingApiException);
-            } else {
-                fail("Expected change plan to succeed because of clean OD state");
+        if (shouldFail) {
+            try {
+                subscription.changePlan("Pistol", term, PriceListSet.DEFAULT_PRICELIST_NAME, clock.getUTCNow(), context);
+            } catch (EntitlementUserApiException e) {
+                assertTrue(e.getCause() instanceof BlockingApiException);
             }
+        } else {
+            // Downgrade
+            changeSubscriptionAndCheckForCompletion(subscription, "Pistol", BillingPeriod.MONTHLY, NextEvent.CHANGE);
         }
     }
 
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
index 2339a8f..d33cfa9 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/TestIntegrationBase.java
@@ -16,7 +16,6 @@
 
 package com.ning.billing.beatrix.integration;
 
-import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
@@ -31,26 +30,27 @@ import org.skife.jdbi.v2.IDBI;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
-import org.testng.annotations.AfterClass;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeClass;
 import org.testng.annotations.BeforeMethod;
 
-import com.google.inject.Inject;
-import com.google.inject.name.Named;
 import com.ning.billing.account.api.Account;
 import com.ning.billing.account.api.AccountData;
 import com.ning.billing.account.api.AccountService;
 import com.ning.billing.account.api.AccountUserApi;
-import com.ning.billing.account.api.BillCycleDay;
 import com.ning.billing.analytics.AnalyticsListener;
 import com.ning.billing.analytics.api.user.DefaultAnalyticsUserApi;
 import com.ning.billing.api.TestApiListener;
+import com.ning.billing.api.TestApiListener.NextEvent;
 import com.ning.billing.api.TestListenerStatus;
 import com.ning.billing.beatrix.BeatrixTestSuiteWithEmbeddedDB;
 import com.ning.billing.beatrix.lifecycle.Lifecycle;
 import com.ning.billing.beatrix.util.InvoiceChecker;
+import com.ning.billing.catalog.api.BillingPeriod;
 import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.catalog.api.PlanPhaseSpecifier;
+import com.ning.billing.catalog.api.PriceListSet;
+import com.ning.billing.catalog.api.ProductCategory;
 import com.ning.billing.dbi.MysqlTestingHelper;
 import com.ning.billing.entitlement.api.EntitlementService;
 import com.ning.billing.entitlement.api.timeline.EntitlementTimelineApi;
@@ -63,23 +63,27 @@ import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceService;
 import com.ning.billing.invoice.api.InvoiceUserApi;
-import com.ning.billing.invoice.generator.InvoiceDateUtils;
 import com.ning.billing.invoice.model.InvoicingConfiguration;
 import com.ning.billing.junction.plumbing.api.BlockingSubscription;
 import com.ning.billing.mock.MockAccountBuilder;
 import com.ning.billing.mock.api.MockBillCycleDay;
 import com.ning.billing.overdue.wrapper.OverdueWrapperFactory;
 import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentApiException;
 import com.ning.billing.payment.api.PaymentMethodPlugin;
 import com.ning.billing.payment.provider.MockPaymentProviderPlugin;
 import com.ning.billing.util.api.TagUserApi;
 import com.ning.billing.util.bus.BusService;
 import com.ning.billing.util.callcontext.CallContext;
 import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.DefaultCallContext;
 import com.ning.billing.util.callcontext.DefaultCallContextFactory;
 import com.ning.billing.util.callcontext.UserType;
 import com.ning.billing.util.clock.ClockMock;
-import com.ning.billing.util.io.IOUtils;
+
+import com.google.common.base.Function;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
 
 import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertNotNull;
@@ -87,6 +91,7 @@ import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
 public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implements TestListenerStatus {
+
     protected static final DateTimeZone testTimeZone = DateTimeZone.UTC;
 
     protected static final int NUMBER_OF_DECIMALS = InvoicingConfiguration.getNumberOfDecimals();
@@ -306,4 +311,92 @@ public class TestIntegrationBase extends BeatrixTestSuiteWithEmbeddedDB implemen
                                        .timeZone(DateTimeZone.UTC)
                                        .build();
     }
+
+    protected void addMonthsAndCheckForCompletion(final int nbMonth, final NextEvent... events) {
+        doCallAndCheckForCompletion(new Function<Void, Void>() {
+            @Override
+            public Void apply(@Nullable final Void dontcare) {
+                clock.addMonths(nbMonth);
+                return null;
+            }
+        }, events);
+    }
+
+    protected void addDaysAndCheckForCompletion(final int nbDays, final NextEvent... events) {
+        doCallAndCheckForCompletion(new Function<Void, Void>() {
+            @Override
+            public Void apply(@Nullable final Void dontcare) {
+                clock.addDays(nbDays);
+                return null;
+            }
+        }, events);
+    }
+
+    protected void createPaymentAndCheckForCompletion(final Account account, final Invoice invoice, final NextEvent... events) {
+        doCallAndCheckForCompletion(new Function<Void, Void>() {
+            @Override
+            public Void apply(@Nullable final Void input) {
+                try {
+                    paymentApi.createPayment(account, invoice.getId(), invoice.getBalance(), new DefaultCallContext("test", null, null, clock));
+                } catch (PaymentApiException e) {
+                    fail(e.toString());
+                }
+                return null;
+            }
+        }, events);
+    }
+
+    protected Subscription createSubscriptionAndCheckForCompletion(final UUID bundleId,
+                                                                   final String productName,
+                                                                   final ProductCategory productCategory,
+                                                                   final BillingPeriod billingPeriod,
+                                                                   final NextEvent... events) {
+        return doCallAndCheckForCompletion(new Function<Void, Subscription>() {
+            @Override
+            public Subscription apply(@Nullable final Void dontcare) {
+                try {
+                    final Subscription subscription = entitlementUserApi.createSubscription(bundleId,
+                                                                                            new PlanPhaseSpecifier(productName, productCategory, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, null),
+                                                                                            null,
+                                                                                            context);
+                    assertNotNull(subscription);
+                    return subscription;
+                } catch (EntitlementUserApiException e) {
+                    fail();
+                    return null;
+                }
+            }
+        }, events);
+    }
+
+    protected Subscription changeSubscriptionAndCheckForCompletion(final Subscription subscription,
+                                                                   final String productName,
+                                                                   final BillingPeriod billingPeriod,
+                                                                   final NextEvent... events) {
+        return doCallAndCheckForCompletion(new Function<Void, Subscription>() {
+            @Override
+            public Subscription apply(@Nullable final Void dontcare) {
+                try {
+                    subscription.changePlan(productName, billingPeriod, PriceListSet.DEFAULT_PRICELIST_NAME, clock.getUTCNow(), context);
+                    return subscription;
+                } catch (EntitlementUserApiException e) {
+                    fail();
+                    return null;
+                }
+            }
+        }, events);
+    }
+
+    private <T> T doCallAndCheckForCompletion(Function<Void, T> f, final NextEvent... events) {
+        log.info("            ************    STARTING BUS HANDLER CHECK    ********************");
+
+        busHandler.pushExpectedEvents(events);
+
+        final T result = f.apply(null);
+        assertTrue(busHandler.isCompleted(DELAY));
+        assertListenerStatus();
+
+        log.info("            ************    DONE WITH BUS HANDLER CHECK    ********************");
+        return result;
+    }
 }
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java b/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
index dca65a5..f098895 100644
--- a/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/util/InvoiceChecker.java
@@ -13,37 +13,50 @@
  * License for the specific language governing permissions and limitations
  * under the License.
  */
+
 package com.ning.billing.beatrix.util;
 
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.UUID;
 
-
 import org.joda.time.LocalDate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 
-import com.google.inject.Inject;
 import com.ning.billing.invoice.api.Invoice;
 import com.ning.billing.invoice.api.InvoiceItem;
 import com.ning.billing.invoice.api.InvoiceItemType;
 import com.ning.billing.invoice.api.InvoiceUserApi;
 
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Inject;
+
 public class InvoiceChecker {
 
-    private final static Logger log = LoggerFactory.getLogger(InvoiceChecker.class);
+    private static final Logger log = LoggerFactory.getLogger(InvoiceChecker.class);
 
     private final InvoiceUserApi invoiceUserApi;
 
     @Inject
     public InvoiceChecker(final InvoiceUserApi invoiceUserApi) {
-       this.invoiceUserApi = invoiceUserApi;
+        this.invoiceUserApi = invoiceUserApi;
+    }
+
+    public void checkInvoice(final UUID accountId, final int invoiceOrderingNumber, final ExpectedItemCheck... expected) {
+        checkInvoice(accountId, invoiceOrderingNumber, ImmutableList.<ExpectedItemCheck>copyOf(expected));
+    }
+
+    public void checkInvoice(final UUID accountId, final int invoiceOrderingNumber, final List<ExpectedItemCheck> expected) {
+        final List<Invoice> invoices = invoiceUserApi.getInvoicesByAccount(accountId);
+        Assert.assertEquals(invoices.size(), invoiceOrderingNumber);
+        final Invoice invoice = invoices.get(invoiceOrderingNumber - 1);
+        checkInvoice(invoice.getId(), expected);
     }
 
     public void checkInvoice(final UUID invoiceId, final List<ExpectedItemCheck> expected) {
-        final Invoice invoice =invoiceUserApi.getInvoice(invoiceId);
+        final Invoice invoice = invoiceUserApi.getInvoice(invoiceId);
         Assert.assertNotNull(invoice);
 
         final List<InvoiceItem> actual = invoice.getInvoiceItems();
@@ -57,21 +70,21 @@ public class InvoiceChecker {
                 }
                 if (in.getAmount().compareTo(cur.getAmount()) != 0) {
                     log.info(String.format("Found item type = %s and startDate = %s but amount differ (actual = %s, expected = %s) ",
-                            cur.getType(), cur.getStartDate(), in.getAmount(), cur.getAmount()));
+                                           cur.getType(), cur.getStartDate(), in.getAmount(), cur.getAmount()));
                     continue;
                 }
 
                 if ((cur.getEndDate() == null && in.getEndDate() == null) ||
-                        (cur.getEndDate() != null && in.getEndDate() != null && cur.getEndDate().compareTo(in.getEndDate()) == 0)) {
+                    (cur.getEndDate() != null && in.getEndDate() != null && cur.getEndDate().compareTo(in.getEndDate()) == 0)) {
                     found = true;
                     break;
                 }
                 log.info(String.format("Found item type = %s and startDate = %s, amount = %s but endDate differ (actual = %s, expected = %s) ",
-                        cur.getType(), cur.getStartDate(), in.getAmount(), in.getEndDate(), cur.getEndDate()));
+                                       cur.getType(), cur.getStartDate(), in.getAmount(), in.getEndDate(), cur.getEndDate()));
             }
             if (!found) {
-                Assert.fail(String.format("Failed to find invoice item type = %s and startDate = %s, amount = %s, endDate = %s",
-                        cur.getType(), cur.getStartDate(), cur.getAmount(), cur.getEndDate()));
+                Assert.fail(String.format("Failed to find invoice item type = %s and startDate = %s, amount = %s, endDate = %s for invoice id %s",
+                                          cur.getType(), cur.getStartDate(), cur.getAmount(), cur.getEndDate(), invoice.getId()));
             }
         }
     }
@@ -84,7 +97,7 @@ public class InvoiceChecker {
         private final BigDecimal Amount;
 
         public ExpectedItemCheck(final LocalDate startDate, final LocalDate endDate,
-                final InvoiceItemType type, final BigDecimal amount) {
+                                 final InvoiceItemType type, final BigDecimal amount) {
             this.startDate = startDate;
             this.endDate = endDate;
             this.type = type;
@@ -94,12 +107,15 @@ public class InvoiceChecker {
         public LocalDate getStartDate() {
             return startDate;
         }
+
         public LocalDate getEndDate() {
             return endDate;
         }
+
         public InvoiceItemType getType() {
             return type;
         }
+
         public BigDecimal getAmount() {
             return Amount;
         }