第 9 个 RPC GenerateReport(学情报告生成):data-ana 学情数据 → LLM 生成 → 结构化提取 新增 ReportService 业务编排层 + GenerateReportRequest/GeneratedReport 模型 gRPC servicer + HTTP POST /v1/ai/generate/report(权限 ai:report:generate) proto_gen 重新生成 + 测试覆盖(servicer/service/HTTP/模型/权限 共 26 用例) 402 测试通过,覆盖率 88.5%
175 lines
5.8 KiB
Python
175 lines
5.8 KiB
Python
"""Pydantic 模型校验测试."""
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from src.ai.models.chat import ChatData, ChatMessage, ChatRequest, Usage
|
|
from src.ai.models.expression import OptimizeExpressionRequest
|
|
from src.ai.models.question import GeneratedQuestionData, GenerateQuestionRequest
|
|
from src.ai.models.report import (
|
|
GeneratedReportData,
|
|
GenerateReportRequest,
|
|
)
|
|
from src.ai.models.workflow import (
|
|
ConfirmRequest,
|
|
LessonPreparationRequest,
|
|
WorkflowStatusData,
|
|
)
|
|
|
|
|
|
class TestChatModels:
|
|
"""聊天模型测试."""
|
|
|
|
def test_chat_message_valid(self) -> None:
|
|
msg = ChatMessage(role="user", content="hello")
|
|
assert msg.role == "user"
|
|
|
|
def test_chat_message_empty_content_rejected(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
ChatMessage(role="user", content="")
|
|
|
|
def test_chat_message_invalid_role(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
ChatMessage(role="invalid", content="text")
|
|
|
|
def test_chat_request_defaults(self) -> None:
|
|
req = ChatRequest(messages=[ChatMessage(role="user", content="hi")])
|
|
assert req.model == "gpt-4o-mini"
|
|
assert req.temperature == 0.7
|
|
assert req.stream is False
|
|
|
|
def test_chat_request_empty_messages_rejected(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
ChatRequest(messages=[])
|
|
|
|
def test_chat_request_temperature_range(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
ChatRequest(
|
|
messages=[ChatMessage(role="user", content="hi")],
|
|
temperature=3.0,
|
|
)
|
|
|
|
def test_usage_defaults(self) -> None:
|
|
usage = Usage()
|
|
assert usage.prompt_tokens == 0
|
|
assert usage.total_tokens == 0
|
|
|
|
def test_chat_data_degraded_fields(self) -> None:
|
|
data = ChatData(
|
|
content="x",
|
|
model="m",
|
|
usage=Usage(),
|
|
degraded=True,
|
|
degraded_reason="test",
|
|
)
|
|
assert data.degraded is True
|
|
|
|
|
|
class TestQuestionModels:
|
|
"""题目模型测试."""
|
|
|
|
def test_generate_question_request_defaults(self) -> None:
|
|
req = GenerateQuestionRequest(prompt="生成题", subject="数学")
|
|
assert req.difficulty == "medium"
|
|
assert req.question_type == "short_answer"
|
|
assert req.count == 1
|
|
|
|
def test_invalid_difficulty(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
GenerateQuestionRequest(prompt="x", subject="数学", difficulty="impossible")
|
|
|
|
def test_invalid_question_type(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
GenerateQuestionRequest(prompt="x", subject="数学", question_type="invalid")
|
|
|
|
def test_count_range(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
GenerateQuestionRequest(prompt="x", subject="数学", count=0)
|
|
with pytest.raises(ValidationError):
|
|
GenerateQuestionRequest(prompt="x", subject="数学", count=11)
|
|
|
|
def test_generated_question_data_defaults(self) -> None:
|
|
data = GeneratedQuestionData(question="q", answer="a", explanation="e")
|
|
assert data.question_type == "short_answer"
|
|
assert data.degraded is False
|
|
assert data.evaluation_score is None
|
|
|
|
|
|
class TestExpressionModels:
|
|
"""表达优化模型测试."""
|
|
|
|
def test_valid_request(self) -> None:
|
|
req = OptimizeExpressionRequest(text="优化这段")
|
|
assert req.text == "优化这段"
|
|
assert req.context == ""
|
|
|
|
def test_empty_text_rejected(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
OptimizeExpressionRequest(text="")
|
|
|
|
|
|
class TestWorkflowModels:
|
|
"""工作流模型测试."""
|
|
|
|
def test_lesson_preparation_request_defaults(self) -> None:
|
|
req = LessonPreparationRequest(
|
|
class_id="c-1",
|
|
subject_id="math",
|
|
topic="代数",
|
|
)
|
|
assert req.target_difficulty == "medium"
|
|
assert req.question_count == 5
|
|
|
|
def test_question_count_range(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
LessonPreparationRequest(
|
|
class_id="c-1",
|
|
subject_id="math",
|
|
topic="t",
|
|
question_count=0,
|
|
)
|
|
|
|
def test_confirm_request_optional(self) -> None:
|
|
req = ConfirmRequest()
|
|
assert req.modifications is None
|
|
|
|
def test_workflow_status_data_defaults(self) -> None:
|
|
data = WorkflowStatusData(workflow_id="wf-1", status="pending")
|
|
assert data.questions == []
|
|
assert data.error is None
|
|
assert data.degraded is False
|
|
|
|
|
|
class TestReportModels:
|
|
"""学情报告模型测试."""
|
|
|
|
def test_generate_report_request_defaults(self) -> None:
|
|
req = GenerateReportRequest(class_id="c-1")
|
|
assert req.report_type == "class_summary"
|
|
assert req.student_id is None
|
|
assert req.user_id is None
|
|
assert req.data_scope is None
|
|
|
|
def test_generate_report_request_invalid_type(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
GenerateReportRequest(class_id="c-1", report_type="invalid")
|
|
|
|
def test_generate_report_request_missing_class_id(self) -> None:
|
|
with pytest.raises(ValidationError):
|
|
GenerateReportRequest()
|
|
|
|
def test_generate_report_request_student_detail(self) -> None:
|
|
req = GenerateReportRequest(
|
|
class_id="c-1",
|
|
report_type="student_detail",
|
|
student_id="s-001",
|
|
)
|
|
assert req.report_type == "student_detail"
|
|
assert req.student_id == "s-001"
|
|
|
|
def test_generated_report_data_defaults(self) -> None:
|
|
data = GeneratedReportData(id="r-1", content="内容", summary="摘要")
|
|
assert data.recommendations == []
|
|
assert data.degraded is False
|
|
assert data.degraded_reason == ""
|