SIENTIAPDE-1205

Refactor OpcRepository and update tests for async handling

- Removed the shutdown method from OpcRepository and adjusted the disconnect logic.
- Updated tests in test_activities.py and test_opc.py to support async shutdown functionality.
- Enhanced test cases in test_opc_repository.py to ensure proper async behavior and error handling in OpcRepository methods.
This commit is contained in:
vitor-aignosi
2025-08-28 10:21:20 -03:00
parent fc799de42e
commit 7f3ecc9add
5 changed files with 260 additions and 131 deletions

View File

@@ -158,16 +158,6 @@ class OpcRepository():
f"Failed to disconnect from OPC server: {e}", self.metadata) f"Failed to disconnect from OPC server: {e}", self.metadata)
self.client = None self.client = None
def shutdown(self):
"""
Disconnects from the OPC server when the object is destroyed.
"""
try:
asyncio.run(self.disconnect())
except Exception as e:
self.logger.custom_error(
f"Error in destructor: {e}", self.metadata)
async def validate_connection(self) -> tuple[bool, dict[str, Any]]: async def validate_connection(self) -> tuple[bool, dict[str, Any]]:
""" """
Validates the connection to the OPC server using protocol state checking. Validates the connection to the OPC server using protocol state checking.

11
run_coverage.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
# Exit on any error
set -e
echo "Activating virtual environment..."
source ./venv/bin/activate
pytest --cov=laborious --cov-report=html
xdg-open htmlcov/index.html

View File

