Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,32 @@ cython_debug/

# Exception for voice_samples
!voice_samples/*.mp3
!voice_samples/*.wav
!voice_samples/*.wav

# Claude settings
.claude/*

# Additional testing artifacts
test-results/
.benchmarks/
.mutmut-cache

# IDE files
*.swp
*.swo
*~
.project
.classpath
.settings/
*.sublime-*

# OS files
Thumbs.db
ehthumbs.db
Desktop.ini
$RECYCLE.BIN/
*.cab
*.msi
*.msm
*.msp
*.lnk
19 changes: 19 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# CLAUDE.md - Project Context for AI Assistant

## Project Overview
JARVIS Voice Assistant - A modular voice assistant application with support for multiple state-of-the-art models.

## Key Commands
- `poetry run test` - Run all tests
- `poetry run tests` - Alternative test command
- `poetry run pytest --cov` - Run tests with coverage
- `./scripts/test.sh` - Run tests without coverage
- `./scripts/test.sh coverage` - Run tests with coverage

## Important Notes
- Testing framework: pytest with pytest-cov and pytest-mock
- Package manager: Poetry
- Coverage threshold: 80% (when running with coverage)
- Test markers: unit, integration, slow
- PyAudio dependency removed due to system requirements

5,725 changes: 5,725 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

137 changes: 137 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
[tool.poetry]
name = "jarvis-voice-assistant"
version = "0.1.0"
description = "A modular voice assistant application with support for multiple state-of-the-art models."
authors = ["Prompt <engineerprompt@gmail.com>"]
readme = "README.md"
homepage = "https://github.com/PromtEngineer/JARVIS-VoiceAssistant"
packages = [{include = "*", from = "."}]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Scientific/Engineering :: Artificial Intelligence"
]

[tool.poetry.dependencies]
python = "^3.10"
annotated-types = "^0.6.0"
anyio = "^4.3.0"
certifi = "^2024.2.2"
charset-normalizer = "^3.3.2"
distro = "^1.9.0"
h11 = "^0.14.0"
httpcore = "^1.0.5"
httpx = "^0.27.0"
idna = "^3.7"
openai = "^1.30.1"
pydantic = "^2.7.1"
pydantic-core = "^2.18.2"
pygame = "^2.5.2"
requests = "^2.31.0"
setuptools = "^69.5.1"
sniffio = "^1.3.1"
SpeechRecognition = "^3.10.4"
tqdm = "^4.66.4"
typing-extensions = "^4.11.0"
urllib3 = "^2.2.1"
colorama = "*"
deepgram-sdk = "*"
groq = "*"
python-dotenv = "*"
keyboard = "*"
elevenlabs = "*"
fastapi = "*"
uvicorn = "*"
numpy = "*"
sounddevice = "*"
cartesia = "*"
soundfile = "*"
ollama = "*"
pydub = "*"

[tool.poetry.group.dev.dependencies]
pytest = "^8.0.0"
pytest-cov = "^5.0.0"
pytest-mock = "^3.14.0"

[tool.poetry.scripts]
jarvis = "run_voice_assistant:main"

[tool.pytest.ini_options]
minversion = "8.0"
addopts = [
"-ra",
"--strict-markers",
"--strict-config",
"-vv",
]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow running tests",
]
filterwarnings = [
"error",
"ignore::UserWarning",
"ignore::DeprecationWarning",
]

[tool.coverage.run]
source = ["tests"]
omit = [
"*/tests/*",
"*/test_*",
"*/__pycache__/*",
"*/venv/*",
"*/env/*",
"*/.venv/*",
"*/.env/*",
"*/virtualenv/*",
"*/setup.py",
"*/conftest.py",
"*/.pytest_cache/*",
"*/htmlcov/*",
"*/dist/*",
"*/build/*",
"*.egg-info/*",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
]
precision = 2
show_missing = true
skip_covered = true
skip_empty = true

[tool.coverage.html]
directory = "htmlcov"

[tool.coverage.xml]
output = "coverage.xml"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
9 changes: 9 additions & 0 deletions scripts/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash
# Script to run tests with Poetry

