import pytest from sentence_api.ai_tutor import AITutor, AITutorError class FakeResponse: def __init__(self, status_code=200, body=None): self.status_code = status_code self.text = str(body) self._body = body or {} def json(self): if self._body is None: raise ValueError("invalid JSON") return self._body def test_ai_tutor_parses_chat_completion(monkeypatch): requests_payloads = [] def fake_post(endpoint, headers, json, timeout): requests_payloads.append((endpoint, headers, json, timeout)) return FakeResponse( body={ "choices": [ {"message": {"content": " 这句话的意思是…… "}} ] } ) monkeypatch.setattr("sentence_api.ai_tutor.requests.post", fake_post) tutor = AITutor("https://ai.test/chat", "secret", "test-model", 1.5) explanation = tutor.explain( "The early bird catches the worm.", course_title="English idioms", sentence_index=1, language="en", ) endpoint, headers, payload, timeout = requests_payloads[0] assert explanation == "这句话的意思是……" assert endpoint == "https://ai.test/chat" assert headers["Authorization"] == "Bearer secret" assert payload["model"] == "test-model" assert "The early bird catches the worm." in payload["messages"][1]["content"] assert timeout == 1.5 def test_ai_tutor_resolves_openai_compatible_base_url(monkeypatch): endpoints = [] def fake_post(endpoint, **kwargs): endpoints.append(endpoint) return FakeResponse(body={"choices": [{"message": {"content": "ok"}}]}) monkeypatch.setattr("sentence_api.ai_tutor.requests.post", fake_post) tutor = AITutor("https://ai.test:8080/v1", "secret", "test-model", 1) assert tutor.explain("Hello.") == "ok" assert endpoints == ["https://ai.test:8080/v1/chat/completions"] assert tutor.request_url == "https://ai.test:8080/v1/chat/completions" def test_ai_tutor_rejects_unexpected_response(monkeypatch): monkeypatch.setattr( "sentence_api.ai_tutor.requests.post", lambda *args, **kwargs: FakeResponse(body={"choices": []}), ) tutor = AITutor("https://ai.test/chat", "secret", "test-model", 1) with pytest.raises(AITutorError): tutor.explain("Hello.") from sentence_api.ai_tutor import _parse_translation_payload def test_ai_tutor_translates_full_document(monkeypatch): requests_payloads = [] def fake_post(endpoint, headers, json, timeout): requests_payloads.append((endpoint, headers, json, timeout)) return FakeResponse(body={"choices": [{"message": {"content": '["你好。", "见到你很高兴。"]'}}]}) monkeypatch.setattr("sentence_api.ai_tutor.requests.post", fake_post) tutor = AITutor("https://ai.test/v1", "secret", "test-model", 1.5) translated = tutor.translate_sentences( [(0, "Hello."), (1, "Nice to meet you.")], course_title="Greetings", ) assert translated == {0: "你好。", 1: "见到你很高兴。"} _endpoint, _headers, payload, timeout = requests_payloads[0] user_prompt = payload["messages"][1]["content"] assert "Hello." in user_prompt assert "Nice to meet you." in user_prompt assert timeout == 90.0 def test_parse_translation_payload_accepts_fenced_json_and_numbered_lines(): assert _parse_translation_payload('```json\n["一", "二"]\n```', 2) == ["一", "二"] assert _parse_translation_payload("1. 一\n2. 二", 2) == ["一", "二"]