"""Tests for configuration loading.""" import tempfile from pathlib import Path from stt_daemon.config import load_config def test_load_config_defaults(): """Test that load_config returns defaults when no file exists.""" with tempfile.TemporaryDirectory() as tmpdir: # Change to temp dir and load (no config.toml exists) import os old_cwd = os.getcwd() try: os.chdir(tmpdir) cfg = load_config() assert cfg.audio.backend == "parec" assert cfg.audio.device == "VirtualMicSink.monitor" assert cfg.audio.sample_rate == 16000 assert cfg.vad.aggressiveness == 2 assert cfg.whisper.model == "small" assert cfg.whisper.language is None assert cfg.output.stdout is True assert cfg.output.jsonl is False finally: os.chdir(old_cwd) def test_load_config_from_file(): """Test loading config from a TOML file.""" config_toml = """ [audio] backend = "sounddevice" device = "hw:0" sample_rate = 44100 [vad] aggressiveness = 3 pre_roll_ms = 500 [whisper] model = "base" device = "cuda" language = "es" [output] stdout = false jsonl = true jsonl_dir = "logs" """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.audio.backend == "sounddevice" assert cfg.audio.device == "hw:0" assert cfg.audio.sample_rate == 44100 assert cfg.vad.aggressiveness == 3 assert cfg.vad.pre_roll_ms == 500 assert cfg.whisper.model == "base" assert cfg.whisper.device == "cuda" assert cfg.whisper.language == "es" assert cfg.output.stdout is False assert cfg.output.jsonl is True assert cfg.output.jsonl_dir == "logs" def test_language_normalization_auto(): """Test that language 'auto' is normalized to None.""" config_toml = """ [whisper] language = "auto" """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.whisper.language is None def test_language_normalization_empty_string(): """Test that language '' is normalized to None.""" config_toml = """ [whisper] language = "" """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.whisper.language is None def test_language_normalization_none(): """Test that language 'none' is normalized to None.""" config_toml = """ [whisper] language = "none" """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.whisper.language is None def test_language_explicit_en(): """Test that explicit language codes are preserved.""" config_toml = """ [whisper] language = "en" """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.whisper.language == "en" def test_partial_config(): """Test that partial configs merge with defaults.""" config_toml = """ [audio] device = "custom_device" """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) # Overridden assert cfg.audio.device == "custom_device" # Defaults assert cfg.audio.backend == "parec" assert cfg.audio.sample_rate == 16000 assert cfg.vad.aggressiveness == 2 def test_chunking_and_confidence_fields(): """New chunking/gate fields load from TOML.""" config_toml = """ [vad] chunk_pause_ms = 500 soft_max_s = 8.0 rms_floor = 0.01 trailing_trim_ms = 150 [whisper] no_speech_prob_max = 0.7 avg_logprob_min = -0.8 """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.vad.chunk_pause_ms == 500 assert cfg.vad.soft_max_s == 8.0 assert cfg.vad.rms_floor == 0.01 assert cfg.vad.trailing_trim_ms == 150 assert cfg.whisper.no_speech_prob_max == 0.7 assert cfg.whisper.avg_logprob_min == -0.8 def test_chunking_defaults(): """New fields have sane defaults.""" with tempfile.TemporaryDirectory() as tmpdir: import os old_cwd = os.getcwd() try: os.chdir(tmpdir) cfg = load_config() finally: os.chdir(old_cwd) assert cfg.vad.chunk_pause_ms == 350 assert cfg.vad.soft_max_s == 10.0 assert cfg.vad.rms_floor == 0.008 assert cfg.vad.trailing_trim_ms == 200 assert cfg.whisper.no_speech_prob_max == 0.6 assert cfg.whisper.avg_logprob_min == -1.0 def test_output_defaults_disable_audio_saving(): with tempfile.TemporaryDirectory() as tmpdir: import os old_cwd = os.getcwd() try: os.chdir(tmpdir) cfg = load_config() finally: os.chdir(old_cwd) assert cfg.output.save_audio is False assert cfg.output.audio_dir == "audio" def test_output_save_audio_fields(): config_toml = """ [output] save_audio = true audio_dir = "clips" """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.output.save_audio is True assert cfg.output.audio_dir == "clips" def test_daemon_defaults(): with tempfile.TemporaryDirectory() as tmpdir: import os old_cwd = os.getcwd() try: os.chdir(tmpdir) cfg = load_config() finally: os.chdir(old_cwd) assert cfg.daemon.queue_size == 50 assert cfg.daemon.backlog_warn_at == 5 def test_daemon_config_fields(): config_toml = """ [daemon] queue_size = 20 backlog_warn_at = 3 """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.daemon.queue_size == 20 assert cfg.daemon.backlog_warn_at == 3 def test_unknown_sections_warn_and_are_ignored(caplog): """A typo'd section like [assitant] must warn, not silently use defaults.""" config_toml = """ [assitant] require_viewer = false """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) with caplog.at_level("WARNING", logger="stt_daemon.config"): cfg = load_config(str(config_path)) assert "assitant" in caplog.text assert cfg.assistant.require_viewer is True def test_cpu_threads_default_is_auto(): """cpu_threads defaults to 0 (CTranslate2's automatic choice).""" with tempfile.TemporaryDirectory() as tmpdir: import os old_cwd = os.getcwd() try: os.chdir(tmpdir) cfg = load_config() finally: os.chdir(old_cwd) assert cfg.whisper.cpu_threads == 0 def test_cpu_threads_config_field(): config_toml = """ [whisper] cpu_threads = 16 """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.whisper.cpu_threads == 16 def test_language_strategy_defaults_to_static(): """language_strategy defaults to 'static'.""" with tempfile.TemporaryDirectory() as tmpdir: import os old_cwd = os.getcwd() try: os.chdir(tmpdir) cfg = load_config() finally: os.chdir(old_cwd) assert cfg.whisper.language_strategy == "static" def test_language_strategy_sticky_is_parsed(): """language_strategy 'sticky' is parsed correctly.""" config_toml = """ [whisper] language_strategy = "sticky" """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.whisper.language_strategy == "sticky" def test_unknown_language_strategy_falls_back_to_static(caplog): """An unknown language_strategy value falls back to 'static' with a warning.""" config_toml = """ [whisper] language_strategy = "bogus" """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) with caplog.at_level("WARNING", logger="stt_daemon.config"): cfg = load_config(str(config_path)) assert cfg.whisper.language_strategy == "static" assert "Unknown language_strategy" in caplog.text assert "bogus" in caplog.text def test_sticky_redetect_logprob_defaults_to_minus_0_8(): """sticky_redetect_logprob defaults to -0.8.""" with tempfile.TemporaryDirectory() as tmpdir: import os old_cwd = os.getcwd() try: os.chdir(tmpdir) cfg = load_config() finally: os.chdir(old_cwd) assert cfg.whisper.sticky_redetect_logprob == -0.8 def test_sticky_redetect_logprob_is_parsed(): """sticky_redetect_logprob is parsed from TOML.""" config_toml = """ [whisper] sticky_redetect_logprob = -0.5 """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.whisper.sticky_redetect_logprob == -0.5 def test_llm_context_and_compaction_defaults(): with tempfile.TemporaryDirectory() as tmpdir: import os old_cwd = os.getcwd() try: os.chdir(tmpdir) cfg = load_config() finally: os.chdir(old_cwd) assert cfg.llm.max_context_tokens == 131072 assert cfg.llm.compaction_safety_margin == 256 assert cfg.llm.compaction_enabled is True assert cfg.llm.compaction_trigger_ratio == 0.8 assert cfg.llm.compaction_keep_turns == 6 assert cfg.llm.compaction_max_tokens == 512 assert cfg.llm.compaction_timeout_s == 30.0 assert cfg.llm.compaction_input_tokens == 8192 assert cfg.llm.inference_queue_size == 8 assert cfg.llm.prompt_budget == 131072 - 1024 - 256 assert not hasattr(cfg.llm, "max_prompt_tokens") def test_llm_context_and_compaction_fields_parsed(): config_toml = """ [llm] max_context_tokens = 8192 max_tokens = 2048 compaction_safety_margin = 128 compaction_enabled = false compaction_trigger_ratio = 0.5 compaction_keep_turns = 2 compaction_max_tokens = 256 compaction_timeout_s = 12.5 compaction_input_tokens = 4096 inference_queue_size = 3 """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.llm.max_context_tokens == 8192 assert cfg.llm.compaction_safety_margin == 128 assert cfg.llm.compaction_enabled is False assert cfg.llm.compaction_trigger_ratio == 0.5 assert cfg.llm.compaction_keep_turns == 2 assert cfg.llm.compaction_max_tokens == 256 assert cfg.llm.compaction_timeout_s == 12.5 assert cfg.llm.compaction_input_tokens == 4096 assert cfg.llm.inference_queue_size == 3 assert cfg.llm.prompt_budget == 8192 - 2048 - 128 def test_max_prompt_tokens_is_deprecated_and_ignored(caplog): config_toml = """ [llm] max_prompt_tokens = 50 """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) with caplog.at_level("WARNING", logger="stt_daemon.config"): cfg = load_config(str(config_path)) assert "max_prompt_tokens" in caplog.text assert "deprecated" in caplog.text assert cfg.llm.prompt_budget == 131072 - 1024 - 256 def test_backend_defaults_to_faster_whisper(): """backend defaults to 'faster-whisper'.""" with tempfile.TemporaryDirectory() as tmpdir: import os old_cwd = os.getcwd() try: os.chdir(tmpdir) cfg = load_config() finally: os.chdir(old_cwd) assert cfg.whisper.backend == "faster-whisper" def test_backend_pywhispercpp_is_parsed(): """backend 'pywhispercpp' is parsed from TOML.""" config_toml = """ [whisper] backend = "pywhispercpp" """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.whisper.backend == "pywhispercpp" def test_backend_is_normalized(): """Backend names are normalized: stripped, lowercased, underscores to dashes.""" config_toml = """ [whisper] backend = " Faster_Whisper " """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) cfg = load_config(str(config_path)) assert cfg.whisper.backend == "faster-whisper" def test_unknown_backend_falls_back_to_faster_whisper(caplog): """An unknown backend value falls back to 'faster-whisper' with a warning.""" config_toml = """ [whisper] backend = "bogus" """ with tempfile.TemporaryDirectory() as tmpdir: config_path = Path(tmpdir) / "config.toml" config_path.write_text(config_toml) with caplog.at_level("WARNING", logger="stt_daemon.config"): cfg = load_config(str(config_path)) assert cfg.whisper.backend == "faster-whisper" assert "Unknown whisper backend" in caplog.text assert "bogus" in caplog.text