126 lines
4.2 KiB
Python
126 lines
4.2 KiB
Python
"""
|
|
Integration tests for FL Studio MCP server.
|
|
|
|
Tests nibble encode/decode roundtrip, command encoding, and tool registration.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import sys
|
|
|
|
sys.path.insert(0, "C:\\Users\\Administrator\\Documents\\fl_control\\mcp")
|
|
|
|
from protocol.sysex import nibble_encode, nibble_decode, encode_command, decode_command, SYSEX_ID
|
|
from protocol.transport import MidiTransport
|
|
|
|
|
|
def test_nibble_encode_decode_roundtrip():
|
|
"""Test that nibble_encode and nibble_decode are perfect inverses."""
|
|
test_cases = [
|
|
b"Hello",
|
|
b"",
|
|
b"\x00\x7f\x80\xff",
|
|
b'{"cmd":"ping","params":{"ts":1}}',
|
|
b"\xff\xfe\xfd\xfc\xfb",
|
|
b"A" * 256, # stress test
|
|
]
|
|
for original in test_cases:
|
|
encoded = nibble_encode(original)
|
|
# Each byte becomes 2 nibbles
|
|
assert len(encoded) == len(original) * 2, f"Length mismatch for {original!r}"
|
|
decoded = nibble_decode(encoded)
|
|
assert decoded == original, f"Roundtrip failed for {original!r}: got {decoded!r}"
|
|
print("PASS: nibble_encode/decode roundtrip")
|
|
|
|
|
|
def test_encode_command_format():
|
|
"""Test that encode_command produces valid SysEx format."""
|
|
# Test 1: Basic command
|
|
result = encode_command("ping", {"ts": 1})
|
|
assert result[0] == 0xF0, "Must start with F0"
|
|
assert result[1] == SYSEX_ID, "Must have SYSEX_ID=0x7D"
|
|
assert result[-1] == 0xF7, "Must end with F7"
|
|
print("PASS: encode_command format (basic)")
|
|
|
|
# Test 2: Empty params
|
|
result2 = encode_command("play")
|
|
assert result2[0] == 0xF0
|
|
assert result2[1] == SYSEX_ID
|
|
assert result2[-1] == 0xF7
|
|
print("PASS: encode_command format (no params)")
|
|
|
|
# Test 3: Command decodes back to original JSON
|
|
original_cmd = "set_tempo"
|
|
original_params = {"tempo": 140.0}
|
|
encoded = encode_command(original_cmd, original_params)
|
|
decoded = decode_command(encoded)
|
|
assert decoded is not None
|
|
assert decoded["cmd"] == original_cmd
|
|
assert decoded["params"] == original_params
|
|
print("PASS: encode_command → decode_command roundtrip")
|
|
|
|
|
|
def test_decode_command_invalid():
|
|
"""Test that decode_command returns None for invalid input."""
|
|
assert decode_command([]) is None
|
|
assert decode_command([0xF0]) is None # too short
|
|
assert decode_command([0xF0, 0x7D]) is None # too short
|
|
assert decode_command([0xF0, 0x00, 0xF7]) is None # wrong ID
|
|
assert decode_command([0xF0, 0x7D, 0xF7]) is None # empty payload
|
|
print("PASS: decode_command rejects invalid input")
|
|
|
|
|
|
def test_sysex_id():
|
|
"""Verify SYSEX_ID is the correct non-commercial experimental ID."""
|
|
assert SYSEX_ID == 0x7D
|
|
print("PASS: SYSEX_ID == 0x7D")
|
|
|
|
|
|
def test_miditransport_list_ports():
|
|
"""Test that MidiTransport.list_ports() works without crashing."""
|
|
ports = MidiTransport.list_ports()
|
|
assert "inputs" in ports
|
|
assert "outputs" in ports
|
|
assert isinstance(ports["inputs"], list)
|
|
assert isinstance(ports["outputs"], list)
|
|
print(f"PASS: list_ports — inputs={ports['inputs']}, outputs={ports['outputs']}")
|
|
|
|
|
|
def test_sysex_protocol_complete_roundtrip():
|
|
"""Full end-to-end: encode → send模拟 → receive → decode."""
|
|
# Simulate a complete conversation
|
|
commands = [
|
|
("ping", {"ts": 1}),
|
|
("set_tempo", {"tempo": 92.0}),
|
|
("select_channel", {"channel_index": 3}),
|
|
("note_on", {"channel_index": 0, "note": 60, "velocity": 100}),
|
|
("stop_all_notes", {}),
|
|
]
|
|
for cmd, params in commands:
|
|
encoded = encode_command(cmd, params)
|
|
# Verify format
|
|
assert encoded[0] == 0xF0
|
|
assert encoded[1] == SYSEX_ID
|
|
assert encoded[-1] == 0xF7
|
|
# Verify decode
|
|
decoded = decode_command(encoded)
|
|
assert decoded is not None
|
|
assert decoded["cmd"] == cmd
|
|
assert decoded["params"] == params
|
|
print("PASS: complete command roundtrip")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("FL Studio MCP — Integration Tests")
|
|
print("==================================\n")
|
|
|
|
test_sysex_id()
|
|
test_nibble_encode_decode_roundtrip()
|
|
test_encode_command_format()
|
|
test_decode_command_invalid()
|
|
test_miditransport_list_ports()
|
|
test_sysex_protocol_complete_roundtrip()
|
|
|
|
print("\nAll tests passed!")
|