Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 97 additions & 84 deletions tests/bios/test_dell_bios_device_recovery_dualmode.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Dual-mode-style coverage for DellBIOSService.DeviceRecovery."""
from __future__ import annotations

import json
from contextlib import contextmanager
import copy
from pathlib import Path

import pytest
from conftest import MockRedfishService, _build_fixture_index
from vendor_corpus import corpus_dir

from redfish_ctl.idrac_manager import IDracManager
Expand All @@ -16,68 +16,73 @@
Path(__file__).parent.parent / "dell_xr8620t_corpus.tar.gz",
"10.252.252.209",
)
DELL_INDEX = {path.name.lower(): path for path in DELL_CORPUS.glob("*.json")}

SYSTEM = "/redfish/v1/Systems/System.Embedded.1"
BIOS_SERVICE = f"{SYSTEM}/Oem/Dell/DellBIOSService"
ACTION = "#DellBIOSService.DeviceRecovery"
TARGET = f"{BIOS_SERVICE}/Actions/DellBIOSService.DeviceRecovery"
TASK_ID = "JID_000000000001"


def _fixture_for_path(path):
name = "_" + path.strip("/").replace("/", "_") + ".json"
return DELL_INDEX.get(name.lower())
@pytest.fixture
def dell_bios_mock():
"""Return a manager and mock service backed by the Dell XR8620t corpus.

The vendor-faithful service realizes an Action POST the Dell way: 202 plus
a ``JID_`` OEM job id in the Location header, never a DMTF-generic token.

def _post_requests(requests):
return [request for request in requests if request.method == "POST"]


@contextmanager
def _mock_dell_corpus(remove_action=False):
:return: tuple of IDracManager and the recording MockRedfishService.
"""
requests_mock = pytest.importorskip("requests_mock")
requests = []

def get_cb(request, context):
requests.append(request)
fixture = _fixture_for_path(request.path)
if fixture is None:
context.status_code = 404
return json.dumps({"error": f"no fixture for {request.path}"})
body = json.loads(fixture.read_text())
if remove_action and request.path.lower() == BIOS_SERVICE.lower():
body["Actions"].pop(ACTION, None)
context.status_code = 200
return json.dumps(body)

def post_cb(request, context):
requests.append(request)
context.status_code = 202
context.headers["Location"] = f"/redfish/v1/TaskService/Tasks/{TASK_ID}"
return ""

service = MockRedfishService(
DELL_CORPUS,
index=_build_fixture_index(DELL_CORPUS),
)
with requests_mock.Mocker() as mocker:
mocker.get(requests_mock.ANY, text=get_cb)
mocker.post(requests_mock.ANY, text=post_cb)
manager = IDracManager(
idrac_ip="mock-dell",
idrac_username="root",
idrac_password="mock",
insecure=True,
is_debug=False,
mocker.get(requests_mock.ANY, text=service.get_cb)
mocker.patch(requests_mock.ANY, text=service.patch_cb)
mocker.post(requests_mock.ANY, text=service.post_cb)
mocker.delete(requests_mock.ANY, text=service.delete_cb)
service.mocker = mocker
yield (
IDracManager(
idrac_ip="mock-dell",
idrac_username="root",
idrac_password="mock",
insecure=True,
is_debug=False,
),
service,
)
yield manager, requests


def test_dell_bios_device_recovery_lists_corpus_target():
def _post_requests(service):
"""Return POST requests recorded by the mock Redfish service.

:param service: the recording MockRedfishService.
:return: list of POST requests.
"""
return [request for request in service.requests if request.method == "POST"]


def _overlay_bios_service(service, body):
"""Overlay DellBIOSService under both common request casings.

