def test_parse_string_None(self): input_string = report_filename = None with self.assertRaises(TypeError): entries, errors, _ = parser.parse_string(input_string) with self.assertRaises(TypeError): entries, errors, _ = parser.parse_string("something", None, report_filename)
def test_run_transformations(self): # Test success case. entries, errors, options_map = parser.parse_string(TEST_INPUT) trans_entries, trans_errors = loader.run_transformations( entries, errors, options_map, None) self.assertEqual(0, len(trans_errors)) # Test an invalid plugin name. entries, errors, options_map = parser.parse_string( 'plugin "invalid.module.name"\n\n' + TEST_INPUT) trans_entries, trans_errors = loader.run_transformations( entries, errors, options_map, None) self.assertEqual(1, len(trans_errors))
def test_print_extracted_entries(self): entries, error, options = parser.parse_string( textwrap.dedent(''' 1970-01-01 * "Test" Assets:Tests 10.00 USD''')) extracted = [ ('/path/to/test.csv', entries), ('/path/to/empty.pdf', []), ] output = io.StringIO() extract.print_extracted_entries(extracted, output) self.assertEqual( output.getvalue(), textwrap.dedent('''\ ;; -*- mode: beancount -*- **** /path/to/test.csv 1970-01-01 * "Test" Assets:Tests 10.00 USD **** /path/to/empty.pdf '''))
def test_bytes_encoded_invalid(self): latin1_bytes = self.test_latin1_string.encode('latin1') entries, errors, _ = parser.parse_string(latin1_bytes, encoding='garbage') self.assertEqual(1, len(errors)) self.assertRegex(errors[0].message, "unknown encoding") self.assertFalse(entries)
def read_string_or_entries(entries_or_str): """Read a string of entries or just entries. Args: entries_or_str: Either a list of directives, or a string containing directives. Returns: A list of directives. """ if isinstance(entries_or_str, str): entries, parse_errors, options_map = parser.parse_string( textwrap.dedent(entries_or_str)) # Don't accept incomplete entries either. if parser.has_auto_postings(entries): raise TestError("Entries in assertions may not use interpolation.") entries, booking_errors = booking.book(entries, options_map) errors = parse_errors + booking_errors # Don't tolerate errors. if errors: oss = io.StringIO() printer.print_errors(errors, file=oss) raise TestError("Unexpected errors in expected: {}".format(oss.getvalue())) else: assert isinstance(entries_or_str, list), "Expecting list: {}".format(entries_or_str) entries = entries_or_str return entries
def test_bytes_encoded_utf8(self): utf8_bytes = self.test_utf8_string.encode('utf8') entries, errors, _ = parser.parse_string(utf8_bytes) self.assertEqual(1, len(entries)) self.assertFalse(errors) # Check that the lexer correctly parsed the UTF8 string. self.assertEqual(self.expected_utf8_string, entries[0].comment)
def test_bytes_encoded_latin1(self): latin1_bytes = self.test_latin1_string.encode('latin1') entries, errors, _ = parser.parse_string(latin1_bytes, encoding='latin1') self.assertEqual(1, len(entries)) self.assertFalse(errors) # Check that the lexer correctly parsed the latin1 string. self.assertEqual(self.expected_latin1_string, entries[0].comment)
def test_print_extracted_entries(self, mock_extract_from_file): entries, _, __ = parser.parse_string(""" 2016-02-01 * "A" Assets:Account1 11.11 USD Assets:Account2 -11.11 USD 2016-02-01 * "B" Assets:Account1 22.22 USD Assets:Account3 -22.22 USD """) mock_extract_from_file.return_value = entries entries[-2].meta[extract.DUPLICATE_META] = True oss = io.StringIO() extract.print_extracted_entries(entries, oss) self.assertEqual(textwrap.dedent("""\ ; 2016-02-01 * "A" ; Assets:Account1 11.11 USD ; Assets:Account2 -11.11 USD 2016-02-01 * "B" Assets:Account1 22.22 USD Assets:Account3 -22.22 USD """).strip(), oss.getvalue().strip())
def test_print_extracted_entries_duplictes(self): entries, error, options = parser.parse_string( textwrap.dedent(''' 1970-01-01 * "Test" Assets:Tests 10.00 USD 1970-01-01 * "Test" Assets:Tests 10.00 USD ''')) # Mark the second entry as duplicate entries[1].meta[extract.DUPLICATE] = True extracted = [ ('/path/to/test.csv', entries), ] output = io.StringIO() extract.print_extracted_entries(extracted, output) self.assertEqual( output.getvalue(), textwrap.dedent('''\ ;; -*- mode: beancount -*- **** /path/to/test.csv 1970-01-01 * "Test" Assets:Tests 10.00 USD ; 1970-01-01 * "Test" ; Assets:Tests 10.00 USD '''))
def test_bytes_encoded_incorrect(self): latin1_bytes = self.test_utf8_string.encode('latin1') entries, errors, _ = parser.parse_string(latin1_bytes) # Check that the lexer failed to convert the string and reported an error. self.assertEqual(1, len(errors)) self.assertRegex(errors[0].message, "^UnicodeDecodeError: 'utf-8' codec ") self.assertFalse(entries)
def test_bytes_encoded_incorrect(self): latin1_bytes = self.test_utf8_string.encode('latin1') entries, errors, _ = parser.parse_string(latin1_bytes) self.assertEqual(1, len(entries)) self.assertFalse(errors) # Check that the lexer failed to convert the string but did not cause # other errors. self.assertNotEqual(self.expected_utf8_string, entries[0].comment)
def test_import_exception(self): # Test an invalid plugin name. entries, errors, options_map = parser.parse_string( 'plugin "invalid.module.name"\n\n' + TEST_INPUT) trans_entries, trans_errors = loader.run_transformations( entries, errors, options_map, None) self.assertEqual(1, len(trans_errors)) self.assertRegex(trans_errors[0].message, "ModuleNotFoundError")
def test_parse_None(self): # None is treated as the empty string... entries, errors, _ = parser.parse_string(None) self.assertEqual(0, len(entries)) self.assertEqual(0, len(errors)) # ...however None in not a valid file like object with self.assertRaises(TypeError): entries, errors, _ = parser.parse_file(None)
def test_extract_with_balance_declared(self): soup, exp_entries = self._extract_with_balance() entries = ofximp.extract(soup, 'test.ofx', '379700001111222', 'Liabilities:CreditCard', '*', ofximp.BalanceType.DECLARED) balance_entries, _, __ = parser.parse_string(""" 2014-01-13 balance Liabilities:CreditCard -2356.38 USD """) self.assertEqualEntries(exp_entries + balance_entries, entries)
def test_two_distinct_balances(self): ofx_contents = clean_xml(""" <OFX> <SIGNONMSGSRSV1> </SIGNONMSGSRSV1> <CREDITCARDMSGSRSV1> <STMTTRNRS> <TRNUID>0 <STMTRS> <CURDEF>USD <ACCTFROM> <ACCTID>379700001111222 </ACCTID> </ACCTFROM> <BANKTRANLIST> </BANKTRANLIST> <LEDGERBAL> <BALAMT>100.00 <DTASOF>20140101000000.000[-7:MST]</DTASOF> </BALAMT> </LEDGERBAL> </CURDEF> </STMTRS> </TRNUID> </STMTTRNRS> <CCSTMTTRNRS> <TRNUID>0 <CCSTMTRS> <CURDEF>USD <CCACCTFROM> <ACCTID>379700001111222 </ACCTID> </CCACCTFROM> <BANKTRANLIST> </BANKTRANLIST> <LEDGERBAL> <BALAMT>200.00 <DTASOF>20140102000000.000[-7:MST]</DTASOF> </BALAMT> </LEDGERBAL> </CURDEF> </CCSTMTRS> </TRNUID> </CCSTMTTRNRS> </CREDITCARDMSGSRSV1> </OFX> """) soup = bs4.BeautifulSoup(ofx_contents, 'lxml') entries = ofx.extract(soup, 'test.ofx', '379700001111222', 'Liabilities:CreditCard', '*', ofx.BalanceType.DECLARED) balance_entries, _, __ = parser.parse_string(""" 2014-01-02 balance Liabilities:CreditCard 100.00 USD 2014-01-03 balance Liabilities:CreditCard 200.00 USD """, dedent=True) self.assertEqualEntries(balance_entries, entries)
def test_extract_with_balance_last(self): soup, exp_entries = self._extract_with_balance() entries = ofx.extract(soup, 'test.ofx', '379700001111222', 'Liabilities:CreditCard', '*', ofx.BalanceType.LAST) balance_entries, _, __ = parser.parse_string(""" 2013-11-27 balance Liabilities:CreditCard -2356.38 USD """) self.assertEqualEntries(exp_entries + balance_entries, entries)
def test_run_transformation_exception(self): # Test another exception occurring during import. entries, errors, options_map = parser.parse_string( 'plugin "failing"\n\n' + TEST_INPUT) loader.run_transformations(entries, errors, options_map, None) trans_entries, trans_errors = loader.run_transformations( entries, errors, options_map, None) self.assertEqual(1, len(trans_errors)) self.assertRegex(trans_errors[0].message, "ValueError")
def deserialise_posting(posting): """Parse JSON to a Beancount Posting.""" amount = posting.get("amount", "") entries, errors, _ = parse_string( '2000-01-01 * "" ""\n Assets:Account {}'.format(amount)) if errors: raise FavaAPIException("Invalid amount: {}".format(amount)) pos = entries[0].postings[0] return pos._replace(account=posting["account"], meta=None)
def test_find_duplicate_entries(self): entries, error, options = parser.parse_string( textwrap.dedent(''' 1970-01-01 * "Test" Assets:Tests 10.00 USD''')) extracted = [ ('/path/to/test.csv', entries), ] marked = extract.find_duplicate_entries(extracted, entries) self.assertTrue(marked[0][1][0].meta[extract.DUPLICATE])
def test_has_auto_postings(self): entries, _, __ = parser.parse_string(""" 2014-01-27 * "UNION MARKET" Liabilities:US:Amex:BlueCash -22.02 USD Expenses:Food:Grocery 22.02 USD """, dedent=True) self.assertFalse(parser.has_auto_postings(entries)) entries, _, __ = parser.parse_string(""" 2014-01-27 * "UNION MARKET" Liabilities:US:Amex:BlueCash -22.02 USD Expenses:Food:Grocery """, dedent=True) self.assertTrue(parser.has_auto_postings(entries))
def deserialise_posting(posting: Any) -> Posting: """Parse JSON to a Beancount Posting.""" amount = posting.get("amount", "") entries, errors, _ = parse_string( f'2000-01-01 * "" ""\n Assets:Account {amount}') if errors: raise FavaAPIException(f"Invalid amount: {amount}") txn = entries[0] assert isinstance(txn, Transaction) pos = txn.postings[0] return pos._replace(account=posting["account"], meta=None)
def test_extract_from_file_ensure_sanity(self): entries, errors, options = parser.parse_string(''' 1970-01-01 * "Test" Assets:Tests 1.00 USD ''') # Break something. entries[-1] = entries[-1]._replace(narration=42) importer = mock.MagicMock(wraps=self.importer) importer.extract.return_value = entries with self.assertRaises(AssertionError): extract.extract_from_file(importer, path.abspath('test.csv'), [])
def _load_testset(testset): path = os.path.join(os.path.dirname(__file__), "data", testset + ".beancount") with open(path, "r") as test_file: _, *sections = re.split(r"# [A-Z]+\n", test_file.read()) parsed_sections = [] for section in sections: entries, errors, __ = parser.parse_string(section) assert not errors parsed_sections.append(entries) assert len(parsed_sections) == 3 return parsed_sections
def get_incomplete_entry(self, string): """Parse an incomplete entry and convert its LotSpec representation to a Lot. Args: string: The input string to parse. Returns: A pair of (entry, list of errors). """ entries, _, options_map = parser.parse_string(string, dedent=True) entries_with_lots, errors = booking.convert_lot_specs_to_lots(entries, options_map) entry = entries_with_lots[0] errors = interpolate.balance_incomplete_postings(entry, options_map) return entry, errors
def test_simple_interpolation(self): entries, _, options_map = parser.parse_string(""" 2013-05-01 open Assets:Bank:Investing 2013-05-01 open Equity:Opening-Balances 2013-05-02 * Assets:Bank:Investing 5 HOOL {501 USD} Equity:Opening-Balances """) interpolated_entries, errors = booking.simple_interpolation( entries, options_map) self.assertFalse(errors) self.assertEqual(D('-2505'), interpolated_entries[-1].postings[-1].position.number)
def test_is_entry_incomplete(self): entries, _, __ = parser.parse_string(""" 2014-01-27 * "UNION MARKET" Liabilities:US:Amex:BlueCash -22.02 USD Expenses:Food:Grocery 22.02 USD 2014-01-27 * "UNION MARKET" Liabilities:US:Amex:BlueCash -22.02 USD Expenses:Food:Grocery """, dedent=True) self.assertFalse(parser.is_entry_incomplete(entries[0])) self.assertTrue(parser.is_entry_incomplete(entries[1]))
def get_incomplete_entry(self, string): """Parse a single incomplete entry and convert its CostSpec to a Cost. Args: string: The input string to parse. Returns: A pair of (entry, list of errors). """ entries, errors, options_map = parser.parse_string(string, dedent=True) self.assertFalse(errors) self.assertEqual(1, len(entries)) (entries_with_lots, errors) = booking_simple.convert_lot_specs_to_lots(entries) self.assertEqual(1, len(entries)) entry = entries_with_lots[0] errors = booking_simple.balance_incomplete_postings(entry, options_map) return entry, errors
def test_extract_from_file(self): entries, errors, options = parser.parse_string( textwrap.dedent(''' 1970-01-03 * "Test" Assets:Tests 1.00 USD 1970-01-01 * "Test" Assets:Tests 1.00 USD 1970-01-02 * "Test" Assets:Tests 1.00 USD ''')) importer = mock.MagicMock(wraps=self.importer) importer.extract.return_value = entries entries = extract.extract_from_file(importer, path.abspath('test.csv'), []) dates = [entry.date for entry in entries] self.assertSequenceEqual(dates, sorted(dates))
def test_validate__use_legacy_fixed_tolerances(self): for input_value, expected_value in [ ('TRUE', True), ('True', True), ('true', True), ('1', True), ('42', False), ('FALSE', False), ('False', False), ('false', False), ('0', False), ('something', False), ('other', False), ]: input_str = """ option "use_legacy_fixed_tolerances" "{}" """.format(input_value) _, errors, options_map = parser.parse_string(input_str) self.assertFalse(errors) self.assertEqual(expected_value, options_map['use_legacy_fixed_tolerances'])
def read_string_or_entries(entries_or_str, allow_incomplete=False): """Read a string of entries or just entries. Args: entries_or_str: Either a list of directives, or a string containing directives. allow_incomplete: A boolean, true if we allow incomplete inputs and perform light-weight booking. Returns: A list of directives. """ if isinstance(entries_or_str, str): entries, errors, options_map = parser.parse_string( textwrap.dedent(entries_or_str)) if allow_incomplete: # Do a simplistic local conversion in order to call the comparison. entries = [_local_booking(entry) for entry in entries] else: # Don't accept incomplete entries either. if any(parser.is_entry_incomplete(entry) for entry in entries): raise TestError( "Entries in assertions may not use interpolation.") entries, booking_errors = booking.book(entries, options_map) errors = errors + booking_errors # Don't tolerate errors. if errors: oss = io.StringIO() printer.print_errors(errors, file=oss) raise TestError("Unexpected errors in expected: {}".format( oss.getvalue())) else: assert isinstance(entries_or_str, list), "Expecting list: {}".format(entries_or_str) entries = entries_or_str return entries
def get_budgets(beancount_string): entries, errors, options_map = parser.parse_string(beancount_string, dedent=True) return Budgets(entries)