Files

170 lines
6.7 KiB
Python

"""Gate H — prove that a plugin contributes ALL extension points without
changing a single core file.
Covers:
1. Agent tool -> app.ai.tool_registry (core layer)
2. Workflow step type -> app.workflows.step_handlers
3. Fallback intent -> app.ai.action_mapper
4. Deactivate symmetry -> everything removed again
5. Re-activate -> present again, NO duplicates
The plugin under test is defined INLINE in this file — if this test passes,
no core file had to be touched to extend the platform.
"""
from __future__ import annotations
from typing import Any
import pytest
# ─── The Gate-H test plugin (inline — zero core changes) ─────────────────────
def _build_gate_h_plugin():
from app.plugins.base import BasePlugin
from app.plugins.manifest import PluginManifest
class GateHTestPlugin(BasePlugin):
"""Minimal plugin exercising every Block-H contribution point."""
manifest = PluginManifest(
name="gate_h_test",
version="1.0.0",
display_name="Gate H Test",
description="Proves plugin contributions without core changes.",
dependencies=[],
routes=[],
events=[],
migrations=[],
permissions=["gate_h_test:read"],
)
async def on_activate(self, db, service_container, event_bus) -> None:
await super().on_activate(db, service_container, event_bus)
# 1) Agent tool via CORE registry
from app.ai.tool_registry import get_tool_registry
async def _echo_handler(arguments: dict[str, Any], context: dict[str, Any]) -> str:
return f"gate-h says: {arguments.get('text', '')}"
get_tool_registry().register(
name="gate_h_echo",
description="Echo text back (Gate H proof).",
parameters={
"type": "object",
"properties": {"text": {"type": "string"}},
"required": ["text"],
},
handler=_echo_handler,
plugin_name=self.manifest.name,
required_permission=None,
category="test",
)
# 2) Workflow step type
from app.workflows.step_handlers import StepResult, register_step_type
@register_step_type("gate_h_noop")
async def _handle_gate_h_noop(db, tenant_id, instance, step): # noqa: ANN001
return StepResult(output={"gate_h": True})
# 3) Fallback intent pattern
from app.ai.action_mapper import register_intent_pattern
def _invoice_intent(query: str, context: dict[str, Any] | None):
return [
{
"method": "POST",
"path": "/api/v1/gate-h/invoices",
"body": {},
"description": "Create invoice (Gate H proof)",
"confidence": 0.9,
}
]
_invoice_intent.owner_tag = self.manifest.name
register_intent_pattern(r"\b(create|new)\b.*\binvoice\b", _invoice_intent, owner=self.manifest.name)
async def on_deactivate(self, db, service_container, event_bus) -> None:
# Symmetry: remove everything registered above
from app.ai.tool_registry import get_tool_registry
from app.workflows.step_handlers import unregister_step_type
from app.ai.action_mapper import unregister_intent_patterns
get_tool_registry().unregister_plugin(self.manifest.name)
unregister_step_type("gate_h_noop")
unregister_intent_patterns(self.manifest.name)
await super().on_deactivate(db, service_container, event_bus)
return GateHTestPlugin
# ─── The proof ────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_gate_h_full_contribution_lifecycle():
"""Activate -> contributed everywhere; deactivate -> gone; reactivate -> back, no dupes."""
from app.ai.tool_registry import get_tool_registry
from app.workflows.step_handlers import get_step_handler
from app.ai.action_mapper import map_query_to_actions
plugin_cls = _build_gate_h_plugin()
plugin = plugin_cls()
# ── Before activation: nothing registered ──
assert get_tool_registry().get("gate_h_echo") is None
assert get_step_handler("gate_h_noop") is None
assert map_query_to_actions("create invoice for Acme") == []
# ── Activate ──
await plugin.on_activate(db=None, service_container=None, event_bus=None)
# 1) Agent tool present in CORE registry
tool = get_tool_registry().get("gate_h_echo")
assert tool is not None
assert tool.plugin_name == "gate_h_test"
result = await tool.handler({"text": "hello"}, {})
assert result == "gate-h says: hello"
# 2) Workflow step type resolvable
handler = get_step_handler("gate_h_noop")
assert handler is not None
# 3) Intent contributes actions for a FACHMODUL term unknown to core
actions = map_query_to_actions("create invoice for Acme")
assert len(actions) == 1
assert actions[0]["path"] == "/api/v1/gate-h/invoices"
# ── Deactivate: full symmetry ──
await plugin.on_deactivate(db=None, service_container=None, event_bus=None)
assert get_tool_registry().get("gate_h_echo") is None
assert get_step_handler("gate_h_noop") is None
assert map_query_to_actions("create invoice for Acme") == []
# ── Re-activate: back AND no duplicates ──
await plugin.on_activate(db=None, service_container=None, event_bus=None)
tool = get_tool_registry().get("gate_h_echo")
assert tool is not None # single entry (dict keyed by name — overwrite, not dupe)
assert get_step_handler("gate_h_noop") is not None
actions = map_query_to_actions("create invoice for Acme")
assert len(actions) == 1 # exactly one contributed intent, not two
# Cleanup so other tests are unaffected
await plugin.on_deactivate(db=None, service_container=None, event_bus=None)
@pytest.mark.asyncio
async def test_gate_h_agent_runtime_survives_ai_assistant_absence():
"""The core tool registry works even though ai_assistant plugin module is never imported."""
from app.ai.tool_registry import ToolRegistry, get_tool_registry
reg = get_tool_registry()
assert isinstance(reg, ToolRegistry)
# And it must be THE same singleton the shim exposes
from app.plugins.builtins.ai_assistant.tool_registry import get_tool_registry as shim_get
assert shim_get() is reg