@@ -90,11 +90,12 @@ def test___init__(mock_gates_init, mock_opc_init, mock_mlflow_init, mock_postgre
) )
@mark.asyncio
@patch('laborious.activities.activities.Postgres', return_value=MagicMock()) @patch('laborious.activities.activities.Postgres', return_value=MagicMock())
@patch('laborious.activities.activities.MLFlow', return_value=MagicMock()) @patch('laborious.activities.activities.MLFlow', return_value=MagicMock())
@patch('laborious.activities.activities.OPC', return_value=MagicMock()) @patch('laborious.activities.activities.OPC', return_value=MagicMock())
def test_shutdown(mock_opc_init, async def test_shutdown(mock_opc_init,
_mock_mlflow_init, mock_postgres_init): _mock_mlflow_init, mock_postgres_init):
postgres_config = { postgres_config = {
'host': 'localhost', 'host': 'localhost',
'port': 5432, 'port': 5432,
@@ -129,6 +130,6 @@ def test_shutdown(mock_opc_init,
notification_handler=notification_handler notification_handler=notification_handler
) )
activities.shutdown() await activities.shutdown()
mock_opc_init.shutdown.assert_called_once() mock_opc_init.shutdown.assert_called_once()
mock_postgres_init.close.assert_called_once() mock_postgres_init.close.assert_called_once()

View File

@@ -1,7 +1,8 @@
from unittest.mock import patch, MagicMock, ANY, call from unittest.mock import patch, MagicMock, ANY, call, AsyncMock
from pandas import DataFrame from pandas import DataFrame
from pytest import fixture, mark from pytest import fixture, mark
from laborious.activities.opc import NotificationLevel import pytest_asyncio
from sientia_do.notifications.models import NotificationLevel
from laborious.activities.opc import OPC from laborious.activities.opc import OPC
@@ -15,27 +16,42 @@ metadata = {
} }
def test__init__():
servers = {
'server1': 'config'
}
opc = OPC(
opc_servers=servers,
logger=MagicMock(),
notification_handler=MagicMock()
)
assert opc.opc_servers == servers
assert opc.opc_repository == {}
@mark.asyncio
@patch("laborious.activities.opc.OpcRepository") @patch("laborious.activities.opc.OpcRepository")
@patch("laborious.activities.opc.OPC.send_notification") @patch("laborious.activities.opc.OPC.send_notification")
def test___init__(mock_send_notification, mock_opc_repository): async def test_init_opc(mock_send_notification, mock_opc_repository):
mock_logger = MagicMock() mock_logger = MagicMock()
server1 = MagicMock( server1 = MagicMock(
connect=MagicMock(return_value=(True, {})), connect=AsyncMock(return_value=(True, {})),
write_data=MagicMock(return_value=(True, {})) write_data=AsyncMock(return_value=(True, {}))
) )
server2 = MagicMock( server2 = MagicMock(
connect=MagicMock(return_value=(True, {})), connect=AsyncMock(return_value=(True, {})),
write_data=MagicMock(return_value=(True, {})) write_data=AsyncMock(return_value=(True, {}))
) )
server3 = MagicMock( server3 = MagicMock(
connect=MagicMock(return_value=(False, { connect=AsyncMock(return_value=(False, {
'notification_id': 'OPC_CONNECTION_ERROR_server3', 'notification_id': 'OPC_CONNECTION_ERROR_server3',
'message': 'Failed to connect to OPC server: Test error', 'message': 'Failed to connect to OPC server: Test error',
'block': 'opc_repository', 'block': 'opc_repository',
'level': NotificationLevel.ERROR, 'level': NotificationLevel.ERROR,
'attachment_content': 'Test error' 'attachment_content': 'Test error'
})), })),
write_data=MagicMock(return_value=(True, {})) write_data=AsyncMock(return_value=(True, {}))
) )
mock_opc_repository.side_effect = [server1, server2, server3] mock_opc_repository.side_effect = [server1, server2, server3]
mock_notification_handler = MagicMock() mock_notification_handler = MagicMock()
@@ -73,6 +89,7 @@ def test___init__(mock_send_notification, mock_opc_repository):
logger=mock_logger, logger=mock_logger,
notification_handler=mock_notification_handler notification_handler=mock_notification_handler
) )
await opc.init_opc()
assert opc.opc_servers == servers assert opc.opc_servers == servers
assert opc.logger == mock_logger assert opc.logger == mock_logger
@@ -129,9 +146,9 @@ def test___init__(mock_send_notification, mock_opc_repository):
]) ])
@fixture @pytest_asyncio.fixture
@patch("laborious.activities.opc.OpcRepository") @patch("laborious.activities.opc.OpcRepository")
def opc(mock_opc_repository): async def opc(mock_opc_repository):
servers = { servers = {
'server1': { 'server1': {
'id': 'server1', 'id': 'server1',
@@ -144,10 +161,10 @@ def opc(mock_opc_repository):
} }
} }
mock_opc_repository.return_value.write_data = MagicMock( mock_opc_repository.return_value.write_data = AsyncMock(
return_value=(True, {}) return_value=(True, {})
) )
mock_opc_repository.return_value.connect = MagicMock( mock_opc_repository.return_value.connect = AsyncMock(
return_value=(True, {}) return_value=(True, {})
) )
opc = OPC( opc = OPC(
@@ -155,7 +172,7 @@ def opc(mock_opc_repository):
logger=MagicMock(), logger=MagicMock(),
notification_handler=MagicMock() notification_handler=MagicMock()
) )
await opc.init_opc()
opc.send_notification = MagicMock() opc.send_notification = MagicMock()
return opc return opc
@@ -169,14 +186,17 @@ WRITE_DATA_CASES = [
@mark.parametrize('tag,data_type,data', WRITE_DATA_CASES) @mark.parametrize('tag,data_type,data', WRITE_DATA_CASES)
def test_write_data_success(opc, tag, data_type, data): @mark.asyncio
assert opc.write_data(server_id='server1', tag=tag, data=data, async def test_write_data_success(opc, tag, data_type, data):
data_type=data_type, tag_type='prediction', metadata=metadata) result = await opc.write_data(server_id='server1', tag=tag, data=data,
data_type=data_type, tag_type='prediction', metadata=metadata)
assert result is True
opc.opc_repository['server1'].write_data.assert_called_once_with( opc.opc_repository['server1'].write_data.assert_called_once_with(
tag, data, data_type, opc.logger, metadata) tag, data, data_type, opc.logger, metadata)
def test_write_data_failed(opc): @mark.asyncio
async def test_write_data_failed(opc):
opc.opc_repository['server1'].write_data.return_value = (False, { opc.opc_repository['server1'].write_data.return_value = (False, {
'notification_id': 'OPC_WRITE_DATA_ERROR_server1', 'notification_id': 'OPC_WRITE_DATA_ERROR_server1',
'message': 'Failed to write data to OPC server: Test error', 'message': 'Failed to write data to OPC server: Test error',
@@ -185,8 +205,9 @@ def test_write_data_failed(opc):
'attachment_content': 'Test error' 'attachment_content': 'Test error'
}) })
assert opc.write_data(server_id='server1', tag='tag1', data=50, result = await opc.write_data(server_id='server1', tag='tag1', data=50,
data_type='int', tag_type='prediction', metadata=metadata) is False data_type='int', tag_type='prediction', metadata=metadata)
assert result is False
opc.send_notification.assert_called_once_with( opc.send_notification.assert_called_once_with(
metadata=metadata, metadata=metadata,
@@ -198,13 +219,14 @@ def test_write_data_failed(opc):
) )
def test_write_data_exception(opc): @mark.asyncio
async def test_write_data_exception(opc):
opc.opc_repository['server1'].write_data.side_effect = Exception( opc.opc_repository['server1'].write_data.side_effect = Exception(
"Test error") "Test error")
try: try:
opc.write_data(server_id='server1', tag='tag1', data=50, await opc.write_data(server_id='server1', tag='tag1', data=50,
data_type='int', tag_type='prediction', metadata=metadata) data_type='int', tag_type='prediction', metadata=metadata)
except Exception: except Exception:
opc.send_notification.assert_called_once_with( opc.send_notification.assert_called_once_with(
@@ -242,7 +264,7 @@ async def test_write_opc_data_success(opc):
} }
# Act # Act
opc.write_data = MagicMock() opc.write_data = AsyncMock(return_value=True)
opc.process_confidence = MagicMock(return_value={'data': 'data'}) opc.process_confidence = MagicMock(return_value={'data': 'data'})
output = await opc.write_opc_data(input_data) output = await opc.write_opc_data(input_data)
@@ -281,8 +303,38 @@ async def test_write_opc_data_empty_config(opc):
}, },
'opc_servers': ['server1'], 'opc_servers': ['server1'],
'opc_output_config': { 'opc_output_config': {
'prediction_tags': {}, 'server1': {
'confidence_tags': {} 'prediction_tags': {},
'confidence_tags': {}
}
}
}
# Act
await opc.write_opc_data(input_data)
# Assert
opc.opc_repository['server1'].write_data.assert_not_called()
@mark.asyncio
async def test_write_opc_data_no_validate_server(opc):
opc.validate_server = MagicMock(return_value=False)
input_data = {
**metadata,
'data': {
'prediction': [0.75],
'prediction_confidence': [0.95]
},
'opc_output_config': {
'server1': {
'prediction_tags': {
'tag1': {'data_type': 'float'}
},
'confidence_tags': {
'tag2': {'data_type': 'float'}
}
}
} }
} }
@@ -305,6 +357,13 @@ def test_process_confidence(opc, data, success, expected):
assert result['prediction_confidence'][0] == expected assert result['prediction_confidence'][0] == expected
def test_shutdown(opc): def test_validate_server(opc):
opc.shutdown() assert opc.validate_server('server1', metadata) is True
assert opc.validate_server('server2', metadata) is False
@mark.asyncio
async def test_shutdown(opc):
opc.opc_repository['server1'].disconnect = AsyncMock(return_value=True)
await opc.shutdown()
opc.opc_repository['server1'].disconnect.assert_called_once() opc.opc_repository['server1'].disconnect.assert_called_once()

