Files
ROMFASTSQL/proxmox/lxc171-claude-agent/discord-bridge/tests/test_commands.py
Claude Agent d466f358ce feat(discord-bridge): punte Discord -> Claude Code pe LXC 171
Implementeaza planul claude-master-plan-discord-bridge-20260830 (15 taskuri,
3 lane-uri paralele) — un bot subtire discord.py peste CLI-ul `claude`, cu
proces persistent per fir alimentat pe stdin cu --input-format stream-json.

Nucleu: runner (proces persistent + reaper 20min + respawn --resume), stream
(parser tolerant), session_store (scriere atomica, lock per fir, detectare PID
reuse, recovery), limits (max 4 procese, timeout tur, rate per user, plafon cost
pe zi), render (un loop de editare per canal, interval adaptiv).

Adaptor: allowlist guild/canal/user fail-closed cu respingerea webhook-urilor,
comenzi !new/!cd/!model/!status/!stop/!cleanup, cost si model in subsolul
fiecarui raspuns. Mesajul sosit in timpul unui tur devine steering, nu tur nou.

Securitate: hook PreToolUse fail-closed care cere confirmare in Discord pentru
operatiuni ireversibile, wrapper `infra` cu lista explicita de hosturi. Deny
rules raman strat cosmetic, nu bariera (verificat: /usr/bin/ssh trece pe langa).

Ops: alerte email pe conventia repo-ului, !cleanup pentru orfani, unit systemd
user cu KillMode=control-group si limite de memorie, install.sh idempotent.

Verificat: 275 teste fara retea/Discord/API (10.8s), identic cu si fara
discord.py instalat; e2e pe CLI real confirma steering-ul mid-tur (mesaj la 6s
intr-un tool call de 25s schimba raspunsul final).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B29CApsP1JkSdjYaGaHpE7
2026-08-30 10:44:39 +00:00

232 lines
7.8 KiB
Python

