testsAndMisc/python_pkg/code_tutor/tests/test_llm_fallback.py
Krzysztof kuhy Rudnicki e3214c813c
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Clear the seven over-cap code_tutor test modules
Partitioned at the section banners each file already used, into ten new
modules named for what they cover. python_pkg/code_tutor is now entirely
under the cap, from thirteen violations.

The splitter copies the whole original header into each new file and
lets ruff's F401 pass prune it - 59 imports removed correctly, versus a
hand-written import block failing on every earlier test split today.
That does not cover module-level helpers: _make_verifier sat between two
banners and was left behind by its callers, so it moved to conftest.py,
taking test_verifier.py's shadowing copy of _make_live_mock with it.

No patch string changed. Pass count is 217 before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
2026-08-16 18:06:38 +02:00

165 lines
4.8 KiB
Python

"""Tests for weak-response detection and the Claude fallback."""
from __future__ import annotations
import json
from unittest.mock import MagicMock, patch
import pytest
from python_pkg.code_tutor._llm import (
OllamaBackend,
_is_weak,
complete_with_fallback,
)
# ---------------------------------------------------------------------------
# _is_weak
# ---------------------------------------------------------------------------
def test_is_weak_short() -> None:
assert _is_weak("ok") is True
def test_is_weak_phrase() -> None:
assert _is_weak("I don't know what this code does at all honestly") is True
def test_is_weak_strong_response() -> None:
long_response = (
"This function reads the first 512 bytes of a file and checks for"
" null bytes to determine if the file is binary."
)
assert _is_weak(long_response) is False
# ---------------------------------------------------------------------------
# complete_with_fallback
# ---------------------------------------------------------------------------
def test_complete_with_fallback_strong() -> None:
mock_primary = MagicMock(spec=OllamaBackend)
mock_primary.complete.return_value = (
"This is a detailed response explaining the code in full."
)
mock_console = MagicMock()
result = complete_with_fallback(
mock_primary, "sys", "user", console=mock_console, input_fn=lambda _: ""
)
assert result == "This is a detailed response explaining the code in full."
mock_primary.complete.assert_called_once()
def test_complete_with_fallback_weak_user_says_yes_claude_ok() -> None:
mock_primary = MagicMock(spec=OllamaBackend)
mock_primary.complete.return_value = "ok" # short → weak
mock_block = MagicMock()
mock_block.text = "claude response is great"
mock_message = MagicMock()
mock_message.content = [mock_block]
mock_client = MagicMock()
mock_client.messages.create.return_value = mock_message
mock_console = MagicMock()
with patch(
"python_pkg.code_tutor._llm.anthropic.Anthropic", return_value=mock_client
):
result = complete_with_fallback(
mock_primary, "sys", "user", console=mock_console, input_fn=lambda _: "y"
)
assert result == "claude response is great"
def test_complete_with_fallback_weak_user_says_yes_claude_fails() -> None:
import anthropic as anthropic_lib
mock_primary = MagicMock(spec=OllamaBackend)
mock_primary.complete.side_effect = [
"ok", # first call → weak
"retry response from ollama", # retry after claude failure
]
mock_client = MagicMock()
mock_client.messages.create.side_effect = anthropic_lib.APIError(
message="fail",
request=MagicMock(),
body=None,
)
mock_console = MagicMock()
with patch(
"python_pkg.code_tutor._llm.anthropic.Anthropic", return_value=mock_client
):
result = complete_with_fallback(
mock_primary, "sys", "user", console=mock_console, input_fn=lambda _: "y"
)
assert result == "retry response from ollama"
assert mock_primary.complete.call_count == 2
def test_complete_with_fallback_weak_user_says_no() -> None:
mock_primary = MagicMock(spec=OllamaBackend)
mock_primary.complete.side_effect = [
"ok", # first call → weak
"second ollama response here too", # retry
]
mock_console = MagicMock()
result = complete_with_fallback(
mock_primary, "sys", "user", console=mock_console, input_fn=lambda _: "n"
)
assert result == "second ollama response here too"
assert mock_primary.complete.call_count == 2
def test_ollama_backend_defaults() -> None:
backend = OllamaBackend()
assert "11434" in backend._url
assert backend._timeout > 0
@pytest.mark.parametrize(
"phrase",
[
"i cannot help with that request really",
"it is unclear what you mean by that",
"i'm not sure about this code",
],
)
def test_is_weak_various_phrases(phrase: str) -> None:
assert _is_weak(phrase) is True
def test_ollama_stream_no_done_marker() -> None:
"""Exhaust the stream loop without a ``done`` marker (no ``break``).
When no line carries ``done: true`` the loop runs to exhaustion and
falls through to ``return "".join(parts)`` rather than breaking early.
"""
lines = [
json.dumps({"response": "foo", "done": False}).encode(),
json.dumps({"response": "bar", "done": False}).encode(),
]
mock_resp = MagicMock()
mock_resp.iter_lines.return_value = iter(lines)
tokens: list[str] = []
with patch("python_pkg.code_tutor._llm.requests.post", return_value=mock_resp):
backend = OllamaBackend()
result = backend.stream("sys", "user", tokens.append)
assert result == "foobar"
assert tokens == ["foo", "bar"]