Port fristhon/telminal (MIT) into the aurelio monorepo as a self-hosted Telegram shell bot for the fleet gateway (CT-217). - telminal/ package: config (env + state file, refuses empty admins), core orchestrator (event handlers, router, watchers, interactive mode, file up/download, xterm.js image render), process (pexpect PTY + streaming + inline control buttons), telegram (Telethon wrapper, swappable for tests), cli (entry point reading TELEGRAM_* env), utils, values. - aurelio hardening vs upstream: no first-run random token auth (explicit admin allowlist required); cd sandbox validated against working root; secrets from env mirroring the CT-217 gateway .env. - 35 real tests (pty capture/control-char, router, watchers, perms, sandbox, fake-client orchestration) -- all green. - deployment: systemd/telminal.service, .env.example entries, README, AGENTS.md. Verified: pytest 35 passed; CLI refuses start with missing env / no admins.
108 lines
3.5 KiB
Python
108 lines
3.5 KiB
Python
"""Tests for configuration, utils, and routing/watcher regexes."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
import pytest
|
|
|
|
from telminal.config import Config, ConfigError
|
|
from telminal.values import GET_PATTERN, WATCHER_PATTERN
|
|
from telminal import utils
|
|
|
|
|
|
def test_config_requires_env(monkeypatch):
|
|
for var in ("TELEGRAM_API_ID", "TELEGRAM_API_HASH", "TELEGRAM_TOKEN"):
|
|
monkeypatch.delenv(var, raising=False)
|
|
with pytest.raises(ConfigError):
|
|
Config.from_env()
|
|
|
|
|
|
def test_config_reads_env_admins(monkeypatch, tmp_path):
|
|
monkeypatch.setenv("TELEGRAM_API_ID", "123")
|
|
monkeypatch.setenv("TELEGRAM_API_HASH", "h")
|
|
monkeypatch.setenv("TELEGRAM_TOKEN", "t")
|
|
monkeypatch.setenv("TELEGRAM_ADMINS", "111, 222")
|
|
# from_env intentionally ignores TELEGRAM_ADMINS (that's the CLI's job);
|
|
# admins come only from the persisted state file, which is empty here.
|
|
cfg = Config.from_env(allow_empty_admins=True)
|
|
assert cfg.api_id == 123
|
|
assert cfg.admins == []
|
|
|
|
|
|
def test_cli_build_config_merges_env_admins(monkeypatch, tmp_path):
|
|
monkeypatch.setenv("TELEGRAM_API_ID", "123")
|
|
monkeypatch.setenv("TELEGRAM_API_HASH", "h")
|
|
monkeypatch.setenv("TELEGRAM_TOKEN", "t")
|
|
monkeypatch.setenv("TELEGRAM_ADMINS", "111, 222")
|
|
from telminal.cli import build_config
|
|
|
|
cfg = build_config()
|
|
assert cfg.admins == [111, 222]
|
|
|
|
|
|
def test_config_refuses_empty_admins(monkeypatch, tmp_path):
|
|
monkeypatch.setenv("TELEGRAM_API_ID", "123")
|
|
monkeypatch.setenv("TELEGRAM_API_HASH", "h")
|
|
monkeypatch.setenv("TELEGRAM_TOKEN", "t")
|
|
monkeypatch.delenv("TELEGRAM_ADMINS", raising=False)
|
|
with pytest.raises(ConfigError):
|
|
Config.from_env()
|
|
|
|
|
|
def test_config_bad_api_id(monkeypatch):
|
|
monkeypatch.setenv("TELEGRAM_API_ID", "not-an-int")
|
|
monkeypatch.setenv("TELEGRAM_API_HASH", "h")
|
|
monkeypatch.setenv("TELEGRAM_TOKEN", "t")
|
|
with pytest.raises(ConfigError):
|
|
Config.from_env(allow_empty_admins=True)
|
|
|
|
|
|
def test_config_persists_and_reloads(tmp_path):
|
|
state = tmp_path / "config.json"
|
|
cfg = Config(
|
|
api_id=1, api_hash="h", token="t", admins=[5],
|
|
working_dir=str(tmp_path), state_file=state,
|
|
)
|
|
cfg.save_state()
|
|
assert state.exists()
|
|
reloaded = Config(api_id=1, api_hash="h", token="t", state_file=state)
|
|
reloaded._load_state()
|
|
assert reloaded.admins == [5]
|
|
assert reloaded.working_dir == str(tmp_path)
|
|
|
|
|
|
def test_watcher_pattern():
|
|
m = __import__("re").match(WATCHER_PATTERN, "!watch 50s telminal.log")
|
|
assert m is not None
|
|
assert m.groups() == ("50", "s", "telminal.log")
|
|
m2 = __import__("re").match(WATCHER_PATTERN, "!watch 24h /backups/sql.dump")
|
|
assert m2.groups() == ("24", "h", "/backups/sql.dump")
|
|
assert __import__("re").match(WATCHER_PATTERN, "!watch bogus") is None
|
|
|
|
|
|
def test_get_pattern():
|
|
assert __import__("re").match(GET_PATTERN, "!get /etc/passwd")
|
|
assert not __import__("re").match(GET_PATTERN, "echo hi")
|
|
|
|
|
|
def test_html_template_render():
|
|
rendered = utils.HTML_TEMPLATE.format(title="t", data="hello world")
|
|
assert "hello world" in rendered
|
|
assert "<html" in rendered
|
|
|
|
|
|
def test_timestamp_and_seconds_readable():
|
|
assert utils.timestamp_to_readable(0).count(":") == 2
|
|
assert utils.seconds_to_readable(65) == "0:01:05"
|
|
|
|
|
|
def test_media_strip():
|
|
assert len(utils.HTML_TEMPLATE) # sanity import works
|
|
from telminal.telegram import Telegram
|
|
|
|
short = "hi"
|
|
assert Telegram.media_strip(short) == "hi"
|
|
long = "y" * 2000
|
|
stripped = Telegram.media_strip(long)
|
|
assert len(stripped) == 1024
|