from ingestor.io.slots import parse_slot RAW = { 'server1': { 'server_id': '1', 'url': 'opc.tcp://h:4840', 'use_security': True, 'subscription_period_ms': 1000, 'tags': { 'ns=2;i=2': { 'frequency': '15000', 'active_read': {'enabled': True, 'cycle_multiple': 3}, 'names': [ { 'collection': 'raw_x', 'tag_name': 'Counter', 'model_name': 'm1', 'model_id': '1', }, { 'collection': 'raw_y', 'tag_name': 'Contador', 'model_name': 'm2', 'model_id': '2', }, ], } }, } } def test_parse_slot_multi_name(): slot = parse_slot(RAW) tag = slot['server1']['tags']['ns=2;i=2'] assert len(tag['names']) == 2 assert tag['active_read']['cycle_multiple'] == 3 def test_parse_slot_defaults_active_read_disabled(): raw = { 's': { 'server_id': '1', 'url': 'u', 'tags': { 'ns=2;i=1': { 'frequency': '1000', 'names': [ {'collection': 'c', 'tag_name': 't', 'model_name': 'm', 'model_id': '1'} ], } }, } } tag = parse_slot(raw)['s']['tags']['ns=2;i=1'] assert tag['active_read'] == {'enabled': False} def test_parse_slot_rejects_tag_without_names(): raw = {'s': {'server_id': '1', 'url': 'u', 'tags': {'ns=2;i=1': {'frequency': '1000'}}}} import pytest with pytest.raises((KeyError, ValueError)): parse_slot(raw) RAW_V1 = { 'kepserver': { 'name': 'kepserver', 'server_id': '1', 'url': 'opc.tcp://kepserver:49320', 'server_uri': 'urn:sientia:opc-ingestor', 'cert_path': '/app/certs/opc-ingestor-cert.pem', 'private_key_path': '/app/certs/opc-ingestor-key.pem', 'server_cert_path': None, 'subscription_period_ms': 15000.0, 'tags': { 'ns=2;s=A.TargetSin': { 'aggr_func': 'lts', 'frequency': 30000, 'server_id': '1', 'tag_address': 'ns=2;s=A.TargetSin', 'tag_name': 'TargetSin', 'topics': ['raw_scouter-sin-approx', 'raw_other-model'], } }, } } def test_parse_slot_converts_v1_topics_to_names(): """Slot v1 (current Orchestrator) is read as v2: one name per topic, no model_id, so the documents stay identical to the legacy ones (SIENTIAPDE-1974 G1).""" server = parse_slot(RAW_V1)['kepserver'] tag = server['tags']['ns=2;s=A.TargetSin'] assert tag['names'] == [ {'collection': 'raw_scouter-sin-approx', 'tag_name': 'TargetSin'}, {'collection': 'raw_other-model', 'tag_name': 'TargetSin'}, ] assert tag['frequency'] == 30000 assert tag['active_read'] == {'enabled': False} def test_parse_slot_v1_cert_path_means_security_but_path_is_dropped(): server = parse_slot(RAW_V1)['kepserver'] assert server['use_security'] is True assert server['name'] == 'kepserver' assert server['server_id'] == '1' assert server['subscription_period_ms'] == 15000.0 # the certificate always comes from MinIO now: no file path survives the parse assert 'cert_path' not in server assert 'private_key_path' not in server def test_parse_slot_v1_without_cert_path_is_insecure(): raw = { 's': { 'server_id': '2', 'url': 'u', 'cert_path': None, 'tags': {'ns=2;i=1': {'tag_name': 't', 'frequency': '1000', 'topics': ['raw_c']}}, } } server = parse_slot(raw)['s'] assert server['use_security'] is False assert server['name'] == 's' # falls back to the slot key def test_parse_slot_v2_explicit_use_security_wins(): raw = { 's': { 'server_id': '1', 'url': 'u', 'use_security': False, 'cert_path': '/legacy/path', 'tags': { 'ns=2;i=1': { 'frequency': '1000', 'names': [{'collection': 'c', 'tag_name': 't', 'model_id': '1'}], } }, } } assert parse_slot(raw)['s']['use_security'] is False def test_parse_slot_rejects_v1_tag_without_tag_name(): raw = { 's': { 'server_id': '1', 'url': 'u', 'tags': {'ns=2;i=1': {'frequency': '1', 'topics': ['c']}}, } } import pytest with pytest.raises(ValueError): parse_slot(raw)