from datetime import date from unittest.mock import patch from django.contrib.admin.sites import AdminSite from django.test import TestCase from basic_info import models as basic_models from settlement import handlers from settlement.admin import DailySettlementConfigAdmin from settlement.models import ( DailySettlementConfig, NotificationChannelEnum, SettlementModuleEnum, ) from settlement.tasks import run_daily_settlement, run_merchant_daily_settlement class SettlementTasksTestCase(TestCase): def setUp(self): self.merchant = basic_models.Merchant.objects.create( name='任务测试商户', type=basic_models.MerchantTypeEnum.FACTORY, ) self.config = DailySettlementConfig.objects.create( merchant=self.merchant, settlement_modules=[SettlementModuleEnum.PLATE_ORDER], notification_enabled=False, notification_channel=NotificationChannelEnum.NONE, ) @patch('settlement.tasks.run_merchant_daily_settlement.delay') @patch('settlement.tasks.timezone.localdate') def test_run_daily_settlement_dispatches_only_configured_merchants( self, mock_localdate, mock_delay, ): mock_localdate.return_value = date(2026, 2, 9) merchant_without_modules = basic_models.Merchant.objects.create( name='无模块商户', type=basic_models.MerchantTypeEnum.FACTORY, ) DailySettlementConfig.objects.create( merchant=merchant_without_modules, settlement_modules=[], notification_enabled=False, notification_channel=NotificationChannelEnum.NONE, ) result = run_daily_settlement.run() self.assertEqual(result['total_merchants'], 1) self.assertEqual(result['settlement_date'], '2026-02-08') mock_delay.assert_called_once_with( merchant_id=self.merchant.id, settlement_date='2026-02-08', ) @patch('settlement.tasks.daily_settlement_completed.send') @patch('settlement.tasks.calculate_printing_order_daily_summary') @patch('settlement.tasks.calculate_plate_order_daily_summary') def test_run_merchant_daily_settlement_success( self, mock_plate_summary, mock_printing_summary, mock_signal_send, ): self.config.settlement_modules = [ SettlementModuleEnum.PLATE_ORDER, SettlementModuleEnum.PRINTING_ORDER, ] self.config.save(update_fields=['settlement_modules']) mock_plate_summary.return_value = {'total_orders': 1} mock_printing_summary.return_value = {'total_orders': 2} result = run_merchant_daily_settlement.run( merchant_id=self.merchant.id, settlement_date='2026-02-08', ) self.assertEqual(result['merchant_id'], self.merchant.id) self.assertEqual(result['settlement_date'], '2026-02-08') self.assertEqual(result['status'], 'success') self.assertEqual(result['errors'], {}) mock_plate_summary.assert_called_once() mock_printing_summary.assert_called_once() mock_signal_send.assert_called_once() @patch('settlement.tasks.daily_settlement_completed.send') @patch('settlement.tasks.calculate_printing_order_daily_summary') @patch('settlement.tasks.calculate_plate_order_daily_summary') def test_run_merchant_daily_settlement_partial_when_one_module_fails( self, mock_plate_summary, mock_printing_summary, mock_signal_send, ): self.config.settlement_modules = [ SettlementModuleEnum.PLATE_ORDER, SettlementModuleEnum.PRINTING_ORDER, ] self.config.save(update_fields=['settlement_modules']) mock_plate_summary.side_effect = RuntimeError('plate failed') mock_printing_summary.return_value = {'total_orders': 2} result = run_merchant_daily_settlement.run( merchant_id=self.merchant.id, settlement_date='2026-02-08', ) self.assertEqual(result['status'], 'partial') self.assertIn(SettlementModuleEnum.PLATE_ORDER, result['errors']) self.assertEqual(result['errors'][SettlementModuleEnum.PLATE_ORDER], 'plate failed') mock_signal_send.assert_called_once() @patch('settlement.tasks.daily_settlement_completed.send') @patch('settlement.tasks.calculate_plate_order_daily_summary') def test_run_merchant_daily_settlement_failed_when_all_modules_fail( self, mock_plate_summary, mock_signal_send, ): self.config.settlement_modules = [SettlementModuleEnum.PLATE_ORDER] self.config.save(update_fields=['settlement_modules']) mock_plate_summary.side_effect = RuntimeError('plate failed') result = run_merchant_daily_settlement.run( merchant_id=self.merchant.id, settlement_date='2026-02-08', ) self.assertEqual(result['status'], 'failed') self.assertIn(SettlementModuleEnum.PLATE_ORDER, result['errors']) mock_signal_send.assert_called_once() @patch('settlement.tasks.daily_settlement_completed.send') @patch('settlement.tasks.calculate_plate_order_daily_summary') @patch('settlement.tasks.timezone.localdate') def test_run_merchant_daily_settlement_uses_yesterday_when_no_date( self, mock_localdate, mock_plate_summary, mock_signal_send, ): mock_localdate.return_value = date(2026, 2, 9) self.config.settlement_modules = [SettlementModuleEnum.PLATE_ORDER] self.config.save(update_fields=['settlement_modules']) mock_plate_summary.return_value = {'total_orders': 1} result = run_merchant_daily_settlement.run(merchant_id=self.merchant.id) self.assertEqual(result['settlement_date'], '2026-02-08') mock_signal_send.assert_called_once() class SettlementHandlersTestCase(TestCase): def setUp(self): self.merchant = basic_models.Merchant.objects.create( name='处理器测试商户', type=basic_models.MerchantTypeEnum.FACTORY, ) @patch('settlement.handlers.logger.info') def test_handler_logs_when_notification_disabled(self, mock_logger_info): DailySettlementConfig.objects.create( merchant=self.merchant, settlement_modules=[SettlementModuleEnum.PLATE_ORDER], notification_enabled=False, notification_channel=NotificationChannelEnum.NONE, ) handlers.on_daily_settlement_completed( sender=self.__class__, merchant=self.merchant, settlement_date=date(2026, 2, 8), status='success', modules=[SettlementModuleEnum.PLATE_ORDER], errors={}, task_id='task-1', ) self.assertTrue(mock_logger_info.called) @patch('settlement.handlers.logger.info') def test_handler_logs_wecom_when_notification_enabled(self, mock_logger_info): DailySettlementConfig.objects.create( merchant=self.merchant, settlement_modules=[SettlementModuleEnum.PLATE_ORDER], notification_enabled=True, notification_channel=NotificationChannelEnum.WECOM, ) handlers.on_daily_settlement_completed( sender=self.__class__, merchant=self.merchant, settlement_date=date(2026, 2, 8), status='success', modules=[SettlementModuleEnum.PLATE_ORDER], errors={}, task_id='task-2', ) self.assertTrue(mock_logger_info.called) class SettlementAdminTestCase(TestCase): def test_get_readonly_fields(self): admin_obj = DailySettlementConfigAdmin( DailySettlementConfig, admin_site=AdminSite(), ) self.assertEqual(admin_obj.get_readonly_fields(request=None, obj=None), []) merchant = basic_models.Merchant.objects.create( name='后台测试商户', type=basic_models.MerchantTypeEnum.FACTORY, ) config = DailySettlementConfig.objects.create( merchant=merchant, settlement_modules=[SettlementModuleEnum.PLATE_ORDER], notification_enabled=False, notification_channel=NotificationChannelEnum.NONE, ) self.assertEqual(admin_obj.get_readonly_fields(request=None, obj=config), ['merchant'])