initial commit after one day coding agent session
This commit is contained in:
commit
372af75b90
88 changed files with 22694 additions and 0 deletions
324
tests/podman/test_podman_client.py
Normal file
324
tests/podman/test_podman_client.py
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
"""
|
||||
Tests for Podman client wrapper.
|
||||
|
||||
Following TDD approach - these tests are written before implementation.
|
||||
Tests cover all requirements from todo.md section 1.3.1.
|
||||
All tests use mocked Podman client (no actual Podman needed).
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, MagicMock, patch
|
||||
|
||||
|
||||
def test_connection_to_podman_socket_succeeds(tmp_path):
|
||||
"""Test that connection to Podman socket succeeds."""
|
||||
from mcp_forge.podman.client import PodmanClient
|
||||
from mcp_forge.security.audit import AuditLogger
|
||||
from mcp_forge.security.allowlist import OperationValidator
|
||||
from mcp_forge.config.schema import SecurityConfig
|
||||
|
||||
socket_path = tmp_path / "podman.sock"
|
||||
socket_path.touch() # Create fake socket file
|
||||
|
||||
audit_logger = AuditLogger(tmp_path / "audit.log")
|
||||
validator = OperationValidator(SecurityConfig(audit_log=tmp_path / "audit.log"))
|
||||
|
||||
client = PodmanClient(
|
||||
socket_path=socket_path,
|
||||
validator=validator,
|
||||
audit_logger=audit_logger
|
||||
)
|
||||
|
||||
with patch('mcp_forge.podman.client.BasePodmanClient') as mock_podman:
|
||||
mock_client_instance = MagicMock()
|
||||
mock_client_instance.ping.return_value = "OK"
|
||||
mock_podman.return_value = mock_client_instance
|
||||
|
||||
client.connect()
|
||||
assert client._client is not None
|
||||
mock_podman.assert_called_once_with(base_url=f"unix://{socket_path}")
|
||||
|
||||
|
||||
def test_connection_failure_raises_clear_error(tmp_path):
|
||||
"""Test that connection failure raises clear error."""
|
||||
from mcp_forge.podman.client import PodmanClient, PodmanConnectionError
|
||||
from mcp_forge.security.audit import AuditLogger
|
||||
from mcp_forge.security.allowlist import OperationValidator
|
||||
from mcp_forge.config.schema import SecurityConfig
|
||||
|
||||
socket_path = tmp_path / "nonexistent.sock"
|
||||
|
||||
audit_logger = AuditLogger(tmp_path / "audit.log")
|
||||
validator = OperationValidator(SecurityConfig(audit_log=tmp_path / "audit.log"))
|
||||
|
||||
client = PodmanClient(
|
||||
socket_path=socket_path,
|
||||
validator=validator,
|
||||
audit_logger=audit_logger
|
||||
)
|
||||
|
||||
with patch('mcp_forge.podman.client.BasePodmanClient', side_effect=Exception("Connection failed")):
|
||||
with pytest.raises(PodmanConnectionError) as exc_info:
|
||||
client.connect()
|
||||
assert "connection" in str(exc_info.value).lower() or "failed" in str(exc_info.value).lower()
|
||||
|
||||
|
||||
def test_socket_path_validation(tmp_path):
|
||||
"""Test that socket path is validated before connecting."""
|
||||
from mcp_forge.podman.client import PodmanClient, PodmanConnectionError
|
||||
from mcp_forge.security.audit import AuditLogger
|
||||
from mcp_forge.security.allowlist import OperationValidator
|
||||
from mcp_forge.config.schema import SecurityConfig
|
||||
|
||||
nonexistent_socket = tmp_path / "nonexistent.sock"
|
||||
|
||||
audit_logger = AuditLogger(tmp_path / "audit.log")
|
||||
validator = OperationValidator(SecurityConfig(audit_log=tmp_path / "audit.log"))
|
||||
|
||||
client = PodmanClient(
|
||||
socket_path=nonexistent_socket,
|
||||
validator=validator,
|
||||
audit_logger=audit_logger
|
||||
)
|
||||
|
||||
with pytest.raises(PodmanConnectionError) as exc_info:
|
||||
client.verify_socket_access()
|
||||
assert "not found" in str(exc_info.value).lower() or "does not exist" in str(exc_info.value).lower()
|
||||
|
||||
|
||||
def test_socket_permissions_check(tmp_path):
|
||||
"""Test that socket permissions are checked."""
|
||||
from mcp_forge.podman.client import PodmanClient
|
||||
from mcp_forge.security.audit import AuditLogger
|
||||
from mcp_forge.security.allowlist import OperationValidator
|
||||
from mcp_forge.config.schema import SecurityConfig
|
||||
|
||||
socket_path = tmp_path / "podman.sock"
|
||||
socket_path.touch()
|
||||
socket_path.chmod(0o000) # Remove all permissions
|
||||
|
||||
audit_logger = AuditLogger(tmp_path / "audit.log")
|
||||
validator = OperationValidator(SecurityConfig(audit_log=tmp_path / "audit.log"))
|
||||
|
||||
client = PodmanClient(
|
||||
socket_path=socket_path,
|
||||
validator=validator,
|
||||
audit_logger=audit_logger
|
||||
)
|
||||
|
||||
# verify_socket_access should check readability
|
||||
# Depending on implementation, might raise error or just warn
|
||||
try:
|
||||
client.verify_socket_access()
|
||||
except Exception as e:
|
||||
# Should mention permissions or access
|
||||
assert "permission" in str(e).lower() or "access" in str(e).lower() or "readable" in str(e).lower()
|
||||
finally:
|
||||
socket_path.chmod(0o644) # Restore for cleanup
|
||||
|
||||
|
||||
def test_api_version_compatibility_check(tmp_path):
|
||||
"""Test that API version is checked."""
|
||||
from mcp_forge.podman.client import PodmanClient
|
||||
from mcp_forge.security.audit import AuditLogger
|
||||
from mcp_forge.security.allowlist import OperationValidator
|
||||
from mcp_forge.config.schema import SecurityConfig
|
||||
|
||||
socket_path = tmp_path / "podman.sock"
|
||||
socket_path.touch()
|
||||
|
||||
audit_logger = AuditLogger(tmp_path / "audit.log")
|
||||
validator = OperationValidator(SecurityConfig(audit_log=tmp_path / "audit.log"))
|
||||
|
||||
client = PodmanClient(
|
||||
socket_path=socket_path,
|
||||
validator=validator,
|
||||
audit_logger=audit_logger
|
||||
)
|
||||
|
||||
with patch('mcp_forge.podman.client.BasePodmanClient') as mock_podman:
|
||||
mock_client = MagicMock()
|
||||
mock_client.version.return_value = {
|
||||
"Version": "4.5.0",
|
||||
"ApiVersion": "4.5.0"
|
||||
}
|
||||
mock_client.ping.return_value = "OK"
|
||||
mock_podman.return_value = mock_client
|
||||
|
||||
client.connect()
|
||||
version_info = client.check_api_version()
|
||||
|
||||
assert "Version" in version_info or "ApiVersion" in version_info
|
||||
|
||||
|
||||
def test_ping_health_check(tmp_path):
|
||||
"""Test that ping/health check works."""
|
||||
from mcp_forge.podman.client import PodmanClient
|
||||
from mcp_forge.security.audit import AuditLogger
|
||||
from mcp_forge.security.allowlist import OperationValidator
|
||||
from mcp_forge.config.schema import SecurityConfig
|
||||
|
||||
socket_path = tmp_path / "podman.sock"
|
||||
socket_path.touch()
|
||||
|
||||
audit_logger = AuditLogger(tmp_path / "audit.log")
|
||||
validator = OperationValidator(SecurityConfig(audit_log=tmp_path / "audit.log"))
|
||||
|
||||
client = PodmanClient(
|
||||
socket_path=socket_path,
|
||||
validator=validator,
|
||||
audit_logger=audit_logger
|
||||
)
|
||||
|
||||
with patch('mcp_forge.podman.client.BasePodmanClient') as mock_podman:
|
||||
mock_client = MagicMock()
|
||||
mock_client.ping.return_value = "OK"
|
||||
mock_podman.return_value = mock_client
|
||||
|
||||
client.connect()
|
||||
result = client.ping()
|
||||
|
||||
assert result is True or result == "OK"
|
||||
|
||||
|
||||
def test_lazy_connection(tmp_path):
|
||||
"""Test that connection is lazy (only connects when needed)."""
|
||||
from mcp_forge.podman.client import PodmanClient
|
||||
from mcp_forge.security.audit import AuditLogger
|
||||
from mcp_forge.security.allowlist import OperationValidator
|
||||
from mcp_forge.config.schema import SecurityConfig
|
||||
|
||||
socket_path = tmp_path / "podman.sock"
|
||||
socket_path.touch()
|
||||
|
||||
audit_logger = AuditLogger(tmp_path / "audit.log")
|
||||
validator = OperationValidator(SecurityConfig(audit_log=tmp_path / "audit.log"))
|
||||
|
||||
# Creating client should not connect
|
||||
client = PodmanClient(
|
||||
socket_path=socket_path,
|
||||
validator=validator,
|
||||
audit_logger=audit_logger
|
||||
)
|
||||
|
||||
assert client._client is None # Not connected yet
|
||||
|
||||
# Accessing client property should trigger connection
|
||||
with patch('mcp_forge.podman.client.BasePodmanClient') as mock_podman:
|
||||
mock_client_instance = MagicMock()
|
||||
mock_client_instance.ping.return_value = "OK"
|
||||
mock_podman.return_value = mock_client_instance
|
||||
_ = client.client
|
||||
assert client._client is not None
|
||||
|
||||
|
||||
def test_disconnect_cleanup(tmp_path):
|
||||
"""Test that disconnect cleans up properly."""
|
||||
from mcp_forge.podman.client import PodmanClient
|
||||
from mcp_forge.security.audit import AuditLogger
|
||||
from mcp_forge.security.allowlist import OperationValidator
|
||||
from mcp_forge.config.schema import SecurityConfig
|
||||
|
||||
socket_path = tmp_path / "podman.sock"
|
||||
socket_path.touch()
|
||||
|
||||
audit_logger = AuditLogger(tmp_path / "audit.log")
|
||||
validator = OperationValidator(SecurityConfig(audit_log=tmp_path / "audit.log"))
|
||||
|
||||
client = PodmanClient(
|
||||
socket_path=socket_path,
|
||||
validator=validator,
|
||||
audit_logger=audit_logger
|
||||
)
|
||||
|
||||
with patch('mcp_forge.podman.client.BasePodmanClient') as mock_podman:
|
||||
mock_client = MagicMock()
|
||||
mock_client.ping.return_value = "OK"
|
||||
mock_podman.return_value = mock_client
|
||||
|
||||
client.connect()
|
||||
assert client._client is not None
|
||||
|
||||
client.disconnect()
|
||||
assert client._client is None
|
||||
|
||||
|
||||
def test_connection_error_includes_socket_path(tmp_path):
|
||||
"""Test that connection errors include the socket path for debugging."""
|
||||
from mcp_forge.podman.client import PodmanClient, PodmanConnectionError
|
||||
from mcp_forge.security.audit import AuditLogger
|
||||
from mcp_forge.security.allowlist import OperationValidator
|
||||
from mcp_forge.config.schema import SecurityConfig
|
||||
|
||||
socket_path = tmp_path / "test.sock"
|
||||
|
||||
audit_logger = AuditLogger(tmp_path / "audit.log")
|
||||
validator = OperationValidator(SecurityConfig(audit_log=tmp_path / "audit.log"))
|
||||
|
||||
client = PodmanClient(
|
||||
socket_path=socket_path,
|
||||
validator=validator,
|
||||
audit_logger=audit_logger
|
||||
)
|
||||
|
||||
with pytest.raises(PodmanConnectionError) as exc_info:
|
||||
client.verify_socket_access()
|
||||
|
||||
assert str(socket_path) in str(exc_info.value) or socket_path.name in str(exc_info.value)
|
||||
|
||||
|
||||
def test_client_property_auto_connects(tmp_path):
|
||||
"""Test that accessing client property auto-connects if not connected."""
|
||||
from mcp_forge.podman.client import PodmanClient
|
||||
from mcp_forge.security.audit import AuditLogger
|
||||
from mcp_forge.security.allowlist import OperationValidator
|
||||
from mcp_forge.config.schema import SecurityConfig
|
||||
|
||||
socket_path = tmp_path / "podman.sock"
|
||||
socket_path.touch()
|
||||
|
||||
audit_logger = AuditLogger(tmp_path / "audit.log")
|
||||
validator = OperationValidator(SecurityConfig(audit_log=tmp_path / "audit.log"))
|
||||
|
||||
client = PodmanClient(
|
||||
socket_path=socket_path,
|
||||
validator=validator,
|
||||
audit_logger=audit_logger
|
||||
)
|
||||
|
||||
with patch('mcp_forge.podman.client.BasePodmanClient') as mock_podman:
|
||||
mock_client = MagicMock()
|
||||
mock_client.ping.return_value = "OK"
|
||||
mock_podman.return_value = mock_client
|
||||
|
||||
# First access should trigger connect
|
||||
_ = client.client
|
||||
assert mock_podman.called
|
||||
|
||||
# Second access should reuse connection
|
||||
mock_podman.reset_mock()
|
||||
_ = client.client
|
||||
assert not mock_podman.called # Should not connect again
|
||||
|
||||
|
||||
def test_validator_and_audit_logger_stored(tmp_path):
|
||||
"""Test that validator and audit logger are stored for later use."""
|
||||
from mcp_forge.podman.client import PodmanClient
|
||||
from mcp_forge.security.audit import AuditLogger
|
||||
from mcp_forge.security.allowlist import OperationValidator
|
||||
from mcp_forge.config.schema import SecurityConfig
|
||||
|
||||
socket_path = tmp_path / "podman.sock"
|
||||
|
||||
audit_logger = AuditLogger(tmp_path / "audit.log")
|
||||
validator = OperationValidator(SecurityConfig(audit_log=tmp_path / "audit.log"))
|
||||
|
||||
client = PodmanClient(
|
||||
socket_path=socket_path,
|
||||
validator=validator,
|
||||
audit_logger=audit_logger
|
||||
)
|
||||
|
||||
assert client.validator is validator
|
||||
assert client.audit_logger is audit_logger
|
||||
Loading…
Add table
Add a link
Reference in a new issue