refactor: ruff autofixes

This commit is contained in:
Mark Paronyan committed 2026-09-27 15:55:10 +03:00
1 parent 2ac637c662
commit a0450b6bce
455 files changed
+12322 -14762

No files matched your search

@@ -69,11 +69,9 @@ The drivers are auto-registered when the tools are imported:
from gns3server.agent.gns3_copilot.tools_v2 import DisplayToolNornir
tool = DisplayToolNornir()
result = tool._run(json.dumps({
"device_names": ["huawei-sw1"],
"commands": ["display version"],
"project_id": "project-uuid"
}))
result = tool._run(
json.dumps({"device_names": ["huawei-sw1"], "commands": ["display version"], "project_id": "project-uuid"})
)
```
## Running Tests
@@ -145,11 +143,14 @@ Create `tests/test_cisco.py`:
import unittest
from gns3server.agent.gns3_copilot.utils.custom_netmiko import cisco
class TestCustomCiscoDriver(unittest.TestCase):
def test_device_type_registered(self):
from netmiko.ssh_dispatcher import CLASS_MAPPER
self.assertIn("cisco_custom", CLASS_MAPPER)
if __name__ == "__main__":
unittest.main()
```
@@ -209,7 +209,7 @@ class GNS3HuaweiTelnetCE(HuaweiBase):
# Ensure we're in a clean state
try:
if hasattr(self, 'base_prompt') and self.base_prompt:
if hasattr(self, "base_prompt") and self.base_prompt:
self._test_channel_read(pattern=self.base_prompt)
else:
# If base_prompt is not set yet, just read to clear buffer
@@ -288,9 +288,7 @@ class GNS3HuaweiTelnetCE(HuaweiBase):
# This method keeps reading until there is no new data for
# 'last_read' seconds. This is the proper Netmiko way to handle
# command output
output += self.read_channel_timing(
read_timeout=read_timeout, last_read=2.0
)
output += self.read_channel_timing(read_timeout=read_timeout, last_read=2.0)
# Exit config mode if requested
if exit_config_mode:
@@ -318,9 +316,7 @@ class GNS3HuaweiTelnetCE(HuaweiBase):
return output
def exit_config_mode(
self, exit_config: str = "return", pattern: str = r"<\S+>|>\s*$"
) -> str:
def exit_config_mode(self, exit_config: str = "return", pattern: str = r"<\S+>|>\s*$") -> str:
r"""
Exit configuration mode for Huawei devices.
@@ -377,6 +373,7 @@ class GNS3HuaweiTelnetCE(HuaweiBase):
# Register the custom device type with Netmiko
_registered = False # Flag to prevent duplicate registration
def register_custom_device_type() -> None:
"""
Register the custom GNS3HuaweiTelnetCE device type with Netmiko.
@@ -451,7 +448,4 @@ try:
except Exception as e:
# Log but don't fail on import
logger = logging.getLogger(__name__)
logger.warning(
f"Failed to register custom device type: {e}",
exc_info=True
)
logger.warning(f"Failed to register custom device type: {e}", exc_info=True)
@@ -60,12 +60,12 @@ class RuijieTelnetEnhanced(RuijieOSBase):
# Interactive command patterns that trigger [yes/no] prompts
# These are commands that commonly require confirmation
INTERACTIVE_PATTERNS = [
re.compile(r'^router-id\s+', re.IGNORECASE), # OSPF router-id
re.compile(r'^erase\s+', re.IGNORECASE), # erase startup-config
re.compile(r'^delete\s+', re.IGNORECASE), # delete files
re.compile(r'^format\s+', re.IGNORECASE), # format filesystem
re.compile(r'^reload\b', re.IGNORECASE), # reload/reboot
re.compile(r'^boot\s+system\s+', re.IGNORECASE), # change boot image
re.compile(r"^router-id\s+", re.IGNORECASE), # OSPF router-id
re.compile(r"^erase\s+", re.IGNORECASE), # erase startup-config
re.compile(r"^delete\s+", re.IGNORECASE), # delete files
re.compile(r"^format\s+", re.IGNORECASE), # format filesystem
re.compile(r"^reload\b", re.IGNORECASE), # reload/reboot
re.compile(r"^boot\s+system\s+", re.IGNORECASE), # change boot image
]
def __init__(
@@ -80,9 +80,7 @@ class RuijieTelnetEnhanced(RuijieOSBase):
kwargs["default_enter"] = "\r\n"
super().__init__(*args, **kwargs)
def _preprocess_interactive_commands(
self, config_commands: list[str]
) -> list[str]:
def _preprocess_interactive_commands(self, config_commands: list[str]) -> list[str]:
"""
Preprocess commands to insert 'yes' after interactive commands.
@@ -105,8 +103,7 @@ class RuijieTelnetEnhanced(RuijieOSBase):
for pattern in self.INTERACTIVE_PATTERNS:
if pattern.match(cmd.strip()):
logging.info(
"Ruijie device: Detected interactive command '%s', "
"inserting 'yes'",
"Ruijie device: Detected interactive command '%s', inserting 'yes'",
cmd.strip(),
)
# Insert 'yes' after this command
@@ -153,25 +150,18 @@ class RuijieTelnetEnhanced(RuijieOSBase):
output += self.config_mode()
# Preprocess: Insert 'yes' after known interactive commands
processed_commands = self._preprocess_interactive_commands(
config_commands
)
processed_commands = self._preprocess_interactive_commands(config_commands)
# Try batch send first (fast path)
try:
output += self._send_config_batch(
processed_commands, read_timeout, delay_factor
)
output += self._send_config_batch(processed_commands, read_timeout, delay_factor)
except Exception as batch_error:
logging.warning(
"Ruijie device: Batch send failed, "
"falling back to one-by-one: %s",
"Ruijie device: Batch send failed, falling back to one-by-one: %s",
batch_error,
)
# Fallback to one-by-one send with real-time detection
output += self._send_config_one_by_one(
config_commands, read_timeout, delay_factor
)
output += self._send_config_one_by_one(config_commands, read_timeout, delay_factor)
# Exit config mode if requested
if exit_config_mode:
@@ -179,9 +169,7 @@ class RuijieTelnetEnhanced(RuijieOSBase):
return output
def _send_config_batch(
self, commands: list[str], read_timeout: int, delay_factor: float
) -> str:
def _send_config_batch(self, commands: list[str], read_timeout: int, delay_factor: float) -> str:
"""
Send configuration commands in batch (fast).
@@ -197,15 +185,11 @@ class RuijieTelnetEnhanced(RuijieOSBase):
# Read all output at once
# Use same default as Netmiko (2.0 seconds) to ensure complete output
output += self.read_channel_timing(
read_timeout=read_timeout, last_read=2.0
)
output += self.read_channel_timing(read_timeout=read_timeout, last_read=2.0)
return output
def _send_config_one_by_one(
self, commands: list[str], read_timeout: int, delay_factor: float
) -> str:
def _send_config_one_by_one(self, commands: list[str], read_timeout: int, delay_factor: float) -> str:
"""
Send commands one-by-one with real-time prompt detection.
@@ -225,26 +209,21 @@ class RuijieTelnetEnhanced(RuijieOSBase):
time.sleep(0.05)
# Read output after this command
new_output = self.read_channel_timing(
read_timeout=10, last_read=0.5
)
new_output = self.read_channel_timing(read_timeout=10, last_read=0.5)
output += new_output
# Check if interactive prompt appeared after this command
for pattern in interactive_patterns:
if re.search(pattern, new_output, re.IGNORECASE):
logging.info(
"Ruijie device: Detected interactive prompt "
"after '%s', sending 'yes'",
"Ruijie device: Detected interactive prompt after '%s', sending 'yes'",
cmd,
)
# Send 'yes' to confirm
self.write_channel(f"yes{self.RETURN}")
time.sleep(0.3)
# Read the confirmation response
output += self.read_channel_timing(
read_timeout=30, last_read=0.5
)
output += self.read_channel_timing(read_timeout=30, last_read=0.5)
break
return output
@@ -253,6 +232,7 @@ class RuijieTelnetEnhanced(RuijieOSBase):
# Register the custom device type with Netmiko
_registered = False # Flag to prevent duplicate registration
def register_custom_device_type() -> None:
"""
Register the custom RuijieTelnetEnhanced device type with Netmiko.
@@ -295,7 +275,4 @@ try:
register_custom_device_type()
except Exception as e:
logger = logging.getLogger(__name__)
logger.warning(
f"Failed to register Ruijie device type: {e}",
exc_info=True
)
logger.warning(f"Failed to register Ruijie device type: {e}", exc_info=True)
@@ -39,6 +39,7 @@ def get_netmiko_version() -> str:
"""Get Netmiko library version."""
try:
import netmiko
return netmiko.__version__
except Exception:
return "Unknown"
@@ -68,47 +69,39 @@ def register_custom_drivers() -> set:
try:
# Import and register Huawei CE driver
from gns3server.agent.gns3_copilot.utils.custom_netmiko \
import huawei_ce
from gns3server.agent.gns3_copilot.utils.custom_netmiko import huawei_ce
# Store custom device types before registration
custom_devices.add("gns3_huawei_telnet_ce")
# The driver auto-registers on import, but we can call it
# explicitly
if hasattr(huawei_ce, 'register_custom_device_type'):
if hasattr(huawei_ce, "register_custom_device_type"):
huawei_ce.register_custom_device_type()
except ImportError:
# Silently skip if custom drivers are not available
pass
except Exception as e:
# Log but don't fail
print(
f"Warning: Failed to register Huawei CE driver: {e}",
file=sys.stderr
)
print(f"Warning: Failed to register Huawei CE driver: {e}", file=sys.stderr)
try:
# Import and register Ruijie Telnet driver
from gns3server.agent.gns3_copilot.utils.custom_netmiko \
import ruijie_telnet
from gns3server.agent.gns3_copilot.utils.custom_netmiko import ruijie_telnet
# Store custom device types before registration
custom_devices.add("gns3_ruijie_telnet")
# The driver auto-registers on import, but we can call it
# explicitly
if hasattr(ruijie_telnet, 'register_custom_device_type'):
if hasattr(ruijie_telnet, "register_custom_device_type"):
ruijie_telnet.register_custom_device_type()
except ImportError:
# Silently skip if custom drivers are not available
pass
except Exception as e:
# Log but don't fail
print(
f"Warning: Failed to register Ruijie driver: {e}",
file=sys.stderr
)
print(f"Warning: Failed to register Ruijie driver: {e}", file=sys.stderr)
return custom_devices
@@ -124,32 +117,32 @@ def extract_brand_name(device_type: str) -> str:
Brand name in a standardized format
"""
# Remove common suffixes
name = device_type.replace('_telnet', '').replace('_ssh', '')
name = name.replace('_serial', '')
name = device_type.replace("_telnet", "").replace("_ssh", "")
name = name.replace("_serial", "")
# Map to standard brand names
brand_mapping = {
'cisco': 'Cisco',
'huawei': 'Huawei',
'juniper': 'Juniper',
'arista': 'Arista',
'hp': 'HP',
'aruba': 'Aruba',
'dell': 'Dell',
'brocade': 'Brocade',
'extreme': 'Extreme',
'ruckus': 'Ruckus',
'ruijie': 'Ruijie (锐捷)',
'zte': 'ZTE (中兴)',
'maipu': 'Maipu (迈普)',
'h3c': 'H3C (华三)',
'nokia': 'Nokia',
'paloalto': 'Palo Alto',
'f5': 'F5',
'checkpoint': 'Check Point',
'generic': 'Generic',
'huaweiyt': 'HuaweiYT',
'vsz': 'Ruckus',
"cisco": "Cisco",
"huawei": "Huawei",
"juniper": "Juniper",
"arista": "Arista",
"hp": "HP",
"aruba": "Aruba",
"dell": "Dell",
"brocade": "Brocade",
"extreme": "Extreme",
"ruckus": "Ruckus",
"ruijie": "Ruijie (锐捷)",
"zte": "ZTE (中兴)",
"maipu": "Maipu (迈普)",
"h3c": "H3C (华三)",
"nokia": "Nokia",
"paloalto": "Palo Alto",
"f5": "F5",
"checkpoint": "Check Point",
"generic": "Generic",
"huaweiyt": "HuaweiYT",
"vsz": "Ruckus",
}
# Check for exact matches first
@@ -163,7 +156,7 @@ def extract_brand_name(device_type: str) -> str:
return value
# Return capitalized version of the first part
return name.split('_')[0].capitalize()
return name.split("_")[0].capitalize()
def group_devices_by_brand(device_types: List[str]) -> Dict[str, List[str]]:
@@ -205,8 +198,8 @@ def get_devices_by_protocol() -> Tuple[List[str], List[str]]:
for device_type in CLASS_MAPPER.keys():
device_lower = device_type.lower()
has_telnet = 'telnet' in device_lower
has_ssh = 'ssh' in device_lower
has_telnet = "telnet" in device_lower
has_ssh = "ssh" in device_lower
if has_telnet and has_ssh:
both_protocols.append(device_type)
@@ -230,17 +223,14 @@ def get_total_device_count() -> int:
"""Get total number of device types in Netmiko."""
try:
from netmiko.ssh_dispatcher import CLASS_MAPPER
return len(CLASS_MAPPER)
except ImportError:
return 0
def generate_markdown(
version: str,
ssh_devices: List[str],
telnet_devices: List[str],
total_count: int,
custom_devices: set
version: str, ssh_devices: List[str], telnet_devices: List[str], total_count: int, custom_devices: set
) -> str:
"""
Generate Markdown documentation with table format.
@@ -261,7 +251,7 @@ def generate_markdown(
md_lines.append("# Netmiko Supported Devices")
md_lines.append("")
md_lines.append(f"**Netmiko Version:** {version}")
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
md_lines.append(f"**Generated:** {timestamp}")
md_lines.append("")
md_lines.append("---")
@@ -313,8 +303,7 @@ def generate_markdown(
# Footer
md_lines.append("---")
md_lines.append("")
md_lines.append("*This document was generated automatically by the "
"Netmiko device list script.*")
md_lines.append("*This document was generated automatically by the Netmiko device list script.*")
return "\n".join(md_lines)
@@ -332,10 +321,8 @@ def save_markdown(content: str, filename: str = "netmiko_devices.md") -> None:
# Go up from: .../gns3server/agent/gns3_copilot/utils/
# custom_netmiko/scripts to project root
# That's 6 levels up
project_root = os.path.dirname(os.path.dirname(
os.path.dirname(os.path.dirname(
os.path.dirname(os.path.dirname(script_dir))))
)
project_root = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(script_dir)))))
)
# Target directory: docs/gns3-copilot
@@ -348,17 +335,15 @@ def save_markdown(content: str, filename: str = "netmiko_devices.md") -> None:
output_path = os.path.join(target_dir, filename)
try:
with open(output_path, 'w', encoding='utf-8') as f:
with open(output_path, "w", encoding="utf-8") as f:
f.write(content)
print(f"Markdown document saved to: {output_path}")
except IOError as e:
print(f"Error: Failed to save Markdown file: {e}",
file=sys.stderr)
print(f"Error: Failed to save Markdown file: {e}", file=sys.stderr)
sys.exit(1)
def print_summary(version: str, ssh_count: int, telnet_count: int,
total_count: int, output_file: str) -> None:
def print_summary(version: str, ssh_count: int, telnet_count: int, total_count: int, output_file: str) -> None:
"""Print summary to console."""
print("=" * 80)
print("Netmiko Device List Generator")
@@ -402,7 +387,7 @@ def main() -> int:
ssh_devices=ssh_devices,
telnet_devices=telnet_devices,
total_count=total_count,
custom_devices=custom_devices
custom_devices=custom_devices,
)
# Save to file
@@ -415,7 +400,7 @@ def main() -> int:
ssh_count=len(ssh_devices),
telnet_count=len(telnet_devices),
total_count=total_count,
output_file=output_file
output_file=output_file,
)
return 0
@@ -25,9 +25,7 @@ import os
# Add project root to path using relative path
test_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(
os.path.dirname(os.path.dirname(
os.path.dirname(os.path.dirname(test_dir)))))
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(test_dir)))))
sys.path.insert(0, project_root)
@@ -103,13 +101,8 @@ class TestHuaweiTelnetCEDriver(unittest.TestCase):
def test_initialization_parameters(self):
"""Test that initialization sets correct parameters."""
# Create a mock instance (without actual connection)
with patch.object(
self.HuaweiTelnetCE, "__init__",
lambda self, *args, **kwargs: None
):
instance = self.HuaweiTelnetCE.__new__(
self.HuaweiTelnetCE
)
with patch.object(self.HuaweiTelnetCE, "__init__", lambda self, *args, **kwargs: None):
instance = self.HuaweiTelnetCE.__new__(self.HuaweiTelnetCE)
# Mock the necessary attributes
instance.protocol = "telnet"
@@ -169,9 +162,7 @@ class TestHuaweiTelnetCEIntegration(unittest.TestCase):
)
# Create a mock instance
instance = GNS3HuaweiTelnetCE.__new__(
GNS3HuaweiTelnetCE
)
instance = GNS3HuaweiTelnetCE.__new__(GNS3HuaweiTelnetCE)
# Mock the necessary attributes and methods
instance.host = "127.0.0.1"
@@ -203,12 +194,8 @@ def run_tests():
suite = unittest.TestSuite()
# Add test cases
suite.addTests(
loader.loadTestsFromTestCase(TestHuaweiTelnetCEDriver)
)
suite.addTests(
loader.loadTestsFromTestCase(TestHuaweiTelnetCEIntegration)
)
suite.addTests(loader.loadTestsFromTestCase(TestHuaweiTelnetCEDriver))
suite.addTests(loader.loadTestsFromTestCase(TestHuaweiTelnetCEIntegration))
# Run tests
runner = unittest.TextTestRunner(verbosity=2)
@@ -218,9 +205,7 @@ def run_tests():
print("\n" + "=" * 100)
print("Test Summary:")
print(f" Run: {result.testsRun}")
success_count = (
result.testsRun - len(result.failures) - len(result.errors)
)
success_count = result.testsRun - len(result.failures) - len(result.errors)
print(f" Success: {success_count}")
print(f" Failed: {len(result.failures)}")
print(f" Errors: {len(result.errors)}")
@@ -25,9 +25,7 @@ import os
# Add project root to path using relative path
test_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(
os.path.dirname(os.path.dirname(
os.path.dirname(os.path.dirname(test_dir)))))
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(test_dir)))))
sys.path.insert(0, project_root)
@@ -92,9 +90,7 @@ class TestRuijieTelnetEnhancedDriver(unittest.TestCase):
import re
# Should have INTERACTIVE_PATTERNS as a class attribute
self.assertTrue(
hasattr(self.RuijieTelnetEnhanced, "INTERACTIVE_PATTERNS")
)
self.assertTrue(hasattr(self.RuijieTelnetEnhanced, "INTERACTIVE_PATTERNS"))
# Check it's a list
patterns = self.RuijieTelnetEnhanced.INTERACTIVE_PATTERNS
@@ -108,18 +104,11 @@ class TestRuijieTelnetEnhancedDriver(unittest.TestCase):
def test_preprocess_interactive_commands(self):
"""Test that interactive commands are preprocessed correctly."""
# Create a mock instance (without actual connection)
with patch.object(
self.RuijieTelnetEnhanced, "__init__",
lambda self, *args, **kwargs: None
):
instance = self.RuijieTelnetEnhanced.__new__(
self.RuijieTelnetEnhanced
)
with patch.object(self.RuijieTelnetEnhanced, "__init__", lambda self, *args, **kwargs: None):
instance = self.RuijieTelnetEnhanced.__new__(self.RuijieTelnetEnhanced)
# Set INTERACTIVE_PATTERNS from class
instance.INTERACTIVE_PATTERNS = (
self.RuijieTelnetEnhanced.INTERACTIVE_PATTERNS
)
instance.INTERACTIVE_PATTERNS = self.RuijieTelnetEnhanced.INTERACTIVE_PATTERNS
# Test commands with router-id (should trigger 'yes')
commands = [
@@ -128,34 +117,23 @@ class TestRuijieTelnetEnhancedDriver(unittest.TestCase):
"network 10.0.0.0 0.0.0.255 area 0",
]
processed = instance._preprocess_interactive_commands(
commands
)
processed = instance._preprocess_interactive_commands(commands)
# Should have inserted 'yes' after router-id command
self.assertEqual(len(processed), 4)
self.assertEqual(processed[0], "router ospf 1")
self.assertEqual(processed[1], "router-id 1.1.1.1")
self.assertEqual(processed[2], "yes")
self.assertEqual(
processed[3], "network 10.0.0.0 0.0.0.255 area 0"
)
self.assertEqual(processed[3], "network 10.0.0.0 0.0.0.255 area 0")
def test_preprocess_non_interactive_commands(self):
"""Test that non-interactive commands are not modified."""
# Create a mock instance
with patch.object(
self.RuijieTelnetEnhanced, "__init__",
lambda self, *args, **kwargs: None
):
instance = self.RuijieTelnetEnhanced.__new__(
self.RuijieTelnetEnhanced
)
with patch.object(self.RuijieTelnetEnhanced, "__init__", lambda self, *args, **kwargs: None):
instance = self.RuijieTelnetEnhanced.__new__(self.RuijieTelnetEnhanced)
# Set INTERACTIVE_PATTERNS from class
instance.INTERACTIVE_PATTERNS = (
self.RuijieTelnetEnhanced.INTERACTIVE_PATTERNS
)
instance.INTERACTIVE_PATTERNS = self.RuijieTelnetEnhanced.INTERACTIVE_PATTERNS
# Test commands without interactive prompts
commands = [
@@ -190,9 +168,7 @@ class TestRuijieTelnetEnhancedDriver(unittest.TestCase):
# Check that default_enter is handled in __init__
# (The actual logic is in the __init__ method body)
init_source = inspect.getsource(
self.RuijieTelnetEnhanced.__init__
)
init_source = inspect.getsource(self.RuijieTelnetEnhanced.__init__)
self.assertIn("default_enter", init_source)
self.assertIn("\\r\\n", init_source)
@@ -207,9 +183,7 @@ class TestRuijieTelnetEnhancedIntegration(unittest.TestCase):
)
# Create a mock instance
instance = RuijieTelnetEnhanced.__new__(
RuijieTelnetEnhanced
)
instance = RuijieTelnetEnhanced.__new__(RuijieTelnetEnhanced)
# Mock the necessary attributes and methods
instance.RETURN = "\r\n"
@@ -224,9 +198,7 @@ class TestRuijieTelnetEnhancedIntegration(unittest.TestCase):
commands = ["interface GigabitEthernet 0/1", "description Test"]
# Call send_config_set
instance.send_config_set(
commands, enter_config_mode=False, exit_config_mode=False
)
instance.send_config_set(commands, enter_config_mode=False, exit_config_mode=False)
# Verify _send_config_batch was called
instance._send_config_batch.assert_called_once()
@@ -238,17 +210,13 @@ class TestRuijieTelnetEnhancedIntegration(unittest.TestCase):
)
# Create a mock instance
instance = RuijieTelnetEnhanced.__new__(
RuijieTelnetEnhanced
)
instance.INTERACTIVE_PATTERNS = (
RuijieTelnetEnhanced.INTERACTIVE_PATTERNS
)
instance = RuijieTelnetEnhanced.__new__(RuijieTelnetEnhanced)
instance.INTERACTIVE_PATTERNS = RuijieTelnetEnhanced.INTERACTIVE_PATTERNS
# Test commands with multiple interactive prompts
commands = [
"router-id 1.1.1.1", # Should trigger 'yes'
"erase startup-config", # Should trigger 'yes'
"router-id 1.1.1.1", # Should trigger 'yes'
"erase startup-config", # Should trigger 'yes'
"interface GigabitEthernet 0/1", # Should NOT trigger
]
@@ -273,12 +241,8 @@ def run_tests():
suite = unittest.TestSuite()
# Add test cases
suite.addTests(
loader.loadTestsFromTestCase(TestRuijieTelnetEnhancedDriver)
)
suite.addTests(
loader.loadTestsFromTestCase(TestRuijieTelnetEnhancedIntegration)
)
suite.addTests(loader.loadTestsFromTestCase(TestRuijieTelnetEnhancedDriver))
suite.addTests(loader.loadTestsFromTestCase(TestRuijieTelnetEnhancedIntegration))
# Run tests
runner = unittest.TextTestRunner(verbosity=2)
@@ -288,9 +252,7 @@ def run_tests():
print("\n" + "=" * 100)
print("Test Summary:")
print(f" Run: {result.testsRun}")
success_count = (
result.testsRun - len(result.failures) - len(result.errors)
)
success_count = result.testsRun - len(result.failures) - len(result.errors)
print(f" Success: {success_count}")
print(f" Failed: {len(result.failures)}")
print(f" Errors: {len(result.errors)}")
@@ -27,9 +27,7 @@ import os
# Add project root to path using relative path
test_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(
os.path.dirname(os.path.dirname(
os.path.dirname(os.path.dirname(test_dir)))))
project_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(test_dir)))))
sys.path.insert(0, project_root)
@@ -121,6 +119,7 @@ class TestVPCSTelnetInit(unittest.TestCase):
from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( # noqa: E501
VPCSTelnet,
)
cls.VPCSTelnet = VPCSTelnet
def test_default_enter_parameter(self):
@@ -134,6 +133,7 @@ class TestVPCSTelnetInit(unittest.TestCase):
def test_default_parameters_when_none(self):
"""Test default parameters when not provided."""
# Mock the BaseConnection.__init__ to avoid actual connection
# But also set device_type since VPCSTelnet.__init__ sets it
def mock_init(self, *args, **kwargs):
@@ -141,23 +141,20 @@ class TestVPCSTelnetInit(unittest.TestCase):
kwargs.setdefault("device_type", "gns3_vpcs_telnet")
self.device_type = kwargs.get("device_type")
with patch.object(
self.VPCSTelnet, "__init__", mock_init
):
with patch.object(self.VPCSTelnet, "__init__", mock_init):
instance = self.VPCSTelnet(host="127.0.0.1")
self.assertEqual(instance.device_type, "gns3_vpcs_telnet")
def test_device_type_set(self):
"""Test that device_type is set to gns3_vpcs_telnet."""
# Mock the __init__ to set device_type
def mock_init(self, *args, **kwargs):
# Simulate what VPCSTelnet.__init__ does
kwargs.setdefault("device_type", "gns3_vpcs_telnet")
self.device_type = kwargs.get("device_type")
with patch.object(
self.VPCSTelnet, "__init__", mock_init
):
with patch.object(self.VPCSTelnet, "__init__", mock_init):
instance = self.VPCSTelnet(host="127.0.0.1")
self.assertEqual(instance.device_type, "gns3_vpcs_telnet")
@@ -171,6 +168,7 @@ class TestVPCSTelnetMethods(unittest.TestCase):
from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( # noqa: E501
VPCSTelnet,
)
cls.VPCSTelnet = VPCSTelnet
def test_check_config_mode_always_false(self):
@@ -213,6 +211,7 @@ class TestVPCSTelnetSendCommand(unittest.TestCase):
from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( # noqa: E501
VPCSTelnet,
)
cls.VPCSTelnet = VPCSTelnet
def test_send_command_writes_bytes(self):
@@ -243,7 +242,7 @@ class TestVPCSTelnetSendCommand(unittest.TestCase):
# Check that expect_string defaults to r"PC\d+>"
source = inspect.getsource(self.VPCSTelnet.send_command)
self.assertIn("expect_string = r\"PC\\d+>\"", source)
self.assertIn('expect_string = r"PC\\d+>"', source)
def test_send_command_timing_calls_send_command(self):
"""Test that send_command_timing delegates to send_command."""
@@ -277,6 +276,7 @@ class TestVPCSTelnetTelnetLogin(unittest.TestCase):
from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( # noqa: E501
VPCSTelnet,
)
cls.VPCSTelnet = VPCSTelnet
def test_telnet_login_default_prompt_pattern(self):
@@ -335,19 +335,16 @@ class TestVPCSTelnetRegistration(unittest.TestCase):
from gns3server.agent.gns3_copilot.utils.custom_netmiko import (
vpcs_telnet,
)
cls.vpcs_telnet = vpcs_telnet
def test_register_function_exists(self):
"""Test that register_custom_device_type function exists."""
self.assertTrue(
hasattr(self.vpcs_telnet, "register_custom_device_type")
)
self.assertTrue(hasattr(self.vpcs_telnet, "register_custom_device_type"))
def test_register_function_is_callable(self):
"""Test that register_custom_device_type is callable."""
self.assertTrue(
callable(self.vpcs_telnet.register_custom_device_type)
)
self.assertTrue(callable(self.vpcs_telnet.register_custom_device_type))
class TestVPCSTelnetAnsiStripping(unittest.TestCase):
@@ -359,6 +356,7 @@ class TestVPCSTelnetAnsiStripping(unittest.TestCase):
from gns3server.agent.gns3_copilot.utils.custom_netmiko.vpcs_telnet import ( # noqa: E501
VPCSTelnet,
)
cls.VPCSTelnet = VPCSTelnet
def test_strip_ansi_codes_method_exists(self):
@@ -435,14 +433,10 @@ IP/MASK : 192.168.1.20/24"""
# Mock necessary attributes
instance.remote_conn = MagicMock()
instance.remote_conn.write = MagicMock()
instance.read_until_pattern = MagicMock(
return_value="\x1b[1mNAME\x1b[0m : PC2\nPC2>"
)
instance.read_until_pattern = MagicMock(return_value="\x1b[1mNAME\x1b[0m : PC2\nPC2>")
# Mock _strip_ansi_codes to verify it's called
instance._strip_ansi_codes = Mock(
return_value="NAME : PC2\nPC2>"
)
instance._strip_ansi_codes = Mock(return_value="NAME : PC2\nPC2>")
# Call send_command
instance.send_command("show ip")
@@ -476,9 +470,7 @@ def run_tests():
print("\n" + "=" * 100)
print("Test Summary:")
print(f" Run: {result.testsRun}")
success_count = (
result.testsRun - len(result.failures) - len(result.errors)
)
success_count = result.testsRun - len(result.failures) - len(result.errors)
print(f" Success: {success_count}")
print(f" Failed: {len(result.failures)}")
print(f" Errors: {len(result.errors)}")
@@ -149,10 +149,7 @@ class VPCSTelnet(BaseConnection):
# Step 3: Wait for VPCS prompt pattern
try:
# Read until we see the prompt
output = self.read_until_pattern(
pattern=pri_prompt_terminator,
read_timeout=10
)
output = self.read_until_pattern(pattern=pri_prompt_terminator, read_timeout=10)
return_msg += output
if re.search(pri_prompt_terminator, return_msg, flags=re.M):
@@ -291,9 +288,7 @@ class VPCSTelnet(BaseConnection):
strip_command=strip_command,
)
def check_config_mode(
self, check_string: str = "", pattern: str = "", force_regex: bool = False
) -> bool:
def check_config_mode(self, check_string: str = "", pattern: str = "", force_regex: bool = False) -> bool:
"""
VPCS has no configuration mode.
@@ -326,9 +321,7 @@ class VPCSTelnet(BaseConnection):
"""
return ANSI_ESCAPE_PATTERN.sub("", text)
def config_mode(
self, config_command: str = "", pattern: str = "", re_flags: int = 0
) -> str:
def config_mode(self, config_command: str = "", pattern: str = "", re_flags: int = 0) -> str:
"""
VPCS has no configuration mode.
@@ -370,6 +363,7 @@ class VPCSTelnet(BaseConnection):
# Register the custom device type with Netmiko
_registered = False # Flag to prevent duplicate registration
def register_custom_device_type() -> None:
"""
Register the custom VPCS Telnet device type with Netmiko.
@@ -431,8 +425,4 @@ try:
register_custom_device_type()
except Exception as e:
# Log but don't fail on import
logger.warning(
"Failed to register VPCS device type: %s",
e,
exc_info=True
)
logger.warning("Failed to register VPCS device type: %s", e, exc_info=True)