generated from john/python-template
Pure remediation; no behavior change. Every item traces to a finding in docs/architecture_code_review_2026-08-17.md. Deletions - Delete app_state.py, which had zero importers and whose get_session_factory raised TypeError at runtime [HIGH-01]. - Delete services/transcription.py and point build_prompt_execution imports at services/sources.py; drop the store.py compatibility aliases [MED-05]. - Delete ServiceBase.queue and its unparameterized asyncio.Queue [MED-07]. - Delete db/operations.get_next_queued_job, a divergent duplicate [CRIT-01]. - Drop the discarded load_docs parameter from list_jobs [LOW-03]. Config - Delete worker_retry_backoff_seconds; no backoff behavior existed anywhere, so wiring it would have been a new feature [MED-02]. - Wire sqlite_check_same_thread through get_engine. The engine hardcoded the setting's own default, so this preserves behavior exactly [MED-02]. - Replace DATABASE_URL in docker-compose.yml with the nested DATABASE__DRIVER / DATABASE__PATH names. Settings uses env_nested_delimiter with extra="ignore", so DATABASE_URL was silently discarded [MED-10]. UI - Move the 23KB inline VIBESCRIBE_LOGO_SVG to ui/static/vibescribe_logo.svg and load it through a cached read_svg sibling of read_css [MED-09]. - Route the portrait upload failure through error_presenter.show_error [LOW-07]. - Cancel the job detail auto-refresh timer instead of only deactivating it, and name its interval constant [LOW-06]. Worker - Make WorkerNotifier runtime_checkable and validate the resolved object in resolve_worker_notifier, which previously returned any non-None attribute unchecked [LOW-04]. Docs and lint - Fix two stale paths in services.instructions.md, one of which pointed at the module deleted here [LOW-02]. - ruff check --fix to zero [LOW-01]. Verified: 264 passed, 4 skipped; ruff check clean. Co-authored-by: Copilot App <[email protected]>
152 lines
5.6 KiB
Python
152 lines
5.6 KiB
Python
"""Tests for transcription.config — settings loading, provider defaults, and paths."""
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from transcription.config import Provider
|
|
from transcription.config import Settings
|
|
from transcription.config import parse_cli_settings
|
|
|
|
|
|
def _make_settings(**overrides) -> Settings:
|
|
"""Build a Settings instance with a dummy API key unless overridden."""
|
|
defaults = {"openrouter_api_key": "test-key-abc123", "provider_models": None}
|
|
defaults.update(overrides)
|
|
return Settings(**defaults)
|
|
|
|
|
|
class TestSettingsLoading:
|
|
"""Verify Settings construction and required-field validation."""
|
|
|
|
def test_loads_from_env(self, monkeypatch):
|
|
"""Settings constructs when OPENROUTER_API_KEY is provided."""
|
|
monkeypatch.setenv("OPENROUTER_API_KEY", "test-key-xyz")
|
|
settings = Settings()
|
|
assert settings.openrouter_api_key.get_secret_value() == "test-key-xyz"
|
|
|
|
def test_requires_api_key(self, monkeypatch):
|
|
"""Settings raises ValidationError when OPENROUTER_API_KEY is missing."""
|
|
monkeypatch.delenv("OPENROUTER_API_KEY", raising=False)
|
|
with pytest.raises(ValidationError):
|
|
Settings(_env_file=None)
|
|
|
|
def test_ignores_process_cli_arguments(self, monkeypatch):
|
|
"""Ordinary settings construction does not consume tooling arguments."""
|
|
monkeypatch.setattr("sys.argv", ["pytest", "--rootdir=/tmp/project"])
|
|
|
|
settings = _make_settings()
|
|
|
|
assert settings.port == 8000
|
|
|
|
def test_explicit_cli_parser_reads_arguments(self):
|
|
"""The executable settings boundary accepts application CLI flags."""
|
|
settings = parse_cli_settings(
|
|
[
|
|
"--openrouter-api-key",
|
|
"test-key",
|
|
"--port",
|
|
"8123",
|
|
"--reload",
|
|
]
|
|
)
|
|
|
|
assert settings.openrouter_api_key.get_secret_value() == "test-key"
|
|
assert settings.port == 8123
|
|
assert settings.reload is True
|
|
|
|
|
|
class TestProviderSettings:
|
|
"""Verify provider enum defaults and validation."""
|
|
|
|
def test_defaults_to_openrouter(self):
|
|
"""Default provider is openrouter when not explicitly set."""
|
|
settings = _make_settings()
|
|
assert settings.provider == Provider.OPENROUTER
|
|
assert settings.provider == "openrouter"
|
|
|
|
def test_rejects_invalid_value(self):
|
|
"""Setting PROVIDER to an invalid value raises ValidationError."""
|
|
with pytest.raises(ValidationError):
|
|
_make_settings(provider="not-a-provider")
|
|
|
|
def test_optional_provider_header_fields_default_to_none(self):
|
|
"""openrouter_http_referer and openrouter_app_title are None when unset."""
|
|
settings = _make_settings()
|
|
assert settings.openrouter_http_referer is None
|
|
assert settings.openrouter_app_title is None
|
|
|
|
@pytest.mark.parametrize(
|
|
("field", "value"),
|
|
[
|
|
("transcription_temperature", -0.1),
|
|
("transcription_temperature", 2.1),
|
|
("transcription_top_p", -0.1),
|
|
("transcription_top_p", 1.1),
|
|
],
|
|
)
|
|
def test_rejects_sampling_values_outside_provider_ranges(self, field, value):
|
|
with pytest.raises(ValidationError):
|
|
_make_settings(**{field: value})
|
|
|
|
def test_rejects_prompt_paths_outside_prompt_directory(self):
|
|
with pytest.raises(ValidationError):
|
|
_make_settings(default_prompt_name="../secret.md")
|
|
|
|
def test_settings_are_immutable_runtime_snapshots(self):
|
|
settings = _make_settings()
|
|
|
|
with pytest.raises(ValidationError):
|
|
settings.port = 9000
|
|
|
|
def test_provider_model_accepts_env_default(self, monkeypatch):
|
|
"""provider_model is sourced when provided through environment configuration."""
|
|
monkeypatch.setenv("PROVIDER_MODEL", "google/gemini-2.5-flash")
|
|
settings = Settings(openrouter_api_key="test-key-abc123")
|
|
assert settings.provider_model == "google/gemini-2.5-flash"
|
|
|
|
def test_provider_models_defaults_to_default_model(self):
|
|
settings = _make_settings(provider_model="vendor/default")
|
|
|
|
assert settings.provider_models == ("vendor/default",)
|
|
|
|
def test_provider_models_are_default_first_trimmed_and_deduplicated(self):
|
|
settings = _make_settings(
|
|
provider_model=" vendor/default ",
|
|
provider_models=["vendor/alternate", "vendor/default", " vendor/other "],
|
|
)
|
|
|
|
assert settings.provider_models == ("vendor/default", "vendor/alternate", "vendor/other")
|
|
|
|
def test_provider_models_rejects_empty_list(self):
|
|
with pytest.raises(ValidationError):
|
|
_make_settings(provider_models=[])
|
|
|
|
def test_provider_models_loads_json_from_environment(self, monkeypatch):
|
|
monkeypatch.setenv("PROVIDER_MODEL", "vendor/default")
|
|
monkeypatch.setenv("PROVIDER_MODELS", '["vendor/alternate","vendor/default"]')
|
|
|
|
settings = Settings(openrouter_api_key="test-key-abc123")
|
|
|
|
assert settings.provider_models == ("vendor/default", "vendor/alternate")
|
|
|
|
|
|
class TestPathSettings:
|
|
"""Verify filesystem path field types."""
|
|
|
|
def test_path_fields_are_path_objects(self):
|
|
"""upload_dir and prompt_dir are Path instances."""
|
|
settings = _make_settings()
|
|
assert isinstance(settings.upload_dir, Path)
|
|
assert isinstance(settings.prompt_dir, Path)
|
|
|
|
|
|
class TestWorkerReliabilitySettings:
|
|
"""Verify worker retry settings defaults."""
|
|
|
|
def test_worker_retry_defaults(self):
|
|
"""worker retry settings default to no retries."""
|
|
settings = _make_settings()
|
|
assert settings.worker_max_retries == 0
|