summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-07-11 19:57:02 +0900
committertanaka takashi <isazaka@gmail.com>2026-07-11 19:57:02 +0900
commite16782c208320138d77b88f2ff359e3e9e059e76 (patch)
tree5e19fde4fa841b9b3f2ddef9f5efdb342ba6e911 /tests
parentc2e5098d030fe902eff258e10d2fefc01226e4bf (diff)
fix: レビュー指摘事項の修正(エラー戻り値統一、タイ延長、ネストリピート、専用エラーコード)
High severity: - compose action エラー戻り値の validation キー統一(server.py) - ppmck/pyxel タイ & の duration 延長修正(_tie_pending フラグ導入) - pyxel ネストリピート対応(RepeatFrame + イベントスナップショット方式) Medium severity: - 専用エラーコード追加: VALIDATION_MISSING_PARAMETER, VALIDATION_INVALID_MODE, VALIDATION_INVALID_ACTION - SYSTEM_INTERNAL_ERROR キャッチ範囲拡大(compose/validate全体をtry/except) Low severity: - 設計書 line/column を1始まりに明記、APIレベルエラーコード追記 テスト: 129 passed, ruff: All checks passed
Diffstat (limited to 'tests')
-rw-r--r--tests/test_compose_mml_tool.py8
-rw-r--r--tests/test_error_codes.py41
-rw-r--r--tests/test_parser_detailed.py37
3 files changed, 80 insertions, 6 deletions
diff --git a/tests/test_compose_mml_tool.py b/tests/test_compose_mml_tool.py
index af1e3e2..eb1b093 100644
--- a/tests/test_compose_mml_tool.py
+++ b/tests/test_compose_mml_tool.py
@@ -85,13 +85,13 @@ def test_template_invalid_fallback(compose_mml):
def test_compose_missing_mml(compose_mml):
result = compose_mml(action="compose", mml="", mode="ppmck")
assert result["success"] is False
- assert any(e["severity"] == "error" for e in result["errors"])
+ assert any(e["severity"] == "error" for e in result["validation"]["errors"])
def test_compose_missing_mode(compose_mml):
result = compose_mml(action="compose", mml="A c", mode="")
assert result["success"] is False
- assert any(e["severity"] == "error" for e in result["errors"])
+ assert any(e["severity"] == "error" for e in result["validation"]["errors"])
def test_validate_missing_mode(compose_mml):
@@ -103,7 +103,7 @@ def test_compose_unknown_mode(compose_mml):
result = compose_mml(action="compose", mml="A c", mode="xyz")
assert result["success"] is False
assert any(
- e["code"] == "SYNTAX_INVALID_TOKEN" for e in result["validation"]["errors"]
+ e["code"] == "VALIDATION_INVALID_MODE" for e in result["validation"]["errors"]
)
@@ -127,7 +127,7 @@ def test_compose_with_warning(compose_mml, tmp_output_dir):
def test_action_invalid(compose_mml):
result = compose_mml(action="unknown")
assert result["success"] is False
- assert any("action" in e["message"] for e in result["errors"])
+ assert any("action" in e["message"] for e in result["validation"]["errors"])
def test_compose_sample_rates(compose_mml, tmp_output_dir):
diff --git a/tests/test_error_codes.py b/tests/test_error_codes.py
index 19b4575..496ce84 100644
--- a/tests/test_error_codes.py
+++ b/tests/test_error_codes.py
@@ -17,14 +17,16 @@ def _find_code(result, code, key="errors"):
def _has_error(result):
- return any(e["severity"] == "error" for e in result["validation"]["errors"])
+ if "validation" in result:
+ return any(e["severity"] == "error" for e in result["validation"]["errors"])
+ return any(e["severity"] == "error" for e in result["errors"])
# --- SYNTAX_INVALID_TOKEN ---
def test_syntax_invalid_token_mode(compose_mml):
result = compose_mml(action="compose", mml="A c", mode="xyz")
assert result["success"] is False
- assert _find_code(result, ErrorCode.SYNTAX_INVALID_TOKEN.value)
+ assert _find_code(result, ErrorCode.VALIDATION_INVALID_MODE.value)
# --- SYNTAX_INVALID_NUMBER ---
@@ -201,6 +203,27 @@ def test_syntax_unterminated_header(compose_mml):
assert _find_code(result, ErrorCode.SYNTAX_UNTERMINATED_HEADER.value)
+# --- VALIDATION_MISSING_PARAMETER ---
+def test_validation_missing_parameter(compose_mml):
+ result = compose_mml(action="compose", mml="", mode="ppmck")
+ assert result["success"] is False
+ assert _find_code(result, ErrorCode.VALIDATION_MISSING_PARAMETER.value)
+
+
+# --- VALIDATION_INVALID_MODE ---
+def test_validation_invalid_mode(compose_mml):
+ result = compose_mml(action="compose", mml="A c", mode="xyz")
+ assert result["success"] is False
+ assert _find_code(result, ErrorCode.VALIDATION_INVALID_MODE.value)
+
+
+# --- VALIDATION_INVALID_ACTION ---
+def test_validation_invalid_action(compose_mml):
+ result = compose_mml(action="unknown")
+ assert result["success"] is False
+ assert _find_code(result, ErrorCode.VALIDATION_INVALID_ACTION.value)
+
+
# --- SYSTEM_SYNTHESIS_FAILED ---
def test_system_synthesis_failed(compose_mml):
with patch(
@@ -234,6 +257,20 @@ def test_system_wav_write_failed(tmp_path):
# --- SYSTEM_INTERNAL_ERROR ---
+def test_system_internal_error(compose_mml):
+ with patch(
+ "mml_composemusic_mcp.server._parse_mml",
+ side_effect=RuntimeError("unexpected internal error"),
+ ):
+ result = compose_mml(
+ action="compose",
+ mml="A t120 l4 o4\n c",
+ mode="ppmck",
+ )
+ assert result["success"] is False
+ assert _find_code(result, ErrorCode.SYSTEM_INTERNAL_ERROR.value)
+
+
def test_system_internal_error_not_exposed(compose_mml):
result = compose_mml(action="compose", mml="A t120 l4 o4\n c", mode="ppmck")
assert isinstance(result, dict)
diff --git a/tests/test_parser_detailed.py b/tests/test_parser_detailed.py
index 88d7095..3752562 100644
--- a/tests/test_parser_detailed.py
+++ b/tests/test_parser_detailed.py
@@ -143,6 +143,28 @@ def test_pyxel_repeat_count3():
assert len(notes) == 6
+def test_pyxel_nested_repeat():
+ source = "0: T120 L4 O4\n[ [ C D ]2 E ]2"
+ tokens = tokenize(source, "pyxel")
+ ns, errors = parse_pyxel(source, tokens)
+ assert not any(e.severity == "error" for e in errors)
+ notes = [e for e in ns.channels["Pulse1"].events if isinstance(e, NoteEvent)]
+ # Inner: [C D]2 -> C D C D (4 notes)
+ # Outer: [ ... E ]2 -> (C D C D E) * 2 = 10 notes
+ assert len(notes) == 10
+
+
+def test_pyxel_nested_repeat_count3():
+ source = "0: T120 L4 O4\n[ [ C D ]2 E ]3"
+ tokens = tokenize(source, "pyxel")
+ ns, errors = parse_pyxel(source, tokens)
+ assert not any(e.severity == "error" for e in errors)
+ notes = [e for e in ns.channels["Pulse1"].events if isinstance(e, NoteEvent)]
+ # Inner: [C D]2 -> C D C D (4 notes)
+ # Outer: [ ... E ]3 -> (C D C D E) * 3 = 15 notes
+ assert len(notes) == 15
+
+
# --- Tie ---
@@ -152,6 +174,21 @@ def test_ppmck_tie_note():
ns, errors = parse_ppmck(source, tokens)
# Tie should merge notes; check no error
assert not any(e.severity == "error" for e in errors)
+ notes = [e for e in ns.channels["Pulse1"].events if isinstance(e, NoteEvent)]
+ # c4 & c4 should produce a single note with doubled duration (192*2=384)
+ assert len(notes) == 1
+ assert notes[0].duration == 384
+
+
+def test_pyxel_tie_note():
+ source = "0: T120 L4 O4\n C4 & C4"
+ tokens = tokenize(source, "pyxel")
+ ns, errors = parse_pyxel(source, tokens)
+ assert not any(e.severity == "error" for e in errors)
+ notes = [e for e in ns.channels["Pulse1"].events if isinstance(e, NoteEvent)]
+ # C4 & C4 should produce a single note with doubled duration (192*2=384)
+ assert len(notes) == 1
+ assert notes[0].duration == 384
def test_pyxel_tie_length():