- 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.
163 lines
5.3 KiB
Python
163 lines
5.3 KiB
Python
"""
|
|
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
|