"""Tests del Log Book naval (Sprint 5).""" from __future__ import annotations import asyncio import pytest from vmssailor.core.enums import Protocol, UnitSI from vmssailor.core.tag import Tag from vmssailor.runtime.server.logbook import ( EngineLogConfig, EngineLogWriter, LogBook, LogEntryKind, SnapshotLogWriter, ) from vmssailor.runtime.server.tag_store import TagStore @pytest.mark.asyncio async def test_logbook_append_and_query(): lb = LogBook() e1 = await lb.append(LogEntryKind.MANUAL, "Primera entrada", user="op1") e2 = await lb.append(LogEntryKind.MANUAL, "Segunda entrada", user="op1") assert e1.id < e2.id assert e2.prev_hash == e1.hash entries = lb.query(limit=10) assert len(entries) == 2 @pytest.mark.asyncio async def test_logbook_chain_integrity(): lb = LogBook() for i in range(5): await lb.append(LogEntryKind.SNAPSHOT, f"Entry {i}") ok, broken = lb.verify_chain() assert ok assert broken == [] @pytest.mark.asyncio async def test_logbook_tamper_detection(): """Si modificamos a mano una entrada, la cadena debe detectarlo.""" lb = LogBook() await lb.append(LogEntryKind.MANUAL, "A") await lb.append(LogEntryKind.MANUAL, "B") # Tamper: cambiar el summary de la entrada 1 directo en SQLite lb._conn.execute("UPDATE logbook SET summary = 'TAMPERED' WHERE id = 1") lb._conn.commit() ok, broken = lb.verify_chain() assert not ok assert 1 in broken or 2 in broken @pytest.mark.asyncio async def test_engine_log_writer_detects_start_stop(): store = TagStore() store.register_tag(Tag(id="ME_PORT.RPM", unit_si=UnitSI.RPM, protocol=Protocol.MODBUS_RTU, address=1)) lb = LogBook() writer = EngineLogWriter(store, lb, config=EngineLogConfig(sustain_seconds=0.0)) await writer.start() try: # Arranque: pasar de quieto a corriendo await store.update("ME_PORT.RPM", 0.0) await asyncio.sleep(0.05) await store.update("ME_PORT.RPM", 1500.0) await asyncio.sleep(0.1) # Parada await store.update("ME_PORT.RPM", 0.0) await asyncio.sleep(0.1) starts = lb.query(kind=LogEntryKind.ENGINE_START) stops = lb.query(kind=LogEntryKind.ENGINE_STOP) assert len(starts) == 1 assert len(stops) == 1 assert "ME_PORT" in starts[0].summary finally: await writer.stop() @pytest.mark.asyncio async def test_snapshot_writer_takes_snapshot(): store = TagStore() store.register_tag(Tag(id="ME_PORT.RPM", unit_si=UnitSI.RPM, protocol=Protocol.MODBUS_RTU, address=1)) lb = LogBook() writer = SnapshotLogWriter(store, lb, period_s=0.1, require_running_engine=True) await writer.start() try: await store.update("ME_PORT.RPM", 1500.0) await asyncio.sleep(0.25) # Permite al menos un tick de 0.1s snapshots = lb.query(kind=LogEntryKind.SNAPSHOT) assert len(snapshots) >= 1 # Sin motor corriendo no debería tomar snapshot snap = snapshots[0] assert "tags" in snap.payload assert "ME_PORT.RPM" in snap.payload["tags"] finally: await writer.stop() @pytest.mark.asyncio async def test_snapshot_writer_skips_when_no_engine(): store = TagStore() store.register_tag(Tag(id="ME_PORT.RPM", unit_si=UnitSI.RPM, protocol=Protocol.MODBUS_RTU, address=1)) lb = LogBook() writer = SnapshotLogWriter(store, lb, period_s=0.1, require_running_engine=True) await writer.start() try: await store.update("ME_PORT.RPM", 0.0) await asyncio.sleep(0.25) snapshots = lb.query(kind=LogEntryKind.SNAPSHOT) assert len(snapshots) == 0 finally: await writer.stop()