killbill-uncached

analytics: add a bunch of tests for BusinessInvoiceFactory This

4/22/2013 6:04:08 PM

Details

diff --git a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java
index ed19754..6347161 100644
--- a/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java
+++ b/osgi-bundles/bundles/analytics/src/main/java/com/ning/billing/osgi/bundles/analytics/dao/factory/BusinessInvoiceFactory.java
@@ -19,9 +19,11 @@ package com.ning.billing.osgi.bundles.analytics.dao.factory;
 import java.math.BigDecimal;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 
 import javax.annotation.Nullable;
@@ -319,7 +321,8 @@ public class BusinessInvoiceFactory extends BusinessFactoryBase {
      * @param allInvoiceItems all invoice items, across all invoices
      * @return a mapping reparee invoice item id to REPAIR_ADJ item
      */
-    private Map<InvoiceItem, InvoiceItem> findRepareeInvoiceItems(final Collection<InvoiceItem> allInvoiceItems) {
+    @VisibleForTesting
+    Map<InvoiceItem, InvoiceItem> findRepareeInvoiceItems(final Collection<InvoiceItem> allInvoiceItems) {
         // Build a convenience mapping between items -> repair_adj items (inverse of linkedItemId)
         final Map<UUID, InvoiceItem> repairedInvoiceItemIdToRepairInvoiceItemMappings = new HashMap<UUID, InvoiceItem>();
         for (final InvoiceItem invoiceItem : allInvoiceItems) {
@@ -358,30 +361,52 @@ public class BusinessInvoiceFactory extends BusinessFactoryBase {
         return repareeInvoiceItemToRepairItemMappings;
     }
 
-    private Collection<AdjustedCBAInvoiceItem> buildMergedCBAItems(final Multimap<UUID, InvoiceItem> allInvoiceItems,
-                                                                   final Map<InvoiceItem, InvoiceItem> repareeInvoiceItemToRepairItemMappings) {
-        final Map<UUID, BigDecimal> cbaPerInvoice = new HashMap<UUID, BigDecimal>();
+    @VisibleForTesting
+    Collection<AdjustedCBAInvoiceItem> buildMergedCBAItems(final Multimap<UUID, InvoiceItem> allInvoiceItems,
+                                                           final Map<InvoiceItem, InvoiceItem> repareeInvoiceItemToRepairItemMappings) {
+        // Build a black list of invoices for which we should not adjust the CBA when rebalancing the reparee item.
+        final Set<UUID> repairInvoiceBlackList = new HashSet<UUID>();
+        for (final InvoiceItem repairInvoiceItem : repareeInvoiceItemToRepairItemMappings.values()) {
+            boolean shouldBlackList = true;
+            // Only adjust CBA items on invoices having CBA already. Otherwise, don't do anything (e.g. for unpaid repaired invoices).
+            for (final InvoiceItem invoiceItem : allInvoiceItems.get(repairInvoiceItem.getInvoiceId())) {
+                if (invoiceItem.getInvoiceItemType().equals(InvoiceItemType.CBA_ADJ) &&
+                    invoiceItem.getAmount().compareTo(repairInvoiceItem.getAmount().negate()) == 0) {
+                    shouldBlackList = false;
+                }
+            }
+            if (shouldBlackList) {
+                // We can't blacklist the reparee invoice because it might have been repaired...
+                repairInvoiceBlackList.add(repairInvoiceItem.getInvoiceId());
+            }
+        }
+
+        final Map<UUID, BigDecimal> cbaAdjustmentPerInvoice = new HashMap<UUID, BigDecimal>();
 
         // Adjust the CBAs in case of repair.
-        // On the new invoice (with the reparee item), we need to add the amount of the reparee to the CBA amount.
         // On the original invoice (with the repair item), we need to substract the amount of the reparee to the CBA amount.
+        // On the new invoice (with the reparee item), we need to add the amount of the reparee to the CBA amount.
         for (final InvoiceItem repareeInvoiceItem : repareeInvoiceItemToRepairItemMappings.keySet()) {
-            // The reparee item was on a new invoice
-            if (cbaPerInvoice.get(repareeInvoiceItem.getInvoiceId()) == null) {
-                cbaPerInvoice.put(repareeInvoiceItem.getInvoiceId(), BigDecimal.ZERO);
-            }
-            final BigDecimal currentCBAForNewInvoice = cbaPerInvoice.get(repareeInvoiceItem.getInvoiceId());
-            final BigDecimal adjustedCBAForNewInvoice = currentCBAForNewInvoice.add(repareeInvoiceItem.getAmount());
-            cbaPerInvoice.put(repareeInvoiceItem.getInvoiceId(), adjustedCBAForNewInvoice);
-
             // The repair item was on the original invoice
             final InvoiceItem repairInvoiceItem = repareeInvoiceItemToRepairItemMappings.get(repareeInvoiceItem);
-            if (cbaPerInvoice.get(repairInvoiceItem.getInvoiceId()) == null) {
-                cbaPerInvoice.put(repairInvoiceItem.getInvoiceId(), BigDecimal.ZERO);
+            if (repairInvoiceBlackList.contains(repairInvoiceItem.getInvoiceId())) {
+                continue;
+            }
+
+            if (cbaAdjustmentPerInvoice.get(repairInvoiceItem.getInvoiceId()) == null) {
+                cbaAdjustmentPerInvoice.put(repairInvoiceItem.getInvoiceId(), BigDecimal.ZERO);
             }
-            final BigDecimal currentCBAForOriginalInvoice = cbaPerInvoice.get(repairInvoiceItem.getInvoiceId());
+            final BigDecimal currentCBAForOriginalInvoice = cbaAdjustmentPerInvoice.get(repairInvoiceItem.getInvoiceId());
             final BigDecimal adjustedCBAForOriginalInvoice = currentCBAForOriginalInvoice.add(repareeInvoiceItem.getAmount().negate());
-            cbaPerInvoice.put(repairInvoiceItem.getInvoiceId(), adjustedCBAForOriginalInvoice);
+            cbaAdjustmentPerInvoice.put(repairInvoiceItem.getInvoiceId(), adjustedCBAForOriginalInvoice);
+
+            // The reparee item was on a new invoice
+            if (cbaAdjustmentPerInvoice.get(repareeInvoiceItem.getInvoiceId()) == null) {
+                cbaAdjustmentPerInvoice.put(repareeInvoiceItem.getInvoiceId(), BigDecimal.ZERO);
+            }
+            final BigDecimal currentCBAForNewInvoice = cbaAdjustmentPerInvoice.get(repareeInvoiceItem.getInvoiceId());
+            final BigDecimal adjustedCBAForNewInvoice = currentCBAForNewInvoice.add(repareeInvoiceItem.getAmount());
+            cbaAdjustmentPerInvoice.put(repareeInvoiceItem.getInvoiceId(), adjustedCBAForNewInvoice);
         }
 
         // Now, we just combine the other CBA items
@@ -391,7 +416,6 @@ public class BusinessInvoiceFactory extends BusinessFactoryBase {
                 continue;
             }
 
-            // Only adjust CBA items on invoices having CBA already. Otherwise, don't do anything (e.g. for unpaid repaired invoices).
             final Collection<InvoiceItem> cbaItemsForInvoice = Collections2.filter(allInvoiceItems.get(invoiceId), new Predicate<InvoiceItem>() {
                 @Override
                 public boolean apply(final InvoiceItem invoiceItem) {
@@ -402,7 +426,7 @@ public class BusinessInvoiceFactory extends BusinessFactoryBase {
                 continue;
             }
 
-            BigDecimal revenueRecognizableCBA = cbaPerInvoice.get(invoiceId);
+            BigDecimal revenueRecognizableCBA = cbaAdjustmentPerInvoice.get(invoiceId);
             if (revenueRecognizableCBA == null) {
                 revenueRecognizableCBA = BigDecimal.ZERO;
             }
diff --git a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/factory/TestBusinessInvoiceFactory.java b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/factory/TestBusinessInvoiceFactory.java
index fa41527..b1621aa 100644
--- a/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/factory/TestBusinessInvoiceFactory.java
+++ b/osgi-bundles/bundles/analytics/src/test/java/com/ning/billing/osgi/bundles/analytics/dao/factory/TestBusinessInvoiceFactory.java
@@ -41,10 +41,12 @@ import com.ning.killbill.osgi.libs.killbill.OSGIKillbillLogService;
 
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Multimap;
 
 public class TestBusinessInvoiceFactory extends AnalyticsTestSuiteNoDB {
 
-    private BusinessInvoiceFactory invoiceDao;
+    private BusinessInvoiceFactory invoiceFactory;
 
     @Override
     @BeforeMethod(groups = "fast")
@@ -65,7 +67,7 @@ public class TestBusinessInvoiceFactory extends AnalyticsTestSuiteNoDB {
             }
         }).when(osgiKillbillLogService).log(Mockito.anyInt(), Mockito.anyString());
 
-        invoiceDao = new BusinessInvoiceFactory(logService, null);
+        invoiceFactory = new BusinessInvoiceFactory(osgiKillbillLogService, null);
     }
 
     @Test(groups = "fast")
@@ -73,114 +75,114 @@ public class TestBusinessInvoiceFactory extends AnalyticsTestSuiteNoDB {
         final UUID invoiceId = UUID.randomUUID();
 
         // Classic account credit ($10), from the perspective of the CREDIT_ADJ item
-        final BusinessInvoiceItemBaseModelDao businessCreditAdjItem = invoiceDao.createBusinessInvoiceItem(account,
-                                                                                                           invoice,
-                                                                                                           createInvoiceItem(invoiceId, InvoiceItemType.CREDIT_ADJ, new BigDecimal("-10")),
-                                                                                                           ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, new BigDecimal("10"))),
-                                                                                                           null,
-                                                                                                           null,
-                                                                                                           null,
-                                                                                                           invoiceItemRecordId,
-                                                                                                           auditLog,
-                                                                                                           accountRecordId,
-                                                                                                           tenantRecordId,
-                                                                                                           reportGroup,
-                                                                                                           callContext);
+        final BusinessInvoiceItemBaseModelDao businessCreditAdjItem = invoiceFactory.createBusinessInvoiceItem(account,
+                                                                                                               invoice,
+                                                                                                               createInvoiceItem(invoiceId, InvoiceItemType.CREDIT_ADJ, new BigDecimal("-10")),
+                                                                                                               ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, new BigDecimal("10"))),
+                                                                                                               null,
+                                                                                                               null,
+                                                                                                               null,
+                                                                                                               invoiceItemRecordId,
+                                                                                                               auditLog,
+                                                                                                               accountRecordId,
+                                                                                                               tenantRecordId,
+                                                                                                               reportGroup,
+                                                                                                               callContext);
         // We ignore these
         Assert.assertNull(businessCreditAdjItem);
 
         // Classic account credit ($10), from the perspective of the CBA_ADJ item
-        final BusinessInvoiceItemBaseModelDao businessCreditItem = invoiceDao.createBusinessInvoiceItem(account,
-                                                                                                        invoice,
-                                                                                                        createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, new BigDecimal("10")),
-                                                                                                        ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.CREDIT_ADJ, new BigDecimal("-10"))),
-                                                                                                        null,
-                                                                                                        null,
-                                                                                                        null,
-                                                                                                        invoiceItemRecordId,
-                                                                                                        auditLog,
-                                                                                                        accountRecordId,
-                                                                                                        tenantRecordId,
-                                                                                                        reportGroup,
-                                                                                                        callContext);
+        final BusinessInvoiceItemBaseModelDao businessCreditItem = invoiceFactory.createBusinessInvoiceItem(account,
+                                                                                                            invoice,
+                                                                                                            createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, new BigDecimal("10")),
+                                                                                                            ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.CREDIT_ADJ, new BigDecimal("-10"))),
+                                                                                                            null,
+                                                                                                            null,
+                                                                                                            null,
+                                                                                                            invoiceItemRecordId,
+                                                                                                            auditLog,
+                                                                                                            accountRecordId,
+                                                                                                            tenantRecordId,
+                                                                                                            reportGroup,
+                                                                                                            callContext);
         // We treat these as NOT recognizable account credits
         Assert.assertEquals(businessCreditItem.getAmount().compareTo(new BigDecimal("10")), 0);
         Assert.assertEquals(businessCreditItem.getItemType(), InvoiceItemType.CBA_ADJ.toString());
         Assert.assertFalse(businessCreditItem.getRevenueRecognizable());
 
         // Invoice adjustment, not to be mixed with credits!