:param service: the recording MockRedfishService.
:param body: replacement BIOS-service body.
"""
service._overlay[BIOS_SERVICE] = body
service._overlay[BIOS_SERVICE.lower()] = body


def test_dell_bios_device_recovery_lists_corpus_target(dell_bios_mock):
"""The command lists the DellBIOSService target advertised by the corpus."""
with _mock_dell_corpus() as (manager, requests):
result = manager.sync_invoke(
ApiRequestType.DellBiosDeviceRecovery,
"dell-bios-device-recovery",
list_only=True,
)
manager, service = dell_bios_mock

result = manager.sync_invoke(
ApiRequestType.DellBiosDeviceRecovery,
"dell-bios-device-recovery",
list_only=True,
)

assert isinstance(result, CommandResult)
assert result.error is None
Expand All @@ -90,16 +95,17 @@ def test_dell_bios_device_recovery_lists_corpus_target():
"devices": ["BIOS"],
}
]
assert _post_requests(requests) == []
assert _post_requests(service) == []


def test_dell_bios_device_recovery_previews_by_default():
def test_dell_bios_device_recovery_previews_by_default(dell_bios_mock):
"""DeviceRecovery defaults to a destructive-action dry-run."""
with _mock_dell_corpus() as (manager, requests):
result = manager.sync_invoke(
ApiRequestType.DellBiosDeviceRecovery,
"dell-bios-device-recovery",
)
manager, service = dell_bios_mock

result = manager.sync_invoke(
ApiRequestType.DellBiosDeviceRecovery,
"dell-bios-device-recovery",
)

assert result.error is None
assert result.data == {
Expand All @@ -113,22 +119,24 @@ def test_dell_bios_device_recovery_previews_by_default():
"bios_service": BIOS_SERVICE,
"device": "BIOS",
}
assert _post_requests(requests) == []
assert _post_requests(service) == []


def test_dell_bios_device_recovery_confirm_posts_device_payload():
"""With --confirm the command POSTs the advertised DeviceRecovery payload."""
with _mock_dell_corpus() as (manager, requests):
result = manager.sync_invoke(
ApiRequestType.DellBiosDeviceRecovery,
"dell-bios-device-recovery",
confirm=True,
)
def test_dell_bios_device_recovery_confirm_posts_device_payload(dell_bios_mock):
"""--confirm POSTs DeviceRecovery; the Dell lens realizes a ``JID_`` job id."""
manager, service = dell_bios_mock

result = manager.sync_invoke(
ApiRequestType.DellBiosDeviceRecovery,
"dell-bios-device-recovery",
confirm=True,
)

posts = _post_requests(requests)
posts = _post_requests(service)
assert result.error is None
assert result.data["executed"] is True
assert result.data["task_id"] == TASK_ID
assert result.data["task_id"] == service.JOB_ID
assert service.JOB_ID.startswith("JID_")
assert result.data["action"] == ACTION
assert result.data["target"] == TARGET
assert result.data["level"] == "destructive"
Expand All @@ -137,15 +145,16 @@ def test_dell_bios_device_recovery_confirm_posts_device_payload():
assert posts[0].json() == {"Device": "BIOS"}


def test_dell_bios_device_recovery_rejects_unadvertised_device():
def test_dell_bios_device_recovery_rejects_unadvertised_device(dell_bios_mock):
"""Payload validation rejects Device values outside the service metadata."""
with _mock_dell_corpus() as (manager, requests):
result = manager.sync_invoke(
ApiRequestType.DellBiosDeviceRecovery,
"dell-bios-device-recovery",
device="BMC",
confirm=True,
)
manager, service = dell_bios_mock

result = manager.sync_invoke(
ApiRequestType.DellBiosDeviceRecovery,
"dell-bios-device-recovery",
device="BMC",
confirm=True,
)

assert result.error == (
"invalid value for DellBIOSService.DeviceRecovery Device: BMC; "
Expand All @@ -154,22 +163,26 @@ def test_dell_bios_device_recovery_rejects_unadvertised_device():
assert result.data["action"] == ACTION
assert result.data["target"] == TARGET
assert result.data["payload"] == {"Device": "BMC"}
assert _post_requests(requests) == []
assert _post_requests(service) == []


def test_dell_bios_device_recovery_missing_action_reports_available():
def test_dell_bios_device_recovery_missing_action_reports_available(dell_bios_mock):
"""A service without DeviceRecovery reports the missing action and never POSTs."""
with _mock_dell_corpus(remove_action=True) as (manager, requests):
result = manager.sync_invoke(
ApiRequestType.DellBiosDeviceRecovery,
"dell-bios-device-recovery",
confirm=True,
)
manager, service = dell_bios_mock
body = copy.deepcopy(service._state(BIOS_SERVICE))
body["Actions"].pop(ACTION, None)
_overlay_bios_service(service, body)

result = manager.sync_invoke(
ApiRequestType.DellBiosDeviceRecovery,
"dell-bios-device-recovery",
confirm=True,
)

assert result.error == (
f"action '{ACTION}' not found on DellBIOSService"
)
assert result.data["action"] == ACTION
assert result.data["available"] == []
assert result.data["attempted"] == [BIOS_SERVICE]
assert _post_requests(requests) == []
assert _post_requests(service) == []
Loading