"""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", [ ("", ""), ("```html\n\n```", ""), (" ", ""), ], ) def test_extract_html(content, expected): assert extract_html(content) == expected