-        final BusinessInvoiceItemBaseModelDao businessInvoiceAdjustmentItem = invoiceDao.createBusinessInvoiceItem(account,
-                                                                                                                   invoice,
-                                                                                                                   createInvoiceItem(invoiceId, InvoiceItemType.CREDIT_ADJ, new BigDecimal("-10")),
-                                                                                                                   ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("10"))),
-                                                                                                                   null,
-                                                                                                                   null,
-                                                                                                                   null,
-                                                                                                                   invoiceItemRecordId,
-                                                                                                                   auditLog,
-                                                                                                                   accountRecordId,
-                                                                                                                   tenantRecordId,
-                                                                                                                   reportGroup,
-                                                                                                                   callContext);
+        final BusinessInvoiceItemBaseModelDao businessInvoiceAdjustmentItem = invoiceFactory.createBusinessInvoiceItem(account,
+                                                                                                                       invoice,
+                                                                                                                       createInvoiceItem(invoiceId, InvoiceItemType.CREDIT_ADJ, new BigDecimal("-10")),
+                                                                                                                       ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("10"))),
+                                                                                                                       null,
+                                                                                                                       null,
+                                                                                                                       null,
+                                                                                                                       invoiceItemRecordId,
+                                                                                                                       auditLog,
+                                                                                                                       accountRecordId,
+                                                                                                                       tenantRecordId,
+                                                                                                                       reportGroup,
+                                                                                                                       callContext);
         Assert.assertEquals(businessInvoiceAdjustmentItem.getAmount().compareTo(new BigDecimal("-10")), 0);
         Assert.assertEquals(businessInvoiceAdjustmentItem.getItemType(), InvoiceItemType.CREDIT_ADJ.toString());
         // Recognizable by default
         Assert.assertTrue(businessInvoiceAdjustmentItem.getRevenueRecognizable());
 
         // Invoice adjustment via refund
