""" 成本分析计算服务单元测试 测试 CostCalculatorService 和 CostSummary / DepartmentCost 的核心逻辑 """ import pytest from unittest.mock import AsyncMock, patch from app.services.analysis.cost_calculator import ( CostCalculatorService, CostSummary, DepartmentCost, ) @pytest.fixture def mock_db(): """模拟数据库会话""" return AsyncMock() @pytest.fixture def cost_calculator(mock_db): """创建成本计算服务实例""" return CostCalculatorService(mock_db) class TestCostSummary: """成本汇总结果测试""" def test_init_defaults(self): """测试默认值""" summary = CostSummary() assert summary.total_cost == 0.0 assert summary.salary_cost == 0.0 assert summary.social_security_cost == 0.0 assert summary.fund_cost == 0.0 assert summary.employee_count == 0 def test_init_with_values(self): """测试带值初始化""" summary = CostSummary( total_cost=50000, salary_cost=30000, social_security_cost=10000, fund_cost=10000, employee_count=10, ) assert summary.total_cost == 50000 assert summary.salary_cost == 30000 assert summary.employee_count == 10 def test_to_dict(self): """测试转字典""" summary = CostSummary(total_cost=10000, salary_cost=8000, employee_count=5) d = summary.to_dict() assert d["total_cost"] == 10000 assert d["salary_cost"] == 8000 assert d["employee_count"] == 5 class TestDepartmentCost: """部门成本测试""" def test_init(self): """测试初始化""" dept = DepartmentCost(department="技术部", employee_count=10, salary_cost=100000) assert dept.department == "技术部" assert dept.employee_count == 10 assert dept.salary_cost == 100000 def test_total_cost_property(self): """测试 total_cost 属性计算""" dept = DepartmentCost( department="财务部", salary_cost=10000, social_security_cost=3000, fund_cost=1200, ) assert dept.total_cost == 14200 def test_to_dict(self): """测试转字典""" dept = DepartmentCost(department="技术部", salary_cost=10000, employee_count=5) d = dept.to_dict() assert d["department"] == "技术部" assert d["total_cost"] == 10000 assert d["employee_count"] == 5 class TestCostCalculatorService: """成本计算服务测试""" def test_init(self, mock_db): """测试初始化""" service = CostCalculatorService(mock_db) assert service.db == mock_db @pytest.mark.asyncio async def test_calculate_total_cost(self, cost_calculator): """测试计算总成本""" mock_data = [ {"应发工资": 10000, "养老保险(公司)": 2000, "医疗保险(公司)": 1000, "失业保险(公司)": 500, "公积金(公司)": 1200}, {"应发工资": 8000, "养老保险(公司)": 1600, "医疗保险(公司)": 800, "失业保险(公司)": 400, "公积金(公司)": 960}, ] with patch.object( cost_calculator, "_load_cleaned_data", return_value=mock_data ): summary = await cost_calculator.calculate_total_cost(task_id=1) assert summary.employee_count == 2 assert summary.salary_cost == 18000 assert summary.social_security_cost == 6300 # (2000+1000+500) + (1600+800+400) assert summary.fund_cost == 2160 # 1200 + 960 assert summary.total_cost == 18000 + 6300 + 2160 @pytest.mark.asyncio async def test_calculate_total_cost_empty(self, cost_calculator): """测试空数据计算""" with patch.object( cost_calculator, "_load_cleaned_data", return_value=[] ): summary = await cost_calculator.calculate_total_cost(task_id=1) assert summary.employee_count == 0 assert summary.total_cost == 0.0 @pytest.mark.asyncio async def test_calculate_by_department(self, cost_calculator): """测试按部门汇总""" mock_data = [ {"部门": "技术部", "应发工资": 10000, "养老保险(公司)": 2000, "公积金(公司)": 1200}, {"部门": "技术部", "应发工资": 8000, "养老保险(公司)": 1600, "公积金(公司)": 960}, {"部门": "财务部", "应发工资": 12000, "养老保险(公司)": 2400, "公积金(公司)": 1440}, ] with patch.object( cost_calculator, "_load_cleaned_data", return_value=mock_data ): departments = await cost_calculator.calculate_by_department(task_id=1) assert len(departments) == 2 tech = [d for d in departments if d.department == "技术部"][0] assert tech.employee_count == 2 assert tech.salary_cost == 18000 finance = [d for d in departments if d.department == "财务部"][0] assert finance.employee_count == 1 assert finance.salary_cost == 12000