119 lines
6.3 KiB
Python
119 lines
6.3 KiB
Python
import datetime
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import unittest
|
|
from decimal import Decimal
|
|
from pathlib import Path
|
|
|
|
from openpyxl import load_workbook
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
import test_expense_template as fixtures
|
|
from expense_preview import PreviewCalculator, UnsupportedFormula, builtin_rmb, prepare_preview
|
|
from expense_template import Template, encoded, set_cell, tag
|
|
|
|
|
|
class ExpensePreviewTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.fixture = fixtures.ExpenseTemplateTests()
|
|
self.fixture.setUp()
|
|
self.addCleanup(self.fixture.tearDown)
|
|
|
|
def test_filled_workbook_previews_current_totals_and_preserves_formulas(self):
|
|
output, _ = self.fixture.export()
|
|
result = prepare_preview(output)
|
|
values = load_workbook(output, data_only=True)
|
|
formulas = load_workbook(output)
|
|
sheet = values.active
|
|
self.assertEqual(sheet['C9'].value, '交通')
|
|
self.assertEqual(sheet['H9'].value, 100.3)
|
|
self.assertEqual(sheet['H10'].value, 444)
|
|
self.assertEqual(sheet['F27'].value, 544.3)
|
|
self.assertEqual(sheet['H29'].value, 544.3)
|
|
self.assertEqual(formulas.active['F27'].value, '=SUM(H9:H21)')
|
|
self.assertEqual(formulas.active['H29'].value, '=IF(F27>C27,F27-C27,0)')
|
|
self.assertEqual(result['previewUncalculatedCells'], [])
|
|
self.assertEqual(sheet['D29'].value, '伍佰肆拾肆元叁角整')
|
|
self.assertEqual(result['expensePreview'], 'xlsx-quicklook-v1')
|
|
values.close()
|
|
formulas.close()
|
|
|
|
def test_builtin_uppercase_amount_formula(self):
|
|
for amount, expected in [('3491.86', '叁仟肆佰玖拾壹元捌角陆分'), ('10001.01', '壹万零壹元零壹分'),
|
|
('100000001', '壹亿零壹元整'), ('100010000', '壹亿零壹万元整'),
|
|
('0.01', '壹分'), ('0.1', '壹角整'), ('-50.02', '负伍拾元零贰分'),
|
|
('0', ''), ('1.999', '贰元整'), ('999999999999.99', '玖仟玖佰玖拾玖亿玖仟玖佰玖拾玖万玖仟玖佰玖拾玖元玖角玖分')]:
|
|
with self.subTest(amount=amount):
|
|
self.assertEqual(builtin_rmb(Decimal(amount)), expected)
|
|
def test_caches_each_custom_page_not_whole_report(self):
|
|
source, mapping = self.fixture.alternate()
|
|
state = dict(matches=self.fixture.state['matches'] + [self.fixture.match('third', '住宿', '50')])
|
|
output, _ = self.fixture.export(mapping=mapping, template=source, state=state)
|
|
prepare_preview(output)
|
|
workbook = load_workbook(output, data_only=True)
|
|
self.assertEqual(workbook['组 B 报销']['F8'].value, 544.3)
|
|
self.assertEqual(workbook['组 B 报销-续2']['F8'].value, 50)
|
|
self.assertIn('保留说明', workbook.sheetnames)
|
|
workbook.close()
|
|
|
|
def test_clears_unknown_cache_and_does_not_execute_formula(self):
|
|
output, _ = self.fixture.export()
|
|
template = Template(output)
|
|
_, path, document = template.sheet('个人报销单')
|
|
set_cell(document, 'I35', '__import__("os").system("touch /tmp/should-not-run")', formula=True)
|
|
from lxml import etree as ET
|
|
ET.SubElement(document.find('.//' + tag('c') + '[@r="I35"]'), tag('v')).text = '9999'
|
|
template.parts[path] = encoded(document)
|
|
self.fixture.rewrite(output, template.parts)
|
|
result = prepare_preview(output)
|
|
self.assertIn('个人报销单!I35', result['previewUncalculatedCells'])
|
|
workbook = load_workbook(output, data_only=True)
|
|
self.assertIsNone(workbook.active['I35'].value)
|
|
workbook.close()
|
|
|
|
def test_calculator_cycles_text_dates_and_conditionals(self):
|
|
template = Template(self.fixture.template)
|
|
_, _, sheet = template.sheet('个人报销单')
|
|
set_cell(sheet, 'J1', 10)
|
|
set_cell(sheet, 'J2', '文字')
|
|
set_cell(sheet, 'J3', '-J1*2+5', formula=True)
|
|
set_cell(sheet, 'J4', 'J5', formula=True)
|
|
set_cell(sheet, 'J5', 'J4', formula=True)
|
|
calculator = PreviewCalculator(sheet)
|
|
self.assertEqual(calculator.cell('J3'), -15)
|
|
self.assertEqual(calculator.expression('SUM(J1:J3)'), -5)
|
|
self.assertEqual(calculator.expression('IF(J1>0,J1/2,1/0)'), 5)
|
|
self.assertEqual(calculator.expression('TODAY()'), (datetime.date.today() - datetime.date(1899, 12, 30)).days)
|
|
self.assertEqual(PreviewCalculator(sheet, True).expression('TODAY()'),
|
|
(datetime.date.today() - datetime.date(1904, 1, 1)).days)
|
|
with self.assertRaises(UnsupportedFormula):
|
|
calculator.cell('J4')
|
|
with self.assertRaises(UnsupportedFormula):
|
|
calculator.expression('2**10000000')
|
|
|
|
def test_engine_process_preview_builtin_and_imported_templates(self):
|
|
for custom in [False, True]:
|
|
with self.subTest(custom=custom):
|
|
output = self.fixture.root / ('custom-preview.xlsx' if custom else 'builtin-preview.xlsx')
|
|
request = dict(operation='expense', state=self.fixture.state, destination=str(output), preparePreview=True)
|
|
if custom:
|
|
request.update(templatePath=str(self.fixture.template), templateMapping=self.fixture.mapping)
|
|
binary = os.environ.get('RECEIPT_ENGINE_BINARY')
|
|
command = [binary] if binary else [sys.executable, str(Path(__file__).resolve().parents[1] / 'engine.py')]
|
|
process = subprocess.run(command, input=json.dumps(request) + '\n', text=True, capture_output=True, timeout=60)
|
|
self.assertEqual(process.returncode, 0, process.stderr + process.stdout)
|
|
result = next(json.loads(line)['result'] for line in process.stdout.splitlines() if json.loads(line).get('event') == 'result')
|
|
self.assertEqual(result['expensePreview'], 'xlsx-quicklook-v1')
|
|
self.assertEqual(result['expenseRowCount'], 2)
|
|
workbook = load_workbook(output, data_only=True)
|
|
self.assertEqual(workbook.active['F27'].value, 544.3)
|
|
self.assertEqual(workbook.active['H29'].value, 544.3)
|
|
self.assertIn(workbook.active['C23'].value, ('', None))
|
|
workbook.close()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|