""" 凭证 API 集成测试 测试科目映射 CRUD、凭证模板等端点 使用同步 TestClient + Mock 数据库会话 """ from unittest.mock import AsyncMock, MagicMock from fastapi.testclient import TestClient from app.models.account_mapping import AccountMapping from tests.conftest import make_db_result def _make_mapping(mapping_id=1, field="基本工资", debit="6601.01", credit="2211.01"): """构造测试科目映射对象""" return AccountMapping( id=mapping_id, company_id=1, standard_field=field, debit_account=debit, debit_account_name=f"管理费用-{field}", credit_account=credit, credit_account_name="应付职工薪酬", cost_center=None, is_active=True, ) class TestVoucherTemplatesAPI: """凭证模板 API 测试""" def test_get_templates(self, client: TestClient, auth_headers: dict): """测试获取默认凭证模板""" response = client.get("/api/vouchers/templates/list", headers=auth_headers) assert response.status_code == 200 data = response.json() assert "基本工资" in data assert "养老保险(公司)" in data def test_get_templates_no_auth(self, client: TestClient): """测试无认证访问模板(该端点不需要认证)""" response = client.get("/api/vouchers/templates/list") assert response.status_code == 200 class TestAccountMappingsAPI: """科目映射 CRUD API 测试""" def test_list_mappings_empty(self, client: TestClient, auth_headers: dict, mock_db: AsyncMock): """测试空映射列表""" mock_db.execute = AsyncMock(return_value=make_db_result(items=[])) response = client.get( "/api/vouchers/account-mappings/list", headers=auth_headers ) assert response.status_code == 200 assert response.json() == [] def test_create_mapping(self, client: TestClient, auth_headers: dict, mock_db: AsyncMock): """测试创建科目映射""" def refresh_side_effect(obj, *args, **kwargs): obj.id = 1 obj.is_active = True mock_db.refresh.side_effect = refresh_side_effect response = client.post( "/api/vouchers/account-mappings", headers=auth_headers, json={ "standard_field": "基本工资", "debit_account": "6601.01", "debit_account_name": "管理费用-工资", "credit_account": "2211.01", "credit_account_name": "应付职工薪酬-工资", }, ) assert response.status_code == 200 data = response.json() assert data["standard_field"] == "基本工资" assert data["debit_account"] == "6601.01" assert data["is_active"] is True def test_create_and_list_mappings(self, client: TestClient, auth_headers: dict, mock_db: AsyncMock): """测试创建后查询映射列表""" mappings = [_make_mapping(1, "基本工资"), _make_mapping(2, "奖金", "6601.02")] mock_db.execute = AsyncMock(return_value=make_db_result(items=mappings)) response = client.get( "/api/vouchers/account-mappings/list", headers=auth_headers ) assert response.status_code == 200 data = response.json() assert len(data) == 2 def test_update_mapping(self, client: TestClient, auth_headers: dict, mock_db: AsyncMock): """测试更新科目映射""" existing = _make_mapping(1) mock_db.get = AsyncMock(return_value=existing) def refresh_side_effect(obj, *args, **kwargs): pass mock_db.refresh.side_effect = refresh_side_effect response = client.put( "/api/vouchers/account-mappings/1", headers=auth_headers, json={ "standard_field": "基本工资", "debit_account": "6601.03", "debit_account_name": "销售费用-工资", "credit_account": "2211.01", "credit_account_name": "应付职工薪酬-工资", }, ) assert response.status_code == 200 data = response.json() assert data["debit_account"] == "6601.03" def test_update_mapping_not_found(self, client: TestClient, auth_headers: dict, mock_db: AsyncMock): """测试更新不存在的映射""" mock_db.get = AsyncMock(return_value=None) response = client.put( "/api/vouchers/account-mappings/99999", headers=auth_headers, json={ "standard_field": "基本工资", "debit_account": "6601.01", "debit_account_name": "管理费用-工资", "credit_account": "2211.01", "credit_account_name": "应付职工薪酬-工资", }, ) assert response.status_code == 404 def test_delete_mapping(self, client: TestClient, auth_headers: dict, mock_db: AsyncMock): """测试删除科目映射""" existing = _make_mapping(1, "奖金") mock_db.get = AsyncMock(return_value=existing) response = client.delete( "/api/vouchers/account-mappings/1", headers=auth_headers ) assert response.status_code == 200 def test_delete_mapping_not_found(self, client: TestClient, auth_headers: dict, mock_db: AsyncMock): """测试删除不存在的映射""" mock_db.get = AsyncMock(return_value=None) response = client.delete( "/api/vouchers/account-mappings/99999", headers=auth_headers ) assert response.status_code == 404 def test_create_mapping_with_cost_center(self, client: TestClient, auth_headers: dict, mock_db: AsyncMock): """测试带成本中心创建映射""" def refresh_side_effect(obj, *args, **kwargs): obj.id = 1 obj.is_active = True mock_db.refresh.side_effect = refresh_side_effect response = client.post( "/api/vouchers/account-mappings", headers=auth_headers, json={ "standard_field": "基本工资", "debit_account": "6601.01", "debit_account_name": "管理费用-工资", "credit_account": "2211.01", "credit_account_name": "应付职工薪酬-工资", "cost_center": "技术部", }, ) assert response.status_code == 200 data = response.json() assert data["cost_center"] == "技术部"