"""Tests for audio state persistence.""" import json from pathlib import Path from stt_daemon.state import AudioState, Loopback, StateStore def test_state_store_save_and_load(tmp_path): """Test round-trip save and load of state.""" state_file = tmp_path / "audio.json" store = StateStore(state_file) original_state = AudioState( sink_name="VirtualMicSink", remap_name="VirtualMic", null_sink_module=10, remap_module=11, loopbacks=[ Loopback(module_id=12, source="alsa_input.usb-Foo"), Loopback(module_id=13, source="alsa_output.pci-0000.monitor"), ], selected_sources=["alsa_input.usb-Foo", "alsa_output.pci-0000.monitor"], ) store.save(original_state) loaded_state = store.load() assert loaded_state is not None assert loaded_state.sink_name == original_state.sink_name assert loaded_state.remap_name == original_state.remap_name assert loaded_state.null_sink_module == original_state.null_sink_module assert loaded_state.remap_module == original_state.remap_module assert len(loaded_state.loopbacks) == 2 assert loaded_state.loopbacks[0].module_id == 12 assert loaded_state.loopbacks[0].source == "alsa_input.usb-Foo" assert loaded_state.selected_sources == original_state.selected_sources def test_state_store_load_missing(): """Test that load returns None for missing file.""" store = StateStore(Path("/nonexistent/path/audio.json")) loaded_state = store.load() assert loaded_state is None def test_state_store_load_corrupt_json(tmp_path): """Test that load returns None for corrupt JSON.""" state_file = tmp_path / "audio.json" state_file.write_text("{ invalid json }") store = StateStore(state_file) loaded_state = store.load() assert loaded_state is None def test_state_store_clear(tmp_path): """Test that clear removes the state file.""" state_file = tmp_path / "audio.json" store = StateStore(state_file) state = AudioState( sink_name="VirtualMicSink", remap_name="VirtualMic", null_sink_module=10, remap_module=11, ) store.save(state) assert state_file.exists() store.clear() assert not state_file.exists() def test_state_store_default_path_env_var(tmp_path, monkeypatch): """Test that StateStore uses STT_DAEMON_STATE env var.""" state_file = tmp_path / "custom_audio.json" monkeypatch.setenv("STT_DAEMON_STATE", str(state_file)) store = StateStore() assert store.path == state_file def test_state_store_default_path_xdg(tmp_path, monkeypatch): """Test that StateStore uses XDG_STATE_HOME for default path.""" monkeypatch.delenv("STT_DAEMON_STATE", raising=False) monkeypatch.setenv("XDG_STATE_HOME", str(tmp_path)) store = StateStore() expected_path = tmp_path / "stt-daemon" / "audio.json" assert store.path == expected_path def test_state_store_creates_parent_dirs(tmp_path): """Test that save creates parent directories.""" state_file = tmp_path / "deep" / "nested" / "dirs" / "audio.json" store = StateStore(state_file) state = AudioState( sink_name="VirtualMicSink", remap_name="VirtualMic", null_sink_module=10, remap_module=11, ) store.save(state) assert state_file.parent.exists() assert state_file.exists() def test_state_store_load_respects_json_format(tmp_path): """Test that load respects the JSON format with loopbacks.""" state_file = tmp_path / "audio.json" data = { "sink_name": "VirtualMicSink", "remap_name": "VirtualMic", "null_sink_module": 10, "remap_module": 11, "loopbacks": [ {"module_id": 12, "source": "alsa_input.usb-Foo"}, ], "selected_sources": ["alsa_input.usb-Foo"], } state_file.write_text(json.dumps(data)) store = StateStore(state_file) loaded_state = store.load() assert loaded_state is not None assert loaded_state.selected_sources == ["alsa_input.usb-Foo"]