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