Files
ROMFASTSQL/proxmox/lxc171-claude-agent/discord-bridge/tests/test_limits.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

119 lines
3.3 KiB
Python

"""T8: 4 procese, coada per fir, rate limit, plafon de cost."""
from __future__ import annotations
import asyncio
import pytest
import limits as L
def mkl(store=None, **kw):
kw.setdefault("alerter", lambda *a, **k: None)
return L.Limits(store, **kw)
async def test_maxim_4_procese_al_cincilea_asteapta():
lim = mkl(max_procs=4)
held = []
ev = asyncio.Event()
async def hold(i):
async with lim.process_slot():
held.append(i)
await ev.wait()
tasks = [asyncio.create_task(hold(i)) for i in range(5)]
await asyncio.sleep(0.05)
assert len(held) == 4 and lim.free_slots == 0
ev.set()
await asyncio.gather(*tasks)
assert len(held) == 5 and lim.free_slots == 4
async def test_coada_per_fir_serializeaza_acelasi_fir():
lim = mkl()
ordine = []
async def tur(tag, delay):
async with lim.thread_lock("1"):
ordine.append(f"start-{tag}")
await asyncio.sleep(delay)
ordine.append(f"stop-{tag}")
await asyncio.gather(tur("a", 0.02), tur("b", 0.0))
assert ordine == ["start-a", "stop-a", "start-b", "stop-b"]
async def test_fire_diferite_merg_in_paralel():
lim = mkl()
async with lim.thread_lock("1"):
assert lim.queued("1") and not lim.queued("2")
async with lim.thread_lock("2"):
pass
def test_rate_limit_per_user():
t = [1000.0]
lim = mkl(rate_per_min=3, clock=lambda: t[0])
for _ in range(3):
lim.admit("u1")
with pytest.raises(L.RateLimited) as exc:
lim.admit("u1")
assert exc.value.retry_after == pytest.approx(60.0)
lim.admit("u2") # alt utilizator nu e afectat
t[0] += 61.0
lim.admit("u1") # fereastra a trecut
def test_plafon_de_cost_opreste_botul(store):
lim = mkl(store, cost_cap=1.0)
assert not lim.stopped() and lim.cost_remaining() == pytest.approx(1.0)
lim.record_cost(0.4, "1")
lim.admit("u1")
assert lim.record_cost(0.7, "1") == pytest.approx(1.1)
assert lim.stopped() and lim.cost_remaining() == 0.0
with pytest.raises(L.CostCapReached):
lim.admit("u1")
def test_plafon_alerteaza_o_singura_data(store):
alerte = []
lim = mkl(store, cost_cap=0.5, alerter=lambda *a, **k: alerte.append(a))
lim.record_cost(0.6, "1")
lim.record_cost(0.1, "1")
assert len(alerte) == 1 and alerte[0][0] == "CRITICAL"
def test_plafonul_se_ridica_a_doua_zi(store):
lim = mkl(store, cost_cap=1.0)
lim.record_cost(2.0, "1")
assert lim.stopped()
store.state["cost"]["day"] = "2000-01-01" # ziua se schimba
assert not lim.stopped()
def test_costul_fara_store_e_local():
lim = mkl(None, cost_cap=1.0)
lim.record_cost(0.5)
lim.record_cost("gunoi")
assert lim.cost_today() == pytest.approx(0.5)
def test_valorile_implicite_vin_din_config(state_dir):
import config
(state_dir / "env").write_text("COST_CAP_USD_DAY=2.5\nMAX_PROCS=2 # comentariu\n")
config.reload(state_dir)
lim = mkl()
assert lim.cost_cap == 2.5 and lim.max_procs == 2
assert lim.turn_timeout == 900.0
async def test_contextul_turn_verifica_tot(store):
lim = mkl(store, max_procs=1, cost_cap=10.0, turn_timeout=42.0)
async with lim.turn("1", "u1") as timeout:
assert timeout == 42.0
assert lim.free_slots == 0
assert lim.free_slots == 1