Files
wehub-resource-sync e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:00:43 +08:00

385 lines
14 KiB
Python

from __future__ import annotations
import json
from pathlib import Path
from deeptutor.services.config.runtime_settings import (
RuntimeSettingsService,
ensure_runtime_settings_files,
)
RUNTIME_ENV_KEYS = (
"BACKEND_PORT",
"FRONTEND_PORT",
"NEXT_PUBLIC_API_BASE_EXTERNAL",
"PUBLIC_API_BASE",
"NEXT_PUBLIC_API_BASE",
"CORS_ORIGIN",
"CORS_ORIGINS",
"DISABLE_SSL_VERIFY",
"CHAT_ATTACHMENT_DIR",
"AUTH_ENABLED",
"NEXT_PUBLIC_AUTH_ENABLED",
"AUTH_USERNAME",
"AUTH_PASSWORD_HASH",
"AUTH_TOKEN_EXPIRE_HOURS",
"AUTH_COOKIE_SECURE",
"POCKETBASE_URL",
"POCKETBASE_PORT",
"POCKETBASE_EXTERNAL_URL",
"POCKETBASE_ADMIN_EMAIL",
"POCKETBASE_ADMIN_PASSWORD",
)
def _read_json(path: Path) -> dict:
return json.loads(path.read_text(encoding="utf-8"))
def _clear_runtime_env(monkeypatch) -> None:
for key in RUNTIME_ENV_KEYS:
monkeypatch.delenv(key, raising=False)
def test_runtime_settings_creates_defaults_without_reading_dotenv(tmp_path: Path) -> None:
legacy_env = tmp_path / ".env"
legacy_env.write_text(
"BACKEND_PORT=9001\nAUTH_ENABLED=true\nPOCKETBASE_URL=http://legacy.invalid\n",
encoding="utf-8",
)
service = RuntimeSettingsService(tmp_path / "settings")
assert service.load_system(include_process_overrides=False)["backend_port"] == 8001
assert service.load_auth(include_process_overrides=False)["enabled"] is False
assert service.load_integrations(include_process_overrides=False)["pocketbase_url"] == ""
assert _read_json(service.path_for("system"))["backend_port"] == 8001
assert _read_json(service.path_for("auth"))["enabled"] is False
def test_runtime_process_env_is_explicit_override(tmp_path: Path) -> None:
service = RuntimeSettingsService(
tmp_path / "settings",
process_env={
"BACKEND_PORT": "9100",
"AUTH_ENABLED": "true",
"POCKETBASE_PORT": "9090",
},
)
service.save_system({"backend_port": 8001, "frontend_port": 3782})
service.save_auth({"enabled": False, "username": "admin"})
service.save_integrations({"pocketbase_port": 8090})
assert service.load_system()["backend_port"] == 9100
assert service.load_auth()["enabled"] is True
assert service.load_integrations()["pocketbase_port"] == 9090
assert _read_json(service.path_for("system"))["backend_port"] == 8001
assert _read_json(service.path_for("auth"))["enabled"] is False
assert _read_json(service.path_for("integrations"))["pocketbase_port"] == 8090
def test_render_environment_uses_json_backed_runtime_names(monkeypatch, tmp_path: Path) -> None:
_clear_runtime_env(monkeypatch)
service = RuntimeSettingsService(tmp_path / "settings")
service.save_system(
{
"backend_port": 8010,
"frontend_port": 3790,
"cors_origins": ["https://app.example"],
"disable_ssl_verify": True,
}
)
service.save_auth({"enabled": True, "username": "admin", "token_expire_hours": 12})
service.save_integrations(
{
"pocketbase_url": "http://pocketbase:8090",
"pocketbase_admin_email": "admin@example.com",
}
)
env = service.render_environment()
assert env["BACKEND_PORT"] == "8010"
assert env["FRONTEND_PORT"] == "3790"
assert env["CORS_ORIGINS"] == "https://app.example"
assert env["DISABLE_SSL_VERIFY"] == "true"
assert env["AUTH_ENABLED"] == "true"
assert env["NEXT_PUBLIC_AUTH_ENABLED"] == "true"
# Server-side proxy contract consumed by web/proxy.ts (the Next.js
# middleware). DEEPTUTOR_AUTH_ENABLED gates the login redirect;
# DEEPTUTOR_API_BASE_URL is where the frontend server reaches the backend
# (falls back to localhost:<backend_port> when no in-network / external base
# is configured).
assert env["DEEPTUTOR_AUTH_ENABLED"] == "true"
assert env["DEEPTUTOR_API_BASE_URL"] == "http://localhost:8010"
assert env["AUTH_TOKEN_EXPIRE_HOURS"] == "12"
assert env["POCKETBASE_URL"] == "http://pocketbase:8090"
assert "AUTH_SECRET" not in env
def test_system_settings_accept_public_api_base_alias_and_normalize_origins(
monkeypatch, tmp_path: Path
) -> None:
_clear_runtime_env(monkeypatch)
service = RuntimeSettingsService(tmp_path / "settings")
system = service.save_system(
{
"public_api_base": "https://api.example.com/base",
"cors_origins": "app.example.com; https://learn.example.com/path",
}
)
assert system["next_public_api_base_external"] == "https://api.example.com/base"
assert system["cors_origins"] == [
"http://app.example.com",
"https://learn.example.com",
]
assert "public_api_base" not in _read_json(service.path_for("system"))
def test_exported_environment_does_not_become_runtime_override(monkeypatch, tmp_path: Path) -> None:
_clear_runtime_env(monkeypatch)
service = RuntimeSettingsService(tmp_path / "settings")
service.save_system({"backend_port": 8010})
service.save_auth({"enabled": False})
service.export_environment(overwrite=True)
assert service.load_system()["backend_port"] == 8010
assert service.load_auth()["enabled"] is False
service.save_system({"backend_port": 8020})
service.save_auth({"enabled": True})
assert service.load_system()["backend_port"] == 8020
assert service.load_auth()["enabled"] is True
def test_existing_process_environment_remains_deployment_override(
monkeypatch, tmp_path: Path
) -> None:
monkeypatch.setenv("BACKEND_PORT", "9100")
service = RuntimeSettingsService(tmp_path / "settings")
service.save_system({"backend_port": 8010})
service.export_environment(overwrite=True)
service.save_system({"backend_port": 8020})
assert service.load_system()["backend_port"] == 9100
def test_startup_ensure_creates_missing_runtime_jsons_with_defaults(
monkeypatch, tmp_path: Path
) -> None:
_clear_runtime_env(monkeypatch)
settings_dir = tmp_path / "settings"
from deeptutor.services.config import model_catalog as model_catalog_module
from deeptutor.services.config import runtime_settings as runtime_settings_module
runtime_settings_module.RuntimeSettingsService._instances.clear()
model_catalog_module.ModelCatalogService._instances.clear()
monkeypatch.setattr(runtime_settings_module, "_global_settings_dir", lambda: settings_dir)
monkeypatch.setattr(
model_catalog_module.get_path_service(),
"get_settings_file",
lambda name: settings_dir / f"{name}.json",
)
ensure_runtime_settings_files()
assert (settings_dir / "system.json").exists()
assert (settings_dir / "auth.json").exists()
assert (settings_dir / "integrations.json").exists()
assert (settings_dir / "document_parsing.json").exists()
assert (settings_dir / "model_catalog.json").exists()
_parsing_file = _read_json(settings_dir / "document_parsing.json")
assert _parsing_file["version"] == 2
assert _parsing_file["engines"]["mineru"]["mode"] == "local"
assert _read_json(settings_dir / "system.json")["backend_port"] == 8001
assert _read_json(settings_dir / "auth.json")["enabled"] is False
assert _read_json(settings_dir / "integrations.json")["pocketbase_url"] == ""
assert set(_read_json(settings_dir / "model_catalog.json")["services"]) == {
"llm",
"embedding",
"search",
"tts",
"stt",
"imagegen",
"videogen",
}
def test_mineru_defaults_and_normalization(tmp_path: Path) -> None:
service = RuntimeSettingsService(tmp_path / "settings", process_env={})
defaults = service.load_mineru(include_process_overrides=False)
assert defaults["mode"] == "local"
assert defaults["model_version"] == "pipeline"
assert defaults["api_token"] == ""
saved = service.save_mineru(
{
"mode": "CLOUD", # case-insensitive
"api_base_url": "https://mineru.net/", # trailing slash stripped
"api_token": " tok-123 ", # trimmed
"model_version": "bogus", # invalid → pipeline
"language": "", # empty → auto
"enable_formula": "no", # coerced to bool
"is_ocr": 1,
}
)
assert saved["mode"] == "cloud"
assert saved["api_base_url"] == "https://mineru.net"
assert saved["api_token"] == "tok-123"
assert saved["model_version"] == "pipeline"
assert saved["language"] == "auto"
assert saved["enable_formula"] is False
assert saved["is_ocr"] is True
# Unknown mode falls back to local.
assert service.save_mineru({"mode": "weird"})["mode"] == "local"
# Model-download fields: source whitelisted, endpoint trimmed.
saved = service.save_mineru(
{
"model_download_source": "ModelScope",
"model_download_endpoint": " https://hf-mirror.com/ ",
}
)
assert saved["model_download_source"] == "modelscope"
assert saved["model_download_endpoint"] == "https://hf-mirror.com"
assert service.save_mineru({"model_download_source": "weird"})["model_download_source"] == (
"huggingface"
)
def test_mineru_local_cli_path_roundtrip(tmp_path: Path) -> None:
service = RuntimeSettingsService(tmp_path / "settings", process_env={})
saved = service.save_mineru({"local_cli_path": " /envs/mineru/bin/mineru "})
assert saved["local_cli_path"] == "/envs/mineru/bin/mineru"
assert service.load_mineru()["local_cli_path"] == "/envs/mineru/bin/mineru"
# Default is empty (auto-detect from PATH).
assert service.save_mineru({})["local_cli_path"] == ""
def test_mineru_process_env_override(tmp_path: Path) -> None:
service = RuntimeSettingsService(
tmp_path / "settings",
process_env={
"MINERU_MODE": "cloud",
"MINERU_API_TOKEN": "env-token",
"MINERU_LOCAL_CLI_PATH": "/env/bin/mineru",
},
)
service.save_mineru({"mode": "local", "api_token": "file-token"})
effective = service.load_mineru()
assert effective["mode"] == "cloud"
assert effective["api_token"] == "env-token"
assert effective["local_cli_path"] == "/env/bin/mineru"
# The persisted file keeps the on-disk values (nested under engines.mineru),
# not the env overrides.
persisted = _read_json(service.path_for("document_parsing"))["engines"]["mineru"]
assert persisted["mode"] == "local"
assert persisted["api_token"] == "file-token"
def test_document_parsing_v1_to_v2_migration(tmp_path: Path) -> None:
"""A legacy flat mineru.json is folded into engines.mineru on first load,
with the active engine pinned to MinerU (preserve existing behavior)."""
from deeptutor.services.config.runtime_settings import _atomic_write_json
service = RuntimeSettingsService(tmp_path / "settings", process_env={})
legacy = {
"version": 1,
"mode": "cloud",
"api_token": "legacy-tok",
"model_version": "vlm",
"language": "ch",
}
_atomic_write_json(service.path_for("mineru"), legacy)
full = service.load_document_parsing(include_process_overrides=False)
assert full["version"] == 2
assert full["engine"] == "mineru" # migrated installs keep MinerU
mineru = full["engines"]["mineru"]
assert mineru["mode"] == "cloud"
assert mineru["api_token"] == "legacy-tok"
assert mineru["model_version"] == "vlm"
assert mineru["language"] == "ch"
assert mineru["allow_local_model_download"] is False
# All engines are always present after normalization.
assert set(full["engines"]) == {
"text_only",
"mineru",
"docling",
"markitdown",
"pymupdf4llm",
}
# Migration is persisted to the renamed file (v2, no top-level flat keys);
# the legacy mineru.json is gone.
assert not service.path_for("mineru").exists()
on_disk = _read_json(service.path_for("document_parsing"))
assert on_disk["version"] == 2
assert "mode" not in on_disk
def test_document_parsing_legacy_filename_rename(tmp_path: Path) -> None:
"""A pre-existing v2 ``mineru.json`` is renamed to ``document_parsing.json``
on first load, preserving its contents."""
from deeptutor.services.config.runtime_settings import _atomic_write_json
service = RuntimeSettingsService(tmp_path / "settings", process_env={})
existing = service.save_document_parsing({"engine": "docling"})
# save_document_parsing already writes the new filename; simulate an old
# install by moving it back to the legacy name.
service.path_for("document_parsing").rename(service.path_for("mineru"))
assert service.path_for("mineru").exists()
assert not service.path_for("document_parsing").exists()
full = service.load_document_parsing(include_process_overrides=False)
assert full["engine"] == "docling"
assert full["engines"]["mineru"] == existing["engines"]["mineru"]
# File was renamed in place, not duplicated.
assert service.path_for("document_parsing").exists()
assert not service.path_for("mineru").exists()
def test_document_parsing_fresh_default_engine_is_text_only(tmp_path: Path) -> None:
service = RuntimeSettingsService(tmp_path / "settings", process_env={})
full = service.load_document_parsing(include_process_overrides=False)
assert full["engine"] == "text_only"
assert full["engines"]["text_only"] == {}
def test_mineru_allow_local_model_download_default_off_and_env(tmp_path: Path) -> None:
service = RuntimeSettingsService(tmp_path / "settings", process_env={})
assert service.load_mineru()["allow_local_model_download"] is False
assert (
service.save_mineru({"allow_local_model_download": True})["allow_local_model_download"]
is True
)
env_service = RuntimeSettingsService(
tmp_path / "settings2",
process_env={"MINERU_ALLOW_LOCAL_MODEL_DOWNLOAD": "1"},
)
assert env_service.load_mineru()["allow_local_model_download"] is True
def test_runtime_settings_can_ignore_process_overrides(tmp_path: Path) -> None:
service = RuntimeSettingsService(
tmp_path / "settings",
process_env={
"DEEPTUTOR_IGNORE_PROCESS_ENV_OVERRIDES": "1",
"BACKEND_PORT": "9901",
"AUTH_ENABLED": "true",
},
)
service.save_system({"backend_port": 8001})
service.save_auth({"enabled": False})
assert service.load_system()["backend_port"] == 8001
assert service.load_auth()["enabled"] is False