""" 完整闭环测试用例 - End-to-End Tests 测试覆盖完整的业务流程: 1. 用户登录 -> 获取Token 2. 科室管理 -> CRUD操作 3. 员工管理 -> CRUD操作 4. 考核指标管理 -> CRUD操作 5. 考核流程 -> 创建、提交、审核、确认 6. 满意度调查 -> 创建问卷、提交回答、统计 7. 评分方法计算 -> 各种评分方法验证 8. BSC维度权重 -> 配置和计算 """ import pytest import httpx import asyncio from typing import Optional # API基础URL BASE_URL = "http://localhost:8000/api/v1" class TestAuth: """认证测试""" @pytest.mark.asyncio async def test_login_success(self): """测试登录成功""" async with httpx.AsyncClient() as client: response = await client.post( f"{BASE_URL}/auth/login-json", json={"username": "admin", "password": "admin123"} ) assert response.status_code == 200 data = response.json() assert data["code"] == 200 assert "access_token" in data["data"] return data["data"]["access_token"] @pytest.mark.asyncio async def test_login_failure(self): """测试登录失败""" async with httpx.AsyncClient() as client: response = await client.post( f"{BASE_URL}/auth/login-json", json={"username": "admin", "password": "wrong_password"} ) assert response.status_code == 401 class TestDepartmentManagement: """科室管理闭环测试""" @pytest.fixture async def auth_token(self): """获取认证Token""" async with httpx.AsyncClient() as client: response = await client.post( f"{BASE_URL}/auth/login-json", json={"username": "admin", "password": "admin123"} ) return response.json()["data"]["access_token"] @pytest.mark.asyncio async def test_get_department_list(self, auth_token): """测试获取科室列表""" async with httpx.AsyncClient() as client: response = await client.get( f"{BASE_URL}/departments", headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 200 data = response.json() assert data["code"] == 200 assert isinstance(data["data"], list) @pytest.mark.asyncio async def test_create_and_delete_department(self, auth_token): """测试创建和删除科室""" async with httpx.AsyncClient() as client: headers = {"Authorization": f"Bearer {auth_token}"} # 创建科室 create_response = await client.post( f"{BASE_URL}/departments", headers=headers, json={ "name": "测试科室E2E", "code": "TEST_E2E_001", "dept_type": "clinical_surgical", "description": "E2E测试用科室" } ) assert create_response.status_code == 200 created = create_response.json() assert created["code"] == 200 dept_id = created["data"]["id"] # 获取科室详情 get_response = await client.get( f"{BASE_URL}/departments/{dept_id}", headers=headers ) assert get_response.status_code == 200 # 删除科室 delete_response = await client.delete( f"{BASE_URL}/departments/{dept_id}", headers=headers ) assert delete_response.status_code == 200 class TestStaffManagement: """员工管理闭环测试""" @pytest.fixture async def auth_token(self): async with httpx.AsyncClient() as client: response = await client.post( f"{BASE_URL}/auth/login", json={"username": "admin", "password": "admin123"} ) return response.json()["data"]["access_token"] @pytest.fixture async def department_id(self, auth_token): """获取一个科室ID用于测试""" async with httpx.AsyncClient() as client: response = await client.get( f"{BASE_URL}/departments", headers={"Authorization": f"Bearer {auth_token}"} ) departments = response.json()["data"] if departments: return departments[0]["id"] return None @pytest.mark.asyncio async def test_get_staff_list(self, auth_token): """测试获取员工列表""" async with httpx.AsyncClient() as client: response = await client.get( f"{BASE_URL}/staff", headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 200 data = response.json() assert data["code"] == 200 class TestIndicatorManagement: """考核指标管理闭环测试""" @pytest.fixture async def auth_token(self): async with httpx.AsyncClient() as client: response = await client.post( f"{BASE_URL}/auth/login", json={"username": "admin", "password": "admin123"} ) return response.json()["data"]["access_token"] @pytest.mark.asyncio async def test_get_indicator_list(self, auth_token): """测试获取指标列表""" async with httpx.AsyncClient() as client: response = await client.get( f"{BASE_URL}/indicators", headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 200 data = response.json() assert data["code"] == 200 @pytest.mark.asyncio async def test_create_indicator(self, auth_token): """测试创建指标""" async with httpx.AsyncClient() as client: response = await client.post( f"{BASE_URL}/indicators", headers={"Authorization": f"Bearer {auth_token}"}, json={ "name": "E2E测试指标", "code": "E2E_TEST_001", "indicator_type": "quality", "bs_dimension": "internal_process", "weight": 1.0, "max_score": 100, "target_value": 95, "target_unit": "%", "calculation_method": "测试计算方法", "assessment_method": "interval_high" } ) assert response.status_code == 200 data = response.json() assert data["code"] == 200 # 清理:删除创建的指标 indicator_id = data["data"]["id"] async with httpx.AsyncClient() as client: await client.delete( f"{BASE_URL}/indicators/{indicator_id}", headers={"Authorization": f"Bearer {auth_token}"} ) class TestAssessmentWorkflow: """考核流程闭环测试""" @pytest.fixture async def auth_token(self): async with httpx.AsyncClient() as client: response = await client.post( f"{BASE_URL}/auth/login", json={"username": "admin", "password": "admin123"} ) return response.json()["data"]["access_token"] @pytest.mark.asyncio async def test_get_assessment_list(self, auth_token): """测试获取考核列表""" async with httpx.AsyncClient() as client: response = await client.get( f"{BASE_URL}/assessments", headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 200 data = response.json() assert data["code"] == 200 class TestSurveyManagement: """满意度调查闭环测试""" @pytest.fixture async def auth_token(self): async with httpx.AsyncClient() as client: response = await client.post( f"{BASE_URL}/auth/login", json={"username": "admin", "password": "admin123"} ) return response.json()["data"]["access_token"] @pytest.mark.asyncio async def test_get_survey_list(self, auth_token): """测试获取问卷列表""" async with httpx.AsyncClient() as client: response = await client.get( f"{BASE_URL}/surveys", headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 200 data = response.json() assert data["code"] == 200 assert isinstance(data["data"], list) @pytest.mark.asyncio async def test_get_survey_detail(self, auth_token): """测试获取问卷详情""" # 先获取问卷列表 async with httpx.AsyncClient() as client: list_response = await client.get( f"{BASE_URL}/surveys", headers={"Authorization": f"Bearer {auth_token}"} ) surveys = list_response.json()["data"] if surveys: survey_id = surveys[0]["id"] detail_response = await client.get( f"{BASE_URL}/surveys/{survey_id}", headers={"Authorization": f"Bearer {auth_token}"} ) assert detail_response.status_code == 200 data = detail_response.json() assert data["code"] == 200 assert data["data"]["id"] == survey_id @pytest.mark.asyncio async def test_create_and_close_survey(self, auth_token): """测试创建和关闭问卷""" async with httpx.AsyncClient() as client: headers = {"Authorization": f"Bearer {auth_token}"} # 创建问卷 create_response = await client.post( f"{BASE_URL}/surveys", headers=headers, json={ "survey_name": "E2E测试问卷", "survey_code": "E2E_TEST_SURVEY_001", "survey_type": "inpatient", "description": "E2E测试用问卷", "is_anonymous": True, "questions": [ { "question_text": "您对服务是否满意?", "question_type": "score", "score_max": 5, "is_required": True } ] } ) assert create_response.status_code == 200 created = create_response.json() assert created["code"] == 200 survey_id = created["data"]["id"] # 发布问卷 publish_response = await client.post( f"{BASE_URL}/surveys/{survey_id}/publish", headers=headers ) assert publish_response.status_code == 200 # 关闭问卷 close_response = await client.post( f"{BASE_URL}/surveys/{survey_id}/close", headers=headers ) assert close_response.status_code == 200 @pytest.mark.asyncio async def test_get_department_satisfaction(self, auth_token): """测试获取科室满意度统计""" async with httpx.AsyncClient() as client: response = await client.get( f"{BASE_URL}/surveys/stats/department", headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 200 data = response.json() assert data["code"] == 200 class TestScoringMethodIntegration: """评分方法集成测试""" @pytest.mark.asyncio async def test_target_reference_method(self): """测试目标参照法(单元测试集成)""" from app.services.scoring_service import ScoringService, ScoringMethod, ScoringParams params = ScoringParams(weight=12.6, target_value=15.0) result = ScoringService.calculate( ScoringMethod.TARGET_REFERENCE, actual_value=18.0, params=params ) assert result.score == 15.12 # 12.6 * (18/15) @pytest.mark.asyncio async def test_interval_high_method(self): """测试区间法-趋高指标""" from app.services.scoring_service import ScoringService, ScoringMethod, ScoringParams params = ScoringParams( weight=12.6, best_value=100, baseline_value=80, worst_value=0 ) result = ScoringService.calculate( ScoringMethod.INTERVAL_HIGH, actual_value=100, params=params ) assert result.score == 12.6 # 达到最佳值,满分 @pytest.mark.asyncio async def test_interval_low_method(self): """测试区间法-趋低指标""" from app.services.scoring_service import ScoringService, ScoringMethod, ScoringParams params = ScoringParams(weight=12.6, target_value=25.0) result = ScoringService.calculate( ScoringMethod.INTERVAL_LOW, actual_value=30.0, params=params ) # 12.6 * (25/30) = 10.5 assert result.score == 10.5 @pytest.mark.asyncio async def test_deduction_method(self): """测试扣分法""" from app.services.scoring_service import ScoringService, ScoringMethod, ScoringParams params = ScoringParams(weight=3.2, deduction_per_unit=5.0) result = ScoringService.calculate( ScoringMethod.DEDUCTION, actual_value=1, # 1次违规 params=params ) assert result.score == 0 # 3.2 - 5 = -1.8 -> 0 (不计负分) @pytest.mark.asyncio async def test_bonus_method(self): """测试加分法""" from app.services.scoring_service import ScoringService, ScoringMethod, ScoringParams params = ScoringParams( weight=4.5, bonus_per_unit=2.0, max_bonus_ratio=0.5 ) result = ScoringService.calculate( ScoringMethod.BONUS, actual_value=2, # 2项加分 params=params ) assert result.score == 8.5 # 4.5 + 2*2 = 8.5 class TestDimensionWeight: """BSC维度权重测试""" @pytest.mark.asyncio async def test_default_weights(self): """测试默认权重配置""" from app.services.dimension_weight_service import DimensionWeightService, DEFAULT_WEIGHTS from app.models.models import DeptType # 测试手术临床科室权重 weights = DimensionWeightService.get_dimension_weights(DeptType.CLINICAL_SURGICAL) assert weights["financial"] == 0.60 assert weights["customer"] == 0.15 assert weights["internal_process"] == 0.20 assert weights["learning_growth"] == 0.05 # 测试护理单元权重 weights = DimensionWeightService.get_dimension_weights(DeptType.NURSING) assert weights["financial"] == 0.20 assert weights["internal_process"] == 0.50 # 护理单元内部流程权重最高 @pytest.mark.asyncio async def test_weight_sum_to_one(self): """测试所有权重总和为1""" from app.services.dimension_weight_service import DEFAULT_WEIGHTS for dept_type, weights in DEFAULT_WEIGHTS.items(): total = ( weights["financial"] + weights["customer"] + weights["internal_process"] + weights["learning_growth"] ) assert abs(total - 1.0) < 0.01, f"{dept_type} 权重总和不为1" class TestStatsAPI: """统计报表API测试""" @pytest.fixture async def auth_token(self): async with httpx.AsyncClient() as client: response = await client.post( f"{BASE_URL}/auth/login", json={"username": "admin", "password": "admin123"} ) return response.json()["data"]["access_token"] @pytest.mark.asyncio async def test_get_bsc_dimension_stats(self, auth_token): """测试获取BSC维度统计""" async with httpx.AsyncClient() as client: response = await client.get( f"{BASE_URL}/stats/bsc-dimension", headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 200 @pytest.mark.asyncio async def test_get_department_stats(self, auth_token): """测试获取科室统计""" async with httpx.AsyncClient() as client: response = await client.get( f"{BASE_URL}/stats/department", headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 200 @pytest.mark.asyncio async def test_get_ranking_stats(self, auth_token): """测试获取排名统计""" async with httpx.AsyncClient() as client: response = await client.get( f"{BASE_URL}/stats/ranking", headers={"Authorization": f"Bearer {auth_token}"} ) assert response.status_code == 200 class TestHealthCheck: """健康检查测试""" @pytest.mark.asyncio async def test_health_endpoint(self): """测试健康检查端点""" async with httpx.AsyncClient() as client: response = await client.get("http://localhost:8000/health") assert response.status_code == 200 data = response.json() assert data["status"] == "healthy" @pytest.mark.asyncio async def test_api_docs_available(self): """测试API文档可访问""" async with httpx.AsyncClient() as client: response = await client.get(f"{BASE_URL}/docs") assert response.status_code == 200