import pytest from datetime import UTC, datetime from decimal import Decimal from pydantic import ValidationError from models.ref import ChannelRef, TransactionRef from models.transaction import ( ATMTransaction, BaseTransaction, DcFlag, EcomPaymentTransaction, FxRate, MiscInTransaction, MiscOutTransaction, PaymentTransaction, PosPaymentTransaction, Transaction, TransactionStatus, TransferTransaction, ) from ulid import ULID @pytest.fixture def channel_ref(): """创建渠道引用 fixture""" from ulid import ULID return ChannelRef(ref_id=ULID()) @pytest.fixture def minimal_txn(channel_ref): """创建最简交易 fixture""" return BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, ) class TestBaseTransactionCreation: """测试基础交易创建""" def test_minimal_transaction(self, minimal_txn): """最简交易创建""" assert minimal_txn.txn_amt == Decimal("100.00") assert minimal_txn.txn_ccy == "CNY" assert minimal_txn.dc_flag == DcFlag.DEBIT assert minimal_txn.cp == "商户A" assert minimal_txn.txn_sts == TransactionStatus.COMPLETED assert minimal_txn.version == 0 assert minimal_txn.ref_transactions == [] assert minimal_txn.deleted_at is None assert minimal_txn.posting_amt is None assert minimal_txn.comm_amt is None assert minimal_txn.surcharge_amt is None assert minimal_txn.disc_amt is None assert minimal_txn.fx_rates is None assert minimal_txn.acq_inst is None assert minimal_txn.clearing_network is None assert minimal_txn.description is None assert minimal_txn.memo is None assert minimal_txn.raw_description is None assert minimal_txn.raw_data is None assert minimal_txn.txn_scene is None def test_full_transaction(self, channel_ref): """完整字段交易 - 闲鱼卖电脑场景""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("5200.00"), txn_ccy="CNY", dc_flag=DcFlag.CREDIT, cp="买家_小明", posting_date=datetime.now(UTC), posting_amt=Decimal("5148.00"), posting_ccy="CNY", comm_amt=Decimal("52.00"), comm_ccy="CNY", txn_sts=TransactionStatus.COMPLETED, acq_inst="支付宝", description="出售 MacBook Pro 14寸 M3", memo="二手电脑交易", raw_description="闲鱼订单 20260622001", raw_data={"platform": "闲鱼", "order_id": "20260622001", "item": "MacBook Pro"}, ref_channel=channel_ref, ) assert txn.txn_amt == Decimal("5200.00") assert txn.txn_ccy == "CNY" assert txn.dc_flag == DcFlag.CREDIT assert txn.posting_amt == Decimal("5148.00") assert txn.posting_ccy == "CNY" assert txn.comm_amt == Decimal("52.00") assert txn.comm_ccy == "CNY" assert txn.txn_sts == TransactionStatus.COMPLETED assert txn.acq_inst == "支付宝" assert txn.description == "出售 MacBook Pro 14寸 M3" assert txn.memo == "二手电脑交易" assert txn.raw_description == "闲鱼订单 20260622001" assert txn.raw_data == {"platform": "闲鱼", "order_id": "20260622001", "item": "MacBook Pro"} def test_cp_as_channel_ref(self, channel_ref): """对手方为渠道引用""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("50.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp=channel_ref, ref_channel=channel_ref, ) assert isinstance(txn.cp, ChannelRef) assert txn.cp.ref_type == "CHANNEL" def test_cp_as_string(self, channel_ref): """对手方为字符串""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("50.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, ) assert txn.cp == "商户A" def test_clearing_network(self, channel_ref): """清算网络字段""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, clearing_network="银联", ) assert txn.clearing_network == "银联" class TestAmtCcyPairValidation: """测试金额/币种对校验""" def test_all_pairs_none(self, minimal_txn): """所有金额/币种对都为 None(应该通过)""" assert minimal_txn.posting_amt is None assert minimal_txn.comm_amt is None assert minimal_txn.surcharge_amt is None assert minimal_txn.disc_amt is None def test_posting_pair_both_set(self, channel_ref): """入账金额/币种都有值(应该通过)""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, posting_amt=Decimal("100.00"), posting_ccy="CNY", ) assert txn.posting_amt == Decimal("100.00") assert txn.posting_ccy == "CNY" def test_posting_amt_only_should_fail(self, channel_ref): """只有入账金额没有币种(应该失败)""" with pytest.raises(ValidationError): BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, posting_amt=Decimal("95.00"), ) def test_posting_ccy_only_should_fail(self, channel_ref): """只有入账币种没有金额(应该失败)""" with pytest.raises(ValidationError): BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, posting_ccy="USD", ) def test_comm_pair_both_set(self, channel_ref): """佣金金额/币种都有值(应该通过)""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, comm_amt=Decimal("2.00"), comm_ccy="CNY", ) assert txn.comm_amt == Decimal("2.00") assert txn.comm_ccy == "CNY" def test_comm_amt_only_should_fail(self, channel_ref): """只有佣金金额没有币种(应该失败)""" with pytest.raises(ValidationError): BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, comm_amt=Decimal("2.00"), ) def test_surcharge_pair_both_set(self, channel_ref): """服务费金额/币种都有值(应该通过)""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, surcharge_amt=Decimal("3.00"), surcharge_ccy="CNY", ) assert txn.surcharge_amt == Decimal("3.00") assert txn.surcharge_ccy == "CNY" def test_surcharge_amt_only_should_fail(self, channel_ref): """只有服务费金额没有币种(应该失败)""" with pytest.raises(ValidationError): BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, surcharge_amt=Decimal("3.00"), ) def test_disc_pair_both_set(self, channel_ref): """折扣金额/币种都有值(应该通过)""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, disc_amt=Decimal("5.00"), disc_ccy="CNY", ) assert txn.disc_amt == Decimal("5.00") assert txn.disc_ccy == "CNY" def test_disc_amt_only_should_fail(self, channel_ref): """只有折扣金额没有币种(应该失败)""" with pytest.raises(ValidationError): BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, disc_amt=Decimal("5.00"), ) def test_multiple_pairs_set(self, channel_ref): """多对金额/币种同时设置(应该通过)""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("1000.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, posting_amt=Decimal("1010.00"), posting_ccy="CNY", comm_amt=Decimal("10.00"), comm_ccy="CNY", ) assert txn.posting_amt == Decimal("1010.00") assert txn.comm_amt == Decimal("10.00") class TestAccountingEquation: """测试会计恒等式校验""" def test_credit_equation_valid(self, channel_ref): """CREDIT: posting_amt = txn_amt + surcharge_amt - comm_amt - disc_amt""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("5200.00"), txn_ccy="CNY", dc_flag=DcFlag.CREDIT, cp="买家_小明", ref_channel=channel_ref, posting_amt=Decimal("5148.00"), posting_ccy="CNY", comm_amt=Decimal("52.00"), comm_ccy="CNY", ) assert txn.posting_amt == Decimal("5148.00") def test_credit_equation_with_surcharge(self, channel_ref): """CREDIT: 含附加费""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("1000.00"), txn_ccy="CNY", dc_flag=DcFlag.CREDIT, cp="客户A", ref_channel=channel_ref, posting_amt=Decimal("1005.00"), posting_ccy="CNY", comm_amt=Decimal("10.00"), comm_ccy="CNY", surcharge_amt=Decimal("15.00"), surcharge_ccy="CNY", ) assert txn.posting_amt == Decimal("1005.00") def test_credit_equation_invalid(self, channel_ref): """CREDIT: 恒等式不成立应失败""" with pytest.raises(ValidationError) as exc_info: BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("5200.00"), txn_ccy="CNY", dc_flag=DcFlag.CREDIT, cp="买家_小明", ref_channel=channel_ref, posting_amt=Decimal("5000.00"), posting_ccy="CNY", comm_amt=Decimal("52.00"), comm_ccy="CNY", ) assert "Accounting equation mismatch" in str(exc_info.value) def test_debit_equation_valid(self, channel_ref): """DEBIT: posting_amt = txn_amt + surcharge_amt + comm_amt - disc_amt""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, posting_amt=Decimal("110.00"), posting_ccy="CNY", comm_amt=Decimal("10.00"), comm_ccy="CNY", ) assert txn.posting_amt == Decimal("110.00") def test_debit_equation_with_disc(self, channel_ref): """DEBIT: 含折扣""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, posting_amt=Decimal("105.00"), posting_ccy="CNY", comm_amt=Decimal("10.00"), comm_ccy="CNY", disc_amt=Decimal("5.00"), disc_ccy="CNY", ) assert txn.posting_amt == Decimal("105.00") def test_debit_equation_invalid(self, channel_ref): """DEBIT: 恒等式不成立应失败""" with pytest.raises(ValidationError) as exc_info: BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, posting_amt=Decimal("100.00"), posting_ccy="CNY", comm_amt=Decimal("10.00"), comm_ccy="CNY", ) assert "Accounting equation mismatch" in str(exc_info.value) def test_different_currency_skip_validation(self, channel_ref): """不同币种跳过校验""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("5200.00"), txn_ccy="CNY", dc_flag=DcFlag.CREDIT, cp="买家_小明", ref_channel=channel_ref, posting_amt=Decimal("700.00"), posting_ccy="USD", ) assert txn.posting_amt == Decimal("700.00") def test_no_posting_amt_skip_validation(self, channel_ref): """无 posting_amt 跳过校验""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("5200.00"), txn_ccy="CNY", dc_flag=DcFlag.CREDIT, cp="买家_小明", ref_channel=channel_ref, ) assert txn.posting_amt is None class TestFxRate: """测试汇率快照""" def test_fx_rate_creation(self): """创建汇率快照""" fx = FxRate(from_ccy="USD", to_ccy="CNY", rate=Decimal("6.75")) assert fx.from_ccy == "USD" assert fx.to_ccy == "CNY" assert fx.rate == Decimal("6.75") def test_invalid_currency_should_fail(self): """无效币种应该失败""" with pytest.raises(ValidationError): FxRate(from_ccy="INVALID", to_ccy="CNY", rate=Decimal("6.75")) def test_transaction_with_fx_rates(self, channel_ref): """交易带多个汇率快照""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="USD", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, fx_rates=[ FxRate(from_ccy="USD", to_ccy="CNY", rate=Decimal("6.75")), FxRate(from_ccy="USD", to_ccy="HKD", rate=Decimal("7.82")), ], ) assert len(txn.fx_rates) == 2 assert txn.fx_rates[0].from_ccy == "USD" assert txn.fx_rates[0].to_ccy == "CNY" assert txn.fx_rates[1].from_ccy == "USD" assert txn.fx_rates[1].to_ccy == "HKD" class TestTransactionStatus: """测试交易状态""" def test_default_status(self, minimal_txn): """默认状态为 COMPLETED""" assert minimal_txn.txn_sts == TransactionStatus.COMPLETED def test_set_pending(self, channel_ref): """设置为 PENDING""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, txn_sts=TransactionStatus.PENDING, ) assert txn.txn_sts == TransactionStatus.PENDING def test_set_failed(self, channel_ref): """设置为 FAILED""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, txn_sts=TransactionStatus.FAILED, ) assert txn.txn_sts == TransactionStatus.FAILED def test_set_refunded(self, channel_ref): """设置为 REFUNDED""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, txn_sts=TransactionStatus.REFUNDED, ) assert txn.txn_sts == TransactionStatus.REFUNDED class TestDcFlag: """测试借贷方向""" def test_debit(self, channel_ref): """借方""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, ) assert txn.dc_flag == DcFlag.DEBIT def test_credit(self, channel_ref): """贷方""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.CREDIT, cp="商户A", ref_channel=channel_ref, ) assert txn.dc_flag == DcFlag.CREDIT class TestTransactionRef: """测试交易引用""" def test_ref_transactions(self, channel_ref): """关联交易引用""" ref = TransactionRef(ref_id=ULID()) txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, ref_transactions=[ref], ) assert len(txn.ref_transactions) == 1 assert txn.ref_transactions[0].ref_type == "TRANSACTION" class TestSoftDelete: def test_not_deleted(self, minimal_txn): """未删除状态""" assert minimal_txn.deleted_at is None def test_set_deleted_at(self, minimal_txn): """设置删除时间""" deleted_at = datetime.now(UTC) minimal_txn.deleted_at = deleted_at assert minimal_txn.deleted_at == deleted_at class TestVersion: """测试乐观锁""" def test_default_version(self, minimal_txn): """默认版本为 0""" assert minimal_txn.version == 0 def test_set_version(self, channel_ref): """设置版本号""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, version=5, ) assert txn.version == 5 class TestOptionalFields: """测试可选字段""" def test_empty_description_allowed(self, channel_ref): """空字符串描述允许""" txn = BaseTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, description="", ) assert txn.description == "" def test_all_optional_fields_none(self, minimal_txn): """所有可选字段都为 None""" assert minimal_txn.description is None assert minimal_txn.memo is None assert minimal_txn.raw_description is None assert minimal_txn.raw_data is None assert minimal_txn.acq_inst is None assert minimal_txn.clearing_network is None class TestMiscInTransaction: """测试其他收入交易""" def test_create(self, channel_ref): """创建其他收入交易""" txn = MiscInTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("500.00"), txn_ccy="CNY", dc_flag=DcFlag.CREDIT, cp="红包", ref_channel=channel_ref, posting_amt=Decimal("500.00"), posting_ccy="CNY", ) assert txn.txn_scene == "MISC_IN" def test_txn_scene_fixed(self, channel_ref): """txn_scene 固定为 MISC_IN""" txn = MiscInTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.CREDIT, cp="退款", ref_channel=channel_ref, posting_amt=Decimal("100.00"), posting_ccy="CNY", ) assert txn.txn_scene == "MISC_IN" class TestMiscOutTransaction: """测试其他支出交易""" def test_create(self, channel_ref): """创建其他支出交易""" txn = MiscOutTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("200.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="捐赠", ref_channel=channel_ref, posting_amt=Decimal("200.00"), posting_ccy="CNY", ) assert txn.txn_scene == "MISC_OUT" class TestPaymentTransaction: """测试支付交易""" def test_create(self, channel_ref): """创建支付交易""" txn = PaymentTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户A", ref_channel=channel_ref, posting_amt=Decimal("100.00"), posting_ccy="CNY", merchant_name="星巴克", ) assert txn.txn_scene == "PAYMENT" assert txn.merchant_name == "星巴克" def test_merchant_name_optional(self, channel_ref): """商户名称可选""" txn = PaymentTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("50.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户B", ref_channel=channel_ref, posting_amt=Decimal("50.00"), posting_ccy="CNY", ) assert txn.merchant_name is None class TestEcomPaymentTransaction: """测试电子商务支付交易""" def test_create(self, channel_ref): """创建电商支付交易""" txn = EcomPaymentTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("299.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="淘宝商家", ref_channel=channel_ref, posting_amt=Decimal("299.00"), posting_ccy="CNY", merchant_name="淘宝店铺", order_id="TB20260622001", ) assert txn.txn_scene == "ECOM_PAYMENT" assert txn.order_id == "TB20260622001" assert txn.merchant_name == "淘宝店铺" def test_order_id_optional(self, channel_ref): """订单ID可选""" txn = EcomPaymentTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("100.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="京东", ref_channel=channel_ref, posting_amt=Decimal("100.00"), posting_ccy="CNY", ) assert txn.order_id is None class TestPosPaymentTransaction: """测试POS支付交易""" def test_create_with_geo(self, channel_ref): """创建POS支付交易(带地理信息)""" txn = PosPaymentTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("50.00"), txn_ccy="HKD", dc_flag=DcFlag.DEBIT, cp="7-11", ref_channel=channel_ref, posting_amt=Decimal("50.00"), posting_ccy="HKD", geo=("HK"), ) assert txn.txn_scene == "POS_PAYMENT" assert txn.geo == ("HK",) def test_geo_country_only(self, channel_ref): """地理信息仅包含国家""" txn = PosPaymentTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("30.00"), txn_ccy="HKD", dc_flag=DcFlag.DEBIT, cp="7-11", ref_channel=channel_ref, posting_amt=Decimal("30.00"), posting_ccy="HKD", geo=("HK",), ) assert txn.geo == ("HK",) def test_geo_optional(self, channel_ref): """地理信息可选""" txn = PosPaymentTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("20.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="商户", ref_channel=channel_ref, posting_amt=Decimal("20.00"), posting_ccy="CNY", ) assert txn.geo is None class TestATMTransaction: """测试ATM交易""" def test_create_with_geo(self, channel_ref): """创建ATM交易(带地理信息)""" txn = ATMTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("2000.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="ATM", ref_channel=channel_ref, posting_amt=Decimal("2000.00"), posting_ccy="CNY", geo=("CN", "SH"), ) assert txn.txn_scene == "ATM" assert txn.geo == ("CN", "SH") def test_geo_optional(self, channel_ref): """地理信息可选""" txn = ATMTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("500.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="ATM", ref_channel=channel_ref, posting_amt=Decimal("500.00"), posting_ccy="CNY", ) assert txn.geo is None class TestTransferTransaction: """测试转账交易""" def test_create(self, channel_ref): """创建转账交易""" txn = TransferTransaction( txn_date=datetime.now(UTC), txn_amt=Decimal("10000.00"), txn_ccy="CNY", dc_flag=DcFlag.DEBIT, cp="朋友A", ref_channel=channel_ref, posting_amt=Decimal("10000.00"), posting_ccy="CNY", ) assert txn.txn_scene == "TRANSFER" class TestDiscriminatedUnion: """测试 Discriminated Union""" def test_all_scenes_present(self): """所有交易场景都已定义""" scenes = {"MISC_IN", "MISC_OUT", "TRANSFER", "PAYMENT", "ECOM_PAYMENT", "POS_PAYMENT", "ATM"} # 验证所有场景都有对应的模型 assert scenes == { MiscInTransaction.model_fields["txn_scene"].default, MiscOutTransaction.model_fields["txn_scene"].default, TransferTransaction.model_fields["txn_scene"].default, PaymentTransaction.model_fields["txn_scene"].default, EcomPaymentTransaction.model_fields["txn_scene"].default, PosPaymentTransaction.model_fields["txn_scene"].default, ATMTransaction.model_fields["txn_scene"].default, }