diff --git a/README.md b/README.md index 725c604..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,6 +0,0 @@ -# Voice Transcriptor - -The editable default transcription model is `gpt-4o-transcribe`. New installations use -Brazilian Portuguese (`pt-BR`) and save output to the user's Documents folder when it is -available, otherwise to the home folder. API keys are stored only in the operating-system -keyring and are never written to the settings file. diff --git a/src/voice_transcriptor/services/settings.py b/src/voice_transcriptor/services/settings.py index c850df9..b8dff72 100644 --- a/src/voice_transcriptor/services/settings.py +++ b/src/voice_transcriptor/services/settings.py @@ -66,7 +66,10 @@ class SettingsRepository: raise SettingsError("Unable to save settings.") from exc finally: if temporary_path is not None and temporary_path.exists(): - temporary_path.unlink(missing_ok=True) + try: + temporary_path.unlink(missing_ok=True) + except OSError: + pass @staticmethod def _settings_from_payload(payload: object) -> AppSettings: diff --git a/tests/test_settings.py b/tests/test_settings.py index d393b62..85b57b2 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -4,7 +4,7 @@ from pathlib import Path import pytest from voice_transcriptor.models import AppSettings -from voice_transcriptor.services.settings import SettingsRepository +from voice_transcriptor.services.settings import SettingsError, SettingsRepository def test_load_missing_file_returns_documented_defaults(monkeypatch, tmp_path: Path) -> None: @@ -78,6 +78,27 @@ def test_save_replaces_existing_file_atomically(monkeypatch, tmp_path: Path) -> assert json.loads(path.read_text(encoding="utf-8"))["model"] == "model" +def test_save_preserves_settings_error_when_replace_and_cleanup_fail( + monkeypatch, tmp_path: Path +) -> None: + path = tmp_path / "settings.json" + replacement_failure = OSError("target is locked") + + def failing_replace(source: Path, destination: Path) -> Path: + raise replacement_failure + + def failing_unlink(path: Path, missing_ok: bool = False) -> None: + raise OSError("temporary file is locked") + + monkeypatch.setattr(Path, "replace", failing_replace) + monkeypatch.setattr(Path, "unlink", failing_unlink) + + with pytest.raises(SettingsError) as caught: + SettingsRepository(path).save(AppSettings("model", "pt-BR", tmp_path / "out")) + + assert caught.value.__cause__ is replacement_failure + + def test_saved_settings_never_include_api_key_fields(tmp_path: Path) -> None: path = tmp_path / "settings.json"