-        final BusinessInvoiceItemBaseModelDao businessRefundInvoiceAdjustmentItem = invoiceDao.createBusinessInvoiceItem(account,
-                                                                                                                         invoice,
-                                                                                                                         createInvoiceItem(invoiceId, InvoiceItemType.REFUND_ADJ, new BigDecimal("-10")),
-                                                                                                                         ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("10"))),
-                                                                                                                         null,
-                                                                                                                         null,
-                                                                                                                         null,
-                                                                                                                         invoiceItemRecordId,
-                                                                                                                         auditLog,
-                                                                                                                         accountRecordId,
-                                                                                                                         tenantRecordId,
-                                                                                                                         reportGroup,
-                                                                                                                         callContext);
+        final BusinessInvoiceItemBaseModelDao businessRefundInvoiceAdjustmentItem = invoiceFactory.createBusinessInvoiceItem(account,
+                                                                                                                             invoice,
+                                                                                                                             createInvoiceItem(invoiceId, InvoiceItemType.REFUND_ADJ, new BigDecimal("-10")),
+                                                                                                                             ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("10"))),
+                                                                                                                             null,
+                                                                                                                             null,
+                                                                                                                             null,
+                                                                                                                             invoiceItemRecordId,
+                                                                                                                             auditLog,
+                                                                                                                             accountRecordId,
+                                                                                                                             tenantRecordId,
+                                                                                                                             reportGroup,
+                                                                                                                             callContext);
         Assert.assertEquals(businessRefundInvoiceAdjustmentItem.getAmount().compareTo(new BigDecimal("-10")), 0);
         Assert.assertEquals(businessRefundInvoiceAdjustmentItem.getItemType(), InvoiceItemType.REFUND_ADJ.toString());
         // Recognizable by default
         Assert.assertTrue(businessRefundInvoiceAdjustmentItem.getRevenueRecognizable());
 
         // Item adjustment
