665 lines
24 KiB
Python
665 lines
24 KiB
Python
"""Unit tests for the BareMetalPlugin provider plugin."""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from iac_reverse.models import (
|
|
CpuArchitecture,
|
|
PlatformCategory,
|
|
ProviderType,
|
|
ScanProgress,
|
|
)
|
|
from iac_reverse.scanner import AuthenticationError
|
|
from iac_reverse.scanner.bare_metal_plugin import BareMetalPlugin
|
|
|
|
|
|
class TestBareMetalPluginInterface:
|
|
"""Tests for basic plugin interface compliance."""
|
|
|
|
def test_implements_provider_plugin(self):
|
|
"""BareMetalPlugin can be instantiated (implements all abstract methods)."""
|
|
plugin = BareMetalPlugin()
|
|
assert plugin is not None
|
|
|
|
def test_get_platform_category(self):
|
|
"""Returns PlatformCategory.BARE_METAL."""
|
|
plugin = BareMetalPlugin()
|
|
assert plugin.get_platform_category() == PlatformCategory.BARE_METAL
|
|
|
|
def test_list_supported_resource_types(self):
|
|
"""Returns the expected bare metal resource types."""
|
|
plugin = BareMetalPlugin()
|
|
expected = [
|
|
"bare_metal_hardware",
|
|
"bare_metal_bmc_config",
|
|
"bare_metal_network_interface",
|
|
"bare_metal_raid_config",
|
|
]
|
|
assert plugin.list_supported_resource_types() == expected
|
|
|
|
def test_list_endpoints_before_auth(self):
|
|
"""Returns empty list before authentication."""
|
|
plugin = BareMetalPlugin()
|
|
assert plugin.list_endpoints() == []
|
|
|
|
|
|
class TestBareMetalAuthentication:
|
|
"""Tests for BMC authentication via Redfish."""
|
|
|
|
@patch("iac_reverse.scanner.bare_metal_plugin.requests.Session")
|
|
def test_authenticate_success(self, mock_session_cls):
|
|
"""Successful authentication stores session and sets base URL."""
|
|
mock_session = MagicMock()
|
|
mock_session_cls.return_value = mock_session
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 201
|
|
mock_response.headers = {"X-Auth-Token": "test-token-123"}
|
|
mock_session.post.return_value = mock_response
|
|
|
|
plugin = BareMetalPlugin()
|
|
plugin.authenticate({
|
|
"host": "192.168.1.100",
|
|
"username": "admin",
|
|
"password": "secret",
|
|
})
|
|
|
|
assert plugin._host == "192.168.1.100"
|
|
assert plugin._base_url == "https://192.168.1.100:443"
|
|
assert plugin._session is not None
|
|
mock_session.post.assert_called_once()
|
|
|
|
@patch("iac_reverse.scanner.bare_metal_plugin.requests.Session")
|
|
def test_authenticate_custom_port(self, mock_session_cls):
|
|
"""Authentication uses custom port when specified."""
|
|
mock_session = MagicMock()
|
|
mock_session_cls.return_value = mock_session
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 200
|
|
mock_response.headers = {"X-Auth-Token": "token"}
|
|
mock_session.post.return_value = mock_response
|
|
|
|
plugin = BareMetalPlugin()
|
|
plugin.authenticate({
|
|
"host": "10.0.0.1",
|
|
"username": "admin",
|
|
"password": "pass",
|
|
"port": "8443",
|
|
})
|
|
|
|
assert plugin._base_url == "https://10.0.0.1:8443"
|
|
|
|
@patch("iac_reverse.scanner.bare_metal_plugin.requests.Session")
|
|
def test_authenticate_no_ssl(self, mock_session_cls):
|
|
"""Authentication uses HTTP when use_ssl is false."""
|
|
mock_session = MagicMock()
|
|
mock_session_cls.return_value = mock_session
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 200
|
|
mock_response.headers = {"X-Auth-Token": "token"}
|
|
mock_session.post.return_value = mock_response
|
|
|
|
plugin = BareMetalPlugin()
|
|
plugin.authenticate({
|
|
"host": "10.0.0.1",
|
|
"username": "admin",
|
|
"password": "pass",
|
|
"use_ssl": "false",
|
|
})
|
|
|
|
assert plugin._base_url == "http://10.0.0.1:443"
|
|
|
|
def test_authenticate_missing_host(self):
|
|
"""Raises AuthenticationError when host is missing."""
|
|
plugin = BareMetalPlugin()
|
|
with pytest.raises(AuthenticationError) as exc_info:
|
|
plugin.authenticate({"username": "admin", "password": "pass"})
|
|
assert "Missing required credentials" in str(exc_info.value)
|
|
|
|
def test_authenticate_missing_username(self):
|
|
"""Raises AuthenticationError when username is missing."""
|
|
plugin = BareMetalPlugin()
|
|
with pytest.raises(AuthenticationError):
|
|
plugin.authenticate({"host": "10.0.0.1", "password": "pass"})
|
|
|
|
def test_authenticate_missing_password(self):
|
|
"""Raises AuthenticationError when password is missing."""
|
|
plugin = BareMetalPlugin()
|
|
with pytest.raises(AuthenticationError):
|
|
plugin.authenticate({"host": "10.0.0.1", "username": "admin"})
|
|
|
|
@patch("iac_reverse.scanner.bare_metal_plugin.requests.Session")
|
|
def test_authenticate_401_unauthorized(self, mock_session_cls):
|
|
"""Raises AuthenticationError on HTTP 401."""
|
|
mock_session = MagicMock()
|
|
mock_session_cls.return_value = mock_session
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 401
|
|
mock_session.post.return_value = mock_response
|
|
|
|
plugin = BareMetalPlugin()
|
|
with pytest.raises(AuthenticationError) as exc_info:
|
|
plugin.authenticate({
|
|
"host": "10.0.0.1",
|
|
"username": "admin",
|
|
"password": "wrong",
|
|
})
|
|
assert "Invalid credentials" in str(exc_info.value)
|
|
|
|
@patch("iac_reverse.scanner.bare_metal_plugin.requests.Session")
|
|
def test_authenticate_connection_error(self, mock_session_cls):
|
|
"""Raises AuthenticationError on connection failure."""
|
|
import requests
|
|
|
|
mock_session = MagicMock()
|
|
mock_session_cls.return_value = mock_session
|
|
mock_session.post.side_effect = requests.exceptions.ConnectionError(
|
|
"Connection refused"
|
|
)
|
|
|
|
plugin = BareMetalPlugin()
|
|
with pytest.raises(AuthenticationError) as exc_info:
|
|
plugin.authenticate({
|
|
"host": "unreachable.host",
|
|
"username": "admin",
|
|
"password": "pass",
|
|
})
|
|
assert "Cannot connect" in str(exc_info.value)
|
|
|
|
@patch("iac_reverse.scanner.bare_metal_plugin.requests.Session")
|
|
def test_authenticate_timeout(self, mock_session_cls):
|
|
"""Raises AuthenticationError on timeout."""
|
|
import requests
|
|
|
|
mock_session = MagicMock()
|
|
mock_session_cls.return_value = mock_session
|
|
mock_session.post.side_effect = requests.exceptions.Timeout("Timed out")
|
|
|
|
plugin = BareMetalPlugin()
|
|
with pytest.raises(AuthenticationError) as exc_info:
|
|
plugin.authenticate({
|
|
"host": "slow.host",
|
|
"username": "admin",
|
|
"password": "pass",
|
|
})
|
|
assert "timed out" in str(exc_info.value)
|
|
|
|
@patch("iac_reverse.scanner.bare_metal_plugin.requests.Session")
|
|
def test_list_endpoints_after_auth(self, mock_session_cls):
|
|
"""Returns host as endpoint after successful authentication."""
|
|
mock_session = MagicMock()
|
|
mock_session_cls.return_value = mock_session
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 201
|
|
mock_response.headers = {"X-Auth-Token": "token"}
|
|
mock_session.post.return_value = mock_response
|
|
|
|
plugin = BareMetalPlugin()
|
|
plugin.authenticate({
|
|
"host": "192.168.1.50",
|
|
"username": "admin",
|
|
"password": "pass",
|
|
})
|
|
|
|
assert plugin.list_endpoints() == ["192.168.1.50"]
|
|
|
|
|
|
class TestBareMetalArchitectureDetection:
|
|
"""Tests for CPU architecture detection via Redfish."""
|
|
|
|
def test_detect_architecture_no_session(self):
|
|
"""Returns AMD64 default when no session is available."""
|
|
plugin = BareMetalPlugin()
|
|
assert plugin.detect_architecture("10.0.0.1") == CpuArchitecture.AMD64
|
|
|
|
@patch("iac_reverse.scanner.bare_metal_plugin.requests.Session")
|
|
def test_detect_architecture_amd64(self, mock_session_cls):
|
|
"""Detects AMD64 architecture from processor data."""
|
|
plugin = BareMetalPlugin()
|
|
mock_session = MagicMock()
|
|
plugin._session = mock_session
|
|
plugin._base_url = "https://10.0.0.1:443"
|
|
|
|
# Mock processors collection response
|
|
proc_collection_response = MagicMock()
|
|
proc_collection_response.status_code = 200
|
|
proc_collection_response.json.return_value = {
|
|
"Members": [{"@odata.id": "/redfish/v1/Systems/1/Processors/CPU.1"}]
|
|
}
|
|
|
|
# Mock individual processor response
|
|
proc_response = MagicMock()
|
|
proc_response.status_code = 200
|
|
proc_response.json.return_value = {
|
|
"InstructionSet": "x86-64",
|
|
"Model": "Intel Xeon E5-2680 v4",
|
|
}
|
|
|
|
mock_session.get.side_effect = [proc_collection_response, proc_response]
|
|
|
|
result = plugin.detect_architecture("10.0.0.1")
|
|
assert result == CpuArchitecture.AMD64
|
|
|
|
@patch("iac_reverse.scanner.bare_metal_plugin.requests.Session")
|
|
def test_detect_architecture_aarch64(self, mock_session_cls):
|
|
"""Detects AARCH64 architecture from processor data."""
|
|
plugin = BareMetalPlugin()
|
|
mock_session = MagicMock()
|
|
plugin._session = mock_session
|
|
plugin._base_url = "https://10.0.0.1:443"
|
|
|
|
proc_collection_response = MagicMock()
|
|
proc_collection_response.status_code = 200
|
|
proc_collection_response.json.return_value = {
|
|
"Members": [{"@odata.id": "/redfish/v1/Systems/1/Processors/CPU.1"}]
|
|
}
|
|
|
|
proc_response = MagicMock()
|
|
proc_response.status_code = 200
|
|
proc_response.json.return_value = {
|
|
"InstructionSet": "AArch64",
|
|
"Model": "Ampere Altra Q80-30",
|
|
}
|
|
|
|
mock_session.get.side_effect = [proc_collection_response, proc_response]
|
|
|
|
result = plugin.detect_architecture("10.0.0.1")
|
|
assert result == CpuArchitecture.AARCH64
|
|
|
|
@patch("iac_reverse.scanner.bare_metal_plugin.requests.Session")
|
|
def test_detect_architecture_arm_from_model(self, mock_session_cls):
|
|
"""Detects ARM architecture from model string."""
|
|
plugin = BareMetalPlugin()
|
|
mock_session = MagicMock()
|
|
plugin._session = mock_session
|
|
plugin._base_url = "https://10.0.0.1:443"
|
|
|
|
proc_collection_response = MagicMock()
|
|
proc_collection_response.status_code = 200
|
|
proc_collection_response.json.return_value = {
|
|
"Members": [{"@odata.id": "/redfish/v1/Systems/1/Processors/CPU.1"}]
|
|
}
|
|
|
|
proc_response = MagicMock()
|
|
proc_response.status_code = 200
|
|
proc_response.json.return_value = {
|
|
"InstructionSet": "",
|
|
"Model": "ARM Cortex-A53",
|
|
}
|
|
|
|
mock_session.get.side_effect = [proc_collection_response, proc_response]
|
|
|
|
result = plugin.detect_architecture("10.0.0.1")
|
|
assert result == CpuArchitecture.ARM
|
|
|
|
@patch("iac_reverse.scanner.bare_metal_plugin.requests.Session")
|
|
def test_detect_architecture_fallback_on_error(self, mock_session_cls):
|
|
"""Falls back to AMD64 on request error."""
|
|
plugin = BareMetalPlugin()
|
|
mock_session = MagicMock()
|
|
plugin._session = mock_session
|
|
plugin._base_url = "https://10.0.0.1:443"
|
|
|
|
mock_session.get.side_effect = Exception("Network error")
|
|
|
|
result = plugin.detect_architecture("10.0.0.1")
|
|
assert result == CpuArchitecture.AMD64
|
|
|
|
|
|
class TestBareMetalDiscoverResources:
|
|
"""Tests for resource discovery via Redfish."""
|
|
|
|
def _make_authenticated_plugin(self):
|
|
"""Create a plugin with a mocked session."""
|
|
plugin = BareMetalPlugin()
|
|
plugin._session = MagicMock()
|
|
plugin._base_url = "https://10.0.0.1:443"
|
|
plugin._host = "10.0.0.1"
|
|
return plugin
|
|
|
|
def test_discover_hardware(self):
|
|
"""Discovers hardware inventory from /redfish/v1/Systems/1."""
|
|
plugin = self._make_authenticated_plugin()
|
|
|
|
# Mock processor detection (for architecture)
|
|
proc_collection = MagicMock()
|
|
proc_collection.status_code = 200
|
|
proc_collection.json.return_value = {
|
|
"Members": [{"@odata.id": "/redfish/v1/Systems/1/Processors/CPU.1"}]
|
|
}
|
|
proc_detail = MagicMock()
|
|
proc_detail.status_code = 200
|
|
proc_detail.json.return_value = {
|
|
"InstructionSet": "x86-64",
|
|
"Model": "Intel Xeon",
|
|
}
|
|
|
|
# Mock system response
|
|
system_response = MagicMock()
|
|
system_response.status_code = 200
|
|
system_response.json.return_value = {
|
|
"Id": "System.Embedded.1",
|
|
"Name": "Dell PowerEdge R740",
|
|
"Manufacturer": "Dell Inc.",
|
|
"Model": "PowerEdge R740",
|
|
"SerialNumber": "ABC123",
|
|
"SKU": "R740",
|
|
"BiosVersion": "2.12.2",
|
|
"MemorySummary": {"TotalSystemMemoryGiB": 256},
|
|
"ProcessorSummary": {"Count": 2, "Model": "Intel Xeon Gold 6248"},
|
|
"PowerState": "On",
|
|
"Status": {"State": "Enabled", "Health": "OK"},
|
|
}
|
|
|
|
plugin._session.get.side_effect = [
|
|
proc_collection, proc_detail, system_response
|
|
]
|
|
|
|
progress_calls = []
|
|
result = plugin.discover_resources(
|
|
endpoints=["10.0.0.1"],
|
|
resource_types=["bare_metal_hardware"],
|
|
progress_callback=lambda p: progress_calls.append(p),
|
|
)
|
|
|
|
assert len(result.resources) == 1
|
|
resource = result.resources[0]
|
|
assert resource.resource_type == "bare_metal_hardware"
|
|
assert resource.unique_id == "10.0.0.1:System.Embedded.1"
|
|
assert resource.name == "Dell PowerEdge R740"
|
|
assert resource.provider == ProviderType.BARE_METAL
|
|
assert resource.platform_category == PlatformCategory.BARE_METAL
|
|
assert resource.architecture == CpuArchitecture.AMD64
|
|
assert resource.attributes["manufacturer"] == "Dell Inc."
|
|
assert resource.attributes["total_memory_gib"] == 256
|
|
assert resource.attributes["processor_count"] == 2
|
|
assert len(progress_calls) == 1
|
|
|
|
def test_discover_bmc_config(self):
|
|
"""Discovers BMC configuration from /redfish/v1/Managers/1."""
|
|
plugin = self._make_authenticated_plugin()
|
|
|
|
# Mock processor detection
|
|
proc_collection = MagicMock()
|
|
proc_collection.status_code = 200
|
|
proc_collection.json.return_value = {"Members": []}
|
|
|
|
# Mock manager response
|
|
manager_response = MagicMock()
|
|
manager_response.status_code = 200
|
|
manager_response.json.return_value = {
|
|
"Id": "iDRAC.Embedded.1",
|
|
"Name": "iDRAC Manager",
|
|
"ManagerType": "BMC",
|
|
"FirmwareVersion": "5.10.50.00",
|
|
"Model": "iDRAC9",
|
|
"Status": {"State": "Enabled", "Health": "OK"},
|
|
"UUID": "abc-def-123",
|
|
}
|
|
|
|
plugin._session.get.side_effect = [proc_collection, manager_response]
|
|
|
|
result = plugin.discover_resources(
|
|
endpoints=["10.0.0.1"],
|
|
resource_types=["bare_metal_bmc_config"],
|
|
progress_callback=lambda p: None,
|
|
)
|
|
|
|
assert len(result.resources) == 1
|
|
resource = result.resources[0]
|
|
assert resource.resource_type == "bare_metal_bmc_config"
|
|
assert resource.unique_id == "10.0.0.1:iDRAC.Embedded.1"
|
|
assert resource.attributes["firmware_version"] == "5.10.50.00"
|
|
assert resource.attributes["manager_type"] == "BMC"
|
|
|
|
def test_discover_network_interfaces(self):
|
|
"""Discovers network interfaces from Redfish EthernetInterfaces."""
|
|
plugin = self._make_authenticated_plugin()
|
|
|
|
# Mock processor detection
|
|
proc_collection = MagicMock()
|
|
proc_collection.status_code = 200
|
|
proc_collection.json.return_value = {"Members": []}
|
|
|
|
# Mock NIC collection
|
|
nic_collection = MagicMock()
|
|
nic_collection.status_code = 200
|
|
nic_collection.json.return_value = {
|
|
"Members": [
|
|
{"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces/NIC.1"},
|
|
{"@odata.id": "/redfish/v1/Systems/1/EthernetInterfaces/NIC.2"},
|
|
]
|
|
}
|
|
|
|
# Mock individual NIC responses
|
|
nic1_response = MagicMock()
|
|
nic1_response.status_code = 200
|
|
nic1_response.json.return_value = {
|
|
"Id": "NIC.Integrated.1-1",
|
|
"Name": "Ethernet Interface 1",
|
|
"MACAddress": "AA:BB:CC:DD:EE:01",
|
|
"SpeedMbps": 10000,
|
|
"Status": {"State": "Enabled", "Health": "OK"},
|
|
"IPv4Addresses": [{"Address": "192.168.1.10"}],
|
|
"IPv6Addresses": [],
|
|
"LinkStatus": "LinkUp",
|
|
"AutoNeg": True,
|
|
}
|
|
|
|
nic2_response = MagicMock()
|
|
nic2_response.status_code = 200
|
|
nic2_response.json.return_value = {
|
|
"Id": "NIC.Integrated.1-2",
|
|
"Name": "Ethernet Interface 2",
|
|
"MACAddress": "AA:BB:CC:DD:EE:02",
|
|
"SpeedMbps": 1000,
|
|
"Status": {"State": "Enabled", "Health": "OK"},
|
|
"IPv4Addresses": [],
|
|
"IPv6Addresses": [],
|
|
"LinkStatus": "LinkDown",
|
|
"AutoNeg": True,
|
|
}
|
|
|
|
plugin._session.get.side_effect = [
|
|
proc_collection, nic_collection, nic1_response, nic2_response
|
|
]
|
|
|
|
result = plugin.discover_resources(
|
|
endpoints=["10.0.0.1"],
|
|
resource_types=["bare_metal_network_interface"],
|
|
progress_callback=lambda p: None,
|
|
)
|
|
|
|
assert len(result.resources) == 2
|
|
assert result.resources[0].attributes["mac_address"] == "AA:BB:CC:DD:EE:01"
|
|
assert result.resources[0].attributes["speed_mbps"] == 10000
|
|
assert result.resources[1].attributes["mac_address"] == "AA:BB:CC:DD:EE:02"
|
|
|
|
def test_discover_raid_config(self):
|
|
"""Discovers RAID configuration from Redfish Storage."""
|
|
plugin = self._make_authenticated_plugin()
|
|
|
|
# Mock processor detection
|
|
proc_collection = MagicMock()
|
|
proc_collection.status_code = 200
|
|
proc_collection.json.return_value = {"Members": []}
|
|
|
|
# Mock storage collection
|
|
storage_collection = MagicMock()
|
|
storage_collection.status_code = 200
|
|
storage_collection.json.return_value = {
|
|
"Members": [
|
|
{"@odata.id": "/redfish/v1/Systems/1/Storage/RAID.Integrated.1-1"}
|
|
]
|
|
}
|
|
|
|
# Mock storage controller detail
|
|
storage_detail = MagicMock()
|
|
storage_detail.status_code = 200
|
|
storage_detail.json.return_value = {
|
|
"Id": "RAID.Integrated.1-1",
|
|
"Name": "PERC H740P Mini",
|
|
"StorageControllers": [{"Name": "PERC H740P Mini"}],
|
|
"Drives": [
|
|
{"@odata.id": "/redfish/v1/Systems/1/Storage/Drives/Disk.0"},
|
|
{"@odata.id": "/redfish/v1/Systems/1/Storage/Drives/Disk.1"},
|
|
],
|
|
"Volumes": {
|
|
"@odata.id": "/redfish/v1/Systems/1/Storage/RAID.Integrated.1-1/Volumes"
|
|
},
|
|
"Status": {"State": "Enabled", "Health": "OK"},
|
|
}
|
|
|
|
# Mock volumes collection
|
|
volumes_response = MagicMock()
|
|
volumes_response.status_code = 200
|
|
volumes_response.json.return_value = {
|
|
"Members": [
|
|
{"@odata.id": "/redfish/v1/Systems/1/Storage/Volumes/Disk.Virtual.0"}
|
|
]
|
|
}
|
|
|
|
plugin._session.get.side_effect = [
|
|
proc_collection, storage_collection, storage_detail, volumes_response
|
|
]
|
|
|
|
result = plugin.discover_resources(
|
|
endpoints=["10.0.0.1"],
|
|
resource_types=["bare_metal_raid_config"],
|
|
progress_callback=lambda p: None,
|
|
)
|
|
|
|
assert len(result.resources) == 1
|
|
resource = result.resources[0]
|
|
assert resource.resource_type == "bare_metal_raid_config"
|
|
assert resource.attributes["drive_count"] == 2
|
|
assert len(resource.attributes["volumes"]) == 1
|
|
assert resource.attributes["storage_controllers"] == ["PERC H740P Mini"]
|
|
|
|
def test_discover_multiple_resource_types(self):
|
|
"""Discovers multiple resource types in a single call."""
|
|
plugin = self._make_authenticated_plugin()
|
|
|
|
# Mock processor detection
|
|
proc_collection = MagicMock()
|
|
proc_collection.status_code = 200
|
|
proc_collection.json.return_value = {"Members": []}
|
|
|
|
# Mock system response
|
|
system_response = MagicMock()
|
|
system_response.status_code = 200
|
|
system_response.json.return_value = {
|
|
"Id": "System.1",
|
|
"Name": "Server",
|
|
"Manufacturer": "Dell",
|
|
"Model": "R740",
|
|
}
|
|
|
|
# Mock manager response
|
|
manager_response = MagicMock()
|
|
manager_response.status_code = 200
|
|
manager_response.json.return_value = {
|
|
"Id": "BMC.1",
|
|
"Name": "BMC",
|
|
"ManagerType": "BMC",
|
|
"FirmwareVersion": "1.0",
|
|
}
|
|
|
|
plugin._session.get.side_effect = [
|
|
proc_collection, system_response, manager_response
|
|
]
|
|
|
|
progress_calls = []
|
|
result = plugin.discover_resources(
|
|
endpoints=["10.0.0.1"],
|
|
resource_types=["bare_metal_hardware", "bare_metal_bmc_config"],
|
|
progress_callback=lambda p: progress_calls.append(p),
|
|
)
|
|
|
|
assert len(result.resources) == 2
|
|
assert len(progress_calls) == 2
|
|
assert progress_calls[0].resource_types_completed == 1
|
|
assert progress_calls[1].resource_types_completed == 2
|
|
|
|
def test_discover_handles_errors_gracefully(self):
|
|
"""Errors during discovery are captured, not raised."""
|
|
plugin = self._make_authenticated_plugin()
|
|
|
|
# Mock processor detection that raises - this causes detect_architecture
|
|
# to fail, which is caught internally. Then the resource handler also
|
|
# needs to raise to trigger the error capture in discover_resources.
|
|
plugin._session.get.side_effect = Exception("Server unreachable")
|
|
|
|
# Patch _discover_resource_type to raise so the outer handler catches it
|
|
with patch.object(
|
|
plugin, "_discover_resource_type", side_effect=Exception("Server unreachable")
|
|
):
|
|
result = plugin.discover_resources(
|
|
endpoints=["10.0.0.1"],
|
|
resource_types=["bare_metal_hardware"],
|
|
progress_callback=lambda p: None,
|
|
)
|
|
|
|
assert len(result.resources) == 0
|
|
assert len(result.errors) == 1
|
|
assert "Server unreachable" in result.errors[0]
|
|
|
|
def test_discover_unsupported_resource_type(self):
|
|
"""Unsupported resource types return empty results without error."""
|
|
plugin = self._make_authenticated_plugin()
|
|
|
|
# Mock processor detection
|
|
proc_collection = MagicMock()
|
|
proc_collection.status_code = 200
|
|
proc_collection.json.return_value = {"Members": []}
|
|
|
|
plugin._session.get.side_effect = [proc_collection]
|
|
|
|
result = plugin.discover_resources(
|
|
endpoints=["10.0.0.1"],
|
|
resource_types=["unknown_type"],
|
|
progress_callback=lambda p: None,
|
|
)
|
|
|
|
assert len(result.resources) == 0
|
|
assert len(result.errors) == 0
|
|
|
|
|
|
class TestParseArchitecture:
|
|
"""Tests for the _parse_architecture static method."""
|
|
|
|
def test_x86_64_instruction_set(self):
|
|
assert BareMetalPlugin._parse_architecture(
|
|
{"InstructionSet": "x86-64", "Model": "Intel Xeon"}
|
|
) == CpuArchitecture.AMD64
|
|
|
|
def test_aarch64_instruction_set(self):
|
|
assert BareMetalPlugin._parse_architecture(
|
|
{"InstructionSet": "AArch64", "Model": "Ampere"}
|
|
) == CpuArchitecture.AARCH64
|
|
|
|
def test_arm_instruction_set(self):
|
|
assert BareMetalPlugin._parse_architecture(
|
|
{"InstructionSet": "ARM", "Model": "Cortex"}
|
|
) == CpuArchitecture.AARCH64
|
|
|
|
def test_arm_model_32bit(self):
|
|
assert BareMetalPlugin._parse_architecture(
|
|
{"InstructionSet": "", "Model": "ARM Cortex-A7"}
|
|
) == CpuArchitecture.ARM
|
|
|
|
def test_arm_model_64bit(self):
|
|
assert BareMetalPlugin._parse_architecture(
|
|
{"InstructionSet": "", "Model": "ARM v8 Processor"}
|
|
) == CpuArchitecture.AARCH64
|
|
|
|
def test_empty_data_defaults_amd64(self):
|
|
assert BareMetalPlugin._parse_architecture(
|
|
{"InstructionSet": "", "Model": ""}
|
|
) == CpuArchitecture.AMD64
|