"""发票模块单元测试 覆盖发票分类、CSV 列定义、常量校验。 """ from src.doc.invoice 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