-        final BusinessInvoiceItemBaseModelDao businessInvoiceItemAdjustmentItem = invoiceDao.createBusinessInvoiceItem(account,
-                                                                                                                       invoice,
-                                                                                                                       createInvoiceItem(invoiceId, InvoiceItemType.ITEM_ADJ, new BigDecimal("-10")),
-                                                                                                                       ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("10"))),
-                                                                                                                       null,
-                                                                                                                       null,
-                                                                                                                       null,
-                                                                                                                       invoiceItemRecordId,
-                                                                                                                       auditLog,
-                                                                                                                       accountRecordId,
-                                                                                                                       tenantRecordId,
-                                                                                                                       reportGroup,
-                                                                                                                       callContext);
+        final BusinessInvoiceItemBaseModelDao businessInvoiceItemAdjustmentItem = invoiceFactory.createBusinessInvoiceItem(account,
+                                                                                                                           invoice,
+                                                                                                                           createInvoiceItem(invoiceId, InvoiceItemType.ITEM_ADJ, new BigDecimal("-10")),
+                                                                                                                           ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("10"))),
+                                                                                                                           null,
+                                                                                                                           null,
+                                                                                                                           null,
+                                                                                                                           invoiceItemRecordId,
+                                                                                                                           auditLog,
+                                                                                                                           accountRecordId,
+                                                                                                                           tenantRecordId,
+                                                                                                                           reportGroup,
+                                                                                                                           callContext);
         Assert.assertEquals(businessInvoiceItemAdjustmentItem.getAmount().compareTo(new BigDecimal("-10")), 0);
         Assert.assertEquals(businessInvoiceItemAdjustmentItem.getItemType(), InvoiceItemType.ITEM_ADJ.toString());
         // Recognizable by default
         Assert.assertTrue(businessInvoiceItemAdjustmentItem.getRevenueRecognizable());
 
         // System generated account credit
