Skip to content

Commit dd563e6

Browse files
author
Tom Softreck
committed
update
1 parent 63e84cb commit dd563e6

File tree

1 file changed

+102
-126
lines changed

1 file changed

+102
-126
lines changed

tests/unit/test_engine.py

Lines changed: 102 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,180 +1,156 @@
1-
"""Unit tests for the DialogEngine class."""
1+
"""Unit tests for the CamelRouterEngine class."""
22
import asyncio
3+
import pytest
34
from unittest.mock import AsyncMock, MagicMock, patch, call
45

5-
import pytest
6+
from dialogchain.engine import CamelRouterEngine
7+
from dialogchain.connectors import Source, Destination
8+
from dialogchain.config import RouteConfig, ConfigError
69

7-
from dialogchain import engine, config, exceptions
8-
from dialogchain.connectors import BaseConnector
910

11+
class MockSource(Source):
12+
"""Mock source for testing."""
13+
14+
def __init__(self):
15+
self.messages = []
16+
self.is_connected = False
17+
18+
async def connect(self):
19+
self.is_connected = True
20+
21+
async def disconnect(self):
22+
self.is_connected = False
23+
24+
async def receive(self):
25+
while self.messages:
26+
yield self.messages.pop(0)
1027

11-
class MockConnector(BaseConnector):
12-
"""Mock connector for testing."""
28+
29+
class MockDestination(Destination):
30+
"""Mock destination for testing."""
1331

14-
def __init__(self, config):
15-
super().__init__(config)
32+
def __init__(self):
1633
self.sent_messages = []
17-
self.received_messages = []
34+
self.is_connected = False
1835

1936
async def connect(self):
2037
self.is_connected = True
2138

2239
async def disconnect(self):
2340
self.is_connected = False
2441

25-
async def send(self, message, **kwargs):
26-
self.sent_messages.append((message, kwargs))
27-
return {"status": "ok"}
28-
29-
async def receive(self, **kwargs):
30-
if self.received_messages:
31-
return self.received_messages.pop(0)
32-
return None
42+
async def send(self, message):
43+
self.sent_messages.append(message)
3344

3445

35-
class TestDialogEngine:
36-
"""Test the DialogEngine class."""
46+
class TestCamelRouterEngine:
47+
"""Test the CamelRouterEngine class."""
3748

