import pytest
from cpex import PluginManager, PipelineResult
def test_cpex_lib_importable():
import cpex
import cpex._lib
assert hasattr(cpex._lib, "PluginManager"), "cpex._lib must expose PluginManager"
assert hasattr(cpex._lib, "PipelineResult"), "cpex._lib must expose PipelineResult"
@pytest.mark.asyncio
async def test_lifecycle_construct_initialize_shutdown(pii_deny_config_path: str):
mgr = PluginManager(pii_deny_config_path)
await mgr.initialize()
await mgr.shutdown()
@pytest.mark.asyncio
async def test_invoke_returns_pipeline_result(manager: PluginManager):
payload = {
"message": {
"role": "user",
"content": [{"content_type": "text", "text": "Hello, world!"}],
}
}
result = await manager.invoke_hook("cmf.tool_pre_invoke", payload)
assert isinstance(result, PipelineResult)
assert result.continue_processing is True
@pytest.mark.asyncio
async def test_pii_deny_returns_violation(pii_deny_config_path: str):
mgr = PluginManager(pii_deny_config_path)
await mgr.initialize()
payload = {
"message": {
"role": "assistant",
"content": [
{
"content_type": "tool_call",
"content": {
"tool_call_id": "tc_001",
"name": "lookup_person",
"arguments": {"ssn": "123-45-6789"},
},
}
],
}
}
result = await mgr.invoke_hook("cmf.tool_pre_invoke", payload)
assert result.continue_processing is False, "pii-scan deny should halt pipeline"
assert result.violation is not None, "violation dict must be populated"
assert "reason" in result.violation, "violation must have a reason field"
await mgr.shutdown()
@pytest.mark.asyncio
async def test_generic_hook_does_not_raise(manager: PluginManager):
result = await manager.invoke_hook("custom.arbitrary.hook", {"data": "value"})
assert isinstance(result, PipelineResult)
assert result.continue_processing is True
@pytest.mark.asyncio
async def test_pii_redact_returns_modified_payload(pii_redact_config_path: str):
mgr = PluginManager(pii_redact_config_path)
await mgr.initialize()
payload = {
"message": {
"role": "assistant",
"content": [
{
"content_type": "tool_call",
"content": {
"tool_call_id": "tc_001",
"name": "lookup_person",
"arguments": {
"ssn": "123-45-6789",
"name": "Alice",
},
},
}
],
}
}
result = await mgr.invoke_hook("cmf.tool_pre_invoke", payload)
assert result.continue_processing is True, "redact mode should allow processing"
assert result.modified_payload is not None, "pii-scan redact must set modified_payload"
args = result.modified_payload["message"]["content"][0]["content"]["arguments"]
assert args["ssn"] == "[PII]", f"SSN should be redacted, got: {args['ssn']!r}"
assert args["name"] == "Alice", "unrelated field must be preserved"
await mgr.shutdown()