-        final BusinessInvoiceItemBaseModelDao businessCBAItem = invoiceDao.createBusinessInvoiceItem(account,
-                                                                                                     invoice,
-                                                                                                     createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, new BigDecimal("10")),
-                                                                                                     ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("30")),
-                                                                                                                                   createInvoiceItem(invoiceId, InvoiceItemType.REPAIR_ADJ, new BigDecimal("-30")),
-                                                                                                                                   createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("20"))),
-                                                                                                     null,
-                                                                                                     null,
-                                                                                                     null,
-                                                                                                     invoiceItemRecordId,
-                                                                                                     auditLog,
-                                                                                                     accountRecordId,
-                                                                                                     tenantRecordId,
-                                                                                                     reportGroup,
-                                                                                                     callContext);
+        final BusinessInvoiceItemBaseModelDao businessCBAItem = invoiceFactory.createBusinessInvoiceItem(account,
+                                                                                                         invoice,
+                                                                                                         createInvoiceItem(invoiceId, InvoiceItemType.CBA_ADJ, new BigDecimal("10")),
+                                                                                                         ImmutableList.<InvoiceItem>of(createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("30")),
+                                                                                                                                       createInvoiceItem(invoiceId, InvoiceItemType.REPAIR_ADJ, new BigDecimal("-30")),
+                                                                                                                                       createInvoiceItem(invoiceId, InvoiceItemType.RECURRING, new BigDecimal("20"))),
+                                                                                                         null,
+                                                                                                         null,
+                                                                                                         null,
+                                                                                                         invoiceItemRecordId,
+                                                                                                         auditLog,
+                                                                                                         accountRecordId,
+                                                                                                         tenantRecordId,
+                                                                                                         reportGroup,
+                                                                                                         callContext);
         Assert.assertEquals(businessCBAItem.getAmount().compareTo(new BigDecimal("10")), 0);
         Assert.assertEquals(businessCBAItem.getItemType(), InvoiceItemType.CBA_ADJ.toString());
         // Recognizable by default
