#!/usr/bin/env python3 """Smoke tests for pure helper behavior in the MiniMax workflow scripts. This harness intentionally stays on the standard library and avoids real audio processing, media downloads, and optional heavyweight dependencies. """ from __future__ import annotations import ast import json import os import subprocess import sys import tempfile import urllib.error import urllib.request from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent if str(SCRIPT_DIR) not in sys.path: sys.path.insert(0, str(SCRIPT_DIR)) def _print_pass(name: str) -> None: print(f"PASS {name}") def _print_fail(name: str, error: BaseException) -> None: print(f"FAIL {name}: {error}") def test_normalize_output_base() -> None: from download_youtube import normalize_output_base cases = { "/tmp/song": "/tmp/song", "/tmp/song.wav": "/tmp/song", "/tmp/song.MP3": "/tmp/song", } for input_path, expected in cases.items(): actual = normalize_output_base(input_path) assert actual == expected, f"{input_path!r} -> {actual!r}, expected {expected!r}" # ---- fetch_lyrics_web tests ---- def test_fetch_lyrics_normalize() -> None: from fetch_lyrics_web import _normalize assert _normalize("Look at the Stars!") == "look at the stars" # Accented characters are kept (python \w is unicode-aware); only ASCII punctuation is stripped. assert _normalize("Múltiple espacios") == "múltiple espacios" assert _normalize("") == "" def test_fetch_lyrics_word_set() -> None: from fetch_lyrics_web import _word_set s = _word_set("Look at the stars, look how they shine!") assert s == {"look", "at", "the", "stars", "how", "they", "shine"} def test_fetch_lyrics_match_score() -> None: from fetch_lyrics_web import _match_score # Identical text: Jaccard = 1.0 assert _match_score("look at the stars", "look at the stars") == 1.0 # "a b c" and "a b d e" share {a, b} = 2, union = {a, b, c, d, e} = 5 # Jaccard = 2/5 = 0.4 s = _match_score("a b c", "a b d e") assert abs(s - 0.4) < 0.01 # No overlap: 0.0 assert _match_score("a b c", "x y z") == 0.0 # Empty inputs: 0.0 assert _match_score("", "anything") == 0.0 assert _match_score("anything", "") == 0.0 assert _match_score("", "") == 0.0 # Realistic Whisper-vs-perfect-lyrics case: Whisper has lots of # filler so the union is much bigger than the intersection, giving # a low Jaccard even for the same song. This is why the default # threshold is 0.35, not 0.6. web = "look at the stars look how they shine for you and everything you do" whisper = "look at the stars look how they shine for you" s = _match_score(whisper, web) # whisper unique: 9 words, web unique: 12 words, all whisper words # are in web, union = 12, Jaccard = 9/12 = 0.75 assert abs(s - 0.75) < 0.01 # And a case with extra noise: Whisper hears 'uh' and 'mm' that are # not in the web lyrics. This should drop the Jaccard noticeably. whisper_noisy = "uh look at the stars mm look how they shine for you" s2 = _match_score(whisper_noisy, web) # whisper unique: 11 (uh, look, at, the, stars, mm, how, they, shine, for, you) # web unique: 12 # intersection: 9 (no uh, no mm) # union: 12 + 2 (uh, mm) = 14 # Jaccard = 9/14 ~ 0.643 assert abs(s2 - 9 / 14) < 0.01 def test_fetch_lyrics_result_shape() -> None: """The result dict must always have the documented field shape, regardless of status. We exercise the no-network path (mocked URL opener) which covers all the field-handling code paths without depending on LRCLib. """ from fetch_lyrics_web import fetch_lyrics_web required_fields = { "status", "lyrics", "source", "match_score", "artist", "title", "album", "duration", "error", } real_urlopen = urllib.request.urlopen def _broken_urlopen(req, *args, **kwargs): raise urllib.error.URLError("simulated") # Path 1: missing args (short-circuit, no network) r = fetch_lyrics_web(artist="", title="") assert r["status"] == "no_web_lyrics" assert set(r.keys()) >= required_fields assert r["lyrics"] is None assert r["error"] == "missing artist or title" # Path 2: network_error (all 9 fields still present, no crash) try: urllib.request.urlopen = _broken_urlopen r = fetch_lyrics_web( artist="Coldplay", title="Yellow", whisper_transcript="...", timeout=2.0, ) finally: urllib.request.urlopen = real_urlopen assert r["status"] == "network_error" assert set(r.keys()) >= required_fields assert r["lyrics"] is None assert r["error"] is not None def test_fetch_lyrics_network_unreachable() -> None: """When the API is unreachable, status should be network_error, not a crash. This test injects a fake urllib.request.urlopen that raises URLError so it does not depend on the network or LRCLib uptime. """ from fetch_lyrics_web import fetch_lyrics_web real_urlopen = urllib.request.urlopen def _broken_urlopen(req, *args, **kwargs): raise urllib.error.URLError("simulated network failure") try: urllib.request.urlopen = _broken_urlopen r = fetch_lyrics_web( artist="Coldplay", title="Yellow", whisper_transcript="look at the stars", timeout=2.0, ) finally: urllib.request.urlopen = real_urlopen assert r["status"] == "network_error", ( f"expected network_error when API is unreachable, got {r['status']!r}" ) assert r["lyrics"] is None assert r["source"] == "lrclib" assert "simulated network failure" in (r.get("error") or "") def test_fetch_lyrics_no_instrumental_in_results() -> None: """The function must skip instrumental results even if plainLyrics is set.""" from fetch_lyrics_web import _best_lyrics_match results = [ { "id": 1, "artistName": "Joe Satriani", "trackName": "Crystal Planet", "albumName": "Crystal Planet", "duration": 275, "instrumental": True, # instrumental: should be skipped "plainLyrics": "Some text that overlaps with whisper", }, { "id": 2, "artistName": "Joe Satriani", "trackName": "Crystal Planet", "albumName": "Crystal Planet", "duration": 275, "instrumental": False, "plainLyrics": "Some text that overlaps with whisper", }, ] best = _best_lyrics_match(results, "Some text that overlaps with whisper", min_match=0.5) assert best is not None assert best["id"] == 2, "instrumental result should be skipped" def test_fetch_lyrics_cli_invocable() -> None: """The CLI entry point should run without import errors and exit non-zero on bad args.""" script = str(SCRIPT_DIR / "fetch_lyrics_web.py") # Missing required args r = subprocess.run( [sys.executable, script], capture_output=True, text=True, timeout=10, ) assert r.returncode == 2, "argparse should exit 2 on missing required args" assert "required" in r.stderr.lower() or "usage" in r.stderr.lower() def test_fetch_lyrics_stdlib_only() -> None: """The script must not import anything outside the standard library.""" import ast as _ast src = (SCRIPT_DIR / "fetch_lyrics_web.py").read_text() tree = _ast.parse(src) for node in _ast.walk(tree): if isinstance(node, _ast.Import): for alias in node.names: root = alias.name.split(".")[0] assert root in sys.stdlib_module_names, f"non-stdlib import: {alias.name}" elif isinstance(node, _ast.ImportFrom): assert node.module is not None root = node.module.split(".")[0] assert root in sys.stdlib_module_names, f"non-stdlib import from: {node.module}" # ---- orchestrator path-stem parsing tests ---- def test_orchestrator_strip_youtube_noise() -> None: """Common YouTube parenthetical/bracket noise must be stripped from titles.""" # The helper is defined inside analysis_orchestrator.py, which is a # heavy module to import (torch, demucs, etc.). Test the behavior # inline using a fresh regex compiled with the same patterns. import re as _re patterns = [ r"\(official\s+(?:music\s+)?video\)", r"\(official\s+lyric\s+video\)", r"\[lyrics?\]", r"\(remastered(?:\s+\d{4})?\)", r"\(lyric\s+video\)", ] compiled = _re.compile("|".join(patterns), _re.IGNORECASE) cases = { "Yellow (Official Music Video)": "Yellow", "Yellow (official video)": "Yellow", "Yellow (Remastered 2011)": "Yellow", "Yellow [Lyrics]": "Yellow", "Yellow (Lyric Video)": "Yellow", "Yellow (Live at Wembley)": "Yellow (Live at Wembley)", # not in our strip list "Yellow": "Yellow", "": "", } for input_text, expected in cases.items(): actual = compiled.sub("", input_text) actual = _re.sub(r"\s+", " ", actual).strip(" \t-.") assert actual == expected, f"{input_text!r} -> {actual!r}, expected {expected!r}" def test_orchestrator_parse_song_stem() -> None: """Parse 'Artist - Title [noise]' into (artist, title) with noise stripped.""" import re as _re # Replicate the exact logic from the orchestrator's _parse_song_stem YT_NOISE = [ r"\(official\s+(?:music\s+)?video\)", r"\(official\s+lyric\s+video\)", r"\[lyrics?\]", r"\(remastered(?:\s+\d{4})?\)", r"\(lyric\s+video\)", ] YT_RE = _re.compile("|".join(YT_NOISE), _re.IGNORECASE) def strip_noise(text): return YT_RE.sub("", text).strip(" \t-.") def parse(stem): if " - " not in stem: return ("", "") artist, title = stem.split(" - ", 1) artist = artist.strip() title = strip_noise(title).strip() if not artist or not title: return ("", "") return (artist, title) assert parse("Coldplay - Yellow") == ("Coldplay", "Yellow") assert parse("Coldplay - Yellow (Official Music Video)") == ("Coldplay", "Yellow") assert parse("Coldplay - Yellow [Lyrics]") == ("Coldplay", "Yellow") assert parse("Coldplay - Yellow (Lyric Video)") == ("Coldplay", "Yellow") assert parse("AC-DC - Back In Black") == ("AC-DC", "Back In Black") assert parse("no-separator-here") == ("", "") assert parse("") == ("", "") assert parse("Artist - ") == ("", "") assert parse(" - Title") == ("", "") def test_cached_or_compute_roundtrip() -> None: from _analysis_cache import cached_or_compute with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) audio_path = tmpdir_path / "input audio.wav" audio_path.write_bytes(b"dummy audio bytes") cache_dir = tmpdir_path / "nested" / "cache" / "dir" calls = {"count": 0} payload = { "title": "café", "artist": "東京", "notes": {"line": "mañana", "tags": ["über", "façade"]}, } def compute(): calls["count"] += 1 return json.loads(json.dumps(payload, ensure_ascii=False)) first = cached_or_compute(str(audio_path), "unicode_analysis", compute, cache_dir=str(cache_dir)) assert first["_cache"] == "miss" assert calls["count"] == 1 assert first["title"] == "café" assert first["notes"]["line"] == "mañana" cache_files = list(cache_dir.glob(".unicode_analysis_*.json")) assert len(cache_files) == 1, f"expected one cache file in {cache_dir}, found {len(cache_files)}" saved_text = cache_files[0].read_text(encoding="utf-8") assert "café" in saved_text and "東京" in saved_text and "mañana" in saved_text assert "\\u" not in saved_text, "unicode should be preserved in the cache file" second = cached_or_compute(str(audio_path), "unicode_analysis", compute, cache_dir=str(cache_dir)) assert second["_cache"] == "hit" assert calls["count"] == 1, "cache hit should not recompute" assert second["title"] == "café" assert second["notes"]["tags"] == ["über", "façade"] def test_build_final_prompt_priorities() -> None: from emotion_to_prompt import build_final_prompt emotion_data = { "emotion_profile": {"intensity_curve": {"pattern": "wave"}}, "music_generation_hints": [], "emotion_sections": [], } style_data = { "estimated_key": "C major", "beat_tracking": { "bpm_estimated": 142, "bpm_confidence": 0.96, "time_signature_estimate": 4, }, "melody_analysis": { "key_estimate_from_midi": "F# minor", "interval_pattern": "rising_fifths", "scale_modes": ["dorian", "aeolian"], }, "brightness": "warm bass-heavy", "energy_description": "moderate", } prompt = build_final_prompt( emotion_data=emotion_data, style_data=style_data, style_category="pop", target_bpm=120, duration_seconds=180, language="english", ) assert "beat grid: 4/4 at 142 BPM (confidence 0.96)" in prompt assert "melodic key from MIDI: F# minor" in prompt assert "tempo 142 BPM in F# minor" in prompt assert "tonal character: dark warm tone, rolled-off highs" in prompt assert "tempo 120 BPM in C major" not in prompt def test_analysis_orchestrator_rejects_three_audio_sources() -> None: script_path = SCRIPT_DIR / "analysis_orchestrator.py" cmd = [ sys.executable, str(script_path), "--audio", "one.wav", "--audio", "two.wav", "--youtube", "https://youtu.be/example", ] result = subprocess.run(cmd, capture_output=True, text=True) assert result.returncode != 0, f"expected parser failure, got {result.returncode}" assert "At most two audio sources are supported" in result.stderr assert "usage:" in result.stderr.lower() def _run_lint_music_request(*args: str) -> dict: script_path = SCRIPT_DIR / "lint_music_request.py" cmd = [sys.executable, str(script_path), *args] result = subprocess.run(cmd, capture_output=True, text=True) assert result.returncode == 0, f"command failed: {result.stderr}" assert result.stdout.strip(), "expected JSON output" return json.loads(result.stdout) def test_lint_music_request_standard_spanish_song() -> None: payload = _run_lint_music_request( "--text", "Write a Spanish pop song about a rainy night in Madrid.", ) assert payload["route"] == "base_prompt" assert payload["request_type"] == "standard_song" assert payload["fields"]["language"]["value"] == "Spanish" assert payload["fields"]["genre"]["value"] == "pop" assert payload["fields"]["theme"]["value"] == "rainy night in Madrid" assert payload["fields"]["lyrics_source"]["value"] == "missing" assert "lyrics_source" in payload["missing_fields"] assert payload["prompt_warnings"] == [] assert payload["blockers"] == [] assert payload["retry_guidance"] == [] def test_lint_music_request_routes_cover_to_minimax_cover() -> None: payload = _run_lint_music_request( "--text", "Make a cover of this YouTube song into a softer acoustic version.", ) assert payload["route"] == "minimax_cover" assert payload["request_type"] == "cover" assert payload["fields"]["references"]["value"] == "YouTube/audio source" assert payload["blockers"] == [] def test_lint_music_request_routes_style_transfer_to_minimax_style_transfer() -> None: payload = _run_lint_music_request( "--text", "Do a style transfer of /tmp/original.wav into reggaeton with original melody-free production.", ) assert payload["route"] == "minimax_style_transfer" assert payload["request_type"] == "style_transfer" assert payload["fields"]["references"]["value"] == "YouTube/audio source" assert "missing target style" not in payload["blockers"] def test_lint_music_request_instrumental_jingle_skips_vocal_question() -> None: payload = _run_lint_music_request( "--text", "Create a 30-second instrumental jingle for a cooking channel, no vocals.", ) assert payload["route"] == "base_prompt" assert payload["request_type"] == "instrumental" assert payload["fields"]["vocal_mode"]["value"] == "instrumental" assert payload["fields"]["vocal_mode"]["confidence"] == "clear" assert payload["fields"]["duration"]["value"] == "30 seconds" assert "vocal_mode" not in payload["missing_fields"] def test_lint_music_request_text_url_stays_base_enrichment() -> None: payload = _run_lint_music_request( "--text", "Make a pop song with the vibe of this article: https://example.com/story", ) assert payload["route"] == "base_prompt" assert payload["request_type"] == "url_or_image_enrichment" def test_lint_music_request_plain_style_reference_needs_clarification() -> None: payload = _run_lint_music_request( "--text", "Make a song like Daft Punk.", ) assert payload["route"] == "needs_clarification" assert payload["request_type"] == "text_reference" assert payload["fields"]["references"]["value"] == "style reference" assert "missing target style or mood for the new song" in payload["blockers"] def test_lint_music_request_text_style_reference_with_clear_target() -> None: payload = _run_lint_music_request( "--text", "Make a synthwave song in the style of Daft Punk.", ) assert payload["route"] == "base_prompt" assert payload["request_type"] == "text_reference" assert payload["fields"]["genre"]["value"] == "synthwave" def test_lint_music_request_youtube_audio_routes_minimax_cover() -> None: payload = _run_lint_music_request( "--text", "Make a reggaeton cover from https://youtube.com/watch?v=abc123", ) assert payload["route"] == "minimax_cover" assert payload["request_type"] == "cover" def test_lint_music_request_mashup_routes_to_minimax_mashup_when_complete() -> None: payload = _run_lint_music_request( "--text", "Mash up Song A (https://youtube.com/watch?v=a) and Song B (https://youtube.com/watch?v=b) into a pop track.", ) assert payload["route"] == "minimax_mashup" assert payload["request_type"] == "mashup" assert payload["blockers"] == [] def test_lint_music_request_emotion_prompt_routes_to_minimax_emotion_prompt() -> None: payload = _run_lint_music_request( "--text", "Run emotion analysis on /tmp/song.wav and turn it into a sad pop prompt.", ) assert payload["route"] == "minimax_emotion_prompt" assert payload["request_type"] == "emotion_prompt" def test_lint_music_request_mashup_blockers_surface_missing_fields() -> None: payload = _run_lint_music_request( "--text", "Make a mashup of two songs.", ) assert payload["route"] == "needs_clarification" assert payload["request_type"] == "mashup" assert "lyrics_source" in payload["missing_fields"] assert "references" in payload["missing_fields"] assert "unclear Song A vs Song B assignment" in payload["blockers"] assert "missing source file or usable URL" in payload["blockers"] assert "missing target style" in payload["blockers"] assert "missing lyrics decision" in payload["blockers"] def test_lint_music_request_cover_blockers_surface_missing_source_style_and_lyrics() -> None: payload = _run_lint_music_request( "--text", "Make a cover.", ) assert payload["route"] == "needs_clarification" assert payload["request_type"] == "cover" assert "missing source file or usable URL" in payload["blockers"] assert "missing target style" in payload["blockers"] assert "missing lyrics decision" in payload["blockers"] def test_lint_music_request_mashup_blocker_for_missing_song_b() -> None: payload = _run_lint_music_request( "--text", "Make a mashup with only Song A in pop style.", ) assert payload["route"] == "needs_clarification" assert payload["request_type"] == "mashup" assert "unclear Song A vs Song B assignment" in payload["blockers"] def test_lint_music_request_blocker_for_missing_lyrics_decision() -> None: payload = _run_lint_music_request( "--text", "Cover a track into reggaeton.", ) assert payload["route"] == "needs_clarification" assert payload["request_type"] == "cover" assert "missing lyrics decision" in payload["blockers"] def test_lint_music_request_blocker_for_conflicting_cover_style_transfer() -> None: payload = _run_lint_music_request( "--text", "Make a cover and style transfer of this YouTube song into pop.", ) assert payload["route"] == "needs_clarification" assert payload["request_type"] == "cover" assert any("conflicting cover/style-transfer" in blocker for blocker in payload["blockers"]) def test_lint_music_request_cinematic_with_instruments_is_not_placeholder() -> None: with tempfile.TemporaryDirectory() as tmpdir: prompt_file = Path(tmpdir) / "prompt.txt" prompt_file.write_text( "Cinematic instrumental, strings, piano, drums, soft verse building to loud chorus. " "ALL instruments always playing throughout, never go a cappella.", encoding="utf-8", ) payload = _run_lint_music_request("--prompt-file", str(prompt_file)) assert not any("vague placeholder" in warning for warning in payload["prompt_warnings"]) def test_lint_music_request_prompt_warnings_and_flag_conflicts() -> None: with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) prompt_file = tmpdir_path / "prompt.txt" prompt_file.write_text( "Make it good and emotional. 80 BPM in C major. Use strings. Verse-chorus-bridge-chorus. Avoid sparse arrangements.", encoding="utf-8", ) flags_file = tmpdir_path / "flags.json" flags_file.write_text( json.dumps( { "bpm": 120, "key": "D minor", "structure": "verse-chorus-verse", "avoid": "sparse, a cappella, minimal arrangement", } ), encoding="utf-8", ) payload = _run_lint_music_request("--prompt-file", str(prompt_file), "--mmx-flags", str(flags_file)) warnings = payload["prompt_warnings"] conflicts = payload["flag_conflicts"] guidance = payload["retry_guidance"] assert any("explicit instruments" in warning for warning in warnings) assert any("anti-sparse" in warning for warning in warnings) assert any("vague placeholder" in warning for warning in warnings) assert any("bpm" in conflict.lower() for conflict in conflicts) assert any("key" in conflict.lower() for conflict in conflicts) assert any("structure" in conflict.lower() for conflict in conflicts) assert any("avoid" in conflict.lower() for conflict in conflicts) assert len(guidance) == len(conflicts) assert any("bpm" in line.lower() for line in guidance) def test_lint_music_request_key_conflict() -> None: with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) prompt_file = tmpdir_path / "prompt.txt" prompt_file.write_text("Pop track at 120 BPM in E minor.", encoding="utf-8") flags_file = tmpdir_path / "flags.json" flags_file.write_text(json.dumps({"bpm": 120, "key": "C major"}), encoding="utf-8") payload = _run_lint_music_request("--prompt-file", str(prompt_file), "--mmx-flags", str(flags_file)) assert any("key conflict" in conflict for conflict in payload["flag_conflicts"]) assert any("key" in line.lower() for line in payload["retry_guidance"]) def test_lint_music_request_duration_conflict() -> None: with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) prompt_file = tmpdir_path / "prompt.txt" prompt_file.write_text("Make a 30 second pop track at 120 BPM.", encoding="utf-8") flags_file = tmpdir_path / "flags.json" flags_file.write_text(json.dumps({"bpm": 120, "duration": "90 seconds"}), encoding="utf-8") payload = _run_lint_music_request("--prompt-file", str(prompt_file), "--mmx-flags", str(flags_file)) assert any("duration conflict" in conflict for conflict in payload["flag_conflicts"]) assert any("duration" in line.lower() for line in payload["retry_guidance"]) def test_lint_music_request_vocal_mode_conflict() -> None: with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) prompt_file = tmpdir_path / "prompt.txt" prompt_file.write_text("Make an instrumental pop track at 120 BPM.", encoding="utf-8") flags_file = tmpdir_path / "flags.json" flags_file.write_text(json.dumps({"bpm": 120, "vocals": "female vocal"}), encoding="utf-8") payload = _run_lint_music_request("--prompt-file", str(prompt_file), "--mmx-flags", str(flags_file)) assert any("vocal mode conflict" in conflict for conflict in payload["flag_conflicts"]) assert any("vocal" in line.lower() for line in payload["retry_guidance"]) def test_lint_music_request_language_conflict() -> None: with tempfile.TemporaryDirectory() as tmpdir: tmpdir_path = Path(tmpdir) prompt_file = tmpdir_path / "prompt.txt" prompt_file.write_text("Write a Spanish pop song at 120 BPM.", encoding="utf-8") flags_file = tmpdir_path / "flags.json" flags_file.write_text(json.dumps({"bpm": 120, "language": "French"}), encoding="utf-8") payload = _run_lint_music_request("--prompt-file", str(prompt_file), "--mmx-flags", str(flags_file)) assert any("language conflict" in conflict for conflict in payload["flag_conflicts"]) assert any("language" in line.lower() for line in payload["retry_guidance"]) def test_lint_music_request_uses_only_stdlib() -> None: """Verify lint_music_request.py imports only standard-library modules.""" lint_path = SCRIPT_DIR / "lint_music_request.py" source = lint_path.read_text(encoding="utf-8") tree = ast.parse(source) imported: list[str] = [] for node in ast.walk(tree): if isinstance(node, ast.Import): for alias in node.names: imported.append(alias.name) elif isinstance(node, ast.ImportFrom): if node.module: imported.append(node.module) stdlib_modules = set(sys.stdlib_module_names) non_stdlib = [name for name in imported if name.split(".")[0] not in stdlib_modules] assert not non_stdlib, f"non-stdlib imports detected: {non_stdlib}" def main() -> int: tests = [ ("download_youtube.normalize_output_base", test_normalize_output_base), ("_analysis_cache.cached_or_compute", test_cached_or_compute_roundtrip), ("emotion_to_prompt.build_final_prompt", test_build_final_prompt_priorities), ("analysis_orchestrator CLI parser", test_analysis_orchestrator_rejects_three_audio_sources), ("lint_music_request standard Spanish song", test_lint_music_request_standard_spanish_song), ("lint_music_request cover routing", test_lint_music_request_routes_cover_to_minimax_cover), ("lint_music_request style transfer routing", test_lint_music_request_routes_style_transfer_to_minimax_style_transfer), ("lint_music_request instrumental jingle", test_lint_music_request_instrumental_jingle_skips_vocal_question), ("lint_music_request base URL enrichment", test_lint_music_request_text_url_stays_base_enrichment), ("lint_music_request plain style reference", test_lint_music_request_plain_style_reference_needs_clarification), ("lint_music_request style reference with target", test_lint_music_request_text_style_reference_with_clear_target), ("lint_music_request YouTube audio routing", test_lint_music_request_youtube_audio_routes_minimax_cover), ("lint_music_request mashup with both songs", test_lint_music_request_mashup_routes_to_minimax_mashup_when_complete), ("lint_music_request emotion prompt routing", test_lint_music_request_emotion_prompt_routes_to_minimax_emotion_prompt), ("lint_music_request mashup blockers", test_lint_music_request_mashup_blockers_surface_missing_fields), ("lint_music_request cover blockers", test_lint_music_request_cover_blockers_surface_missing_source_style_and_lyrics), ("lint_music_request missing Song B", test_lint_music_request_mashup_blocker_for_missing_song_b), ("lint_music_request missing lyrics decision", test_lint_music_request_blocker_for_missing_lyrics_decision), ("lint_music_request conflicting cover/style transfer", test_lint_music_request_blocker_for_conflicting_cover_style_transfer), ("lint_music_request cinematic specificity", test_lint_music_request_cinematic_with_instruments_is_not_placeholder), ("lint_music_request prompt and flag lint", test_lint_music_request_prompt_warnings_and_flag_conflicts), ("lint_music_request key conflict", test_lint_music_request_key_conflict), ("lint_music_request duration conflict", test_lint_music_request_duration_conflict), ("lint_music_request vocal mode conflict", test_lint_music_request_vocal_mode_conflict), ("lint_music_request language conflict", test_lint_music_request_language_conflict), ("lint_music_request stdlib-only imports", test_lint_music_request_uses_only_stdlib), ("fetch_lyrics_web normalize", test_fetch_lyrics_normalize), ("fetch_lyrics_web word_set", test_fetch_lyrics_word_set), ("fetch_lyrics_web match_score", test_fetch_lyrics_match_score), ("fetch_lyrics_web result shape", test_fetch_lyrics_result_shape), ("fetch_lyrics_web network resilient", test_fetch_lyrics_network_unreachable), ("fetch_lyrics_web skips instrumental", test_fetch_lyrics_no_instrumental_in_results), ("fetch_lyrics_web CLI invocable", test_fetch_lyrics_cli_invocable), ("fetch_lyrics_web stdlib-only imports", test_fetch_lyrics_stdlib_only), ("orchestrator strip youtube noise", test_orchestrator_strip_youtube_noise), ("orchestrator parse song stem", test_orchestrator_parse_song_stem), ] failures = 0 for name, func in tests: try: func() except Exception as exc: # noqa: BLE001 - smoke harness wants to report any failure failures += 1 _print_fail(name, exc) else: _print_pass(name) if failures: print(f"{failures} test(s) failed") return 1 print("All smoke tests passed") return 0 if __name__ == "__main__": raise SystemExit(main())