import json from unittest.mock import MagicMock, patch from pytest import fixture from asyncua.crypto.security_policies import SecurityPolicyBasic256 from ingestor.managers.opc_manager import OpcManager from sientia_do.notifications.models import NotificationLevel tags = { 'ns=3;i=1001': { 'aggregation_function': 'LTS', 'frequency': 1000, 'max_value': 100, 'min_value': 0, 'tag_name': 'Counter' }, 'ns=3;i=1003': { 'aggregation_function': 'AVG', 'frequency': 1000, 'max_value': 100, 'min_value': 0, 'tag_name': 'Random' }, 'ns=3;i=1004': { 'aggregation_function': 'MDN', 'frequency': 1000, 'max_value': 100, 'min_value': 0, 'tag_name': 'Sawtooth' } } @fixture def raw_opc_manager(): return OpcManager( 'TestConnector', 'opc.tcp://localhost:4840', MagicMock(), MagicMock(), 'opc.tcp://localhost:4840' ) @fixture def opc_manager(raw_opc_manager): raw_opc_manager.client = MagicMock() raw_opc_manager.cert_path = 'cert.pem' raw_opc_manager.private_key_path = 'private_key.pem' raw_opc_manager.server_cert_path = 'server_cert.pem' return raw_opc_manager @fixture def opc_manager_subscribed(opc_manager): opc_manager.subscriptions['sub1'] = MagicMock() return opc_manager def test_set_security_success(opc_manager): opc_manager.set_security() assert opc_manager.client.application_uri == opc_manager.server_uri opc_manager.client.set_security.assert_called_once_with( SecurityPolicyBasic256, certificate=opc_manager.cert_path, private_key=opc_manager.private_key_path, server_certificate=opc_manager.server_cert_path ) assert opc_manager.client.secure_channel_timeout == 10000000 assert opc_manager.client.session_timeout == 10000000 def test_set_security_no_cert(opc_manager): opc_manager.cert_path = None opc_manager.private_key_path = None try: opc_manager.set_security() except ValueError as e: assert str( e) == "Certificate and private key paths must be provided for secure connection." else: assert False, "ValueError not raised" assert opc_manager.client.set_security.call_count == 0 @patch('ingestor.managers.opc_manager.Client') def test_connect_no_security(client, raw_opc_manager): raw_opc_manager.set_security = MagicMock() raw_opc_manager.connect() client.assert_called_once_with(raw_opc_manager.url) raw_opc_manager.client.connect.assert_called_once() raw_opc_manager.set_security.assert_not_called() @patch('ingestor.managers.opc_manager.Client') def test_connect_with_security(client, raw_opc_manager): raw_opc_manager.cert_path = 'cert.pem' raw_opc_manager.private_key_path = 'private_key.pem' raw_opc_manager.server_cert_path = 'server_cert.pem' raw_opc_manager.set_security = MagicMock() raw_opc_manager.connect() client.assert_called_once_with(raw_opc_manager.url) raw_opc_manager.client.connect.assert_called_once() raw_opc_manager.set_security.assert_called_once() def test_create_subscription_no_client(raw_opc_manager): try: raw_opc_manager.create_subscription('sub1') except ValueError as e: assert str(e) == "Client not connected. Call connect first." else: assert False, "ValueError not raised" def test_create_subscription_success_has_period(opc_manager): opc_manager.create_subscription('sub1', 1000) opc_manager.client.create_subscription.assert_called_once_with( 1000, opc_manager) assert opc_manager.subscriptions['sub1'] is not None def test_create_subscription_success_no_period(opc_manager): opc_manager.create_subscription('sub1', None) opc_manager.client.create_subscription.assert_called_once_with( 500, opc_manager) assert opc_manager.subscriptions['sub1'] is not None def test_subscribe_no_subscription(opc_manager): try: opc_manager.subscribe('sub1', tags, 1000) except ValueError as e: assert str( e) == "Subscription not created. Call create_subscription first." else: assert False, "ValueError not raised" def test_subscribe_success(opc_manager_subscribed): opc_manager_subscribed.nodes = { 'ns=3;i=1001': 'data' } opc_manager_subscribed.subscribe('sub1', tags, 1000) assert opc_manager_subscribed.nodes == tags assert opc_manager_subscribed.addr_nodes == [ opc_manager_subscribed.client.get_node(n) for n in tags if n != 'ns=3;i=1001'] def test_unsubscribe_no_subscription(opc_manager): try: opc_manager.unsubscribe('sub1') except ValueError as e: assert str( e) == "Subscription not created. Call create_subscription first." else: assert False, "ValueError not raised" def test_unsubscribe_success(opc_manager_subscribed): opc_manager_subscribed.unsubscribe('sub1') opc_manager_subscribed.subscriptions.get('sub1') is None def test_disconnect_success(opc_manager_subscribed): opc_manager_subscribed.client = MagicMock() opc_manager_subscribed.disconnect() opc_manager_subscribed.subscriptions['sub1'].delete.assert_called_once() opc_manager_subscribed.client.disconnect.assert_called_once() def test_disconnect_error(opc_manager_subscribed): opc_manager_subscribed.client = MagicMock() opc_manager_subscribed.subscriptions['sub1'] = MagicMock( delete=MagicMock(side_effect=Exception("Test error")) ) opc_manager_subscribed.disconnect() opc_manager_subscribed.subscriptions['sub1'].delete.assert_called_once() opc_manager_subscribed.client.disconnect.assert_not_called() opc_manager_subscribed.logger.error.assert_called_once_with( "Failed to clean up subscription: Test error") def test_datachange_notification(opc_manager_subscribed): data = MagicMock( monitored_item=MagicMock( Value=MagicMock( Value=MagicMock(Value=42), SourceTimestamp='2021-01-01T00:00:00' ))) opc_manager_subscribed.nodes = { 'ns=3;i=1001': { 'tag_name': 'Counter', 'cycle_rule': { 'cycle_increment': 1.0, 'cycle_count': 2 }, 'topics': ['topic1', 'topic2'] } } opc_manager_subscribed.datachange_notification( 'ns=3;i=1001', None, data) opc_manager_subscribed.data_manager.publish.assert_any_call( 'topic1', { 'tag': 'ns=3;i=1001', 'name': 'Counter', 'timestamp': '2021-01-01T00:00:00', 'value': 42 }) opc_manager_subscribed.data_manager.publish.assert_any_call( 'topic2', { 'tag': 'ns=3;i=1001', 'name': 'Counter', 'timestamp': '2021-01-01T00:00:00', 'value': 42 }) assert opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] == 0 def test_check_cycles(opc_manager_subscribed): handler = MagicMock() opc_manager_subscribed.nodes = tags opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule'] = { 'cycle_increment': 1.0, 'cycle_count': 0 } opc_manager_subscribed.check_cycles( {}, 'ns=3;i=1001', tags['ns=3;i=1001'], handler) assert opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] == 1 opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] = 0 opc_manager_subscribed.check_cycles({'ns=3;i=1001': {}}, 'ns=3;i=1001', tags['ns=3;i=1001'], handler) assert opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] == 0 opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] = 4 opc_manager_subscribed.check_cycles( {}, 'ns=3;i=1001', tags['ns=3;i=1001'], handler) assert opc_manager_subscribed.nodes['ns=3;i=1001']['cycle_rule']['cycle_count'] == 5 handler.assert_called_once_with('5.0 cycles without receive from ns=3;i=1001:Counter', 'TAG_ns=3;i=1001:Counter_LISTENNING_STOPPED', NotificationLevel.WARNING) def test_check_opc_listenning_5_cycles(opc_manager_subscribed): handler = MagicMock() opc_manager_subscribed.nodes = tags opc_manager_subscribed.non_receive_count = 4 opc_manager_subscribed.check_opc_listenning(handler) handler.assert_called_once_with( f'5 cycles without receive from OPC TestConnector. Tags: {json.dumps(tags)}', 'OPC_LISTENNING_STOPPED__TestConnector', NotificationLevel.ERROR ) def test_check_opc_listenning_no_cycles(opc_manager_subscribed): handler = MagicMock() opc_manager_subscribed.non_receive_count = 0 opc_manager_subscribed.check_opc_listenning(handler) handler.assert_not_called() def test_check_opc_listenning_no_handler(opc_manager_subscribed): opc_manager_subscribed.non_receive_count = 5 opc_manager_subscribed.check_opc_listenning(None) assert opc_manager_subscribed.non_receive_count == 6 def test_check_opc_listenning_15_cycles(opc_manager_subscribed): handler = MagicMock() opc_manager_subscribed.init_collector = MagicMock() opc_manager_subscribed.non_receive_count = 14 opc_manager_subscribed.collect_period = 1000 opc_manager_subscribed.period = 500 opc_manager_subscribed.nodes = tags opc_manager_subscribed.check_opc_listenning(handler) handler.assert_any_call( f'15 cycles without receive from OPC TestConnector. Tags: {json.dumps(tags)}', 'OPC_LISTENNING_STOPPED__TestConnector', NotificationLevel.ERROR ) handler.assert_any_call( 'Retrying to connect to server TestConnector', 'OPC_CONNECTION_RETRY__TestConnector', NotificationLevel.ERROR ) opc_manager_subscribed.init_collector.assert_called_once_with( tags, 1000, 500)