mirror of
https://github.com/razzant/ouroboros.git
synced 2026-08-04 16:19:50 +00:00
This RC makes review evidence first-class across repo and skill review: skill findings are no longer silently truncated, review.json is readable cognitive evidence, and skill review status is computed live from findings plus the current enforcement mode. It also fixes the SKILL_NOT_FINALIZED transcript bug, adds LLM-first recent task context recovery, hardens owner-state protection for skill review history, removes MagicMock JSONL pollution, and updates the release/docs/UI surfaces for the new review flow.
81 lines
3.2 KiB
Python
81 lines
3.2 KiB
Python
"""Shared helpers for the Ouroboros test suite.
|
|
|
|
These functions are reused across multiple ``tests/test_*.py`` modules to
|
|
avoid duplicated boilerplate (extension-loader cleanup, claude_agent_sdk
|
|
mock installation). They are intentionally plain module-level callables,
|
|
not fixtures — many callers need them at module import time.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import types
|
|
from unittest.mock import MagicMock
|
|
|
|
|
|
def clean_extension_runtime_state() -> None:
|
|
"""Reset every extension_loader namespace to a pristine state.
|
|
|
|
Superset of cleanup logic that previously lived (with minor variations)
|
|
in ``test_skill_exec.py``, ``test_extensions_api.py`` and
|
|
``test_extension_loader.py``. Extra clears are inert when the namespace
|
|
is already empty, so the superset is safe for every caller.
|
|
"""
|
|
from ouroboros import extension_loader
|
|
|
|
with extension_loader._lock:
|
|
loaded_names = list(extension_loader._extensions.keys())
|
|
for name in loaded_names:
|
|
extension_loader.unload_extension(name)
|
|
with extension_loader._lock:
|
|
extension_loader._extension_modules.clear()
|
|
extension_loader._load_failures.clear()
|
|
extension_loader._unloading.clear()
|
|
extension_loader._lifecycle_locks.clear()
|
|
extension_loader._tools.clear()
|
|
extension_loader._routes.clear()
|
|
extension_loader._ws_handlers.clear()
|
|
extension_loader._ui_tabs.clear()
|
|
extension_loader._settings_sections.clear()
|
|
extension_loader.set_ws_broadcaster(None)
|
|
|
|
|
|
def ensure_claude_agent_sdk_mock() -> None:
|
|
"""Install a lightweight ``claude_agent_sdk`` mock when truly absent.
|
|
|
|
Uses ``importlib.util.find_spec`` so an installed-but-not-yet-imported
|
|
SDK is never masked. Idempotent — safe to call from multiple modules at
|
|
import time.
|
|
"""
|
|
import importlib.util as _ilu
|
|
try:
|
|
spec = _ilu.find_spec("claude_agent_sdk")
|
|
sdk_available = spec is not None
|
|
except (ValueError, ModuleNotFoundError):
|
|
sdk_available = "claude_agent_sdk" in sys.modules
|
|
if sdk_available:
|
|
return
|
|
mock_sdk = types.ModuleType("claude_agent_sdk")
|
|
mock_sdk.ClaudeAgentOptions = type("ClaudeAgentOptions", (), {})
|
|
mock_sdk.ClaudeSDKClient = type("ClaudeSDKClient", (), {})
|
|
mock_sdk.HookMatcher = type("HookMatcher", (), {"__init__": lambda self, **kw: None})
|
|
mock_sdk.AssistantMessage = type("AssistantMessage", (), {})
|
|
mock_sdk.ResultMessage = type("ResultMessage", (), {})
|
|
mock_sdk.query = lambda **kw: None
|
|
sys.modules["claude_agent_sdk"] = mock_sdk
|
|
|
|
|
|
def make_safe_mock_ctx(tmp_path, *, repo_dir=None):
|
|
"""Return a MagicMock ToolContext whose drive paths resolve to real dirs.
|
|
|
|
Several observability paths append to ``ctx.drive_logs() / "events.jsonl"``.
|
|
A bare MagicMock would stringify into a filename in the repo root.
|
|
"""
|
|
ctx = MagicMock()
|
|
ctx.repo_dir = repo_dir if repo_dir is not None else tmp_path
|
|
ctx.drive_root = tmp_path
|
|
logs = tmp_path / "logs"
|
|
logs.mkdir(parents=True, exist_ok=True)
|
|
ctx.drive_logs.return_value = logs
|
|
ctx.emit_progress_fn = lambda *a, **kw: None
|
|
ctx.task_id = "test-task"
|
|
return ctx
|