Files
WiseGrade/backend/tests/test_api.py
T

113 lines
6.7 KiB
Python

from fastapi.testclient import TestClient
from sqlalchemy import select
from wisegrade import agent_tools
from wisegrade.db import get_db
from wisegrade.main import app
from wisegrade.models import (Conversation, CourseVersion, CriterionScore, KnowledgeChunk,
KnowledgePage, Project, RubricVersion, Student)
from wisegrade.rag import ingest
from wisegrade.scoring import run_batch
def client_for(session_factory):
def override_db():
with session_factory() as db:
yield db
app.dependency_overrides[get_db] = override_db
return TestClient(app)
def test_file_page_has_text_and_image_virtual_page_has_text_only(session_factory, environment, monkeypatch):
image = environment / "processed" / "course-1" / "report" / "page-49.png"
image.parent.mkdir(parents=True)
image.write_bytes(b"\x89PNG\r\n\x1a\n")
vectors = lambda texts: [[0.1] * 1024 for _ in texts]
with session_factory() as db:
document = ingest(db, course_version_id=1, project_id=None, submission_file_id=None,
title="课程报告.pdf", category="course", source_hash="file",
pages=[{"page_number": 49, "text": "第四十九页完整文字", "tables": [{"rows": [["指标", "结果"]]}],
"rendered_image_path": str(image)}], embedder=vectors)
virtual = ingest(db, course_version_id=1, project_id=None, submission_file_id=None,
title="系统生成规则", category="course", source_hash="virtual",
pages=[{"page_number": 1, "text": "规则文字"}], embedder=vectors)
assert db.get(KnowledgePage, (document.id, 49)).image_path == "course-1/report/page-49.png"
chunk = db.scalar(select(KnowledgeChunk).where(KnowledgeChunk.document_id == document.id))
monkeypatch.setattr(agent_tools, "retrieve", lambda *_, **__: [{"chunk_id": chunk.id,
"document_id": document.id, "project_id": None, "page": 49, "text": chunk.text}])
citation = agent_tools.execute(db, Conversation(course_version_id=1), "search_course_material",
{"course_version_id": 1, "query": "完整文字"}, "")["citations"][0]
assert citation["url"] == f"/api/knowledge/documents/{document.id}/pages/49"
assert citation["image_url"] == citation["url"] + "/image"
document_id, virtual_id = document.id, virtual.id
client = client_for(session_factory)
try:
resource = client.get(f"/api/knowledge/documents/{document_id}/pages/49")
assert resource.status_code == 200
assert resource.json()["text"] == "第四十九页完整文字\n表格: [['指标', '结果']]"
image_url = resource.json()["image_url"]
assert image_url == f"/api/knowledge/documents/{document_id}/pages/49/image"
assert client.get(image_url).content.startswith(b"\x89PNG")
virtual_resource = client.get(f"/api/knowledge/documents/{virtual_id}/pages/1")
assert virtual_resource.json()["text"] == "规则文字"
assert virtual_resource.json()["image_url"] is None
assert client.get(f"/api/knowledge/documents/{virtual_id}/pages/1/image").status_code == 404
finally:
app.dependency_overrides.clear()
def test_course_and_rubric_versions_are_immutable(session_factory):
client = client_for(session_factory)
try:
new = client.post("/api/courses/1/versions", json={"title": "新学期规则", "source_version_id": 1})
assert new.status_code == 200
version = new.json()
version["objectives"][0]["name"] = "修订后的目标"
edited = client.put(f"/api/course-versions/{version['id']}", json={key: version[key] for key in
("title", "objectives", "indicators", "directions", "prompts", "templates")})
assert edited.status_code == 200
assert client.post(f"/api/course-versions/{version['id']}/publish").status_code == 200
assert client.put(f"/api/course-versions/{version['id']}", json={key: version[key] for key in
("title", "objectives", "indicators", "directions", "prompts", "templates")}).status_code == 409
rubric = client.get(f"/api/course-versions/{version['id']}/rubrics").json()[0]
rubric["criteria"][0]["name"] = "修订后的评分项"
assert client.put(f"/api/rubrics/{rubric['id']}", json={"criteria": rubric["criteria"], "weights": rubric["weights"]}).status_code == 200
assert client.post(f"/api/rubrics/{rubric['id']}/publish").status_code == 200
batch = client.post("/api/scoring-batches", json={"course_version_id": version["id"], "rubric_version_id": rubric["id"]})
assert batch.status_code == 200
with session_factory() as db:
from wisegrade.models import ScoringBatch
snapshot = db.get(ScoringBatch, batch.json()["id"])
assert snapshot.course_snapshot["objectives"][0]["name"] == "修订后的目标"
assert snapshot.rubric_snapshot["criteria"][0]["name"] == "修订后的评分项"
assert db.get(CourseVersion, 1).objectives[0]["name"] != "修订后的目标"
assert db.get(RubricVersion, 1).criteria[0]["name"] != "修订后的评分项"
finally:
app.dependency_overrides.clear()
def test_missing_material_is_explicit_and_reviewable(session_factory):
with session_factory() as db:
student = Student(student_no="1023009007", name="测试学生")
db.add(student)
db.flush()
db.add(Project(course_version_id=1, student_id=student.id, topic="没有材料", source_dir="empty/student"))
db.commit()
client = client_for(session_factory)
try:
batch_id = client.post("/api/scoring-batches", json={"course_version_id": 1, "rubric_version_id": 1}).json()["id"]
run_batch(batch_id, session_factory=session_factory)
status = client.get(f"/api/scoring-batches/{batch_id}/progress").json()
assert status["status"] == "partial" and status["progress_done"] == status["progress_total"] == 22
result = client.get(f"/api/scoring-batches/{batch_id}/scores").json()[0]
assert result["totals"]["overall"] is None
assert all(row["status"] == "unscorable" and row["error"] for row in result["rows"])
first_id = result["rows"][0]["score_id"]
response = client.put(f"/api/criterion-scores/{first_id}/review", json={"score": 3, "comment": "人工核定", "reason": "教师确认"})
assert response.status_code == 200 and response.json()["model_score"] is None
with session_factory() as db:
score = db.get(CriterionScore, first_id)
assert score.teacher_score == 3 and score.model_score is None
finally:
app.dependency_overrides.clear()