test: settings output_language survives save→apply→recreate
Some checks failed
Release / semantic-release (push) Successful in 42s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / build-linux (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled
tests / Unit tests (Linux, Python 3.11) (push) Has been cancelled

Integration test driving the real bridge _save() and engine
_resolve_output_language(): a language chosen in the settings UI is written to
both the persistent volume and the runtime config, applies immediately (config
wins over the OUTPUT_LANGUAGE env), and survives a simulated container recreate
(entrypoint re-renders the config then merges the persistent override). Also
asserts the persona and reply directive both follow the persisted language.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
javis-bot
2026-06-16 19:56:13 +09:00
parent 7870a76314
commit ccddbd6448

View File

@@ -0,0 +1,119 @@
"""End-to-end persistence of the output_language settings change.
Closes the loop the reviewer flagged: a language chosen in the settings web UI
must (1) take effect immediately for the reply engine and (2) survive a
container recreate. The pieces:
bridge._save() -> writes BOTH /data/jarvis-settings.json (persistent)
and JARVIS_CONFIG_PATH (live runtime config)
entrypoint merge -> on recreate, re-renders config from the env template
then merges the persistent overrides back on top
engine._resolve_output_language() -> reads JARVIS_CONFIG_PATH, config wins
over the OUTPUT_LANGUAGE env
This test drives the REAL bridge save function and the REAL engine resolver
(the resolver is loaded standalone because the full engine import needs the
mcp package, which isn't installed in CI here). It simulates the env default
disagreeing with the chosen language, which is exactly the bug condition.
"""
import ast
import json
import os
from pathlib import Path
import pytest
# bridge.settings_web imports only stdlib at module load (flask is imported
# lazily inside register()), so it is safe to import directly.
import sys
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "bridge"))
import settings_web # noqa: E402
def _load_resolver():
"""Load engine._resolve_output_language + _extra_config without importing
the heavy jarvis package (which pulls in the optional mcp dependency)."""
src = (
Path(__file__).resolve().parents[1]
/ "src/jarvis/reply/engine.py"
).read_text("utf-8")
tree = ast.parse(src)
wanted = {"_extra_config", "_resolve_output_language"}
mod = ast.Module(
body=[
n
for n in tree.body
if isinstance(n, ast.FunctionDef) and n.name in wanted
],
type_ignores=[],
)
ns = {"os": os, "Optional": __import__("typing").Optional}
exec(compile(mod, "engine_subset", "exec"), ns) # noqa: S102
return ns["_resolve_output_language"]
def _simulate_recreate_merge(template_lang: str, config_path: Path, persist_path: Path):
"""Mirror docker/entrypoint.sh: re-render the runtime config from the env
template, then merge the persistent overrides on top."""
config_path.write_text(json.dumps({"output_language": template_lang}), "utf-8")
if persist_path.exists():
base = json.loads(config_path.read_text("utf-8"))
ov = json.loads(persist_path.read_text("utf-8"))
base.update(ov)
config_path.write_text(json.dumps(base, ensure_ascii=False, indent=2), "utf-8")
@pytest.mark.integration
def test_settings_save_applies_and_survives_recreate(monkeypatch, tmp_path):
config_path = tmp_path / "jarvis.json"
persist_path = tmp_path / "data" / "jarvis-settings.json"
# The compose env default is the "old" language that must be overridden.
monkeypatch.setenv("OUTPUT_LANGUAGE", "English")
monkeypatch.setenv("JARVIS_CONFIG_PATH", str(config_path))
monkeypatch.setenv("JARVIS_SETTINGS_PATH", str(persist_path))
# Start from the env-rendered config (as entrypoint would produce).
config_path.write_text(json.dumps({"output_language": "English"}), "utf-8")
resolve = _load_resolver()
# Before the change: the env default wins.
assert resolve() == "English"
# 1) User saves Korean in the settings UI.
settings_web._save({"output_language": "Korean"})
# Both targets are written.
assert json.loads(config_path.read_text("utf-8"))["output_language"] == "Korean"
assert json.loads(persist_path.read_text("utf-8"))["output_language"] == "Korean"
# 2) Applies immediately: the resolver now returns Korean (config > env).
assert resolve() == "Korean"
# 3) Survives a container recreate: entrypoint re-renders the config from the
# env template (still English) then merges the persistent override.
_simulate_recreate_merge("English", config_path, persist_path)
assert json.loads(config_path.read_text("utf-8"))["output_language"] == "Korean"
assert resolve() == "Korean"
@pytest.mark.integration
def test_persona_and_directive_follow_persisted_language(monkeypatch, tmp_path):
"""After persistence, the persona and the reply directive both lock to the
saved language, not the env default."""
from jarvis.system_prompt import build_system_prompt, reply_language_directive
config_path = tmp_path / "jarvis.json"
persist_path = tmp_path / "data" / "jarvis-settings.json"
monkeypatch.setenv("OUTPUT_LANGUAGE", "English")
monkeypatch.setenv("JARVIS_CONFIG_PATH", str(config_path))
monkeypatch.setenv("JARVIS_SETTINGS_PATH", str(persist_path))
config_path.write_text(json.dumps({"output_language": "English"}), "utf-8")
settings_web._save({"output_language": "Korean"})
lang = _load_resolver()()
persona = build_system_prompt("Jarvis", lang)
directive = reply_language_directive(lang, "melo")
assert "in Korean" in persona and "in English" not in persona
assert directive is not None and "Korean" in directive