ClassLens-dev / chatkit /backend /tests /test_report_generator.py
Yu Chen
Refactor to add answer grid & assoicate llm parsing
ad5d4b6
Raw
History Blame Contribute Delete
3.84 kB
"""Unit tests for report_generator prompt assembly."""
from __future__ import annotations
import pytest
from app.answer_grid import AnswerGrid, QuestionMeta, StudentRow, WrongAnswer
from app.report_generator import (
build_student_markdown,
build_wrong_block,
extract_html,
)
def _make_grid(official, student_answers) -> AnswerGrid:
n = len(official)
return AnswerGrid(
total_questions=n,
official_answers=tuple(official),
students=(StudentRow(name="Alice", answers=tuple(student_answers)),),
questions=tuple(
QuestionMeta(number=i + 1, text=f"Q{i+1}", options=("A) a", "B) b", "C) c"))
for i in range(n)
),
)
@pytest.mark.unit
def test_build_wrong_block_full():
wa = WrongAnswer(
question=QuestionMeta(
number=3,
text="What is 2+2?",
options=("A) 3", "B) 4", "C) 5"),
),
student_answer="A",
correct_answer="B",
)
block = build_wrong_block(wa)
assert "### 第 3 題" in block
assert "What is 2+2?" in block
assert "A) 3" in block
assert "**學生作答**: A" in block
assert "**正確答案**: B" in block
@pytest.mark.unit
def test_build_wrong_block_without_options():
wa = WrongAnswer(
question=QuestionMeta(number=1, text="Q1", options=()),
student_answer="A",
correct_answer="B",
)
block = build_wrong_block(wa)
assert "**選項**" not in block
assert "**學生作答**: A" in block
@pytest.mark.unit
def test_build_wrong_block_blank_student_answer():
wa = WrongAnswer(
question=QuestionMeta(number=1, text="Q", options=()),
student_answer=None,
correct_answer="A",
)
block = build_wrong_block(wa)
assert "(未作答)" in block
@pytest.mark.unit
def test_build_student_markdown_filters_to_wrong_only():
grid = _make_grid(["A", "B", "C"], ["A", "X", "C"])
result = build_student_markdown(grid, 0)
assert result.wrong_count == 1
assert result.total_questions == 3
assert "### 第 2 題" in result.markdown_prompt
assert "### 第 1 題" not in result.markdown_prompt
assert "### 第 3 題" not in result.markdown_prompt
@pytest.mark.unit
def test_build_student_markdown_header_contains_counts():
grid = _make_grid(["A", "B", "C"], ["X", "Y", "Z"])
result = build_student_markdown(grid, 0)
assert "總題數:3,答錯:3" in result.markdown_prompt
assert "學生:Alice" in result.markdown_prompt
@pytest.mark.unit
def test_build_student_markdown_all_correct_path():
grid = _make_grid(["A", "B"], ["A", "B"])
result = build_student_markdown(grid, 0)
assert result.wrong_count == 0
assert "答錯:0" in result.markdown_prompt
assert "全部答對" in result.markdown_prompt
@pytest.mark.unit
def test_build_student_markdown_preserves_question_ordering():
grid = _make_grid(["A", "B", "C", "D"], ["X", "B", "Y", "Z"])
result = build_student_markdown(grid, 0)
# Blocks must appear in Q1, Q3, Q4 order
idx1 = result.markdown_prompt.index("### 第 1 題")
idx3 = result.markdown_prompt.index("### 第 3 題")
idx4 = result.markdown_prompt.index("### 第 4 題")
assert idx1 < idx3 < idx4
@pytest.mark.unit
def test_build_student_markdown_out_of_range():
grid = _make_grid(["A"], ["A"])
with pytest.raises(IndexError):
build_student_markdown(grid, 5)
@pytest.mark.unit
@pytest.mark.parametrize(
"content, expected",
[
("<!DOCTYPE html><html></html>", "<!DOCTYPE html><html></html>"),
("```html\n<!DOCTYPE html><html></html>\n```", "<!DOCTYPE html><html></html>"),
(" <html></html> ", "<html></html>"),
],
)
def test_extract_html(content, expected):
assert extract_html(content) == expected