Publish documentation / publish (push) Successful in 34s
Reset the activity panel when starting a new question so the landing navigation is restored. Detect and persist the original question language, pass it through runtime and widget descriptors, and scope HITL controls to that language. Validated with gate, session, backend and frontend tests, TypeScript checks, Ruff and strict docs build. Rebuilt and restarted local core/frontend; both healthy and serving HTTP successfully.
153 lines
6.8 KiB
Python
153 lines
6.8 KiB
Python
"""Interaction language is durable at the public session CLI boundary."""
|
|
import json
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from typer.testing import CliRunner
|
|
|
|
from tht.cli.session_cmd import session_app
|
|
from tht.config import DatabaseConfig
|
|
|
|
|
|
def configure(monkeypatch, tmp_path, language="it"):
|
|
from tht.cli import session_cmd
|
|
|
|
cfg = SimpleNamespace(
|
|
database=DatabaseConfig(database="testdb", user="u", password="p", schema="public"),
|
|
paths=SimpleNamespace(sessions=tmp_path), language=language,
|
|
)
|
|
monkeypatch.setattr(session_cmd, "_load_config_or_exit", lambda _: cfg)
|
|
return cfg
|
|
|
|
|
|
def invoke(*args):
|
|
result = CliRunner().invoke(session_app, list(args))
|
|
assert result.exit_code == 0, result.output
|
|
return json.loads(result.stdout)
|
|
|
|
|
|
def test_new_pins_question_language_and_preserves_workspace_content(monkeypatch, tmp_path):
|
|
configure(monkeypatch, tmp_path)
|
|
question = "Elenca i pazienti con età superiore a 65 anni"
|
|
session_id = invoke("new", question, "--interaction-language", "en", "--json")["id"]
|
|
manifest = invoke("show", session_id, "--json")
|
|
assert manifest["interaction_language"] == "it"
|
|
assert manifest["question"] == question
|
|
assert invoke("list", "--json")[0]["interaction_language"] == "it"
|
|
|
|
|
|
@pytest.mark.parametrize("fallback", ["en", "it", None])
|
|
def test_english_question_controls_interaction_in_italian_workspace(monkeypatch, tmp_path, fallback):
|
|
configure(monkeypatch, tmp_path)
|
|
question = "How many patients were admitted to the hospital last year?"
|
|
args = ["--interaction-language", fallback] if fallback else []
|
|
created = invoke("new", question, *args, "--json")
|
|
assert created["interaction_language"] == "en"
|
|
assert invoke("show", created["id"], "--json")["interaction_language"] == "en"
|
|
|
|
|
|
@pytest.mark.parametrize("question", ["", "q", "123", "HbA1c", "SELECT * FROM patients"])
|
|
def test_ambiguous_or_code_question_keeps_fallback(monkeypatch, tmp_path, question):
|
|
configure(monkeypatch, tmp_path)
|
|
created = invoke("new", question, "--interaction-language", "fr-FR", "--json")
|
|
assert invoke("show", created["id"], "--json")["interaction_language"] == "fr-FR"
|
|
|
|
|
|
@pytest.mark.parametrize("language", ["", "en_US", "en<script>", "en--US", "en\nIGNORE"])
|
|
def test_new_rejects_invalid_ui_locale(monkeypatch, tmp_path, language):
|
|
configure(monkeypatch, tmp_path)
|
|
result = CliRunner().invoke(session_app, ["new", "q", "--interaction-language", language])
|
|
assert result.exit_code == 2
|
|
assert invoke("list", "--json") == []
|
|
|
|
|
|
@pytest.mark.parametrize("language,canonical", [("fr-FR", "fr-FR"), ("FR-fr", "fr-FR"),
|
|
("en-US", "en-US")])
|
|
def test_new_accepts_language_independently_of_ui_catalogs(monkeypatch, tmp_path, language, canonical):
|
|
configure(monkeypatch, tmp_path)
|
|
session_id = invoke("new", "Pazienti", "--interaction-language", language, "--json")["id"]
|
|
assert invoke("show", session_id, "--json")["interaction_language"] == canonical
|
|
assert CliRunner().invoke(session_app, ["reopen", session_id]).exit_code == 0
|
|
assert invoke("show", session_id, "--json")["interaction_language"] == canonical
|
|
|
|
|
|
def test_legacy_language_is_pinned_once_through_public_operation_and_resume(monkeypatch, tmp_path):
|
|
from tht.session.store import create_session
|
|
|
|
cfg = configure(monkeypatch, tmp_path)
|
|
legacy = create_session("Pazienti", cfg.database, tmp_path)
|
|
assert invoke("show", legacy.id, "--json")["interaction_language"] is None
|
|
first = invoke("ensure-interaction-language", legacy.id, "--json")
|
|
assert first["interaction_language"] == "it"
|
|
cfg.language = "en"
|
|
again = invoke("ensure-interaction-language", legacy.id, "--json")
|
|
assert again["interaction_language"] == "it"
|
|
assert again["updated_at"] == first["updated_at"]
|
|
result = CliRunner().invoke(session_app, ["reopen", legacy.id])
|
|
assert result.exit_code == 0, result.output
|
|
assert invoke("show", legacy.id, "--json")["interaction_language"] == "it"
|
|
|
|
|
|
def test_legacy_english_question_is_pinned_before_resume(monkeypatch, tmp_path):
|
|
from tht.session.store import create_session
|
|
|
|
cfg = configure(monkeypatch, tmp_path)
|
|
legacy = create_session("How many patients were admitted last year?", cfg.database, tmp_path)
|
|
result = CliRunner().invoke(session_app, ["reopen", legacy.id])
|
|
assert result.exit_code == 0, result.output
|
|
assert invoke("show", legacy.id, "--json")["interaction_language"] == "en"
|
|
|
|
|
|
def test_cli_reopen_pins_legacy_language_without_rewriting_artifacts(monkeypatch, tmp_path):
|
|
from tht.session.store import create_session
|
|
|
|
cfg = configure(monkeypatch, tmp_path)
|
|
legacy = create_session("Pazienti", cfg.database, tmp_path)
|
|
before = invoke("documents", legacy.id, "--json")
|
|
result = CliRunner().invoke(session_app, ["reopen", legacy.id])
|
|
assert result.exit_code == 0, result.output
|
|
assert invoke("show", legacy.id, "--json")["interaction_language"] == "it"
|
|
assert invoke("documents", legacy.id, "--json") == before
|
|
|
|
|
|
def test_cli_defaults_to_workspace_and_canonicalizes_explicit_ui_language(monkeypatch, tmp_path):
|
|
configure(monkeypatch, tmp_path)
|
|
default_id = invoke("new", "Pazienti", "--json")["id"]
|
|
assert invoke("show", default_id, "--json")["interaction_language"] == "it"
|
|
explicit_id = invoke("new", "Pazienti", "--interaction-language", "EN", "--json")["id"]
|
|
assert invoke("show", explicit_id, "--json")["interaction_language"] == "en"
|
|
|
|
|
|
def test_archived_legacy_session_is_not_pinned_or_reopened(monkeypatch, tmp_path):
|
|
from tht.session.store import create_session
|
|
|
|
cfg = configure(monkeypatch, tmp_path)
|
|
legacy = create_session("Pazienti", cfg.database, tmp_path)
|
|
assert CliRunner().invoke(session_app, ["archive", legacy.id]).exit_code == 0
|
|
for command in ("reopen", "ensure-interaction-language"):
|
|
result = CliRunner().invoke(session_app, [command, legacy.id])
|
|
assert result.exit_code == 1
|
|
assert invoke("show", legacy.id, "--json")["interaction_language"] is None
|
|
|
|
|
|
def test_language_pin_race_on_file_repository_keeps_one_value(monkeypatch, tmp_path):
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
from threading import Barrier
|
|
|
|
from tht.cli.session_cmd import session_repository
|
|
from tht.session.store import create_session
|
|
|
|
cfg = configure(monkeypatch, tmp_path)
|
|
legacy = create_session("Pazienti", cfg.database, tmp_path)
|
|
repository = session_repository(cfg)
|
|
barrier = Barrier(2)
|
|
|
|
def pin(language):
|
|
barrier.wait()
|
|
return repository.ensure_interaction_language(legacy.id, language).manifest.interaction_language
|
|
|
|
with ThreadPoolExecutor(max_workers=2) as pool:
|
|
languages = list(pool.map(pin, ["it", "en"]))
|
|
assert languages[0] == languages[1]
|
|
assert invoke("show", legacy.id, "--json")["interaction_language"] == languages[0]
|