"""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() != ""