"""Tests for src/reaper_builder/ — RPPBuilder, rpp_writer.""" import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parents[1])) import pytest import tempfile from src.core.schema import SongDefinition, SongMeta, TrackDef, ClipDef, MidiNote, PluginDef from src.reaper_builder import RPPBuilder class TestRPPBuilderWrite: """Test RPPBuilder.write() produces valid .rpp output.""" def test_write_produces_reaper_project_marker(self): """RPPBuilder.write() produces a file containing 'REAPER_PROJECT'.""" meta = SongMeta(bpm=95, key="Am", title="Test") song = SongDefinition(meta=meta, tracks=[]) builder = RPPBuilder(song) with tempfile.NamedTemporaryFile( mode="w", suffix=".rpp", delete=False, encoding="utf-8" ) as f: tmp_path = f.name try: builder.write(tmp_path) content = Path(tmp_path).read_text(encoding="utf-8") assert "REAPER_PROJECT" in content finally: Path(tmp_path).unlink(missing_ok=True) def test_write_produces_tempo_line(self): """Output contains TEMPO line with correct BPM.""" meta = SongMeta(bpm=95, key="Am") song = SongDefinition(meta=meta, tracks=[]) builder = RPPBuilder(song) with tempfile.NamedTemporaryFile( mode="w", suffix=".rpp", delete=False, encoding="utf-8" ) as f: tmp_path = f.name try: builder.write(tmp_path) content = Path(tmp_path).read_text(encoding="utf-8") assert "TEMPO 95 " in content finally: Path(tmp_path).unlink(missing_ok=True) class TestRPPBuilderAudioTrack: """Test audio track generates SOURCE WAVE block with correct file path.""" def test_audio_track_generates_source_wave_block(self): """Audio clip produces block with FILE path.""" meta = SongMeta(bpm=95, key="Am") clip = ClipDef( position=0.0, length=16.0, name="Kick Loop", audio_path="C:/samples/kick.wav", ) track = TrackDef(name="Drums", clips=[clip]) song = SongDefinition(meta=meta, tracks=[track]) builder = RPPBuilder(song) with tempfile.NamedTemporaryFile( mode="w", suffix=".rpp", delete=False, encoding="utf-8" ) as f: tmp_path = f.name try: builder.write(tmp_path) content = Path(tmp_path).read_text(encoding="utf-8") assert "= 1, f"Expected at least 1 FXCHAIN, got {fxchain_count}" # Master track FXCHAIN has master-specific FXID assert "FXID {" in content finally: Path(tmp_path).unlink(missing_ok=True) class TestVST3GUIDPresence: """Test that VST3 plugins output with uniqueid{GUID} tokens.""" def test_vst3_plugin_output_contains_guid(self): """VST3 element contains GUID from registry lookup.""" meta = SongMeta(bpm=95, key="Am", title="VST3 Test") plugin = PluginDef(name="Serum2", path="Serum2.vst3", index=0) track = TrackDef(name="Bass", clips=[], plugins=[plugin]) song = SongDefinition(meta=meta, tracks=[track]) builder = RPPBuilder(song) with tempfile.NamedTemporaryFile( mode="w", suffix=".rpp", delete=False, encoding="utf-8" ) as f: tmp_path = f.name try: builder.write(tmp_path) content = Path(tmp_path).read_text(encoding="utf-8") # Must contain the GUID token from VST3_REGISTRY["Serum2"] assert "691258006{56534558667350736572756D20320000}" in content # Must also contain correct display name and filename assert "VST3: Serum 2 (Xfer Records)" in content assert "Serum2.vst3" in content finally: Path(tmp_path).unlink(missing_ok=True) def test_fabfilter_proq3_contains_guid(self): """FabFilter Pro-Q 3 outputs with correct GUID.""" meta = SongMeta(bpm=95, key="Am", title="VST3 Test") plugin = PluginDef(name="FabFilter Pro-Q 3", path="FabFilter Pro-Q 3.vst3", index=0) track = TrackDef(name="Lead", clips=[], plugins=[plugin]) song = SongDefinition(meta=meta, tracks=[track]) builder = RPPBuilder(song) with tempfile.NamedTemporaryFile( mode="w", suffix=".rpp", delete=False, encoding="utf-8" ) as f: tmp_path = f.name try: builder.write(tmp_path) content = Path(tmp_path).read_text(encoding="utf-8") # Must contain the GUID token from VST3_REGISTRY["FabFilter Pro-Q 3"] assert "756089518{72C4DB717A4D459AB97E51745D84B39D}" in content assert "VST3: Pro-Q 3 (FabFilter)" in content assert "FabFilter Pro-Q 3.vst3" in content finally: Path(tmp_path).unlink(missing_ok=True) class TestVST3PresetData: """Test that VST3 plugins include base64 preset data inside VST blocks.""" def test_serum2_vst_contains_preset_data(self): """Serum2 VST block contains base64 preset lines.""" meta = SongMeta(bpm=95, key="Am", title="VST3 Preset Test") plugin = PluginDef(name="Serum2", path="Serum2.vst3", index=0) track = TrackDef(name="Bass", clips=[], plugins=[plugin]) song = SongDefinition(meta=meta, tracks=[track]) builder = RPPBuilder(song) with tempfile.NamedTemporaryFile( mode="w", suffix=".rpp", delete=False, encoding="utf-8" ) as f: tmp_path = f.name try: builder.write(tmp_path) content = Path(tmp_path).read_text(encoding="utf-8") # Serum2 preset starts with this magic line (first base64 line) assert "Z4R+ae5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAbQgAAAEAAAAAAAAA" in content # Last line of all presets is the same terminator assert "AFByb2dyYW0gMQAAAAAA" in content # A mid-preset line (line 2) assert "zQQAAAEAAABYZmVySnNvbgC5AAAAAAAAAHsiY29tcG9uZW50IjoicHJvY2Vzc29yIiwiaGFzaCI6IjgxZTEyMWYxNGI2Y2IyYjA2YzMzMjQzZDk1ZDIxYWIxIiwicHJv" in content finally: Path(tmp_path).unlink(missing_ok=True) def test_fabfilter_proq3_vst_contains_preset_data(self): """FabFilter Pro-Q 3 VST block contains base64 preset lines.""" meta = SongMeta(bpm=95, key="Am", title="VST3 Preset Test") plugin = PluginDef(name="FabFilter Pro-Q 3", path="FabFilter Pro-Q 3.vst3", index=0) track = TrackDef(name="Lead", clips=[], plugins=[plugin]) song = SongDefinition(meta=meta, tracks=[track]) builder = RPPBuilder(song) with tempfile.NamedTemporaryFile( mode="w", suffix=".rpp", delete=False, encoding="utf-8" ) as f: tmp_path = f.name try: builder.write(tmp_path) content = Path(tmp_path).read_text(encoding="utf-8") # Pro-Q 3 preset starts with this line assert "rgIRLe5e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAAoGAAABAAAAAAAAAA==" in content assert "AFByb2dyYW0gMQAAAAAA" in content finally: Path(tmp_path).unlink(missing_ok=True) def test_all_registry_plugins_have_preset_data(self): """All 10 VST3 plugins in VST3_REGISTRY have preset data.""" meta = SongMeta(bpm=95, key="Am", title="VST3 Preset Test") # Use actual filenames from registry so _build_plugin recognizes them as VST3 plugins = [ PluginDef(name=name, path=entry[1], index=i) for i, (name, entry) in enumerate(RPPBuilder.VST3_REGISTRY.items()) ] track = TrackDef(name="Test", clips=[], plugins=plugins) song = SongDefinition(meta=meta, tracks=[track]) builder = RPPBuilder(song) with tempfile.NamedTemporaryFile( mode="w", suffix=".rpp", delete=False, encoding="utf-8" ) as f: tmp_path = f.name try: builder.write(tmp_path) content = Path(tmp_path).read_text(encoding="utf-8") for name, preset_lines in RPPBuilder.VST3_PRESETS.items(): assert len(preset_lines) > 0, f"{name} has no preset lines" # Check first preset line — most distinctive, no collision risk first_line = preset_lines[0] assert first_line in content, f"{name} preset line not found in output" finally: Path(tmp_path).unlink(missing_ok=True)