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
25 changes: 24 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,27 @@ lib
__pycache__
*.egg-info
dist
build
build

# Testing
.pytest_cache/
.coverage
htmlcov/
coverage.xml
.tox/
.nox/

# Virtual environments
venv/
env/
ENV/

# IDE files
.vscode/
.idea/
*.swp
*.swo
*~

# Claude settings
.claude/*
439 changes: 439 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
[tool.poetry]
name = "homescript"
version = "5.2"
description = "HomeScript CLI: Command Line Control of HomeBridge"
authors = ["Menahi Shayan <menahi.shayan@gmail.com>"]
readme = "README.md"
homepage = "https://github.com/menahishayan/HomeScript"
repository = "https://github.com/menahishayan/HomeScript"
license = "GPL-3.0-or-later"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
"Operating System :: OS Independent",
]
packages = [{include = "homescript"}]

[tool.poetry.dependencies]
python = "^3.7"
requests = "*"

[tool.poetry.group.test.dependencies]
pytest = "^7.0.0"
pytest-cov = "^4.0.0"
pytest-mock = "^3.10.0"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--strict-config",
"--verbose",
"--cov=homescript",
"--cov-report=term-missing",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
"--cov-fail-under=80",
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow running tests",
]

[tool.coverage.run]
source = ["homescript"]
omit = [
"*/tests/*",
"*/test_*",
"*/__pycache__/*",
"*/venv/*",
"*/env/*",
"setup.py",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"@abstract",
]
show_missing = true
skip_covered = false

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

[tool.coverage.xml]
output = "coverage.xml"
Empty file added tests/__init__.py
Empty file.
141 changes: 141 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
"""Shared pytest fixtures for the homescript test suite."""

import os
import tempfile
from pathlib import Path
from unittest.mock import Mock, MagicMock
from typing import Dict, Any

import pytest


@pytest.fixture
def temp_dir():
"""Provide a temporary directory for testing."""
with tempfile.TemporaryDirectory() as tmp_dir:
yield Path(tmp_dir)


@pytest.fixture
def mock_config():
"""Provide a mock configuration dictionary."""
return {
'host': 'localhost',
'port': 8080,
'username': 'test_user',
'password': 'test_pass',
'timeout': 30
}


@pytest.fixture
def mock_requests_session():
"""Provide a mock requests session."""
session = Mock()
session.get.return_value = Mock()
session.post.return_value = Mock()
session.put.return_value = Mock()
session.delete.return_value = Mock()
return session


@pytest.fixture
def mock_response():
"""Provide a mock HTTP response."""
response = Mock()
response.status_code = 200
response.json.return_value = {'status': 'success'}
response.text = '{"status": "success"}'
response.headers = {'Content-Type': 'application/json'}
return response


@pytest.fixture
def mock_environment(monkeypatch):
"""Provide a clean environment for testing."""
test_env = {
'HOME': '/tmp/test_home',
'PATH': '/usr/local/bin:/usr/bin:/bin'
}
for key, value in test_env.items():
monkeypatch.setenv(key, value)
return test_env


@pytest.fixture
def sample_device_data():
"""Provide sample device data for testing."""
return {
'devices': [
{
'id': 'device_1',
'name': 'Test Device 1',
'type': 'switch',
'state': 'on'
},
{
'id': 'device_2',
'name': 'Test Device 2',
'type': 'sensor',
'value': 25.5
}
]
}


@pytest.fixture
def mock_file_system(tmp_path):
"""Provide a mock file system structure."""
config_dir = tmp_path / "config"
config_dir.mkdir()

data_dir = tmp_path / "data"
data_dir.mkdir()

log_dir = tmp_path / "logs"
log_dir.mkdir()

return {
'base': tmp_path,
'config': config_dir,
'data': data_dir,
'logs': log_dir
}


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


@pytest.fixture
def mock_logger():
"""Provide a mock logger."""
logger = Mock()
logger.info = Mock()
logger.warning = Mock()
logger.error = Mock()
logger.debug = Mock()
return logger


@pytest.fixture
def disable_network_calls(monkeypatch):
"""Disable actual network calls during testing."""
import requests

def mock_request(*args, **kwargs):
raise RuntimeError("Network calls are disabled in tests")

monkeypatch.setattr(requests, "get", mock_request)
monkeypatch.setattr(requests, "post", mock_request)
monkeypatch.setattr(requests, "put", mock_request)
monkeypatch.setattr(requests, "delete", mock_request)


@pytest.fixture
def capsys_disabled(capsys):
"""Fixture to temporarily disable capsys when needed."""
with capsys.disabled():
yield capsys
Empty file added tests/integration/__init__.py
Empty file.
63 changes: 63 additions & 0 deletions tests/test_infrastructure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Test infrastructure validation tests."""

import pytest
import sys
from pathlib import Path


def test_pytest_working():
"""Verify pytest is working correctly."""
assert True


def test_python_version():
"""Verify Python version meets requirements."""
assert sys.version_info >= (3, 7)


def test_project_structure():
"""Verify project structure is correct."""
project_root = Path(__file__).parent.parent

assert (project_root / "homescript").exists()
assert (project_root / "homescript" / "__init__.py").exists()
assert (project_root / "pyproject.toml").exists()
assert (project_root / "tests").exists()


def test_fixtures_available(temp_dir, mock_config, mock_response):
"""Verify shared fixtures are available and working."""
assert temp_dir.exists()
assert isinstance(mock_config, dict)
assert mock_response.status_code == 200


@pytest.mark.unit
def test_unit_marker():
"""Test unit test marker."""
assert True


@pytest.mark.integration
def test_integration_marker():
"""Test integration test marker."""
assert True


@pytest.mark.slow
def test_slow_marker():
"""Test slow test marker."""
assert True


def test_coverage_integration():
"""Test that coverage is properly configured."""
import coverage
assert coverage.__version__


def test_mock_functionality(mock_requests_session):
"""Test mock functionality works."""
response = mock_requests_session.get("http://example.com")
assert response is not None
mock_requests_session.get.assert_called_once_with("http://example.com")
Empty file added tests/unit/__init__.py
Empty file.