""" 满意度调查服务测试用例 测试覆盖: 1. 问卷管理功能 2. 题目管理功能 3. 回答提交功能 4. 统计分析功能 """ import pytest from unittest.mock import AsyncMock, MagicMock, patch from datetime import datetime from app.services.survey_service import SurveyService from app.models.models import ( Survey, SurveyQuestion, SurveyResponse, SurveyAnswer, SurveyStatus, SurveyType, QuestionType ) class TestSurveyManagement: """问卷管理测试""" @pytest.mark.asyncio async def test_create_survey(self): """测试创建问卷""" mock_db = AsyncMock() # 模拟数据库操作 survey = MagicMock() survey.id = 1 survey.survey_name = "测试问卷" survey.survey_code = "TEST_001" survey.survey_type = SurveyType.INPATIENT survey.status = SurveyStatus.DRAFT with patch.object(SurveyService, 'get_survey_by_id', return_value=None): with patch.object(SurveyService, 'get_survey_list', return_value=([], 0)): result = await SurveyService.create_survey( mock_db, survey_name="测试问卷", survey_code="TEST_001", survey_type=SurveyType.INPATIENT ) mock_db.add.assert_called_once() @pytest.mark.asyncio async def test_publish_survey(self): """测试发布问卷""" mock_db = AsyncMock() # 创建模拟问卷 survey = MagicMock() survey.id = 1 survey.status = SurveyStatus.DRAFT survey.total_questions = 5 with patch.object(SurveyService, 'get_survey_by_id', return_value=survey): result = await SurveyService.publish_survey(mock_db, 1) assert survey.status == SurveyStatus.PUBLISHED mock_db.flush.assert_called() @pytest.mark.asyncio async def test_publish_survey_without_questions(self): """测试发布无题目的问卷(应失败)""" mock_db = AsyncMock() survey = MagicMock() survey.id = 1 survey.status = SurveyStatus.DRAFT survey.total_questions = 0 with patch.object(SurveyService, 'get_survey_by_id', return_value=survey): with pytest.raises(ValueError, match="问卷没有题目"): await SurveyService.publish_survey(mock_db, 1) @pytest.mark.asyncio async def test_close_survey(self): """测试结束问卷""" mock_db = AsyncMock() survey = MagicMock() survey.id = 1 survey.status = SurveyStatus.PUBLISHED with patch.object(SurveyService, 'get_survey_by_id', return_value=survey): result = await SurveyService.close_survey(mock_db, 1) assert survey.status == SurveyStatus.CLOSED class TestQuestionManagement: """题目管理测试""" @pytest.mark.asyncio async def test_add_question(self): """测试添加题目""" mock_db = AsyncMock() survey = MagicMock() survey.id = 1 survey.status = SurveyStatus.DRAFT survey.total_questions = 0 with patch.object(SurveyService, 'get_survey_by_id', return_value=survey): # 模拟查询最大排序号 mock_result = MagicMock() mock_result.scalar.return_value = None with patch('app.services.survey_service.select') as mock_select: mock_db.execute.return_value = mock_result result = await SurveyService.add_question( mock_db, survey_id=1, question_text="您对服务是否满意?", question_type=QuestionType.SCORE ) mock_db.add.assert_called() @pytest.mark.asyncio async def test_add_question_to_published_survey(self): """测试向已发布问卷添加题目(应失败)""" mock_db = AsyncMock() survey = MagicMock() survey.id = 1 survey.status = SurveyStatus.PUBLISHED with patch.object(SurveyService, 'get_survey_by_id', return_value=survey): with pytest.raises(ValueError, match="只有草稿状态的问卷可以添加题目"): await SurveyService.add_question( mock_db, survey_id=1, question_text="测试题目", question_type=QuestionType.SCORE ) class TestResponseSubmission: """回答提交测试""" @pytest.mark.asyncio async def test_submit_response(self): """测试提交问卷回答""" mock_db = AsyncMock() # 创建模拟问卷 survey = MagicMock() survey.id = 1 survey.status = SurveyStatus.PUBLISHED # 创建模拟题目 question = MagicMock() question.id = 1 question.question_type = QuestionType.SCORE question.score_max = 5 question.options = None # 设置mock返回值 mock_q_result = MagicMock() mock_q_result.scalar_one_or_none.return_value = question with patch.object(SurveyService, 'get_survey_by_id', return_value=survey): with patch('app.services.survey_service.select') as mock_select: mock_db.execute.return_value = mock_q_result result = await SurveyService.submit_response( mock_db, survey_id=1, department_id=1, answers=[ {"question_id": 1, "answer_value": "4"} ] ) mock_db.add.assert_called() @pytest.mark.asyncio async def test_submit_response_to_closed_survey(self): """测试向已结束问卷提交回答(应失败)""" mock_db = AsyncMock() survey = MagicMock() survey.id = 1 survey.status = SurveyStatus.CLOSED with patch.object(SurveyService, 'get_survey_by_id', return_value=survey): with pytest.raises(ValueError, match="问卷未发布或已结束"): await SurveyService.submit_response( mock_db, survey_id=1, department_id=1, answers=[] ) class TestSatisfactionStats: """满意度统计测试""" @pytest.mark.asyncio async def test_get_department_satisfaction(self): """测试获取科室满意度统计""" mock_db = AsyncMock() # 模拟查询结果 mock_result = MagicMock() mock_row = MagicMock() mock_row.department_id = 1 mock_row.department_name = "内科" mock_row.response_count = 10 mock_row.avg_satisfaction = 85.5 mock_row.total_score = 850.0 mock_row.max_score = 1000.0 mock_result.fetchall.return_value = [mock_row] with patch('app.services.survey_service.select') as mock_select: mock_db.execute.return_value = mock_result result = await SurveyService.get_department_satisfaction( mock_db, survey_id=1 ) assert len(result) == 1 assert result[0]["department_name"] == "内科" assert result[0]["avg_satisfaction"] == 85.5 class TestQuestionTypes: """不同题型测试""" def test_score_question_options(self): """测试评分题选项""" question = MagicMock() question.question_type = QuestionType.SCORE question.score_max = 5 question.options = None # 评分题不需要选项 # 评分题不需要选项 assert question.options is None def test_single_choice_options(self): """测试单选题选项""" import json options = [ {"label": "非常满意", "value": "5", "score": 5}, {"label": "满意", "value": "4", "score": 4}, {"label": "一般", "value": "3", "score": 3}, {"label": "不满意", "value": "2", "score": 2} ] question = MagicMock() question.question_type = QuestionType.SINGLE_CHOICE question.options = json.dumps(options, ensure_ascii=False) parsed_options = json.loads(question.options) assert len(parsed_options) == 4 def test_multiple_choice_options(self): """测试多选题选项""" import json options = [ {"label": "选项A", "value": "a", "score": 1}, {"label": "选项B", "value": "b", "score": 1}, {"label": "选项C", "value": "c", "score": 1} ] question = MagicMock() question.question_type = QuestionType.MULTIPLE_CHOICE question.options = json.dumps(options, ensure_ascii=False) parsed_options = json.loads(question.options) assert len(parsed_options) == 3