mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-31 03:12:16 +00:00
test: add 59 tests for system prompt, Slack messaging, and connector health
- test_deep_research_prompt.py (8 tests): date injection, time, day of week, dynamic generation, response types, tool descriptions, /no_think, Jarvis name - test_slack_messaging.py (7 tests): connect with/without tokens, error state, disconnect, handler registration, send without token - test_connector_health.py (44 tests): all 11 connectors instantiate, have required methods, report not-connected without creds, have metadata. Plus KnowledgeStore data presence checks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
7fd685f53a
commit
1add224599
@@ -0,0 +1,89 @@
|
||||
"""Tests for DeepResearch system prompt — date injection and adaptive behavior."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from datetime import datetime
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_system_prompt_contains_current_date() -> None:
|
||||
"""The system prompt includes today's date."""
|
||||
from openjarvis.agents.deep_research import _build_system_prompt
|
||||
|
||||
prompt = _build_system_prompt()
|
||||
today = datetime.now().strftime("%B %d, %Y")
|
||||
assert today in prompt, f"Expected '{today}' in prompt"
|
||||
|
||||
|
||||
def test_system_prompt_contains_current_time() -> None:
|
||||
"""The system prompt includes the current time (hour)."""
|
||||
from openjarvis.agents.deep_research import _build_system_prompt
|
||||
|
||||
prompt = _build_system_prompt()
|
||||
# Just check that a time-like pattern exists (e.g. "02:28 PM")
|
||||
assert re.search(r"\d{1,2}:\d{2} [AP]M", prompt), "No time found in prompt"
|
||||
|
||||
|
||||
def test_system_prompt_contains_day_of_week() -> None:
|
||||
"""The system prompt includes the day of week."""
|
||||
from openjarvis.agents.deep_research import _build_system_prompt
|
||||
|
||||
prompt = _build_system_prompt()
|
||||
day = datetime.now().strftime("%A")
|
||||
assert day in prompt, f"Expected '{day}' in prompt"
|
||||
|
||||
|
||||
def test_system_prompt_is_dynamic() -> None:
|
||||
"""Each call to _build_system_prompt returns a fresh prompt with current time."""
|
||||
from openjarvis.agents.deep_research import _build_system_prompt
|
||||
|
||||
p1 = _build_system_prompt()
|
||||
p2 = _build_system_prompt()
|
||||
# Both should contain "Today is" — they may differ by seconds
|
||||
assert "Today is" in p1
|
||||
assert "Today is" in p2
|
||||
|
||||
|
||||
def test_system_prompt_has_response_types() -> None:
|
||||
"""The prompt describes multiple response types (not just deep research)."""
|
||||
from openjarvis.agents.deep_research import _build_system_prompt
|
||||
|
||||
prompt = _build_system_prompt()
|
||||
assert "Casual" in prompt or "conversational" in prompt
|
||||
assert "Quick data lookup" in prompt or "Quick lookup" in prompt
|
||||
assert "Deep research" in prompt
|
||||
assert "People lookup" in prompt
|
||||
assert "digest" in prompt.lower()
|
||||
assert "Meeting prep" in prompt or "meeting" in prompt.lower()
|
||||
assert "Task" in prompt or "action item" in prompt.lower()
|
||||
assert "Contact analysis" in prompt or "haven't I messaged" in prompt
|
||||
|
||||
|
||||
def test_system_prompt_has_tools() -> None:
|
||||
"""The prompt describes all 4 tools."""
|
||||
from openjarvis.agents.deep_research import _build_system_prompt
|
||||
|
||||
prompt = _build_system_prompt()
|
||||
assert "knowledge_search" in prompt
|
||||
assert "knowledge_sql" in prompt
|
||||
assert "scan_chunks" in prompt
|
||||
assert "think" in prompt
|
||||
|
||||
|
||||
def test_system_prompt_has_no_think_directive() -> None:
|
||||
"""The prompt starts with /no_think for Qwen compatibility."""
|
||||
from openjarvis.agents.deep_research import _build_system_prompt
|
||||
|
||||
prompt = _build_system_prompt()
|
||||
assert prompt.startswith("/no_think")
|
||||
|
||||
|
||||
def test_system_prompt_mentions_jarvis() -> None:
|
||||
"""The agent identifies as Jarvis."""
|
||||
from openjarvis.agents.deep_research import _build_system_prompt
|
||||
|
||||
prompt = _build_system_prompt()
|
||||
assert "Jarvis" in prompt
|
||||
@@ -0,0 +1,91 @@
|
||||
"""Tests for Slack messaging channel — Socket Mode binding and message handling."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_slack_channel_connect_with_tokens() -> None:
|
||||
"""SlackChannel.connect() succeeds with bot_token + app_token."""
|
||||
from openjarvis.channels.slack import SlackChannel
|
||||
|
||||
ch = SlackChannel(
|
||||
bot_token="xoxb-test-token",
|
||||
app_token="xapp-test-token",
|
||||
)
|
||||
# Mock the SocketModeClient import to avoid real connection
|
||||
with patch("openjarvis.channels.slack.SlackChannel._socket_mode_loop"):
|
||||
ch.connect()
|
||||
|
||||
from openjarvis.channels._stubs import ChannelStatus
|
||||
|
||||
assert ch.status() == ChannelStatus.CONNECTED
|
||||
|
||||
|
||||
def test_slack_channel_send_only_without_app_token() -> None:
|
||||
"""SlackChannel with only bot_token enters send-only mode."""
|
||||
from openjarvis.channels.slack import SlackChannel
|
||||
|
||||
ch = SlackChannel(bot_token="xoxb-test-token")
|
||||
ch.connect()
|
||||
|
||||
from openjarvis.channels._stubs import ChannelStatus
|
||||
|
||||
assert ch.status() == ChannelStatus.CONNECTED
|
||||
assert ch._listener_thread is None # No Socket Mode listener
|
||||
|
||||
|
||||
def test_slack_channel_no_token_errors() -> None:
|
||||
"""SlackChannel without any token enters error state."""
|
||||
from openjarvis.channels.slack import SlackChannel
|
||||
|
||||
ch = SlackChannel()
|
||||
ch.connect()
|
||||
|
||||
from openjarvis.channels._stubs import ChannelStatus
|
||||
|
||||
assert ch.status() == ChannelStatus.ERROR
|
||||
|
||||
|
||||
def test_slack_channel_disconnect() -> None:
|
||||
"""SlackChannel.disconnect() sets status to disconnected."""
|
||||
from openjarvis.channels.slack import SlackChannel
|
||||
|
||||
ch = SlackChannel(bot_token="xoxb-test-token")
|
||||
ch.connect()
|
||||
ch.disconnect()
|
||||
|
||||
from openjarvis.channels._stubs import ChannelStatus
|
||||
|
||||
assert ch.status() == ChannelStatus.DISCONNECTED
|
||||
|
||||
|
||||
def test_slack_channel_handler_registration() -> None:
|
||||
"""on_message registers a handler that receives messages."""
|
||||
from openjarvis.channels.slack import SlackChannel
|
||||
|
||||
ch = SlackChannel(bot_token="xoxb-test-token")
|
||||
handler = MagicMock()
|
||||
ch.on_message(handler)
|
||||
assert handler in ch._handlers
|
||||
|
||||
|
||||
def test_slack_channel_send_without_connection_fails_gracefully() -> None:
|
||||
"""send() to an unconnected channel returns False without crashing."""
|
||||
from openjarvis.channels.slack import SlackChannel
|
||||
|
||||
ch = SlackChannel() # No token
|
||||
result = ch.send("C123456", "Hello from test")
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_slack_channel_send_with_token_is_callable() -> None:
|
||||
"""send() with a token is callable and returns a bool."""
|
||||
from openjarvis.channels.slack import SlackChannel
|
||||
|
||||
ch = SlackChannel(bot_token="xoxb-test-token")
|
||||
# Don't actually call the API, just verify interface
|
||||
assert callable(ch.send)
|
||||
assert hasattr(ch, "send")
|
||||
@@ -0,0 +1,158 @@
|
||||
"""Health check tests for all data source connectors.
|
||||
|
||||
Verifies that each connector can be instantiated, reports connection
|
||||
status correctly, and has the required interface methods.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
# All connectors that should be testable without credentials
|
||||
_LOCAL_CONNECTORS = [
|
||||
("apple_notes", "openjarvis.connectors.apple_notes", "AppleNotesConnector"),
|
||||
("imessage", "openjarvis.connectors.imessage", "IMessageConnector"),
|
||||
]
|
||||
|
||||
_TOKEN_CONNECTORS = [
|
||||
("gmail_imap", "openjarvis.connectors.gmail_imap", "GmailIMAPConnector"),
|
||||
("outlook", "openjarvis.connectors.outlook", "OutlookConnector"),
|
||||
("slack", "openjarvis.connectors.slack_connector", "SlackConnector"),
|
||||
("notion", "openjarvis.connectors.notion", "NotionConnector"),
|
||||
("granola", "openjarvis.connectors.granola", "GranolaConnector"),
|
||||
("gdrive", "openjarvis.connectors.gdrive", "GDriveConnector"),
|
||||
("gcalendar", "openjarvis.connectors.gcalendar", "GCalendarConnector"),
|
||||
("gcontacts", "openjarvis.connectors.gcontacts", "GContactsConnector"),
|
||||
("dropbox", "openjarvis.connectors.dropbox", "DropboxConnector"),
|
||||
]
|
||||
|
||||
_ALL_CONNECTORS = _LOCAL_CONNECTORS + _TOKEN_CONNECTORS
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"connector_id,module_path,class_name",
|
||||
_ALL_CONNECTORS,
|
||||
ids=[c[0] for c in _ALL_CONNECTORS],
|
||||
)
|
||||
def test_connector_instantiates(
|
||||
connector_id: str, module_path: str, class_name: str,
|
||||
) -> None:
|
||||
"""Every connector can be instantiated without errors."""
|
||||
import importlib
|
||||
|
||||
mod = importlib.import_module(module_path)
|
||||
cls = getattr(mod, class_name)
|
||||
instance = cls()
|
||||
assert instance is not None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"connector_id,module_path,class_name",
|
||||
_ALL_CONNECTORS,
|
||||
ids=[c[0] for c in _ALL_CONNECTORS],
|
||||
)
|
||||
def test_connector_has_required_methods(
|
||||
connector_id: str, module_path: str, class_name: str,
|
||||
) -> None:
|
||||
"""Every connector implements the BaseConnector interface."""
|
||||
import importlib
|
||||
|
||||
mod = importlib.import_module(module_path)
|
||||
cls = getattr(mod, class_name)
|
||||
instance = cls()
|
||||
assert hasattr(instance, "is_connected")
|
||||
assert hasattr(instance, "disconnect")
|
||||
assert hasattr(instance, "sync")
|
||||
assert hasattr(instance, "sync_status")
|
||||
assert callable(instance.is_connected)
|
||||
assert callable(instance.disconnect)
|
||||
assert callable(instance.sync)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"connector_id,module_path,class_name",
|
||||
_TOKEN_CONNECTORS,
|
||||
ids=[c[0] for c in _TOKEN_CONNECTORS],
|
||||
)
|
||||
def test_connector_not_connected_without_creds(
|
||||
connector_id: str, module_path: str, class_name: str,
|
||||
) -> None:
|
||||
"""Token connectors report not connected without credentials."""
|
||||
import importlib
|
||||
|
||||
mod = importlib.import_module(module_path)
|
||||
cls = getattr(mod, class_name)
|
||||
# Use a temp credentials path that doesn't exist
|
||||
try:
|
||||
instance = cls(credentials_path="/tmp/nonexistent_creds.json")
|
||||
except TypeError:
|
||||
instance = cls()
|
||||
assert instance.is_connected() is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"connector_id,module_path,class_name",
|
||||
_ALL_CONNECTORS,
|
||||
ids=[c[0] for c in _ALL_CONNECTORS],
|
||||
)
|
||||
def test_connector_has_metadata(
|
||||
connector_id: str, module_path: str, class_name: str,
|
||||
) -> None:
|
||||
"""Every connector has connector_id, display_name, and auth_type."""
|
||||
import importlib
|
||||
|
||||
mod = importlib.import_module(module_path)
|
||||
cls = getattr(mod, class_name)
|
||||
assert hasattr(cls, "connector_id")
|
||||
assert hasattr(cls, "display_name")
|
||||
assert hasattr(cls, "auth_type")
|
||||
assert cls.connector_id == connector_id
|
||||
assert isinstance(cls.display_name, str)
|
||||
assert cls.auth_type in ("oauth", "local", "bridge", "filesystem")
|
||||
|
||||
|
||||
def test_knowledge_store_has_data() -> None:
|
||||
"""KnowledgeStore at default path has data (if it exists)."""
|
||||
from openjarvis.connectors.store import KnowledgeStore
|
||||
from openjarvis.core.config import DEFAULT_CONFIG_DIR
|
||||
|
||||
db_path = DEFAULT_CONFIG_DIR / "knowledge.db"
|
||||
if not db_path.exists():
|
||||
pytest.skip("No knowledge.db found")
|
||||
|
||||
store = KnowledgeStore(str(db_path))
|
||||
count = store.count()
|
||||
assert count > 0, "KnowledgeStore has no data"
|
||||
|
||||
# Check sources exist
|
||||
rows = store._conn.execute(
|
||||
"SELECT DISTINCT source FROM knowledge_chunks"
|
||||
).fetchall()
|
||||
sources = [r[0] for r in rows]
|
||||
assert len(sources) > 0, "No sources in KnowledgeStore"
|
||||
store.close()
|
||||
|
||||
|
||||
def test_knowledge_store_sources_have_chunks() -> None:
|
||||
"""Each connected source has at least 1 chunk in the store."""
|
||||
from openjarvis.connectors.store import KnowledgeStore
|
||||
from openjarvis.core.config import DEFAULT_CONFIG_DIR
|
||||
|
||||
db_path = DEFAULT_CONFIG_DIR / "knowledge.db"
|
||||
if not db_path.exists():
|
||||
pytest.skip("No knowledge.db found")
|
||||
|
||||
store = KnowledgeStore(str(db_path))
|
||||
rows = store._conn.execute(
|
||||
"SELECT source, COUNT(*) as n FROM knowledge_chunks "
|
||||
"GROUP BY source ORDER BY n DESC"
|
||||
).fetchall()
|
||||
|
||||
for source, count in rows:
|
||||
assert count > 0, f"Source '{source}' has 0 chunks"
|
||||
|
||||
store.close()
|
||||
Reference in New Issue
Block a user