68 lines
3.3 KiB
Python
68 lines
3.3 KiB
Python
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")) == "0002"
|
|
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"] == "机器人系统"
|