from pathlib import Path import pytest from sqlalchemy import select 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_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"] == "机器人系统"