Files
wehub-resource-sync e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

178 lines
5.6 KiB
Python

"""Unit tests for the choice-question data contract
(:mod:`deeptutor.capabilities.mastery.choices`).
These exercise the pure option-handling rules in isolation — parsing, body
validation, answer normalisation, and legacy recovery — independent of the
tool/engine wiring that :mod:`test_mastery_tools` drives end to end."""
from __future__ import annotations
import pytest
from deeptutor.capabilities.mastery.choices import (
format_options,
has_option_bodies,
parse_options,
recover_options_from_turn,
resolve_answer,
)
# ── parse_options ────────────────────────────────────────────────────────────
def test_parse_options_reads_labelled_bodies():
assert parse_options(["A: first", "B) second", "C、third"]) == {
"A": "first",
"B": "second",
"C": "third",
}
def test_parse_options_keeps_bare_labels_for_legacy_data():
assert parse_options(["A", "B", "C", "D"]) == {"A": "A", "B": "B", "C": "C", "D": "D"}
def test_parse_options_assigns_positional_labels_to_unprefixed_text():
assert parse_options(["first answer", "second answer"]) == {
"A": "first answer",
"B": "second answer",
}
def test_parse_options_skips_blank_entries():
assert parse_options(["A: keep", " ", ""]) == {"A": "keep"}
# ── has_option_bodies ────────────────────────────────────────────────────────
def test_has_option_bodies_true_for_real_text():
assert has_option_bodies({"A": "first", "B": "second"}) is True
def test_has_option_bodies_false_for_bare_labels():
assert has_option_bodies({"A": "A", "B": "B"}) is False
def test_has_option_bodies_false_when_fewer_than_two():
assert has_option_bodies({"A": "only one"}) is False
# ── format_options ───────────────────────────────────────────────────────────
def test_format_options_round_trips_with_parse():
options = {"A": "first", "B": "second"}
assert parse_options(format_options(options)) == options
# ── resolve_answer ───────────────────────────────────────────────────────────
def test_resolve_answer_accepts_direct_label():
assert resolve_answer("C", {"A": "x", "B": "y", "C": "z"}) == "C"
def test_resolve_answer_strips_label_prefix():
assert resolve_answer("C: the answer", {"A": "x", "C": "the answer"}) == "C"
def test_resolve_answer_matches_full_body_exactly():
assert (
resolve_answer(
"Step 6 — add the stop condition",
{
"A": "Step 2 — write the first tool",
"C": "Step 6 — add the stop condition",
},
)
== "C"
)
def test_resolve_answer_matches_unique_substring():
assert (
resolve_answer(
"Step 6",
{
"A": "Step 2 — write the first tool",
"C": "Step 6 — add the stop condition",
},
)
== "C"
)
def test_resolve_answer_blank_when_ambiguous():
assert resolve_answer("Step", {"A": "Step 2", "B": "Step 6"}) == ""
def test_resolve_answer_blank_when_empty():
assert resolve_answer("", {"A": "x", "B": "y"}) == ""
# ── recover_options_from_turn ────────────────────────────────────────────────
class _FakeStore:
def __init__(self, events):
self._events = events
async def get_turn_events(self, turn_id, after_seq=0):
return self._events
def _ask_user_event(prompt, options):
return {
"type": "tool_call",
"metadata": {
"tool_name": "ask_user",
"args": {"questions": [{"prompt": prompt, "options": options}]},
},
}
@pytest.mark.asyncio
async def test_recover_options_from_turn_pulls_bodies_from_ask_user():
store = _FakeStore(
[
_ask_user_event(
"Where is the stop condition added?",
[
{"label": "A", "description": "Step 2"},
{"label": "B", "description": "Step 4"},
{"label": "C", "description": "Step 6"},
],
)
]
)
recovered = await recover_options_from_turn(
store, "turn_1", "Where is the stop condition added?"
)
assert recovered == {"A": "Step 2", "B": "Step 4", "C": "Step 6"}
@pytest.mark.asyncio
async def test_recover_options_from_turn_ignores_non_matching_prompt():
store = _FakeStore(
[
_ask_user_event(
"An unrelated question",
[{"label": "A", "description": "x"}, {"label": "B", "description": "y"}],
)
]
)
assert await recover_options_from_turn(store, "turn_1", "Different prompt") == {}
@pytest.mark.asyncio
async def test_recover_options_from_turn_handles_missing_capability_and_errors():
assert await recover_options_from_turn(object(), "turn_1", "q") == {}
class _Raising:
async def get_turn_events(self, turn_id, after_seq=0):
raise RuntimeError("boom")
assert await recover_options_from_turn(_Raising(), "turn_1", "q") == {}
assert await recover_options_from_turn(_FakeStore([]), "", "q") == {}