Files
Auto-Finance/tests/test_invoice.py
wandering 1b35f07fd7 refactor: 架构重组 — doc/bot → core/infra,新增 Agent 调度模块
- src/doc/ 拆分为 src/core/extraction/, matching/, validation/(核心业务逻辑)
- src/bot/ 重命名为 src/infra/browser/(浏览器自动化基础设施)
- fill_consumable_doc.py → src/infra/documents/consumable.py
- 新增 Agent 调度模块:coordinator.py, events.py, session.py,重构 orchestrator.py
- 更新 AGENTS.md、README.md 及所有子目录 README
2026-07-02 18:36:19 +08:00

110 lines
3.4 KiB
Python

"""发票模块单元测试
覆盖发票分类、CSV 列定义、常量校验。
"""
from src.infra.documents import (
INVOICE_LEVEL_COLUMNS,
PAYMENT_RECORD_COLUMNS,
classify_invoice_batch,
)
# 字段键名与发票类型(与源码中的字符串字面量保持一致)
K_INVOICE_TYPE = "invoice_type"
K_INVOICE_NUMBER = "invoice_number"
K_TOTAL_AMOUNT = "total_amount"
K_ITEM_NAME = "item_name"
K_PERSON_NAME = "person_name"
K_CARD_DATE = "card_date"
K_CARD_NO = "card_no"
K_CARD_AMOUNT = "card_amount"
K_MATCHED_INVOICES = "_matched_invoices"
K_RELATIVE_INVOICE_COUNT = "relative_invoice_count"
K_INVOICE_DETAIL = "invoice_detail"
K_REMARK = "remark"
K_SOURCE_FILE = "source_file"
INVOICE_TYPE_TRAIN = "train"
INVOICE_TYPE_HOTEL = "hotel"
INVOICE_TYPE_GENERAL = "general"
DOCUMENT_TYPE_APPLICATION = "application"
INVOICE_TYPE_TRAVEL = [INVOICE_TYPE_TRAIN, INVOICE_TYPE_HOTEL]
def is_travel_invoice(invoice_type: str) -> bool:
return invoice_type in INVOICE_TYPE_TRAVEL
class TestInvoiceConstants:
"""发票常量校验"""
def test_travel_types_contain_train(self) -> None:
assert INVOICE_TYPE_TRAIN in INVOICE_TYPE_TRAVEL
def test_travel_types_contain_hotel(self) -> None:
assert INVOICE_TYPE_HOTEL in INVOICE_TYPE_TRAVEL
def test_general_not_in_travel(self) -> None:
assert INVOICE_TYPE_GENERAL not in INVOICE_TYPE_TRAVEL
def test_invoice_columns_not_empty(self) -> None:
assert len(INVOICE_LEVEL_COLUMNS) > 0
def test_payment_columns_not_empty(self) -> None:
assert len(PAYMENT_RECORD_COLUMNS) > 0
class TestIsTravelInvoice:
"""差旅发票判断"""
def test_train_is_travel(self) -> None:
assert is_travel_invoice(INVOICE_TYPE_TRAIN) is True
def test_hotel_is_travel(self) -> None:
assert is_travel_invoice(INVOICE_TYPE_HOTEL) is True
def test_general_not_travel(self) -> None:
assert is_travel_invoice(INVOICE_TYPE_GENERAL) is False
def test_unknown_not_travel(self) -> None:
assert is_travel_invoice("unknown") is False
class TestClassifyInvoiceBatch:
"""发票分类"""
def test_empty_list(self) -> None:
result = classify_invoice_batch([])
assert result == {"travel": [], "general": [], "application": []}
def test_all_travel(self) -> None:
invoices = [
{K_INVOICE_TYPE: INVOICE_TYPE_TRAIN, K_INVOICE_NUMBER: "001"},
{K_INVOICE_TYPE: INVOICE_TYPE_HOTEL, K_INVOICE_NUMBER: "002"},
]
result = classify_invoice_batch(invoices)
assert len(result["travel"]) == 2
assert len(result["general"]) == 0
def test_all_general(self) -> None:
invoices = [
{K_INVOICE_TYPE: INVOICE_TYPE_GENERAL, K_INVOICE_NUMBER: "001"},
]
result = classify_invoice_batch(invoices)
assert len(result["travel"]) == 0
assert len(result["general"]) == 1
def test_mixed(self) -> None:
invoices = [
{K_INVOICE_TYPE: INVOICE_TYPE_TRAIN, K_INVOICE_NUMBER: "001"},
{K_INVOICE_TYPE: INVOICE_TYPE_GENERAL, K_INVOICE_NUMBER: "002"},
]
result = classify_invoice_batch(invoices)
assert len(result["travel"]) == 1
assert len(result["general"]) == 1
def test_missing_type_defaults_to_general(self) -> None:
invoices = [{K_INVOICE_NUMBER: "001"}]
result = classify_invoice_batch(invoices)
assert len(result["general"]) == 1