from pathlib import Path import pytest from sqlalchemy import select, text from wisegrade.models import CourseObjective, CourseVersion, GraduationIndicator, RubricCriterion, RubricScoreLevel, RubricVersion, TopicDirection from wisegrade.rules import RubricData, totals from wisegrade.storage import classify, extract_identity, parse_student_folder, secure_path, sha256 def test_postgresql_migrations_and_vector_extension(db): assert db.bind.dialect.name == "postgresql" assert db.scalar(text("SELECT version_num FROM alembic_version")) == "0009" assert db.scalar(text("SELECT extname FROM pg_extension WHERE extname = 'vector'")) == "vector" def test_seeded_course_and_weights(db): version = db.scalar(select(CourseVersion)) rubric = db.scalar(select(RubricVersion)) assert version.published and rubric.published assert {item["code"] for item in version.indicators} == {"2.3", "2.4", "4.1", "4.2", "4.3", "4.4", "11.1", "11.2"} assert len(RubricData(criteria=rubric.criteria, weights=rubric.weights).criteria) == 22 assert rubric.weights == {"stage1": .1, "stage2": .1, "stage3": .1, "final": .7} assert len(db.scalars(select(CourseObjective)).all()) == 4 assert len(db.scalars(select(GraduationIndicator)).all()) == 8 assert len(db.scalars(select(TopicDirection)).all()) == 3 assert len(db.scalars(select(RubricCriterion)).all()) == 22 assert len(db.scalars(select(RubricScoreLevel)).all()) == 66 def test_score_totals_preserve_missing(db): rubric = db.scalar(select(RubricVersion)) scores = {item["criterion_id"]: item["max_score"] for item in rubric.criteria} assert totals(scores, rubric.criteria, rubric.weights)["overall"] == 100 scores.pop("stage2.hardware") result = totals(scores, rubric.criteria, rubric.weights) assert result["stage2"] is None and result["overall"] is None def test_invalid_rubric_weights(db): rubric = db.scalar(select(RubricVersion)) with pytest.raises(ValueError): RubricData(criteria=rubric.criteria, weights={"stage1": .2, "stage2": .1, "stage3": .1, "final": .7}) def test_path_security_and_hash(environment): root = environment / "inbox" target = root / "student" / "report.pdf" target.parent.mkdir() target.write_bytes(b"unchanged") assert secure_path("student/report.pdf", root=root) == target assert sha256(target) == "aaa8d3c8d74ad3e8f6b1772aa9c7e0eaa528cb42fc93599ce2f125b00d4c424c" for bad in ("../outside", "/etc/passwd", "student/../../outside", "student\\report.pdf"): with pytest.raises(ValueError): secure_path(bad, root=root) (root / "link").symlink_to(environment) with pytest.raises(ValueError): secure_path("link/test.db", root=root, must_exist=False) def test_student_folder_and_file_recognition(): assert parse_student_folder("1023009006_李傲琦_基于ROS2系统的自瞄移动机器人")["topic"].startswith("基于ROS2") assert parse_student_folder("bad_name")["status"] == "needs_confirmation" assert classify(Path("汇报PPT.pdf")) == ("presentation", "needs_confirmation") assert classify(Path("阶段报告2.docx")) == ("stage2", "recognized") assert classify(Path("legacy.doc")) == ("unknown", "unsupported") assert extract_identity("学号:1023009006\n姓名:李傲琦\n题目:机器人系统")["topic"] == "机器人系统"