144 lines
4.6 KiB
Python
144 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from opensquilla.memory.dream_factory import _session_lock_for
|
|
from opensquilla.scheduler.dream_handler import make_memory_dream_handler
|
|
from opensquilla.scheduler.types import CronJob
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_memory_dream_handler_skips_without_building_dream() -> None:
|
|
def build_dream(agent_id: str) -> object:
|
|
raise AssertionError(f"dream should not be built for {agent_id}")
|
|
|
|
handler = make_memory_dream_handler(build_dream, should_skip=lambda: "disabled")
|
|
|
|
result = await handler(
|
|
CronJob(
|
|
id="dream-main",
|
|
name="memory_dream:main",
|
|
payload={"agent_id": "main"},
|
|
)
|
|
)
|
|
|
|
assert result.summary == "dream skipped: disabled"
|
|
assert result.delivery_status == "skipped"
|
|
|
|
|
|
def test_dream_factory_uses_public_turn_runner_lock_surface() -> None:
|
|
class _Runner:
|
|
def __init__(self) -> None:
|
|
self.keys: list[str] = []
|
|
self.lock = object()
|
|
|
|
def get_session_lock(self, key: str) -> object:
|
|
self.keys.append(key)
|
|
return self.lock
|
|
|
|
def _get_session_lock(self, _key: str) -> object:
|
|
raise AssertionError("private lock surface should not be used")
|
|
|
|
runner = _Runner()
|
|
|
|
assert _session_lock_for(runner, "main") is runner.lock
|
|
assert runner.keys == ["memory_dream:main"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_post_dream_hook_invoked_on_successful_run() -> None:
|
|
"""When build_dream succeeds the hook fires with the same agent_id."""
|
|
|
|
class _StubDream:
|
|
def __init__(self, agent_id: str) -> None:
|
|
self.agent_id = agent_id
|
|
|
|
async def run(self) -> object:
|
|
class _R:
|
|
files_processed = 0
|
|
evidence_status = "ok"
|
|
apply_status = "ok"
|
|
return _R()
|
|
|
|
captured: list[tuple[str, str]] = []
|
|
|
|
async def hook(agent_id: str, dream_summary: str) -> None:
|
|
captured.append((agent_id, dream_summary))
|
|
|
|
handler = make_memory_dream_handler(
|
|
build_dream=lambda aid: _StubDream(aid),
|
|
post_dream_hook=hook,
|
|
)
|
|
result = await handler(CronJob(id="dream-x", payload={"agent_id": "agent-x"}))
|
|
assert result.summary.startswith("dream agent=agent-x")
|
|
assert captured == [("agent-x", result.summary)]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_post_dream_hook_exception_does_not_poison_handler_result() -> None:
|
|
"""Hook failure logs an exception but the dream's HandlerResult
|
|
must still reflect the successful dream — observability cannot
|
|
convert a working dream into a failed one."""
|
|
|
|
class _StubDream:
|
|
async def run(self) -> object:
|
|
class _R:
|
|
files_processed = 1
|
|
evidence_status = "ok"
|
|
apply_status = "ok"
|
|
return _R()
|
|
|
|
async def hook(_agent_id: str, _dream_summary: str) -> None:
|
|
raise RuntimeError("auto_propose blew up")
|
|
|
|
handler = make_memory_dream_handler(
|
|
build_dream=lambda _aid: _StubDream(),
|
|
post_dream_hook=hook,
|
|
)
|
|
result = await handler(CronJob(id="dream-y", payload={"agent_id": "main"}))
|
|
assert result.summary.startswith("dream agent=main")
|
|
assert "blew up" not in result.summary
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_post_dream_hook_not_called_when_dream_skipped() -> None:
|
|
"""Predicate-skip path short-circuits before the hook would fire."""
|
|
|
|
fired = False
|
|
|
|
async def hook(_agent_id: str, _dream_summary: str) -> None:
|
|
nonlocal fired
|
|
fired = True
|
|
|
|
def build(_aid: str) -> object:
|
|
raise AssertionError("dream should not be built when skipped")
|
|
|
|
handler = make_memory_dream_handler(
|
|
build_dream=build,
|
|
should_skip=lambda: "disabled",
|
|
post_dream_hook=hook,
|
|
)
|
|
result = await handler(CronJob(id="dream-z", payload={"agent_id": "main"}))
|
|
assert result.delivery_status == "skipped"
|
|
assert fired is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_memory_dream_handler_kill_switch_skips_before_guard(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setenv("OPENSQUILLA_MEMORY_DREAM_DISABLED", "1")
|
|
|
|
def build_dream(agent_id: str) -> object:
|
|
raise AssertionError(f"dream should not be built for {agent_id}")
|
|
|
|
def should_skip() -> str | None:
|
|
raise AssertionError("kill switch should short-circuit the guard")
|
|
|
|
handler = make_memory_dream_handler(build_dream, should_skip=should_skip)
|
|
|
|
result = await handler(CronJob(id="dream-main", payload={"agent_id": "main"}))
|
|
|
|
assert result.summary == "dream skipped: kill_switch"
|
|
assert result.delivery_status == "skipped"
|