Files
computer-playbook/tests/unit/python/cli/build/i18n/test_main.py
T
kevinveenbirkenbachandClaude Opus 5 666312dfbd refactor(i18n): translate against the dedicated LibreTranslate alone
server() preferred the deployed service and fell back to a throwaway
container when it did not answer. That container ran the upstream image
directly, with LT_UPDATE_MODELS on and neither the package-cache CA nor
the argos-net.com hijack, so it refetched gigabytes from upstream on
every run while the deployed service already carries those models in its
image, installed through the cache at build time. The fallback is gone.

It was reached by accident rather than by failure. deploy() ended in
await_service(), which polled for 300 seconds; the first start of the
service installs its models and takes far longer, so the wait expired,
deploy() reported nothing running, and the caller spawned the throwaway
beside the service it had just deployed successfully. await_service and
SERVICE_TIMEOUT_SECONDS are removed, leaving the single readiness wait
the client already performs with the budget those models need.

Step 0 of the deploy also stopped forcing a recreate. It rebuilt a
healthy lane on every invocation and took whatever model install was in
flight with it; compose recreates on a changed configuration by itself.

The wait no longer looks like a hang: it reports how many of the wanted
languages the server offers, which ones are still missing, how long it
has waited against its budget, and what the lane has pulled. i18n-tune
waits the same way and gets the same report.

service_url() and lane_container() replace the two places that each
rebuilt one of those strings, and deploy() reads the environment once.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-27 10:56:42 +02:00

72 lines
2.4 KiB
Python

from __future__ import annotations
import contextlib
import importlib
import unittest
import unittest.mock as mock
from pathlib import Path
from tempfile import TemporaryDirectory
from utils.i18n.catalog import catalog_path, new_catalog, write_catalog
from utils.i18n.languages import LANGUAGES_FILE
from utils.i18n.libretranslate import Outcome
cli = importlib.import_module("cli.build.i18n.__main__")
LANGUAGES = """\
en: {name: English, native: English, direction: ltr, libretranslate: true}
de: {name: German, native: Deutsch, direction: ltr, libretranslate: true}
fr: {name: French, native: Français, direction: ltr, libretranslate: true}
"""
def _catalog(root: Path, code: str, translation: str) -> None:
catalog = new_catalog("core", code)
catalog.add("Hello", translation, context="menu:x:title")
path = catalog_path(root, code, "core")
path.parent.mkdir(parents=True)
write_catalog(path, catalog)
class TestTranslate(unittest.TestCase):
def setUp(self) -> None:
self._tmp = TemporaryDirectory()
self.addCleanup(self._tmp.cleanup)
self.root = Path(self._tmp.name)
(self.root / LANGUAGES_FILE).parent.mkdir()
(self.root / LANGUAGES_FILE).write_text(LANGUAGES, encoding="utf-8")
_catalog(self.root, "de", "Hallo")
def _run(self) -> tuple[int, mock.Mock, mock.Mock]:
started = mock.Mock(return_value=contextlib.nullcontext("http://lt"))
client = mock.Mock()
client.return_value.translate.side_effect = lambda texts, code: Outcome(
["Bonjour"] * len(texts), 0, 0, ""
)
with (
mock.patch.object(cli, "PROJECT_ROOT", self.root),
mock.patch.object(cli, "server", started),
mock.patch.object(cli, "LibreTranslate", client),
):
return cli.translate(["core"], []), started, client
def test_only_languages_with_pending_entries_are_awaited(self) -> None:
_catalog(self.root, "fr", "")
status, _started, client = self._run()
self.assertEqual(status, 0)
self.assertEqual(client.return_value.wait.call_args.args[0], ["fr"])
def test_nothing_pending_starts_no_container(self) -> None:
_catalog(self.root, "fr", "Bonjour")
status, started, _client = self._run()
self.assertEqual(status, 0)
started.assert_not_called()
if __name__ == "__main__":
unittest.main()