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
65 lines
1.6 KiB
Python
65 lines
1.6 KiB
Python
"""Fixturi comune: totul in tmp, zero retea, zero Discord, zero API."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import pathlib
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
import config # noqa: E402
|
|
|
|
FAKE_CLAUDE = str(pathlib.Path(__file__).resolve().parent / "fake_claude.py")
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def state_dir(tmp_path, monkeypatch):
|
|
"""Muta ~/.claude-discord in tmp pentru fiecare test."""
|
|
d = tmp_path / "claude-discord"
|
|
d.mkdir()
|
|
monkeypatch.setenv("CLAUDE_DISCORD_DIR", str(d))
|
|
config.reload(d)
|
|
config.ensure_dirs()
|
|
yield d
|
|
config.reload()
|
|
|
|
|
|
@pytest.fixture
|
|
def store(state_dir):
|
|
import session_store
|
|
|
|
s = session_store.SessionStore(state_dir / "state.json", alerter=lambda *a, **k: None)
|
|
s.load()
|
|
return s
|
|
|
|
|
|
@pytest.fixture
|
|
def fake_bin():
|
|
"""Comanda care inlocuieste `claude` in teste."""
|
|
return [sys.executable, FAKE_CLAUDE]
|
|
|
|
|
|
@pytest.fixture
|
|
def scenario(monkeypatch):
|
|
def _set(name: str, **env):
|
|
monkeypatch.setenv("FAKE_CLAUDE_SCENARIO", name)
|
|
for k, v in env.items():
|
|
monkeypatch.setenv(k, str(v))
|
|
return _set
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _no_real_claude(monkeypatch, request):
|
|
"""Nicio suita implicita nu are voie sa cheme `claude --version`."""
|
|
if "e2e" in request.keywords:
|
|
return
|
|
import stream
|
|
|
|
monkeypatch.setattr(stream, "_cli_version_cache", "test-cli", raising=False)
|
|
monkeypatch.setattr(stream, "cli_version", lambda: "test-cli")
|