Phase 1-4: Audio stack, mixer engine, MIDI, and network API
Lint & Validate / lint (push) Has been cancelled
Lint & Validate / lint (push) Has been cancelled
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)
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
"""Tests for JSON schemas / Pydantic models."""
|
||||
|
||||
import json
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
|
||||
from src.network.schemas import (
|
||||
ChannelSchema,
|
||||
MasterBusSchema,
|
||||
MixerStateSchema,
|
||||
PluginSchema,
|
||||
TransportSchema,
|
||||
RoutingSchema,
|
||||
AuxBusSchema,
|
||||
SubgroupSchema,
|
||||
VCASchema,
|
||||
EQSchema,
|
||||
EQBandSchema,
|
||||
CompressorSchema,
|
||||
GateSchema,
|
||||
FXSendsSchema,
|
||||
ParameterUpdateSchema,
|
||||
ParameterUpdateBatchSchema,
|
||||
ParameterSetRequest,
|
||||
WSMessage,
|
||||
WSMessageType,
|
||||
MixerParameterSchema,
|
||||
ParameterCategoryEnum,
|
||||
ParameterTypeEnum,
|
||||
RouteNodeSchema,
|
||||
RoutingEdgeSchema,
|
||||
NodeTypeEnum,
|
||||
APIResponse,
|
||||
PluginParamMapSchema,
|
||||
)
|
||||
|
||||
|
||||
class TestChannelSchema:
|
||||
"""Tests for ChannelSchema."""
|
||||
|
||||
def test_default_channel(self):
|
||||
ch = ChannelSchema(channel=0)
|
||||
assert ch.channel == 0
|
||||
assert ch.volume_db == 0.0
|
||||
assert ch.pan == 0.0
|
||||
assert not ch.mute
|
||||
assert not ch.solo
|
||||
|
||||
def test_channel_with_eq(self):
|
||||
ch = ChannelSchema(
|
||||
channel=3,
|
||||
volume_db=-6.0,
|
||||
eq=EQSchema(
|
||||
enabled=True,
|
||||
low=EQBandSchema(freq_hz=80.0, gain_db=3.0, q=1.0),
|
||||
),
|
||||
)
|
||||
assert ch.eq.enabled
|
||||
assert ch.eq.low.freq_hz == 80.0
|
||||
assert ch.eq.low.gain_db == 3.0
|
||||
|
||||
def test_serialize_deserialize(self):
|
||||
ch = ChannelSchema(channel=5, volume_db=-3.0, pan=0.5)
|
||||
data = ch.model_dump()
|
||||
ch2 = ChannelSchema.model_validate(data)
|
||||
assert ch2.channel == 5
|
||||
assert ch2.volume_db == -3.0
|
||||
assert ch2.pan == 0.5
|
||||
|
||||
def test_json_roundtrip(self):
|
||||
ch = ChannelSchema(channel=0, mute=True)
|
||||
json_str = ch.model_dump_json()
|
||||
data = json.loads(json_str)
|
||||
assert data["channel"] == 0
|
||||
assert data["mute"] is True
|
||||
|
||||
|
||||
class TestMasterBusSchema:
|
||||
"""Tests for MasterBusSchema."""
|
||||
|
||||
def test_default_master(self):
|
||||
m = MasterBusSchema()
|
||||
assert m.volume_db == 0.0
|
||||
assert not m.muted
|
||||
assert not m.dim_active
|
||||
assert m.aux_buses == []
|
||||
|
||||
def test_with_buses(self):
|
||||
m = MasterBusSchema(
|
||||
volume_db=-10.0,
|
||||
aux_buses=[
|
||||
AuxBusSchema(index=0, label="Reverb"),
|
||||
AuxBusSchema(index=1, label="Delay"),
|
||||
],
|
||||
subgroups=[
|
||||
SubgroupSchema(index=0, label="Drums", members=[0, 1, 2]),
|
||||
],
|
||||
vca_groups=[
|
||||
VCASchema(index=0, label="All Vocals", master_db=-3.0, members=[3, 4, 5]),
|
||||
],
|
||||
)
|
||||
assert len(m.aux_buses) == 2
|
||||
assert m.aux_buses[0].label == "Reverb"
|
||||
assert len(m.subgroups) == 1
|
||||
assert m.subgroups[0].members == [0, 1, 2]
|
||||
assert m.vca_groups[0].master_db == -3.0
|
||||
|
||||
|
||||
class TestTransportSchema:
|
||||
"""Tests for TransportSchema."""
|
||||
|
||||
def test_defaults(self):
|
||||
t = TransportSchema()
|
||||
assert not t.playing
|
||||
assert not t.recording
|
||||
assert t.tempo_bpm == 120.0
|
||||
assert t.position_sec == 0.0
|
||||
|
||||
def test_playing_state(self):
|
||||
t = TransportSchema(playing=True, tempo_bpm=142.0, position_sec=35.2)
|
||||
assert t.playing
|
||||
assert not t.recording
|
||||
assert t.tempo_bpm == 142.0
|
||||
|
||||
|
||||
class TestMixerStateSchema:
|
||||
"""Tests for full MixerStateSchema."""
|
||||
|
||||
def test_empty_state(self):
|
||||
state = MixerStateSchema()
|
||||
assert state.channels == []
|
||||
assert state.master.volume_db == 0.0
|
||||
assert state.transport.tempo_bpm == 120.0
|
||||
|
||||
def test_full_state(self):
|
||||
state = MixerStateSchema(
|
||||
channels=[
|
||||
ChannelSchema(channel=0, volume_db=-6.0),
|
||||
ChannelSchema(channel=1, mute=True),
|
||||
],
|
||||
master=MasterBusSchema(volume_db=-3.0),
|
||||
transport=TransportSchema(playing=True),
|
||||
scenes=["Intro", "Verse", "Chorus"],
|
||||
uptime_seconds=3600.0,
|
||||
)
|
||||
assert len(state.channels) == 2
|
||||
assert state.scenes == ["Intro", "Verse", "Chorus"]
|
||||
assert state.uptime_seconds == 3600.0
|
||||
|
||||
def test_json_roundtrip(self):
|
||||
state = MixerStateSchema(
|
||||
channels=[ChannelSchema(channel=0)],
|
||||
uptime_seconds=10.0,
|
||||
)
|
||||
json_str = state.model_dump_json()
|
||||
data = json.loads(json_str)
|
||||
assert data["uptime_seconds"] == 10.0
|
||||
assert len(data["channels"]) == 1
|
||||
|
||||
|
||||
class TestParameterUpdate:
|
||||
"""Tests for parameter update schemas."""
|
||||
|
||||
def test_single_update(self):
|
||||
update = ParameterUpdateSchema(
|
||||
param_type=ParameterTypeEnum.VOLUME,
|
||||
value=-3.0,
|
||||
channel=2,
|
||||
)
|
||||
assert update.param_type == ParameterTypeEnum.VOLUME
|
||||
assert update.value == -3.0
|
||||
assert update.channel == 2
|
||||
|
||||
def test_batch_update(self):
|
||||
batch = ParameterUpdateBatchSchema(updates=[
|
||||
ParameterUpdateSchema(param_type=ParameterTypeEnum.VOLUME, value=-6.0, channel=0),
|
||||
ParameterUpdateSchema(param_type=ParameterTypeEnum.MUTE, value=1.0, channel=0),
|
||||
ParameterUpdateSchema(param_type=ParameterTypeEnum.MASTER_VOLUME, value=-3.0),
|
||||
])
|
||||
assert len(batch.updates) == 3
|
||||
assert batch.updates[2].param_type == ParameterTypeEnum.MASTER_VOLUME
|
||||
|
||||
|
||||
class TestWSMessage:
|
||||
"""Tests for WebSocket message schemas."""
|
||||
|
||||
def test_parameter_update_message(self):
|
||||
msg = WSMessage(
|
||||
type=WSMessageType.PARAMETER_UPDATE,
|
||||
payload={"param_type": "volume", "value": -3.0, "channel": 1},
|
||||
)
|
||||
assert msg.type == WSMessageType.PARAMETER_UPDATE
|
||||
assert msg.payload["param_type"] == "volume"
|
||||
|
||||
def test_full_state_message(self):
|
||||
msg = WSMessage(
|
||||
type=WSMessageType.FULL_STATE,
|
||||
payload={"channels": []},
|
||||
)
|
||||
data = msg.model_dump()
|
||||
assert data["type"] == "full_state"
|
||||
|
||||
def test_error_message(self):
|
||||
msg = WSMessage(
|
||||
type=WSMessageType.ERROR,
|
||||
payload={"message": "Something went wrong"},
|
||||
)
|
||||
assert msg.payload["message"] == "Something went wrong"
|
||||
|
||||
|
||||
class TestParameterSetRequest:
|
||||
"""Tests for ParameterSetRequest."""
|
||||
|
||||
def test_default_param_type(self):
|
||||
req = ParameterSetRequest(value=-3.0)
|
||||
assert req.param_type == "volume"
|
||||
assert req.value == -3.0
|
||||
|
||||
def test_explicit_param_type(self):
|
||||
req = ParameterSetRequest(param_type="mute", value=1.0)
|
||||
assert req.param_type == "mute"
|
||||
|
||||
def test_json_parse(self):
|
||||
json_str = '{"param_type": "pan", "value": 0.5}'
|
||||
req = ParameterSetRequest.model_validate_json(json_str)
|
||||
assert req.param_type == "pan"
|
||||
assert req.value == 0.5
|
||||
|
||||
|
||||
class TestPluginSchema:
|
||||
"""Tests for PluginSchema."""
|
||||
|
||||
def test_plugin(self):
|
||||
p = PluginSchema(
|
||||
plugin_id=1,
|
||||
name="Ch1 Gate",
|
||||
role="gate",
|
||||
channel=0,
|
||||
params=[
|
||||
PluginParamMapSchema(name="threshold", index=0, value=0.5),
|
||||
PluginParamMapSchema(name="ratio", index=1, value=0.3),
|
||||
],
|
||||
)
|
||||
assert p.plugin_id == 1
|
||||
assert len(p.params) == 2
|
||||
assert p.params[0].name == "threshold"
|
||||
|
||||
|
||||
class TestRoutingSchema:
|
||||
"""Tests for RoutingSchema."""
|
||||
|
||||
def test_empty_routing(self):
|
||||
r = RoutingSchema()
|
||||
assert r.nodes == []
|
||||
assert r.edges == []
|
||||
|
||||
def test_with_nodes_and_edges(self):
|
||||
r = RoutingSchema(
|
||||
nodes=[
|
||||
RouteNodeSchema(node_id="sys_in_1", type=NodeTypeEnum.SYSTEM_INPUT, jack_port="system:capture_1"),
|
||||
RouteNodeSchema(node_id="ch_0_input", type=NodeTypeEnum.CHANNEL_INPUT, channel=0),
|
||||
],
|
||||
edges=[
|
||||
RoutingEdgeSchema(source="sys_in_1", dest="ch_0_input"),
|
||||
],
|
||||
solo_nodes=["ch_0_input"],
|
||||
)
|
||||
assert len(r.nodes) == 2
|
||||
assert len(r.edges) == 1
|
||||
assert r.edges[0].source == "sys_in_1"
|
||||
assert r.solo_nodes == ["ch_0_input"]
|
||||
Reference in New Issue
Block a user