Skip to content

Commit fda7d84

Browse files
author
Tom Softreck
committed
update
1 parent 7a732f2 commit fda7d84

2 files changed

Lines changed: 93 additions & 241 deletions

File tree

tests/unit/test_connectors.py

Lines changed: 54 additions & 229 deletions
Original file line numberDiff line numberDiff line change
@@ -1,250 +1,75 @@
11
"""Unit tests for the connectors module."""
22
import asyncio
3-
import json
4-
from unittest.mock import AsyncMock, MagicMock, patch
5-
63
import pytest
7-
import aiohttp
8-
import pytest_asyncio
4+
from unittest.mock import AsyncMock, MagicMock, patch
95

106
from dialogchain import connectors
11-
from dialogchain.config import Config
12-
13-
14-
class TestBaseConnector:
15-
"""Test the base connector functionality."""
16-
17-
def test_init(self):
18-
"""Test connector initialization."""
19-
config = {"name": "test_connector", "type": "test", "timeout": 30}
20-
connector = connectors.BaseConnector(config)
21-
assert connector.name == "test_connector"
22-
assert connector.timeout == 30
23-
assert connector.is_connected is False
24-
25-
@pytest.mark.asyncio
26-
async def test_connect_disconnect(self):
27-
"""Test connect and disconnect methods."""
28-
connector = connectors.BaseConnector({"name": "test"})
29-
await connector.connect()
30-
assert connector.is_connected is True
31-
await connector.disconnect()
32-
assert connector.is_connected is False
33-
34-
@pytest.mark.asyncio
35-
async def test_context_manager(self):
36-
"""Test connector as a context manager."""
37-
connector = connectors.BaseConnector({"name": "test"})
38-
async with connector:
39-
assert connector.is_connected is True
40-
assert connector.is_connected is False
417

428

43-
class TestHttpConnector:
44-
"""Test the HTTP connector implementation."""
9+
class TestSourceConnector:
10+
"""Test the Source connector functionality."""
4511

46-
@pytest.fixture
47-
def connector_config(self):
48-
"""Return a sample HTTP connector config."""
49-
return {
50-
"name": "http_test",
51-
"type": "http",
52-
"base_url": "http://example.com/api",
53-
"timeout": 10,
54-
"headers": {"X-Test": "test"},
55-
"auth": {"username": "user", "password": "pass"},
56-
}
12+
def test_source_initialization(self):
13+
"""Test source connector initialization."""
14+
class TestSource(connectors.Source):
15+
async def receive(self):
16+
yield "test"
17+
18+
source = TestSource()
19+
assert isinstance(source, connectors.Source)
5720

58-
@pytest.fixture
59-
def mock_response(self):
60-
"""Create a mock aiohttp response."""
61-
response = AsyncMock(spec=aiohttp.ClientResponse)
62-
response.status = 200
63-
response.json.return_value = {"status": "ok"}
64-
response.text.return_value = '{"status": "ok"}'
65-
response.__aenter__.return_value = response
66-
return response
6721

68-
@pytest.fixture
69-
def mock_session(self, mock_response):
70-
"""Create a mock aiohttp client session."""
71-
session = AsyncMock(spec=aiohttp.ClientSession)
72-
session.request.return_value = mock_response
73-
session.__aenter__.return_value = session
74-
return session
22+
class TestDestinationConnector:
23+
"""Test the Destination connector functionality."""
7524

76-
@pytest.mark.asyncio
77-
async def test_http_connector_init(self, connector_config):
78-
"""Test HTTP connector initialization."""
79-
connector = connectors.HttpConnector(connector_config)
80-
assert connector.base_url == "http://example.com/api"
81-
assert connector.timeout == 10
82-
assert connector.headers == {"X-Test": "test"}
83-
assert connector.auth == aiohttp.BasicAuth("user", "pass")
25+
def test_destination_initialization(self):
26+
"""Test destination connector initialization."""
27+
class TestDestination(connectors.Destination):
28+
async def send(self, message):
29+
pass
30+
31+
destination = TestDestination()
32+
assert isinstance(destination, connectors.Destination)
8433

