Spaces:
Runtime error
Runtime error
File size: 5,400 Bytes
4c4a722 d2075b0 4c4a722 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | """Tests for the SQLAlchemy data-access layer (run against a temp SQLite file)."""
from __future__ import annotations
import pytest
from app import config as config_module
from app import database
from app import db as db_module
from app.answer_grid import from_dict as grid_from_dict, score_student
@pytest.fixture
async def fresh_db(tmp_path, monkeypatch):
db_file = tmp_path / "t.db"
cached = config_module.get_settings()
monkeypatch.setattr(cached, "database_provider", "sqlite", raising=False)
monkeypatch.setattr(cached, "supabase_db_url", "", raising=False)
monkeypatch.setattr(cached, "database_path", str(db_file), raising=False)
monkeypatch.setattr(cached, "admin_emails", "", raising=False)
db_module.get_engine.cache_clear()
db_module.get_sessionmaker.cache_clear()
await database.init_database()
yield
db_module.get_engine.cache_clear()
db_module.get_sessionmaker.cache_clear()
@pytest.mark.unit
async def test_teacher_profile_roundtrip(fresh_db):
uid = await database.create_user(
"t@example.com", "hash", full_name="Wang", school="First High"
)
user = await database.get_user_by_email("t@example.com")
assert user["id"] == uid
assert user["full_name"] == "Wang"
assert user["school"] == "First High"
assert user["is_admin"] is False
@pytest.mark.unit
async def test_student_matched_by_normalized_name(fresh_db):
uid = await database.create_user("t@example.com", "h")
a = await database.match_or_create_student(uid, "Alice Chen")
b = await database.match_or_create_student(uid, " alice chen ")
c = await database.match_or_create_student(uid, "Bob")
assert a == b # whitespace/case-insensitive match
assert a != c
students = await database.get_students(uid)
assert len(students) == 2
@pytest.mark.unit
async def test_student_timeline_across_quizzes(fresh_db):
uid = await database.create_user("t@example.com", "h")
sid = await database.match_or_create_student(uid, "Alice")
q1 = await database.create_session(uid, "Quiz 1")
await database.save_student_result(q1, sid, {"answers": ["A"]}, 5, 3, 60.0)
q2 = await database.create_session(uid, "Quiz 2")
await database.save_student_result(q2, sid, {"answers": ["B"]}, 5, 5, 100.0)
timeline = await database.get_student_timeline(sid)
assert [t["score"] for t in timeline] == [60.0, 100.0]
assert timeline[0]["quiz_title"] == "Quiz 1"
@pytest.mark.unit
async def test_save_student_result_is_upsert(fresh_db):
uid = await database.create_user("t@example.com", "h")
sid = await database.match_or_create_student(uid, "Alice")
q = await database.create_session(uid, "Quiz")
await database.save_student_result(q, sid, {"answers": []}, 5, 2, 40.0)
await database.save_student_result(q, sid, {"answers": []}, 5, 4, 80.0)
timeline = await database.get_student_timeline(sid)
assert len(timeline) == 1 # same quiz+student -> updated, not duplicated
assert timeline[0]["score"] == 80.0
@pytest.mark.unit
async def test_stats_counts(fresh_db):
uid = await database.create_user("t@example.com", "h")
sid = await database.match_or_create_student(uid, "Alice")
q = await database.create_session(uid, "Quiz")
await database.save_student_result(q, sid, {"answers": []}, 3, 3, 100.0)
stats = await database.get_stats()
assert stats["teachers"] == 1
assert stats["students"] == 1
assert stats["quizzes"] == 1
assert stats["student_results"] == 1
@pytest.mark.unit
async def test_stats_and_lists_scope_to_teacher(fresh_db):
# Two teachers, each with their own quiz + student + result.
t1 = await database.create_user("t1@example.com", "h")
t2 = await database.create_user("t2@example.com", "h")
s1 = await database.match_or_create_student(t1, "Alice")
s2 = await database.match_or_create_student(t2, "Bob")
q1 = await database.create_session(t1, "T1 Quiz")
q2 = await database.create_session(t2, "T2 Quiz")
await database.save_student_result(q1, s1, {"answers": []}, 3, 3, 100.0)
await database.save_student_result(q2, s2, {"answers": []}, 3, 1, 33.3)
# Global (admin) sees everything.
g = await database.get_stats()
assert g["teachers"] == 2 and g["students"] == 2 and g["quizzes"] == 2 and g["student_results"] == 2
# Scoped to t1 sees only t1's data.
sc = await database.get_stats(t1)
assert sc["students"] == 1 and sc["quizzes"] == 1 and sc["student_results"] == 1
# Scoped list endpoints filter by teacher.
assert {x["title"] for x in await database.admin_list_sessions(t1)} == {"T1 Quiz"}
assert {x["name"] for x in await database.admin_list_students(t1)} == {"Alice"}
assert len(await database.admin_list_sessions()) == 2 # admin sees both
@pytest.mark.unit
def test_score_student_grades_only_concrete_keys():
grid = grid_from_dict(
{
"total_questions": 4,
# Q4 official is blank -> not graded
"official_answers": ["A", "B", "C", ""],
"students": [{"name": "Alice", "answers": ["A", "X", "=", ""]}],
"questions": [],
}
)
graded, correct, score = score_student(grid, 0)
# Graded = Q1..Q3 (3). Correct: Q1 (A), Q3 ("=") -> 2 correct, Q2 wrong.
assert graded == 3
assert correct == 2
assert score == pytest.approx(66.7, abs=0.1)
|