1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
|
# coding=utf-8
"""
Tests for the ofxstatement Betterment plugin.
"""
from datetime import date
from decimal import Decimal
import logging
import unittest
from ofxstatement.statement import StatementLine
import os
from ofxstatement.plugins.betterment import BettermentPlugin, generate_stable_transaction_id
__author__ = 'cmayes'
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('test_betterment')
DATA_DIR = os.path.join(os.path.dirname(__file__), 'test_data')
ZERO_BAL_CSV_PATH = os.path.join(DATA_DIR, 'transactions.csv')
POS_CSV_PATH = os.path.join(DATA_DIR, 'transactions_positive.csv')
NOV_2015_CSV_PATH = os.path.join(DATA_DIR, 'transactions_11_2015.csv')
NOV_2015_CSV_PATH_NO_COMPLETED = os.path.join(DATA_DIR, 'transactions_11_2015_no_completed.csv')
ALL_ZERO_CSV_PATH = os.path.join(DATA_DIR, 'transactions_zeros.csv')
HEADER_CSV_PATH = os.path.join(DATA_DIR, 'transactions_header.csv')
EMPTY_CSV_PATH = os.path.join(DATA_DIR, 'transactions_empty.csv')
BLANK_BAL_PATH = os.path.join(DATA_DIR, 'transactions_blankbal.csv')
# Shared Methods #
class TestBettermentStatement(unittest.TestCase):
def testZeroBalance(self):
"""Statement starts with a zero balance."""
stmt = BettermentPlugin(None, {}).get_parser(ZERO_BAL_CSV_PATH).parse()
self.assertEqual(Decimal('0.0'), stmt.start_balance)
self.assertEqual(Decimal('817.44'), stmt.end_balance)
self.assertEqual(date(2015, 9, 22), to_date(stmt.start_date))
self.assertEqual(date(2015, 9, 25), to_date(stmt.end_date))
self.assertEqual(5, len(stmt.lines))
for stline in stmt.lines:
self.assertTrue(stline.memo in ('Initial Deposit from ****', 'Market Change'))
self.assertFalse(stline.amount == 0)
self.assertTrue(date(2015, 9, 25) >= to_date(stline.date) >= date(2015, 9, 22))
def testPositiveBalance(self):
"""Statement starts with a positive balance."""
stmt = BettermentPlugin(None, {}).get_parser(POS_CSV_PATH).parse()
self.assertEqual(Decimal('1000.0'), stmt.start_balance)
self.assertEqual(Decimal('1817.44'), stmt.end_balance)
self.assertEqual(date(2015, 9, 22), to_date(stmt.start_date))
self.assertEqual(date(2015, 9, 25), to_date(stmt.end_date))
self.assertEqual(5, len(stmt.lines))
for stline in stmt.lines:
self.assertTrue(stline.memo in ('Initial Deposit from ****', 'Market Change'))
self.assertFalse(stline.amount == 0)
self.assertTrue(date(2015, 9, 25) >= to_date(stline.date) >= date(2015, 9, 22))
def testPositiveBalanceNov2015(self):
"""Statement starts with a positive balance and the new November 2015 CSV format."""
stmt = BettermentPlugin(None, {}).get_parser(NOV_2015_CSV_PATH).parse()
self.assertEqual(Decimal('1631.25'), stmt.start_balance)
self.assertEqual(Decimal('1254.43'), stmt.end_balance)
self.assertEqual(date(2015, 10, 29), to_date(stmt.start_date))
self.assertEqual(date(2015, 11, 11), to_date(stmt.end_date))
self.assertEqual(12, len(stmt.lines))
for stline in stmt.lines:
self.assertTrue(stline.memo in ('Initial Deposit from ****', 'Market Change', 'Dividend'))
self.assertFalse(stline.amount == 0)
self.assertTrue(date(2015, 11, 11) >= to_date(stline.date) >= date(2015, 10, 29))
def testPositiveBalanceNov2015NoCompleted(self):
"""Statement is missing a completed date for the latest line in the new November 2015 CSV format."""
stmt = BettermentPlugin(None, {}).get_parser(NOV_2015_CSV_PATH_NO_COMPLETED).parse()
self.assertEqual(Decimal('1631.25'), stmt.start_balance)
self.assertEqual(Decimal('1240.33'), stmt.end_balance)
self.assertEqual(date(2015, 10, 29), to_date(stmt.start_date))
self.assertEqual(date(2015, 11, 10), to_date(stmt.end_date))
self.assertEqual(11, len(stmt.lines))
for stline in stmt.lines:
self.assertTrue(stline.memo in ('Initial Deposit from ****', 'Market Change', 'Dividend'))
self.assertFalse(stline.amount == 0)
self.assertTrue(date(2015, 11, 10) >= to_date(stline.date) >= date(2015, 10, 29))
class TestBettermentSettings(unittest.TestCase):
def testAccountNoFilterZeros(self):
"""Set an account and disable zero-value transaction filtering."""
stmt = BettermentPlugin(None, {'account': '12345', 'filter_zeros': 'false'}
).get_parser(ZERO_BAL_CSV_PATH).parse()
self.assertEqual(stmt.account_id, '12345')
self.assertEqual(stmt.bank_id, 'Betterment')
# Check that the zero balance lines are present
self.assertEqual(7, len(stmt.lines))
def testBankNoFilterZeros(self):
"""Set a custom bank and filter zero-value transactions."""
stmt = BettermentPlugin(None, {'bank': 'CustomBank', 'filter_zeros': 'true'}
).get_parser(ZERO_BAL_CSV_PATH).parse()
self.assertIsNone(stmt.account_id)
self.assertEqual(stmt.bank_id, 'CustomBank')
# Check that the zero balance lines are present
self.assertEqual(5, len(stmt.lines))
class TestBettermentEmpties(unittest.TestCase):
def testAccountFilterZerosAll(self):
"""All lines filtered by zeros policy."""
stmt = BettermentPlugin(None, {}).get_parser(ALL_ZERO_CSV_PATH).parse()
self.assertIsNone(stmt.start_balance)
self.assertIsNone(stmt.end_balance)
self.assertIsNone(stmt.start_date)
self.assertIsNone(stmt.end_date)
self.assertEqual(0, len(stmt.lines))
def testHeaderOnly(self):
"""File only has a header line."""
stmt = BettermentPlugin(None, {}).get_parser(HEADER_CSV_PATH).parse()
self.assertIsNone(stmt.start_balance)
self.assertIsNone(stmt.end_balance)
self.assertIsNone(stmt.start_date)
self.assertIsNone(stmt.end_date)
self.assertEqual(0, len(stmt.lines))
def testEmpty(self):
"""Empty file."""
stmt = BettermentPlugin(None, {}).get_parser(EMPTY_CSV_PATH).parse()
self.assertIsNone(stmt.start_balance)
self.assertIsNone(stmt.end_balance)
self.assertIsNone(stmt.start_date)
self.assertIsNone(stmt.end_date)
self.assertEqual(0, len(stmt.lines))
def testEmptyEndingBalance(self):
"""Strip out pending transactions."""
stmt = BettermentPlugin(None, {}).get_parser(BLANK_BAL_PATH).parse()
self.assertEqual(Decimal('1319.74'), stmt.start_balance)
self.assertEqual(Decimal('1692.68'), stmt.end_balance)
self.assertEqual(date(2015, 10, 2), to_date(stmt.start_date))
self.assertEqual(date(2015, 10, 2), to_date(stmt.end_date))
self.assertEqual(3, len(stmt.lines))
for stline in stmt.lines:
self.assertTrue(stline.memo in ('Dividend Reinvestment', 'Market Change'))
self.assertFalse(stline.amount == 0)
self.assertEqual(date(2015, 10, 2), to_date(stline.date))
class TestBettermentStatementIdStability(unittest.TestCase):
all_hash = "316fcd14b9bb7d9bdb6a00473356c896aede98da3df9268bc57c319f209ad3d1"
amt_hash = "4ebc4a141b378980461430980948a55988fbf56f85d084ac33d8a8f61b9fab88"
none_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
def testAllFilled(self):
"""All of the fields used in the hash have a value"""
sline = StatementLine()
sline.date = date(2015, 10, 2)
sline.memo = "Test statement line"
sline.amount = Decimal('123.45')
self.assertEqual(self.all_hash, generate_stable_transaction_id(sline))
def testAmount(self):
"""Only amount is set"""
sline = StatementLine()
sline.amount = Decimal('123.45')
self.assertEqual(self.amt_hash, generate_stable_transaction_id(sline))
def testNone(self):
"""Nothing is set"""
sline = StatementLine()
self.assertEqual(self.none_hash, generate_stable_transaction_id(sline))
def to_date(dt):
"""Creates a comparable date object from the given datetime."""
return date(dt.year, dt.month, dt.day)
|