"""Comenzile puntii: !new (+ --fork), !cd, !model, !status, !stop, !cleanup, !help."""
from __future__ import annotations
import pytest
import bot
try:
from tests.test_bot import (CHANNEL, USER, FakeChannel, FakeMessage, allowed, bridge)
except ImportError: # pragma: no cover
from test_bot import (CHANNEL, USER, FakeChannel, FakeMessage, allowed, bridge)
__all__ = ["allowed", "bridge"]
async def run(bridge, text, ch=None):
ch = ch or FakeChannel()
msg = FakeMessage(text, channel=ch)
label = await bridge.handle_message(msg)
return label, ch
# ------------------------------------------------------------------- parser
def test_parse_command():
cmd = bot.parse_command("!cd /workspace/romfastsql")
assert cmd.name == "cd" and cmd.rest == "/workspace/romfastsql"
assert bot.parse_command("!MODEL opus").name == "model"
assert bot.parse_command("salut") is None
assert bot.parse_command("!") is None
async def test_comanda_necunoscuta(bridge):
label, ch = await run(bridge, "!inexistenta")
assert label == "unknown-command"
assert "nu exista" in ch.all_text
async def test_help(bridge):
label, ch = await run(bridge, "!help")
assert label == "cmd:help"
for c in ("!new", "!cd", "!model", "!status", "!stop", "!cleanup"):
assert c in ch.all_text
# ---------------------------------------------------------------------- !new
async def test_new_sterge_sesiunea(bridge, store):
ch = FakeChannel()
await run(bridge, "salut", ch) # creeaza sesiunea
assert store.thread(str(ch.id))["sid"] == "sid-fake-0001"
label, _ = await run(bridge, "!new", ch)
assert label == "cmd:new"
assert store.thread(str(ch.id))["sid"] is None
assert bridge.runner.procs[str(ch.id)].alive is False
async def test_new_fork_pastreaza_contextul(bridge, store):
ch = FakeChannel()
await run(bridge, "salut", ch)
await run(bridge, "!new --fork", ch)
assert store.thread(str(ch.id))["sid"] == "sid-fake-0001"
assert "fork" in ch.all_text
assert bridge.runner.procs[str(ch.id)].sid == "sid-fake-0001"
# ----------------------------------------------------------------------- !cd
async def test_cd_oriunde_in_workspace(bridge, store, tmp_path):
target = tmp_path / "proiect-nou"
target.mkdir()
label, ch = await run(bridge, f"!cd {target}")
assert label == "cmd:cd"
assert store.thread(str(ch.id))["cwd"] == str(target)
assert str(target) in ch.all_text
async def test_cd_refuza_ce_nu_e_director(bridge, store):
label, ch = await run(bridge, "!cd /nu/exista/nicaieri")
assert "nu e un director" in ch.all_text
assert store.thread(str(ch.id))["cwd"] != "/nu/exista/nicaieri"
async def test_cd_fara_argument(bridge):
_, ch = await run(bridge, "!cd")
assert "Foloseste" in ch.all_text
async def test_cd_se_aplica_procesului_urmator(bridge, tmp_path):
ch = FakeChannel()
await run(bridge, "salut", ch)
target = tmp_path / "alt-proiect"
target.mkdir()
await run(bridge, f"!cd {target}", ch)
assert bridge.runner.procs[str(ch.id)].cwd == str(target)
assert bridge.runner.procs[str(ch.id)].alive is False # se reia cu --resume
# -------------------------------------------------------------------- !model
async def test_model_implicit_e_sonnet(bridge, store):
_, ch = await run(bridge, "salut")
assert store.thread(str(ch.id))["model"] == "sonnet"
assert "sonnet" in ch.all_text
async def test_model_opus_per_fir(bridge, store):
ch = FakeChannel()
label, _ = await run(bridge, "!model opus", ch)
assert label == "cmd:model"
assert store.thread(str(ch.id))["model"] == "opus"
# firul vecin (alt thread, acelasi canal-parinte) ramane pe sonnet
ch2 = FakeChannel("201", parent_id=CHANNEL)
await run(bridge, "!status", ch2)
assert "model : sonnet" in ch2.all_text
async def test_modelul_ales_ajunge_in_subsol(bridge):
ch = FakeChannel()
await run(bridge, "!model opus", ch)
await run(bridge, "salut", ch)
assert "opus" in ch.sent[-1].content
async def test_model_necunoscut_refuzat(bridge, store):
ch = FakeChannel()
await run(bridge, "!model gpt", ch)
assert "necunoscut" in ch.all_text
assert store.thread(str(ch.id))["model"] == "sonnet"
async def test_model_fara_argument_arata_curentul(bridge):
_, ch = await run(bridge, "!model")
assert "Model curent" in ch.all_text
# ------------------------------------------------------------------- !status
async def test_status_arata_campurile_cerute(bridge):
ch = FakeChannel()
await run(bridge, "salut", ch)
_, _ = await run(bridge, "!status", ch)
text = ch.sent[-1].content
for camp in ("fir", "sesiune", "director", "model", "proces", "tur in zbor",
"in coada", "cost fir", "cost azi"):
assert camp in text
assert "sid-fake-0001" in text
assert "$0.0123" in text
async def test_status_arata_stderr(bridge, monkeypatch):
ch = FakeChannel()
monkeypatch.setenv("FAKE_CLAUDE_SCENARIO", "crash")
await run(bridge, "salut", ch) # esueaza si lasa stderr in buffer
proc = bridge.runner.procs[str(ch.id)]
proc.stderr_buf.append("fake_claude: boom")
_, _ = await run(bridge, "!status", ch)
assert "boom" in ch.sent[-1].content
async def test_status_pe_fir_fara_proces(bridge):
_, ch = await run(bridge, "!status")
assert "proces : oprit" in ch.all_text
assert "(noua)" in ch.all_text
# --------------------------------------------------------------------- !stop
async def test_stop_opreste_procesul(bridge, store):
ch = FakeChannel()
await run(bridge, "salut", ch)
proc = bridge.runner.procs[str(ch.id)]
assert proc.alive is True # procesul ramane viu intre tururi
await run(bridge, "!stop", ch)
assert "oprit" in ch.all_text
assert proc.alive is False
async def test_stop_fara_proces(bridge):
_, ch = await run(bridge, "!stop")
assert "Nu ruleaza nimic" in ch.all_text
async def test_stop_curata_inflight(bridge, store, monkeypatch):
ch = FakeChannel()
store.set_inflight(str(ch.id), "t1", USER, "m1")
await run(bridge, "!stop", ch)
assert store.is_inflight(str(ch.id)) is False
# ------------------------------------------------------------------ !cleanup
async def test_cleanup_deleaga_in_lane_c(bridge, monkeypatch):
calls = {}
class FakeCleanup:
@staticmethod
def find_orphans(state):
calls["state"] = state
return [{"pid": 1234, "cmdline": "claude -p", "age_s": 10, "rss_mb": 406.0}]
@staticmethod
def kill_orphans(orphans, dry_run=True):
calls["dry_run"] = dry_run
return [{"pid": 1234, "action": "dry-run", "detail": "s-ar trimite SIGTERM"}]
@staticmethod
def format_report(orphans, results=None):
calls["results"] = results
return f"{len(orphans)} orfani"
monkeypatch.setattr(bot, "cleanup", FakeCleanup)
_, ch = await run(bridge, "!cleanup")
assert "1 orfani" in ch.all_text
assert calls["dry_run"] is True and calls["results"] is None
assert "threads" in calls["state"]
_, ch2 = await run(bridge, "!cleanup --force")
assert calls["dry_run"] is False and calls["results"] is not None
async def test_cleanup_care_crapa_nu_doboara_botul(bridge, monkeypatch):
class Boom:
@staticmethod
def find_orphans(state):
raise RuntimeError("proc ilizibil")
monkeypatch.setattr(bot, "cleanup", Boom)
_, ch = await run(bridge, "!cleanup")
assert "a esuat" in ch.all_text
async def test_cleanup_real_ruleaza_in_dry_run(bridge):
"""Modulul real al lui Lane C: nu omoara nimic implicit."""
cleanup = pytest.importorskip("cleanup")
_, ch = await run(bridge, "!cleanup")
assert ch.all_text.strip() != ""