"""Tests for SSH-specific CSV export functionality.""" from unittest.mock import Mock, patch from src.sslysze_scan.reporter.csv_export import ( _export_ssh_encryption_algorithms, _export_ssh_host_keys, _export_ssh_kex_methods, _export_ssh_mac_algorithms, ) class TestSshCsvExport: """Tests for SSH CSV export functions.""" def test_export_ssh_kex_methods(self) -> None: """Test SSH key exchange methods export.""" # Create mock exporter with patch( "src.sslysze_scan.reporter.csv_export.CSVExporter" ) as mock_exporter_class: mock_exporter = Mock() mock_exporter_class.return_value = mock_exporter mock_exporter.write_csv.return_value = "/tmp/test.csv" # Test data port = 22 ssh_kex_methods = [ { "name": "curve25519-sha256", "accepted": True, "iana_recommended": "Y", "bsi_approved": True, "bsi_valid_until": 2031, "compliant": True, }, { "name": "diffie-hellman-group14-sha256", "accepted": True, "iana_recommended": "N", "bsi_approved": False, "bsi_valid_until": None, "compliant": False, }, ] # Call the function result = _export_ssh_kex_methods(mock_exporter, port, ssh_kex_methods) # Verify the call assert len(result) == 1 mock_exporter.write_csv.assert_called_once() args, kwargs = mock_exporter.write_csv.call_args assert args[0] == "22_ssh_kex_methods.csv" assert args[1] == "ssh_kex_methods" assert len(args[2]) == 2 # Two rows of data plus header def test_export_ssh_encryption_algorithms(self) -> None: """Test SSH encryption algorithms export.""" # Create mock exporter with patch( "src.sslysze_scan.reporter.csv_export.CSVExporter" ) as mock_exporter_class: mock_exporter = Mock() mock_exporter_class.return_value = mock_exporter mock_exporter.write_csv.return_value = "/tmp/test.csv" # Test data port = 22 ssh_encryption_algorithms = [ { "name": "chacha20-poly1305@openssh.com", "accepted": True, "iana_recommended": "Y", "bsi_approved": True, "bsi_valid_until": 2031, "compliant": True, }, { "name": "aes128-ctr", "accepted": True, "iana_recommended": "N", "bsi_approved": False, "bsi_valid_until": None, "compliant": False, }, ] # Call the function result = _export_ssh_encryption_algorithms( mock_exporter, port, ssh_encryption_algorithms ) # Verify the call assert len(result) == 1 mock_exporter.write_csv.assert_called_once() args, kwargs = mock_exporter.write_csv.call_args assert args[0] == "22_ssh_encryption_algorithms.csv" assert args[1] == "ssh_encryption_algorithms" assert len(args[2]) == 2 # Two rows of data plus header def test_export_ssh_mac_algorithms(self) -> None: """Test SSH MAC algorithms export.""" # Create mock exporter with patch( "src.sslysze_scan.reporter.csv_export.CSVExporter" ) as mock_exporter_class: mock_exporter = Mock() mock_exporter_class.return_value = mock_exporter mock_exporter.write_csv.return_value = "/tmp/test.csv" # Test data port = 22 ssh_mac_algorithms = [ { "name": "hmac-sha2-256", "accepted": True, "iana_recommended": "Y", "bsi_approved": True, "bsi_valid_until": 2031, "compliant": True, }, { "name": "umac-64-etm@openssh.com", "accepted": True, "iana_recommended": "N", "bsi_approved": False, "bsi_valid_until": None, "compliant": False, }, ] # Call the function result = _export_ssh_mac_algorithms(mock_exporter, port, ssh_mac_algorithms) # Verify the call assert len(result) == 1 mock_exporter.write_csv.assert_called_once() args, kwargs = mock_exporter.write_csv.call_args assert args[0] == "22_ssh_mac_algorithms.csv" assert args[1] == "ssh_mac_algorithms" assert len(args[2]) == 2 # Two rows of data plus header def test_export_ssh_host_keys(self) -> None: """Test SSH host keys export.""" # Create mock exporter with patch( "src.sslysze_scan.reporter.csv_export.CSVExporter" ) as mock_exporter_class: mock_exporter = Mock() mock_exporter_class.return_value = mock_exporter mock_exporter.write_csv.return_value = "/tmp/test.csv" # Test data port = 22 ssh_host_keys = [ { "algorithm": "ssh-ed25519", "type": "ed25519", "bits": 256, "bsi_approved": False, "bsi_valid_until": None, "compliant": False, }, { "algorithm": "rsa-sha2-512", "type": "rsa", "bits": 3072, "bsi_approved": False, "bsi_valid_until": None, "compliant": False, }, { "algorithm": "ecdsa-sha2-nistp256", "type": "ecdsa", "bits": None, # Test the derivation logic "bsi_approved": True, "bsi_valid_until": 2031, "compliant": True, }, { "algorithm": "rsa-sha2-256", "type": "rsa", "bits": "-", # Test the derivation logic "bsi_approved": False, "bsi_valid_until": None, "compliant": False, }, ] # Call the function result = _export_ssh_host_keys(mock_exporter, port, ssh_host_keys) # Verify the call assert len(result) == 1 mock_exporter.write_csv.assert_called_once() args, kwargs = mock_exporter.write_csv.call_args assert args[0] == "22_ssh_host_keys.csv" assert args[1] == "ssh_host_keys" assert len(args[2]) == 4 # Four rows of data plus header # Verify that each row has 6 columns (algorithm, type, bits, bsi_approved, bsi_valid_until, compliant) for row in args[2]: assert ( len(row) == 6 ) # 6 columns: Algorithm, Type, Bits, BSI Approved, BSI Valid Until, Compliant # Verify that the bits are derived correctly when not provided # Row 2 should have bits = 256 for nistp256 assert ( args[2][2][2] == 256 ) # Third row (index 2), third column (index 2) should be 256 # Row 3 should have bits = 2048 for rsa-sha2-256 assert ( args[2][3][2] == 2048 ) # Fourth row (index 3), third column (index 2) should be 2048 def test_export_ssh_host_keys_derived_bits(self) -> None: """Test that SSH host keys export properly derives bits from algorithm names.""" # Create mock exporter with patch( "src.sslysze_scan.reporter.csv_export.CSVExporter" ) as mock_exporter_class: mock_exporter = Mock() mock_exporter_class.return_value = mock_exporter mock_exporter.write_csv.return_value = "/tmp/test.csv" # Test data with missing bits to test derivation logic port = 22 ssh_host_keys = [ { "algorithm": "ecdsa-sha2-nistp521", "type": "ecdsa", "bits": None, "bsi_approved": True, "bsi_valid_until": 2031, "compliant": True, }, { "algorithm": "ecdsa-sha2-nistp384", "type": "ecdsa", "bits": None, "bsi_approved": True, "bsi_valid_until": 2031, "compliant": True, }, { "algorithm": "ecdsa-sha2-nistp256", "type": "ecdsa", "bits": None, "bsi_approved": True, "bsi_valid_until": 2031, "compliant": True, }, { "algorithm": "ssh-ed25519", "type": "ed25519", "bits": None, "bsi_approved": False, "bsi_valid_until": None, "compliant": False, }, { "algorithm": "rsa-sha2-256", "type": "rsa", "bits": None, "bsi_approved": False, "bsi_valid_until": None, "compliant": False, }, { "algorithm": "rsa-sha2-512", "type": "rsa", "bits": None, # Should derive 4096 from algorithm name "fingerprint": "SHA256:test6", "iana_recommended": None, "bsi_approved": False, "bsi_valid_until": None, "compliant": False, }, { "algorithm": "unknown-algorithm", "type": "unknown", "bits": None, # Should remain as "-" for unknown algorithm "fingerprint": "SHA256:test7", "iana_recommended": None, "bsi_approved": False, "bsi_valid_until": None, "compliant": False, }, ] # Call the function result = _export_ssh_host_keys(mock_exporter, port, ssh_host_keys) # Verify the call assert len(result) == 1 mock_exporter.write_csv.assert_called_once() args, kwargs = mock_exporter.write_csv.call_args # Verify that each row has 7 columns (algorithm, type, bits, iana_recommended, bsi_approved, bsi_valid_until, compliant) # Verify that each row has 6 columns for row in args[2]: assert ( len(row) == 6 ) # 6 columns: Algorithm, Type, Bits, BSI Approved, BSI Valid Until, Compliant # Verify that bits are derived correctly from algorithm names assert args[2][0][2] == 521 # nistp521 -> 521 assert args[2][1][2] == 384 # nistp384 -> 384 assert args[2][2][2] == 256 # nistp256 -> 256 assert args[2][3][2] == 255 # ed25519 -> 255 assert args[2][4][2] == 2048 # rsa-sha2-256 -> 2048 assert ( args[2][6][2] == "-" ) # unknown algorithm -> "-" (since bits is None and no derivation rule)