Add Discord-native hybrid front-end for Jarvis (bot + bridge)
Some checks failed
Release / semantic-release (push) Successful in 59s
tests / Unit tests (Linux, Python 3.11) (push) Successful in 13m45s
Release / build-linux (push) Failing after 7m47s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled
Some checks failed
Release / semantic-release (push) Successful in 59s
tests / Unit tests (Linux, Python 3.11) (push) Successful in 13m45s
Release / build-linux (push) Failing after 7m47s
Release / build-windows (push) Has been cancelled
Release / build-macos (arm64, macos-latest) (push) Has been cancelled
Release / build-macos (x64, macos-15-intel) (push) Has been cancelled
Release / release-main (push) Has been cancelled
Release / release-develop (push) Has been cancelled
Transform isair/jarvis into a Discord-controlled voice assistant running on the Ubuntu VNC desktop, keeping the mature ~39k-line Python brain intact. - bot/ (Node + bun, discord.js): /자비스 slash commands (ephemeral), voice channel join + voice receive/playback, pluggable VNC screen broadcast (selfbot live / noVNC / screenshot) - bridge/ (Python, Flask): wraps jarvis STT + run_reply_engine + Piper TTS behind a thin localhost HTTP API - .env.example, scripts/ (start_bridge/start_bot/dev), README rewrite, docs/language-comparison.md and docs/vnc-xfce-setup.md Language decision: hybrid (Python brain + Node/bun Discord layer) because Discord blocks bot video; native screen broadcast only works via a Node selfbot library.
This commit is contained in:
124
tests/conftest.py
Normal file
124
tests/conftest.py
Normal file
@@ -0,0 +1,124 @@
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import pytest
|
||||
|
||||
# Robustly locate repository root (directory containing src/jarvis)
|
||||
_this_file = Path(__file__).resolve()
|
||||
ROOT = None
|
||||
for parent in _this_file.parents:
|
||||
if (parent / "src" / "jarvis").exists():
|
||||
ROOT = parent
|
||||
break
|
||||
if ROOT is None:
|
||||
# Fallback to two levels up
|
||||
ROOT = _this_file.parent.parent
|
||||
|
||||
SRC = ROOT / "src"
|
||||
# Both ROOT and SRC are on sys.path so tests can write either
|
||||
# ``from src.jarvis.x import ...`` (older style, ``src.`` prefix)
|
||||
# or
|
||||
# ``from jarvis.x import ...`` (newer style, no prefix)
|
||||
# CAUTION: those two import paths resolve to *distinct module instances*.
|
||||
# A monkeypatch on ``src.jarvis.memory.conversation.X`` does NOT take
|
||||
# effect on ``jarvis.memory.conversation.X`` and vice versa. When a test
|
||||
# stubs out a symbol the production code calls, you MUST patch the same
|
||||
# module instance the production code resolves at runtime. Production code
|
||||
# in ``src/`` imports without the ``src.`` prefix (e.g. inside endpoint
|
||||
# handlers it's ``from jarvis.memory.conversation import ...``), so a test
|
||||
# that monkeypatches a symbol used by production should also import
|
||||
# without the prefix. This is the convention going forward; the older
|
||||
# ``from src.X`` style is left in place to avoid a churn-only sweep, but
|
||||
# do not adopt it for new tests that monkeypatch.
|
||||
# Add repository root so that 'src' is a package prefix.
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
# Also add the src directory (optional, for backwards compatibility with direct 'jarvis' imports)
|
||||
if str(SRC) not in sys.path:
|
||||
sys.path.insert(0, str(SRC))
|
||||
|
||||
|
||||
@dataclass
|
||||
class MockConfig:
|
||||
"""Minimal config object for unit tests that need a config."""
|
||||
ollama_base_url: str = "http://localhost:11434"
|
||||
ollama_chat_model: str = "gemma4:e2b"
|
||||
ollama_embed_model: str = "nomic-embed-text"
|
||||
db_path: str = ":memory:"
|
||||
sqlite_vss_path: Optional[str] = None
|
||||
voice_debug: bool = True
|
||||
tts_enabled: bool = False
|
||||
tts_engine: str = "piper"
|
||||
tts_voice: Optional[str] = None
|
||||
tts_rate: int = 200
|
||||
tts_piper_model_path: Optional[str] = None
|
||||
tts_piper_speaker: Optional[int] = None
|
||||
tts_piper_length_scale: float = 1.0
|
||||
tts_piper_noise_scale: float = 0.667
|
||||
tts_piper_noise_w: float = 0.8
|
||||
tts_piper_sentence_silence: float = 0.2
|
||||
tts_chatterbox_device: str = "cpu"
|
||||
tts_chatterbox_audio_prompt: Optional[str] = None
|
||||
tts_chatterbox_exaggeration: float = 0.5
|
||||
tts_chatterbox_cfg_weight: float = 0.5
|
||||
web_search_enabled: bool = True
|
||||
brave_search_api_key: str = ""
|
||||
wikipedia_fallback_enabled: bool = True
|
||||
llm_tools_timeout_sec: float = 8.0
|
||||
llm_embed_timeout_sec: float = 10.0
|
||||
llm_chat_timeout_sec: float = 45.0
|
||||
agentic_max_turns: int = 8
|
||||
tool_selection_strategy: str = "embedding"
|
||||
tool_router_model: str = ""
|
||||
memory_enrichment_max_results: int = 5
|
||||
memory_enrichment_source: str = "diary"
|
||||
location_enabled: bool = True
|
||||
location_ip_address: Optional[str] = None
|
||||
location_auto_detect: bool = False
|
||||
location_cgnat_resolve_public_ip: bool = False
|
||||
dialogue_memory_timeout: int = 300
|
||||
llm_thinking_enabled: bool = False
|
||||
intent_judge_thinking_enabled: bool = False
|
||||
dictation_thinking_enabled: bool = False
|
||||
mcps: Dict[str, Any] = field(default_factory=dict)
|
||||
use_stdin: bool = True
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config():
|
||||
"""Provide a mock configuration for unit tests."""
|
||||
return MockConfig()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db():
|
||||
"""Provide an in-memory database for unit tests."""
|
||||
from jarvis.memory.db import Database
|
||||
database = Database(":memory:", sqlite_vss_path=None)
|
||||
yield database
|
||||
database.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dialogue_memory():
|
||||
"""Provide a dialogue memory instance for unit tests."""
|
||||
from jarvis.memory.conversation import DialogueMemory
|
||||
return DialogueMemory(inactivity_timeout=300, max_interactions=20)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def qapp():
|
||||
"""Provide a shared QApplication for Qt-based UI tests.
|
||||
|
||||
Qt requires exactly one QApplication per process. Re-uses an existing
|
||||
instance when present so repeated test runs inside a single session
|
||||
don't error.
|
||||
"""
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
app = QApplication.instance()
|
||||
if app is None:
|
||||
app = QApplication([])
|
||||
yield app
|
||||
|
||||
Reference in New Issue
Block a user