85-
@pytest.mark.asyncio
86-
async def test_http_connector_connect(self, connector_config, mock_session):
87-
"""Test HTTP connector connection."""
88-
with patch('aiohttp.ClientSession', return_value=mock_session):
89-
connector = connectors.HttpConnector(connector_config)
90-
await connector.connect()
91-
assert connector.is_connected is True
92-
assert connector.session is not None
9334

35+
class TestRTSPSource:
36+
"""Test the RTSP source connector."""
37+
9438
@pytest.mark.asyncio
95-
async def test_http_connector_request(self, connector_config, mock_session, mock_response):
96-
"""Test HTTP connector request method."""
97-
with patch('aiohttp.ClientSession', return_value=mock_session):
98-
connector = connectors.HttpConnector(connector_config)
99-
await connector.connect()
39+
async def test_rtsp_source_receive(self):
40+
"""Test RTSP source receive method."""
41+
with patch('cv2.VideoCapture') as mock_capture:
42+
# Setup mock
43+
mock_capture.return_value.isOpened.return_value = True
44+
mock_capture.return_value.grab.return_value = True
45+
mock_capture.return_value.retrieve.return_value = (True, "frame_data")
10046

101-
# Test GET request
102-
response = await connector.request("GET", "/test")
103-
assert response == {"status": "ok"}
104-
mock_session.request.assert_called_once_with(
105-
"GET",
106-
"http://example.com/api/test",
107-
headers={"X-Test": "test"},
108-
auth=aiohttp.BasicAuth("user", "pass"),
109-
timeout=10,
110-
json=None,
111-
data=None,
112-
params=None
113-
)
114-
115-
# Reset mock for next test
116-
mock_session.request.reset_mock()
117-
118-
# Test POST with data
119-
await connector.request(
120-
"POST",
121-
"/test",
122-
json={"key": "value"},
123-
headers={"X-Custom": "header"},
124-
params={"q": "test"}
125-
)
126-
mock_session.request.assert_called_once_with(
127-
"POST",
128-
"http://example.com/api/test",
129-
headers={"X-Test": "test", "X-Custom": "header"},
130-
auth=aiohttp.BasicAuth("user", "pass"),
131-
timeout=10,
132-
json={"key": "value"},
133-
data=None,
134-
params={"q": "test"}
135-
)
136-
137-
@pytest.mark.asyncio
138-
async def test_http_connector_http_errors(self, connector_config, mock_session, mock_response):
139-
"""Test HTTP connector error handling."""
140-
# Test 404 error
141-
mock_response.status = 404
142-
mock_response.text.return_value = '{"error": "Not found"}'
143-
144-
with patch('aiohttp.ClientSession', return_value=mock_session):
145-
connector = connectors.HttpConnector(connector_config)
146-
await connector.connect()
47+
source = connectors.RTSPSource("rtsp://test")
48+
source.reconnect_attempts = 1 # Limit reconnect attempts for test
14749

148-
with pytest.raises(connectors.ConnectorError) as exc_info:
149-
await connector.request("GET", "/nonexistent")
150-
assert "404" in str(exc_info.value)
50+
# Test receive generator
51+
async for frame in source.receive():
52+
assert frame == {"frame": "frame_data", "metadata": {}}
53+
break # Just test one iteration
15154

15255

