Connection Failures Testing
ParamikoMock now supports testing various connection failure scenarios, similar to how the responses library works for HTTP mocking. This allows you to test how your application handles different types of connection problems.
Using Convenience Methods
The ParamikoMockEnviron class provides convenience methods for setting up common failure scenarios:
DNS Failure
from paramiko_mock import ParamikoMockEnviron
from unittest.mock import patch
import paramiko
import socket
def test_dns_failure():
# Set up DNS resolution failure
ParamikoMockEnviron().setup_dns_failure('unreachable_host')
def connect_function():
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('unreachable_host', port=22)
return client
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(socket.gaierror) as exc_info:
connect_function()
assert 'Name or service not known' in str(exc_info.value)
ParamikoMockEnviron().cleanup_environment()
Timeout Failure
def test_timeout_failure():
# Set up connection timeout
ParamikoMockEnviron().setup_timeout_failure('slow_host')
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(TimeoutError):
connect_function()
ParamikoMockEnviron().cleanup_environment()
Authentication Failure
def test_authentication_failure():
# Set up authentication failure
ParamikoMockEnviron().setup_authentication_failure('secure_host')
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(paramiko.ssh_exception.AuthenticationException):
connect_function()
ParamikoMockEnviron().cleanup_environment()
Connection Refused
def test_connection_refused():
# Set up connection refused
ParamikoMockEnviron().setup_connection_refused('busy_host')
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(ConnectionRefusedError):
connect_function()
ParamikoMockEnviron().cleanup_environment()
Bad Host Key
def test_bad_host_key():
# Set up bad host key failure
ParamikoMockEnviron().setup_badhost_failure('suspicious_host')
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(paramiko.BadHostKeyException):
connect_function()
ParamikoMockEnviron().cleanup_environment()
Bad Host Key with Custom Keys
def test_bad_host_key_custom():
# Set up bad host key failure with custom keys
from paramiko.pkey import PKey
from paramiko.message import Message
custom_got_key = PKey(msg=Message("CustomGotKey".encode()), data="CustomGotKeyData")
custom_pkey = PKey(msg=Message("CustomExpectedKey".encode()), data="CustomExpectedKeyData")
ParamikoMockEnviron().setup_badhost_failure(
'suspicious_host',
custom_got_key=custom_got_key,
custom_pkey=custom_pkey
)
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(paramiko.BadHostKeyException) as exc_info:
connect_function()
# You can inspect the exception details
exception = exc_info.value
assert exception.hostname == 'suspicious_host'
assert exception.got_key == custom_got_key
assert exception.pkey == custom_pkey
ParamikoMockEnviron().cleanup_environment()
Using Custom Failures
You can also set up custom exceptions for specific scenarios:
def test_custom_failure():
# Set up a custom exception
custom_exception = ValueError("Custom SSH error")
ParamikoMockEnviron().setup_custom_failure('custom_host', 22, custom_exception)
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(ValueError) as exc_info:
connect_function()
assert 'Custom SSH error' in str(exc_info.value)
ParamikoMockEnviron().cleanup_environment()
Using ConnectionFailureConfig Directly
For more control, you can use the ConnectionFailureConfig class directly:
from paramiko_mock.exceptions import ConnectionFailureConfig
def test_direct_failure_config():
# Set up failure using the direct method
ParamikoMockEnviron().add_responses_for_host(
'direct_fail_host', 22, {},
connection_failure=ConnectionFailureConfig.dns_failure('test.example.com')
)
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(socket.gaierror) as exc_info:
connect_function()
assert 'test.example.com' in str(exc_info.value)
ParamikoMockEnviron().cleanup_environment()
Available Failure Types
The following failure types are available:
- DNS Failure:
socket.gaierror(-2, "Name or service not known: {hostname}") - Timeout:
TimeoutError("timed out") - Authentication:
paramiko.ssh_exception.AuthenticationException("Authentication failed") - Connection Refused:
ConnectionRefusedError("Connection refused") - Bad Host Key:
paramiko.BadHostKeyException(hostname, got_key, pkey) - Custom: Any exception you provide
Mixing Success and Failure Scenarios
You can set up different hosts with different behaviors in the same test:
def test_mixed_scenarios():
# Set up a successful host
ParamikoMockEnviron().add_responses_for_host('good_host', 22, {
'ls': SSHCommandMock('', 'success', '')
}, 'user', 'pass')
# Set up a failing host
ParamikoMockEnviron().setup_dns_failure('bad_host')
def connect_to_good_host():
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('good_host', port=22, username='user', password='pass')
stdin, stdout, stderr = client.exec_command('ls')
return stdout.read()
def connect_to_bad_host():
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('bad_host', port=22)
return client
with patch('paramiko.SSHClient', new=SSHClientMock):
# Good host should work
output = connect_to_good_host()
assert output == b'success'
# Bad host should fail
with pytest.raises(socket.gaierror):
connect_to_bad_host()
ParamikoMockEnviron().cleanup_environment()
Complete Example
Here's a comprehensive example showing various connection failure scenarios:
import pytest
from paramiko_mock import ParamikoMockEnviron, SSHClientMock, SSHCommandMock
from unittest.mock import patch
import paramiko
import socket
class TestConnectionFailures:
def test_dns_resolution_failure(self):
"""Test DNS resolution failure scenario"""
ParamikoMockEnviron().setup_dns_failure('nonexistent.example.com')
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(socket.gaierror) as exc_info:
client = paramiko.SSHClient()
client.connect('nonexistent.example.com', port=22)
assert 'Name or service not known' in str(exc_info.value)
ParamikoMockEnviron().cleanup_environment()
def test_connection_timeout(self):
"""Test connection timeout scenario"""
ParamikoMockEnviron().setup_timeout_failure('slow-server.example.com')
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(TimeoutError):
client = paramiko.SSHClient()
client.connect('slow-server.example.com', port=22)
ParamikoMockEnviron().cleanup_environment()
def test_authentication_failure(self):
"""Test authentication failure scenario"""
ParamikoMockEnviron().setup_authentication_failure('secure-server.example.com')
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(paramiko.ssh_exception.AuthenticationException):
client = paramiko.SSHClient()
client.connect('secure-server.example.com', port=22, username='user', password='pass')
ParamikoMockEnviron().cleanup_environment()
def test_connection_refused(self):
"""Test connection refused scenario"""
ParamikoMockEnviron().setup_connection_refused('busy-server.example.com')
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(ConnectionRefusedError):
client = paramiko.SSHClient()
client.connect('busy-server.example.com', port=22)
ParamikoMockEnviron().cleanup_environment()
def test_bad_host_key(self):
"""Test bad host key scenario"""
ParamikoMockEnviron().setup_badhost_failure('suspicious-server.example.com')
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(paramiko.BadHostKeyException):
client = paramiko.SSHClient()
client.connect('suspicious-server.example.com', port=22)
ParamikoMockEnviron().cleanup_environment()
def test_custom_exception(self):
"""Test custom exception scenario"""
custom_error = RuntimeError("Custom network error")
ParamikoMockEnviron().setup_custom_failure('custom-error.example.com', 22, custom_error)
with patch('paramiko.SSHClient', new=SSHClientMock):
with pytest.raises(RuntimeError) as exc_info:
client = paramiko.SSHClient()
client.connect('custom-error.example.com', port=22)
assert 'Custom network error' in str(exc_info.value)
ParamikoMockEnviron().cleanup_environment()
def test_mixed_success_and_failure(self):
"""Test mixing successful and failing connections in the same test"""
# Set up successful host
ParamikoMockEnviron().add_responses_for_host('working-host.example.com', 22, {
'uptime': SSHCommandMock('', 'Server is up', '')
}, 'user', 'pass')
# Set up failing host
ParamikoMockEnviron().setup_connection_refused('failing-host.example.com')
def execute_on_working_host():
client = paramiko.SSHClient()
client.connect('working-host.example.com', port=22, username='user', password='pass')
stdin, stdout, stderr = client.exec_command('uptime')
return stdout.read()
def connect_to_failing_host():
client = paramiko.SSHClient()
client.connect('failing-host.example.com', port=22)
return client
with patch('paramiko.SSHClient', new=SSHClientMock):
# Working host should succeed
result = execute_on_working_host()
assert result == b'Server is up'
# Failing host should raise exception
with pytest.raises(ConnectionRefusedError):
connect_to_failing_host()
ParamikoMockEnviron().cleanup_environment()
This comprehensive testing approach allows you to verify that your application handles various network failure scenarios gracefully and appropriately.