feat: 凭证生成、成本分析、AI问答、前端页面、集成测试与E2E测试
- 后端: 凭证生成引擎、金蝶导出器、凭证模板服务 - 后端: 成本分析服务、AI问答服务 - 后端: 科目映射CRUD API、分析API、QA API - 后端: 集成测试(认证/任务/凭证) 49个测试全部通过 - 前端: 凭证管理、成本分析、导出中心、知识库、系统设置页面 - 前端: AuthGuard认证守卫、Dashboard AI聊天功能 - 前端: Playwright E2E测试 16 passed, 1 skipped - 基础设施: Docker Compose、Nginx反向代理、.env.example - 文档: 用户手册、管理员手册、发布检查清单
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
成本分析计算服务单元测试
|
||||
|
||||
测试 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
|
||||
|
||||
Reference in New Issue
Block a user