View File

@@ -1,17 +1,17 @@
from unittest.mock import Mock, patch, MagicMock, ANY, call import pytest
from unittest.mock import AsyncMock, Mock, patch, MagicMock, ANY, call
from asyncua.crypto.security_policies import SecurityPolicyBasic256 from asyncua.crypto.security_policies import SecurityPolicyBasic256
from pytest import fixture
from laborious.utils.repository.opc_repository import OpcRepository from laborious.utils.repository.opc_repository import OpcRepository
from sientia_do.notifications.models import NotificationLevel from sientia_do.notifications.models import NotificationLevel
from datetime import datetime from datetime import datetime
@fixture @pytest.fixture
def mock_logger(): def mock_logger():
return Mock() return Mock()
@fixture @pytest.fixture
def opc_repository(mock_logger): def opc_repository(mock_logger):
return OpcRepository( return OpcRepository(
id="test_repo", id="test_repo",
@@ -26,10 +26,10 @@ def opc_repository(mock_logger):
) )
@fixture @pytest.fixture
def mock_client(): def mock_client():
with patch('laborious.utils.repository.opc_repository.Client') as mock: with patch('laborious.utils.repository.opc_repository.Client') as mock:
client_instance = MagicMock() client_instance = AsyncMock()
mock.return_value = client_instance mock.return_value = client_instance
yield client_instance yield client_instance
@@ -57,9 +57,10 @@ def test_init(opc_repository):
assert opc_repository.error_count == 0 assert opc_repository.error_count == 0
def test_set_security(opc_repository, mock_client): @pytest.mark.asyncio
async def test_set_security(opc_repository, mock_client):
opc_repository.client = mock_client opc_repository.client = mock_client
opc_repository.set_security() await opc_repository.set_security()
mock_client.application_uri = "urn:test:server" mock_client.application_uri = "urn:test:server"
mock_client.set_security.assert_called_once_with( mock_client.set_security.assert_called_once_with(
@@ -72,50 +73,59 @@ def test_set_security(opc_repository, mock_client):
assert mock_client.session_timeout == 10000000 assert mock_client.session_timeout == 10000000
def test_set_security_missing_certificates(opc_repository): @pytest.mark.asyncio
async def test_set_security_missing_certificates(opc_repository):
opc_repository.cert_path = None opc_repository.cert_path = None
opc_repository.private_key_path = None opc_repository.private_key_path = None
try: try:
opc_repository.set_security() await opc_repository.set_security()
except ValueError as e: except ValueError as e:
assert str( assert str(
e) == "Certificate and private key paths must be provided for secure connection." e) == "Certificate and private key paths must be provided for secure connection."
def test_connect_with_security(opc_repository, mock_client): @pytest.mark.asyncio
opc_repository.try_connect = MagicMock() async def test_connect_with_security(opc_repository, mock_client):
opc_repository.connect() opc_repository.try_connect = AsyncMock(return_value=(True, {}))
result = await opc_repository.connect()
opc_repository.try_connect.assert_called_once() opc_repository.try_connect.assert_called_once()
assert opc_repository.client == mock_client assert opc_repository.client == mock_client
assert result == (True, {})
def test_connect_without_security(opc_repository, mock_client): @pytest.mark.asyncio
async def test_connect_without_security(opc_repository, mock_client):
opc_repository.cert_path = None opc_repository.cert_path = None
opc_repository.try_connect = MagicMock() opc_repository.try_connect = AsyncMock(return_value=(True, {}))
opc_repository.set_security = MagicMock() opc_repository.set_security = AsyncMock()
opc_repository.connect() result = await opc_repository.connect()
opc_repository.try_connect.assert_called_once() opc_repository.try_connect.assert_called_once()
opc_repository.set_security.assert_not_called() opc_repository.set_security.assert_not_called()
assert opc_repository.client == mock_client assert opc_repository.client == mock_client
assert result == (True, {})
def test_try_connect_sucess(opc_repository): @pytest.mark.asyncio
async def test_try_connect_success(opc_repository):
opc_repository.last_reconnection_time = None opc_repository.last_reconnection_time = None
opc_repository.client = MagicMock() opc_repository.client = AsyncMock()
opc_repository.try_connect() result = await opc_repository.try_connect()
opc_repository.client.connect.assert_called_once() opc_repository.client.connect.assert_called_once()
assert opc_repository.last_reconnection_time is not None assert opc_repository.last_reconnection_time is not None
assert result == (True, {})
def test_try_connect_fail(opc_repository): @pytest.mark.asyncio
async def test_try_connect_fail(opc_repository):
opc_repository.last_reconnection_time = None opc_repository.last_reconnection_time = None
opc_repository.client = MagicMock() opc_repository.client = MagicMock()
opc_repository.client.connect.side_effect = Exception("Test error") opc_repository.client.connect.side_effect = Exception("Test error")
is_connected, error_data = opc_repository.try_connect() is_connected, error_data = await opc_repository.try_connect()
opc_repository.client.connect.assert_called_once() opc_repository.client.connect.assert_called_once()
assert is_connected is False assert is_connected is False
@@ -126,18 +136,26 @@ def test_try_connect_fail(opc_repository):
assert error_data['attachment_content'] is not None assert error_data['attachment_content'] is not None
def test_disconnect(opc_repository, mock_client): @pytest.mark.asyncio
async def test_disconnect(opc_repository, mock_client):
opc_repository.client = mock_client opc_repository.client = mock_client
opc_repository.disconnect() await opc_repository.disconnect()
mock_client.disconnect.assert_called_once() mock_client.disconnect.assert_called_once()
assert opc_repository.client is None assert opc_repository.client is None
def test_disconnect_error(opc_repository, mock_client): @pytest.mark.asyncio
async def test_disconnect_no_client(opc_repository):
opc_repository.client = None
assert await opc_repository.disconnect() is None
@pytest.mark.asyncio
async def test_disconnect_error(opc_repository, mock_client):
opc_repository.client = mock_client opc_repository.client = mock_client
mock_client.disconnect.side_effect = Exception("Test error") mock_client.disconnect.side_effect = Exception("Test error")
opc_repository.disconnect() await opc_repository.disconnect()
opc_repository.logger.custom_error.assert_called_once_with( opc_repository.logger.custom_error.assert_called_once_with(
"Failed to disconnect from OPC server: Test error", "Failed to disconnect from OPC server: Test error",
@@ -146,21 +164,25 @@ def test_disconnect_error(opc_repository, mock_client):
assert opc_repository.client is None assert opc_repository.client is None
def test_validate_connection_none_client(opc_repository): @pytest.mark.asyncio
async def test_validate_connection_none_client(opc_repository):
opc_repository.client = None opc_repository.client = None
opc_repository.connect = MagicMock() opc_repository.connect = AsyncMock(return_value=(True, {}))
response = opc_repository.validate_connection() response = await opc_repository.validate_connection()
assert response assert response == (True, {})
opc_repository.connect.assert_called_once() opc_repository.connect.assert_called_once()
def test_validate_connection_error_count_disconnect_error(opc_repository): @pytest.mark.asyncio
async def test_validate_connection_error_count_disconnect_error(opc_repository):
opc_repository.error_count = 6 opc_repository.error_count = 6
opc_repository.client = MagicMock() opc_repository.client = AsyncMock()
opc_repository.disconnect = MagicMock(side_effect=Exception("Test error")) opc_repository.disconnect = AsyncMock(
opc_repository.connect = MagicMock() side_effect=Exception("Test error")
)
opc_repository.connect = AsyncMock(return_value=(True, {}))
response = opc_repository.validate_connection() response = await opc_repository.validate_connection()
assert response == opc_repository.connect.return_value assert response == opc_repository.connect.return_value
opc_repository.disconnect.assert_called_once() opc_repository.disconnect.assert_called_once()
opc_repository.connect.assert_called_once() opc_repository.connect.assert_called_once()
@@ -171,18 +193,37 @@ def test_validate_connection_error_count_disconnect_error(opc_repository):
) )
@patch('laborious.utils.repository.opc_repository.hasattr', return_value=True) @pytest.mark.asyncio
@patch('laborious.utils.repository.opc_repository.datetime', async def test_validate_connection_error_validate_connection_error(opc_repository):
MagicMock(now=MagicMock(return_value=datetime(2025, 1, 1, 0, 0, 0)))) opc_repository.client = MagicMock(
def test_validate_connection_lost_not_time_to_reconect(_mock_datetime, opc_repository): uaclient=Exception("Test error")
)
opc_repository.error_count = 0
response = await opc_repository.validate_connection()
assert response == (False, {
"notification_id": f"OPC_CONNECTION_CHECK_ERROR_{opc_repository.id}",
"message": "Failed to validate connection to OPC server: 'Exception' object has no attribute 'protocol'",
"block": "opc_repository",
"level": NotificationLevel.ERROR,
"attachment_content": ANY
})
@pytest.mark.asyncio
@patch('laborious.utils.repository.opc_repository.datetime')
async def test_validate_connection_lost_not_time_to_reconnect(_mock_datetime, opc_repository):
_mock_datetime.now = MagicMock(
return_value=datetime(2025, 1, 1, 0, 0, 0))
opc_repository.error_count = 0 opc_repository.error_count = 0
opc_repository.client = MagicMock() opc_repository.client = MagicMock()
opc_repository.client.aio_obj.uaclient.protocol = None opc_repository.client.uaclient.protocol = None
opc_repository.last_reconnection_time = datetime(2025, 1, 1, 0, 0, 0) opc_repository.last_reconnection_time = datetime(2025, 1, 1, 0, 0, 0)
opc_repository.try_connect = MagicMock() opc_repository.connect = MagicMock(return_value=(True, {}))
response = opc_repository.validate_connection() response = await opc_repository.validate_connection()
opc_repository.try_connect.assert_not_called() opc_repository.connect.assert_not_called()
assert response == (False, { assert response == (False, {
"notification_id": f"OPC_CONNECTION_AWAITING_RECONNECTION_WINDOW_{opc_repository.id}", "notification_id": f"OPC_CONNECTION_AWAITING_RECONNECTION_WINDOW_{opc_repository.id}",
"message": f"OPC server {opc_repository.id} is not connected, waiting for next reconnection window...", "message": f"OPC server {opc_repository.id} is not connected, waiting for next reconnection window...",
@@ -191,96 +232,120 @@ def test_validate_connection_lost_not_time_to_reconect(_mock_datetime, opc_repos
}) })
@patch('laborious.utils.repository.opc_repository.hasattr', return_value=True) @pytest.mark.asyncio
@patch('laborious.utils.repository.opc_repository.datetime', @patch('laborious.utils.repository.opc_repository.datetime')
MagicMock(now=MagicMock(return_value=datetime(2025, 1, 1, 1, 0, 0)))) async def test_validate_connection_lost_time_to_reconnect(mock_datetime, opc_repository):
def test_validate_connection_lost_time_to_reconect(_mock_datetime, opc_repository): mock_datetime.now = MagicMock(
return_value=datetime(2025, 1, 1, 1, 0, 0))
opc_repository.error_count = 0 opc_repository.error_count = 0
opc_repository.client = MagicMock() opc_repository.client = AsyncMock()
opc_repository.client.aio_obj.uaclient.protocol = None opc_repository.client.uaclient.protocol = None
opc_repository.last_reconnection_time = datetime(2025, 1, 1, 0, 0, 0) opc_repository.last_reconnection_time = datetime(2025, 1, 1, 0, 0, 0)
opc_repository.connect = MagicMock() opc_repository.connect = AsyncMock(return_value=(True, {}))
response = opc_repository.validate_connection() response = await opc_repository.validate_connection()
opc_repository.connect.assert_called_once() opc_repository.connect.assert_called_once()
assert response == opc_repository.connect.return_value assert response == opc_repository.connect.return_value
def test_validate_connection_failed(opc_repository): @pytest.mark.asyncio
async def test_validate_connection_success(opc_repository):
opc_repository.client = MagicMock() opc_repository.client = MagicMock()
opc_repository.error_count = 0 opc_repository.error_count = 0
opc_repository.client.uaclient.protocol = MagicMock()
opc_repository.client.uaclient.protocol.state = "open"
output = opc_repository.validate_connection() output = await opc_repository.validate_connection()
assert output == (True, {}) assert output == (True, {})
def test_write_data_validate_connection_do_nothing(opc_repository): @pytest.mark.asyncio
opc_repository.validate_connection = MagicMock(return_value=(True, {})) async def test_write_data_validate_connection_do_nothing(opc_repository):
opc_repository.client = MagicMock() opc_repository.validate_connection = AsyncMock(return_value=(True, {}))
opc_repository.write_data("ns=2;s=TestNode", 42.0, opc_repository.client = AsyncMock(
"float", opc_repository.logger, metadata) get_node=MagicMock()
)
mock_node = AsyncMock()
opc_repository.client.get_node.return_value = mock_node
result = await opc_repository.write_data("ns=2;s=TestNode", 42.0,
"float", opc_repository.logger, metadata['metadata'])
opc_repository.validate_connection.assert_called_once() opc_repository.validate_connection.assert_called_once()
opc_repository.client.get_node.assert_called_once_with("ns=2;s=TestNode") opc_repository.client.get_node.assert_called_once_with("ns=2;s=TestNode")
assert result == (True, {})
def test_write_data_validate_connection_failed(opc_repository): @pytest.mark.asyncio
opc_repository.validate_connection = MagicMock(return_value=(False, {})) async def test_write_data_validate_connection_failed(opc_repository):
opc_repository.client = MagicMock() opc_repository.validate_connection = AsyncMock(return_value=(False, {}))
opc_repository.client = AsyncMock()
opc_repository.error_count = 0 opc_repository.error_count = 0
opc_repository.write_data("ns=2;s=TestNode", 42.0,
"float", opc_repository.logger, metadata) result = await opc_repository.write_data("ns=2;s=TestNode", 42.0,
"float", opc_repository.logger, metadata['metadata'])
opc_repository.validate_connection.assert_called_once() opc_repository.validate_connection.assert_called_once()
opc_repository.client.get_node.assert_not_called() opc_repository.client.get_node.assert_not_called()
assert result == (False, {})
def test_write_data_get_node_failed(opc_repository): @pytest.mark.asyncio
opc_repository.validate_connection = MagicMock(return_value=(True, {})) async def test_write_data_get_node_failed(opc_repository):
opc_repository.client = MagicMock() opc_repository.validate_connection = AsyncMock(return_value=(True, {}))
opc_repository.client = AsyncMock()
opc_repository.error_count = 0 opc_repository.error_count = 0
opc_repository.client.get_node.side_effect = Exception("Test error") opc_repository.client.get_node = MagicMock(
is_success, error_data = opc_repository.write_data("ns=2;s=TestNode", 42.0, side_effect=Exception("Test error"))
"float", opc_repository.logger, metadata)
is_success, error_data = await opc_repository.write_data("ns=2;s=TestNode", 42.0,
"float", opc_repository.logger, metadata['metadata'])
opc_repository.validate_connection.assert_called_once() opc_repository.validate_connection.assert_called_once()
opc_repository.client.get_node.assert_called_once_with("ns=2;s=TestNode") opc_repository.client.get_node.assert_called_once_with("ns=2;s=TestNode")
assert is_success is False assert is_success is False
assert error_data['notification_id'] == f"OPC_WRITE_GET_NODE_ERROR_{opc_repository.id}" assert error_data['notification_id'] == f"OPC_WRITE_GET_NODE_ERROR_{opc_repository.id}"
assert error_data['message'] == "Failed to get node from OPC server: Test error | metadata: {'metadata': {'model_id': 'test_model', 'model_name': 'test_model', 'workflow_name': 'test_workflow', 'schema_name': 'test_schedule'}}" assert error_data['message'] == "Failed to get node from OPC server: Test error | metadata: {'model_id': 'test_model', 'model_name': 'test_model', 'workflow_name': 'test_workflow', 'schema_name': 'test_schedule'}"
assert error_data['block'] == "opc_repository" assert error_data['block'] == "opc_repository"
assert error_data['level'] == NotificationLevel.ERROR assert error_data['level'] == NotificationLevel.ERROR
assert error_data['attachment_content'] is not None assert error_data['attachment_content'] is not None
def test_write_data_invalid_data_type(opc_repository, mock_client): @pytest.mark.asyncio
opc_repository.validate_connection = MagicMock(return_value=(True, {})) async def test_write_data_invalid_data_type(opc_repository, mock_client):
opc_repository.validate_connection = AsyncMock(return_value=(True, {}))
opc_repository.client = mock_client opc_repository.client = mock_client
mock_node = MagicMock() mock_node = AsyncMock()
mock_client.get_node.return_value = mock_node mock_client.get_node = MagicMock(return_value=mock_node)
is_success, error_data = await opc_repository.write_data("ns=2;s=TestNode", 42.0,
"invalid_type", opc_repository.logger, metadata['metadata'])
is_success, error_data = opc_repository.write_data("ns=2;s=TestNode", 42.0,
"invalid_type", opc_repository.logger, metadata)
opc_repository.validate_connection.assert_called_once() opc_repository.validate_connection.assert_called_once()
mock_client.get_node.assert_called_once_with("ns=2;s=TestNode") mock_client.get_node.assert_called_once_with("ns=2;s=TestNode")
assert is_success is False assert is_success is False
assert error_data['notification_id'] == f"OPC_WRITE_DATA_TYPE_ERROR_{opc_repository.id}" assert error_data['notification_id'] == f"OPC_WRITE_DATA_TYPE_ERROR_{opc_repository.id}"
assert error_data['message'] == "Unsupported data type: invalid_type | metadata: {'metadata': {'model_id': 'test_model', 'model_name': 'test_model', 'workflow_name': 'test_workflow', 'schema_name': 'test_schedule'}}" assert error_data['message'] == "Unsupported data type: invalid_type | metadata: {'model_id': 'test_model', 'model_name': 'test_model', 'workflow_name': 'test_workflow', 'schema_name': 'test_schedule'}"
assert error_data['block'] == "opc_repository" assert error_data['block'] == "opc_repository"
assert error_data['level'] == NotificationLevel.ERROR assert error_data['level'] == NotificationLevel.ERROR
assert error_data.get('attachment_content') is None assert error_data.get('attachment_content') is None
@pytest.mark.asyncio
@patch('laborious.utils.repository.opc_repository.metrics') @patch('laborious.utils.repository.opc_repository.metrics')
def test_write_data(mock_metrics, opc_repository, mock_client): async def test_write_data(mock_metrics, opc_repository, mock_client):
opc_repository.validate_connection = MagicMock(return_value=(True, {})) opc_repository.validate_connection = AsyncMock(return_value=(True, {}))
opc_repository.client = mock_client opc_repository.client = mock_client
mock_node = MagicMock() mock_node = AsyncMock()
mock_client.get_node.return_value = mock_node mock_client.get_node = MagicMock(return_value=mock_node)
opc_repository.write_data("ns=2;s=TestNode", 42.0, result = await opc_repository.write_data("ns=2;s=TestNode", 42.0,
"float", opc_repository.logger, metadata['metadata']) "float", opc_repository.logger, metadata['metadata'])
mock_client.get_node.assert_called_once_with("ns=2;s=TestNode") mock_client.get_node.assert_called_once_with("ns=2;s=TestNode")
mock_node.write_value.assert_called_once() mock_node.write_value.assert_called_once()
assert result == (True, {})
mock_metrics.PREDICTION_OPC_WRITING_COUNT.labels.assert_called_once_with( mock_metrics.PREDICTION_OPC_WRITING_COUNT.labels.assert_called_once_with(
pod_id=opc_repository.pod_id, pod_id=opc_repository.pod_id,
@@ -300,21 +365,24 @@ def test_write_data(mock_metrics, opc_repository, mock_client):
ANY) ANY)
def test_write_data_write_value_failed(opc_repository, mock_client): @pytest.mark.asyncio
opc_repository.validate_connection = MagicMock(return_value=(True, {})) async def test_write_data_write_value_failed(opc_repository, mock_client):
opc_repository.validate_connection = AsyncMock(return_value=(True, {}))
opc_repository.client = mock_client opc_repository.client = mock_client
mock_node = MagicMock() mock_node = AsyncMock()
opc_repository.error_count = 0 opc_repository.error_count = 0
mock_client.get_node.return_value = mock_node mock_client.get_node = MagicMock(return_value=mock_node)
mock_node.write_value.side_effect = Exception("Test error") mock_node.write_value.side_effect = Exception("Test error")
is_success, error_data = opc_repository.write_data("ns=2;s=TestNode", 42.0,
"float", opc_repository.logger, metadata) is_success, error_data = await opc_repository.write_data("ns=2;s=TestNode", 42.0,
"float", opc_repository.logger, metadata['metadata'])
opc_repository.validate_connection.assert_called_once() opc_repository.validate_connection.assert_called_once()
mock_client.get_node.assert_called_once_with("ns=2;s=TestNode") mock_client.get_node.assert_called_once_with("ns=2;s=TestNode")
mock_node.write_value.assert_called_once() mock_node.write_value.assert_called_once()
assert is_success is False assert is_success is False
assert error_data['notification_id'] == f"OPC_WRITE_DATA_ERROR_{opc_repository.id}" assert error_data['notification_id'] == f"OPC_WRITE_DATA_ERROR_{opc_repository.id}"
assert error_data['message'] == "Failed to write data to OPC server: Test error | metadata: {'metadata': {'model_id': 'test_model', 'model_name': 'test_model', 'workflow_name': 'test_workflow', 'schema_name': 'test_schedule'}}" assert error_data['message'] == "Failed to write data to OPC server: Test error | metadata: {'model_id': 'test_model', 'model_name': 'test_model', 'workflow_name': 'test_workflow', 'schema_name': 'test_schedule'}"
assert error_data['block'] == "opc_repository" assert error_data['block'] == "opc_repository"
assert error_data['level'] == NotificationLevel.ERROR assert error_data['level'] == NotificationLevel.ERROR
assert error_data['attachment_content'] is not None assert error_data['attachment_content'] is not None