1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
"""Protocol-level tests for compose_mml multimedia results."""
import asyncio
import base64
import json
from unittest.mock import patch
from fastmcp import Client
from mcp.types import AudioContent, ResourceLink, TextContent
from mml_composemusic_mcp.server import mcp
VALID_MML = """A t120 l4 o4 v15 q2
c e g c
"""
async def _call_tool(arguments, *, raise_on_error=True):
async with Client(mcp) as client:
return await client.call_tool(
"compose_mml",
arguments,
raise_on_error=raise_on_error,
)
def test_compose_returns_audio_content_without_exposing_path(tmp_output_dir):
result = asyncio.run(
_call_tool({"action": "compose", "mml": VALID_MML, "mode": "ppmck"})
)
assert result.is_error is False
assert result.structured_content["success"] is True
assert "wav_path" not in result.structured_content
assert result.structured_content["audio"] == {
"mime_type": "audio/wav",
"size_bytes": result.structured_content["audio"]["size_bytes"],
"sample_rate": 44100,
}
audio_blocks = [block for block in result.content if isinstance(block, AudioContent)]
assert len(audio_blocks) == 1
assert audio_blocks[0].mimeType == "audio/wav"
decoded = base64.b64decode(audio_blocks[0].data, validate=True)
assert len(decoded) > 100_000
wav_files = list(tmp_output_dir.rglob("output.wav"))
assert len(wav_files) == 1
assert decoded == wav_files[0].read_bytes()
assert result.structured_content["audio"]["size_bytes"] == len(decoded)
assert not any(isinstance(block, ResourceLink) for block in result.content)
text_blocks = [block for block in result.content if isinstance(block, TextContent)]
assert len(text_blocks) == 1
assert json.loads(text_blocks[0].text) == result.structured_content
assert "wav_path" not in text_blocks[0].text
def test_compose_failure_is_mcp_tool_error_without_audio(tmp_output_dir):
result = asyncio.run(
_call_tool(
{"action": "compose", "mml": "A t0 c", "mode": "ppmck"},
raise_on_error=False,
)
)
assert result.is_error is True
assert result.structured_content["success"] is False
assert result.structured_content["audio"] is None
assert not any(isinstance(block, AudioContent) for block in result.content)
assert not list(tmp_output_dir.rglob("output.wav"))
def test_invalid_validate_result_is_not_mcp_tool_error():
result = asyncio.run(
_call_tool({"action": "validate", "mml": "A t0 c", "mode": "ppmck"})
)
assert result.is_error is False
assert result.structured_content["valid"] is False
assert not any(isinstance(block, AudioContent) for block in result.content)
def test_invalid_validate_mode_is_mcp_tool_error():
result = asyncio.run(
_call_tool(
{"action": "validate", "mml": VALID_MML, "mode": "unknown"},
raise_on_error=False,
)
)
assert result.is_error is True
assert result.structured_content["valid"] is False
def test_unknown_action_is_mcp_tool_error():
result = asyncio.run(
_call_tool({"action": "unknown"}, raise_on_error=False)
)
assert result.is_error is True
assert result.structured_content["success"] is False
def test_audio_serialization_failure_is_sanitized_tool_error(tmp_output_dir):
with patch(
"mml_composemusic_mcp.server.Audio.to_audio_content",
side_effect=OSError("C:\\private\\output.wav"),
):
result = asyncio.run(
_call_tool(
{"action": "compose", "mml": VALID_MML, "mode": "ppmck"},
raise_on_error=False,
)
)
assert result.is_error is True
assert result.structured_content["success"] is False
assert result.structured_content["audio"] is None
assert "C:\\private" not in json.dumps(result.structured_content)
assert not any(isinstance(block, AudioContent) for block in result.content)
def test_unexpected_execution_failure_is_sanitized_tool_error():
with patch(
"mml_composemusic_mcp.server._execute_compose_mml",
side_effect=OSError("C:\\private\\source.mml"),
):
result = asyncio.run(
_call_tool(
{"action": "compose", "mml": VALID_MML, "mode": "ppmck"},
raise_on_error=False,
)
)
assert result.is_error is True
assert result.structured_content["success"] is False
assert "C:\\private" not in json.dumps(result.structured_content)
|