"""DeepSeekBackend tests — mocked requests.post.""" from decimal import Decimal from unittest.mock import MagicMock, patch import pytest @pytest.fixture def app(tmp_path, monkeypatch): monkeypatch.setenv("SUBLARR_DB_PATH", str(tmp_path / "app.db")) from config import reload_settings reload_settings() from app import create_app app = create_app(testing=True) from extensions import db as sa_db with app.app_context(): sa_db.create_all() yield app @pytest.fixture def reset_conc(): from translation.concurrency import reset_for_tests reset_for_tests() def _deepseek(api_key="sk-test", model="deepseek-chat"): from translation.deepseek import DeepSeekBackend return DeepSeekBackend(api_key=api_key, model=model) def test_construction_requires_api_key(): from translation.deepseek import DeepSeekBackend with pytest.raises(ValueError, match="api_key"): DeepSeekBackend(api_key="", model="deepseek-chat") def test_default_model_and_prices(): b = _deepseek() assert b.name == "deepseek" assert b.default_model == "deepseek-chat" assert b.cost_per_1m_tokens_in == Decimal("0.14") assert b.cost_per_1m_tokens_out == Decimal("0.28") assert b.supports_glossary is True assert b.supports_batch is True def test_build_request_preserves_openai_shape(): """DeepSeek accepts OpenAI chat/completions format — no reshape.""" b = _deepseek() messages = [ {"role": "system", "content": "translate to de"}, {"role": "user", "content": "hello"}, ] payload = b._build_request(messages, max_tokens=500) assert payload["model"] == "deepseek-chat" assert payload["messages"] == messages # unchanged assert payload["max_tokens"] == 500 def test_parse_response_extracts_tokens(): from translation.llm_base import LLMResponse b = _deepseek() raw = { "choices": [ { "message": {"role": "assistant", "content": "hallo\nwelt"}, "finish_reason": "stop", } ], "usage": { "prompt_tokens": 30, "completion_tokens": 20, "total_tokens": 50, }, "model": "deepseek-chat", } resp = b._parse_response(raw) assert isinstance(resp, LLMResponse) assert resp.translations == ["hallo", "welt"] assert resp.tokens_in == 30 assert resp.tokens_out == 20 assert resp.finish_reason == "stop" def test_call_api_uses_bearer_auth(): b = _deepseek() mock_resp = MagicMock() mock_resp.raise_for_status.return_value = None mock_resp.json.return_value = { "choices": [ { "message": {"role": "assistant", "content": "ok"}, "finish_reason": "stop", } ], "usage": { "prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2, }, "model": "deepseek-chat", } with patch("translation.deepseek.requests.post") as mock_post: mock_post.return_value = mock_resp b._call_api( { "model": "deepseek-chat", "messages": [{"role": "user", "content": "y"}], "max_tokens": 100, }, timeout_s=60, ) call_args = mock_post.call_args url = call_args.args[0] if call_args.args else call_args.kwargs.get("url", "") assert "api.deepseek.com" in url assert "chat/completions" in url headers = call_args.kwargs.get("headers") or {} assert headers.get("Authorization") == "Bearer sk-test" def test_cost_calculation_matches_price_sheet(): from translation.cost_tracker import calculate_llm_cost_micro_usd b = _deepseek() # 1000 * 0.14/1M + 500 * 0.28/1M = 0.00014 + 0.00014 = 0.00028 = 280 micro_usd cost = calculate_llm_cost_micro_usd( tokens_in=1000, tokens_out=500, price_in_per_1m=b.cost_per_1m_tokens_in, price_out_per_1m=b.cost_per_1m_tokens_out, ) assert cost == 280 def test_deepseek_coder_model_also_priced(): b = _deepseek(model="deepseek-coder") assert b.cost_per_1m_tokens_in == Decimal("0.14") assert b.cost_per_1m_tokens_out == Decimal("0.28") def test_config_fields_present(): b = _deepseek() field_names = {f["key"] if isinstance(f, dict) else f.key for f in b.config_fields} assert "api_key" in field_names assert "model" in field_names def test_end_to_end_with_mock(app, reset_conc): from translation.concurrency import get_concurrency get_concurrency().register("deepseek", 2) b = _deepseek() mock_resp = MagicMock() mock_resp.raise_for_status.return_value = None mock_resp.json.return_value = { "choices": [ { "message": {"role": "assistant", "content": "hallo\nwelt\ntest"}, "finish_reason": "stop", } ], "usage": { "prompt_tokens": 30, "completion_tokens": 20, "total_tokens": 50, }, "model": "deepseek-chat", } with patch("translation.deepseek.requests.post") as mock_post: mock_post.return_value = mock_resp with app.app_context(): result = b.translate_batch( ["hi", "world", "test"], source_lang="en", target_lang="de", ) assert result.success is True assert result.translated_lines == ["hallo", "welt", "test"]