from datetime import datetime, timezone from queue import Queue from uuid import uuid4 from zipfile import BadZipFile import pytest from dynalab_core.buffer import ValueBuffer from dynalab_core.derive import DeriveRegistry, DeriveUnit from dynalab_core.dlpak import DLPak from dynalab_core.errors import ( CoreError, DLPakError, DeriveError, DynaLabError, PlaybackError, ) from dynalab_core.playback import PlaybackEngine from dynalab_core.protocols.packets.data import ValueDescriptor from dynalab_core.protocols.packets.handshake import SignalDescriptor def _identity( value: ValueDescriptor, output: SignalDescriptor ) -> ValueDescriptor: return ValueDescriptor( signal_id=output.id, value=value.value, timestamp=value.timestamp, ) def test_subsystem_errors_share_public_base_and_code() -> None: error = PlaybackError("Playback data is unavailable", code="no_data") assert isinstance(error, DynaLabError) assert error.code == "no_data" assert str(error) == "Playback data is unavailable" def test_core_rejects_invalid_mode_and_missing_recording() -> None: from dynalab_core import Core core = Core.__new__(Core) core._processing_buffer = None with pytest.raises(CoreError, match="Unknown core mode") as mode: core.set_mode("invalid") # type: ignore[arg-type] with pytest.raises(CoreError, match="no recording") as recording: core.write(".", "unused") assert mode.value.code == "invalid_mode" assert recording.value.code == "no_recording" def test_dlpak_requires_data_before_manifest() -> None: package = DLPak() with pytest.raises(DLPakError, match="before setting its data") as raised: package.set_manifest(datetime.now(timezone.utc), []) assert raised.value.code == "no_data" def test_dlpak_wraps_archive_read_failures(tmp_path) -> None: invalid_archive = tmp_path / "invalid.dlpak" invalid_archive.write_text("not a zip archive") with pytest.raises(DLPakError, match="Could not read") as raised: DLPak().read(invalid_archive) assert raised.value.code == "read_failed" assert isinstance(raised.value.__cause__, BadZipFile) def test_derive_unit_reports_argument_mismatch() -> None: input_signal = SignalDescriptor(id=uuid4(), name="Input", type="number") output_signal = SignalDescriptor( id=uuid4(), name="Output", type="number", origin="derived" ) unit = DeriveUnit(uuid4(), _identity, [input_signal], output_signal, Queue()) try: with pytest.raises(DeriveError, match="expected 1 input") as raised: unit.process_offline([]) finally: unit.stop() assert raised.value.code == "argument_mismatch" def test_derive_registry_wraps_invalid_source() -> None: registry = DeriveRegistry(Queue(), lambda signal_id: None) output_signal = SignalDescriptor( id=uuid4(), name="Output", type="number", origin="derived" ) try: with pytest.raises(DeriveError, match="Could not load") as raised: registry.register("def broken(", [], output_signal) finally: registry.stop() assert raised.value.code == "invalid_source" assert isinstance(raised.value.__cause__, SyntaxError) def test_derive_unit_rejects_configured_input_mismatch() -> None: output_signal = SignalDescriptor( id=uuid4(), name="Output", type="number", origin="derived" ) with pytest.raises(DeriveError, match="input signal") as raised: DeriveUnit(uuid4(), _identity, [], output_signal, Queue()) assert raised.value.code == "input_signal_mismatch" def test_playback_rejects_incomplete_data_and_negative_seek() -> None: engine = PlaybackEngine(Queue()) try: with pytest.raises(PlaybackError, match="incomplete") as incomplete: engine.set_data(DLPak()) with pytest.raises(PlaybackError, match="cannot be negative") as timestamp: engine.seek(-1) finally: engine.stop() assert incomplete.value.code == "incomplete_data" assert timestamp.value.code == "invalid_timestamp" def test_playback_accepts_complete_dlpak() -> None: signal = SignalDescriptor(id=uuid4(), name="Input", type="number") data = ValueBuffer() data.append(1, signal.id, 2.0) package = DLPak() package.set_data(data) package.set_manifest(datetime.now(timezone.utc), [signal]) engine = PlaybackEngine(Queue()) try: engine.set_data(package) finally: engine.stop()