153-
class TestMqttConnector:
154-
"""Test the MQTT connector implementation."""
155-
156-
@pytest.fixture
157-
def connector_config(self):
158-
"""Return a sample MQTT connector config."""
159-
return {
160-
"name": "mqtt_test",
161-
"type": "mqtt",
162-
"host": "test.mosquitto.org",
163-
"port": 1883,
164-
"client_id": "test_client",
165-
"clean_session": True,
166-
"topics": ["test/topic"],
167-
}
168-
169-
@pytest.fixture
170-
def mock_mqtt_client(self):
171-
"""Create a mock MQTT client."""
172-
client = AsyncMock()
173-
client.connect = AsyncMock()
174-
client.disconnect = AsyncMock()
175-
client.subscribe = AsyncMock()
176-
client.unsubscribe = AsyncMock()
177-
client.publish = AsyncMock()
178-
return client
179-
180-
@pytest.mark.asyncio
181-
async def test_mqtt_connector_init(self, connector_config):
182-
"""Test MQTT connector initialization."""
183-
with patch('asyncio_mqtt.Client') as mock_client:
184-
connector = connectors.MqttConnector(connector_config)
185-
assert connector.host == "test.mosquitto.org"
186-
assert connector.port == 1883
187-
assert connector.client_id == "test_client"
188-
56+
class TestHTTPDestination:
57+
"""Test the HTTP destination connector."""
58+
18959
@pytest.mark.asyncio
190-
async def test_mqtt_connector_connect_disconnect(self, connector_config, mock_mqtt_client):
191-
"""Test MQTT connector connection and disconnection."""
192-
with patch('asyncio_mqtt.Client', return_value=mock_mqtt_client):
193-
connector = connectors.MqttConnector(connector_config)
194-
195-
# Test connect
196-
await connector.connect()
197-
assert connector.is_connected is True
198-
mock_mqtt_client.connect.assert_awaited_once()
60+
async def test_http_destination_send(self):
61+
"""Test HTTP destination send method."""
62+
with patch('aiohttp.ClientSession.post') as mock_post:
63+
# Setup mock response
64+
mock_response = AsyncMock()
65+
mock_response.status = 200
66+
mock_response.json.return_value = {"status": "success"}
67+
mock_post.return_value.__aenter__.return_value = mock_response
19968

200-
# Test subscribe on connect
201-
mock_mqtt_client.subscribe.assert_awaited_once_with("test/topic")
69+
destination = connectors.HTTPDestination("http://test")
70+
await destination.send({"test": "data"})
20271

203-
# Test disconnect
204-
await connector.disconnect()
205-
assert connector.is_connected is False
206-
mock_mqtt_client.disconnect.assert_awaited_once()
207-
208-
@pytest.mark.asyncio
209-
async def test_mqtt_connector_publish(self, connector_config, mock_mqtt_client):
210-
"""Test MQTT connector publish method."""
211-
with patch('asyncio_mqtt.Client', return_value=mock_mqtt_client):
212-
connector = connectors.MqttConnector(connector_config)
213-
await connector.connect()
214-
215-
# Test publish
216-
await connector.publish("test/topic", {"key": "value"}, qos=1)
217-
mock_mqtt_client.publish.assert_awaited_once()
218-
219-
# Get the actual call arguments
220-
args, kwargs = mock_mqtt_client.publish.call_args
221-
assert args[0] == "test/topic"
222-
assert json.loads(args[1]) == {"key": "value"}
223-
assert kwargs["qos"] == 1
224-
225-
226-
class TestConnectorFactory:
227-
"""Test the connector factory."""
228-
229-
def test_create_connector_http(self):
230-
"""Test creating an HTTP connector."""
231-
config = {"name": "http_test", "type": "http", "base_url": "http://example.com"}
232-
connector = connectors.create_connector(config)
233-
assert isinstance(connector, connectors.HttpConnector)
234-
assert connector.name == "http_test"
235-
assert connector.base_url == "http://example.com"
236-
237-
def test_create_connector_mqtt(self):
238-
"""Test creating an MQTT connector."""
239-
config = {"name": "mqtt_test", "type": "mqtt", "host": "test.mosquitto.org"}
240-
connector = connectors.create_connector(config)
241-
assert isinstance(connector, connectors.MqttConnector)
242-
assert connector.name == "mqtt_test"
243-
assert connector.host == "test.mosquitto.org"
244-
245-
def test_create_connector_invalid_type(self):
246-
"""Test creating a connector with an invalid type."""
247-
config = {"name": "invalid", "type": "invalid_type"}
248-
with pytest.raises(ValueError) as exc_info:
249-
connectors.create_connector(config)
250-
assert "Unknown connector type" in str(exc_info.value)
72+
# Verify the request was made correctly
73+
mock_post.assert_called_once()
74+
args, kwargs = mock_post.call_args
75+
assert kwargs['json'] == {"test": "data"}

