from __future__ import annotations import json import os from pathlib import Path import tempfile import unittest from unittest.mock import patch from pi_dictate_service.config import ( ConfigError, CredentialError, load_config, parse_config, provider_keywords, read_credential, ) from pi_dictate_service.recorder import audio_level_window_bytes, pw_record_command class ConfigTests(unittest.TestCase): def test_recorder_supports_both_backend_sample_rates(self) -> None: self.assertEqual(pw_record_command(16_000)[5], "16000") self.assertEqual(pw_record_command(24_000)[5], "24000") self.assertEqual(audio_level_window_bytes(24_000), 4_800) def test_rejects_unknown_selected_fields_and_invalid_backend_values(self) -> None: for invalid_backend in ([], {}, 1, True, None): with self.subTest(backend=invalid_backend): with self.assertRaisesRegex(ConfigError, "backend must be"): parse_config({"version": 1, "backend": invalid_backend}) base = { "version": 1, "backend": "openai", "openai": { "model": "gpt-live-transcribe", "apiKeyFile": "~/.config/pi-dictate/credentials/openai-api-key", "termsPath": "/tmp/terms", }, } with patch.dict(os.environ, {"HOME": "/tmp/pi-dictate-test"}): invalid = {**base, "openai": {**base["openai"], "endpoint": "https://bad"}} with self.assertRaises(ConfigError): parse_config(invalid) with self.assertRaises(ConfigError): parse_config({**base, "version": 2}) configured = parse_config( {**base, "openai": {**base["openai"], "delay": "low"}} ) self.assertEqual(configured.openai.delay, "low") with self.assertRaises(ConfigError): parse_config({**base, "openai": {**base["openai"], "delay": "fast"}}) with self.assertRaises(ConfigError): parse_config({**base, "openai": {**base["openai"], "termsPath": "relative/terms"}}) def test_reads_only_one_trailing_newline_from_a_private_credential(self) -> None: with tempfile.TemporaryDirectory() as directory: path = Path(directory) / "key" path.write_text("secret\n", encoding="utf-8") path.chmod(0o600) self.assertEqual(read_credential(path), "secret") path.chmod(0o640) with self.assertRaises(CredentialError): read_credential(path) path.chmod(0o600) path.write_text("secret\nextra", encoding="utf-8") with self.assertRaises(CredentialError): read_credential(path) def test_rejects_a_credential_reached_through_a_symlinked_directory(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) actual = root / "actual" actual.mkdir() key = actual / "key" key.write_text("secret", encoding="utf-8") key.chmod(0o600) linked = root / "credentials" linked.symlink_to(actual, target_is_directory=True) with self.assertRaisesRegex(CredentialError, "ancestry"): read_credential(linked / "key") def test_config_file_is_regular_private_and_provider_terms_reject_cr(self) -> None: with tempfile.TemporaryDirectory() as directory: root = Path(directory) config_path = root / "config.json" config_path.write_text( json.dumps( { "version": 1, "backend": "local", "local": { "adapter": "nemo-parakeet", "modelPath": "/tmp/model.nemo", "modelSha256": "0" * 64, "termsPath": "/tmp/terms", "boostAlpha": 1.0, }, } ), encoding="utf-8", ) config_path.chmod(0o600) self.assertEqual(load_config(config_path).backend, "local") terms = root / "terms" terms.write_bytes(b"bad\rterm") with self.assertRaises(ConfigError): provider_keywords(terms) terms.write_bytes(b"safe\nbad\x00term\n") with self.assertRaises(ConfigError): provider_keywords(terms) if __name__ == "__main__": unittest.main()