115 lines
3.6 KiB
Python
115 lines
3.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Example: Console Channel Contract Test
|
|
|
|
Demonstrates how to implement contract tests for channel subclasses.
|
|
When BaseChannel changes, these tests ensure ConsoleChannel still complies.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
|
|
from pineagents.app.channels.renderer import ChannelDisplayConfig
|
|
|
|
from tests.contract.channels import ChannelContractTest
|
|
|
|
if TYPE_CHECKING:
|
|
from pineagents.app.channels.base import BaseChannel
|
|
|
|
|
|
def create_mock_process_handler():
|
|
"""Create a mock process handler for channel testing."""
|
|
mock = AsyncMock()
|
|
|
|
async def mock_process(*_args, **_kwargs):
|
|
from unittest.mock import MagicMock
|
|
|
|
mock_event = MagicMock()
|
|
mock_event.object = "message"
|
|
mock_event.status = "completed"
|
|
yield mock_event
|
|
|
|
mock.side_effect = mock_process
|
|
return mock
|
|
|
|
|
|
class TestConsoleChannelContract(ChannelContractTest):
|
|
"""
|
|
ConsoleChannel must satisfy ALL contracts defined in ChannelContractTest.
|
|
|
|
If BaseChannel adds a new requirement (e.g., a new abstract method),
|
|
this test automatically picks it up and fails until ConsoleChannel
|
|
implements it.
|
|
"""
|
|
|
|
def create_instance(self) -> "BaseChannel":
|
|
"""Provide a ConsoleChannel instance for contract testing."""
|
|
from pineagents.app.channels.console.channel import ConsoleChannel
|
|
|
|
process = create_mock_process_handler()
|
|
return ConsoleChannel(
|
|
process=process,
|
|
enabled=True,
|
|
bot_prefix="[TEST] ",
|
|
display_config=ChannelDisplayConfig(
|
|
show_tool_calls=False,
|
|
show_tool_results=False,
|
|
),
|
|
)
|
|
|
|
# Subclass-specific tests can be added here
|
|
def test_console_specific_behavior(self, instance):
|
|
"""Console-specific: uses stdout for output."""
|
|
# Console channel outputs to stdout/stderr
|
|
assert hasattr(instance, "bot_prefix")
|
|
|
|
|
|
# =============================================================================
|
|
# Example: Contract Coverage Check
|
|
# =============================================================================
|
|
|
|
|
|
@pytest.mark.skip(reason="Meta-test - run manually to check coverage")
|
|
def test_all_channel_subclasses_have_contract_tests():
|
|
"""
|
|
Meta-test: Verify all BaseChannel subclasses have contract tests.
|
|
|
|
This prevents adding a new channel without corresponding contract coverage.
|
|
Run periodically in CI to ensure completeness.
|
|
"""
|
|
import inspect
|
|
from pineagents.app.channels.base import BaseChannel
|
|
|
|
# Get all concrete ChannelContractTest implementations
|
|
tested_classes = set()
|
|
for test_class in ChannelContractTest.get_concrete_tests():
|
|
try:
|
|
instance = test_class().create_instance()
|
|
tested_classes.add(instance.__class__.__name__)
|
|
except Exception as e:
|
|
pytest.skip(f"Cannot instantiate {test_class.__name__}: {e}")
|
|
|
|
# Get all BaseChannel subclasses
|
|
def get_channel_subclasses(cls):
|
|
result = []
|
|
for subclass in cls.__subclasses__():
|
|
if not inspect.isabstract(subclass):
|
|
result.append(subclass.__name__)
|
|
result.extend(get_channel_subclasses(subclass))
|
|
return result
|
|
|
|
all_channels = set(get_channel_subclasses(BaseChannel))
|
|
|
|
# Check coverage
|
|
untested = all_channels - tested_classes
|
|
|
|
if untested:
|
|
pytest.fail(
|
|
f"Channels missing contract tests: {untested}\n"
|
|
f"Create test class inheriting from ChannelContractTest for each.",
|
|
)
|