| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- """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"]
|