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
28 changes: 28 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,31 @@
*~
.*.sw?
ssh-identc

# Testing
.pytest_cache/
.coverage
htmlcov/
coverage.xml
*.py[cod]
__pycache__/
*.egg-info/

# Claude
.claude/*

# Virtual environments
venv/
env/
virtualenv/
.venv/

# IDE
.vscode/
.idea/
*.iml

# Build artifacts
build/
dist/
*.egg
282 changes: 282 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
[tool.poetry]
name = "ssh-ident"
version = "1.0.0"
description = "Start and use ssh-agent and load identities as necessary"
authors = ["SSH-Ident Contributors"]
readme = "README"
license = "BSD"
package-mode = false

[tool.poetry.dependencies]
python = "^3.8"

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

[tool.poetry.scripts]

[tool.pytest.ini_options]
minversion = "8.0"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--verbose",
"--cov=.",
"--cov-report=html",
"--cov-report=xml",
"--cov-report=term-missing",
"--cov-fail-under=0",
"-ra",
"--showlocals",
"--tb=short"
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow running tests"
]
filterwarnings = [
"error",
"ignore::DeprecationWarning"
]

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

[tool.coverage.report]
precision = 2
show_missing = true
skip_covered = false
fail_under = 0
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise AssertionError",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
"if typing.TYPE_CHECKING:"
]

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

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

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Empty file added tests/__init__.py
Empty file.
104 changes: 104 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""Shared pytest fixtures and configuration."""
import os
import tempfile
import shutil
from pathlib import Path
from unittest.mock import Mock, MagicMock
import pytest


@pytest.fixture
def temp_dir():
"""Create a temporary directory for tests."""
temp_path = tempfile.mkdtemp()
yield Path(temp_path)
shutil.rmtree(temp_path)


@pytest.fixture
def temp_file(temp_dir):
"""Create a temporary file in the temp directory."""
def _create_temp_file(name="test_file", content=""):
file_path = temp_dir / name
file_path.write_text(content)
return file_path
return _create_temp_file


@pytest.fixture
def mock_env_vars():
"""Mock environment variables."""
original_env = os.environ.copy()
yield os.environ
os.environ.clear()
os.environ.update(original_env)


@pytest.fixture
def mock_home_dir(temp_dir, monkeypatch):
"""Mock the home directory."""
home_path = temp_dir / "home"
home_path.mkdir()
monkeypatch.setenv("HOME", str(home_path))
return home_path


@pytest.fixture
def mock_ssh_dir(mock_home_dir):
"""Create a mock .ssh directory."""
ssh_dir = mock_home_dir / ".ssh"
ssh_dir.mkdir()
return ssh_dir


@pytest.fixture
def mock_config():
"""Create a mock configuration object."""
config = Mock()
config.get = MagicMock(return_value=None)
config.set = MagicMock()
return config


@pytest.fixture
def sample_ssh_key(mock_ssh_dir):
"""Create a sample SSH key file."""
key_path = mock_ssh_dir / "id_rsa"
key_path.write_text("-----BEGIN RSA PRIVATE KEY-----\nMOCK_KEY_CONTENT\n-----END RSA PRIVATE KEY-----\n")
key_path.chmod(0o600)
return key_path


@pytest.fixture
def sample_ssh_config(mock_ssh_dir):
"""Create a sample SSH config file."""
config_path = mock_ssh_dir / "config"
config_path.write_text("""Host example.com
HostName example.com
User testuser
IdentityFile ~/.ssh/id_rsa

Host *.internal
ProxyCommand ssh gateway.internal nc %h %p
""")
return config_path


@pytest.fixture(autouse=True)
def reset_modules():
"""Reset module imports between tests."""
import sys
modules_to_remove = [m for m in sys.modules if m.startswith('ssh_ident')]
for module in modules_to_remove:
del sys.modules[module]


@pytest.fixture
def capture_stdout():
"""Capture stdout output."""
import io
import sys
old_stdout = sys.stdout
sys.stdout = io.StringIO()
yield sys.stdout
sys.stdout = old_stdout
Empty file added tests/integration/__init__.py
Empty file.
107 changes: 107 additions & 0 deletions tests/test_setup_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""Validation tests to ensure the testing infrastructure is properly configured."""
import pytest
import sys
import os
from pathlib import Path


class TestSetupValidation:
"""Validate that the testing infrastructure is properly configured."""

def test_pytest_is_available(self):
"""Test that pytest is installed and available."""
assert pytest.__version__

def test_pytest_cov_is_available(self):
"""Test that pytest-cov plugin is installed."""
import pytest_cov
assert pytest_cov

def test_pytest_mock_is_available(self):
"""Test that pytest-mock plugin is installed."""
import pytest_mock
assert pytest_mock

def test_test_directory_structure_exists(self):
"""Test that the test directory structure is properly created."""
test_root = Path(__file__).parent
assert test_root.exists()
assert test_root.name == "tests"

unit_dir = test_root / "unit"
assert unit_dir.exists()
assert unit_dir.is_dir()

integration_dir = test_root / "integration"
assert integration_dir.exists()
assert integration_dir.is_dir()

def test_conftest_exists(self):
"""Test that conftest.py exists and is importable."""
conftest_path = Path(__file__).parent / "conftest.py"
assert conftest_path.exists()

# Test that conftest is importable
from . import conftest
assert conftest

def test_fixtures_are_available(self, temp_dir, mock_config):
"""Test that custom fixtures from conftest.py are available."""
assert temp_dir.exists()
assert temp_dir.is_dir()
assert hasattr(mock_config, 'get')
assert hasattr(mock_config, 'set')

@pytest.mark.unit
def test_unit_marker_works(self):
"""Test that the unit test marker is properly configured."""
assert True

@pytest.mark.integration
def test_integration_marker_works(self):
"""Test that the integration test marker is properly configured."""
assert True

@pytest.mark.slow
def test_slow_marker_works(self):
"""Test that the slow test marker is properly configured."""
assert True

def test_coverage_configuration(self):
"""Test that coverage is properly configured."""
# This test will pass if coverage is running (which it should be)
import coverage
assert coverage.__version__

def test_python_version_compatibility(self):
"""Test that we're running on a supported Python version."""
assert sys.version_info >= (3, 8)

def test_project_root_is_in_path(self):
"""Test that the project root is in Python path for imports."""
project_root = Path(__file__).parent.parent
assert str(project_root) in sys.path or str(project_root.absolute()) in sys.path

def test_ssh_ident_is_importable(self):
"""Test that the main ssh-ident module can be imported."""
# Since ssh-ident is a script without .py extension,
# we'll check if the file exists instead
ssh_ident_path = Path(__file__).parent.parent / "ssh-ident"
assert ssh_ident_path.exists()
assert ssh_ident_path.is_file()


def test_conftest_fixtures_work(temp_dir, mock_config, temp_file):
"""Test that conftest fixtures work correctly."""
# Test temp_dir
assert temp_dir.exists()
assert temp_dir.is_dir()

# Test temp_file
test_file = temp_file("test.txt", "test content")
assert test_file.exists()
assert test_file.read_text() == "test content"

# Test mock_config
assert hasattr(mock_config, 'get')
assert hasattr(mock_config, 'set')
Empty file added tests/unit/__init__.py
Empty file.