from datetime import UTC, datetime, timedelta from types import SimpleNamespace from typing import Any, cast from test_conversations import StubFrontend, World, world from beaver_gateway.jobs.job import Job, JobRun from beaver_gateway.storage.models import Conversation __all__ = ["world"] async def _noop(_: JobRun) -> None: return None def _run(world: World) -> JobRun: return JobRun( job=Job("test", _noop), trigger="test", payload={}, scheduler=cast("Any", SimpleNamespace(conversations=world.conversations)), ) async def _quiet_since(world: World, conv: Conversation, ago: timedelta) -> None: async def apply(row: Conversation) -> None: row.last_activity_at = datetime.now(UTC) - ago await world.conversations._update(conv, apply) # noqa: SLF001 async def test_close_quiet_closes_and_marks_only_idle_branches(world: World) -> None: marked: list[tuple[str, str]] = [] class MarkingFrontend(StubFrontend): async def mark_closed(self, conv: Conversation) -> bool: marked.append((conv.external_id, conv.status)) return True tg = MarkingFrontend("tg", ("master", "branch")) tg.conversations = world.conversations world.conversations._frontends = [tg] # noqa: SLF001 convs = world.conversations master = await convs.create(kind="master", agent="a", session_id="m") idle = await convs.create(kind="branch", agent="a", parent=master, session_id="1") fresh = await convs.create(kind="branch", agent="a", parent=master, session_id="2") waiting = await convs.create( kind="branch", agent="a", parent=master, session_id="3" ) await _quiet_since(world, idle, timedelta(days=4)) await _quiet_since(world, fresh, timedelta(days=1)) await _quiet_since(world, waiting, timedelta(days=4)) await convs.queue.push( conversation_id=cast("int", waiting.id), priority="normal", origin="cron", text="later", ) closed = await _run(world).close_quiet(kind="branch", days=3) assert [c.external_id for c in closed] == [idle.external_id] assert marked == [(idle.external_id, "closed")] assert (await convs.get(fresh.external_id)).status == "open" assert (await convs.get(waiting.external_id)).status == "open" async def test_inject_into_falls_back_to_the_master(world: World) -> None: convs = world.conversations master = await convs.create(kind="master", agent="a", session_id="m") branch = await convs.create(kind="branch", agent="a", parent=master) gone = await convs.create(kind="branch", agent="a", parent=master) await convs.set_status(gone, "closed") run = _run(world) assert await run.inject_into(branch.external_id, "to branch", urgency="normal") assert await run.inject_into(gone.external_id, "to closed", urgency="normal") assert await run.inject_into(None, "to nobody", urgency="normal") assert [i.text for i in await convs.queue.pending(branch.id)] == ["to branch"] assert [i.text for i in await convs.queue.pending(master.id)] == [ "to closed", "to nobody", ]