feat: modernize project with uv, add pytest test suite
- Add pyproject.toml with modern Python packaging (PEP 517/518) - Add uv-based dependency management replacing inline venv bootstrap - Add requirements.txt and requirements-dev.txt for compatibility - Add mise.toml with dev tasks (test, lint, run, sync, ci) - Add .python-version pinned to Python 3.12 - Add comprehensive pytest test suite (73 tests) for: - engine/config, filter, terminal, sources, mic, ntfy modules - Configure pytest with coverage reporting (16% total, 100% on tested modules) - Configure ruff for linting with Python 3.10+ target - Remove redundant venv bootstrap code from mainline.py - Update .gitignore for uv/venv artifacts Run 'uv sync' to install dependencies, 'uv run pytest' to test.
This commit is contained in:
162
tests/test_config.py
Normal file
162
tests/test_config.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""
|
||||
Tests for engine.config module.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from engine import config
|
||||
|
||||
|
||||
class TestArgValue:
|
||||
"""Tests for _arg_value helper."""
|
||||
|
||||
def test_returns_value_when_flag_present(self):
|
||||
"""Returns the value following the flag."""
|
||||
with patch.object(sys, "argv", ["prog", "--font-file", "test.otf"]):
|
||||
result = config._arg_value("--font-file")
|
||||
assert result == "test.otf"
|
||||
|
||||
def test_returns_none_when_flag_missing(self):
|
||||
"""Returns None when flag is not present."""
|
||||
with patch.object(sys, "argv", ["prog"]):
|
||||
result = config._arg_value("--font-file")
|
||||
assert result is None
|
||||
|
||||
def test_returns_none_when_no_value(self):
|
||||
"""Returns None when flag is last."""
|
||||
with patch.object(sys, "argv", ["prog", "--font-file"]):
|
||||
result = config._arg_value("--font-file")
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestArgInt:
|
||||
"""Tests for _arg_int helper."""
|
||||
|
||||
def test_parses_valid_int(self):
|
||||
"""Parses valid integer."""
|
||||
with patch.object(sys, "argv", ["prog", "--font-index", "5"]):
|
||||
result = config._arg_int("--font-index", 0)
|
||||
assert result == 5
|
||||
|
||||
def test_returns_default_on_invalid(self):
|
||||
"""Returns default on invalid input."""
|
||||
with patch.object(sys, "argv", ["prog", "--font-index", "abc"]):
|
||||
result = config._arg_int("--font-index", 0)
|
||||
assert result == 0
|
||||
|
||||
def test_returns_default_when_missing(self):
|
||||
"""Returns default when flag missing."""
|
||||
with patch.object(sys, "argv", ["prog"]):
|
||||
result = config._arg_int("--font-index", 10)
|
||||
assert result == 10
|
||||
|
||||
|
||||
class TestResolveFontPath:
|
||||
"""Tests for _resolve_font_path helper."""
|
||||
|
||||
def test_returns_absolute_paths(self):
|
||||
"""Absolute paths are returned as-is."""
|
||||
result = config._resolve_font_path("/absolute/path.otf")
|
||||
assert result == "/absolute/path.otf"
|
||||
|
||||
def test_resolves_relative_paths(self):
|
||||
"""Relative paths are resolved to repo root."""
|
||||
result = config._resolve_font_path("fonts/test.otf")
|
||||
assert str(config._REPO_ROOT) in result
|
||||
|
||||
def test_expands_user_home(self):
|
||||
"""Tilde paths are expanded."""
|
||||
with patch("pathlib.Path.expanduser", return_value=Path("/home/user/fonts")):
|
||||
result = config._resolve_font_path("~/fonts/test.otf")
|
||||
assert isinstance(result, str)
|
||||
|
||||
|
||||
class TestListFontFiles:
|
||||
"""Tests for _list_font_files helper."""
|
||||
|
||||
def test_returns_empty_for_missing_dir(self):
|
||||
"""Returns empty list for missing directory."""
|
||||
result = config._list_font_files("/nonexistent/directory")
|
||||
assert result == []
|
||||
|
||||
def test_filters_by_extension(self):
|
||||
"""Only returns valid font extensions."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
Path(tmpdir, "valid.otf").touch()
|
||||
Path(tmpdir, "valid.ttf").touch()
|
||||
Path(tmpdir, "invalid.txt").touch()
|
||||
Path(tmpdir, "image.png").touch()
|
||||
|
||||
result = config._list_font_files(tmpdir)
|
||||
assert len(result) == 2
|
||||
assert all(f.endswith((".otf", ".ttf")) for f in result)
|
||||
|
||||
def test_sorts_alphabetically(self):
|
||||
"""Results are sorted alphabetically."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
Path(tmpdir, "zfont.otf").touch()
|
||||
Path(tmpdir, "afont.otf").touch()
|
||||
|
||||
result = config._list_font_files(tmpdir)
|
||||
filenames = [Path(f).name for f in result]
|
||||
assert filenames == ["afont.otf", "zfont.otf"]
|
||||
|
||||
|
||||
class TestDefaults:
|
||||
"""Tests for default configuration values."""
|
||||
|
||||
def test_headline_limit(self):
|
||||
"""HEADLINE_LIMIT has sensible default."""
|
||||
assert config.HEADLINE_LIMIT > 0
|
||||
|
||||
def test_feed_timeout(self):
|
||||
"""FEED_TIMEOUT has sensible default."""
|
||||
assert config.FEED_TIMEOUT > 0
|
||||
|
||||
def test_font_extensions(self):
|
||||
"""Font extensions are defined."""
|
||||
assert ".otf" in config._FONT_EXTENSIONS
|
||||
assert ".ttf" in config._FONT_EXTENSIONS
|
||||
assert ".ttc" in config._FONT_EXTENSIONS
|
||||
|
||||
|
||||
class TestGlyphs:
|
||||
"""Tests for glyph constants."""
|
||||
|
||||
def test_glitch_glyphs_defined(self):
|
||||
"""GLITCH glyphs are defined."""
|
||||
assert len(config.GLITCH) > 0
|
||||
|
||||
def test_kata_glyphs_defined(self):
|
||||
"""KATA glyphs are defined."""
|
||||
assert len(config.KATA) > 0
|
||||
|
||||
|
||||
class TestSetFontSelection:
|
||||
"""Tests for set_font_selection function."""
|
||||
|
||||
def test_updates_font_path(self):
|
||||
"""Updates FONT_PATH globally."""
|
||||
original = config.FONT_PATH
|
||||
config.set_font_selection(font_path="/new/path.otf")
|
||||
assert config.FONT_PATH == "/new/path.otf"
|
||||
config.FONT_PATH = original
|
||||
|
||||
def test_updates_font_index(self):
|
||||
"""Updates FONT_INDEX globally."""
|
||||
original = config.FONT_INDEX
|
||||
config.set_font_selection(font_index=5)
|
||||
assert config.FONT_INDEX == 5
|
||||
config.FONT_INDEX = original
|
||||
|
||||
def test_handles_none_values(self):
|
||||
"""Handles None values gracefully."""
|
||||
original_path = config.FONT_PATH
|
||||
original_index = config.FONT_INDEX
|
||||
|
||||
config.set_font_selection(font_path=None, font_index=None)
|
||||
assert original_path == config.FONT_PATH
|
||||
assert original_index == config.FONT_INDEX
|
||||
Reference in New Issue
Block a user