"""Tests for mapping persistence.""" import json import tempfile from pathlib import Path import pytest from src.midi.types import MIDIMapping, MIDIMessageType, ParameterType from src.midi.mapping_store import ( mapping_to_dict, mapping_from_dict, save_mappings, load_mappings, list_sessions, delete_session, DEFAULT_MAPPINGS_DIR, DEFAULT_SESSION_NAME, ) class TestSerialisation: def test_roundtrip_cc_mapping(self): original = MIDIMapping( msg_type=MIDIMessageType.CONTROL_CHANGE, channel=0, controller=7, param_type=ParameterType.VOLUME, param_channel=0, invert=True, curve="logarithmic", label="CH0 Vol", ) d = mapping_to_dict(original) restored = mapping_from_dict(d) assert restored.msg_type == original.msg_type assert restored.channel == original.channel assert restored.controller == original.controller assert restored.param_type == original.param_type assert restored.param_channel == original.param_channel assert restored.invert == original.invert assert restored.curve == original.curve assert restored.label == original.label def test_roundtrip_nrpn_mapping(self): original = MIDIMapping( msg_type=MIDIMessageType.CONTROL_CHANGE, channel=1, is_nrpn=True, nrpn_number=258, param_type=ParameterType.COMP_THRESHOLD, param_channel=3, ) d = mapping_to_dict(original) restored = mapping_from_dict(d) assert restored.is_nrpn assert restored.nrpn_number == 258 def test_roundtrip_with_device(self): original = MIDIMapping( controller=10, source_device="X-Touch", ) d = mapping_to_dict(original) restored = mapping_from_dict(d) assert restored.source_device == "X-Touch" def test_roundtrip_disabled(self): original = MIDIMapping(enabled=False) d = mapping_to_dict(original) restored = mapping_from_dict(d) assert not restored.enabled def test_dict_format_has_expected_keys(self): m = MIDIMapping() d = mapping_to_dict(m) assert "midi" in d assert "target" in d assert "value" in d assert "meta" in d assert d["midi"]["type"] == "CONTROL_CHANGE" assert d["target"]["parameter"] == "volume" class TestFileIO: @pytest.fixture(autouse=True) def temp_dir(self, monkeypatch): """Redirect mappings to a temp directory.""" td = tempfile.mkdtemp(prefix="rpi-mixer-test-") monkeypatch.setattr( "src.midi.mapping_store.DEFAULT_MAPPINGS_DIR", Path(td), ) yield td # Cleanup import shutil shutil.rmtree(td, ignore_errors=True) def test_save_and_load(self): mappings = [ MIDIMapping( msg_type=MIDIMessageType.CONTROL_CHANGE, channel=0, controller=7, param_type=ParameterType.VOLUME, param_channel=0, label="CH0 Vol", ), MIDIMapping( msg_type=MIDIMessageType.CONTROL_CHANGE, channel=0, controller=10, param_type=ParameterType.PAN, param_channel=0, label="CH0 Pan", ), ] path = save_mappings(mappings, DEFAULT_SESSION_NAME) assert path.exists() loaded = load_mappings(DEFAULT_SESSION_NAME) assert len(loaded) == 2 assert loaded[0].controller == 7 assert loaded[1].controller == 10 assert loaded[0].label == "CH0 Vol" assert loaded[1].label == "CH0 Pan" def test_missing_session_returns_empty(self): loaded = load_mappings("nonexistent-session") assert loaded == [] def test_list_sessions(self, temp_dir): save_mappings([MIDIMapping()], "session-a") save_mappings([MIDIMapping()], "session-b") sessions = list_sessions() assert "session-a" in sessions assert "session-b" in sessions def test_delete_session(self): save_mappings([MIDIMapping()], "to-delete") assert delete_session("to-delete") assert load_mappings("to-delete") == [] def test_delete_nonexistent_session(self): assert not delete_session("never-existed") def test_save_empty_mappings(self): path = save_mappings([], DEFAULT_SESSION_NAME) assert path.exists() loaded = load_mappings(DEFAULT_SESSION_NAME) assert loaded == [] def test_json_is_valid(self): """Saved JSON should be parseable by standard json module.""" mappings = [ MIDIMapping(controller=7, param_type=ParameterType.VOLUME, param_channel=0), MIDIMapping(controller=10, param_type=ParameterType.PAN, param_channel=1, invert=True), ] path = save_mappings(mappings, DEFAULT_SESSION_NAME) with open(path) as fh: doc = json.load(fh) assert doc["version"] == 1 assert len(doc["mappings"]) == 2 assert doc["mappings"][0]["target"]["parameter"] == "volume" assert doc["mappings"][1]["value"]["invert"] is True