feat(gateway): gRPC server ReadTag (C4)
This commit is contained in:
96
tests/unit/adapters/test_grpc_server.py
Normal file
96
tests/unit/adapters/test_grpc_server.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import grpc
|
||||
import pytest
|
||||
|
||||
from ingestor.adapters.grpc_server import GrpcServer, ReadTagServicer
|
||||
from ingestor.proto.opc_gateway_pb2 import ReadTagRequest
|
||||
|
||||
|
||||
def make_request(server_id='srv1', addr='ns=2;i=5'):
|
||||
return ReadTagRequest(server_id=server_id, addr=addr, data_type='float')
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_tag_maps_full_result_onto_response():
|
||||
active_read = AsyncMock()
|
||||
active_read.read_now.return_value = {
|
||||
'value': 1.5,
|
||||
'opc_status_code': 'Good',
|
||||
'source_timestamp': '2026-07-15T10:00:00-03:00',
|
||||
'server_timestamp': '2026-07-15T10:00:01-03:00',
|
||||
'persisted': False,
|
||||
}
|
||||
servicer = ReadTagServicer(active_read, MagicMock())
|
||||
|
||||
response = await servicer.ReadTag(make_request(), AsyncMock())
|
||||
|
||||
assert response.value == '1.5'
|
||||
assert response.opc_status_code == 'Good'
|
||||
assert response.source_timestamp == '2026-07-15T10:00:00-03:00'
|
||||
assert response.server_timestamp == '2026-07-15T10:00:01-03:00'
|
||||
assert response.persisted is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_tag_none_timestamps_become_empty_string():
|
||||
active_read = AsyncMock()
|
||||
active_read.read_now.return_value = {
|
||||
'value': 1.5,
|
||||
'opc_status_code': None,
|
||||
'source_timestamp': None,
|
||||
'server_timestamp': None,
|
||||
'persisted': False,
|
||||
}
|
||||
servicer = ReadTagServicer(active_read, MagicMock())
|
||||
|
||||
response = await servicer.ReadTag(make_request(), AsyncMock())
|
||||
|
||||
assert response.opc_status_code == ''
|
||||
assert response.source_timestamp == ''
|
||||
assert response.server_timestamp == ''
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_tag_aborts_with_internal_on_engine_failure():
|
||||
active_read = AsyncMock()
|
||||
active_read.read_now.side_effect = RuntimeError('boom')
|
||||
logger = MagicMock()
|
||||
servicer = ReadTagServicer(active_read, logger)
|
||||
context = AsyncMock()
|
||||
context.abort.side_effect = grpc.RpcError('aborted')
|
||||
|
||||
with pytest.raises(grpc.RpcError):
|
||||
await servicer.ReadTag(make_request(), context)
|
||||
|
||||
context.abort.assert_awaited_once()
|
||||
args = context.abort.call_args.args
|
||||
assert args[0] == grpc.StatusCode.INTERNAL
|
||||
logger.error.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read_tag_uses_slot_lookup_names_when_provided():
|
||||
active_read = AsyncMock()
|
||||
active_read.read_now.return_value = {
|
||||
'value': 1,
|
||||
'opc_status_code': 'Good',
|
||||
'source_timestamp': None,
|
||||
'server_timestamp': None,
|
||||
'persisted': True,
|
||||
}
|
||||
names = [{'collection': 'c1', 'name': 't1'}]
|
||||
slot_lookup = MagicMock(return_value=names)
|
||||
servicer = ReadTagServicer(active_read, MagicMock(), slot_lookup=slot_lookup)
|
||||
|
||||
await servicer.ReadTag(make_request(server_id='srv1', addr='ns=2;i=5'), AsyncMock())
|
||||
|
||||
slot_lookup.assert_called_once_with('srv1', 'ns=2;i=5')
|
||||
active_read.read_now.assert_awaited_once_with('srv1', 'ns=2;i=5', names=names)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_grpc_server_stop_before_start_does_not_raise():
|
||||
server = GrpcServer(active_read=AsyncMock(), logger=MagicMock())
|
||||
|
||||
await server.stop() # no raise
|
||||
Reference in New Issue
Block a user