aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorEduardo Pedroni <e.pedroni91@gmail.com>2016-03-26 14:16:42 +0100
committerEduardo Pedroni <e.pedroni91@gmail.com>2016-03-26 14:16:42 +0100
commit19800a0886d51220f28f85f3845c74e7f46500e3 (patch)
tree73ec9bc1dee88e0fac83c99c3bd98b07a9904379 /test
parent5a9327b4721d50cee954369eaf25d096ac8b9c52 (diff)
Refactored storage hierarchy, now need to look into decks again
Diffstat (limited to 'test')
-rw-r--r--test/eu/equalparts/cardbase/CardbaseTest.java6
-rw-r--r--test/eu/equalparts/cardbase/cardstorage/ReferenceCardContainerTest.java131
-rw-r--r--test/eu/equalparts/cardbase/cardstorage/StandaloneCardContainerSortTest.java (renamed from test/eu/equalparts/cardbase/CardbaseSortTest.java)11
-rw-r--r--test/eu/equalparts/cardbase/cardstorage/StandaloneCardContainerTest.java170
-rw-r--r--test/eu/equalparts/cardbase/decks/DeckTest.java53
-rw-r--r--test/eu/equalparts/cardbase/decks/StatisticsTest.java148
-rw-r--r--test/eu/equalparts/cardbase/decks/deck.cbd1
-rw-r--r--test/testbase.cb2
8 files changed, 310 insertions, 212 deletions
diff --git a/test/eu/equalparts/cardbase/CardbaseTest.java b/test/eu/equalparts/cardbase/CardbaseTest.java
index 1a54298..c9576ce 100644
--- a/test/eu/equalparts/cardbase/CardbaseTest.java
+++ b/test/eu/equalparts/cardbase/CardbaseTest.java
@@ -151,13 +151,13 @@ public class CardbaseTest {
final int testCount = 5;
File testFile = tempFolder.newFile("saveTest.cb");
- uut.writeCollection(testFile);
+ uut.write(testFile);
uut = Cardbase.load(testFile);
assertEquals("Cardbase should contain no cards.", 0, uut.getCards().size());
uut.addCard(testCard, testCount);
- uut.writeCollection(testFile);
+ uut.write(testFile);
uut = Cardbase.load(testFile);
assertEquals("Cardbase should contain 1 card.", 1, uut.getCards().size());
Card card = uut.getCard("M15", "281");
@@ -173,7 +173,7 @@ public class CardbaseTest {
File testFile = tempFolder.newFile("saveTest.cb");
testFile.setWritable(false);
exception.expect(IOException.class);
- uut.writeCollection(testFile);
+ uut.write(testFile);
}
@Test
diff --git a/test/eu/equalparts/cardbase/cardstorage/ReferenceCardContainerTest.java b/test/eu/equalparts/cardbase/cardstorage/ReferenceCardContainerTest.java
new file mode 100644
index 0000000..d233ddf
--- /dev/null
+++ b/test/eu/equalparts/cardbase/cardstorage/ReferenceCardContainerTest.java
@@ -0,0 +1,131 @@
+package eu.equalparts.cardbase.cardstorage;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import eu.equalparts.cardbase.cards.Card;
+
+/**
+ * This test class tests storage-by-reference behaviour only.
+ * For standalone storage behaviour tests, see {@code StandaloneCardContainerTest}.
+ *
+ * @author Eduardo Pedroni
+ *
+ */
+public class ReferenceCardContainerTest {
+ private ReferenceCardContainer uut;
+ private static Card testCard;
+
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ testCard = mapper.readValue(ReferenceCardContainerTest.class.getResourceAsStream("/shivandragon.json"), Card.class);
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ uut = new ReferenceCardContainer() {
+ };
+ }
+
+ /***********************************************************************************
+ * Adding card tests, happy path
+ ***********************************************************************************/
+ @Test
+ public void newCardIsAdded() throws Exception {
+ uut.addCard(testCard, 1);
+
+ assertEquals("Container should have contained 1 test card.", 1, uut.getCount(testCard));
+ }
+
+ @Test
+ public void existingCardIsIncremented() throws Exception {
+ uut.addCard(testCard, 2);
+ uut.addCard(testCard, 4);
+
+ assertEquals("Container should have contained 6 test cards.", 6, uut.getCount(testCard));
+ }
+
+ /*
+ * Edge cases
+ */
+ @Test
+ public void cardAddedIsNull() throws Exception {
+ exception.expect(NullPointerException.class);
+ uut.addCard(null, 0);
+ }
+
+ /***********************************************************************************
+ * Removing card tests, happy path
+ ***********************************************************************************/
+ @Test
+ public void cardRemoveCountIsLessThanCardCount() throws Exception {
+ uut.addCard(testCard, 5);
+
+ int removed = uut.removeCard(testCard, 3);
+
+ assertEquals("Card count was not updated correctly.", 2, uut.getCount(testCard));
+ assertEquals("Container reports wrong removed count.", 3, removed);
+ }
+
+ @Test
+ public void cardRemoveCountIsEqualToCardCount() throws Exception {
+ uut.addCard(testCard, 5);
+
+ int removed = uut.removeCard(testCard, 5);
+
+ assertEquals("Card was not removed from container.", 0, uut.getCount(testCard));
+ assertEquals("Container reports wrong removed count.", 5, removed);
+ }
+
+ @Test
+ public void cardRemoveCountIsGreaterThanCardCount() throws Exception {
+ uut.addCard(testCard, 3);
+
+ int removed = uut.removeCard(testCard, 5);
+
+ assertEquals("Card was not removed from container.", 0, uut.getCount(testCard));
+ assertEquals("Container reports wrong removed count.", 3, removed);
+ }
+
+ /*
+ * Edge cases
+ */
+ @Test
+ public void removedCardIsNull() throws Exception {
+ exception.expect(NullPointerException.class);
+ uut.removeCard(null, 0);
+ }
+
+ @Test
+ public void removedCardIsNotInCardbase() throws Exception {
+ int removed = uut.removeCard(testCard, 1);
+
+ assertEquals("Removed count should be 0.", 0, removed);
+ }
+
+ @Test
+ public void removedCountIsLessThanZero() throws Exception {
+ uut.addCard(testCard, 3);
+
+ int removed = uut.removeCard(testCard, -4);
+
+ assertEquals("Card count in container should be unchanged.", 3, uut.getCount(testCard));
+ assertEquals("Container reports wrong removed count.", 0, removed);
+ }
+}
diff --git a/test/eu/equalparts/cardbase/CardbaseSortTest.java b/test/eu/equalparts/cardbase/cardstorage/StandaloneCardContainerSortTest.java
index b0dbd40..2a4a3ba 100644
--- a/test/eu/equalparts/cardbase/CardbaseSortTest.java
+++ b/test/eu/equalparts/cardbase/cardstorage/StandaloneCardContainerSortTest.java
@@ -1,4 +1,4 @@
-package eu.equalparts.cardbase;
+package eu.equalparts.cardbase.cardstorage;
import static org.junit.Assert.assertTrue;
@@ -13,7 +13,6 @@ import org.junit.Test;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
-import eu.equalparts.cardbase.Cardbase;
import eu.equalparts.cardbase.cards.Card;
/**
@@ -22,15 +21,15 @@ import eu.equalparts.cardbase.cards.Card;
* @author Eduardo Pedroni
*
*/
-public class CardbaseSortTest {
+public class StandaloneCardContainerSortTest {
- private Cardbase uut;
+ private StandaloneCardContainer uut;
private static List<Card> testCards;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
ObjectMapper mapper = new ObjectMapper();
- testCards = mapper.readValue(CardbaseSortTest.class.getResourceAsStream("/testcards.json"), new TypeReference<List<Card>>() {});
+ testCards = mapper.readValue(StandaloneCardContainerSortTest.class.getResourceAsStream("/testcards.json"), new TypeReference<List<Card>>() {});
}
@AfterClass
@@ -39,7 +38,7 @@ public class CardbaseSortTest {
@Before
public void setUp() throws Exception {
- uut = new Cardbase();
+ uut = new StandaloneCardContainer() {};
int[] cardCounts = {1, 2, 3, 8, 1, 15, 1, 1};
for (int i = 0; i < testCards.size(); i++) {
uut.addCard(testCards.get(i), cardCounts[i]);
diff --git a/test/eu/equalparts/cardbase/cardstorage/StandaloneCardContainerTest.java b/test/eu/equalparts/cardbase/cardstorage/StandaloneCardContainerTest.java
new file mode 100644
index 0000000..bd1bbfb
--- /dev/null
+++ b/test/eu/equalparts/cardbase/cardstorage/StandaloneCardContainerTest.java
@@ -0,0 +1,170 @@
+package eu.equalparts.cardbase.cardstorage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Field;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import eu.equalparts.cardbase.cards.Card;
+
+public class StandaloneCardContainerTest {
+ private StandaloneCardContainer uut;
+ private static Card testCard;
+
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ testCard = mapper.readValue(StandaloneCardContainerTest.class.getResourceAsStream("/shivandragon.json"), Card.class);
+ }
+
+ @AfterClass
+ public static void tearDownAfterClass() throws Exception {
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ uut = new StandaloneCardContainer() {
+ };
+ }
+
+ /***********************************************************************************
+ * StandaloneCardContainer should extend ReferenceCardContainer
+ ***********************************************************************************/
+ @Test
+ public void classInherits() throws Exception {
+ assertTrue("StandaloneCardContainer should be subclass of ReferenceCardContainer.", uut instanceof ReferenceCardContainer);
+ }
+
+ /***********************************************************************************
+ * Adding card tests, happy path
+ ***********************************************************************************/
+ @Test
+ public void newCardIsAdded() throws Exception {
+ assertNull("Container should not contain the test card to begin with.", uut.getCard(testCard.setCode, testCard.number));
+
+ uut.addCard(testCard, 1);
+
+ assertEquals("Container should contain the test card once it is added.", testCard, uut.getCard(testCard.setCode, testCard.number));
+ }
+
+ @Test
+ public void existingCardIsIncremented() throws Exception {
+ uut.addCard(testCard, 2);
+ uut.addCard(testCard, 4);
+
+ Card addedCard = uut.getCard(testCard.setCode, testCard.number);
+ assertNotNull("Card was not found in cardbase.", addedCard);
+ assertEquals("Card count was not updated correctly.", 6, uut.getCount(addedCard));
+ }
+
+ /*
+ * Edge cases
+ */
+ @Test
+ public void cardAddedIsNull() throws Exception {
+ exception.expect(NullPointerException.class);
+ uut.addCard(null, 0);
+ }
+
+ /***********************************************************************************
+ * Removing card tests, happy path
+ ***********************************************************************************/
+ @Test
+ public void cardIsStillPresentIfRemoveCountIsLessThanCardCount() throws Exception {
+ uut.addCard(testCard, 5);
+
+ uut.removeCard(testCard, 3);
+
+ assertEquals("Card is missing from container.", testCard, uut.getCard(testCard.setCode, testCard.number));
+ }
+
+ @Test
+ public void cardIsRemovedIfRemoveCountIsEqualToCardCount() throws Exception {
+ uut.addCard(testCard, 5);
+
+ uut.removeCard(testCard, 5);
+
+ assertNull("Card is not missing from container.", uut.getCard(testCard.setCode, testCard.number));
+ }
+
+ @Test
+ public void cardIsRemovedIfRemoveCountIsGreaterThanCardCount() throws Exception {
+ uut.addCard(testCard, 3);
+
+ uut.removeCard(testCard, 5);
+
+ assertNull("Card is not missing from container.", uut.getCard(testCard.setCode, testCard.number));
+ }
+
+ /*
+ * Edge cases
+ */
+ @Test
+ public void removedCardIsNull() throws Exception {
+ exception.expect(NullPointerException.class);
+ uut.removeCard(null, 0);
+ }
+
+ @Test
+ public void removedCardIsNotInContainer() throws Exception {
+ assertNull("Card is not initially missing from container.", uut.getCard(testCard.setCode, testCard.number));
+
+ uut.removeCard(testCard, 1);
+
+ assertNull("Card is not missing from container.", uut.getCard(testCard.setCode, testCard.number));
+ }
+
+ @Test
+ public void removedCountIsLessThanZero() throws Exception {
+ uut.addCard(testCard, 3);
+
+ uut.removeCard(testCard, -4);
+
+ assertEquals("Card should not be missing from container.", testCard, uut.getCard(testCard.setCode, testCard.number));
+ }
+
+ /***********************************************************************************
+ * Card getter tests, happy path
+ ***********************************************************************************/
+ @Test
+ public void correctCardIsReturnedByGetter() throws Exception {
+ uut.addCard(testCard, 1);
+
+ Card card = uut.getCard(testCard.setCode, testCard.number);
+
+ for (Field field : Card.class.getFields()) {
+ assertEquals("Field " + field.getName(), field.get(testCard), field.get(card));
+ }
+ }
+
+ @Test
+ public void correctCardCollectionIsReturnedByGetter() throws Exception {
+ uut.addCard(testCard, 1);
+
+ assertTrue("Not all cards were returned by the getter.", uut.getCards().contains(testCard));
+ }
+
+ @Test
+ public void cardCollectionWhenContainerIsEmpty() throws Exception {
+ assertEquals("Returned collection size should have been 0.", 0, uut.getCards().size());
+ }
+
+ @Test
+ public void getCardIsNotInCardbase() throws Exception {
+ assertNull("Method should have returned null", uut.getCard(testCard.setCode, testCard.number));
+ }
+}
diff --git a/test/eu/equalparts/cardbase/decks/DeckTest.java b/test/eu/equalparts/cardbase/decks/DeckTest.java
deleted file mode 100644
index 1c00e5b..0000000
--- a/test/eu/equalparts/cardbase/decks/DeckTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package eu.equalparts.cardbase.decks;
-
-import static org.junit.Assert.*;
-
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import eu.equalparts.cardbase.cards.Card;
-import eu.equalparts.cardbase.decks.ReferenceDeck;
-import eu.equalparts.cardbase.decks.StandaloneDeck;
-
-public class DeckTest {
-
- @BeforeClass
- public static void setUpBeforeClass() throws Exception {
-
- }
-
- @AfterClass
- public static void tearDownAfterClass() throws Exception {
- }
-
- @Before
- public void setUp() throws Exception {
- }
-
- // TODO sort out
-// @Test
-// public void test_createReferenceDeckFromStandaloneDeck() throws Exception {
-// ObjectMapper mapper = new ObjectMapper();
-// StandaloneDeck standaloneDeck = mapper.readValue(getClass().getResourceAsStream("deck.cbd"), StandaloneDeck.class);
-//
-// ReferenceDeck uut = new ReferenceDeck(standaloneDeck);
-//
-// boolean condition = uut.name == standaloneDeck.name &&
-// uut.plains == standaloneDeck.plains &&
-// uut.islands == standaloneDeck.islands &&
-// uut.swamps == standaloneDeck.swamps &&
-// uut.mountains == standaloneDeck.mountains &&
-// uut.forests == standaloneDeck.forests;
-// assertTrue("Metadata was not correctly set.", condition);
-// assertEquals("Wrong number of cards.", uut.cardReferences.size(), standaloneDeck.cards.size());
-// for (Card card : standaloneDeck.cards) {
-// Integer count = uut.cardReferences.get(card.hashCode());
-// assertNotNull("Reference missing in deck.", count);
-// assertEquals("Card count is wrong.", card.count, count);
-// }
-// }
-}
diff --git a/test/eu/equalparts/cardbase/decks/StatisticsTest.java b/test/eu/equalparts/cardbase/decks/StatisticsTest.java
deleted file mode 100644
index 6210f89..0000000
--- a/test/eu/equalparts/cardbase/decks/StatisticsTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-package eu.equalparts.cardbase.decks;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import eu.equalparts.cardbase.decks.StandaloneDeck;
-import eu.equalparts.cardbase.decks.Statistics;
-
-public class StatisticsTest {
-
- private static StandaloneDeck uut;
-
- @BeforeClass
- public static void setUpBeforeClass() throws Exception {
- ObjectMapper mapper = new ObjectMapper();
- uut = mapper.readValue(StatisticsTest.class.getResourceAsStream("deck.cbd"), StandaloneDeck.class);
- }
-
- @AfterClass
- public static void tearDownAfterClass() throws Exception {
- }
-
- @Before
- public void setUp() throws Exception {
- }
-
- @Test
- public void test_totalLandCountIsComputedCorrectly() throws Exception {
- int count = Statistics.count(uut, "Land");
-
- assertEquals(23, count);
- }
-
- @Test
- public void test_basicLandCountIsComputedCorrectly() throws Exception {
- int count = Statistics.count(uut, "Basic Land");
-
- assertEquals(20, count);
- }
-
- @Test
- public void test_cardCountIsComputedCorrectly() throws Exception {
- int count = Statistics.count(uut);
-
- assertEquals(60, count);
- }
-
- @Test
- public void test_landPercentageIsComputedCorrectly() throws Exception {
- double percentage = Statistics.calculatePercentage(uut, "Land");
-
- assertTrue("Land percentage should be " + (23.0 / 60.0) + ", is " + percentage, percentage == (23.0 / 60.0));
- }
-
- @Test
- public void test_creaturePercentageIsComputedCorrectly() throws Exception {
- double percentage = Statistics.calculatePercentage(uut, "Creature");
-
- assertTrue("Creature percentage should be " + (24.0 / 60.0) + ", is " + percentage, percentage == (24.0 / 60.0));
- }
-
- @Test
- public void test_creatureCountIsComputedCorrectly() throws Exception {
- int count = Statistics.count(uut, "Creature");
-
- assertEquals(24, count);
- }
-
- @Test
- public void test_sorceryCountIsComputedCorrectly() throws Exception {
- int count = Statistics.count(uut, "Sorcery");
-
- assertEquals(1, count);
- }
-
- @Test
- public void test_instantCountIsComputedCorrectly() throws Exception {
- int count = Statistics.count(uut, "Instant");
-
- assertEquals(6, count);
- }
-
- @Test
- public void test_planeswalkerCountIsComputedCorrectly() throws Exception {
- int count = Statistics.count(uut, "Planeswalker");
-
- assertEquals(0, count);
- }
-
- @Test
- public void test_elfCountIsComputedCorrectly() throws Exception {
- int count = Statistics.count(uut, "Elf");
-
- assertEquals(2, count);
- }
-
- @Test
- public void test_overallCostDistributionIsComputedCorrectly() throws Exception {
- int[] actualCosts = Statistics.computeDistribution(uut);
- int[] expectedCosts = {0, 8, 11, 3, 4, 7, 4};
-
- assertEquals("Array lengths do not match.", expectedCosts.length, actualCosts.length);
- for (int i = 0; i < expectedCosts.length; i++) {
- assertEquals("CMC: " + i, expectedCosts[i], actualCosts[i]);
- }
- }
-
- @Test
- public void test_creatureCostDistributionIsComputedCorrectly() throws Exception {
- int[] actualCosts = Statistics.computeDistribution(uut, "Creature");
- int[] expectedCosts = {0, 3, 6, 2, 2, 7, 4};
-
- assertEquals("Array lengths do not match.", expectedCosts.length, actualCosts.length);
- for (int i = 0; i < expectedCosts.length; i++) {
- assertEquals("CMC: " + i, expectedCosts[i], actualCosts[i]);
- }
- }
-
- @Test
- public void test_instantCostDistributionIsComputedCorrectly() throws Exception {
- int[] actualCosts = Statistics.computeDistribution(uut, "Instant");
- int[] expectedCosts = {0, 2, 4};
-
- assertEquals("Array lengths do not match.", expectedCosts.length, actualCosts.length);
- for (int i = 0; i < expectedCosts.length; i++) {
- assertEquals("CMC: " + i, expectedCosts[i], actualCosts[i]);
- }
- }
-
- @Test
- public void test_planeswalkerCostDistributionIsComputedCorrectly() throws Exception {
- int[] actualCosts = Statistics.computeDistribution(uut, "Planeswalker");
- int[] expectedCosts = {};
-
- assertEquals("Array lengths do not match.", expectedCosts.length, actualCosts.length);
- for (int i = 0; i < expectedCosts.length; i++) {
- assertEquals("CMC: " + i, expectedCosts[i], actualCosts[i]);
- }
- }
-
-} \ No newline at end of file
diff --git a/test/eu/equalparts/cardbase/decks/deck.cbd b/test/eu/equalparts/cardbase/decks/deck.cbd
deleted file mode 100644
index 57d479a..0000000
--- a/test/eu/equalparts/cardbase/decks/deck.cbd
+++ /dev/null
@@ -1 +0,0 @@
-{"name":"Red-green Ramp","plains":0,"islands":0,"swamps":0,"mountains":11,"forests":9,"cards":[{"name":"Titanic Growth","layout":"normal","manaCost":"{1}{G}","cmc":2,"type":"Instant","rarity":"Common","text":"Target creature gets +4/+4 until end of turn.","flavor":"The massive dominate through might. The tiny survive with guile. Beware the tiny who become massive.","artist":"Ryan Pancoast","number":"203","power":null,"toughness":null,"loyalty":null,"multiverseid":383415,"imageName":"titanic growth","watermark":null,"setCode":"M15","imageCode":"m15","count":1},{"name":"Nessian Asp","layout":"normal","manaCost":"{4}{G}","cmc":5,"type":"Creature — Snake","rarity":"Common","text":"Reach\n{6}{G}: Monstrosity 4. (If this creature isn't monstrous, put four +1/+1 counters on it and it becomes monstrous.)","flavor":"It's not the two heads you should fear. It's the four fangs.","artist":"Alex Horley-Orlandelli","number":"164","power":"4","toughness":"5","loyalty":null,"multiverseid":373650,"imageName":"nessian asp","watermark":null,"setCode":"THS","imageCode":"ths","count":2},{"name":"Satyr Hedonist","layout":"normal","manaCost":"{1}{G}","cmc":2,"type":"Creature — Satyr","rarity":"Common","text":"{R}, Sacrifice Satyr Hedonist: Add {R}{R}{R} to your mana pool.","flavor":"\"Any festival you can walk away from wasn't worth attending in the first place.\"","artist":"Chase Stone","number":"174","power":"2","toughness":"1","loyalty":null,"multiverseid":373744,"imageName":"satyr hedonist","watermark":null,"setCode":"THS","imageCode":"ths","count":1},{"name":"Awe for the Guilds","layout":"normal","manaCost":"{2}{R}","cmc":3,"type":"Sorcery","rarity":"Common","text":"Monocolored creatures can't block this turn.","flavor":"When the guilds cooperate, the guildless celebrate their peaceful society. When the guilds clash, the guildless just try to keep out of the way.","artist":"Mathias Kollros","number":"31","power":null,"toughness":null,"loyalty":null,"multiverseid":369053,"imageName":"awe for the guilds","watermark":null,"setCode":"DGM","imageCode":"dgm","count":1},{"name":"Voyaging Satyr","layout":"normal","manaCost":"{1}{G}","cmc":2,"type":"Creature — Satyr Druid","rarity":"Common","text":"{T}: Untap target land.","flavor":"\"None can own the land's bounty. The gods made this world for all to share its riches. And I'm not just saying that because you caught me stealing your fruit.\"","artist":"Tyler Jacobson","number":"182","power":"1","toughness":"2","loyalty":null,"multiverseid":373518,"imageName":"voyaging satyr","watermark":null,"setCode":"THS","imageCode":"ths","count":1},{"name":"Rugged Highlands","layout":"normal","manaCost":null,"cmc":null,"type":"Land","rarity":"Common","text":"Rugged Highlands enters the battlefield tapped.\nWhen Rugged Highlands enters the battlefield, you gain 1 life.\n{T}: Add {R} or {G} to your mana pool.","flavor":null,"artist":"Eytan Zana","number":"240","power":null,"toughness":null,"loyalty":null,"multiverseid":386641,"imageName":"rugged highlands","watermark":null,"setCode":"KTK","imageCode":"ktk","count":1},{"name":"Karametra's Favor","layout":"normal","manaCost":"{1}{G}","cmc":2,"type":"Enchantment — Aura","rarity":"Common","text":"Enchant creature\nWhen Karametra's Favor enters the battlefield, draw a card.\nEnchanted creature has \"{T}: Add one mana of any color to your mana pool.\"","flavor":"The harvest god's cornucopia contains the fruits of the fields, the forest, and beyond.","artist":"Chase Stone","number":"125","power":null,"toughness":null,"loyalty":null,"multiverseid":378497,"imageName":"karametra's favor","watermark":null,"setCode":"BNG","imageCode":"bng","count":1},{"name":"Segmented Krotiq","layout":"normal","manaCost":"{5}{G}","cmc":6,"type":"Creature — Insect","rarity":"Common","text":"Megamorph {6}{G} (You may cast this card face down as a 2/2 creature for {3}. Turn it face up any time for its megamorph cost and put a +1/+1 counter on it.)","flavor":"The list of things a krotiq eats is as long as the krotiq itself.","artist":"Christopher Moeller","number":"202","power":"6","toughness":"5","loyalty":null,"multiverseid":394684,"imageName":"segmented krotiq","watermark":null,"setCode":"DTK","imageCode":"dtk","count":2},{"name":"Sprinting Warbrute","layout":"normal","manaCost":"{4}{R}","cmc":5,"type":"Creature — Ogre Berserker","rarity":"Common","text":"Sprinting Warbrute attacks each turn if able.\nDash {3}{R} (You may cast this spell for its dash cost. If you do, it gains haste, and it's returned from the battlefield to its owner's hand at the beginning of the next end step.)","flavor":null,"artist":"Lake Hurwitz","number":"157","power":"5","toughness":"4","loyalty":null,"multiverseid":394709,"imageName":"sprinting warbrute","watermark":"Kolaghan","setCode":"DTK","imageCode":"dtk","count":3},{"name":"Searing Blood","layout":"normal","manaCost":"{R}{R}","cmc":2,"type":"Instant","rarity":"Uncommon","text":"Searing Blood deals 2 damage to target creature. When that creature dies this turn, Searing Blood deals 3 damage to the creature's controller.","flavor":"Purphoros's blessing: sometimes a boon, sometimes a bane. Always ablaze.","artist":"Daniel Ljunggren","number":"111","power":null,"toughness":null,"loyalty":null,"multiverseid":378483,"imageName":"searing blood","watermark":null,"setCode":"BNG","imageCode":"bng","count":1},{"name":"Surrak, the Hunt Caller","layout":"normal","manaCost":"{2}{G}{G}","cmc":4,"type":"Legendary Creature — Human Warrior","rarity":"Rare","text":"Formidable — At the beginning of combat on your turn, if creatures you control have total power 8 or greater, target creature you control gains haste until end of turn.","flavor":"\"The greatest honor is to feed Atarka.\"","artist":"Wesley Burt","number":"210","power":"5","toughness":"4","loyalty":null,"multiverseid":394721,"imageName":"surrak, the hunt caller","watermark":"Atarka","setCode":"DTK","imageCode":"dtk","count":1},{"name":"Kolaghan Aspirant","layout":"normal","manaCost":"{1}{R}","cmc":2,"type":"Creature — Human Warrior","rarity":"Common","text":"Whenever Kolaghan Aspirant becomes blocked by a creature, Kolaghan Aspirant deals 1 damage to that creature.","flavor":"She answers the call of the Crave, the desire for battle sated only by bloodshed.","artist":"Aaron Miller","number":"143","power":"2","toughness":"1","loyalty":null,"multiverseid":394608,"imageName":"kolaghan aspirant","watermark":"Kolaghan","setCode":"DTK","imageCode":"dtk","count":1},{"name":"Alpine Grizzly","layout":"normal","manaCost":"{2}{G}","cmc":3,"type":"Creature — Bear","rarity":"Common","text":null,"flavor":"The Temur welcome bears into the clan, fighting alongside them in battle. The relationship dates back to when they labored side by side under Sultai rule.","artist":"John Severin Brassell","number":"127","power":"4","toughness":"2","loyalty":null,"multiverseid":386474,"imageName":"alpine grizzly","watermark":null,"setCode":"KTK","imageCode":"ktk","count":1},{"name":"Valley Dasher","layout":"normal","manaCost":"{1}{R}","cmc":2,"type":"Creature — Human Berserker","rarity":"Common","text":"Haste\nValley Dasher attacks each turn if able.","flavor":"Mardu riders' greatest fear is that a battle might end before their weapons draw blood.","artist":"Matt Stewart","number":"125","power":"2","toughness":"2","loyalty":null,"multiverseid":386712,"imageName":"valley dasher","watermark":"Mardu","setCode":"KTK","imageCode":"ktk","count":1},{"name":"Terra Stomper","layout":"normal","manaCost":"{3}{G}{G}{G}","cmc":6,"type":"Creature — Beast","rarity":"Rare","text":"Terra Stomper can't be countered.\nTrample (If this creature would assign enough damage to its blockers to destroy them, you may have it assign the rest of its damage to defending player or planeswalker.)","flavor":"Its footfalls cause violent earthquakes, hurtling boulders, and unseasonable dust storms.","artist":"Goran Josic","number":"284","power":"8","toughness":"8","loyalty":null,"multiverseid":383173,"imageName":"terra stomper","watermark":null,"setCode":"M15","imageCode":"m15","count":1},{"name":"Vaultbreaker","layout":"normal","manaCost":"{3}{R}","cmc":4,"type":"Creature — Orc Rogue","rarity":"Uncommon","text":"Whenever Vaultbreaker attacks, you may discard a card. If you do, draw a card.\nDash {2}{R} (You may cast this spell for its dash cost. If you do, it gains haste, and it's returned from the battlefield to its owner's hand at the beginning of the next end step.)","flavor":null,"artist":"Wayne Reynolds","number":"117","power":"4","toughness":"2","loyalty":null,"multiverseid":391951,"imageName":"vaultbreaker","watermark":"Mardu","setCode":"FRF","imageCode":"frf","count":1},{"name":"Elvish Mystic","layout":"normal","manaCost":"{G}","cmc":1,"type":"Creature — Elf Druid","rarity":"Common","text":"{T}: Add {G} to your mana pool.","flavor":"\"Life grows everywhere. My kin merely find those places where it grows strongest.\"\n—Nissa Revane","artist":"Wesley Burt","number":"173","power":"1","toughness":"1","loyalty":null,"multiverseid":383229,"imageName":"elvish mystic","watermark":null,"setCode":"M15","imageCode":"m15","count":2},{"name":"Collateral Damage","layout":"normal","manaCost":"{R}","cmc":1,"type":"Instant","rarity":"Common","text":"As an additional cost to cast Collateral Damage, sacrifice a creature.\nCollateral Damage deals 3 damage to target creature or player.","flavor":"It is much easier to create fire than to contain it.","artist":"Ryan Barger","number":"95","power":null,"toughness":null,"loyalty":null,"multiverseid":391811,"imageName":"collateral damage","watermark":null,"setCode":"FRF","imageCode":"frf","count":1},{"name":"Dragon Whisperer","layout":"normal","manaCost":"{R}{R}","cmc":2,"type":"Creature — Human Shaman","rarity":"Mythic Rare","text":"{R}: Dragon Whisperer gains flying until end of turn.\n{1}{R}: Dragon Whisperer gets +1/+0 until end of turn.\nFormidable — {4}{R}{R}: Put a 4/4 red Dragon creature token with flying onto the battlefield. Activate this ability only if creatures you control have total power 8 or greater.","flavor":null,"artist":"Chris Rallis","number":"137","power":"2","toughness":"2","loyalty":null,"multiverseid":394543,"imageName":"dragon whisperer","watermark":"Atarka","setCode":"DTK","imageCode":"dtk","count":1},{"name":"Magma Spray","layout":"normal","manaCost":"{R}","cmc":1,"type":"Instant","rarity":"Common","text":"Magma Spray deals 2 damage to target creature. If that creature would die this turn, exile it instead.","flavor":"The ancient dragon Thraxes sleeps in Purphoros's sacred peak. When he stirs in dreams, so does the mountain.","artist":"Richard Wright","number":"103","power":null,"toughness":null,"loyalty":null,"multiverseid":380452,"imageName":"magma spray","watermark":null,"setCode":"JOU","imageCode":"jou","count":1},{"name":"Hammerhand","layout":"normal","manaCost":"{R}","cmc":1,"type":"Enchantment — Aura","rarity":"Common","text":"Enchant creature\nWhen Hammerhand enters the battlefield, target creature can't block this turn.\nEnchanted creature gets +1/+1 and has haste. (It can attack and {T} no matter when it came under your control.)","flavor":null,"artist":"Tomasz Jedruszek","number":"147","power":null,"toughness":null,"loyalty":null,"multiverseid":383262,"imageName":"hammerhand","watermark":null,"setCode":"M15","imageCode":"m15","count":1},{"name":"Brindle Boar","layout":"normal","manaCost":"{2}{G}","cmc":3,"type":"Creature — Boar","rarity":"Common","text":"Sacrifice Brindle Boar: You gain 4 life.","flavor":"\"Tell the cooks to prepare the fires. Tonight we feast!\"\n—Tolar Wolfbrother, Krosan tracker","artist":"Dave Allsop","number":"167","power":"2","toughness":"2","loyalty":null,"multiverseid":370778,"imageName":"brindle boar","watermark":null,"setCode":"M14","imageCode":"m14","count":1},{"name":"Harbinger of the Hunt","layout":"normal","manaCost":"{3}{R}{G}","cmc":5,"type":"Creature — Dragon","rarity":"Rare","text":"Flying\n{2}{R}: Harbinger of the Hunt deals 1 damage to each creature without flying.\n{2}{G}: Harbinger of the Hunt deals 1 damage to each other creature with flying.","flavor":"An Atarka dragon's exhale cooks what its inhale consumes.","artist":"Aaron Miller","number":"223","power":"5","toughness":"3","loyalty":null,"multiverseid":394591,"imageName":"harbinger of the hunt","watermark":"Atarka","setCode":"DTK","imageCode":"dtk","count":1},{"name":"Lightning Strike","layout":"normal","manaCost":"{1}{R}","cmc":2,"type":"Instant","rarity":"Common","text":"Lightning Strike deals 3 damage to target creature or player.","flavor":"\"The hand of Keranos can be seen in every rumbling storm cloud. Best not to stand where he points.\"\n—Rakleia of Shrine Peak","artist":"Adam Paquette","number":"127","power":null,"toughness":null,"loyalty":null,"multiverseid":373651,"imageName":"lightning strike","watermark":null,"setCode":"THS","imageCode":"ths","count":2},{"name":"Monastery Swiftspear","layout":"normal","manaCost":"{R}","cmc":1,"type":"Creature — Human Monk","rarity":"Uncommon","text":"Haste\nProwess (Whenever you cast a noncreature spell, this creature gets +1/+1 until end of turn.)","flavor":"The calligraphy of combat is written with strokes of sudden blood.","artist":"Steve Argyle","number":"118","power":"1","toughness":"2","loyalty":null,"multiverseid":386608,"imageName":"monastery swiftspear","watermark":"Jeskai","setCode":"KTK","imageCode":"ktk","count":1},{"name":"Evolving Wilds","layout":"normal","manaCost":null,"cmc":null,"type":"Land","rarity":"Common","text":"{T}, Sacrifice Evolving Wilds: Search your library for a basic land card and put it onto the battlefield tapped. Then shuffle your library.","flavor":"Without the interfering hands of civilization, nature will always shape itself to its own needs.","artist":"Steven Belledin","number":"243","power":null,"toughness":null,"loyalty":null,"multiverseid":383235,"imageName":"evolving wilds","watermark":null,"setCode":"M15","imageCode":"m15","count":2},{"name":"Market Festival","layout":"normal","manaCost":"{3}{G}","cmc":4,"type":"Enchantment — Aura","rarity":"Common","text":"Enchant land\nWhenever enchanted land is tapped for mana, its controller adds two mana in any combination of colors to his or her mana pool (in addition to the mana the land produces).","flavor":"Commerce is always the basis for peace.","artist":"Ryan Barger","number":"130","power":null,"toughness":null,"loyalty":null,"multiverseid":380454,"imageName":"market festival","watermark":null,"setCode":"JOU","imageCode":"jou","count":2},{"name":"Lightning Shrieker","layout":"normal","manaCost":"{4}{R}","cmc":5,"type":"Creature — Dragon","rarity":"Common","text":"Flying, trample, haste\nAt the beginning of the end step, Lightning Shrieker's owner shuffles it into his or her library.","flavor":"Dragonslayers learned to keep silent about their deeds after seeing the terrible vengeance wrought by Kolaghan and her brood.","artist":"Slawomir Maniak","number":"106","power":"5","toughness":"5","loyalty":null,"multiverseid":391868,"imageName":"lightning shrieker","watermark":"Kolaghan","setCode":"FRF","imageCode":"frf","count":1},{"name":"Kalonian Tusker","layout":"normal","manaCost":"{G}{G}","cmc":2,"type":"Creature — Beast","rarity":"Uncommon","text":null,"flavor":"\"And all this time I thought we were tracking it.\"\n—Juruk, Kalonian tracker","artist":"Svetlin Velinov","number":"182","power":"3","toughness":"3","loyalty":null,"multiverseid":370700,"imageName":"kalonian tusker","watermark":null,"setCode":"M14","imageCode":"m14","count":1},{"name":"Dragon Mantle","layout":"normal","manaCost":"{R}","cmc":1,"type":"Enchantment — Aura","rarity":"Common","text":"Enchant creature\nWhen Dragon Mantle enters the battlefield, draw a card.\nEnchanted creature has \"{R}: This creature gets +1/+0 until end of turn.\"","flavor":null,"artist":"Anthony Palumbo","number":"119","power":null,"toughness":null,"loyalty":null,"multiverseid":373634,"imageName":"dragon mantle","watermark":null,"setCode":"THS","imageCode":"ths","count":2},{"name":"Shivan Dragon","layout":"normal","manaCost":"{4}{R}{R}","cmc":6,"type":"Creature — Dragon","rarity":"Rare","text":"Flying (This creature can't be blocked except by creatures with flying or reach.)\n{R}: Shivan Dragon gets +1/+0 until end of turn.","flavor":"The undisputed master of the mountains of Shiv.","artist":"Donato Giancola","number":"281","power":"5","toughness":"5","loyalty":null,"multiverseid":383172,"imageName":"shivan dragon","watermark":null,"setCode":"M15","imageCode":"m15","count":1}]} \ No newline at end of file
diff --git a/test/testbase.cb b/test/testbase.cb
index e617fd8..cf06e9f 100644
--- a/test/testbase.cb
+++ b/test/testbase.cb
@@ -1 +1 @@
-{"cards":{"1588769":{"name":"Reverberate","layout":"normal","manaCost":"{R}{R}","cmc":2,"type":"Instant","rarity":"Rare","text":"Copy target instant or sorcery spell. You may choose new targets for the copy.","flavor":"For every action, there is a swifter and more violent reaction.","artist":"jD","number":"152","power":null,"toughness":null,"loyalty":null,"multiverseid":233722,"imageName":"reverberate","watermark":null,"setCode":"M12","imageCode":"m12"},"126251":{"name":"Mighty Leap","layout":"normal","manaCost":"{1}{W}","cmc":2,"type":"Instant","rarity":"Common","text":"Target creature gets +2/+2 and gains flying until end of turn. (It can't be blocked except by creatures with flying or reach.)","flavor":"\"The southern fortress taken by invaders? Heh, sure . . . when elephants fly.\"\n—Brezard Skeinbow, captain of the guard","artist":"rk post","number":"26","power":null,"toughness":null,"loyalty":null,"multiverseid":241989,"imageName":"mighty leap","watermark":null,"setCode":"M12","imageCode":"m12"},"1580419":{"name":"Formless Nurturing","layout":"normal","manaCost":"{3}{G}","cmc":4,"type":"Sorcery","rarity":"Common","text":"Manifest the top card of your library, then put a +1/+1 counter on it. (To manifest a card, put it onto the battlefield face down as a 2/2 creature. Turn it face up any time for its mana cost if it's a creature card.)","flavor":null,"artist":"Cliff Childs","number":"129","power":null,"toughness":null,"loyalty":null,"multiverseid":391837,"imageName":"formless nurturing","watermark":null,"setCode":"FRF","imageCode":"frf"},"1580388":{"name":"Feral Krushok","layout":"normal","manaCost":"{4}{G}","cmc":5,"type":"Creature — Beast","rarity":"Common","text":null,"flavor":"In a stunning act of diplomacy, Yasova Dragonclaw ceded a portion of Temur lands to the Sultai. Her clan protested until they saw she had given the Sultai the breeding grounds of the krushoks. They hadn't realized she had a sense of humor.","artist":"Kev Walker","number":"128","power":"5","toughness":"4","loyalty":null,"multiverseid":391831,"imageName":"feral krushok","watermark":null,"setCode":"FRF","imageCode":"frf"},"1580357":{"name":"Destructor Dragon","layout":"normal","manaCost":"{4}{G}{G}","cmc":6,"type":"Creature — Dragon","rarity":"Uncommon","text":"Flying\nWhen Destructor Dragon dies, destroy target noncreature permanent.","flavor":"After countless attacks on the Salt Road where it passes through the frozen tundra, the Abzan began to refer to the area as Atarka territory rather than Temur lands.","artist":"Peter Mohrbacher","number":"127","power":"4","toughness":"4","loyalty":null,"multiverseid":391818,"imageName":"destructor dragon","watermark":"Atarka","setCode":"FRF","imageCode":"frf"},"127150":{"name":"Siege Mastodon","layout":"normal","manaCost":"{4}{W}","cmc":5,"type":"Creature — Elephant","rarity":"Common","text":null,"flavor":"\"The walls of the wicked will fall before us. Ready the siege engines. We proceed to war!\"\n—General Avitora","artist":"Matt Cavotta","number":"34","power":"3","toughness":"5","loyalty":null,"multiverseid":222635,"imageName":"siege mastodon","watermark":null,"setCode":"M12","imageCode":"m12"}},"collection":{"1588769":1,"126251":1,"1580419":8,"1580388":1,"1580357":1,"127150":2},"decks":{}} \ No newline at end of file
+{"cardData":{"1588769":{"name":"Reverberate","layout":"normal","manaCost":"{R}{R}","cmc":2,"type":"Instant","rarity":"Rare","text":"Copy target instant or sorcery spell. You may choose new targets for the copy.","flavor":"For every action, there is a swifter and more violent reaction.","artist":"jD","number":"152","power":null,"toughness":null,"loyalty":null,"multiverseid":233722,"imageName":"reverberate","watermark":null,"setCode":"M12","imageCode":"m12"},"126251":{"name":"Mighty Leap","layout":"normal","manaCost":"{1}{W}","cmc":2,"type":"Instant","rarity":"Common","text":"Target creature gets +2/+2 and gains flying until end of turn. (It can't be blocked except by creatures with flying or reach.)","flavor":"\"The southern fortress taken by invaders? Heh, sure . . . when elephants fly.\"\n—Brezard Skeinbow, captain of the guard","artist":"rk post","number":"26","power":null,"toughness":null,"loyalty":null,"multiverseid":241989,"imageName":"mighty leap","watermark":null,"setCode":"M12","imageCode":"m12"},"1580419":{"name":"Formless Nurturing","layout":"normal","manaCost":"{3}{G}","cmc":4,"type":"Sorcery","rarity":"Common","text":"Manifest the top card of your library, then put a +1/+1 counter on it. (To manifest a card, put it onto the battlefield face down as a 2/2 creature. Turn it face up any time for its mana cost if it's a creature card.)","flavor":null,"artist":"Cliff Childs","number":"129","power":null,"toughness":null,"loyalty":null,"multiverseid":391837,"imageName":"formless nurturing","watermark":null,"setCode":"FRF","imageCode":"frf"},"1580388":{"name":"Feral Krushok","layout":"normal","manaCost":"{4}{G}","cmc":5,"type":"Creature — Beast","rarity":"Common","text":null,"flavor":"In a stunning act of diplomacy, Yasova Dragonclaw ceded a portion of Temur lands to the Sultai. Her clan protested until they saw she had given the Sultai the breeding grounds of the krushoks. They hadn't realized she had a sense of humor.","artist":"Kev Walker","number":"128","power":"5","toughness":"4","loyalty":null,"multiverseid":391831,"imageName":"feral krushok","watermark":null,"setCode":"FRF","imageCode":"frf"},"1580357":{"name":"Destructor Dragon","layout":"normal","manaCost":"{4}{G}{G}","cmc":6,"type":"Creature — Dragon","rarity":"Uncommon","text":"Flying\nWhen Destructor Dragon dies, destroy target noncreature permanent.","flavor":"After countless attacks on the Salt Road where it passes through the frozen tundra, the Abzan began to refer to the area as Atarka territory rather than Temur lands.","artist":"Peter Mohrbacher","number":"127","power":"4","toughness":"4","loyalty":null,"multiverseid":391818,"imageName":"destructor dragon","watermark":"Atarka","setCode":"FRF","imageCode":"frf"},"127150":{"name":"Siege Mastodon","layout":"normal","manaCost":"{4}{W}","cmc":5,"type":"Creature — Elephant","rarity":"Common","text":null,"flavor":"\"The walls of the wicked will fall before us. Ready the siege engines. We proceed to war!\"\n—General Avitora","artist":"Matt Cavotta","number":"34","power":"3","toughness":"5","loyalty":null,"multiverseid":222635,"imageName":"siege mastodon","watermark":null,"setCode":"M12","imageCode":"m12"}},"cardReferences":{"1588769":1,"126251":1,"1580419":8,"1580388":1,"1580357":1,"127150":2},"decks":{}}