@@ -246,7 +248,7 @@ public class TestBusinessInvoiceFactory extends AnalyticsTestSuiteNoDB {
         allInvoiceItems.putAll(originalInvoice1, ImmutableList.<InvoiceItem>of(recurring1, repair1, reparation1));
         allInvoiceItems.putAll(originalInvoice2, ImmutableList.<InvoiceItem>of(recurring2, repair2, reparation2));
         allInvoiceItems.put(otherInvoice3, externalCharge);
-        final Collection<InvoiceItem> sanitizedInvoiceItems = invoiceDao.sanitizeInvoiceItems(allInvoiceItems);
+        final Collection<InvoiceItem> sanitizedInvoiceItems = invoiceFactory.sanitizeInvoiceItems(allInvoiceItems);
         Assert.assertEquals(sanitizedInvoiceItems.size(), 2 + 2 + 1);
         for (final InvoiceItem invoiceItem : sanitizedInvoiceItems) {
             if (invoiceItem.getId().equals(recurring1.getId())) {
@@ -276,4 +278,192 @@ public class TestBusinessInvoiceFactory extends AnalyticsTestSuiteNoDB {
             }
         }
     }
+
+    @Test(groups = "fast")
+    public void testFindReparee() throws Exception {
+        final UUID subscriptionId1 = UUID.randomUUID();
+        final LocalDate startDate1 = new LocalDate(2013, 4, 1);
+        final LocalDate endDate1 = new LocalDate(2013, 4, 30);
+        final BigDecimal amount1 = new BigDecimal("30");
+        final UUID originalInvoice1 = UUID.randomUUID();
+        final InvoiceItem recurring1 = createInvoiceItem(originalInvoice1, InvoiceItemType.RECURRING, subscriptionId1, startDate1, endDate1, amount1, null);
+        final InvoiceItem repair1 = createInvoiceItem(originalInvoice1, InvoiceItemType.REPAIR_ADJ, subscriptionId1, startDate1, endDate1, amount1.negate(), recurring1.getId());
+
+        final UUID repareeInvoice1 = UUID.randomUUID();
+        final LocalDate repareeEndDate1 = new LocalDate(2013, 4, 10);
+        final BigDecimal repareeAmount1 = new BigDecimal("10");
+        final InvoiceItem reparee1 = createInvoiceItem(repareeInvoice1, InvoiceItemType.RECURRING, subscriptionId1, startDate1, repareeEndDate1, repareeAmount1, null);
+
+        Assert.assertEquals(invoiceFactory.findRepareeInvoiceItems(ImmutableList.<InvoiceItem>of(recurring1, repair1, reparee1)).size(), 1);
+        Assert.assertEquals(invoiceFactory.findRepareeInvoiceItems(ImmutableList.<InvoiceItem>of(recurring1, repair1, reparee1)).get(reparee1), repair1);
+    }
+
+    @Test(groups = "fast")
+    public void testCantFindRepareeWrongSubscription() throws Exception {
+        final UUID subscriptionId1 = UUID.randomUUID();
+        final LocalDate startDate1 = new LocalDate(2013, 4, 1);
+        final LocalDate endDate1 = new LocalDate(2013, 4, 30);
+        final BigDecimal amount1 = new BigDecimal("30");
+        final UUID originalInvoice1 = UUID.randomUUID();
+        final InvoiceItem recurring1 = createInvoiceItem(originalInvoice1, InvoiceItemType.RECURRING, subscriptionId1, startDate1, endDate1, amount1, null);
+        final InvoiceItem repair1 = createInvoiceItem(originalInvoice1, InvoiceItemType.REPAIR_ADJ, subscriptionId1, startDate1, endDate1, amount1.negate(), recurring1.getId());
+
+        final UUID repareeInvoice1 = UUID.randomUUID();
+        final LocalDate repareeEndDate1 = new LocalDate(2013, 4, 10);
+        final BigDecimal repareeAmount1 = new BigDecimal("10");
+        final InvoiceItem reparee1 = createInvoiceItem(repareeInvoice1, InvoiceItemType.RECURRING, UUID.randomUUID(), startDate1, repareeEndDate1, repareeAmount1, null);
+
+        Assert.assertEquals(invoiceFactory.findRepareeInvoiceItems(ImmutableList.<InvoiceItem>of(recurring1, repair1, reparee1)).size(), 0);
+    }
+
+    @Test(groups = "fast")
+    public void testCantFindRepareeWrongEndDate() throws Exception {
+        final UUID subscriptionId1 = UUID.randomUUID();
+        final LocalDate startDate1 = new LocalDate(2013, 4, 1);
+        final LocalDate endDate1 = new LocalDate(2013, 4, 30);
+        final BigDecimal amount1 = new BigDecimal("30");
+        final UUID originalInvoice1 = UUID.randomUUID();
+        final InvoiceItem recurring1 = createInvoiceItem(originalInvoice1, InvoiceItemType.RECURRING, subscriptionId1, startDate1, endDate1, amount1, null);
+        final InvoiceItem repair1 = createInvoiceItem(originalInvoice1, InvoiceItemType.REPAIR_ADJ, subscriptionId1, startDate1, endDate1, amount1.negate(), recurring1.getId());
+
+        final UUID repareeInvoice1 = UUID.randomUUID();
+        final LocalDate repareeEndDate1 = new LocalDate(2038, 4, 10);
+        final BigDecimal repareeAmount1 = new BigDecimal("10");
+        final InvoiceItem reparee1 = createInvoiceItem(repareeInvoice1, InvoiceItemType.RECURRING, subscriptionId1, startDate1, repareeEndDate1, repareeAmount1, null);
+
+        Assert.assertEquals(invoiceFactory.findRepareeInvoiceItems(ImmutableList.<InvoiceItem>of(recurring1, repair1, reparee1)).size(), 0);
+    }
+
+    @Test(groups = "fast")
+    public void testMergeCBAsNormal() throws Exception {
+        final UUID invoiceId1 = UUID.randomUUID();
+        final InvoiceItem cba1 = createInvoiceItem(invoiceId1, InvoiceItemType.CBA_ADJ, BigDecimal.ONE);
+        final InvoiceItem cba2 = createInvoiceItem(invoiceId1, InvoiceItemType.CBA_ADJ, BigDecimal.TEN.negate());
+        final InvoiceItem charge = createInvoiceItem(invoiceId1, InvoiceItemType.EXTERNAL_CHARGE, new BigDecimal("9"));
+
+        final UUID invoiceId2 = UUID.randomUUID();
+        final InvoiceItem cba3 = createInvoiceItem(invoiceId2, InvoiceItemType.CBA_ADJ, BigDecimal.ONE);
+
+        final Multimap<UUID, InvoiceItem> allInvoiceItems = ArrayListMultimap.<UUID, InvoiceItem>create();
+        allInvoiceItems.put(invoiceId1, cba1);
+        allInvoiceItems.put(invoiceId1, cba2);
+        allInvoiceItems.put(invoiceId1, charge);
+        allInvoiceItems.put(invoiceId2, cba3);
+
+        final Collection<AdjustedCBAInvoiceItem> adjustedCBAInvoiceItems = invoiceFactory.buildMergedCBAItems(allInvoiceItems, ImmutableMap.<InvoiceItem, InvoiceItem>of());
+        Assert.assertEquals(adjustedCBAInvoiceItems.size(), 2);
+        for (final AdjustedCBAInvoiceItem item : adjustedCBAInvoiceItems) {
+            Assert.assertEquals(item.getAmount(), item.getInvoiceId().equals(invoiceId1) ? new BigDecimal("-9") : BigDecimal.ONE);
+        }
+    }
+
+    @Test(groups = "fast")
+    public void testMergeCBAsWithRepairAndPayment() throws Exception {
+        /*
+         * Scenario:
+         *  Recurring1: +30
+         *  Repair1:	-30
+         *  CBA:	    +30
+         */
+        final UUID subscriptionId1 = UUID.randomUUID();
+        final LocalDate startDate1 = new LocalDate(2013, 4, 1);
+        final LocalDate endDate1 = new LocalDate(2013, 4, 30);
+        final BigDecimal amount1 = new BigDecimal("30");
+        final UUID invoiceId1 = UUID.randomUUID();
+        final InvoiceItem recurring1 = createInvoiceItem(invoiceId1, InvoiceItemType.RECURRING, subscriptionId1, startDate1, endDate1, amount1, null);
+        final InvoiceItem repair1 = createInvoiceItem(invoiceId1, InvoiceItemType.REPAIR_ADJ, subscriptionId1, startDate1, endDate1, amount1.negate(), recurring1.getId());
+        final InvoiceItem cba1 = createInvoiceItem(invoiceId1, InvoiceItemType.CBA_ADJ, amount1);
+
+        /*
+         * Scenario:
+         *  Recurring1: +10
+         *  CBA use:	-10
+         *  Charge:     +9
+         *  CBA use:	-9
+         */
+        final UUID invoiceId2 = UUID.randomUUID();
+        final LocalDate repareeEndDate1 = new LocalDate(2013, 4, 10);
+        final BigDecimal repareeAmount1 = new BigDecimal("10");
+        final InvoiceItem reparee1 = createInvoiceItem(invoiceId2, InvoiceItemType.RECURRING, subscriptionId1, startDate1, repareeEndDate1, repareeAmount1, null);
+        final InvoiceItem cba2 = createInvoiceItem(invoiceId2, InvoiceItemType.CBA_ADJ, repareeAmount1.negate());
+        final InvoiceItem charge = createInvoiceItem(invoiceId2, InvoiceItemType.EXTERNAL_CHARGE, new BigDecimal("9"));
+        final InvoiceItem cba3 = createInvoiceItem(invoiceId2, InvoiceItemType.CBA_ADJ, new BigDecimal("-9"));
+
+        final Multimap<UUID, InvoiceItem> allInvoiceItems = ArrayListMultimap.<UUID, InvoiceItem>create();
+        allInvoiceItems.put(invoiceId1, recurring1);
+        allInvoiceItems.put(invoiceId1, repair1);
+        allInvoiceItems.put(invoiceId1, cba1);
+        allInvoiceItems.put(invoiceId2, reparee1);
+        allInvoiceItems.put(invoiceId2, cba2);
+        allInvoiceItems.put(invoiceId2, charge);
+        allInvoiceItems.put(invoiceId2, cba3);
+
+        /*
+         * Expected invoice 1:
+         *  Recurring1: +30
+         *  Adjustment:	-20
+         *  Recurring1: +10
+         *  CBA:        +20
+         *
+         * Expected invoice 2:
+         *  Charge:  +9
+         *  CBA use: -9
+         */
+        final Collection<AdjustedCBAInvoiceItem> adjustedCBAInvoiceItems = invoiceFactory.buildMergedCBAItems(allInvoiceItems, ImmutableMap.<InvoiceItem, InvoiceItem>of(reparee1, repair1));
+        Assert.assertEquals(adjustedCBAInvoiceItems.size(), 2);
+        for (final AdjustedCBAInvoiceItem item : adjustedCBAInvoiceItems) {
+            Assert.assertEquals(item.getAmount(), item.getInvoiceId().equals(invoiceId1) ? new BigDecimal("20") : new BigDecimal("-9"));
+        }
+    }
+
+    // TODO Should add the same test, but where the second invoice is repaired to check the blacklist stuff works as expected
+    @Test(groups = "fast")
+    public void testMergeCBAsWithRepairAndNoPayment() throws Exception {
+        /*
+         * Scenario:
+         *  Recurring1: +30
+         *  Repair1:	-30
+         */
+        final UUID subscriptionId1 = UUID.randomUUID();
+        final LocalDate startDate1 = new LocalDate(2013, 4, 1);
+        final LocalDate endDate1 = new LocalDate(2013, 4, 30);
+        final BigDecimal amount1 = new BigDecimal("30");
+        final UUID invoiceId1 = UUID.randomUUID();
+        final InvoiceItem recurring1 = createInvoiceItem(invoiceId1, InvoiceItemType.RECURRING, subscriptionId1, startDate1, endDate1, amount1, null);
+        final InvoiceItem repair1 = createInvoiceItem(invoiceId1, InvoiceItemType.REPAIR_ADJ, subscriptionId1, startDate1, endDate1, amount1.negate(), recurring1.getId());
+
+        /*
+         * Scenario (assume account has 9 credits):
+         *  Recurring1: +10
+         *  Charge:     +9
+         *  CBA use:    -9
+         */
+        final UUID invoiceId2 = UUID.randomUUID();
+        final LocalDate repareeEndDate1 = new LocalDate(2013, 4, 10);
+        final BigDecimal repareeAmount1 = new BigDecimal("10");
+        final InvoiceItem reparee1 = createInvoiceItem(invoiceId2, InvoiceItemType.RECURRING, subscriptionId1, startDate1, repareeEndDate1, repareeAmount1, null);
+        final InvoiceItem charge = createInvoiceItem(invoiceId2, InvoiceItemType.EXTERNAL_CHARGE, new BigDecimal("9"));
+        final InvoiceItem cba1 = createInvoiceItem(invoiceId2, InvoiceItemType.CBA_ADJ, new BigDecimal("-9"));
+
+        final Multimap<UUID, InvoiceItem> allInvoiceItems = ArrayListMultimap.<UUID, InvoiceItem>create();
+        allInvoiceItems.put(invoiceId1, recurring1);
+        allInvoiceItems.put(invoiceId1, repair1);
+        allInvoiceItems.put(invoiceId2, reparee1);
+        allInvoiceItems.put(invoiceId2, cba1);
+        allInvoiceItems.put(invoiceId2, charge);
+
+        /*
+         * Expected invoice 1:
+         *  Recurring1: +30
+         *  Adjustment:	-20
+         *  Recurring1: +10
+         *
+         * Expected invoice 2:
+         *  Charge:  +9
+         *  CBA use: -9
+         */
+        final Collection<AdjustedCBAInvoiceItem> adjustedCBAInvoiceItems = invoiceFactory.buildMergedCBAItems(allInvoiceItems, ImmutableMap.<InvoiceItem, InvoiceItem>of(reparee1, repair1));
+        Assert.assertEquals(adjustedCBAInvoiceItems.size(), 1);
+        Assert.assertEquals(adjustedCBAInvoiceItems.iterator().next().getAmount(), new BigDecimal("-9"));
+    }
 }