"""Tests del Alarm Engine (Sprint 4).""" from __future__ import annotations import asyncio import pytest from vmssailor.core.alarm import Alarm from vmssailor.core.enums import AlarmPriority, AlarmState, Protocol, UnitSI from vmssailor.core.tag import AlarmConfig, Tag from vmssailor.runtime.server.alarm_engine import AlarmEngine from vmssailor.runtime.server.tag_store import TagStore def _tag_with_alarm(low_threshold: float = 1.5, delay_s: float = 0.0) -> Tag: return Tag( id="X.PRESS", unit_si=UnitSI.BAR, protocol=Protocol.MODBUS_RTU, address=1, alarms=[ AlarmConfig( id="X.PRESS.LOW", threshold=low_threshold, operator="<", priority=AlarmPriority.EMERGENCY, hysteresis=0.2, delay_seconds=delay_s, ) ], ) @pytest.mark.asyncio async def test_alarm_fires_when_below_threshold(): store = TagStore() store.register_tag(_tag_with_alarm(low_threshold=1.5)) events: list[Alarm] = [] engine = AlarmEngine(store, on_alarm_event=events.append) await engine.start() try: await store.update("X.PRESS", 1.0) # debajo del threshold await asyncio.sleep(0.05) assert len(events) == 1 assert events[0].state == AlarmState.ACTIVE assert events[0].priority == AlarmPriority.EMERGENCY finally: await engine.stop() @pytest.mark.asyncio async def test_alarm_clears_with_hysteresis(): store = TagStore() store.register_tag(_tag_with_alarm(low_threshold=1.5)) events: list[Alarm] = [] engine = AlarmEngine(store, on_alarm_event=events.append) await engine.start() try: await store.update("X.PRESS", 1.0) await asyncio.sleep(0.05) # Subiendo a 1.6 NO debería limpiar (dentro de la hysteresis +0.2 = 1.7) await store.update("X.PRESS", 1.6) await asyncio.sleep(0.05) # Subiendo a 2.0 SÍ limpia await store.update("X.PRESS", 2.0) await asyncio.sleep(0.05) states = [e.state for e in events] assert AlarmState.ACTIVE in states assert AlarmState.CLEARED in states finally: await engine.stop() @pytest.mark.asyncio async def test_alarm_ack(): store = TagStore() store.register_tag(_tag_with_alarm(low_threshold=1.5)) events: list[Alarm] = [] engine = AlarmEngine(store, on_alarm_event=events.append) await engine.start() try: await store.update("X.PRESS", 1.0) await asyncio.sleep(0.05) active = engine.active_alarms() assert len(active) == 1 ack = engine.ack(active[0].id, user="operator1") assert ack is not None assert ack.state == AlarmState.ACK assert ack.acknowledged_by == "operator1" finally: await engine.stop()