summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/fixtures/account_listing_aggregation.ofx51
-rw-r--r--tests/fixtures/bank_medium.ofx18
-rw-r--r--tests/fixtures/bank_small.ofx11
-rw-r--r--tests/fixtures/checking.ofx83
-rw-r--r--tests/fixtures/fail_nice/date_missing.ofx65
-rw-r--r--tests/fixtures/fail_nice/decimal_error.ofx51
-rw-r--r--tests/fixtures/fail_nice/empty_balance.ofx48
-rw-r--r--tests/fixtures/fidelity.ofx11
-rw-r--r--tests/fixtures/investment_medium.ofx119
-rw-r--r--tests/fixtures/multiple_accounts.ofx61
-rw-r--r--tests/fixtures/multiple_accounts2.ofx61
-rw-r--r--tests/fixtures/signon_fail.ofx11
-rw-r--r--tests/fixtures/signon_success.ofx11
-rw-r--r--tests/fixtures/signon_success_no_message.ofx11
-rw-r--r--tests/fixtures/vanguard.ofx11
-rw-r--r--tests/support.py9
-rw-r--r--tests/test_parse.py623
-rw-r--r--tests/test_write.py17
19 files changed, 1272 insertions, 0 deletions
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/fixtures/account_listing_aggregation.ofx b/tests/fixtures/account_listing_aggregation.ofx
new file mode 100644
index 0000000..5ecf241
--- /dev/null
+++ b/tests/fixtures/account_listing_aggregation.ofx
@@ -0,0 +1,51 @@
+OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET:1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:85230611d6fc414fa391a8c2425f8e9e
+
+<OFX>
+<SIGNONMSGSRSV1>
+<SONRS>
+<STATUS><CODE>0<SEVERITY>INFO<MESSAGE>Success</STATUS>
+<DTSERVER>20120814060142<LANGUAGE>ENG
+<FI><ORG>USAA<FID>24591</FI>
+</SONRS>
+</SIGNONMSGSRSV1>
+<SIGNUPMSGSRSV1>
+<ACCTINFOTRNRS>
+<TRNUID>09ca62d0198049388252f0a547bae86a<STATUS>
+<CODE>0<SEVERITY>INFO<MESSAGE>Success</STATUS>
+<CLTCOOKIE>4<ACCTINFORS>
+<DTACCTUP>20120814120000<ACCTINFO>
+<DESC>USAA SAVINGS<BANKACCTINFO>
+<BANKACCTFROM>
+<BANKID>314074269<ACCTID>0000000001<ACCTTYPE>SAVINGS</BANKACCTFROM>
+<SUPTXDL>Y<XFERSRC>N<XFERDEST>N<SVCSTATUS>ACTIVE</BANKACCTINFO>
+</ACCTINFO>
+<ACCTINFO>
+<DESC>FOUR STAR CHECKING<BANKACCTINFO>
+<BANKACCTFROM>
+<BANKID>314074269<ACCTID>0000000002<ACCTTYPE>CHECKING</BANKACCTFROM>
+<SUPTXDL>Y<XFERSRC>N<XFERDEST>N<SVCSTATUS>ACTIVE</BANKACCTINFO>
+</ACCTINFO>
+<ACCTINFO>
+<DESC>LINE OF CREDIT<BANKACCTINFO>
+<BANKACCTFROM>
+<BANKID>314074269<ACCTID>00000000000003<ACCTTYPE>CREDITLINE</BANKACCTFROM>
+<SUPTXDL>Y<XFERSRC>N<XFERDEST>N<SVCSTATUS>ACTIVE</BANKACCTINFO>
+</ACCTINFO>
+<ACCTINFO>
+<DESC>MY CREDIT CARD<CCACCTINFO>
+<CCACCTFROM>
+<ACCTID>4111111111111111</CCACCTFROM>
+<SUPTXDL>Y<XFERSRC>N<XFERDEST>N<SVCSTATUS>ACTIVE</CCACCTINFO>
+</ACCTINFO>
+</ACCTINFORS>
+</ACCTINFOTRNRS>
+</SIGNUPMSGSRSV1>
+</OFX>
diff --git a/tests/fixtures/bank_medium.ofx b/tests/fixtures/bank_medium.ofx
new file mode 100644
index 0000000..08c71c3
--- /dev/null
+++ b/tests/fixtures/bank_medium.ofx
@@ -0,0 +1,18 @@
+OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET:1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:NONE
+
+<OFX><SIGNONMSGSRSV1><SONRS><STATUS><CODE>0<SEVERITY>INFO<MESSAGE>OK</STATUS><DTSERVER>20090523122017<LANGUAGE>ENG<DTPROFUP>20090523122017<DTACCTUP>20090523122017<INTU.BID>00024</SONRS></SIGNONMSGSRSV1>
+<BANKMSGSRSV1><STMTTRNRS><TRNUID>20090523122017<STATUS><CODE>0<SEVERITY>INFO<MESSAGE>OK</STATUS>
+<STMTRS><CURDEF>CAD<BANKACCTFROM><BANKID>160000100<BRANCHID>00<ACCTID>12300 000012345678<ACCTTYPE>CHECKING</BANKACCTFROM>
+<BANKTRANLIST><DTSTART>20090401<DTEND>20090523122017
+<STMTTRN><TRNTYPE>POS<DTPOSTED>20090401122017.000[-5:EST]<TRNAMT>-6.60<FITID>0000123456782009040100001<NAME>MCDONALD'S #112<MEMO>POS MERCHANDISE;MCDONALD'S #112</STMTTRN>
+<STMTTRN><TRNTYPE>CHECK<DTPOSTED>20090402122017.000[-5:EST]<TRNAMT>-316.67<FITID>0000123456782009040200004<CHECKNUM>0<NAME>Joe's Bald Hairstyles<MEMO>MISCELLANEOUS PAYMENTS;Joe's Bald Hairstyles</STMTTRN>
+<STMTTRN><TRNTYPE>POS<DTPOSTED>20090403122017.000[-5:EST]<TRNAMT>-22.00<FITID>0000123456782009040300005<NAME>CONNIE'S HAIR D<MEMO>POS MERCHANDISE;CONNIE'S HAIR D</STMTTRN>
+</BANKTRANLIST><LEDGERBAL><BALAMT>382.34<DTASOF>20090523122017</LEDGERBAL><AVAILBAL><BALAMT>682.34<DTASOF>20090523122017</AVAILBAL></STMTRS></STMTTRNRS></BANKMSGSRSV1></OFX>
diff --git a/tests/fixtures/bank_small.ofx b/tests/fixtures/bank_small.ofx
new file mode 100644
index 0000000..3040f8f
--- /dev/null
+++ b/tests/fixtures/bank_small.ofx
@@ -0,0 +1,11 @@
+OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET:1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:NONE
+
+<OFX></OFX>
diff --git a/tests/fixtures/checking.ofx b/tests/fixtures/checking.ofx
new file mode 100644
index 0000000..6c32904
--- /dev/null
+++ b/tests/fixtures/checking.ofx
@@ -0,0 +1,83 @@
+OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET:1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:NONE
+
+<OFX>
+ <SIGNONMSGSRSV1>
+ <SONRS>
+ <STATUS>
+ <CODE>0
+ <SEVERITY>INFO
+ </STATUS>
+ <DTSERVER>20130525225731.258
+ <LANGUAGE>ENG
+ <DTPROFUP>20050531060000.000
+ <FI>
+ <ORG>FAKE
+ <FID>1101
+ </FI>
+ <INTU.BID>51123
+ <INTU.USERID>9774652
+ </SONRS>
+ </SIGNONMSGSRSV1>
+ <BANKMSGSRSV1>
+ <STMTTRNRS>
+ <TRNUID>0
+ <STATUS>
+ <CODE>0
+ <SEVERITY>INFO
+ </STATUS>
+ <STMTRS>
+ <CURDEF>USD
+ <BANKACCTFROM>
+ <BANKID>5472369148
+ <ACCTID>1452687~7
+ <ACCTTYPE>CHECKING
+ </BANKACCTFROM>
+ <BANKTRANLIST>
+ <DTSTART>20000101070000.000
+ <DTEND>20130525060000.000
+ <STMTTRN>
+ <TRNTYPE>CREDIT
+ <DTPOSTED>20110331120000.000
+ <TRNAMT>0.01
+ <FITID>0000486
+ <NAME>DIVIDEND EARNED FOR PERIOD OF 03
+ <MEMO>DIVIDEND EARNED FOR PERIOD OF 03/01/2011 THROUGH 03/31/2011 ANNUAL PERCENTAGE YIELD EARNED IS 0.05%
+ </STMTTRN>
+ <STMTTRN>
+ <TRNTYPE>DEBIT
+ <DTPOSTED>20110405120000.000
+ <TRNAMT>-34.51
+ <FITID>0000487
+ <NAME>AUTOMATIC WITHDRAWAL, ELECTRIC BILL
+ <MEMO>AUTOMATIC WITHDRAWAL, ELECTRIC BILL WEB(S )
+ </STMTTRN>
+ <STMTTRN>
+ <TRNTYPE>CHECK
+ <DTPOSTED>20110407120000.000
+ <TRNAMT>-25.00
+ <FITID>0000488
+ <CHECKNUM>319
+ <NAME>RETURNED CHECK FEE, CHECK # 319
+ <MEMO>RETURNED CHECK FEE, CHECK # 319 FOR $45.33 ON 04/07/11
+ </STMTTRN>
+ </BANKTRANLIST>
+ <LEDGERBAL>
+ <BALAMT>100.99
+ <DTASOF>20130525225731.258
+ </LEDGERBAL>
+ <AVAILBAL>
+ <BALAMT>75.99
+ <DTASOF>20130525225731.258
+ </AVAILBAL>
+ </STMTRS>
+ </STMTTRNRS>
+ </BANKMSGSRSV1>
+</OFX> \ No newline at end of file
diff --git a/tests/fixtures/fail_nice/date_missing.ofx b/tests/fixtures/fail_nice/date_missing.ofx
new file mode 100644
index 0000000..931f438
--- /dev/null
+++ b/tests/fixtures/fail_nice/date_missing.ofx
@@ -0,0 +1,65 @@
+
+
+<OFX>
+ <SIGNONMSGSRSV1>
+ <SONRS>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ </STATUS>
+ <DTSERVER>20110614</DTSERVER>
+ <LANGUAGE></LANGUAGE>
+ </SONRS>
+ </SIGNONMSGSRSV1>
+ <BANKMSGSRSV1>
+ <STMTTRNRS>
+ <TRNUID>1</TRNUID>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ </STATUS>
+ <STMTRS>
+ <CURDEF>USD</CURDEF>
+ <BANKACCTFROM>
+ <BANKID>123845030</BANKID>
+ <ACCTID>192639749</ACCTID>
+ <ACCTTYPE>CHECKING</ACCTTYPE>
+ </BANKACCTFROM>
+
+ <BANKTRANLIST>
+ <DTSTART>20110412</DTSTART>
+ <DTEND>20110614</DTEND>
+
+ <STMTTRN>
+ <TRNTYPE>OTHER</TRNTYPE>
+ <TRNAMT>-80.00</TRNAMT>
+ <FITID>184997056</FITID>
+ <NAME>TestFail1</NAME>
+ </STMTTRN>
+
+ <STMTTRN>
+ <TRNTYPE>OTHER</TRNTYPE>
+ <DTPOSTED></DTPOSTED>
+ <TRNAMT>200.00</TRNAMT>
+ <FITID>2000957249</FITID>
+ <NAME>TestFail2</NAME>
+ </STMTTRN>
+
+ <STMTTRN>
+ <TRNTYPE>OTHER</TRNTYPE>
+ <DTPOSTED>20120231</DTPOSTED>
+ <TRNAMT>200.00</TRNAMT>
+ <FITID>2000957249</FITID>
+ <NAME>TestFail2</NAME>
+ </STMTTRN>
+
+ </BANKTRANLIST>
+
+ <LEDGERBAL>
+ <BALAMT>0</BALAMT>
+ <DTASOF>20110614</DTASOF>
+ </LEDGERBAL>
+ </STMTRS>
+ </STMTTRNRS>
+ </BANKMSGSRSV1>
+</OFX>
diff --git a/tests/fixtures/fail_nice/decimal_error.ofx b/tests/fixtures/fail_nice/decimal_error.ofx
new file mode 100644
index 0000000..4389f6c
--- /dev/null
+++ b/tests/fixtures/fail_nice/decimal_error.ofx
@@ -0,0 +1,51 @@
+
+
+<OFX>
+ <SIGNONMSGSRSV1>
+ <SONRS>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ </STATUS>
+ <DTSERVER>20110614</DTSERVER>
+ <LANGUAGE></LANGUAGE>
+ </SONRS>
+ </SIGNONMSGSRSV1>
+ <BANKMSGSRSV1>
+ <STMTTRNRS>
+ <TRNUID>1</TRNUID>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ </STATUS>
+ <STMTRS>
+ <CURDEF>CAD
+ <BANKACCTFROM>
+ <BANKID>123845030</BANKID>
+ <ACCTID>192639749</ACCTID>
+ <ACCTTYPE>CHECKING</ACCTTYPE>
+ </BANKACCTFROM>
+
+ <BANKTRANLIST>
+ <DTSTART>20110412</DTSTART>
+ <DTEND>20110614</DTEND>
+
+
+ <STMTTRN>
+ <TRNTYPE>OTHER</TRNTYPE>
+ <DTPOSTED>201120000000</DTPOSTED>
+ <TRNAMT>$120</TRNAMT>
+ <FITID>2000957249</FITID>
+ <NAME>Fail1</NAME>
+ </STMTTRN>
+
+ </BANKTRANLIST>
+
+ <LEDGERBAL>
+ <BALAMT>0</BALAMT>
+ <DTASOF>20110614</DTASOF>
+ </LEDGERBAL>
+ </STMTRS>
+ </STMTTRNRS>
+ </BANKMSGSRSV1>
+</OFX>
diff --git a/tests/fixtures/fail_nice/empty_balance.ofx b/tests/fixtures/fail_nice/empty_balance.ofx
new file mode 100644
index 0000000..bf76b48
--- /dev/null
+++ b/tests/fixtures/fail_nice/empty_balance.ofx
@@ -0,0 +1,48 @@
+<OFX>
+ <SIGNONMSGSRSV1>
+ <SONRS>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ </STATUS>
+ <DTSERVER>20110614</DTSERVER>
+ <LANGUAGE></LANGUAGE>
+ </SONRS>
+ </SIGNONMSGSRSV1>
+ <BANKMSGSRSV1>
+ <STMTTRNRS>
+ <TRNUID>1</TRNUID>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ </STATUS>
+ <STMTRS>
+ <CURDEF>CAD
+ <BANKACCTFROM>
+ <BANKID>123845030</BANKID>
+ <ACCTID>192639749</ACCTID>
+ <ACCTTYPE>CHECKING</ACCTTYPE>
+ </BANKACCTFROM>
+ <BANKTRANLIST>
+ <DTSTART>20110412</DTSTART>
+ <DTEND>20110614</DTEND>
+ <STMTTRN>
+ <TRNTYPE>OTHER</TRNTYPE>
+ <DTPOSTED>20110308020000</DTPOSTED>
+ <TRNAMT>120</TRNAMT>
+ <FITID>2000957249</FITID>
+ <NAME>Foobar</NAME>
+ </STMTTRN>
+ </BANKTRANLIST>
+ <LEDGERBAL>
+ <BALAMT> </BALAMT>
+ <DTASOF>20110614</DTASOF>
+ </LEDGERBAL>
+ <AVAILBAL>
+ <BALAMT></BALAMT>
+ <DTASOF>20110614</DTASOF>
+ </AVAILBAL>
+ </STMTRS>
+ </STMTTRNRS>
+ </BANKMSGSRSV1>
+</OFX>
diff --git a/tests/fixtures/fidelity.ofx b/tests/fixtures/fidelity.ofx
new file mode 100644
index 0000000..de8e580
--- /dev/null
+++ b/tests/fixtures/fidelity.ofx
@@ -0,0 +1,11 @@
+OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET:1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0
+
+<OFX><SIGNONMSGSRSV1><SONRS><STATUS><CODE>0<SEVERITY>INFO<MESSAGE>SUCCESS</STATUS><DTSERVER>20120908190849.317[-4:EDT]<LANGUAGE>ENG<FI><ORG>fidelity.com<FID>7776</FI></SONRS></SIGNONMSGSRSV1><INVSTMTMSGSRSV1><INVSTMTTRNRS><TRNUID>a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0<STATUS><CODE>0<SEVERITY>INFO<MESSAGE>SUCCESS</STATUS><INVSTMTRS><DTASOF>20120908033034.000[-4:EDT]<CURDEF>USD<INVACCTFROM><BROKERID>fidelity.com<ACCTID>01234567890</INVACCTFROM><INVTRANLIST><DTSTART>20120710000000.000[-4:EDT]<DTEND>20120908190849.555[-4:EDT]<BUYSTOCK><INVBUY><INVTRAN><FITID>0123456789020201120120720<DTTRADE>20120720000000.000[-4:EDT]<MEMO>YOU BOUGHT</INVTRAN><SECID><UNIQUEID>458140100<UNIQUEIDTYPE>CUSIP</SECID><UNITS>+0000000000100.00000<UNITPRICE>000000025.635000000<COMMISSION>+00000000000007.9500<FEES>+00000000000000.0000<TOTAL>-00000000002571.4500<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY><SUBACCTSEC>CASH<SUBACCTFUND>CASH</INVBUY><BUYTYPE>BUY </BUYSTOCK><BUYSTOCK><INVBUY><INVTRAN><FITID>0123456789020901120120727<DTTRADE>20120727000000.000[-4:EDT]<MEMO>YOU BOUGHT</INVTRAN><SECID><UNIQUEID>G7945E105<UNIQUEIDTYPE>CUSIP</SECID><UNITS>+0000000000128.00000<UNITPRICE>000000039.390900000<COMMISSION>+00000000000007.9500<FEES>+00000000000000.0000<TOTAL>-00000000005049.9900<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY><SUBACCTSEC>CASH<SUBACCTFUND>CASH</INVBUY><BUYTYPE>BUY </BUYSTOCK><BUYSTOCK><INVBUY><INVTRAN><FITID>0123456789020901220120727<DTTRADE>20120727000000.000[-4:EDT]<MEMO>YOU BOUGHT</INVTRAN><SECID><UNIQUEID>431571108<UNIQUEIDTYPE>CUSIP</SECID><UNITS>+0000000000115.00000<UNITPRICE>000000017.250000000<COMMISSION>+00000000000007.9500<FEES>+00000000000000.0000<TOTAL>-00000000001991.7000<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY><SUBACCTSEC>CASH<SUBACCTFUND>CASH</INVBUY><BUYTYPE>BUY </BUYSTOCK><BUYSTOCK><INVBUY><INVTRAN><FITID>0123456789021301120120731<DTTRADE>20120731000000.000[-4:EDT]<MEMO>YOU BOUGHT</INVTRAN><SECID><UNIQUEID>19421R200<UNIQUEIDTYPE>CUSIP</SECID><UNITS>+0000000000069.00000<UNITPRICE>000000014.469900000<COMMISSION>+00000000000007.9500<FEES>+00000000000000.0000<TOTAL>-00000000001006.3700<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY><SUBACCTSEC>CASH<SUBACCTFUND>CASH</INVBUY><BUYTYPE>BUY </BUYSTOCK><BUYSTOCK><INVBUY><INVTRAN><FITID>0123456789021301620120731<DTTRADE>20120731000000.000[-4:EDT]<MEMO>YOU BOUGHT</INVTRAN><SECID><UNIQUEID>98417P105<UNIQUEIDTYPE>CUSIP</SECID><UNITS>+0000000000386.00000<UNITPRICE>000000002.588700000<COMMISSION>+00000000000007.9500<FEES>+00000000000000.0000<TOTAL>-00000000001007.1900<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY><SUBACCTSEC>CASH<SUBACCTFUND>CASH</INVBUY><BUYTYPE>BUY </BUYSTOCK><BUYSTOCK><INVBUY><INVTRAN><FITID>0123456789023501220120820<DTTRADE>20120820000000.000[-4:EDT]<MEMO>REINVESTMENT</INVTRAN><SECID><UNIQUEID>98417P105<UNIQUEIDTYPE>CUSIP</SECID><UNITS>+0000000000004.90900<UNITPRICE>000000002.947400000<COMMISSION>+00000000000000.0000<FEES>+00000000000000.0000<TOTAL>-00000000000014.4700<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY><SUBACCTSEC>CASH<SUBACCTFUND>CASH</INVBUY><BUYTYPE>BUY </BUYSTOCK><BUYSTOCK><INVBUY><INVTRAN><FITID>0123456789024401120120831<DTTRADE>20120831000000.000[-4:EDT]<MEMO>REINVESTMENT</INVTRAN><SECID><UNIQUEID>19421R200<UNIQUEIDTYPE>CUSIP</SECID><UNITS>+0000000000001.57300<UNITPRICE>000000014.257000000<COMMISSION>+00000000000000.0000<FEES>+00000000000000.0000<TOTAL>-00000000000022.4300<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY><SUBACCTSEC>CASH<SUBACCTFUND>CASH</INVBUY><BUYTYPE>BUY </BUYSTOCK><BUYSTOCK><INVBUY><INVTRAN><FITID>0123456789024801120120901<DTTRADE>20120901000000.000[-4:EDT]<MEMO>REINVESTMENT</INVTRAN><SECID><UNIQUEID>458140100<UNIQUEIDTYPE>CUSIP</SECID><UNITS>+0000000000000.91100<UNITPRICE>000000024.705500000<COMMISSION>+00000000000000.0000<FEES>+00000000000000.0000<TOTAL>-00000000000022.5000<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY><SUBACCTSEC>CASH<SUBACCTFUND>CASH</INVBUY><BUYTYPE>BUY </BUYSTOCK><INCOME> <INVTRAN><FITID>0123456789021301520120731<DTTRADE>20120731000000.000[-4:EDT]<MEMO>DIVIDEND RECEIVED</INVTRAN><SECID><UNIQUEID>78462F103<UNIQUEIDTYPE>CUSIP</SECID><INCOMETYPE>DIV<TOTAL>+00000000000005.5300<SUBACCTSEC>CASH<SUBACCTFUND>CASH<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY> </INCOME><INCOME> <INVTRAN><FITID>0123456789023501320120820<DTTRADE>20120820000000.000[-4:EDT]<MEMO>DIVIDEND RECEIVED</INVTRAN><SECID><UNIQUEID>98417P105<UNIQUEIDTYPE>CUSIP</SECID><INCOMETYPE>DIV<TOTAL>+00000000000015.4400<SUBACCTSEC>CASH<SUBACCTFUND>CASH<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY> </INCOME><INCOME> <INVTRAN><FITID>0123456789024401220120831<DTTRADE>20120831000000.000[-4:EDT]<MEMO>DIVIDEND RECEIVED</INVTRAN><SECID><UNIQUEID>19421R200<UNIQUEIDTYPE>CUSIP</SECID><INCOMETYPE>DIV<TOTAL>+00000000000022.4300<SUBACCTSEC>CASH<SUBACCTFUND>CASH<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY> </INCOME><INCOME> <INVTRAN><FITID>0123456789024801220120901<DTTRADE>20120901000000.000[-4:EDT]<MEMO>DIVIDEND RECEIVED</INVTRAN><SECID><UNIQUEID>458140100<UNIQUEIDTYPE>CUSIP</SECID><INCOMETYPE>DIV<TOTAL>+00000000000022.5000<SUBACCTSEC>CASH<SUBACCTFUND>CASH<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY> </INCOME><SELLSTOCK><INVSELL><INVTRAN><FITID>0123456789020901320120727<DTTRADE>20120727000000.000[-4:EDT]<MEMO>YOU SOLD</INVTRAN><SECID><UNIQUEID>78462F103<UNIQUEIDTYPE>CUSIP</SECID><UNITS>-0000000000008.00000<UNITPRICE>000000137.160000000<COMMISSION>+00000000000007.9500<FEES>+00000000000000.0000<TOTAL>+00000000001089.3000<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY><SUBACCTSEC>CASH<SUBACCTFUND>CASH</INVSELL><SELLTYPE>SELL </SELLSTOCK><SELLSTOCK><INVSELL><INVTRAN><FITID>0123456789021401420120801<DTTRADE>20120801000000.000[-4:EDT]<MEMO>IN LIEU OF FRX SHARE</INVTRAN><SECID><UNIQUEID>78462F103<UNIQUEIDTYPE>CUSIP</SECID><UNITS>-0000000000000.03500<UNITPRICE>000000137.142857143<COMMISSION>+00000000000000.0000<FEES>+00000000000000.0000<TOTAL>+00000000000004.8000<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY><SUBACCTSEC>CASH<SUBACCTFUND>CASH</INVSELL><SELLTYPE>SELL </SELLSTOCK><INVBANKTRAN> <STMTTRN><TRNTYPE>DEP<DTPOSTED>20120731000000.000[-4:EDT]<TRNAMT>+00000000000000.2400<FITID>0123456789021301320120731<NAME>INTEREST EARNED<MEMO>INTEREST EARNED<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY> </STMTTRN><SUBACCTFUND>CASH </INVBANKTRAN><INVBANKTRAN> <STMTTRN><TRNTYPE>OTHER<DTPOSTED>20120820000000.000[-4:EDT]<TRNAMT>-00000000000000.9700<FITID>0123456789023501120120820<NAME>LATE SETTLEMENT FEE<MEMO>LATE SETTLEMENT FEE<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY> </STMTTRN><SUBACCTFUND>CASH </INVBANKTRAN><INVBANKTRAN> <STMTTRN><TRNTYPE>DEP<DTPOSTED>20120831000000.000[-4:EDT]<TRNAMT>+00000000000000.1600<FITID>0123456789024401420120831<NAME>INTEREST EARNED<MEMO>INTEREST EARNED<CURRENCY><CURRATE>1.00<CURSYM>USD</CURRENCY> </STMTTRN><SUBACCTFUND>CASH </INVBANKTRAN></INVTRANLIST><INVPOSLIST><POSSTOCK><INVPOS><SECID><UNIQUEID>G7945E105<UNIQUEIDTYPE>CUSIP</SECID><HELDINACCT>CASH<POSTYPE>LONG<UNITS>128.00000<UNITPRICE>40.8700000<MKTVAL>+00000005231.36<DTPRICEASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.0<CURSYM>USD</CURRENCY></INVPOS></POSSTOCK><POSSTOCK><INVPOS><SECID><UNIQUEID>19421R200<UNIQUEIDTYPE>CUSIP</SECID><HELDINACCT>CASH<POSTYPE>LONG<UNITS>70.57300<UNITPRICE>14.3200000<MKTVAL>+00000001010.60<DTPRICEASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.0<CURSYM>USD</CURRENCY></INVPOS></POSSTOCK><POSSTOCK><INVPOS><SECID><UNIQUEID>431571108<UNIQUEIDTYPE>CUSIP</SECID><HELDINACCT>CASH<POSTYPE>LONG<UNITS>115.00000<UNITPRICE>18.9300000<MKTVAL>+00000002176.95<DTPRICEASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.0<CURSYM>USD</CURRENCY></INVPOS></POSSTOCK><POSSTOCK><INVPOS><SECID><UNIQUEID>458140100<UNIQUEIDTYPE>CUSIP</SECID><HELDINACCT>CASH<POSTYPE>LONG<UNITS>100.91100<UNITPRICE>24.1900000<MKTVAL>+00000002441.03<DTPRICEASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.0<CURSYM>USD</CURRENCY></INVPOS></POSSTOCK><POSSTOCK><INVPOS><SECID><UNIQUEID>756577102<UNIQUEIDTYPE>CUSIP</SECID><HELDINACCT>CASH<POSTYPE>LONG<UNITS>50.00000<UNITPRICE>59.1500000<MKTVAL>+00000002957.50<DTPRICEASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.0<CURSYM>USD</CURRENCY></INVPOS></POSSTOCK><POSSTOCK><INVPOS><SECID><UNIQUEID>98417P105<UNIQUEIDTYPE>CUSIP</SECID><HELDINACCT>CASH<POSTYPE>LONG<UNITS>390.90900<UNITPRICE>2.8200000<MKTVAL>+00000001102.36<DTPRICEASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.0<CURSYM>USD</CURRENCY></INVPOS></POSSTOCK></INVPOSLIST><INVBAL><AVAILCASH>18073.98<MARGINBALANCE>+00000000000.00<SHORTBALANCE>+00000000000.00<BUYPOWER>+00000000000.00<BALLIST><BAL><NAME>Networth<DESC>The net market value of all long and short positions in the account<BALTYPE>DOLLAR<VALUE>32993.79<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Margin Equity<DESC>The margin market value less any margin debit balance<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Margin Equity Percentage<DESC>Margin equity / market value of long and short positions<BALTYPE>PERCENT<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Cash Debit Balance<DESC>Cash Debit Balance<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Total Money Markets<DESC>The total value of all money market positions in the cash account<BALTYPE>DOLLAR<VALUE>18073.98<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>House Surplus<DESC>Equity amount above house requirements<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>NYSE Surplus<DESC>Equity amount above exchange requirements<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Federal Surplus<DESC>Amount above federal requirements<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Buying Power - Equities<DESC>Amount of equities you can buy on margin without generating a margin call<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Buying Power - Municipal Bonds<DESC>Amount of municipal bonds you can buy on margin without generating a margin call<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Buying Power - Government Bonds<DESC>Amount of government bonds you can buy on margin without generating a margin cal<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Buying Power - Corporate Bonds<DESC>Amount you can buy of corporate bonds on margin with no margin call<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Option Market Value<DESC>The market value of all options in the account<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Option In The Money Amount<DESC>The in-the-money amount on covered options<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Cash Market value<DESC>Total value of all cash account positions<BALTYPE>DOLLAR<VALUE>14919.8<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Margin Market Value<DESC>Total value of positions in margin less in-the-money amount of covered options<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Short Market Value<DESC>Total value of short positions less in-the-money amount of covered put options<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL><BAL><NAME>Available to Borrow<DESC>Cash amount that can be borrowed without generating a margin call<BALTYPE>DOLLAR<VALUE>0.0<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD</CURRENCY></BAL></BALLIST></INVBAL></INVSTMTRS></INVSTMTTRNRS></INVSTMTMSGSRSV1><SECLISTMSGSRSV1><SECLIST><STOCKINFO><SECINFO><SECID><UNIQUEID>G7945E105<UNIQUEIDTYPE>CUSIP</SECID><SECNAME>SEADRILL LTD USD2<TICKER>SDRL<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD </CURRENCY></SECINFO><STOCKTYPE>COMMON<DTYIELDASOF>20120908033034.000[-4:EDT]</STOCKINFO><STOCKINFO><SECINFO><SECID><UNIQUEID>19421R200<UNIQUEIDTYPE>CUSIP</SECID><SECNAME>COLLECTORS UNIVERSE INC<TICKER>CLCT<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD </CURRENCY></SECINFO><STOCKTYPE>COMMON<DTYIELDASOF>20120908033034.000[-4:EDT]</STOCKINFO><STOCKINFO><SECINFO><SECID><UNIQUEID>431571108<UNIQUEIDTYPE>CUSIP</SECID><SECNAME>HILLENBRAND INC COM<TICKER>HI<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD </CURRENCY></SECINFO><STOCKTYPE>COMMON<DTYIELDASOF>20120908033034.000[-4:EDT]</STOCKINFO><STOCKINFO><SECINFO><SECID><UNIQUEID>458140100<UNIQUEIDTYPE>CUSIP</SECID><SECNAME>INTEL CORP<TICKER>INTC<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD </CURRENCY></SECINFO><STOCKTYPE>COMMON<DTYIELDASOF>20120908033034.000[-4:EDT]</STOCKINFO><STOCKINFO><SECINFO><SECID><UNIQUEID>756577102<UNIQUEIDTYPE>CUSIP</SECID><SECNAME>RED HAT INC<TICKER>RHT<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD </CURRENCY></SECINFO><STOCKTYPE>COMMON<DTYIELDASOF>20120908033034.000[-4:EDT]</STOCKINFO><STOCKINFO><SECINFO><SECID><UNIQUEID>98417P105<UNIQUEIDTYPE>CUSIP</SECID><SECNAME>XINYUAN REAL ESTATE ADR EACH REPR 2 ORD SHS<TICKER>XIN<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD </CURRENCY></SECINFO><STOCKTYPE>COMMON<DTYIELDASOF>20120908033034.000[-4:EDT]</STOCKINFO><STOCKINFO><SECINFO><SECID><UNIQUEID>78462F103<UNIQUEIDTYPE>CUSIP</SECID><SECNAME>SPDR S&amp;P 500 ETF TRUST UNIT SER 1 S&amp;P<TICKER>SPY<DTASOF>20120908033034.000[-4:EDT]<CURRENCY><CURRATE>1.000<CURSYM>USD </CURRENCY></SECINFO><STOCKTYPE>COMMON<DTYIELDASOF>20120908033034.000[-4:EDT]</STOCKINFO></SECLIST></SECLISTMSGSRSV1></OFX>
diff --git a/tests/fixtures/investment_medium.ofx b/tests/fixtures/investment_medium.ofx
new file mode 100644
index 0000000..87f6eba
--- /dev/null
+++ b/tests/fixtures/investment_medium.ofx
@@ -0,0 +1,119 @@
+OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET:1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:NONE
+<OFX>
+ <SIGNONMSGSRSV1>
+ <SONRS>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ </STATUS>
+ <DTSERVER>20091217162416.000[-:EST]</DTSERVER>
+ <LANGUAGE>ENG</LANGUAGE>
+ <FI>
+ <ORG>REDACTEDINC-US</ORG>
+ <FID>1234</FID>
+ </FI>
+ </SONRS>
+ </SIGNONMSGSRSV1>
+ <INVSTMTMSGSRSV1>
+ <INVSTMTTRNRS>
+ <TRNUID>0</TRNUID>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ </STATUS>
+ <INVSTMTRS>
+ <DTASOF>20091215202000.000[-4:EST]</DTASOF>
+ <CURDEF>CAD</CURDEF>
+ <INVACCTFROM>
+ <BROKERID>404</BROKERID>
+ <ACCTID>ABC123</ACCTID>
+ </INVACCTFROM>
+ <INVTRANLIST>
+ <DTSTART>20091214202000.000[-5:EST]</DTSTART>
+ <DTEND>20091215202000.000[-5:EST]</DTEND>
+ <INVBANKTRAN>
+ <STMTTRN>
+ <TRNTYPE>DEBIT</TRNTYPE>
+ <DTPOSTED>20091215202000.000[-4:EST]</DTPOSTED>
+ <TRNAMT>-3.65</TRNAMT>
+ <FITID>20091215.U489357.e.USD.1510480481</FITID>
+ <MEMO>CASH TRADE: AUD.USD</MEMO>
+ <CURRENCY>
+ <CURRATE>1.06</CURRATE>
+ <CURSYM>USD</CURSYM>
+ </CURRENCY>
+ </STMTTRN>
+ <SUBACCTFUND>CASH</SUBACCTFUND>
+ </INVBANKTRAN>
+ <INVBANKTRAN>
+ <STMTTRN>
+ <TRNTYPE>CREDIT</TRNTYPE>
+ <DTPOSTED>20091215202000.000[-4:EST]</DTPOSTED>
+ <TRNAMT>3.35</TRNAMT>
+ <FITID>20091215.U489357.e.USD.1510982018</FITID>
+ <MEMO>CASH TRADE: AUD.USD</MEMO>
+ <CURRENCY>
+ <CURRATE>1.06</CURRATE>
+ <CURSYM>USD</CURSYM>
+ </CURRENCY>
+ </STMTTRN>
+ <SUBACCTFUND>CASH</SUBACCTFUND>
+ </INVBANKTRAN>
+ <INVBANKTRAN>
+ <STMTTRN>
+ <TRNTYPE>DEBIT</TRNTYPE>
+ <DTPOSTED>20091215202000.000[-4:EST]</DTPOSTED>
+ <TRNAMT>-3.65</TRNAMT>
+ <FITID>20091215.U489357.e.USD.1511863617</FITID>
+ <MEMO>CASH TRADE: AUD.USD</MEMO>
+ <CURRENCY>
+ <CURRATE>1.06</CURRATE>
+ <CURSYM>USD</CURSYM>
+ </CURRENCY>
+ </STMTTRN>
+ <SUBACCTFUND>CASH</SUBACCTFUND>
+ </INVBANKTRAN>
+ </INVTRANLIST>
+ <INVBAL>
+ <AVAILCASH>1.00</AVAILCASH>
+ <MARGINBALANCE>0</MARGINBALANCE>
+ <SHORTBALANCE>0</SHORTBALANCE>
+ <BALLIST>
+ <BAL>
+ <NAME>ENDING CASH</NAME>
+ <DESC>ENDING CASH BALANCE</DESC>
+ <BALTYPE>NUMBER</BALTYPE>
+ <VALUE>2.00</VALUE>
+ </BAL>
+ <BAL>
+ <NAME>STOCK VALUE</NAME>
+ <DESC>TOTAL EQUITY IN STOCKS</DESC>
+ <BALTYPE>NUMBER</BALTYPE>
+ <VALUE>0.00</VALUE>
+ </BAL>
+ <BAL>
+ <NAME>OPTION VALUE</NAME>
+ <DESC>TOTAL EQUITY IN OPTIONS</DESC>
+ <BALTYPE>NUMBER</BALTYPE>
+ <VALUE>0.00</VALUE>
+ </BAL>
+ <BAL>
+ <NAME>IBGROUPNOTES VALUE</NAME>
+ <DESC>TOTAL EQUITY IN IBGROUPNOTES</DESC>
+ <BALTYPE>NUMBER</BALTYPE>
+ <VALUE>0.00</VALUE>
+ </BAL>
+ </BALLIST>
+ </INVBAL>
+ </INVSTMTRS>
+ </INVSTMTTRNRS>
+ </INVSTMTMSGSRSV1>
+</OFX>
diff --git a/tests/fixtures/multiple_accounts.ofx b/tests/fixtures/multiple_accounts.ofx
new file mode 100644
index 0000000..853c788
--- /dev/null
+++ b/tests/fixtures/multiple_accounts.ofx
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?OFX OFXHEADER="200" VERSION="211" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>
+<OFX>
+ <SIGNONMSGSRSV1>
+ <SONRS>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ <MESSAGE>The operation succeeded.</MESSAGE>
+ </STATUS>
+ <DTSERVER>20120603203135.547[-7:PDT]</DTSERVER>
+ <LANGUAGE>ENG</LANGUAGE>
+ <FI>
+ <ORG>blah</ORG>
+ <FID>1000</FID>
+ </FI>
+ </SONRS>
+ </SIGNONMSGSRSV1>
+ <BANKMSGSRSV1>
+ <STMTTRNRS>
+ <TRNUID>1001</TRNUID>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ </STATUS>
+ <STMTRS>
+ <CURDEF>USD</CURDEF>
+ <BANKACCTFROM>
+ <BANKID>123</BANKID>
+ <BRANCHID>00</BRANCHID>
+ <ACCTID>9100</ACCTID>
+ <ACCTTYPE>CHECKING</ACCTTYPE>
+ </BANKACCTFROM>
+ <LEDGERBAL>
+ <BALAMT>111</BALAMT>
+ <DTASOF>20120603133220.000[-7:PDT]</DTASOF>
+ </LEDGERBAL>
+ </STMTRS>
+ </STMTTRNRS>
+ <STMTTRNRS>
+ <TRNUID>1002</TRNUID>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ </STATUS>
+ <STMTRS>
+ <CURDEF>USD</CURDEF>
+ <BANKACCTFROM>
+ <BANKID>123</BANKID>
+ <BRANCHID>00</BRANCHID>
+ <ACCTID>9200</ACCTID>
+ <ACCTTYPE>SAVINGS</ACCTTYPE>
+ </BANKACCTFROM>
+ <LEDGERBAL>
+ <BALAMT>222</BALAMT>
+ <DTASOF>20120603133220.000[-7:PDT]</DTASOF>
+ </LEDGERBAL>
+ </STMTRS>
+ </STMTTRNRS>
+ </BANKMSGSRSV1>
+</OFX>
diff --git a/tests/fixtures/multiple_accounts2.ofx b/tests/fixtures/multiple_accounts2.ofx
new file mode 100644
index 0000000..853c788
--- /dev/null
+++ b/tests/fixtures/multiple_accounts2.ofx
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?OFX OFXHEADER="200" VERSION="211" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>
+<OFX>
+ <SIGNONMSGSRSV1>
+ <SONRS>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ <MESSAGE>The operation succeeded.</MESSAGE>
+ </STATUS>
+ <DTSERVER>20120603203135.547[-7:PDT]</DTSERVER>
+ <LANGUAGE>ENG</LANGUAGE>
+ <FI>
+ <ORG>blah</ORG>
+ <FID>1000</FID>
+ </FI>
+ </SONRS>
+ </SIGNONMSGSRSV1>
+ <BANKMSGSRSV1>
+ <STMTTRNRS>
+ <TRNUID>1001</TRNUID>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ </STATUS>
+ <STMTRS>
+ <CURDEF>USD</CURDEF>
+ <BANKACCTFROM>
+ <BANKID>123</BANKID>
+ <BRANCHID>00</BRANCHID>
+ <ACCTID>9100</ACCTID>
+ <ACCTTYPE>CHECKING</ACCTTYPE>
+ </BANKACCTFROM>
+ <LEDGERBAL>
+ <BALAMT>111</BALAMT>
+ <DTASOF>20120603133220.000[-7:PDT]</DTASOF>
+ </LEDGERBAL>
+ </STMTRS>
+ </STMTTRNRS>
+ <STMTTRNRS>
+ <TRNUID>1002</TRNUID>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ </STATUS>
+ <STMTRS>
+ <CURDEF>USD</CURDEF>
+ <BANKACCTFROM>
+ <BANKID>123</BANKID>
+ <BRANCHID>00</BRANCHID>
+ <ACCTID>9200</ACCTID>
+ <ACCTTYPE>SAVINGS</ACCTTYPE>
+ </BANKACCTFROM>
+ <LEDGERBAL>
+ <BALAMT>222</BALAMT>
+ <DTASOF>20120603133220.000[-7:PDT]</DTASOF>
+ </LEDGERBAL>
+ </STMTRS>
+ </STMTTRNRS>
+ </BANKMSGSRSV1>
+</OFX>
diff --git a/tests/fixtures/signon_fail.ofx b/tests/fixtures/signon_fail.ofx
new file mode 100644
index 0000000..d55ef86
--- /dev/null
+++ b/tests/fixtures/signon_fail.ofx
@@ -0,0 +1,11 @@
+OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET:1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:0e1a88e56fc548a1ba2e83bce6323bb6
+
+<OFX><SIGNONMSGSRSV1><SONRS><STATUS><CODE>15500<SEVERITY>ERROR<MESSAGE>Your request could not be processed because you supplied an invalid identification code or your password was incorrect</STATUS><DTSERVER>20130325211209.350[-7:MST]<LANGUAGE>ENG<FI><ORG>AMEX<FID>3101</FI><START.TIME>20130325211209<ERROR.CODE>15500</SONRS></SIGNONMSGSRSV1></OFX>
diff --git a/tests/fixtures/signon_success.ofx b/tests/fixtures/signon_success.ofx
new file mode 100644
index 0000000..82f34a3
--- /dev/null
+++ b/tests/fixtures/signon_success.ofx
@@ -0,0 +1,11 @@
+OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET:1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:3bb6707632b64da196722ef312e6376d
+
+<OFX><SIGNONMSGSRSV1><SONRS><STATUS><CODE>0<SEVERITY>INFO<MESSAGE>Login successful</STATUS><DTSERVER>20130325211405.187[-7:MST]<LANGUAGE>ENG<FI><ORG>AMEX<FID>3101</FI><START.TIME>20130325211405<ORIGIN.ID>FMPWeb</SONRS></SIGNONMSGSRSV1></OFX>
diff --git a/tests/fixtures/signon_success_no_message.ofx b/tests/fixtures/signon_success_no_message.ofx
new file mode 100644
index 0000000..636bffa
--- /dev/null
+++ b/tests/fixtures/signon_success_no_message.ofx
@@ -0,0 +1,11 @@
+OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET:1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:3bb6707632b64da196722ef312e6376d
+
+<OFX><SIGNONMSGSRSV1><SONRS><STATUS><CODE>0<SEVERITY>INFO</STATUS><DTSERVER>20130325211405.187[-7:MST]<LANGUAGE>ENG<FI><ORG>AMEX<FID>3101</FI><START.TIME>20130325211405<ORIGIN.ID>FMPWeb</SONRS></SIGNONMSGSRSV1></OFX>
diff --git a/tests/fixtures/vanguard.ofx b/tests/fixtures/vanguard.ofx
new file mode 100644
index 0000000..47f42a0
--- /dev/null
+++ b/tests/fixtures/vanguard.ofx
@@ -0,0 +1,11 @@
+OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET:1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0
+
+<OFX><SIGNONMSGSRSV1><SONRS><STATUS><CODE>0<SEVERITY>INFO<MESSAGE>Successful Sign On</STATUS><DTSERVER>20110727001702[-5:EST]<LANGUAGE>ENG<DTPROFUP>20010918083000<FI><ORG>The Vanguard Group</FI></SONRS></SIGNONMSGSRSV1><INVSTMTMSGSRSV1><INVSTMTTRNRS><TRNUID>a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0<STATUS><CODE>0<SEVERITY>INFO</STATUS><CLTCOOKIE>4<INVSTMTRS><DTASOF>20110727<CURDEF>USD<INVACCTFROM><BROKERID>vanguard.com<ACCTID>01234567890</INVACCTFROM><INVTRANLIST><DTSTART>20110625160000.000[-5:EST]<DTEND>20110727160000.000[-5:EST]<SELLMF><INVSELL><INVTRAN><FITID>01234567890.0123.07152011.0<DTTRADE>20110715160000.000[-5:EST]<DTSETTLE>20110715160000.000[-5:EST]<MEMO>THIS IS A MEMO</INVTRAN><SECID><UNIQUEID>012345678<UNIQUEIDTYPE>CUSIP</SECID><UNITS>-42.123<UNITPRICE>100.00<TOTAL>4212.3<SUBACCTSEC>CASH<SUBACCTFUND>OTHER</INVSELL><SELLTYPE>SELL</SELLMF></INVTRANLIST><INVPOSLIST><POSMF><INVPOS><SECID><UNIQUEID>012345678<UNIQUEIDTYPE>CUSIP</SECID><HELDINACCT>OTHER<POSTYPE>LONG<UNITS>102.0<UNITPRICE>100.00<MKTVAL>10200.0<DTPRICEASOF>20110726160000.000[-5:EST]<MEMO>Price as of date based on closing price</INVPOS><REINVDIV>Y<REINVCG>Y</POSMF><POSMF><INVPOS><SECID><UNIQUEID>012345678<UNIQUEIDTYPE>CUSIP</SECID><HELDINACCT>OTHER<POSTYPE>LONG<UNITS>142.2<UNITPRICE>100.42<MKTVAL>14279.72<DTPRICEASOF>20110726160000.000[-5:EST]<MEMO>Price as of date based on closing price</INVPOS><REINVDIV>Y<REINVCG>Y</POSMF></INVPOSLIST></INVSTMTRS></INVSTMTTRNRS></INVSTMTMSGSRSV1><SECLISTMSGSRSV1><SECLIST><MFINFO><SECINFO><SECID><UNIQUEID>012345678<UNIQUEIDTYPE>CUSIP</SECID><SECNAME>Name of the security<TICKER>VFINX<FIID>0122<UNITPRICE>54.0<MEMO>Price as of date based on closing price</SECINFO><MFTYPE>OPENEND</MFINFO><MFINFO><SECINFO><SECID><UNIQUEID>012345678<UNIQUEIDTYPE>CUSIP</SECID><SECNAME>Name of share<TICKER>VFIAX<FIID>0123<UNITPRICE>123.45<MEMO>Price as of date based on closing price</SECINFO><MFTYPE>OPENEND</MFINFO></SECLIST></SECLISTMSGSRSV1></OFX> \ No newline at end of file
diff --git a/tests/support.py b/tests/support.py
new file mode 100644
index 0000000..fe381f5
--- /dev/null
+++ b/tests/support.py
@@ -0,0 +1,9 @@
+import os
+
+
+def open_file(filename):
+ ''' Load a file from the fixtures directory. '''
+ path = 'fixtures/' + filename
+ if ('tests' in os.listdir('.')):
+ path = 'tests/' + path
+ return open(path, mode='rb')
diff --git a/tests/test_parse.py b/tests/test_parse.py
new file mode 100644
index 0000000..35260ce
--- /dev/null
+++ b/tests/test_parse.py
@@ -0,0 +1,623 @@
+from __future__ import absolute_import
+
+from ofxparse.ofxparse import soup_maker
+from datetime import datetime, timedelta
+from decimal import Decimal
+from unittest import TestCase
+import sys
+sys.path.append('..')
+
+import six
+
+from .support import open_file
+from ofxparse import OfxParser, AccountType, Account, Statement, Transaction
+from ofxparse.ofxparse import OfxFile, OfxPreprocessedFile, OfxParserException
+
+class TestOfxPreprocessedFile(TestCase):
+
+ def testPreprocess(self):
+ fh = six.BytesIO(six.b("""OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET:1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:NONE
+
+<OFX><DTASOF><![CDATA[></tricky]]><LEAVE ALONE><VAL.UE>a<VAL_UE>b<TE_ST></TE_ST><TE.ST></TE.ST><INVBAL><BALLIST><BAL><NAME>Net<DTASOF>2222</BAL><BAL><NAME>Gross<DTASOF>3333</BAL></BALLIST></INVBAL></OFX>
+"""))
+ expect = """OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET:1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:NONE
+
+<OFX><DTASOF><![CDATA[></tricky]]><LEAVE ALONE></DTASOF><VAL.UE>a</VAL.UE><VAL_UE>b</VAL_UE><TE_ST></TE_ST><TE.ST></TE.ST><INVBAL><BALLIST><BAL><NAME>Net</NAME><DTASOF>2222</DTASOF></BAL><BAL><NAME>Gross</NAME><DTASOF>3333</DTASOF></BAL></BALLIST></INVBAL></OFX>
+"""
+ ofx_file = OfxPreprocessedFile(fh)
+ data = ofx_file.fh.read()
+ self.assertEqual(data,expect)
+
+ def testHeaders(self):
+ expect = {"OFXHEADER": six.u("100"),
+ "DATA": six.u("OFXSGML"),
+ "VERSION": six.u("102"),
+ "SECURITY": None,
+ "ENCODING": six.u("USASCII"),
+ "CHARSET": six.u("1252"),
+ "COMPRESSION": None,
+ "OLDFILEUID": None,
+ "NEWFILEUID": None,
+ }
+ ofx = OfxParser.parse(open_file('bank_medium.ofx'))
+ self.assertEquals(expect, ofx.headers)
+
+ def testUTF8(self):
+ fh = six.BytesIO(six.b("""OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:UNICODE
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:NONE
+
+"""))
+ ofx_file = OfxPreprocessedFile(fh)
+ headers = ofx_file.headers
+ data = ofx_file.fh.read()
+
+ self.assertTrue(type(data) is six.text_type)
+ for key, value in six.iteritems(headers):
+ self.assertTrue(type(key) is six.text_type)
+ self.assertTrue(type(value) is not six.binary_type)
+
+ def testCP1252(self):
+ fh = six.BytesIO(six.b("""OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET: 1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:NONE
+"""))
+ ofx_file = OfxPreprocessedFile(fh)
+ headers = ofx_file.headers
+ result = ofx_file.fh.read()
+
+ self.assertTrue(type(result) is six.text_type)
+ for key, value in six.iteritems(headers):
+ self.assertTrue(type(key) is six.text_type)
+ self.assertTrue(type(value) is not six.binary_type)
+
+ def testUTF8Japanese(self):
+ fh = six.BytesIO(six.b("""OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:UTF-8
+CHARSET:CSUNICODE
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:NONE
+"""))
+ ofx_file = OfxPreprocessedFile(fh)
+ headers = ofx_file.headers
+ result = ofx_file.fh.read()
+
+ self.assertTrue(type(result) is six.text_type)
+ for key, value in six.iteritems(headers):
+ self.assertTrue(type(key) is six.text_type)
+ self.assertTrue(type(value) is not six.binary_type)
+
+ def testBrokenLineEndings(self):
+ fh = six.BytesIO(six.b("OFXHEADER:100\rDATA:OFXSGML\r"))
+ ofx_file = OfxPreprocessedFile(fh)
+ self.assertEquals(len(ofx_file.headers.keys()), 2)
+
+
+
+class TestOfxFile(TestCase):
+ def testHeaders(self):
+ expect = {"OFXHEADER": six.u("100"),
+ "DATA": six.u("OFXSGML"),
+ "VERSION": six.u("102"),
+ "SECURITY": None,
+ "ENCODING": six.u("USASCII"),
+ "CHARSET": six.u("1252"),
+ "COMPRESSION": None,
+ "OLDFILEUID": None,
+ "NEWFILEUID": None,
+ }
+ ofx = OfxParser.parse(open_file('bank_medium.ofx'))
+ self.assertEquals(expect, ofx.headers)
+
+ def testUTF8(self):
+ fh = six.BytesIO(six.b("""OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:UNICODE
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:NONE
+
+"""))
+ ofx_file = OfxFile(fh)
+ headers = ofx_file.headers
+ data = ofx_file.fh.read()
+
+ self.assertTrue(type(data) is six.text_type)
+ for key, value in six.iteritems(headers):
+ self.assertTrue(type(key) is six.text_type)
+ self.assertTrue(type(value) is not six.binary_type)
+
+ def testCP1252(self):
+ fh = six.BytesIO(six.b("""OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:USASCII
+CHARSET: 1252
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:NONE
+"""))
+ ofx_file = OfxFile(fh)
+ headers = ofx_file.headers
+ result = ofx_file.fh.read()
+
+ self.assertTrue(type(result) is six.text_type)
+ for key, value in six.iteritems(headers):
+ self.assertTrue(type(key) is six.text_type)
+ self.assertTrue(type(value) is not six.binary_type)
+
+ def testUTF8Japanese(self):
+ fh = six.BytesIO(six.b("""OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:NONE
+ENCODING:UTF-8
+CHARSET:CSUNICODE
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:NONE
+"""))
+ ofx_file = OfxFile(fh)
+ headers = ofx_file.headers
+ result = ofx_file.fh.read()
+
+ self.assertTrue(type(result) is six.text_type)
+ for key, value in six.iteritems(headers):
+ self.assertTrue(type(key) is six.text_type)
+ self.assertTrue(type(value) is not six.binary_type)
+
+ def testBrokenLineEndings(self):
+ fh = six.BytesIO(six.b("OFXHEADER:100\rDATA:OFXSGML\r"))
+ ofx_file = OfxFile(fh)
+ self.assertEquals(len(ofx_file.headers.keys()), 2)
+
+
+class TestParse(TestCase):
+ def testEmptyFile(self):
+ fh = six.BytesIO(six.b(""))
+ self.assertRaises(OfxParserException, OfxParser.parse, fh)
+
+ def testThatParseWorksWithoutErrors(self):
+ OfxParser.parse(open_file('bank_medium.ofx'))
+
+ def testThatParseFailsIfNothingToParse(self):
+ self.assertRaises(TypeError, OfxParser.parse, None)
+
+ def testThatParseFailsIfAPathIsPassedIn(self):
+ # A file handle should be passed in, not the path.
+ self.assertRaises(RuntimeError, OfxParser.parse, '/foo/bar')
+
+ def testThatParseReturnsAResultWithABankAccount(self):
+ ofx = OfxParser.parse(open_file('bank_medium.ofx'))
+ self.assertTrue(ofx.account is not None)
+
+ def testEverything(self):
+ ofx = OfxParser.parse(open_file('bank_medium.ofx'))
+ self.assertEquals('12300 000012345678', ofx.account.number)
+ self.assertEquals('160000100', ofx.account.routing_number)
+ self.assertEquals('00', ofx.account.branch_id)
+ self.assertEquals('CHECKING', ofx.account.account_type)
+ self.assertEquals(Decimal('382.34'), ofx.account.statement.balance)
+ # Todo: support values in decimal or int form.
+ # self.assertEquals('15',
+ # ofx.bank_account.statement.balance_in_pennies)
+ self.assertEquals(
+ Decimal('682.34'), ofx.account.statement.available_balance)
+ self.assertEquals(
+ datetime(2009, 4, 1), ofx.account.statement.start_date)
+ self.assertEquals(
+ datetime(2009, 5, 23, 12, 20, 17), ofx.account.statement.end_date)
+
+ self.assertEquals(3, len(ofx.account.statement.transactions))
+
+ transaction = ofx.account.statement.transactions[0]
+ self.assertEquals("MCDONALD'S #112", transaction.payee)
+ self.assertEquals('pos', transaction.type)
+ self.assertEquals(Decimal('-6.60'), transaction.amount)
+ # Todo: support values in decimal or int form.
+ # self.assertEquals('15', transaction.amount_in_pennies)
+
+ def testMultipleAccounts(self):
+ ofx = OfxParser.parse(open_file('multiple_accounts2.ofx'))
+ self.assertEquals(2, len(ofx.accounts))
+ self.assertEquals('9100', ofx.accounts[0].number)
+ self.assertEquals('9200', ofx.accounts[1].number)
+ self.assertEquals('123', ofx.accounts[0].routing_number)
+ self.assertEquals('123', ofx.accounts[1].routing_number)
+ self.assertEquals('CHECKING', ofx.accounts[0].account_type)
+ self.assertEquals('SAVINGS', ofx.accounts[1].account_type)
+
+
+class TestStringToDate(TestCase):
+ ''' Test the string to date parser '''
+ def test_bad_format(self):
+ ''' A poorly formatted string should throw a ValueError '''
+
+ bad_string = 'abcdLOL!'
+ self.assertRaises(ValueError, OfxParser.parseOfxDateTime, bad_string)
+
+ bad_but_close_string = '881103'
+ self.assertRaises(ValueError, OfxParser.parseOfxDateTime, bad_string)
+
+ no_month_string = '19881301'
+ self.assertRaises(ValueError, OfxParser.parseOfxDateTime, bad_string)
+
+ def test_parses_correct_time(self):
+ '''Test whether it can parse correct time for some valid time fields'''
+ self.assertEquals(OfxParser.parseOfxDateTime('19881201'),
+ datetime(1988, 12, 1, 0, 0))
+ self.assertEquals(OfxParser.parseOfxDateTime('19881201230100'),
+ datetime(1988, 12, 1, 23, 1))
+ self.assertEquals(OfxParser.parseOfxDateTime('20120229230100'),
+ datetime(2012, 2, 29, 23, 1))
+
+ def test_parses_time_offset(self):
+ ''' Test that we handle GMT offset '''
+ self.assertEquals(OfxParser.parseOfxDateTime('20001201120000 [0:GMT]'),
+ datetime(2000, 12, 1, 12, 0))
+ self.assertEquals(OfxParser.parseOfxDateTime('19991201120000 [1:ITT]'),
+ datetime(1999, 12, 1, 11, 0))
+ self.assertEquals(
+ OfxParser.parseOfxDateTime('19881201230100 [-5:EST]'),
+ datetime(1988, 12, 2, 4, 1))
+ self.assertEquals(
+ OfxParser.parseOfxDateTime('20120229230100 [-6:CAT]'),
+ datetime(2012, 3, 1, 5, 1))
+ self.assertEquals(
+ OfxParser.parseOfxDateTime('20120412120000 [-5.5:XXX]'),
+ datetime(2012, 4, 12, 17, 30))
+ self.assertEquals(
+ OfxParser.parseOfxDateTime('20120412120000 [-5:XXX]'),
+ datetime(2012, 4, 12, 17))
+ self.assertEquals(
+ OfxParser.parseOfxDateTime('20120922230000 [+9:JST]'),
+ datetime(2012, 9, 22, 14, 0))
+
+
+class TestParseStmtrs(TestCase):
+ input = '''
+<STMTRS><CURDEF>CAD<BANKACCTFROM><BANKID>160000100<ACCTID>12300 000012345678<ACCTTYPE>CHECKING</BANKACCTFROM>
+<BANKTRANLIST><DTSTART>20090401<DTEND>20090523122017
+<STMTTRN><TRNTYPE>POS<DTPOSTED>20090401122017.000[-5:EST]<TRNAMT>-6.60<FITID>0000123456782009040100001<NAME>MCDONALD'S #112<MEMO>POS MERCHANDISE;MCDONALD'S #112</STMTTRN>
+</BANKTRANLIST><LEDGERBAL><BALAMT>382.34<DTASOF>20090523122017</LEDGERBAL><AVAILBAL><BALAMT>682.34<DTASOF>20090523122017</AVAILBAL></STMTRS>
+ '''
+
+ def testThatParseStmtrsReturnsAnAccount(self):
+ stmtrs = soup_maker(self.input)
+ account = OfxParser.parseStmtrs(
+ stmtrs.find('stmtrs'), AccountType.Bank)[0]
+ self.assertEquals('12300 000012345678', account.number)
+ self.assertEquals('160000100', account.routing_number)
+ self.assertEquals('CHECKING', account.account_type)
+
+ def testThatReturnedAccountAlsoHasAStatement(self):
+ stmtrs = soup_maker(self.input)
+ account = OfxParser.parseStmtrs(
+ stmtrs.find('stmtrs'), AccountType.Bank)[0]
+ self.assertTrue(hasattr(account, 'statement'))
+
+
+class TestAccount(TestCase):
+ def testThatANewAccountIsValid(self):
+ account = Account()
+ self.assertEquals('', account.number)
+ self.assertEquals('', account.routing_number)
+ self.assertEquals('', account.account_type)
+ self.assertEquals(None, account.statement)
+
+
+class TestParseStatement(TestCase):
+ def testThatParseStatementReturnsAStatement(self):
+ input = '''
+<STMTTRNRS>
+ <TRNUID>20090523122017
+ <STATUS>
+ <CODE>0
+ <SEVERITY>INFO
+ <MESSAGE>OK
+ </STATUS>
+ <STMTRS>
+ <CURDEF>CAD
+ <BANKACCTFROM>
+ <BANKID>160000100
+ <ACCTID>12300 000012345678
+ <ACCTTYPE>CHECKING
+ </BANKACCTFROM>
+ <BANKTRANLIST>
+ <DTSTART>20090401
+ <DTEND>20090523122017
+ <STMTTRN>
+ <TRNTYPE>POS
+ <DTPOSTED>20090401122017.000[-5:EST]
+ <TRNAMT>-6.60
+ <FITID>0000123456782009040100001
+ <NAME>MCDONALD'S #112
+ <MEMO>POS MERCHANDISE;MCDONALD'S #112
+ </STMTTRN>
+ </BANKTRANLIST>
+ <LEDGERBAL>
+ <BALAMT>382.34
+ <DTASOF>20090523122017
+ </LEDGERBAL>
+ <AVAILBAL>
+ <BALAMT>682.34
+ <DTASOF>20090523122017
+ </AVAILBAL>
+ </STMTRS>
+</STMTTRNRS>
+ '''
+ txn = soup_maker(input)
+ statement = OfxParser.parseStatement(txn.find('stmttrnrs'))
+ self.assertEquals(datetime(2009, 4, 1), statement.start_date)
+ self.assertEquals(
+ datetime(2009, 5, 23, 12, 20, 17), statement.end_date)
+ self.assertEquals(1, len(statement.transactions))
+ self.assertEquals(Decimal('382.34'), statement.balance)
+ self.assertEquals(Decimal('682.34'), statement.available_balance)
+
+
+class TestStatement(TestCase):
+ def testThatANewStatementIsValid(self):
+ statement = Statement()
+ self.assertEquals('', statement.start_date)
+ self.assertEquals('', statement.end_date)
+ self.assertEquals(0, len(statement.transactions))
+
+
+class TestParseTransaction(TestCase):
+ def testThatParseTransactionReturnsATransaction(self):
+ input = '''
+<STMTTRN>
+ <TRNTYPE>POS
+ <DTPOSTED>20090401122017.000[-5:EST]
+ <TRNAMT>-6.60
+ <FITID>0000123456782009040100001
+ <NAME>MCDONALD'S #112
+ <MEMO>POS MERCHANDISE;MCDONALD'S #112
+</STMTTRN>
+'''
+ txn = soup_maker(input)
+ transaction = OfxParser.parseTransaction(txn.find('stmttrn'))
+ self.assertEquals('pos', transaction.type)
+ self.assertEquals(datetime(
+ 2009, 4, 1, 12, 20, 17) - timedelta(hours=-5), transaction.date)
+ self.assertEquals(Decimal('-6.60'), transaction.amount)
+ self.assertEquals('0000123456782009040100001', transaction.id)
+ self.assertEquals("MCDONALD'S #112", transaction.payee)
+ self.assertEquals("POS MERCHANDISE;MCDONALD'S #112", transaction.memo)
+
+
+ def testThatParseTransactionWithFieldCheckNum(self):
+ input = '''
+<STMTTRN>
+ <TRNTYPE>DEP
+ <DTPOSTED>20130306
+ <TRNAMT>1000.00
+ <FITID>2013030601009100
+ <CHECKNUM>700
+ <MEMO>DEPOSITO ONLINE
+</STMTTRN>
+'''
+ txn = soup_maker(input)
+ transaction = OfxParser.parseTransaction(txn.find('stmttrn'))
+ self.assertEquals('700', transaction.checknum)
+
+class TestTransaction(TestCase):
+ def testThatAnEmptyTransactionIsValid(self):
+ t = Transaction()
+ self.assertEquals('', t.payee)
+ self.assertEquals('', t.type)
+ self.assertEquals(None, t.date)
+ self.assertEquals(None, t.amount)
+ self.assertEquals('', t.id)
+ self.assertEquals('', t.memo)
+ self.assertEquals('', t.checknum)
+
+
+class TestInvestmentAccount(TestCase):
+ sample = '''
+<?xml version="1.0" encoding="UTF-8" ?>
+<?OFX OFXHEADER="200" VERSION="200" SECURITY="NONE"
+ OLDFILEUID="NONE" NEWFILEUID="NONE" ?>
+<OFX>
+ <INVSTMTMSGSRSV1>
+ <INVSTMTTRNRS>
+ <TRNUID>38737714201101012011062420110624</TRNUID>
+ <STATUS>
+ <CODE>0</CODE>
+ <SEVERITY>INFO</SEVERITY>
+ </STATUS>
+ <INVSTMTRS>
+ </INVSTMTRS>
+ </INVSTMTTRNRS>
+ </INVSTMTMSGSRSV1>
+</OFX>
+'''
+
+ def testThatParseCanCreateAnInvestmentAccount(self):
+ OfxParser.parse(six.BytesIO(six.b(self.sample)))
+ # Success!
+
+
+
+class TestVanguardInvestmentStatement(TestCase):
+ def testForUnclosedTags(self):
+ ofx = OfxParser.parse(open_file('vanguard.ofx'))
+ self.assertTrue(hasattr(ofx, 'account'))
+ self.assertTrue(hasattr(ofx.account, 'statement'))
+ self.assertTrue(hasattr(ofx.account.statement, 'transactions'))
+ self.assertEquals(len(ofx.account.statement.transactions), 1)
+ self.assertEquals(ofx.account.statement.transactions[0].id,
+ '01234567890.0123.07152011.0')
+ self.assertEquals(ofx.account.statement.transactions[0]
+ .tradeDate, datetime(2011, 7, 15, 21))
+ self.assertEquals(ofx.account.statement.transactions[0]
+ .settleDate, datetime(2011, 7, 15, 21))
+ self.assertTrue(hasattr(ofx.account.statement, 'positions'))
+ self.assertEquals(len(ofx.account.statement.positions), 2)
+ self.assertEquals(
+ ofx.account.statement.positions[0].units, Decimal('102.0'))
+
+ def testSecurityListSuccess(self):
+ ofx = OfxParser.parse(open_file('vanguard.ofx'))
+ self.assertEquals(len(ofx.security_list), 2)
+
+
+class TestFidelityInvestmentStatement(TestCase):
+ def testForUnclosedTags(self):
+ ofx = OfxParser.parse(open_file('fidelity.ofx'))
+ self.assertTrue(hasattr(ofx.account.statement, 'positions'))
+ self.assertEquals(len(ofx.account.statement.positions), 6)
+ self.assertEquals(
+ ofx.account.statement.positions[0].units, Decimal('128.0'))
+
+ def testSecurityListSuccess(self):
+ ofx = OfxParser.parse(open_file('fidelity.ofx'))
+ self.assertEquals(len(ofx.security_list), 7)
+
+
+class TestAccountInfoAggregation(TestCase):
+ def testForFourAccounts(self):
+ ofx = OfxParser.parse(open_file('account_listing_aggregation.ofx'))
+ self.assertTrue(hasattr(ofx, 'accounts'))
+ self.assertEquals(len(ofx.accounts), 4)
+
+ # first account
+ account = ofx.accounts[0]
+ self.assertEquals(account.account_type, 'SAVINGS')
+ self.assertEquals(account.desc, 'USAA SAVINGS')
+ self.assertEquals(account.institution.organization, 'USAA')
+ self.assertEquals(account.number, '0000000001')
+ self.assertEquals(account.routing_number, '314074269')
+
+ # second
+ account = ofx.accounts[1]
+ self.assertEquals(account.account_type, 'CHECKING')
+ self.assertEquals(account.desc, 'FOUR STAR CHECKING')
+ self.assertEquals(account.institution.organization, 'USAA')
+ self.assertEquals(account.number, '0000000002')
+ self.assertEquals(account.routing_number, '314074269')
+
+ # third
+ account = ofx.accounts[2]
+ self.assertEquals(account.account_type, 'CREDITLINE')
+ self.assertEquals(account.desc, 'LINE OF CREDIT')
+ self.assertEquals(account.institution.organization, 'USAA')
+ self.assertEquals(account.number, '00000000000003')
+ self.assertEquals(account.routing_number, '314074269')
+
+ # fourth
+ account = ofx.accounts[3]
+ self.assertEquals(account.account_type, '')
+ self.assertEquals(account.desc, 'MY CREDIT CARD')
+ self.assertEquals(account.institution.organization, 'USAA')
+ self.assertEquals(account.number, '4111111111111111')
+
+
+class TestGracefulFailures(TestCase):
+ ''' Test that when fail_fast is False, failures are returned to the
+ caller as warnings and discarded entries in the Statement class.
+ '''
+ def testDateFieldMissing(self):
+ ''' The test file contains three transactions in a single
+ statement.
+
+ They fail due to:
+ 1) No date
+ 2) Empty date
+ 3) Invalid date
+ '''
+ ofx = OfxParser.parse(open_file('fail_nice/date_missing.ofx'), False)
+ self.assertEquals(len(ofx.account.statement.transactions), 0)
+ self.assertEquals(len(ofx.account.statement.discarded_entries), 3)
+ self.assertEquals(len(ofx.account.statement.warnings), 0)
+
+ # Test that it raises an error otherwise.
+ self.assertRaises(OfxParserException, OfxParser.parse,
+ open_file('fail_nice/date_missing.ofx'))
+
+ def testDecimalConversionError(self):
+ ''' The test file contains a transaction that has a poorly formatted
+ decimal number ($20). Test that we catch this.
+ '''
+ ofx = OfxParser.parse(open_file('fail_nice/decimal_error.ofx'), False)
+ self.assertEquals(len(ofx.account.statement.transactions), 0)
+ self.assertEquals(len(ofx.account.statement.discarded_entries), 1)
+
+ # Test that it raises an error otherwise.
+ self.assertRaises(OfxParserException, OfxParser.parse,
+ open_file('fail_nice/decimal_error.ofx'))
+
+ def testEmptyBalance(self):
+ ''' The test file contains empty or blank strings in the balance
+ fields. Fail nicely on those.
+ '''
+ ofx = OfxParser.parse(open_file('fail_nice/empty_balance.ofx'), False)
+ self.assertEquals(len(ofx.account.statement.transactions), 1)
+ self.assertEquals(len(ofx.account.statement.discarded_entries), 0)
+ self.assertFalse(hasattr(ofx.account.statement, 'balance'))
+ self.assertFalse(hasattr(ofx.account.statement, 'available_balance'))
+
+ # Test that it raises an error otherwise.
+ self.assertRaises(OfxParserException, OfxParser.parse,
+ open_file('fail_nice/empty_balance.ofx'))
+
+class TestParseSonrs(TestCase):
+
+ def testSuccess(self):
+ ofx = OfxParser.parse(open_file('signon_success.ofx'), True)
+ self.assertTrue(ofx.signon.success)
+ self.assertEquals(ofx.signon.code, 0)
+ self.assertEquals(ofx.signon.severity, 'INFO')
+ self.assertEquals(ofx.signon.message, 'Login successful')
+
+ ofx = OfxParser.parse(open_file('signon_success_no_message.ofx'), True)
+ self.assertTrue(ofx.signon.success)
+ self.assertEquals(ofx.signon.code, 0)
+ self.assertEquals(ofx.signon.severity, 'INFO')
+ self.assertEquals(ofx.signon.message, '')
+
+ def testFailure(self):
+ ofx = OfxParser.parse(open_file('signon_fail.ofx'), True)
+ self.assertFalse(ofx.signon.success)
+ self.assertEquals(ofx.signon.code, 15500)
+ self.assertEquals(ofx.signon.severity, 'ERROR')
+ self.assertEquals(ofx.signon.message, 'Your request could not be processed because you supplied an invalid identification code or your password was incorrect')
+
+if __name__ == "__main__":
+ import unittest
+ unittest.main()
diff --git a/tests/test_write.py b/tests/test_write.py
new file mode 100644
index 0000000..3366217
--- /dev/null
+++ b/tests/test_write.py
@@ -0,0 +1,17 @@
+from __future__ import absolute_import
+
+from ofxparse import OfxParser as op
+from unittest import TestCase
+import sys
+sys.path.append('..')
+from .support import open_file
+
+class TestOfxWrite(TestCase):
+ def test_write(self):
+ test_file = open_file('fidelity.ofx')
+ ofx_doc = op.parse(test_file)
+ self.assertEqual(str(ofx_doc), "")
+
+if __name__ == "__main__":
+ import unittest
+ unittest.main()