Files
raspberry-pi-mixer/tests/test_mapping_store.py
T
shawn 9cd8292acc
Lint & Validate / lint (push) Has been cancelled
Phase 1-4: Audio stack, mixer engine, MIDI, and network API
P2-R1: ALSA + JACK2 low-latency config (scripts, quirks, tuning)
P2-R2: Carla integration (build scripts, 8ch rack config, NAM LV2 support)
P2-R3: Plugin manager, categories, blacklist, NAM model support
P3-R1: Mixer DSP engine (channel strip, routing matrix, bus mgr, automation)
P4-R1: MIDI engine (learn mode, clock sync, HID discovery, mapping store)
P4-R2: Network API (OSC server, FastAPI REST, WebSocket, auth, rate limiter)
P5-R1: Touchscreen UI evaluation + main entry point
docs: Audio stack, Carla integration, MIDI support, UI evaluation
tests: Full test suite (292 passing)
2026-05-19 20:39:17 -04:00

174 lines
5.3 KiB
Python

"""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