Files
2026-07-13 13:12:33 +08:00

160 lines
4.9 KiB
Python

"""Tests that _drain_task_runtime_for_reset is called on every reset branch.
Asserts that ``_drain_task_runtime_for_reset`` is invoked regardless of
whether ``flush_service`` is None or wired, and regardless of the
``force`` flag.
"""
from __future__ import annotations
from dataclasses import dataclass
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch
import pytest
import opensquilla.gateway.rpc_sessions # noqa: F401 — ensures handler registration
from opensquilla.gateway.auth import Principal
from opensquilla.gateway.config import GatewayConfig
from opensquilla.gateway.rpc import RpcContext, get_dispatcher
_ADMIN_PRINCIPAL = Principal(
role="operator",
scopes=frozenset({"operator.admin", "operator.write"}),
is_owner=True,
authenticated=True,
)
_SESSION_KEY = "agent:main:drain-test"
_SESSION_ID = "drain-test"
@dataclass
class _FakeSession:
session_key: str = _SESSION_KEY
session_id: str = _SESSION_ID
agent_id: str = "main"
status: str = "idle"
created_at: int = 0
updated_at: int = 0
display_name: str | None = None
derived_title: str | None = None
channel: str | None = None
chat_type: str = "unknown"
group_id: str | None = None
subject: str | None = None
last_channel: str | None = None
last_to: str | None = None
last_account_id: str | None = None
last_thread_id: str | None = None
delivery_context: dict | None = None
parent_session_key: str | None = None
spawned_by: str | None = None
origin: dict | None = None
model: str | None = None
model_override: str | None = None
class _FakeStorage:
def __init__(self) -> None:
self._sessions: dict[str, _FakeSession] = {_SESSION_KEY: _FakeSession()}
self._transcripts: dict[str, list] = {}
async def get_session(self, key: str) -> _FakeSession | None:
return self._sessions.get(key)
async def delete_transcript(self, session_id: str) -> None:
self._transcripts.pop(session_id, None)
class _FakeSessionManager:
def __init__(self) -> None:
self._storage = _FakeStorage()
self.applied_intents: list[tuple[str, str]] = []
async def get_transcript(self, key: str) -> list:
return []
async def apply_intent(self, key: str, intent: object, **kwargs):
self.applied_intents.append((key, str(intent)))
session = await self._storage.get_session(key)
if session is None:
raise KeyError(key)
old_id = session.session_id
session.session_id = f"{old_id}-rotated"
return session, True
def _make_ctx(flush_service=None, task_runtime=None) -> RpcContext:
ctx = RpcContext(
conn_id="test-drain",
principal=_ADMIN_PRINCIPAL,
config=GatewayConfig(),
)
ctx.session_manager = _FakeSessionManager()
ctx.flush_service = flush_service
ctx.task_runtime = task_runtime
return ctx
def _make_task_runtime() -> SimpleNamespace:
"""Minimal task_runtime double with cancel() and no list/wait (non-listing path)."""
rt = SimpleNamespace()
rt.cancel = AsyncMock(return_value=0)
# No `list` or `wait` attributes → has_runtime_listing=False path
return rt
@pytest.mark.asyncio
async def test_drain_called_when_flush_service_none():
"""drain is called even when flush_service is None (kill-switch path)."""
task_runtime = _make_task_runtime()
ctx = _make_ctx(flush_service=None, task_runtime=task_runtime)
target = "opensquilla.gateway.rpc_sessions._drain_task_runtime_for_reset"
with patch(target, new_callable=AsyncMock) as mock_drain:
result = await get_dispatcher().dispatch(
"r1",
"sessions.reset",
{"key": _SESSION_KEY},
ctx,
)
assert result.error is None, result.error
mock_drain.assert_awaited_once_with(task_runtime, _SESSION_KEY)
@pytest.mark.asyncio
async def test_drain_called_with_flush_service():
"""drain is called when flush_service is wired (normal path)."""
from opensquilla.memory.session_flush import FlushReceipt
task_runtime = _make_task_runtime()
flush_receipt = FlushReceipt(
mode="skipped",
flushed_paths=[],
slug=None,
message_count=0,
duration_ms=0,
raw_reason=None,
error=None,
)
fake_flush_service = SimpleNamespace(
execute=AsyncMock(return_value=flush_receipt),
)
ctx = _make_ctx(flush_service=fake_flush_service, task_runtime=task_runtime)
target = "opensquilla.gateway.rpc_sessions._drain_task_runtime_for_reset"
with patch(target, new_callable=AsyncMock) as mock_drain:
result = await get_dispatcher().dispatch(
"r1",
"sessions.reset",
{"key": _SESSION_KEY},
ctx,
)
assert result.error is None, result.error
mock_drain.assert_awaited_once_with(task_runtime, _SESSION_KEY)