J.A.R.V.I.S-py/tests/test_plugins_store.py
Bossiara13 59a1258dcc feat: Wave 3 — plugin loader + Flet GUI (parity with Rust fork)
Plugin loader (mirrors Rust #29):
- plugins_store.py walks %APPDATA%\com.priler.jarvis\plugins\<name>\
  for commands.yaml fragments and merges them into VA_CMD_LIST at
  startup. Same schema as the project root commands.yaml. Per-pack
  `disabled` flag file skips a pack without deletion. Built-in ids
  win conflicts so a malicious pack can't silently override
  open_browser. 9 unit tests (parity with the Rust tests).
- main.py: plugin merge runs after the root yaml load; logs the
  number of extra commands picked up.

Flet GUI (#27 — first parity with the Rust Tauri GUI):
- New gui/ package: __main__.py launches via `python -m gui` (or
  gui.bat). app.py routes between 7 pages (Главная / Команды /
  Макросы / Расписание / Память / Плагины / Настройки) via
  NavigationRail; dark theme matching the Tauri look.
- gui/services.py centralises every store-module call the pages
  share — single place to swap implementations later.
- One file per page in gui/pages/, all expose `build(page)` and use
  the same card layout idiom. Pages mirror the Tauri ones feature-
  for-feature: home (status + counters + launcher), commands
  (filterable list of all VA_CMD_LIST entries), macros (list +
  delete + recording banner), scheduler (list + remove), memory
  (add/forget/search), plugins (toggle/open folder), settings
  (profile + LLM hot-swap).
- requirements.txt: flet>=0.85 (optional; assistant runs without).
- 3 smoke tests verify imports + build callables; skip cleanly when
  Flet isn't installed.

README: documents both the GUI and the plugin layout in user-facing
terms. Tests: 42 → 54 (+9 plugins +3 GUI smoke).
2026-05-16 13:27:04 +03:00

96 lines
3 KiB
Python

"""Tests for `plugins_store.py` — mirrors the Rust `plugins.rs` tests."""
import os
import pytest
import plugins_store
def write(path, content):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "wt", encoding="utf8") as f:
f.write(content)
GOOD_YAML = """
my_plugin_hello:
phrases:
- привет плагин
- тест плагин
action: {type: shell, cmd: 'start "" notepad.exe'}
"""
BAD_YAML = "this is :: not valid yaml ::\n ::: -"
def test_discover_empty_dir_returns_empty(tmp_path):
assert plugins_store.discover_in(str(tmp_path)) == {}
def test_discover_loads_yaml(tmp_path):
write(str(tmp_path / "greeter" / "commands.yaml"), GOOD_YAML)
out = plugins_store.discover_in(str(tmp_path))
assert "my_plugin_hello" in out
assert out["my_plugin_hello"]["action"]["type"] == "shell"
def test_disabled_flag_skips_pack(tmp_path):
write(str(tmp_path / "muted" / "commands.yaml"), GOOD_YAML)
write(str(tmp_path / "muted" / plugins_store.DISABLED_FLAG), "")
out = plugins_store.discover_in(str(tmp_path))
assert out == {}
def test_malformed_yaml_does_not_break_other_packs(tmp_path):
write(str(tmp_path / "bad" / "commands.yaml"), BAD_YAML)
write(str(tmp_path / "good" / "commands.yaml"), GOOD_YAML)
out = plugins_store.discover_in(str(tmp_path))
assert "my_plugin_hello" in out
# bad pack contributed nothing
assert len(out) == 1
def test_duplicate_ids_across_packs_first_wins(tmp_path):
write(str(tmp_path / "pack_a" / "commands.yaml"), GOOD_YAML)
write(
str(tmp_path / "pack_b" / "commands.yaml"),
"""
my_plugin_hello:
phrases: [перезаписан]
action: {type: url, href: "http://evil.com"}
""",
)
out = plugins_store.discover_in(str(tmp_path))
# alphabetical order means pack_a wins
assert out["my_plugin_hello"]["action"]["type"] == "shell"
def test_non_dirs_ignored(tmp_path):
write(str(tmp_path / "README.md"), "# not a pack")
out = plugins_store.discover_in(str(tmp_path))
assert out == {}
def test_top_level_must_be_dict(tmp_path):
write(str(tmp_path / "weird" / "commands.yaml"), "- just\n- a\n- list\n")
out = plugins_store.discover_in(str(tmp_path))
assert out == {}
def test_list_packs_in_reports_both_states(tmp_path):
write(str(tmp_path / "alpha" / "commands.yaml"), GOOD_YAML)
write(str(tmp_path / "bravo" / "commands.yaml"), GOOD_YAML)
write(str(tmp_path / "bravo" / plugins_store.DISABLED_FLAG), "")
listing = plugins_store.list_packs_in(str(tmp_path))
by_name = {p["name"]: p for p in listing}
assert by_name["alpha"]["enabled"] is True
assert by_name["bravo"]["enabled"] is False
assert by_name["alpha"]["command_count"] == 1
def test_list_packs_reports_yaml_errors(tmp_path):
write(str(tmp_path / "bad" / "commands.yaml"), BAD_YAML)
listing = plugins_store.list_packs_in(str(tmp_path))
assert len(listing) == 1
assert listing[0]["error"] is not None
assert listing[0]["command_count"] == 0