Details
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java b/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java
index 96bbda4..49a8c2d 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/AccountItemTree.java
@@ -89,24 +89,6 @@ public class AccountItemTree {
addExistingItem(existingItem, false);
}
- /*
- EXTERNAL_CHARGE,
- // Fixed (one-time) charge
- FIXED,
- // Recurring charge
- RECURRING,
- // Internal adjustment, used for repair
- REPAIR_ADJ,
- // Internal adjustment, used as rollover credits
- CBA_ADJ,
- // Credit adjustment, either at the account level (on its own invoice) or against an existing invoice
- // (invoice level adjustment)
- CREDIT_ADJ,
- // Invoice item adjustment (by itself or triggered by a refund)
- ITEM_ADJ,
- // Refund adjustment (against a posted payment), used when adjusting invoices
- REFUND_ADJ
- */
private void addExistingItem(final InvoiceItem existingItem, boolean failOnMissingSubscription) {
Preconditions.checkState(!isBuilt);
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsNodeInterval.java b/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsNodeInterval.java
index df31e26..fdf29f0 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsNodeInterval.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/ItemsNodeInterval.java
@@ -1,11 +1,18 @@
package com.ning.billing.invoice.tree;
+import java.io.IOException;
+import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
import org.joda.time.LocalDate;
+import com.ning.billing.util.jackson.ObjectMapper;
+
+import com.fasterxml.jackson.annotation.JsonIdentityInfo;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.core.JsonGenerator;
import com.google.common.base.Preconditions;
public class ItemsNodeInterval extends NodeInterval {
@@ -21,12 +28,13 @@ public class ItemsNodeInterval extends NodeInterval {
this.items = new ItemsInterval(this, item);
}
- public ItemsInterval getItems() {
+ @JsonIgnore
+ public ItemsInterval getItemsInterval() {
return items;
}
- public boolean containsItem(final UUID targetId) {
- return items.containsItem(targetId);
+ public List<Item> getItems() {
+ return items.getItems();
}
/**
@@ -53,14 +61,14 @@ public class ItemsNodeInterval extends NodeInterval {
public void buildForExistingItems(final List<Item> output) {
build(new BuildNodeCallback() {
@Override
- public void buildMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
- final ItemsInterval items = ((ItemsNodeInterval) curNode).getItems();
+ public void onMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
+ final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
items.buildForMissingInterval(startDate, endDate, output, false);
}
@Override
- public void buildNode(final NodeInterval curNode) {
- final ItemsInterval items = ((ItemsNodeInterval) curNode).getItems();
+ public void onLastNode(final NodeInterval curNode) {
+ final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
items.buildFromItems(output, false);
}
});
@@ -91,14 +99,14 @@ public class ItemsNodeInterval extends NodeInterval {
public void mergeExistingAndProposed(final List<Item> output) {
build(new BuildNodeCallback() {
@Override
- public void buildMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
- final ItemsInterval items = ((ItemsNodeInterval) curNode).getItems();
+ public void onMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
+ final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
items.buildForMissingInterval(startDate, endDate, output, true);
}
@Override
- public void buildNode(final NodeInterval curNode) {
- final ItemsInterval items = ((ItemsNodeInterval) curNode).getItems();
+ public void onLastNode(final NodeInterval curNode) {
+ final ItemsInterval items = ((ItemsNodeInterval) curNode).getItemsInterval();
items.buildFromItems(output, true);
}
});
@@ -115,8 +123,8 @@ public class ItemsNodeInterval extends NodeInterval {
@Override
public boolean onExistingNode(final NodeInterval existingNode) {
if (!existingNode.isRoot() && newNode.getStart().compareTo(existingNode.getStart()) == 0 && newNode.getEnd().compareTo(existingNode.getEnd()) == 0) {
- final Item item = newNode.getItems().getItems().get(0);
- final ItemsInterval existingOrNewNodeItems = ((ItemsNodeInterval) existingNode).getItems();
+ final Item item = newNode.getItems().get(0);
+ final ItemsInterval existingOrNewNodeItems = ((ItemsNodeInterval) existingNode).getItemsInterval();
existingOrNewNodeItems.insertSortedItem(item);
}
// There is no new node added but instead we just populated the list of items for the already existing node.
@@ -148,8 +156,8 @@ public class ItemsNodeInterval extends NodeInterval {
}
Preconditions.checkState(newNode.getStart().compareTo(existingNode.getStart()) == 0 && newNode.getEnd().compareTo(existingNode.getEnd()) == 0);
- final Item item = newNode.getItems().getItems().get(0);
- final ItemsInterval existingOrNewNodeItems = ((ItemsNodeInterval) existingNode).getItems();
+ final Item item = newNode.getItems().get(0);
+ final ItemsInterval existingOrNewNodeItems = ((ItemsNodeInterval) existingNode).getItemsInterval();
existingOrNewNodeItems.cancelItems(item);
// In the merge logic, whether we really insert the node or find an existing node on which to insert items should be seen
// as an insertion (so as to avoid keeping that proposed item, see how return value of addProposedItem is used)
@@ -164,10 +172,10 @@ public class ItemsNodeInterval extends NodeInterval {
return false;
}
- final ItemsInterval insertionNodeItems = ((ItemsNodeInterval) insertionNode).getItems();
+ final ItemsInterval insertionNodeItems = ((ItemsNodeInterval) insertionNode).getItemsInterval();
Preconditions.checkState(insertionNodeItems.getItems().size() == 1, "Expected existing node to have only one item");
final Item insertionNodeItem = insertionNodeItems.getItems().get(0);
- final Item newNodeItem = newNode.getItems().getItems().get(0);
+ final Item newNodeItem = newNode.getItems().get(0);
// If we receive a new proposed that is the same kind as the reversed existing we want to insert it to generate
// a piece of repair
@@ -194,13 +202,46 @@ public class ItemsNodeInterval extends NodeInterval {
final NodeInterval node = findNode(new SearchCallback() {
@Override
public boolean isMatch(final NodeInterval curNode) {
- return ((ItemsNodeInterval) curNode).getItems().containsItem(targetId);
+ return ((ItemsNodeInterval) curNode).getItemsInterval().containsItem(targetId);
}
});
Preconditions.checkNotNull(node, "Cannot add adjustement for item = " + targetId + ", date = " + adjustementDate);
((ItemsNodeInterval) node).setAdjustment(amount.negate(), targetId);
}
+ public void jsonSerializeTree(final ObjectMapper mapper, final OutputStream output) throws IOException {
+
+ final JsonGenerator generator = mapper.getFactory().createJsonGenerator(output);
+ generator.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
+
+ walkTree(new WalkCallback() {
+
+ private int curDepth = 0;
+
+ @Override
+ public void onCurrentNode(final int depth, final NodeInterval curNode, final NodeInterval parent) {
+ final ItemsNodeInterval node = (ItemsNodeInterval) curNode;
+ if (node.isRoot()) {
+ return;
+ }
+
+ try {
+ if (curDepth < depth) {
+ generator.writeStartArray();
+ curDepth = depth;
+ } else if (curDepth > depth) {
+ generator.writeEndArray();
+ curDepth = depth;
+ }
+ generator.writeObject(node);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to deserialize tree", e);
+ }
+ }
+ });
+ generator.close();
+ }
+
protected void setAdjustment(final BigDecimal amount, final UUID linkedId) {
items.setAdjustment(amount, linkedId);
}
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java b/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
index 02365c0..b1d1483 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/NodeInterval.java
@@ -20,10 +20,11 @@ import java.util.List;
import org.joda.time.LocalDate;
+import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
-public abstract class NodeInterval {
+public class NodeInterval {
protected NodeInterval parent;
protected NodeInterval leftChild;
@@ -54,7 +55,7 @@ public abstract class NodeInterval {
Preconditions.checkNotNull(callback);
if (leftChild == null) {
- callback.buildNode(this);
+ callback.onLastNode(this);
return;
}
@@ -62,7 +63,7 @@ public abstract class NodeInterval {
NodeInterval curChild = leftChild;
while (curChild != null) {
if (curChild.getStart().compareTo(curDate) > 0) {
- callback.buildMissingInterval(this, curDate, curChild.getStart());
+ callback.onMissingInterval(this, curDate, curChild.getStart());
}
curChild.build(callback);
curDate = curChild.getEnd();
@@ -71,7 +72,7 @@ public abstract class NodeInterval {
// Finally if there is a hole at the end, we build the missing piece from ourself
if (curDate.compareTo(end) < 0) {
- callback.buildMissingInterval(this, curDate, end);
+ callback.onMissingInterval(this, curDate, end);
}
}
@@ -145,6 +146,85 @@ public abstract class NodeInterval {
}
}
+ /**
+ * Return the first node satisfying the date and match callback.
+ *
+ * @param targetDate target date for possible match nodes whose interval comprises that date
+ * @param callback custom logic to decide if a given node is a match
+ * @return the found node or null if there is nothing.
+ */
+ public NodeInterval findNode(final LocalDate targetDate, final SearchCallback callback) {
+
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(targetDate);
+
+ if (targetDate.compareTo(getStart()) < 0 || targetDate.compareTo(getEnd()) > 0) {
+ return null;
+ }
+
+ NodeInterval curChild = leftChild;
+ while (curChild != null) {
+ if (curChild.getStart().compareTo(targetDate) <= 0 && curChild.getEnd().compareTo(targetDate) >= 0) {
+ if (callback.isMatch(curChild)) {
+ return curChild;
+ }
+ NodeInterval result = curChild.findNode(targetDate, callback);
+ if (result != null) {
+ return result;
+ }
+ }
+ curChild = curChild.getRightSibling();
+ }
+ return null;
+ }
+
+ /**
+ * Return the first node satisfying the date and match callback.
+ *
+ * @param callback custom logic to decide if a given node is a match
+ * @return the found node or null if there is nothing.
+ */
+ public NodeInterval findNode(final SearchCallback callback) {
+
+ Preconditions.checkNotNull(callback);
+ if (callback.isMatch(this)) {
+ return this;
+ }
+
+ NodeInterval curChild = leftChild;
+ while (curChild != null) {
+ final NodeInterval result = curChild.findNode(callback);
+ if (result != null) {
+ return result;
+ }
+ curChild = curChild.getRightSibling();
+ }
+ return null;
+ }
+
+ /**
+ * Walk the tree (depth first search) and invoke callback for each node.
+ *
+ * @param callback
+ */
+ public void walkTree(WalkCallback callback) {
+ Preconditions.checkNotNull(callback);
+ walkTreeWithDepth(callback, 0);
+ }
+
+ private void walkTreeWithDepth(WalkCallback callback, int depth) {
+
+ Preconditions.checkNotNull(callback);
+ callback.onCurrentNode(depth, this, parent);
+
+ NodeInterval curChild = leftChild;
+ while (curChild != null) {
+ curChild.walkTreeWithDepth(callback, (depth + 1));
+ curChild = curChild.getRightSibling();
+ }
+ }
+
+
public boolean isItemContained(final NodeInterval newNode) {
return (newNode.getStart().compareTo(start) >= 0 &&
newNode.getStart().compareTo(end) <= 0 &&
@@ -159,6 +239,7 @@ public abstract class NodeInterval {
newNode.getEnd().compareTo(end) > 0));
}
+ @JsonIgnore
public boolean isRoot() {
return parent == null;
}
@@ -171,18 +252,22 @@ public abstract class NodeInterval {
return end;
}
+ @JsonIgnore
public NodeInterval getParent() {
return parent;
}
+ @JsonIgnore
public NodeInterval getLeftChild() {
return leftChild;
}
+ @JsonIgnore
public NodeInterval getRightSibling() {
return rightSibling;
}
+ @JsonIgnore
public int getNbChildren() {
int result = 0;
NodeInterval curChild = leftChild;
@@ -247,68 +332,22 @@ public abstract class NodeInterval {
}
/**
- * Return the first node satisfying the date and match callback.
- *
- * @param targetDate target date for possible match nodes whose interval comprises that date
- * @param callback custom logic to decide if a given node is a match
- * @return the found node or null if there is nothing.
+ * Provides callback for walking the tree.
*/
- public NodeInterval findNode(final LocalDate targetDate, final SearchCallback callback) {
-
- Preconditions.checkNotNull(callback);
- Preconditions.checkNotNull(targetDate);
-
- if (targetDate.compareTo(getStart()) < 0 || targetDate.compareTo(getEnd()) > 0) {
- return null;
- }
-
- NodeInterval curChild = leftChild;
- while (curChild != null) {
- if (curChild.getStart().compareTo(targetDate) <= 0 && curChild.getEnd().compareTo(targetDate) >= 0) {
- if (callback.isMatch(curChild)) {
- return curChild;
- }
- NodeInterval result = curChild.findNode(targetDate, callback);
- if (result != null) {
- return result;
- }
- }
- curChild = curChild.getRightSibling();
- }
- return null;
- }
-
- /**
- * Return the first node satisfying the date and match callback.
- *
- * @param callback custom logic to decide if a given node is a match
- * @return the found node or null if there is nothing.
- */
- public NodeInterval findNode(final SearchCallback callback) {
-
- System.out.println("ENTERING [" + start + " - " + end + "]");
-
- Preconditions.checkNotNull(callback);
- if (/*!isRoot() && */callback.isMatch(this)) {
- return this;
- }
-
- NodeInterval curChild = leftChild;
- while (curChild != null) {
- final NodeInterval result = curChild.findNode(callback);
- if (result != null) {
- return result;
- }
- curChild = curChild.getRightSibling();
- }
- return null;
+ public interface WalkCallback {
+ public void onCurrentNode(final int depth, final NodeInterval curNode, final NodeInterval parent);
}
/**
* Provides custom logic for the search.
*/
public interface SearchCallback {
-
+ /**
+ * Custom logic to decide which node to return.
+ *
+ * @param curNode found node
+ * @return evaluates whether this is the node that should be returned
+ */
boolean isMatch(NodeInterval curNode);
}
@@ -324,14 +363,14 @@ public abstract class NodeInterval {
* @param startDate startDate of the new interval to build
* @param endDate endDate of the new interval to build
*/
- public void buildMissingInterval(NodeInterval curNode, LocalDate startDate, LocalDate endDate);
+ public void onMissingInterval(NodeInterval curNode, LocalDate startDate, LocalDate endDate);
/**
* Called when we hit a node with no children
*
* @param curNode current node
*/
- public void buildNode(NodeInterval curNode);
+ public void onLastNode(NodeInterval curNode);
}
/**
diff --git a/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java b/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java
index b04f343..8aa8312 100644
--- a/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java
+++ b/invoice/src/main/java/com/ning/billing/invoice/tree/SubscriptionItemTree.java
@@ -28,6 +28,7 @@ import org.joda.time.LocalDate;
import com.ning.billing.invoice.api.InvoiceItem;
import com.ning.billing.invoice.tree.Item.ItemAction;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
@@ -273,4 +274,8 @@ public class SubscriptionItemTree {
return result;
}
+ @VisibleForTesting
+ ItemsNodeInterval getRoot() {
+ return root;
+ }
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tree/TestNodeInterval.java b/invoice/src/test/java/com/ning/billing/invoice/tree/TestNodeInterval.java
index 4cc9d0d..9512591 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tree/TestNodeInterval.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tree/TestNodeInterval.java
@@ -16,29 +16,24 @@
package com.ning.billing.invoice.tree;
-import java.math.BigDecimal;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import org.joda.time.LocalDate;
-import org.testng.Assert;
import org.testng.annotations.Test;
-import com.ning.billing.catalog.api.Currency;
-import com.ning.billing.invoice.api.InvoiceItem;
-import com.ning.billing.invoice.model.RecurringInvoiceItem;
-import com.ning.billing.invoice.tree.Item.ItemAction;
import com.ning.billing.invoice.tree.NodeInterval.AddNodeCallback;
import com.ning.billing.invoice.tree.NodeInterval.BuildNodeCallback;
import com.ning.billing.invoice.tree.NodeInterval.SearchCallback;
+import com.ning.billing.invoice.tree.NodeInterval.WalkCallback;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertTrue;
public class TestNodeInterval /* extends InvoiceTestSuiteNoDB */ {
-
private AddNodeCallback CALLBACK = new DummyAddNodeCallback();
public class DummyNodeInterval extends NodeInterval {
@@ -103,7 +98,6 @@ public class TestNodeInterval /* extends InvoiceTestSuiteNoDB */ {
checkNode(thirdChildLevel2, 0, thirdChildLevel1, null, null);
}
-
@Test(groups = "fast")
public void testAddExistingItemWithRebalance() {
final DummyNodeInterval root = new DummyNodeInterval();
@@ -135,9 +129,8 @@ public class TestNodeInterval /* extends InvoiceTestSuiteNoDB */ {
checkNode(thirdChildLevel2, 0, thirdChildLevel1, null, null);
}
-
@Test(groups = "fast")
- public void testBuild() {
+ public void testBuild() {
final DummyNodeInterval root = new DummyNodeInterval();
final DummyNodeInterval top = createNodeInterval("2014-01-01", "2014-02-01");
@@ -162,11 +155,12 @@ public class TestNodeInterval /* extends InvoiceTestSuiteNoDB */ {
// Just build the missing pieces.
root.build(new BuildNodeCallback() {
@Override
- public void buildMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
+ public void onMissingInterval(final NodeInterval curNode, final LocalDate startDate, final LocalDate endDate) {
output.add(createNodeInterval(startDate, endDate));
}
+
@Override
- public void buildNode(final NodeInterval curNode) {
+ public void onLastNode(final NodeInterval curNode) {
// Nothing
}
});
@@ -182,7 +176,6 @@ public class TestNodeInterval /* extends InvoiceTestSuiteNoDB */ {
checkInterval(output.get(1), expected.get(1));
}
-
@Test(groups = "fast")
public void testSearch() {
final DummyNodeInterval root = new DummyNodeInterval();
@@ -234,6 +227,66 @@ public class TestNodeInterval /* extends InvoiceTestSuiteNoDB */ {
assertNull(nullSearch);
}
+ @Test(groups = "fast")
+ public void testWalkTree() {
+ final DummyNodeInterval root = new DummyNodeInterval();
+
+ final DummyNodeInterval firstChildLevel0 = createNodeInterval("2014-01-01", "2014-02-01");
+ root.addNode(firstChildLevel0, CALLBACK);
+
+ final DummyNodeInterval secondChildLevel0 = createNodeInterval("2014-02-01", "2014-03-01");
+ root.addNode(secondChildLevel0, CALLBACK);
+
+ final DummyNodeInterval firstChildLevel1 = createNodeInterval("2014-01-01", "2014-01-07");
+ final DummyNodeInterval secondChildLevel1 = createNodeInterval("2014-01-08", "2014-01-15");
+ final DummyNodeInterval thirdChildLevel1 = createNodeInterval("2014-01-16", "2014-02-01");
+ root.addNode(firstChildLevel1, CALLBACK);
+ root.addNode(secondChildLevel1, CALLBACK);
+ root.addNode(thirdChildLevel1, CALLBACK);
+
+ final DummyNodeInterval firstChildLevel2 = createNodeInterval("2014-01-01", "2014-01-03");
+ final DummyNodeInterval secondChildLevel2 = createNodeInterval("2014-01-03", "2014-01-05");
+ final DummyNodeInterval thirdChildLevel2 = createNodeInterval("2014-01-16", "2014-01-17");
+ root.addNode(firstChildLevel2, CALLBACK);
+ root.addNode(secondChildLevel2, CALLBACK);
+ root.addNode(thirdChildLevel2, CALLBACK);
+
+ final DummyNodeInterval firstChildLevel3 = createNodeInterval("2014-01-01", "2014-01-02");
+ final DummyNodeInterval secondChildLevel3 = createNodeInterval("2014-01-03", "2014-01-04");
+ root.addNode(firstChildLevel3, CALLBACK);
+ root.addNode(secondChildLevel3, CALLBACK);
+
+ final List<NodeInterval> expected = new LinkedList<NodeInterval>();
+ expected.add(root);
+ expected.add(firstChildLevel0);
+ expected.add(firstChildLevel1);
+ expected.add(firstChildLevel2);
+ expected.add(firstChildLevel3);
+ expected.add(secondChildLevel2);
+ expected.add(secondChildLevel3);
+ expected.add(secondChildLevel1);
+ expected.add(thirdChildLevel1);
+ expected.add(thirdChildLevel2);
+ expected.add(secondChildLevel0);
+
+ final List<NodeInterval> result = new LinkedList<NodeInterval>();
+ root.walkTree(new WalkCallback() {
+ @Override
+ public void onCurrentNode(final int depth, final NodeInterval curNode, final NodeInterval parent) {
+ result.add(curNode);
+ }
+ });
+
+ assertEquals(result.size(), expected.size());
+ for (int i = 0; i < result.size(); i++) {
+ if (i == 0) {
+ assertTrue(result.get(0).isRoot());
+ checkInterval(result.get(0), createNodeInterval("2014-01-01", "2014-03-01"));
+ } else {
+ checkInterval(result.get(i), expected.get(i));
+ }
+ }
+ }
private void checkInterval(final NodeInterval real, final NodeInterval expected) {
assertEquals(real.getStart(), expected.getStart());
@@ -247,6 +300,7 @@ public class TestNodeInterval /* extends InvoiceTestSuiteNoDB */ {
assertEquals(node.getLeftChild(), expectedLeftChild);
assertEquals(node.getLeftChild(), expectedLeftChild);
}
+
private DummyNodeInterval createNodeInterval(final LocalDate startDate, final LocalDate endDate) {
return new DummyNodeInterval(null, startDate, endDate);
}
diff --git a/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java b/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java
index 5b8428a..b3fd61e 100644
--- a/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java
+++ b/invoice/src/test/java/com/ning/billing/invoice/tree/TestSubscriptionItemTree.java
@@ -16,10 +16,14 @@
package com.ning.billing.invoice.tree;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.List;
import java.util.UUID;
+import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.testng.annotations.Test;
@@ -29,8 +33,11 @@ import com.ning.billing.invoice.model.FixedPriceInvoiceItem;
import com.ning.billing.invoice.model.ItemAdjInvoiceItem;
import com.ning.billing.invoice.model.RecurringInvoiceItem;
import com.ning.billing.invoice.model.RepairAdjInvoiceItem;
+import com.ning.billing.util.jackson.ObjectMapper;
+import com.apple.jobjc.appkit.NSToolbarItemGroup;
import com.google.common.collect.Lists;
+import junit.framework.Assert;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
@@ -781,6 +788,41 @@ public class TestSubscriptionItemTree /* extends InvoiceTestSuiteNoDB */ {
verifyResult(tree.getView(), expectedResult);
}
+ @Test(groups = "fast")
+ public void verifyJson() {
+
+ SubscriptionItemTree tree = new SubscriptionItemTree(subscriptionId);
+ final UUID id1 = UUID.fromString("e8ba6ce7-9bd4-417d-af53-70951ecaa99f");
+ final InvoiceItem yearly1 = new RecurringInvoiceItem(id1, new DateTime(), invoiceId, accountId, bundleId, subscriptionId, planName, phaseName, new LocalDate("2014-01-01"), new LocalDate("2015-01-01"), BigDecimal.TEN, BigDecimal.TEN, currency);
+ tree.addItem(yearly1);
+
+ final UUID id2 = UUID.fromString("48db1317-9a6e-4666-bcc5-fc7d3d0defc8");
+ final InvoiceItem newItem = new RecurringInvoiceItem(id2, new DateTime(), invoiceId, accountId, bundleId, subscriptionId, "other-plan", "other-plan", new LocalDate("2014-08-01"), new LocalDate("2015-01-01"), BigDecimal.ONE, BigDecimal.ONE, currency);
+ tree.addItem(newItem);
+
+ final UUID id3 = UUID.fromString("02ec57f5-2723-478b-86ba-ebeaedacb9db");
+ final InvoiceItem repair = new RepairAdjInvoiceItem(id3, new DateTime(), invoiceId, accountId, new LocalDate("2014-08-01"), new LocalDate("2015-01-01"), BigDecimal.TEN.negate(), currency, yearly1.getId());
+ tree.addItem(repair);
+
+
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ try {
+ tree.getRoot().jsonSerializeTree(new ObjectMapper(), outputStream);
+
+ final String json = outputStream.toString("UTF-8");
+ System.out.println(json);
+
+ final String expectedJson = "[{\"start\":\"2014-01-01\",\"end\":\"2015-01-01\",\"items\":[{\"id\":\"e8ba6ce7-9bd4-417d-af53-70951ecaa99f\",\"startDate\":\"2014-01-01\",\"endDate\":\"2015-01-01\",\"amount\":10,\"currency\":\"USD\",\"linkedId\":null,\"action\":\"ADD\"}]},[{\"start\":\"2014-08-01\",\"end\":\"2015-01-01\",\"items\":[{\"id\":\"48db1317-9a6e-4666-bcc5-fc7d3d0defc8\",\"startDate\":\"2014-08-01\",\"endDate\":\"2015-01-01\",\"amount\":1,\"currency\":\"USD\",\"linkedId\":null,\"action\":\"ADD\"},{\"id\":\"02ec57f5-2723-478b-86ba-ebeaedacb9db\",\"startDate\":\"2014-08-01\",\"endDate\":\"2015-01-01\",\"amount\":10,\"currency\":\"USD\",\"linkedId\":\"e8ba6ce7-9bd4-417d-af53-70951ecaa99f\",\"action\":\"CANCEL\"}]}]]";
+
+ assertEquals(json, expectedJson);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ Assert.fail(e.getMessage());
+ }
+
+ }
+
private void verifyResult(final List<InvoiceItem> result, final List<InvoiceItem> expectedResult) {
assertEquals(result.size(), expectedResult.size());
for (int i = 0; i < expectedResult.size(); i++) {