J.A.R.V.I.S-py/tests/test_memory_store.py

80 lines
2.6 KiB
Python
Raw Permalink Normal View History

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.
2026-05-16 00:56:02 +03:00
"""Tests for memory_store — port of long_term_memory tests in rust."""
import json
import os
import pytest
def test_remember_recall_round_trip(isolated_state):
m = isolated_state('memory_store', ['_PATH'])
m.remember("любимый чай", "улун")
assert m.recall("любимый чай") == "улун"
assert m.recall("Любимый Чай") == "улун" # case-insensitive
def test_recall_returns_none_for_missing(isolated_state):
m = isolated_state('memory_store', ['_PATH'])
assert m.recall("nonexistent") is None
def test_forget_removes(isolated_state):
m = isolated_state('memory_store', ['_PATH'])
m.remember("foo", "bar")
assert m.forget("foo") is True
assert m.recall("foo") is None
assert m.forget("foo") is False # idempotent
def test_search_by_key_and_value(isolated_state):
m = isolated_state('memory_store', ['_PATH'])
m.remember("любимый чай", "улун")
m.remember("любимый кофе", "арабика")
m.remember("питомец", "собака бася")
hits = m.search("чай", 5)
assert len(hits) == 1
assert hits[0]['key'] == "любимый чай"
hits = m.search("бася", 5)
assert len(hits) == 1
assert hits[0]['key'] == "питомец"
def test_search_respects_limit(isolated_state):
m = isolated_state('memory_store', ['_PATH'])
for i in range(10):
m.remember(f"key{i}", "value")
hits = m.search("value", 3)
assert len(hits) == 3
def test_search_empty_query_returns_nothing(isolated_state):
m = isolated_state('memory_store', ['_PATH'])
m.remember("foo", "bar")
assert m.search("", 5) == []
assert m.search(" ", 5) == []
def test_persists_across_module_reload(isolated_state, tmp_path):
m = isolated_state('memory_store', ['_PATH'])
m.remember("persistent", "yes")
json_file = m._PATH
assert os.path.isfile(json_file)
with open(json_file, encoding='utf-8') as f:
data = json.load(f)
assert data['entries']['persistent']['value'] == 'yes'
def test_build_llm_context_format(isolated_state):
m = isolated_state('memory_store', ['_PATH'])
m.remember("любимый чай", "улун")
# Query "чай" is substring of key "любимый чай" → matches.
ctx = m.build_llm_context("чай", 5)
assert "любимый чай = улун" in ctx
assert ctx.startswith("Известные факты")
def test_build_llm_context_empty(isolated_state):
m = isolated_state('memory_store', ['_PATH'])
assert m.build_llm_context("anything", 5) == ""