-
Notifications
You must be signed in to change notification settings - Fork 126
Expand file tree
/
Copy pathtesting_plugins.py
More file actions
134 lines (99 loc) · 4.37 KB
/
Copy pathtesting_plugins.py
File metadata and controls
134 lines (99 loc) · 4.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# pytest: unit
# Testing plugins — how to unit-test hook functions without a live session.
#
# This example shows how to:
# 1. Construct a payload manually
# 2. Call a hook function directly (await my_hook(payload, ctx))
# 3. Assert the return value for both pass-through and blocking cases
#
# Run:
# uv run python docs/examples/plugins/testing_plugins.py
import asyncio
import logging
from datetime import UTC, datetime, timezone
from unittest.mock import MagicMock
from mellea.plugins import HookType, PluginMode, block, hook
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%dT%H:%M:%S",
)
log = logging.getLogger("testing_plugins")
# ---------------------------------------------------------------------------
# The plugin under test
# ---------------------------------------------------------------------------
MAX_DESCRIPTION_LENGTH = 500
@hook(HookType.COMPONENT_PRE_EXECUTE, mode=PluginMode.SEQUENTIAL, priority=10)
async def enforce_description_length(payload, ctx):
"""Block components whose action text exceeds the maximum length."""
action_text = str(payload.action._description) if payload.action else ""
if len(action_text) > MAX_DESCRIPTION_LENGTH:
return block(
f"Action text is {len(action_text)} chars, max is {MAX_DESCRIPTION_LENGTH}",
code="DESC_TOO_LONG",
details={"length": len(action_text), "max": MAX_DESCRIPTION_LENGTH},
)
# ---------------------------------------------------------------------------
# Test helpers
# ---------------------------------------------------------------------------
def make_payload(action_text: str):
"""Construct a minimal ComponentPreExecutePayload for testing.
In a real test suite you would import the payload class directly:
from mellea.plugins.hooks.component import ComponentPreExecutePayload
Here we use a simple mock to keep the example dependency-free.
"""
payload = MagicMock()
payload.action = MagicMock()
payload.action._description = action_text
payload.component_type = "Instruction"
payload.timestamp = datetime.now(UTC)
payload.hook = HookType.COMPONENT_PRE_EXECUTE.value
return payload
def make_ctx():
"""Construct a minimal PluginContext mock."""
return MagicMock()
# ---------------------------------------------------------------------------
# Test cases
# ---------------------------------------------------------------------------
async def test_pass_through():
"""Short action text should pass through (return None)."""
payload = make_payload("What is 2 + 2?")
ctx = make_ctx()
result = await enforce_description_length(payload, ctx)
assert result is None, f"Expected None (pass-through), got {result}"
log.info("[PASS] test_pass_through: short text passes through")
async def test_blocks_long_description():
"""Long action text should be blocked."""
long_text = "x" * (MAX_DESCRIPTION_LENGTH + 100)
payload = make_payload(long_text)
ctx = make_ctx()
result = await enforce_description_length(payload, ctx)
assert result is not None, "Expected a blocking PluginResult, got None"
assert result.continue_processing is False, "Expected continue_processing=False"
assert result.violation is not None, "Expected a violation"
assert result.violation.code == "DESC_TOO_LONG"
log.info(
"[PASS] test_blocks_long_description: long text is blocked with code=%s",
result.violation.code,
)
async def test_exact_boundary():
"""Text at exactly the max length should pass through."""
boundary_text = "y" * MAX_DESCRIPTION_LENGTH
payload = make_payload(boundary_text)
ctx = make_ctx()
result = await enforce_description_length(payload, ctx)
assert result is None, f"Expected None at boundary, got {result}"
log.info("[PASS] test_exact_boundary: boundary-length text passes through")
# ---------------------------------------------------------------------------
# Run all tests
# ---------------------------------------------------------------------------
async def run_all():
await test_pass_through()
await test_blocks_long_description()
await test_exact_boundary()
log.info("")
log.info("All tests passed!")
if __name__ == "__main__":
log.info("--- Testing plugins example ---")
log.info("")
asyncio.run(run_all())