"""E2E scenarios — 7. Pod lifecycle: shutdown, idle connections, startup failures. See ``e2e/scenarios.md``.""" import asyncio import pytest from e2e.helpers import ( Pod, insert_prediction, load_scenario_input, readings, stays_false, wait_until, write_status, ) from e2e.opc_server import free_port pytestmark = [pytest.mark.integration, pytest.mark.asyncio] TIMESTAMP = '2026-10-13T12:00:00-03:00' async def test_scenario_7_1_1_shutdown_closes_sessions_and_stops_consuming( opc_servers, pods, put_slot, mongo_db, postgres_engine, publish_command ): server = await opc_servers('plant-a', {'Temperature': 20.0, 'Setpoint': 0.0}) put_slot(1, load_scenario_input('slot_single_server.json', {'plant-a': server})) insert_prediction(postgres_engine, 2, TIMESTAMP, 'pending') pod = await pods('pod-a') await pod.tick() await wait_until(lambda: readings(mongo_db, 'raw_plant_a')) assert server.session_count() == 1 await pod.stop() await wait_until(lambda: server.session_count() == 0) await publish_command(load_scenario_input('write_command.json')) assert await stays_false(lambda: write_status(postgres_engine, 2, TIMESTAMP) != 'pending') await server.set('Temperature', 50.0) assert await stays_false(lambda: readings(mongo_db, 'raw_plant_a', value=50.0)) async def test_scenario_7_1_2_idle_write_connection_is_closed_and_reopened( opc_servers, pods, put_slot, mongo_db, postgres_engine, publish_command ): reader = await opc_servers('plant-a', {'Temperature': 20.0}) writer = await opc_servers('plant-b', {'Setpoint': 0.0}) put_slot(1, load_scenario_input('slot_single_server.json', {'plant-a': reader})) connector = load_scenario_input('connector_plant_a.json', {'plant-a': writer}) mongo_db['connectors'].insert_one({**connector, 'connector_id': '2', 'name': 'plant-b'}) insert_prediction(postgres_engine, 2, TIMESTAMP, 'pending') pod = await pods('pod-a', IDLE_CONN_MAX_S='1') await pod.tick() await publish_command(load_scenario_input('write_command.json', server_id='2')) await wait_until(lambda: write_status(postgres_engine, 2, TIMESTAMP) == 'ok') assert writer.session_count() == 1 await asyncio.sleep(1.5) await pod.tick() # idle_sweep await wait_until(lambda: writer.session_count() == 0) assert reader.session_count() == 1 # subscribed: pinned, never idle insert_prediction(postgres_engine, 3, TIMESTAMP, 'pending') await publish_command(load_scenario_input('write_command.json', server_id='2', model_id='3')) await wait_until(lambda: write_status(postgres_engine, 3, TIMESTAMP) == 'ok') assert writer.session_count() == 1 async def test_scenario_7_2_1_redis_unreachable_fails_the_startup(monkeypatch): monkeypatch.setenv('REDIS_PORT', str(free_port())) with pytest.raises(Exception, match='(?i)connect'): await Pod('pod-a').start() async def test_scenario_7_2_2_restarted_pod_takes_its_slot_back( opc_servers, pods, put_slot, mongo_db, redis_client ): server = await opc_servers('plant-a', {'Temperature': 20.0}) put_slot(1, load_scenario_input('slot_single_server.json', {'plant-a': server})) pod = await pods('pod-a', LEASE_TTL='2', HEARTBEAT_TTL='2') await pod.tick() await pod.stop() await wait_until(lambda: redis_client.get('lease:opc_tags:1') is None, timeout=5) restarted = await pods('pod-a', LEASE_TTL='2', HEARTBEAT_TTL='2') await restarted.tick() assert restarted.slots == {'1'} await server.set('Temperature', 31.0) await wait_until(lambda: readings(mongo_db, 'raw_plant_a', value=31.0))