tests/unit/test_engine.py

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import pytest
44
from unittest.mock import AsyncMock, MagicMock, patch, call
55

6-
from dialogchain.engine import CamelRouterEngine
7-
from dialogchain.connectors import Source, Destination
8-
from dialogchain.config import RouteConfig, ConfigError
6+
from dialogchain.engine import CamelRouterEngine, parse_uri
7+
from dialogchain.connectors import Source, Destination, RTSPSource, HTTPDestination
8+
from dialogchain.config import RouteConfig, ValidationError
99

1010

1111
class MockSource(Source):
@@ -62,7 +62,17 @@ def sample_config(self):
6262
}
6363
]
6464
}
65-
]
65+
],
66+
"sources": {
67+
"rtsp://camera1": {
68+
"type": "rtsp"
69+
}
70+
},
71+
"destinations": {
72+
"http://api.example.com/webhook": {
73+
"type": "http"
74+
}
75+
}
6676
}
6777

6878
@pytest.fixture
@@ -81,13 +91,30 @@ async def test_engine_initialization(self, sample_config):
8191
engine = CamelRouterEngine(sample_config)
8292
assert engine.config == sample_config
8393
assert len(engine.routes) == 1
84-
assert engine.routes[0].name == "test_route"
94+
assert engine.routes[0]["name"] == "test_route"
95+
96+
@pytest.mark.asyncio
97+
async def test_parse_uri(self):
98+
"""Test the parse_uri function."""
99+
# Test with standard URI
100+
scheme, path = parse_uri("http://example.com/path")
101+
assert scheme == "http"
102+
assert path == "//example.com/path"
103+
104+
# Test with simple scheme:path format
105+
scheme, path = parse_uri("timer:5s")
106+
assert scheme == "timer"
107+
assert path == "5s"
108+
109+
# Test with invalid URI
110+
with pytest.raises(ValueError):
111+
parse_uri("invalid_uri")
85112

86113
@pytest.mark.asyncio
87114
async def test_engine_start_stop(self, sample_config, mock_source, mock_destination):
88115
"""Test starting and stopping the engine."""
89-
with patch('dialogchain.connectors.create_source', return_value=mock_source), \
90-
patch('dialogchain.connectors.create_destination', return_value=mock_destination):
116+
with patch('dialogchain.connectors.RTSPSource', return_value=mock_source), \
117+
patch('dialogchain.connectors.HTTPDestination', return_value=mock_destination):
91118

92119
engine = CamelRouterEngine(sample_config)
93120
await engine.start()
@@ -108,8 +135,8 @@ async def test_engine_process_message(self, sample_config, mock_source, mock_des
108135
test_message = {"frame": "test_frame", "confidence": 0.7}
109136
mock_source.messages = [test_message]
110137

111-
with patch('dialogchain.connectors.create_source', return_value=mock_source), \
112-
patch('dialogchain.connectors.create_destination', return_value=mock_destination):
138+
with patch('dialogchain.connectors.RTSPSource', return_value=mock_source), \
139+
patch('dialogchain.connectors.HTTPDestination', return_value=mock_destination):
113140

114141
engine = CamelRouterEngine(sample_config)
115142
await engine.start()
@@ -130,14 +157,14 @@ async def test_engine_invalid_config(self):
130157
"""Test engine initialization with invalid config."""
131158
invalid_config = {"routes": [{"name": "invalid"}]} # Missing required fields
132159

133-
with pytest.raises(ConfigError):
160+
with pytest.raises(ValidationError):
134161
CamelRouterEngine(invalid_config)
135162

136163
@pytest.mark.asyncio
137164
async def test_engine_context_manager(self, sample_config, mock_source, mock_destination):
138165
"""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):
166+
with patch('dialogchain.connectors.RTSPSource', return_value=mock_source), \
167+
patch('dialogchain.connectors.HTTPDestination', return_value=mock_destination):
141168

142169
async with CamelRouterEngine(sample_config) as engine:
143170
# Verify engine is running

0 commit comments

Comments
 (0)