killbill-uncached

Test refactoring of payment

5/4/2012 11:52:14 PM

Details

diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/MockPaymentInfoReceiver.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/MockPaymentInfoReceiver.java
new file mode 100644
index 0000000..ab7ed20
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/MockPaymentInfoReceiver.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.beatrix.integration.payment;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import com.google.common.eventbus.Subscribe;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
+
+public class MockPaymentInfoReceiver {
+    private final List<PaymentInfoEvent> processedPayments = Collections.synchronizedList(new ArrayList<PaymentInfoEvent>());
+    private final List<PaymentErrorEvent> errors = Collections.synchronizedList(new ArrayList<PaymentErrorEvent>());
+
+    @Subscribe
+    public void processedPayment(PaymentInfoEvent paymentInfo) {
+        processedPayments.add(paymentInfo);
+    }
+
+    @Subscribe
+    public void processedPaymentError(PaymentErrorEvent paymentError) {
+        errors.add(paymentError);
+    }
+
+    public List<PaymentInfoEvent> getProcessedPayments() {
+        return new ArrayList<PaymentInfoEvent>(processedPayments);
+    }
+
+    public List<PaymentErrorEvent> getErrors() {
+        return new ArrayList<PaymentErrorEvent>(errors);
+    }
+
+    public void clear() {
+        processedPayments.clear();
+        errors.clear();
+    }
+}
\ No newline at end of file
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/PaymentTestModule.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/PaymentTestModule.java
new file mode 100644
index 0000000..538f3dd
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/PaymentTestModule.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.beatrix.integration.payment;
+
+import org.apache.commons.collections.MapUtils;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.inject.Provider;
+import com.ning.billing.account.dao.AccountDao;
+import com.ning.billing.account.dao.MockAccountDao;
+import com.ning.billing.config.PaymentConfig;
+import com.ning.billing.invoice.dao.InvoiceDao;
+import com.ning.billing.invoice.dao.MockInvoiceDao;
+import com.ning.billing.junction.api.BillingApi;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.payment.dao.MockPaymentDao;
+import com.ning.billing.payment.dao.PaymentDao;
+import com.ning.billing.payment.provider.MockPaymentProviderPluginModule;
+import com.ning.billing.payment.setup.PaymentModule;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.InMemoryBus;
+import com.ning.billing.util.notificationq.MockNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+
+public class PaymentTestModule extends PaymentModule {
+	public static class MockProvider implements Provider<BillingApi> {
+		@Override
+		public BillingApi get() {
+			return BrainDeadProxyFactory.createBrainDeadProxyFor(BillingApi.class);
+		}
+
+	}
+
+
+    public PaymentTestModule() {
+        super(MapUtils.toProperties(ImmutableMap.of("killbill.payment.provider.default", "my-mock",
+                "killbill.payment.engine.events.off", "false")));
+    }
+
+    @Override
+    protected void installPaymentDao() {
+        bind(PaymentDao.class).to(MockPaymentDao.class).asEagerSingleton();
+    }
+
+    @Override
+    protected void installPaymentProviderPlugins(PaymentConfig config) {
+        install(new MockPaymentProviderPluginModule("my-mock"));
+    }
+
+    @Override
+    protected void configure() {
+        super.configure();
+        bind(Bus.class).to(InMemoryBus.class).asEagerSingleton();
+        bind(MockAccountDao.class).asEagerSingleton();
+        bind(AccountDao.class).to(MockAccountDao.class);
+        bind(MockInvoiceDao.class).asEagerSingleton();
+        bind(InvoiceDao.class).to(MockInvoiceDao.class);
+        bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
+
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestHelper.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestHelper.java
new file mode 100644
index 0000000..24c8042
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestHelper.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.beatrix.integration.payment;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.apache.commons.lang.RandomStringUtils;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.account.api.user.AccountBuilder;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.test.InvoiceTestApi;
+import com.ning.billing.invoice.model.DefaultInvoice;
+import com.ning.billing.invoice.model.RecurringInvoiceItem;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.CallContextFactory;
+import com.ning.billing.util.callcontext.CallOrigin;
+import com.ning.billing.util.callcontext.UserType;
+import com.ning.billing.util.entity.EntityPersistenceException;
+
+public class TestHelper {
+    protected final AccountUserApi accountUserApi;
+    protected final InvoiceTestApi invoiceTestApi;
+    private final CallContext context;
+
+    @Inject
+    public TestHelper(CallContextFactory factory, AccountUserApi accountUserApi, InvoiceTestApi invoiceTestApi) {
+        this.accountUserApi = accountUserApi;
+        this.invoiceTestApi = invoiceTestApi;
+        context = factory.createCallContext("Princess Buttercup", CallOrigin.TEST, UserType.TEST);
+    }
+
+    // These helper methods can be overridden in a plugin implementation
+    public Account createTestCreditCardAccount() throws EntityPersistenceException {
+        final String name = "First" + RandomStringUtils.randomAlphanumeric(5) + " " + "Last" + RandomStringUtils.randomAlphanumeric(5);
+        final String externalKey = RandomStringUtils.randomAlphanumeric(10);
+        final Account account = new AccountBuilder(UUID.randomUUID()).name(name)
+                                                                     .firstNameLength(name.length())
+                                                                     .externalKey(externalKey)
+                                                                     .phone("123-456-7890")
+                                                                     .email("ccuser" + RandomStringUtils.randomAlphanumeric(8) + "@example.com")
+                                                                     .currency(Currency.USD)
+                                                                     .billingCycleDay(1)
+                                                                     .build();
+        ((ZombieControl)accountUserApi).addResult("getAccountById", account);
+        ((ZombieControl)accountUserApi).addResult("getAccountByKey", account);
+        return account;
+    }
+
+    public Account createTestPayPalAccount() throws EntityPersistenceException {
+        final String name = "First" + RandomStringUtils.randomAlphanumeric(5) + " " + "Last" + RandomStringUtils.randomAlphanumeric(5);
+        final String externalKey = RandomStringUtils.randomAlphanumeric(10);
+        final Account account = new AccountBuilder(UUID.randomUUID()).name(name)
+                                                                     .firstNameLength(name.length())
+                                                                     .externalKey(externalKey)
+                                                                     .phone("123-456-7890")
+                                                                     .email("ppuser@example.com")
+                                                                     .currency(Currency.USD)
+                                                                     .billingCycleDay(1)
+                                                                     .build();
+        ((ZombieControl)accountUserApi).addResult("getAccountById", account);
+        ((ZombieControl)accountUserApi).addResult("getAccountByKey", account);
+        return account;
+    }
+
+    public Invoice createTestInvoice(Account account,
+                                     DateTime targetDate,
+                                     Currency currency,
+                                     InvoiceItem... items) {
+        Invoice invoice = new DefaultInvoice(account.getId(), new DateTime(), targetDate, currency);
+
+        for (InvoiceItem item : items) {
+            if (item instanceof RecurringInvoiceItem) {
+                RecurringInvoiceItem recurringInvoiceItem = (RecurringInvoiceItem) item;
+                invoice.addInvoiceItem(new RecurringInvoiceItem(invoice.getId(),
+                                                               account.getId(),
+                                                               recurringInvoiceItem.getBundleId(),
+                                                               recurringInvoiceItem.getSubscriptionId(),
+                                                               recurringInvoiceItem.getPlanName(),
+                                                               recurringInvoiceItem.getPhaseName(),
+                                                               recurringInvoiceItem.getStartDate(),
+                                                               recurringInvoiceItem.getEndDate(),
+                                                               recurringInvoiceItem.getAmount(),
+                                                               recurringInvoiceItem.getRate(),
+                                                               recurringInvoiceItem.getCurrency()));
+            }
+        }
+
+        invoiceTestApi.create(invoice, context);
+        return invoice;
+    }
+
+    public Invoice createTestInvoice(Account account) {
+        final DateTime now = new DateTime(DateTimeZone.UTC);
+        final UUID subscriptionId = UUID.randomUUID();
+        final UUID bundleId = UUID.randomUUID();
+        final BigDecimal amount = new BigDecimal("10.00");
+        
+        final InvoiceItem item = new RecurringInvoiceItem(null, account.getId(), bundleId, subscriptionId, "test plan", "test phase", now, now.plusMonths(1),
+                amount, new BigDecimal("1.0"), Currency.USD);
+
+
+        return createTestInvoice(account, now, Currency.USD, item);
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestNotifyInvoicePaymentApi.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestNotifyInvoicePaymentApi.java
new file mode 100644
index 0000000..d7febde
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestNotifyInvoicePaymentApi.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.beatrix.integration.payment;
+
+import static org.testng.Assert.assertNotNull;
+
+import java.util.UUID;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import com.google.inject.Inject;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.AccountApiException;
+import com.ning.billing.account.api.AccountUserApi;
+import com.ning.billing.account.glue.AccountModuleWithMocks;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.ning.billing.mock.BrainDeadProxyFactory;
+import com.ning.billing.mock.BrainDeadProxyFactory.ZombieControl;
+import com.ning.billing.mock.glue.MockJunctionModule;
+import com.ning.billing.payment.RequestProcessor;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.callcontext.TestCallContext;
+import com.ning.billing.util.entity.EntityPersistenceException;
+
+@Test
+@Guice(modules = { PaymentTestModule.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class, MockJunctionModule.class})
+public class TestNotifyInvoicePaymentApi {
+    @Inject
+    private Bus eventBus;
+    @Inject
+    private RequestProcessor invoiceProcessor;
+    @Inject
+    private InvoicePaymentApi invoicePaymentApi;
+    @Inject
+    private TestHelper testHelper;
+    @Inject
+    private AccountUserApi accountApi;
+
+    private CallContext context = new TestCallContext("Payment Api Tests");
+
+    @BeforeClass(alwaysRun = true)
+    public void setUpClass() {
+        ((ZombieControl)accountApi).addResult("getAccountById", BrainDeadProxyFactory.ZOMBIE_VOID);
+    }
+    
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws EventBusException {
+        eventBus.start();
+        eventBus.register(invoiceProcessor);
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws EventBusException {
+        eventBus.unregister(invoiceProcessor);
+        eventBus.stop();
+    }
+
+    @Test
+    public void testNotifyPaymentSuccess() throws AccountApiException, EntityPersistenceException {
+        final Account account = testHelper.createTestCreditCardAccount();
+        final Invoice invoice = testHelper.createTestInvoice(account);
+
+        PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+
+        invoicePaymentApi.notifyOfPaymentAttempt(invoice.getId(),
+                                     invoice.getBalance(),
+                                     invoice.getCurrency(),
+                                     paymentAttempt.getPaymentAttemptId(),
+                                     paymentAttempt.getPaymentAttemptDate(),
+                                     context);
+
+        InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayment(paymentAttempt.getPaymentAttemptId());
+
+        assertNotNull(invoicePayment);
+    }
+
+    @Test
+    public void testNotifyPaymentFailure() throws AccountApiException, EntityPersistenceException {
+        final Account account = testHelper.createTestCreditCardAccount();
+        final Invoice invoice = testHelper.createTestInvoice(account);
+
+        PaymentAttempt paymentAttempt = new PaymentAttempt(UUID.randomUUID(), invoice);
+        invoicePaymentApi.notifyOfPaymentAttempt(invoice.getId(),
+                                                 paymentAttempt.getPaymentAttemptId(),
+                                                 paymentAttempt.getPaymentAttemptDate(),
+                                                 context);
+
+        InvoicePayment invoicePayment = invoicePaymentApi.getInvoicePayment(paymentAttempt.getPaymentAttemptId());
+
+        assertNotNull(invoicePayment);
+    }
+
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestPaymentInvoiceIntegration.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestPaymentInvoiceIntegration.java
new file mode 100644
index 0000000..32a7157
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestPaymentInvoiceIntegration.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.beatrix.integration.payment;
+
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import org.apache.commons.io.IOUtils;
+import org.joda.time.DateTime;
+import org.skife.jdbi.v2.IDBI;
+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 org.testng.annotations.Test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.ning.billing.account.api.Account;
+import com.ning.billing.dbi.MysqlTestingHelper;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoicePaymentApi;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.ning.billing.mock.glue.MockJunctionModule;
+import com.ning.billing.payment.RequestProcessor;
+import com.ning.billing.payment.api.PaymentApi;
+import com.ning.billing.payment.api.PaymentAttempt;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
+import com.ning.billing.payment.setup.PaymentTestModuleWithEmbeddedDb;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+import com.ning.billing.util.clock.MockClockModule;
+import com.ning.billing.util.glue.CallContextModule;
+
+public class TestPaymentInvoiceIntegration {
+    // create payment for received invoice and save it -- positive and negative
+    // check that notification for payment attempt is created
+    // check that invoice-payment is saved
+    @Inject
+    private Bus eventBus;
+    @Inject
+    private RequestProcessor invoiceProcessor;
+    @Inject
+    private InvoicePaymentApi invoicePaymentApi;
+    @Inject
+    private PaymentApi paymentApi;
+    @Inject
+    private TestHelper testHelper;
+
+    private MockPaymentInfoReceiver paymentInfoReceiver;
+
+    private IDBI dbi;
+    private MysqlTestingHelper helper;
+    
+    @BeforeClass(alwaysRun = true)
+    public void startMysql() throws IOException {
+        final String accountddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/account/ddl.sql"));
+        final String utilddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/util/ddl.sql"));
+        final String invoiceddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/invoice/ddl.sql"));
+        final String paymentddl = IOUtils.toString(MysqlTestingHelper.class.getResourceAsStream("/com/ning/billing/payment/ddl.sql"));
+
+        helper = new MysqlTestingHelper();
+        helper.startMysql();
+        helper.initDb(accountddl + "\n" + invoiceddl + "\n" + utilddl + "\n" + paymentddl);
+        dbi = helper.getDBI();
+    }
+
+    @AfterClass(alwaysRun = true)
+    public void stopMysql() {
+        if (helper != null) helper.stopMysql();
+    }
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws EventBusException {
+        Injector injector = Guice.createInjector(new PaymentTestModuleWithEmbeddedDb(),
+                                                 new InvoiceModuleWithMocks(),
+                                                 new CallContextModule(),
+                                                 new MockClockModule(),
+                                                 new MockJunctionModule(),
+                                                 new AbstractModule()
+                                            
+                                                  {
+                                                    @Override
+                                                    protected void configure() {
+                                                        bind(IDBI.class).toInstance(dbi);
+                                                    }
+                                                });
+        injector.injectMembers(this);
+
+        paymentInfoReceiver = new MockPaymentInfoReceiver();
+
+        eventBus.start();
+        eventBus.register(invoiceProcessor);
+        eventBus.register(paymentInfoReceiver);
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws EventBusException {
+        if (eventBus != null) {
+            eventBus.unregister(invoiceProcessor);
+            eventBus.unregister(paymentInfoReceiver);
+            eventBus.stop();
+        }
+    }
+
+    @Test
+    public void testInvoiceIntegration() throws Exception {
+        final Account account = testHelper.createTestCreditCardAccount();
+        final Invoice invoice = testHelper.createTestInvoice(account);
+
+        await().atMost(1, MINUTES).until(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                List<PaymentInfoEvent> processedPayments = paymentInfoReceiver.getProcessedPayments();
+                List<PaymentErrorEvent> errors = paymentInfoReceiver.getErrors();
+
+                return processedPayments.size() == 1 || errors.size() == 1;
+            }
+        });
+
+        assertFalse(paymentInfoReceiver.getProcessedPayments().isEmpty());
+        assertTrue(paymentInfoReceiver.getErrors().isEmpty());
+
+        List<PaymentInfoEvent> payments = paymentInfoReceiver.getProcessedPayments();
+        PaymentAttempt paymentAttempt = paymentApi.getPaymentAttemptForPaymentId(payments.get(0).getPaymentId());
+        Assert.assertNotNull(paymentAttempt);
+
+        Invoice invoiceForPayment = invoicePaymentApi.getInvoiceForPaymentAttemptId(paymentAttempt.getPaymentAttemptId());
+
+        Assert.assertNotNull(invoiceForPayment);
+        Assert.assertEquals(invoiceForPayment.getId(), invoice.getId());
+        Assert.assertEquals(invoiceForPayment.getAccountId(), account.getId());
+
+        DateTime invoicePaymentAttempt = invoiceForPayment.getLastPaymentAttempt();
+        DateTime correctedDate = invoicePaymentAttempt.minus(invoicePaymentAttempt.millisOfSecond().get());
+        Assert.assertTrue(correctedDate.isEqual(paymentAttempt.getPaymentAttemptDate()));
+
+        Assert.assertEquals(invoiceForPayment.getBalance().floatValue(), new BigDecimal("0").floatValue());
+        Assert.assertEquals(invoiceForPayment.getAmountPaid().floatValue(), invoice.getAmountPaid().floatValue());
+    }
+}
diff --git a/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestPaymentProvider.java b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestPaymentProvider.java
new file mode 100644
index 0000000..a28f7df
--- /dev/null
+++ b/beatrix/src/test/java/com/ning/billing/beatrix/integration/payment/TestPaymentProvider.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.beatrix.integration.payment;
+
+import static com.jayway.awaitility.Awaitility.await;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+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.ning.billing.account.api.Account;
+import com.ning.billing.account.glue.AccountModuleWithMocks;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.glue.InvoiceModuleWithMocks;
+import com.ning.billing.mock.glue.MockJunctionModule;
+import com.ning.billing.payment.RequestProcessor;
+import com.ning.billing.payment.api.PaymentErrorEvent;
+import com.ning.billing.payment.api.PaymentInfoEvent;
+import com.ning.billing.payment.setup.PaymentTestModuleWithMocks;
+import com.ning.billing.util.bus.Bus;
+import com.ning.billing.util.bus.Bus.EventBusException;
+
+@Guice(modules = { PaymentTestModule.class, AccountModuleWithMocks.class, InvoiceModuleWithMocks.class, MockJunctionModule.class })
+public class TestPaymentProvider {
+    @Inject
+    private Bus eventBus;
+    @Inject
+    private RequestProcessor invoiceProcessor;
+    @Inject
+    private TestHelper testHelper;
+
+    private MockPaymentInfoReceiver paymentInfoReceiver;
+
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws EventBusException {
+        paymentInfoReceiver = new MockPaymentInfoReceiver();
+
+        eventBus.start();
+        eventBus.register(invoiceProcessor);
+        eventBus.register(paymentInfoReceiver);
+
+        assertTrue(true);
+    }
+
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws EventBusException {
+        eventBus.unregister(invoiceProcessor);
+        eventBus.unregister(paymentInfoReceiver);
+        eventBus.stop();
+
+        assertTrue(true);
+    }
+
+    @Test
+    public void testSimpleInvoice() throws Exception {
+        final Account account = testHelper.createTestCreditCardAccount();
+
+        Invoice invoice = testHelper.createTestInvoice(account);
+
+        await().atMost(1, MINUTES).until(new Callable<Boolean>() {
+            @Override
+            public Boolean call() throws Exception {
+                List<PaymentInfoEvent> processedPayments = paymentInfoReceiver.getProcessedPayments();
+                List<PaymentErrorEvent> errors = paymentInfoReceiver.getErrors();
+
+                return processedPayments.size() == 1 || errors.size() == 1;
+            }
+        });
+
+        assertFalse(paymentInfoReceiver.getProcessedPayments().isEmpty());
+        // can't check errors; the mock is flaky and results in $0 payment attempt
+        assertTrue(invoice.getPayments().size() > 0);
+    }
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/MockInvoice.java b/payment/src/test/java/com/ning/billing/payment/MockInvoice.java
new file mode 100644
index 0000000..6da3323
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/MockInvoice.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.payment;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.Invoice;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.invoice.api.InvoicePayment;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.entity.ExtendedEntityBase;
+
+public class MockInvoice extends ExtendedEntityBase implements Invoice {
+    private final List<InvoiceItem> invoiceItems = new ArrayList<InvoiceItem>();
+    private final List<InvoicePayment> payments = new ArrayList<InvoicePayment>();
+    private final UUID accountId;
+    private final Integer invoiceNumber;
+    private final DateTime invoiceDate;
+    private final DateTime targetDate;
+    private final Currency currency;
+    private final boolean migrationInvoice;
+
+    // used to create a new invoice
+    public MockInvoice(UUID accountId, DateTime invoiceDate, DateTime targetDate, Currency currency) {
+        this(UUID.randomUUID(), accountId, null, invoiceDate, targetDate, currency, false, null, null);
+    }
+
+    // used to hydrate invoice from persistence layer
+    public MockInvoice(UUID invoiceId, UUID accountId, @Nullable Integer invoiceNumber, DateTime invoiceDate,
+                          DateTime targetDate, Currency currency, boolean isMigrationInvoice, @Nullable String createdBy, @Nullable DateTime createdDate) {
+        super(invoiceId, createdBy, createdDate);
+        this.accountId = accountId;
+        this.invoiceNumber = invoiceNumber;
+        this.invoiceDate = invoiceDate;
+        this.targetDate = targetDate;
+        this.currency = currency;
+        this.migrationInvoice = isMigrationInvoice;
+    }
+
+    @Override
+    public boolean addInvoiceItem(final InvoiceItem item) {
+        return invoiceItems.add(item);
+    }
+
+    @Override
+    public boolean addInvoiceItems(final List<InvoiceItem> items) {
+        return this.invoiceItems.addAll(items);
+    }
+
+    @Override
+    public List<InvoiceItem> getInvoiceItems() {
+        return invoiceItems;
+    }
+
+    @Override
+    public <T extends InvoiceItem> List<InvoiceItem> getInvoiceItems(Class<T> clazz) {
+        List<InvoiceItem> results = new ArrayList<InvoiceItem>();
+        for (InvoiceItem item : invoiceItems) {
+            if ( clazz.isInstance(item) ) {
+                results.add(item);
+            }
+        }
+        return results;
+    }
+
+    @Override
+    public int getNumberOfItems() {
+        return invoiceItems.size();
+    }
+
+    @Override
+    public boolean addPayment(final InvoicePayment payment) {
+        return payments.add(payment);
+    }
+
+    @Override
+    public boolean addPayments(final List<InvoicePayment> payments) {
+        return this.payments.addAll(payments);
+    }
+
+    @Override
+    public List<InvoicePayment> getPayments() {
+        return payments;
+    }
+
+    @Override
+    public int getNumberOfPayments() {
+        return payments.size();
+    }
+
+    @Override
+    public UUID getId() {
+        return id;
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    /**
+     * null until retrieved from the database
+     * @return the invoice number
+     */
+    @Override
+    public Integer getInvoiceNumber() {
+        return invoiceNumber;
+    }
+
+    @Override
+    public DateTime getInvoiceDate() {
+        return invoiceDate;
+    }
+
+    @Override
+    public DateTime getTargetDate() {
+        return targetDate;
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+    
+    @Override
+    public boolean isMigrationInvoice() {
+		return migrationInvoice;
+	}
+
+	@Override
+    public DateTime getLastPaymentAttempt() {
+        DateTime lastPaymentAttempt = null;
+
+        for (final InvoicePayment paymentAttempt : payments) {
+            DateTime paymentAttemptDate = paymentAttempt.getPaymentAttemptDate();
+            if (lastPaymentAttempt == null) {
+                lastPaymentAttempt = paymentAttemptDate;
+            }
+
+            if (lastPaymentAttempt.isBefore(paymentAttemptDate)) {
+                lastPaymentAttempt = paymentAttemptDate;
+            }
+        }
+
+        return lastPaymentAttempt;
+    }
+
+    @Override
+    public BigDecimal getAmountPaid() {
+        BigDecimal amountPaid = BigDecimal.ZERO;
+        for (final InvoicePayment payment : payments) {
+            if (payment.getAmount() != null) {
+                amountPaid = amountPaid.add(payment.getAmount());
+            }
+        }
+        return amountPaid;
+    }
+
+    @Override
+    public BigDecimal getTotalAmount() {
+        BigDecimal result = BigDecimal.ZERO;
+    
+        for(InvoiceItem i : invoiceItems) {
+            result = result.add(i.getAmount());
+        }
+        return result;
+    }
+
+    @Override
+    public BigDecimal getBalance() {
+        return getTotalAmount().subtract(getAmountPaid());
+    }
+
+    @Override
+    public boolean isDueForPayment(final DateTime targetDate, final int numberOfDays) {
+        if (getTotalAmount().compareTo(BigDecimal.ZERO) == 0) {
+            return false;
+        }
+
+        DateTime lastPaymentAttempt = getLastPaymentAttempt();
+        if (lastPaymentAttempt == null) {
+            return true;
+        }
+
+        return !lastPaymentAttempt.plusDays(numberOfDays).isAfter(targetDate);
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultInvoice [items=" + invoiceItems + ", payments=" + payments + ", id=" + id + ", accountId=" + accountId + ", invoiceDate=" + invoiceDate + ", targetDate=" + targetDate + ", currency=" + currency + ", amountPaid=" + getAmountPaid() + ", lastPaymentAttempt=" + getLastPaymentAttempt() + "]";
+    }
+
+    @Override
+    public String getObjectName() {
+        return Invoice.ObjectType;
+    }
+
+    @Override
+    public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void saveFields(List<CustomField> fields, CallContext context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void clearPersistedFields(CallContext context) {
+        throw new UnsupportedOperationException();
+    }
+}
+
diff --git a/payment/src/test/java/com/ning/billing/payment/MockInvoiceCreationEvent.java b/payment/src/test/java/com/ning/billing/payment/MockInvoiceCreationEvent.java
new file mode 100644
index 0000000..9a700cd
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/MockInvoiceCreationEvent.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.payment;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnore;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.joda.time.DateTime;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceCreationEvent;
+import com.ning.billing.util.bus.BusEvent.BusEventType;
+
+public class MockInvoiceCreationEvent implements InvoiceCreationEvent {
+	
+    private final UUID invoiceId;
+    private final UUID accountId;
+    private final BigDecimal amountOwed;
+    private final Currency currency;
+    private final DateTime invoiceCreationDate;
+    private final UUID userToken;
+
+    @JsonCreator
+    public MockInvoiceCreationEvent(@JsonProperty("invoiceId") UUID invoiceId,
+            @JsonProperty("accountId") UUID accountId,
+            @JsonProperty("amountOwed") BigDecimal amountOwed,
+            @JsonProperty("currency") Currency currency,
+            @JsonProperty("invoiceCreationDate") DateTime invoiceCreationDate,
+            @JsonProperty("userToken") UUID userToken) {
+        this.invoiceId = invoiceId;
+        this.accountId = accountId;
+        this.amountOwed = amountOwed;
+        this.currency = currency;
+        this.invoiceCreationDate = invoiceCreationDate;
+        this.userToken = userToken;
+    }
+
+    @JsonIgnore
+	@Override
+	public BusEventType getBusEventType() {
+		return BusEventType.INVOICE_CREATION;
+	}
+
+	@Override
+	public UUID getUserToken() {
+		return userToken;
+	}
+
+    @Override
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    @Override
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public BigDecimal getAmountOwed() {
+        return amountOwed;
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+
+    @Override
+    public DateTime getInvoiceCreationDate() {
+        return invoiceCreationDate;
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultInvoiceCreationNotification [invoiceId=" + invoiceId + ", accountId=" + accountId + ", amountOwed=" + amountOwed + ", currency=" + currency + ", invoiceCreationDate=" + invoiceCreationDate + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((accountId == null) ? 0 : accountId.hashCode());
+        result = prime * result
+                + ((amountOwed == null) ? 0 : amountOwed.hashCode());
+        result = prime * result
+                + ((currency == null) ? 0 : currency.hashCode());
+        result = prime
+                * result
+                + ((invoiceCreationDate == null) ? 0 : invoiceCreationDate
+                        .hashCode());
+        result = prime * result
+                + ((invoiceId == null) ? 0 : invoiceId.hashCode());
+        result = prime * result
+                + ((userToken == null) ? 0 : userToken.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        MockInvoiceCreationEvent other = (MockInvoiceCreationEvent) obj;
+        if (accountId == null) {
+            if (other.accountId != null)
+                return false;
+        } else if (!accountId.equals(other.accountId))
+            return false;
+        if (amountOwed == null) {
+            if (other.amountOwed != null)
+                return false;
+        } else if (!amountOwed.equals(other.amountOwed))
+            return false;
+        if (currency != other.currency)
+            return false;
+        if (invoiceCreationDate == null) {
+            if (other.invoiceCreationDate != null)
+                return false;
+        } else if (invoiceCreationDate.compareTo(other.invoiceCreationDate) != 0)
+            return false;
+        if (invoiceId == null) {
+            if (other.invoiceId != null)
+                return false;
+        } else if (!invoiceId.equals(other.invoiceId))
+            return false;
+        if (userToken == null) {
+            if (other.userToken != null)
+                return false;
+        } else if (!userToken.equals(other.userToken))
+            return false;
+        return true;
+    }
+    
+    
+}
diff --git a/payment/src/test/java/com/ning/billing/payment/MockRecurringInvoiceItem.java b/payment/src/test/java/com/ning/billing/payment/MockRecurringInvoiceItem.java
new file mode 100644
index 0000000..40a927f
--- /dev/null
+++ b/payment/src/test/java/com/ning/billing/payment/MockRecurringInvoiceItem.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.payment;
+
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.invoice.api.InvoiceItem;
+import com.ning.billing.util.entity.EntityBase;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.joda.time.DateTime;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+public class MockRecurringInvoiceItem extends EntityBase implements InvoiceItem {
+    private final BigDecimal rate;
+    private final UUID reversedItemId;
+    protected final UUID invoiceId;
+    protected final UUID accountId;
+    protected final UUID subscriptionId;
+    protected final UUID bundleId;
+    protected final String planName;
+    protected final String phaseName;
+    protected final DateTime startDate;
+    protected final DateTime endDate;
+    protected final BigDecimal amount;
+    protected final Currency currency;
+
+
+    public MockRecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency) { 
+        this(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, rate, null);
+
+    }
+
+    public MockRecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency, UUID reversedItemId) {
+        this(invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate,
+                amount, currency, rate, reversedItemId);
+    }
+
+    public MockRecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency,
+                                String createdBy, DateTime createdDate) {
+        this(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, createdBy, createdDate, rate, null);
+
+    }
+
+    public MockRecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+                                DateTime startDate, DateTime endDate,
+                                BigDecimal amount, BigDecimal rate,
+                                Currency currency, UUID reversedItemId,
+                                String createdBy, DateTime createdDate) {
+        this(id, invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, startDate, endDate, amount, currency, createdBy, createdDate, rate, reversedItemId);
+    }
+    public MockRecurringInvoiceItem(UUID invoiceId, UUID accountId, UUID bundleId, UUID subscriptionId, String planName, String phaseName,
+            DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency, BigDecimal rate, UUID reversedItemId){
+        this(UUID.randomUUID(), invoiceId, accountId, bundleId, subscriptionId, planName, phaseName,
+                startDate, endDate, amount, currency, null, null, rate, reversedItemId);
+    }
+
+
+    public MockRecurringInvoiceItem(UUID id, UUID invoiceId, UUID accountId, @Nullable UUID bundleId, @Nullable UUID subscriptionId, String planName, String phaseName,
+            DateTime startDate, DateTime endDate, BigDecimal amount, Currency currency,
+            @Nullable String createdBy, @Nullable DateTime createdDate, BigDecimal rate, UUID reversedItemId) {
+        super(id, createdBy, createdDate);
+        this.invoiceId = invoiceId;
+        this.accountId = accountId;
+        this.subscriptionId = subscriptionId;
+        this.bundleId = bundleId;
+        this.planName = planName;
+        this.phaseName = phaseName;
+        this.startDate = startDate;
+        this.endDate = endDate;
+        this.amount = amount;
+        this.currency = currency;
+        this.rate = rate;
+        this.reversedItemId = reversedItemId;
+
+    }
+
+    public DateTime getCreatedDate() {
+        return createdDate;
+    }
+
+    @Override
+    public UUID getId() {
+        return id;
+    }
+
+    @Override
+    public UUID getInvoiceId() {
+        return invoiceId;
+    }
+
+    @Override
+    public UUID getBundleId() {
+        return bundleId;
+    }
+    
+    public UUID getAccountId() {
+        return accountId;
+    }
+
+    @Override
+    public UUID getSubscriptionId() {
+        return subscriptionId;
+    }
+
+    @Override
+    public String getPlanName() {
+        return planName;
+    }
+
+    @Override
+    public String getPhaseName() {
+        return phaseName;
+    }
+
+    @Override
+    public BigDecimal getAmount() {
+        return amount;
+    }
+
+    @Override
+    public DateTime getStartDate() {
+        return startDate;
+    }
+
+    @Override
+    public DateTime getEndDate() {
+        return endDate;
+    }
+
+    @Override
+    public Currency getCurrency() {
+        return currency;
+    }
+    @Override
+    public InvoiceItem asReversingItem() {
+        throw new NotImplementedException();
+    }
+
+    @Override
+    public String getDescription() {
+        return String.format("%s from %s to %s", phaseName, startDate.toString(), endDate.toString());
+    }
+
+    public UUID getReversedItemId() {
+        return reversedItemId;
+    }
+
+    public boolean reversesItem() {
+        return (reversedItemId != null);
+    }
+
+    public BigDecimal getRate() {
+        return rate;
+    }
+
+    @Override
+    public int compareTo(InvoiceItem item) {
+        if (item == null) {
+            return -1;
+        }
+        if (!(item instanceof MockRecurringInvoiceItem)) {
+            return -1;
+        }
+
+        MockRecurringInvoiceItem that = (MockRecurringInvoiceItem) item;
+        int compareAccounts = getAccountId().compareTo(that.getAccountId());
+        if (compareAccounts == 0 && bundleId != null) {
+            int compareBundles = getBundleId().compareTo(that.getBundleId());
+            if (compareBundles == 0 && subscriptionId != null) {
+
+                int compareSubscriptions = getSubscriptionId().compareTo(that.getSubscriptionId());
+                if (compareSubscriptions == 0) {
+                    int compareStartDates = getStartDate().compareTo(that.getStartDate());
+                    if (compareStartDates == 0) {
+                        return getEndDate().compareTo(that.getEndDate());
+                    } else {
+                        return compareStartDates;
+                    }
+                } else {
+                    return compareSubscriptions;
+                }
+            } else {
+                return compareBundles;
+            }
+        } else {
+            return compareAccounts;
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        MockRecurringInvoiceItem that = (MockRecurringInvoiceItem) o;
+
+        if (accountId.compareTo(that.accountId) != 0) return false;
+        if (amount.compareTo(that.amount) != 0) return false;
+        if (currency != that.currency) return false;
+        if (startDate.compareTo(that.startDate) != 0) return false;
+        if (endDate.compareTo(that.endDate) != 0) return false;
+        if (!phaseName.equals(that.phaseName)) return false;
+        if (!planName.equals(that.planName)) return false;
+        if (rate.compareTo(that.rate) != 0) return false;
+        if (reversedItemId != null ? !reversedItemId.equals(that.reversedItemId) : that.reversedItemId != null)
+            return false;
+        if (subscriptionId != null ? !subscriptionId.equals(that.subscriptionId) : that.subscriptionId != null)
+            return false;
+        if (bundleId != null ? !bundleId.equals(that.bundleId) : that.bundleId != null)
+            return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = accountId.hashCode();
+        result = 31 * result + (subscriptionId != null ? subscriptionId.hashCode() : 0);
+        result = 31 * result + (bundleId != null ? bundleId.hashCode() : 0);
+        result = 31 * result + planName.hashCode();
+        result = 31 * result + phaseName.hashCode();
+        result = 31 * result + startDate.hashCode();
+        result = 31 * result + endDate.hashCode();
+        result = 31 * result + amount.hashCode();
+        result = 31 * result + rate.hashCode();
+        result = 31 * result + currency.hashCode();
+        result = 31 * result + (reversedItemId != null ? reversedItemId.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append(phaseName).append(", ");
+        sb.append(startDate.toString()).append(", ");
+        sb.append(endDate.toString()).append(", ");
+        sb.append(amount.toString()).append(", ");
+        sb.append("subscriptionId = ").append(subscriptionId == null ? null : subscriptionId.toString()).append(", ");
+        sb.append("bundleId = ").append(bundleId == null ? null : bundleId.toString()).append(", ");
+
+        return sb.toString();
+    }
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/MockNotificationQueueModule.java b/util/src/test/java/com/ning/billing/mock/glue/MockNotificationQueueModule.java
new file mode 100644
index 0000000..899be2b
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/MockNotificationQueueModule.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.mock.glue;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.util.notificationq.MockNotificationQueueService;
+import com.ning.billing.util.notificationq.NotificationQueueService;
+
+public class MockNotificationQueueModule extends AbstractModule {
+
+    @Override
+    protected void configure() {
+        bind(NotificationQueueService.class).to(MockNotificationQueueService.class).asEagerSingleton();
+    }
+
+}
diff --git a/util/src/test/java/com/ning/billing/mock/glue/TestDbiModule.java b/util/src/test/java/com/ning/billing/mock/glue/TestDbiModule.java
new file mode 100644
index 0000000..c42717d
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/glue/TestDbiModule.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.mock.glue;
+
+import org.skife.config.ConfigurationObjectFactory;
+import org.skife.jdbi.v2.IDBI;
+
+import com.google.inject.AbstractModule;
+import com.ning.billing.dbi.DBIProvider;
+import com.ning.billing.dbi.DbiConfig;
+import com.ning.billing.dbi.MysqlTestingHelper;
+
+public class TestDbiModule extends AbstractModule {
+ 
+    protected void configure() {
+
+        final MysqlTestingHelper helper = new MysqlTestingHelper();
+        bind(MysqlTestingHelper.class).toInstance(helper);
+        if (helper.isUsingLocalInstance()) {
+            bind(IDBI.class).toProvider(DBIProvider.class).asEagerSingleton();
+            final DbiConfig config = new ConfigurationObjectFactory(System.getProperties()).build(DbiConfig.class);
+            bind(DbiConfig.class).toInstance(config);
+        } else {
+            final IDBI dbi = helper.getDBI(); 
+            bind(IDBI.class).toInstance(dbi);
+        }
+
+     }
+}
diff --git a/util/src/test/java/com/ning/billing/mock/MockAccountBuilder.java b/util/src/test/java/com/ning/billing/mock/MockAccountBuilder.java
new file mode 100644
index 0000000..ec2b431
--- /dev/null
+++ b/util/src/test/java/com/ning/billing/mock/MockAccountBuilder.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright 2010-2011 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.mock;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.commons.lang.NotImplementedException;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+
+import com.ning.billing.account.api.Account;
+import com.ning.billing.account.api.MutableAccountData;
+import com.ning.billing.catalog.api.Currency;
+import com.ning.billing.junction.api.BlockingState;
+import com.ning.billing.util.callcontext.CallContext;
+import com.ning.billing.util.customfield.CustomField;
+import com.ning.billing.util.tag.ControlTagType;
+import com.ning.billing.util.tag.Tag;
+import com.ning.billing.util.tag.TagDefinition;
+
+public class MockAccountBuilder {
+    private final UUID id;
+    private String externalKey;
+    private String email;
+    private String name;
+    private int firstNameLength;
+    private Currency currency;
+    private int billingCycleDay;
+    private String paymentProviderName;
+    private DateTimeZone timeZone;
+    private String locale;
+    private String address1;
+    private String address2;
+    private String companyName;
+    private String city;
+    private String stateOrProvince;
+    private String country;
+    private String postalCode;
+    private String phone;
+    private boolean migrated;
+    private boolean isNotifiedForInvoices;
+    private String createdBy;
+    private DateTime createdDate;
+    private String updatedBy;
+    private DateTime updatedDate;
+
+    public MockAccountBuilder() {
+        this(UUID.randomUUID());
+    }
+
+    public MockAccountBuilder(final UUID id) {
+        this.id = id;
+    }
+
+    public MockAccountBuilder externalKey(final String externalKey) {
+        this.externalKey = externalKey;
+        return this;
+    }
+
+    public MockAccountBuilder email(final String email) {
+        this.email = email;
+        return this;
+    }
+
+    public MockAccountBuilder name(final String name) {
+        this.name = name;
+        return this;
+    }
+
+    public MockAccountBuilder firstNameLength(final int firstNameLength) {
+        this.firstNameLength = firstNameLength;
+        return this;
+    }
+
+    public MockAccountBuilder billingCycleDay(final int billingCycleDay) {
+        this.billingCycleDay = billingCycleDay;
+        return this;
+    }
+
+    public MockAccountBuilder currency(final Currency currency) {
+        this.currency = currency;
+        return this;
+    }
+
+    public MockAccountBuilder paymentProviderName(final String paymentProviderName) {
+        this.paymentProviderName = paymentProviderName;
+        return this;
+    }
+
+    public MockAccountBuilder timeZone(final DateTimeZone timeZone) {
+        this.timeZone = timeZone;
+        return this;
+    }
+
+    public MockAccountBuilder locale(final String locale) {
+        this.locale = locale;
+        return this;
+    }
+
+    public MockAccountBuilder address1(final String address1) {
+        this.address1 = address1;
+        return this;
+    }
+
+    public MockAccountBuilder address2(final String address2) {
+        this.address2 = address2;
+        return this;
+    }
+
+    public MockAccountBuilder companyName(final String companyName) {
+        this.companyName = companyName;
+        return this;
+    }
+
+    public MockAccountBuilder city(final String city) {
+        this.city = city;
+        return this;
+    }
+
+    public MockAccountBuilder stateOrProvince(final String stateOrProvince) {
+        this.stateOrProvince = stateOrProvince;
+        return this;
+    }
+
+    public MockAccountBuilder postalCode(final String postalCode) {
+        this.postalCode = postalCode;
+        return this;
+    }
+
+    public MockAccountBuilder country(final String country) {
+        this.country = country;
+        return this;
+    }
+
+    public MockAccountBuilder phone(final String phone) {
+        this.phone = phone;
+        return this;
+    }
+
+    public MockAccountBuilder migrated(final boolean migrated) {
+        this.migrated = migrated;
+        return this;
+    }
+
+    public MockAccountBuilder isNotifiedForInvoices(final boolean isNotifiedForInvoices) {
+        this.isNotifiedForInvoices = isNotifiedForInvoices;
+        return this;
+    }
+
+    public MockAccountBuilder createdBy(final String createdBy) {
+        this.createdBy = createdBy;
+        return this;
+    }
+
+    public MockAccountBuilder createdDate(final DateTime createdDate) {
+        this.createdDate = createdDate;
+        return this;
+    }
+
+    public MockAccountBuilder updatedBy(final String updatedBy) {
+        this.updatedBy = updatedBy;
+        return this;
+    }
+
+    public MockAccountBuilder updatedDate(final DateTime updatedDate) {
+        this.updatedDate = updatedDate;
+        return this;
+    }
+
+    public Account build() {
+        return new Account(){
+            
+            @Override
+            public String getExternalKey() {
+                return externalKey;
+            }
+
+            @Override
+            public String getName() {
+               
+                return name;
+            }
+
+            @Override
+            public int getFirstNameLength() {
+               
+                return firstNameLength;
+            }
+
+            @Override
+            public String getEmail() {
+               
+                return email;
+            }
+
+            @Override
+            public int getBillCycleDay() {
+               
+                return billingCycleDay;
+            }
+
+            @Override
+            public Currency getCurrency() {
+               
+                return currency;
+            }
+
+            @Override
+            public String getPaymentProviderName() {
+               
+                return paymentProviderName;
+            }
+
+            @Override
+            public DateTimeZone getTimeZone() {
+               
+                return timeZone;
+            }
+
+            @Override
+            public String getLocale() {
+               
+                return locale;
+            }
+
+            @Override
+            public String getAddress1() {
+               
+                return address1;
+            }
+
+            @Override
+            public String getAddress2() {
+               
+                return address2;
+            }
+
+            @Override
+            public String getCompanyName() {
+               
+                return companyName;
+            }
+
+            @Override
+            public String getCity() {
+               
+                return city;
+            }
+
+            @Override
+            public String getStateOrProvince() {
+               
+                return stateOrProvince;
+            }
+
+            @Override
+            public String getPostalCode() {
+               
+                return postalCode;
+            }
+
+            @Override
+            public String getCountry() {
+               
+                return country;
+            }
+
+            @Override
+            public String getPhone() {
+               
+                return phone;
+            }
+
+            @Override
+            public boolean isMigrated() {
+               
+                return migrated;
+            }
+
+            @Override
+            public boolean isNotifiedForInvoices() {
+               
+                return isNotifiedForInvoices;
+            }
+
+            @Override
+            public String getFieldValue(String fieldName) {
+               
+                return null;
+            }
+
+            @Override
+            public void setFieldValue(String fieldName, String fieldValue) {
+               
+                
+            }
+
+            @Override
+            public void saveFieldValue(String fieldName, String fieldValue, CallContext context) {
+               
+                
+            }
+
+            @Override
+            public List<CustomField> getFieldList() {
+               
+                return null;
+            }
+
+            @Override
+            public void setFields(List<CustomField> fields) {
+               
+                
+            }
+
+            @Override
+            public void saveFields(List<CustomField> fields, CallContext context) {
+               
+                
+            }
+
+            @Override
+            public void clearFields() {
+               
+                
+            }
+
+            @Override
+            public void clearPersistedFields(CallContext context) {
+               
+                
+            }
+
+            @Override
+            public String getObjectName() {
+               
+                return null;
+            }
+
+            @Override
+            public String getUpdatedBy() {
+               
+                return updatedBy;
+            }
+
+            @Override
+            public DateTime getUpdatedDate() {
+               
+                return updatedDate;
+            }
+
+            @Override
+            public UUID getId() {
+               
+                return id;
+            }
+
+            @Override
+            public String getCreatedBy() {
+               
+                return createdBy;
+            }
+
+            @Override
+            public DateTime getCreatedDate() {
+               
+                return createdDate;
+            }
+
+            @Override
+            public List<Tag> getTagList() {
+               
+                return null;
+            }
+
+            @Override
+            public boolean hasTag(TagDefinition tagDefinition) {
+               
+                return false;
+            }
+
+            @Override
+            public boolean hasTag(ControlTagType controlTagType) {
+               
+                return false;
+            }
+
+            @Override
+            public void addTag(TagDefinition definition) {
+               
+                
+            }
+
+            @Override
+            public void addTags(List<Tag> tags) {
+               
+                
+            }
+
+            @Override
+            public void addTagsFromDefinitions(List<TagDefinition> tagDefinitions) {
+               
+                
+            }
+
+            @Override
+            public void clearTags() {
+               
+                
+            }
+
+            @Override
+            public void removeTag(TagDefinition definition) {
+               
+                
+            }
+
+            @Override
+            public boolean generateInvoice() {
+               
+                return true;
+            }
+
+            @Override
+            public boolean processPayment() {
+               
+                return true;
+            }
+
+            @Override
+            public BlockingState getBlockingState() {
+               
+                return null;
+            }
+
+            @Override
+            public MutableAccountData toMutableAccountData() {
+               
+                throw new NotImplementedException();
+            }
+            
+            
+        };
+        
+       
+    }
+}