# Run tests with the desired options
if [ "$1" == "coverage" ]; then
poetry run pytest --cov=. --cov-branch --cov-report=term-missing:skip-covered --cov-report=html:htmlcov --cov-report=xml:coverage.xml --cov-fail-under=80 "${@:2}"
else
poetry run pytest "$@"
fi
Empty file added tests/__init__.py
Empty file.
171 changes: 171 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import os
import sys
import tempfile
import shutil
from pathlib import Path
from typing import Generator, Dict, Any
import pytest
from unittest.mock import Mock, MagicMock


sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))


@pytest.fixture
def temp_dir() -> Generator[Path, None, None]:
"""Create a temporary directory for test files."""
temp_path = tempfile.mkdtemp()
yield Path(temp_path)
shutil.rmtree(temp_path)


@pytest.fixture
def temp_file(temp_dir: Path) -> Generator[Path, None, None]:
"""Create a temporary file within the temporary directory."""
temp_file_path = temp_dir / "test_file.txt"
temp_file_path.write_text("Test content")
yield temp_file_path


@pytest.fixture
def mock_config() -> Dict[str, Any]:
"""Provide a mock configuration dictionary."""
return {
"api_key": "test-api-key",
"model": "test-model",
"temperature": 0.7,
"max_tokens": 100,
"debug": True,
"log_level": "DEBUG",
"timeout": 30,
}


@pytest.fixture
def mock_env_vars(monkeypatch) -> Dict[str, str]:
"""Set up mock environment variables."""
env_vars = {
"OPENAI_API_KEY": "test-openai-key",
"GROQ_API_KEY": "test-groq-key",
"DEEPGRAM_API_KEY": "test-deepgram-key",
"ELEVENLABS_API_KEY": "test-elevenlabs-key",
"DEBUG": "true",
}
for key, value in env_vars.items():
monkeypatch.setenv(key, value)
return env_vars


@pytest.fixture
def mock_api_response() -> Dict[str, Any]:
"""Provide a mock API response structure."""
return {
"id": "test-response-id",
"object": "chat.completion",
"created": 1234567890,
"model": "test-model",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "Test response content"
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 20,
"total_tokens": 30
}
}


@pytest.fixture
def mock_audio_data() -> bytes:
"""Provide mock audio data for testing."""
return b'\x00\x01' * 1000


@pytest.fixture
def mock_llm_client():
"""Create a mock LLM client."""
client = Mock()
client.generate_response = MagicMock(return_value="Test LLM response")
client.is_connected = MagicMock(return_value=True)
return client


@pytest.fixture
def mock_tts_client():
"""Create a mock TTS client."""
client = Mock()
client.synthesize = MagicMock(return_value=b'\x00\x01' * 1000)
client.is_available = MagicMock(return_value=True)
return client


@pytest.fixture
def mock_stt_client():
"""Create a mock STT client."""
client = Mock()
client.transcribe = MagicMock(return_value="Test transcription")
client.is_available = MagicMock(return_value=True)
return client


@pytest.fixture(autouse=True)
def reset_singleton_instances():
"""Reset any singleton instances between tests."""
yield


@pytest.fixture
def capture_logs(caplog):
"""Capture log messages during tests."""
with caplog.at_level("DEBUG"):
yield caplog


@pytest.fixture
def mock_file_system(tmp_path):
"""Create a mock file system structure for testing."""
dirs = ["config", "data", "logs", "models"]
for dir_name in dirs:
(tmp_path / dir_name).mkdir()

config_file = tmp_path / "config" / "config.json"
config_file.write_text('{"test": true}')

return tmp_path


@pytest.fixture
def performance_timer():
"""Measure test performance."""
import time
start_time = time.time()
yield
end_time = time.time()
print(f"\nTest execution time: {end_time - start_time:.3f} seconds")


@pytest.fixture(scope="session")
def test_data_dir() -> Path:
"""Provide path to test data directory."""
return Path(__file__).parent / "test_data"


def pytest_configure(config):
"""Configure pytest with custom markers."""
config.addinivalue_line("markers", "unit: Mark test as a unit test")
config.addinivalue_line("markers", "integration: Mark test as an integration test")
config.addinivalue_line("markers", "slow: Mark test as slow running")


def pytest_collection_modifyitems(config, items):
"""Modify test collection to add markers based on test location."""
for item in items:
if "unit" in str(item.fspath):
item.add_marker(pytest.mark.unit)
elif "integration" in str(item.fspath):
item.add_marker(pytest.mark.integration)
Empty file added tests/integration/__init__.py
Empty file.
Loading