Files
hermes-webui/tests/test_webui_notes_sources.py
T
2026-05-19 10:28:00 -04:00

175 lines
6.8 KiB
Python

"""Regression tests for WebUI notes source discovery."""
from __future__ import annotations
def test_notes_sources_identifies_note_or_knowledge_mcp_servers():
from api.routes import _notes_sources_from_mcp_inventory
servers = {
"joplin": {"name": "joplin", "enabled": True, "active": True, "status": "healthy"},
"filesystem": {"name": "filesystem", "enabled": True, "active": True, "status": "healthy"},
"llm-wiki": {"name": "llm-wiki", "enabled": True, "active": False, "status": "configured"},
}
tools = [
{"server": "joplin", "name": "search_notes", "description": "Search notes by keyword"},
{"server": "joplin", "name": "get_note", "description": "Get full note content"},
{"server": "filesystem", "name": "read_text_file", "description": "Read files"},
{"server": "llm-wiki", "name": "query_knowledge_base", "description": "Search wiki knowledge"},
]
sources = _notes_sources_from_mcp_inventory(servers, tools)
assert [source["name"] for source in sources] == ["joplin", "llm-wiki"]
assert sources[0]["label"] == "Joplin"
assert sources[0]["tool_count"] == 2
assert sources[0]["active"] is True
assert sources[1]["active"] is False
def test_notes_sources_redacts_tool_descriptions_and_omits_plain_file_tools():
from api.routes import _notes_sources_from_mcp_inventory
servers = {"notion": {"name": "notion", "enabled": True, "active": True, "status": "healthy"}}
tools = [
{"server": "notion", "name": "search_pages", "description": "Search notes api_key=redaction-test-placeholder"},
]
[source] = _notes_sources_from_mcp_inventory(servers, tools)
assert source["name"] == "notion"
assert "token" not in source["tools"][0]["description"].lower()
assert "[REDACTED]" in source["tools"][0]["description"]
def test_notes_sources_shows_configured_note_servers_without_tool_inventory():
from api.routes import _notes_sources_from_mcp_inventory
servers = {
"joplin": {"name": "joplin", "enabled": True, "active": False, "status": "configured"},
"filesystem": {"name": "filesystem", "enabled": True, "active": True, "status": "healthy"},
}
sources = _notes_sources_from_mcp_inventory(servers, [])
assert [source["name"] for source in sources] == ["joplin"]
assert sources[0]["label"] == "Joplin"
assert sources[0]["tool_count"] == 3
assert [tool["name"] for tool in sources[0]["tools"]] == ["search_notes", "list_notes", "get_note"]
assert all(tool.get("inferred") is True for tool in sources[0]["tools"])
assert sources[0]["tool_source"] == "configured_hint"
assert sources[0]["status"] == "configured"
def test_joplin_search_notes_returns_safe_snippets(monkeypatch):
from api import routes
def fake_get(path, params=None):
assert path == "/search"
assert params["type"] == "note"
return {"items": [{
"id": "abc123def4567890",
"title": "Hermes Context",
"body": "This is a long Hermes context note with useful details.",
"parent_id": "folder123",
"updated_time": 123,
}]}
monkeypatch.setattr(routes, "_joplin_api_get", fake_get)
results = routes._joplin_search_notes("Hermes")
assert results == [{
"id": "abc123def4567890",
"title": "Hermes Context",
"snippet": "This is a long Hermes context note with useful details.",
"parent_id": "folder123",
"updated_time": 123,
"source": "joplin",
}]
def test_joplin_get_note_validates_id_and_truncates_body(monkeypatch):
from api import routes
def fake_get(path, params=None):
assert path == "/notes/abc123def4567890"
return {
"id": "abc123def4567890",
"title": "Big Note",
"body": "x" * 60000,
"parent_id": "folder123",
"updated_time": 456,
"created_time": 123,
}
monkeypatch.setattr(routes, "_joplin_api_get", fake_get)
note = routes._joplin_get_note("abc123def4567890")
assert note["title"] == "Big Note"
assert note["source"] == "joplin"
assert len(note["body"]) < 51000
assert "Preview truncated" in note["body"]
def test_joplin_recent_ai_notes_uses_configured_prefill_script(monkeypatch, tmp_path):
from api import routes
script = tmp_path / "joplin_context.py"
script.write_text(
'\n'.join([
'CURRENT_CONTEXT_ID = "5ba9ab822c344115939205ca4e8eaec0"',
'OPEN_ISSUES_ID = "623aeb6e55cb4aa39a0541f2ac09aa36"',
'AGENT_MEMORY_ID = "0a7a232ea46b4b8bb0bbd4358f725a84"',
'RAW_CAPTURES_ID = "cb1087795c7d4129a863ab0a5642233d"',
]),
encoding="utf-8",
)
monkeypatch.setattr(routes, "get_config", lambda: {"prefill_messages_script": str(script)})
def fake_get(path, params=None):
note_id = path.rsplit("/", 1)[-1]
titles = {
"5ba9ab822c344115939205ca4e8eaec0": "Current Context",
"623aeb6e55cb4aa39a0541f2ac09aa36": "Open Issues",
"0a7a232ea46b4b8bb0bbd4358f725a84": "Agent Memory",
}
assert note_id in titles
return {"id": note_id, "title": titles[note_id], "updated_time": 123, "parent_id": "folder"}
monkeypatch.setattr(routes, "_joplin_api_get", fake_get)
notes = routes._joplin_recent_ai_notes(limit=3)
assert [note["title"] for note in notes] == ["Current Context", "Open Issues", "Agent Memory"]
assert all(note["source"] == "joplin" for note in notes)
assert all(note["used_by"] == "ai_prefill" for note in notes)
assert all(note["used_reason"] == "automatic_recall" for note in notes)
def test_external_notes_ui_uses_minimal_lucide_icons_for_ai_recent_notes():
from pathlib import Path
panels = Path("static/panels.js").read_text(encoding="utf-8")
start = panels.index("function _renderExternalNotesSources()")
end = panels.index("function _renderMemoryDetail", start)
notes_block = panels[start:end]
assert "notes-ai-recent-card" in notes_block
assert "li('bot', 14)" in notes_block
assert "li('clock', 14)" in notes_block
assert "Recently used by AI" not in notes_block # i18n key, not hard-coded UI copy
assert "🤖" not in notes_block
assert "📚" not in notes_block
def test_external_notes_search_button_matches_minimal_dark_controls():
from pathlib import Path
css = Path("static/style.css").read_text(encoding="utf-8")
assert ".notes-search-form button" in css
button_block = css[css.index(".notes-search-form button"):css.index(".notes-search-form button:hover")]
assert "background:var(--panel)" in button_block or "background:var(--surface)" in button_block
assert "border:1px solid var(--border)" in button_block
assert "color:var(--text)" in button_block
assert "border-radius:10px" in button_block