mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-26 00:28:17 +08:00
The test calls collect_strings() directly, which now lazy-imports langflow.utils.i18n_keys. Patch a minimal fake module into sys.modules so the test runs without langflow installed. Also fix the key assertion to match the lowercased output of normalize_component_key.
169 lines
6.4 KiB
Python
169 lines
6.4 KiB
Python
"""Tests for extract_backend_strings.py."""
|
|
|
|
import json
|
|
from unittest.mock import patch
|
|
|
|
import extract_backend_strings as extract_mod
|
|
import pytest
|
|
|
|
SAMPLE_STRINGS = {
|
|
"components.ChatInput.description": "Get chat inputs from the Playground.",
|
|
"components.ChatInput.display_name": "Chat Input",
|
|
"components.ChatInput.inputs.input_value.display_name": "Input Text",
|
|
"components.ChatInput.outputs.message.display_name": "Chat Message",
|
|
}
|
|
|
|
|
|
def _run_main(*args):
|
|
with patch("sys.argv", ["extract_backend_strings.py", *args]):
|
|
extract_mod.main()
|
|
|
|
|
|
class TestExtractBackendStrings:
|
|
def test_writes_en_json_to_output_path(self, tmp_path):
|
|
output_file = tmp_path / "en.json"
|
|
with (
|
|
patch.object(extract_mod, "collect_strings", return_value=SAMPLE_STRINGS),
|
|
patch.object(extract_mod, "OUTPUT_PATH", output_file),
|
|
):
|
|
_run_main()
|
|
|
|
assert output_file.exists()
|
|
data = json.loads(output_file.read_text(encoding="utf-8"))
|
|
assert data == SAMPLE_STRINGS
|
|
|
|
def test_writes_keys_in_order_returned_by_collect_strings(self, tmp_path):
|
|
"""main() writes keys in the order collect_strings() returns them.
|
|
|
|
collect_strings() always returns sorted keys, so the output is sorted in practice.
|
|
"""
|
|
output_file = tmp_path / "en.json"
|
|
pre_sorted = { # collect_strings() always returns sorted keys
|
|
"components.A.display_name": "A",
|
|
"components.M.display_name": "M",
|
|
"components.Z.display_name": "Z",
|
|
}
|
|
with (
|
|
patch.object(extract_mod, "collect_strings", return_value=pre_sorted),
|
|
patch.object(extract_mod, "OUTPUT_PATH", output_file),
|
|
):
|
|
_run_main()
|
|
|
|
raw = output_file.read_text(encoding="utf-8")
|
|
keys_in_order = [line.strip().split('"')[1] for line in raw.splitlines() if '": "' in line]
|
|
assert keys_in_order == list(pre_sorted.keys())
|
|
|
|
def test_check_mode_passes_when_in_sync(self, tmp_path):
|
|
output_file = tmp_path / "en.json"
|
|
expected_content = json.dumps(SAMPLE_STRINGS, ensure_ascii=False, indent=2) + "\n"
|
|
output_file.write_text(expected_content, encoding="utf-8")
|
|
|
|
with (
|
|
patch.object(extract_mod, "collect_strings", return_value=SAMPLE_STRINGS),
|
|
patch.object(extract_mod, "OUTPUT_PATH", output_file),
|
|
pytest.raises(SystemExit) as exc_info,
|
|
):
|
|
_run_main("--check")
|
|
|
|
assert exc_info.value.code == 0
|
|
|
|
def test_check_mode_fails_when_out_of_sync(self, tmp_path):
|
|
output_file = tmp_path / "en.json"
|
|
output_file.write_text('{"components.OldKey.display_name": "Old"}', encoding="utf-8")
|
|
|
|
with (
|
|
patch.object(extract_mod, "collect_strings", return_value=SAMPLE_STRINGS),
|
|
patch.object(extract_mod, "OUTPUT_PATH", output_file),
|
|
pytest.raises(SystemExit) as exc_info,
|
|
):
|
|
_run_main("--check")
|
|
|
|
assert exc_info.value.code == 1
|
|
|
|
def test_check_mode_fails_when_file_missing(self, tmp_path):
|
|
missing_file = tmp_path / "en.json"
|
|
|
|
with (
|
|
patch.object(extract_mod, "collect_strings", return_value=SAMPLE_STRINGS),
|
|
patch.object(extract_mod, "OUTPUT_PATH", missing_file),
|
|
pytest.raises(SystemExit) as exc_info,
|
|
):
|
|
_run_main("--check")
|
|
|
|
assert exc_info.value.code == 1
|
|
|
|
def test_creates_output_directory_if_missing(self, tmp_path):
|
|
nested_file = tmp_path / "nested" / "dir" / "en.json"
|
|
|
|
with (
|
|
patch.object(extract_mod, "collect_strings", return_value=SAMPLE_STRINGS),
|
|
patch.object(extract_mod, "OUTPUT_PATH", nested_file),
|
|
):
|
|
_run_main()
|
|
|
|
assert nested_file.exists()
|
|
|
|
def test_collect_strings_skips_deactivated_modules(self):
|
|
"""collect_strings() must skip any module whose name contains 'deactivated'."""
|
|
import hashlib
|
|
import pkgutil
|
|
import re
|
|
import sys
|
|
import types
|
|
|
|
fake_modules = [
|
|
pkgutil.ModuleInfo(module_finder=None, name="lfx.components.active", ispkg=False),
|
|
pkgutil.ModuleInfo(module_finder=None, name="lfx.components.deactivated.old", ispkg=False),
|
|
]
|
|
|
|
fake_components_pkg = types.ModuleType("lfx.components")
|
|
fake_components_pkg.__path__ = []
|
|
fake_components_pkg.__name__ = "lfx.components"
|
|
|
|
active_module = types.ModuleType("lfx.components.active")
|
|
active_module.__name__ = "lfx.components.active"
|
|
|
|
class FakeComponent:
|
|
__module__ = "lfx.components.active"
|
|
code_class_base_inheritance = True
|
|
display_name = "Active Component"
|
|
description = "An active component"
|
|
name = "ActiveComponent"
|
|
inputs = []
|
|
outputs = []
|
|
|
|
active_module.FakeComponent = FakeComponent
|
|
|
|
# Provide a minimal fake langflow.utils.i18n_keys so collect_strings()
|
|
# can be called without langflow installed in the test environment.
|
|
fake_i18n_keys = types.ModuleType("langflow.utils.i18n_keys")
|
|
|
|
def _content_hash(english: str) -> str:
|
|
return hashlib.sha256(english.encode()).hexdigest()[:8]
|
|
|
|
fake_i18n_keys.component_field_key = lambda norm, path, eng: f"components.{norm}.{path}.{_content_hash(eng)}"
|
|
fake_i18n_keys.normalize_component_key = lambda name: name.replace(" ", "").lower()
|
|
fake_i18n_keys.safe_flow_key = lambda name: re.sub(r"[^a-zA-Z0-9]+", "_", name).strip("_").lower()
|
|
|
|
fake_langflow = types.ModuleType("langflow")
|
|
fake_langflow_utils = types.ModuleType("langflow.utils")
|
|
|
|
with (
|
|
patch.dict(
|
|
sys.modules,
|
|
{
|
|
"lfx": types.ModuleType("lfx"),
|
|
"lfx.components": fake_components_pkg,
|
|
"langflow": fake_langflow,
|
|
"langflow.utils": fake_langflow_utils,
|
|
"langflow.utils.i18n_keys": fake_i18n_keys,
|
|
},
|
|
),
|
|
patch("pkgutil.walk_packages", return_value=fake_modules),
|
|
patch("importlib.import_module", return_value=active_module),
|
|
):
|
|
strings = extract_mod.collect_strings()
|
|
|
|
# Deactivated module was skipped; active module processed
|
|
assert any("activecomponent" in k for k in strings)
|