feat+test: pytest suite + Now Playing + World Clock + Daily Quote packs (154 commands)
Adds first formal test suite for Python fork — 42 tests covering memory_store,
profiles_store, scheduler_store, macros_store, llm_backend. All pass.
Tests (tests/, ~290 lines)
conftest.py — `isolated_state` fixture: rewires _PATH/_PROFILES_DIR etc to
a per-test tmp_path so concurrent runs don't touch real data.
test_memory_store.py — 9 tests: remember/recall/forget round-trip,
search by key/value, limit/empty query,
persistence across reload, build_llm_context
test_profiles_store.py — 7 tests: seeded defaults, set_active persistence,
allows_command logic (default/work/driving)
test_scheduler_store.py — 12 tests: parser (daily/at/every/in, ru units,
bad input), add/remove/clear/remove_by_text,
_next_fire for each schedule kind
test_macros_store.py — 8 tests: is_macro_control filter, start/save
round-trip, control-phrase filtering, list_names
sorted, replay unknown raises
test_llm_backend.py — 6 tests: parse_backend aliases (ru + en),
persist round-trip, auto-detect logic
Run: cd /c/Jarvis/python && python -m pytest tests/
Three new fun packs (commands.yaml: 151 → 154)
now_playing — Windows Media Session API via PowerShell
"что играет" / "что за песня" / "какой трек"
Works with Spotify, YouTube, Foobar, Yandex Music, anything
that exposes SMTC (Win10 1803+).
world_clock — worldtimeapi.org (free, no key)
"сколько времени в Токио" / "время в Лондоне"
21 Russian + world cities pre-mapped to IANA timezones.
daily_quote — zenquotes.io (free, no key) + LLM translation
"цитата дня" / "вдохнови меня"
Fetches English quote, translates to Russian via active LLM
(Groq or Ollama), speaks "text — author."
Pack count: 151 → 154. Tests: 42 pytest + ast.parse + yaml.safe_load.
This commit is contained in:
parent
8d3da4ea06
commit
4e62049a98
10 changed files with 637 additions and 0 deletions
45
tests/conftest.py
Normal file
45
tests/conftest.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
"""Pytest config: temp-dir isolation for state modules.
|
||||
|
||||
Each test gets a fresh dir for memory.json / schedule.json / etc so concurrent
|
||||
runs don't pollute the real user data.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import importlib
|
||||
import pytest
|
||||
|
||||
# Make parent package importable.
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def isolated_state(tmp_path, monkeypatch):
|
||||
"""Patch state modules to use a tmp_path so we don't touch real state files."""
|
||||
|
||||
def patch_module(mod_name, path_attrs):
|
||||
"""Reimport a module and rewire its `_PATH` and friends to tmp_path."""
|
||||
if mod_name in sys.modules:
|
||||
del sys.modules[mod_name]
|
||||
mod = importlib.import_module(mod_name)
|
||||
for attr in path_attrs:
|
||||
if not hasattr(mod, attr):
|
||||
continue
|
||||
original = getattr(mod, attr)
|
||||
new_val = os.path.join(str(tmp_path), os.path.basename(original))
|
||||
monkeypatch.setattr(mod, attr, new_val)
|
||||
# Also reset any cached _loaded / _initialized flags
|
||||
for flag in ('_loaded', '_initialized', '_thread_started'):
|
||||
if hasattr(mod, flag):
|
||||
monkeypatch.setattr(mod, flag, False)
|
||||
# Reset stores
|
||||
for store_attr in ('_store', '_tasks'):
|
||||
if hasattr(mod, store_attr):
|
||||
cur = getattr(mod, store_attr)
|
||||
if isinstance(cur, dict):
|
||||
monkeypatch.setattr(mod, store_attr, {})
|
||||
elif isinstance(cur, list):
|
||||
monkeypatch.setattr(mod, store_attr, [])
|
||||
return mod
|
||||
|
||||
yield patch_module
|
||||
Loading…
Add table
Add a link
Reference in a new issue