"""Tests for interactive device picker (non-TTY fallback).""" import pytest from stt_daemon.picker import select_sources from stt_daemon.pulse import Device def test_select_sources_blank_keeps_preselected(monkeypatch, capsys): """Test that blank input keeps preselected sources.""" devices = [ Device(name="alsa_input.usb-Foo", description="USB Mic", kind="input"), Device(name="alsa_output.pci-0000.monitor", description="Speakers", kind="output"), ] monkeypatch.setattr("sys.stdin.isatty", lambda: False) monkeypatch.setattr("builtins.input", lambda _: "") result = select_sources(devices, preselected=["alsa_input.usb-Foo"]) assert result == ["alsa_input.usb-Foo"] def test_select_sources_all(monkeypatch, capsys): """Test 'all' response.""" devices = [ Device(name="alsa_input.usb-Foo", description="USB Mic", kind="input"), Device(name="alsa_output.pci-0000.monitor", description="Speakers", kind="output"), ] monkeypatch.setattr("sys.stdin.isatty", lambda: False) monkeypatch.setattr("builtins.input", lambda _: "all") result = select_sources(devices, preselected=[]) assert len(result) == 2 assert "alsa_input.usb-Foo" in result assert "alsa_output.pci-0000.monitor" in result def test_select_sources_none(monkeypatch): """Test 'none' response.""" devices = [ Device(name="alsa_input.usb-Foo", description="USB Mic", kind="input"), Device(name="alsa_output.pci-0000.monitor", description="Speakers", kind="output"), ] monkeypatch.setattr("sys.stdin.isatty", lambda: False) monkeypatch.setattr("builtins.input", lambda _: "none") result = select_sources(devices, preselected=[]) assert result == [] def test_select_sources_space_separated_indices(monkeypatch): """Test space-separated numeric indices.""" devices = [ Device(name="alsa_input.usb-Foo", description="USB Mic", kind="input"), Device(name="alsa_output.pci-0000.monitor", description="Speakers", kind="output"), ] monkeypatch.setattr("sys.stdin.isatty", lambda: False) monkeypatch.setattr("builtins.input", lambda _: "1 2") result = select_sources(devices, preselected=[]) assert len(result) == 2 assert result[0] == "alsa_input.usb-Foo" assert result[1] == "alsa_output.pci-0000.monitor" def test_select_sources_comma_separated_indices(monkeypatch): """Test comma-separated numeric indices.""" devices = [ Device(name="alsa_input.usb-Foo", description="USB Mic", kind="input"), Device(name="alsa_output.pci-0000.monitor", description="Speakers", kind="output"), ] monkeypatch.setattr("sys.stdin.isatty", lambda: False) monkeypatch.setattr("builtins.input", lambda _: "2,1") result = select_sources(devices, preselected=[]) # Order preserved from devices list (not selection order) assert len(result) == 2 assert set(result) == {"alsa_input.usb-Foo", "alsa_output.pci-0000.monitor"} def test_select_sources_deduplicates(monkeypatch): """Test that duplicate indices are deduplicated.""" devices = [ Device(name="alsa_input.usb-Foo", description="USB Mic", kind="input"), Device(name="alsa_output.pci-0000.monitor", description="Speakers", kind="output"), ] monkeypatch.setattr("sys.stdin.isatty", lambda: False) monkeypatch.setattr("builtins.input", lambda _: "1 1 1") result = select_sources(devices, preselected=[]) assert result == ["alsa_input.usb-Foo"] def test_select_sources_invalid_then_valid(monkeypatch): """Test recovery from invalid input.""" devices = [ Device(name="alsa_input.usb-Foo", description="USB Mic", kind="input"), Device(name="alsa_output.pci-0000.monitor", description="Speakers", kind="output"), ] inputs = iter(["invalid input", "all"]) monkeypatch.setattr("sys.stdin.isatty", lambda: False) monkeypatch.setattr("builtins.input", lambda _: next(inputs)) result = select_sources(devices, preselected=[]) assert len(result) == 2 def test_select_sources_eof_raises(monkeypatch): """Test that EOF raises RuntimeError with helpful message.""" devices = [ Device(name="alsa_input.usb-Foo", description="USB Mic", kind="input"), ] def raise_eof(_): raise EOFError() monkeypatch.setattr("sys.stdin.isatty", lambda: False) monkeypatch.setattr("builtins.input", raise_eof) with pytest.raises(RuntimeError, match="No TTY and EOF reached"): select_sources(devices, preselected=[]) def test_select_sources_preserves_order(monkeypatch): """Test that result preserves device list order.""" devices = [ Device(name="device-a", description="A", kind="input"), Device(name="device-b", description="B", kind="input"), Device(name="device-c", description="C", kind="input"), ] # Select indices 3, 1, 2 but result should be in device order: 1, 2, 3 monkeypatch.setattr("sys.stdin.isatty", lambda: False) monkeypatch.setattr("builtins.input", lambda _: "3 1 2") result = select_sources(devices, preselected=[]) assert result == ["device-a", "device-b", "device-c"] def test_build_choices_preselects_via_checked(): """Regression: questionary preselects via Choice.checked, NOT a default=[...] list. Passing a list as `default` to questionary.checkbox raises "Invalid `default` value passed ...", which broke `stt-daemon setup`. """ pytest.importorskip("questionary") import questionary from stt_daemon.picker import _build_choices devices = [ Device("alsa_input.a", "USB Mic", "input"), Device("alsa_output.b.monitor", "HDMI", "output"), ] choices = _build_choices(devices, {"alsa_output.b.monitor"}) # Constructing the prompt must not raise. questionary.checkbox("Select audio sources to capture:", choices=choices) assert {c.value: c.checked for c in choices} == { "alsa_input.a": False, "alsa_output.b.monitor": True, } def test_select_sources_tui_path(monkeypatch): """TUI path returns the selection in device-list order.""" pytest.importorskip("questionary") import questionary from stt_daemon import picker devices = [ Device("b", "B", "input"), Device("a", "A", "input"), ] class FakePrompt: def ask(self): return ["a", "b"] monkeypatch.setattr("sys.stdin.isatty", lambda: True) monkeypatch.setattr(questionary, "checkbox", lambda *a, **k: FakePrompt()) assert picker.select_sources(devices, preselected=[]) == ["b", "a"] def test_select_sources_tui_abort_keeps_preselected(monkeypatch): """Aborting the TUI (ask() returns None) preserves the previous selection.""" pytest.importorskip("questionary") import questionary from stt_daemon import picker devices = [Device("a", "A", "input")] class AbortPrompt: def ask(self): return None monkeypatch.setattr("sys.stdin.isatty", lambda: True) monkeypatch.setattr(questionary, "checkbox", lambda *a, **k: AbortPrompt()) assert picker.select_sources(devices, preselected=["a"]) == ["a"]