feat(settings): настройка relay_dir — абсолютный путь папки реле или null (#5)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-13 00:37:59 +04:00
parent d4a7f29cbd
commit e025577d05
3 changed files with 32 additions and 0 deletions

View File

@ -1,6 +1,7 @@
"""Настройки хаба: пары ключ → JSON в таблице setting. Любая правка поднимает settings_version,
агент перечитывает настройки, когда видит новую версию в ответе claim."""
import json
import os
import secrets
DEFAULT_PACING = {"ensure_issue": 120, "ensure_fields": 120, "ensure_comment": 30, "ensure_status": 30,
@ -29,6 +30,7 @@ DEFAULTS = {
"transitions_map": {},
"link_types": {},
"board_id": None,
"relay_dir": None,
}
# Что получает агент (без токенов и служебных ключей хаба)
@ -47,6 +49,7 @@ def validate(key, value) -> None:
"""Проверить значение настройки до записи; ValueError — на 422 в API.
pacing — объект {тип команды: целое >= 0} только с известными типами команд;
backup_keep, auto_refresh_minutes, min_agent_version — целые >= 0; port — целое 1..65535;
relay_dir — null или абсолютный путь строкой;
остальные ключи — по типу значения по умолчанию (объект, список, строка, флаг), если оно не None;
неизвестные ключи и ключи с умолчанием None принимаются как есть."""
if key == "pacing":
@ -66,6 +69,10 @@ def validate(key, value) -> None:
if not _is_int(value) or value < 0:
raise ValueError(f"{key} — целое >= 0")
return
if key == "relay_dir":
if value is not None and (not isinstance(value, str) or not os.path.isabs(value)):
raise ValueError("relay_dir — абсолютный путь папки реле или null")
return
default = DEFAULTS.get(key)
if default is not None and type(value) is not type(default):
names = {dict: "объект", list: "список", str: "строка", bool: "true/false", int: "целое"}

View File

@ -301,3 +301,15 @@ def test_full_refresh_marks_unseen_issue_as_neighbor(hub):
# частичный refresh (по ключам) других задач ничего не помечает
_run_refresh(hub, {"keys": ["A-1"]}, [{"key": "A-1", "summary": "т2", "labels": []}])
assert hub["client"].get("/issues/A-1")["neighbor"] is False
def test_relay_dir_put_validation(hub):
c = hub["client"]
before = c.get("/settings")["settings_version"]
with pytest.raises(HubError) as e:
c.put("/settings", {"relay_dir": "rshb/jira-bridge"})
assert e.value.status == 422
assert c.get("/settings")["settings_version"] == before
assert c.put("/settings", {"relay_dir": "/tmp/jira-bridge"})["settings_version"] == before + 1
assert c.put("/settings", {"relay_dir": None})["settings_version"] == before + 2
assert "relay_dir" not in hub["agent"].get("/agent/settings")

View File

@ -53,3 +53,16 @@ def test_init_from_config():
with pytest.raises(RuntimeError):
settings.init_from_config(conn, cfg)
settings.init_from_config(conn, cfg, force=True)
def test_relay_dir_default_and_validation(tmp_path):
conn = db.connect(tmp_path / "hub.sqlite")
settings.ensure_defaults(conn)
assert settings.get(conn, "relay_dir") is None
settings.validate("relay_dir", None)
settings.validate("relay_dir", "/Volumes/externalSSD/utm/rshb/jira-bridge")
for bad in ("rshb/jira-bridge", "", 5, ["/x"]):
with pytest.raises(ValueError):
settings.validate("relay_dir", bad)
assert "relay_dir" not in settings.AGENT_KEYS
conn.close()