3849
@pytest.fixture
3950
def sample_config(self):
40-
"""Return a sample engine configuration."""
51+
"""Return a sample route configuration."""
4152
return {
42-
"version": "1.0",
43-
"name": "test_engine",
44-
"connectors": {
45-
"test_input": {
46-
"type": "test",
47-
"name": "test_input"
48-
},
49-
"test_output": {
50-
"type": "test",
51-
"name": "test_output"
53+
"routes": [
54+
{
55+
"name": "test_route",
56+
"from": "rtsp://camera1",
57+
"to": "http://api.example.com/webhook",
58+
"processors": [
59+
{
60+
"type": "filter",
61+
"config": {"min_confidence": 0.5}
62+
}
63+
]
5264
}
53-
},
54-
"processors": {
55-
"test_processor": {
56-
"type": "test",
57-
"input": "test_input",
58-
"output": "test_output"
59-
}
60-
},
61-
"workflows": {
62-
"default": ["test_processor"]
63-
}
65+
]
6466
}
6567

6668
@pytest.fixture
67-
def mock_connector_factory(self):
68-
"""Return a mock connector factory."""
69-
return MagicMock(side_effect=MockConnector)
69+
def mock_source(self):
70+
"""Create a mock source for testing."""
71+
return MockSource()
7072

7173
@pytest.fixture
72-
def mock_processor_factory(self):
73-
"""Return a mock processor factory."""
74-
mock_processor = AsyncMock()
75-
mock_processor.process.return_value = {"processed": True}
76-
return MagicMock(return_value=mock_processor)
74+
def mock_destination(self):
75+
"""Create a mock destination for testing."""
76+
return MockDestination()
7777

7878
@pytest.mark.asyncio
79-
async def test_engine_initialization(self, sample_config, mock_connector_factory, mock_processor_factory):
79+
async def test_engine_initialization(self, sample_config):
8080
"""Test engine initialization with valid config."""
81-
with patch('dialogchain.connectors.create_connector', mock_connector_factory), \
82-
patch('dialogchain.processors.create_processor', mock_processor_factory):
83-
84-
dialog_engine = engine.DialogEngine(sample_config)
85-
assert dialog_engine.name == "test_engine"
86-
assert len(dialog_engine.connectors) == 2
87-
assert len(dialog_engine.processors) == 1
88-
assert len(dialog_engine.workflows) == 1
81+
engine = CamelRouterEngine(sample_config)
82+
assert engine.config == sample_config
83+
assert len(engine.routes) == 1
84+
assert engine.routes[0].name == "test_route"
8985

9086
@pytest.mark.asyncio
91-
async def test_engine_start_stop(self, sample_config, mock_connector_factory, mock_processor_factory):
87+
async def test_engine_start_stop(self, sample_config, mock_source, mock_destination):
9288
"""Test starting and stopping the engine."""
93-
with patch('dialogchain.connectors.create_connector', mock_connector_factory), \
94-
patch('dialogchain.processors.create_processor', mock_processor_factory):
89+
with patch('dialogchain.connectors.create_source', return_value=mock_source), \
90+
patch('dialogchain.connectors.create_destination', return_value=mock_destination):
9591

96-
dialog_engine = engine.DialogEngine(sample_config)
97-
await dialog_engine.start()
92+
engine = CamelRouterEngine(sample_config)
93+
await engine.start()
9894

99-
# Verify connectors are connected
100-
for connector in dialog_engine.connectors.values():
101-
assert connector.is_connected is True
95+
# Verify source and destination are connected
96+
assert mock_source.is_connected is True
97+
assert mock_destination.is_connected is True
10298

103-
await dialog_engine.stop()
99+
await engine.stop()
104100

105-
# Verify connectors are disconnected
106-
for connector in dialog_engine.connectors.values():
107-
assert connector.is_connected is False
101+
# Verify source and destination are disconnected
102+
assert mock_source.is_connected is False
103+
assert mock_destination.is_connected is False
108104

109105
@pytest.mark.asyncio
110-
async def test_engine_process_message(self, sample_config, mock_connector_factory, mock_processor_factory):
106+
async def test_engine_process_message(self, sample_config, mock_source, mock_destination):
111107
"""Test processing a message through the engine."""
112-
with patch('dialogchain.connectors.create_connector', mock_connector_factory), \
113-
patch('dialogchain.processors.create_processor', mock_processor_factory):
114-
115-
dialog_engine = engine.DialogEngine(sample_config)
116-
await dialog_engine.start()
117-
118-
# Get the input connector and simulate receiving a message
119-
input_connector = dialog_engine.connectors["test_input"]
120-
input_connector.received_messages = [{"text": "Hello"}]
108+
test_message = {"frame": "test_frame", "confidence": 0.7}
109+
mock_source.messages = [test_message]
110+
111+
with patch('dialogchain.connectors.create_source', return_value=mock_source), \
112+
patch('dialogchain.connectors.create_destination', return_value=mock_destination):
121113

122-
# Process a single message
123-
await dialog_engine.process_next_message("test_input", workflow="default")
114+
engine = CamelRouterEngine(sample_config)
115+
await engine.start()
124116

125-
# Verify the processor was called with the message
126-
mock_processor = mock_processor_factory()
127-
mock_processor.process.assert_awaited_once_with({"text": "Hello"})
117+
# Process messages for a short duration
118+
process_task = asyncio.create_task(engine.run())
119+
await asyncio.sleep(0.1) # Allow some time for processing
120+
process_task.cancel()
128121

129-
# Verify the output connector received the processed message
130-
output_connector = dialog_engine.connectors["test_output"]
131-
assert len(output_connector.sent_messages) == 1
132-
assert output_connector.sent_messages[0][0] == {"processed": True}
122+
# Verify the message was processed and sent to destination
123+
assert len(mock_destination.sent_messages) > 0
124+
assert mock_destination.sent_messages[0] == test_message
133125

134-
await dialog_engine.stop()
126+
await engine.stop()
135127

136128
@pytest.mark.asyncio
137-
async def test_engine_invalid_workflow(self, sample_config, mock_connector_factory, mock_processor_factory):
138-
"""Test processing with an invalid workflow name."""
139-
with patch('dialogchain.connectors.create_connector', mock_connector_factory), \
140-
patch('dialogchain.processors.create_processor', mock_processor_factory):
141-
142-
dialog_engine = engine.DialogEngine(sample_config)
143-
await dialog_engine.start()
144-
145-
with pytest.raises(exceptions.ConfigurationError) as exc_info:
146-
await dialog_engine.process_next_message("test_input", workflow="nonexistent")
147-
assert "Unknown workflow" in str(exc_info.value)
148-
149-
await dialog_engine.stop()
129+
async def test_engine_invalid_config(self):
130+
"""Test engine initialization with invalid config."""
131+
invalid_config = {"routes": [{"name": "invalid"}]} # Missing required fields
132+
133+
with pytest.raises(ConfigError):
134+
CamelRouterEngine(invalid_config)
150135

151136
@pytest.mark.asyncio
152-
async def test_engine_error_handling(self, sample_config, mock_connector_factory, mock_processor_factory):
153-
"""Test error handling during message processing."""
154-
# Create a processor that raises an exception
155-
mock_processor = AsyncMock()
156-
mock_processor.process.side_effect = Exception("Test error")
157-
mock_processor_factory.return_value = mock_processor
158-
159-
with patch('dialogchain.connectors.create_connector', mock_connector_factory), \
160-
patch('dialogchain.processors.create_processor', mock_processor_factory):
161-
162-
dialog_engine = engine.DialogEngine(sample_config)
163-
await dialog_engine.start()
137+
async def test_engine_context_manager(self, sample_config, mock_source, mock_destination):
138+
"""Test using the engine as a context manager."""
139+
with patch('dialogchain.connectors.create_source', return_value=mock_source), \
140+
patch('dialogchain.connectors.create_destination', return_value=mock_destination):
164141

165-
# Get the input connector and simulate receiving a message
166-
input_connector = dialog_engine.connectors["test_input"]
167-
input_connector.received_messages = [{"text": "Hello"}]
142+
async with CamelRouterEngine(sample_config) as engine:
143+
# Verify engine is running
144+
assert engine.is_running is True
145+
assert mock_source.is_connected is True
146+
assert mock_destination.is_connected is True
168147

169-
# Process a message that will cause an error
170-
with pytest.raises(Exception) as exc_info:
171-
await dialog_engine.process_next_message("test_input", workflow="default")
172-
assert "Test error" in str(exc_info.value)
148+
# Verify engine is stopped after context
149+
assert engine.is_running is False
150+
assert mock_source.is_connected is False
151+
assert mock_destination.is_connected is False
173152

174-
await dialog_engine.stop()
175-
176-
@pytest.mark.asyncio
177-
async def test_engine_context_manager(self, sample_config, mock_connector_factory, mock_processor_factory):
153+
178154
"""Test using the engine as a context manager."""
179155
with patch('dialogchain.connectors.create_connector', mock_connector_factory), \
180156
patch('dialogchain.processors.create_processor', mock_processor_factory):

0 commit comments

Comments
 (0)