File size: 3,840 Bytes
ad5d4b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""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