import shutil from pathlib import Path import pymupdf as fitz import pytest from fastapi.testclient import TestClient from sqlalchemy import select from wisegrade.config import get_settings from wisegrade.db import get_db from wisegrade.llm import ConsistencyResult, FactResult, ScoreResult from wisegrade.main import app from wisegrade.models import CourseVersion, CriterionScore, RubricVersion, ScoringBatch, TeacherRevision from wisegrade.rag import retrieve from wisegrade.scoring import run_batch from wisegrade.storage import scan def fake_embed(texts): return [[1.0] + [0.0] * 1023 for _ in texts] class FakeModel: def json_call(self, _prompt, payload, schema, images=None): if schema is FactResult: return FactResult(topic="自动化实践示例") if schema is ConsistencyResult: return ConsistencyResult() criterion = payload["criterion"] evidence = payload["evidence"][0] return ScoreResult(criterion_id=criterion["criterion_id"], score=criterion["max_score"] / 2, max_score=criterion["max_score"], reason="示例证据支持部分要求", evidence=[{"chunk_id": evidence["chunk_id"], "page": evidence["page"], "quote": evidence["text"][:min(12, len(evidence["text"]))]}], missing_evidence=[], suggestion="补充量化论证", confidence=.7) def make_pdf(path, title): pdf = fitz.open() page = pdf.new_page() page.insert_text((72, 72), title + " results and analysis for robot control system. " * 4) pdf.save(path) @pytest.mark.integration def test_docs_examples_end_to_end(environment, session_factory): root = environment / "inbox" / "2026春季第1批" / "1023009006_李傲琦_基于ROS2系统的自瞄移动机器人" root.mkdir(parents=True) docs = Path(__file__).resolve().parents[2] / "docs" for filename in ("阶段报告1.pdf", "阶段报告2.pdf", "阶段报告3.pdf", "自动化专业综合实践报告.pdf"): shutil.copy2(docs / filename, root / filename) make_pdf(root / "汇报PPT.pdf", "Presentation") data = scan("2026春季第1批") assert len(data["projects"]) == 1 scanned = data["projects"][0] assert scanned["missing"] == [] assert next(f for f in scanned["files"] if f["category"] == "presentation")["status"] == "needs_confirmation" def override_db(): with session_factory() as session: yield session app.dependency_overrides[get_db] = override_db client = TestClient(app) try: files = [{"relative_path": f["relative_path"], "category": f["category"], "sha256": f["sha256"], "status": "recognized"} for f in scanned["files"]] response = client.post("/api/import/confirm", json={"course_version_id": 1, "projects": [{"relative_dir": scanned["relative_dir"], "student_no": scanned["student_no"], "name": scanned["name"], "topic": scanned["topic"], "files": files}]}) assert response.status_code == 200, response.text project_id = response.json()["project_ids"][0] response = client.post("/api/scoring-batches", json={"course_version_id": 1, "rubric_version_id": 1}) assert response.status_code == 200, response.text batch_id = response.json()["id"] run_batch(batch_id, session_factory=session_factory, client_factory=lambda db, batch: FakeModel(), embedder=fake_embed, retriever=lambda db, **kwargs: retrieve(db, embedder=fake_embed, **kwargs)) result = client.get(f"/api/projects/{project_id}/scores?batch_id={batch_id}").json() assert result["totals"]["overall"] == 50 assert all(row["evidence"] and row["evidence"][0]["page"] >= 1 for row in result["rows"]) assert client.get(result["rows"][0]["evidence"][0]["image_url"]).content[:8] == b"\x89PNG\r\n\x1a\n" score_id = result["rows"][0]["score_id"] reviewed = client.put(f"/api/criterion-scores/{score_id}/review", json={"score": 20, "comment": "教师核定", "reason": "核查原文"}) assert reviewed.status_code == 200 assert reviewed.json()["model_score"] != reviewed.json()["teacher_score"] assert len(client.get(f"/api/criterion-scores/{score_id}/revisions").json()) == 1 excel = client.post("/api/exports/excel", json={"batch_id": batch_id}) pdf = client.post("/api/exports/project-pdf", json={"batch_id": batch_id, "project_id": project_id}) assert excel.status_code == pdf.status_code == 200 assert client.get(f"/api/exports/{excel.json()['id']}/download").content[:2] == b"PK" assert client.get(f"/api/exports/{pdf.json()['id']}/download").content[:4] == b"%PDF" finally: app.dependency_overrides.clear()