Spaces:
Running on Zero
Running on Zero
File size: 10,482 Bytes
36fc86c 2cf98f7 36fc86c 2cf98f7 36fc86c 2cf98f7 36fc86c a8287eb 36fc86c a8287eb 36fc86c a8287eb 36fc86c a8287eb 36fc86c a8287eb 36fc86c a8287eb 36fc86c a8287eb | 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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | import threading
from unittest.mock import call, patch
import pytest
from engine import translate as tr
# ββ _resolve_key / using_openrouter βββββββββββββββββββββββββββββββββββββββββ
def test_resolve_key_prefers_explicit_argument(monkeypatch):
monkeypatch.setenv("OPENROUTER_API_KEY", "env-key")
assert tr._resolve_key("explicit-key") == "explicit-key"
def test_resolve_key_falls_back_to_env_var(monkeypatch):
monkeypatch.setenv("OPENROUTER_API_KEY", "env-key")
assert tr._resolve_key(None) == "env-key"
assert tr._resolve_key("") == "env-key"
assert tr._resolve_key(" ") == "env-key"
def test_resolve_key_empty_when_neither_set(monkeypatch):
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
assert tr._resolve_key(None) == ""
def test_resolve_key_strips_whitespace(monkeypatch):
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
assert tr._resolve_key(" key-with-spaces ") == "key-with-spaces"
def test_using_openrouter_true_and_false(monkeypatch):
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
assert tr.using_openrouter("some-key") is True
assert tr.using_openrouter(None) is False
assert tr.using_openrouter("") is False
# ββ translate_one dispatch βββββββββββββββββββββββββββββββββββββββββββββββββββ
def test_translate_one_uses_openrouter_when_key_present(monkeypatch):
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
with patch("engine.translate.openrouter_backend.translate_one", return_value="translated") as mock_or, \
patch("engine.translate.local_backend.translate_batch") as mock_local:
result = tr.translate_one("source", "api-key", "model-x")
assert result == "translated"
mock_or.assert_called_once()
mock_local.assert_not_called()
def test_translate_one_uses_local_backend_when_no_key(monkeypatch):
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
with patch("engine.translate.openrouter_backend.translate_one") as mock_or, \
patch("engine.translate.local_backend.translate_batch", return_value=["local translation"]) as mock_local:
result = tr.translate_one("source", None, "model-x")
assert result == "local translation"
mock_or.assert_not_called()
mock_local.assert_called_once_with(["source"])
# ββ translate_segments βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def _segments(*sources):
return [{"source": s, "target": ""} for s in sources]
def test_translate_segments_skips_already_translated(monkeypatch):
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
segments = [{"source": "a", "target": ""}, {"source": "b", "target": "already done"}]
with patch("engine.translate.local_backend.translate_batch", return_value=["A"]) as mock_local:
result, errors = tr.translate_segments(segments, None, "model")
assert result[0]["target"] == "A"
assert result[1]["target"] == "already done"
assert errors == []
mock_local.assert_called_once_with(["a"])
def test_translate_segments_skips_empty_source():
segments = [{"source": "", "target": ""}, {"source": " ", "target": ""}]
with patch("engine.translate.local_backend.translate_batch") as mock_local:
result, errors = tr.translate_segments(segments, None, "model")
mock_local.assert_not_called()
assert errors == []
def test_translate_segments_local_backend_path(monkeypatch):
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
segments = _segments("a", "b")
# local backend is called one segment at a time (see test_local_backend_calls_one_at_a_time),
# so the fake must respond per-call rather than returning one fixed list for all calls.
with patch("engine.translate.local_backend.translate_batch", side_effect=lambda texts: [texts[0].upper()]):
result, errors = tr.translate_segments(segments, None, "model")
assert [s["target"] for s in result] == ["A", "B"]
assert errors == []
def test_translate_segments_local_backend_calls_one_at_a_time(monkeypatch):
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
segments = _segments("a", "b", "c")
with patch(
"engine.translate.local_backend.translate_batch", side_effect=lambda texts: [t.upper() for t in texts]
) as mock_local:
tr.translate_segments(segments, None, "model")
assert mock_local.call_args_list == [call(["a"]), call(["b"]), call(["c"])]
def test_translate_segments_local_backend_progress_callback_per_segment(monkeypatch):
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
segments = _segments("a", "b", "c")
progress_calls = []
with patch("engine.translate.local_backend.translate_batch", side_effect=lambda texts: [t.upper() for t in texts]):
tr.translate_segments(
segments, None, "model",
progress_callback=lambda i, total: progress_calls.append((i, total)),
)
assert progress_calls == [(0, 3), (1, 3), (2, 3)]
def test_translate_segments_local_backend_respects_stop_between_segments(monkeypatch):
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
segments = _segments("a", "b", "c")
stop = threading.Event()
def fake_batch(texts):
if texts == ["b"]:
stop.set()
return [t.upper() for t in texts]
with patch("engine.translate.local_backend.translate_batch", side_effect=fake_batch):
result, errors = tr.translate_segments(segments, None, "model", stop=stop)
assert [s["target"] for s in result] == ["A", "B", ""]
assert errors == []
def test_translate_segments_local_backend_one_segment_error_does_not_stop_others(monkeypatch):
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
segments = _segments("a", "b", "c")
def fake_batch(texts):
if texts == ["b"]:
raise RuntimeError("boom")
return [t.upper() for t in texts]
with patch("engine.translate.local_backend.translate_batch", side_effect=fake_batch):
result, errors = tr.translate_segments(segments, None, "model")
assert [s["target"] for s in result] == ["A", "", "C"]
assert len(errors) == 1
assert "Segment 2" in errors[0]
def test_translate_segments_local_backend_error_recorded(monkeypatch):
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
segments = _segments("a", "b")
with patch("engine.translate.local_backend.translate_batch", side_effect=RuntimeError("model crashed")):
result, errors = tr.translate_segments(segments, None, "model")
assert len(errors) == 2
assert "model crashed" in errors[0]
assert all(s["target"] == "" for s in result)
def test_translate_segments_openrouter_batch_success():
segments = _segments("a", "b")
with patch("engine.translate.openrouter_backend.translate_batch", return_value=["A", "B"]) as mock_batch:
result, errors = tr.translate_segments(segments, "key", "model")
assert [s["target"] for s in result] == ["A", "B"]
assert errors == []
mock_batch.assert_called_once()
def test_translate_segments_openrouter_batch_falls_back_to_per_segment():
segments = _segments("a", "b")
with patch("engine.translate.openrouter_backend.translate_batch", return_value=None), \
patch("engine.translate.openrouter_backend.translate_one", side_effect=["A", "B"]) as mock_one:
result, errors = tr.translate_segments(segments, "key", "model")
assert [s["target"] for s in result] == ["A", "B"]
assert errors == []
assert mock_one.call_count == 2
def test_translate_segments_per_segment_fallback_records_errors_and_continues():
segments = _segments("a", "b")
with patch("engine.translate.openrouter_backend.translate_batch", return_value=None), \
patch("engine.translate.openrouter_backend.translate_one", side_effect=[RuntimeError("bad"), "B"]):
result, errors = tr.translate_segments(segments, "key", "model")
assert result[0]["target"] == "" # failed segment left untouched
assert result[1]["target"] == "B"
assert len(errors) == 1
assert "Segment 1" in errors[0]
def test_translate_segments_respects_stop_event_between_batches():
stop = threading.Event()
total = tr._BATCH_SIZE * 3 # several batches' worth, so stopping mid-way is observable
segments = _segments(*[f"seg{i}" for i in range(total)])
call_count = {"n": 0}
def fake_batch(texts, *a, **kw):
call_count["n"] += 1
if call_count["n"] == 1:
stop.set()
return [t.upper() for t in texts]
with patch("engine.translate.openrouter_backend.translate_batch", side_effect=fake_batch):
result, errors = tr.translate_segments(segments, "key", "model", stop=stop)
assert call_count["n"] == 1 # stopped before the second batch
assert result[0]["target"] == "SEG0"
assert result[-1]["target"] == ""
def test_translate_segments_progress_callback_invoked_per_batch():
total = tr._BATCH_SIZE * 2 + 1 # forces (at least) 3 batches
segments = _segments(*[f"seg{i}" for i in range(total)])
progress_calls = []
with patch("engine.translate.openrouter_backend.translate_batch", side_effect=lambda texts, *a, **kw: texts):
tr.translate_segments(
segments, "key", "model",
progress_callback=lambda i, total: progress_calls.append((i, total)),
)
expected = [(i, total) for i in range(0, total, tr._BATCH_SIZE)]
assert progress_calls == expected
def test_translate_segments_passes_recent_translations_as_preceding_context():
total = tr._BATCH_SIZE * 2
segments = _segments(*[f"seg{i}" for i in range(total)])
seen_preceding = []
def fake_batch(texts, api_key, model, template, preceding=None):
seen_preceding.append(preceding)
return texts
with patch("engine.translate.openrouter_backend.translate_batch", side_effect=fake_batch):
tr.translate_segments(segments, "key", "model")
last_of_first_batch = [f"seg{i}" for i in range(tr._BATCH_SIZE)][-3:]
assert seen_preceding[0] is None # nothing translated yet for the first batch
assert seen_preceding[1] == last_of_first_batch
|