SIENTIAPDE-1975: align the gateway write path with the Laborious
- GW1: write commands (C2) carry only the server_id; ServerConfigResolver resolves the connection (command override, live pooled connection, connectors, opc-servers) with a 60 s cache and a 10 s negative cache. - GW2: write_status keeps the worst outcome (ok < error < session_bad, pending/NULL replaced), same rule as the Laborious, built with SQLAlchemy Core (no SQL formatting). - GW3: update_write_status never raises; outcome (updated, kept, row_not_found, invalid, error) logged and counted (opc_write_status_update_total); runs off the event loop. - GW4: the reason of a failed write is logged; opc_write_status_by_model shows the last outcome; a bad timestamp is a failed write, not an exception. - GW5: the NATS first connect gives up after 5 s (nats-py's defaults block the main loop for about 4 minutes while NATS is down); reconnection after it is unbounded; idle_sweep restarts a connection closed for good; stop() is safe on a closed connection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,7 @@ def mocks(monkeypatch):
|
||||
patched['pool'].release_idle = AsyncMock()
|
||||
patched['NatsWriteConsumer'].return_value.start = AsyncMock()
|
||||
patched['NatsWriteConsumer'].return_value.stop = AsyncMock()
|
||||
patched['NatsWriteConsumer'].return_value.closed = False
|
||||
patched['GrpcServer'].return_value.start = AsyncMock()
|
||||
patched['GrpcServer'].return_value.stop = AsyncMock()
|
||||
return patched
|
||||
@@ -173,3 +174,47 @@ async def test_idle_sweep_releases_idle_connections(mocks):
|
||||
await runtime.idle_sweep()
|
||||
|
||||
mocks['pool'].release_idle.assert_awaited_once_with(42.0)
|
||||
|
||||
|
||||
def test_write_engine_resolves_servers_from_the_pool_and_mongo(mocks):
|
||||
"""SIENTIAPDE-1975 GW1: write commands carry only the server_id."""
|
||||
data_manager = MagicMock()
|
||||
runtime = gr.GatewayRuntime(
|
||||
config=GatewayConfig(),
|
||||
data_manager=data_manager,
|
||||
pool=mocks['pool'],
|
||||
logger=MagicMock(),
|
||||
notification_handler=MagicMock(),
|
||||
metrics_controller=MagicMock(),
|
||||
)
|
||||
|
||||
_, kwargs = mocks['WriteEngine'].call_args
|
||||
assert kwargs['servers'] is runtime.servers
|
||||
assert runtime.servers.pool is mocks['pool']
|
||||
assert runtime.servers.mongo is data_manager.mongo_repository
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_idle_sweep_restarts_a_closed_nats_connection(mocks):
|
||||
runtime = make_runtime(pool=mocks['pool'])
|
||||
nats_mock = mocks['NatsWriteConsumer'].return_value
|
||||
await runtime.start()
|
||||
assert nats_mock.start.await_count == 1
|
||||
|
||||
nats_mock.closed = True # closed for good (e.g. by the server)
|
||||
await runtime.idle_sweep()
|
||||
|
||||
assert nats_mock.start.await_count == 2
|
||||
assert runtime._nats_started is True
|
||||
runtime.logger.warning.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_idle_sweep_leaves_a_live_nats_connection_alone(mocks):
|
||||
runtime = make_runtime(pool=mocks['pool'])
|
||||
nats_mock = mocks['NatsWriteConsumer'].return_value
|
||||
await runtime.start()
|
||||
|
||||
await runtime.idle_sweep()
|
||||
|
||||
assert nats_mock.start.await_count == 1
|
||||
|
||||
Reference in New Issue
Block a user