# Copyright 2017 Red Hat, Inc.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import json
from unittest import mock

from dateutil import parser

import sushy
from sushy import exceptions
from sushy.resources.chassis import chassis
from sushy.resources import constants as res_cons
from sushy.resources.manager import manager
from sushy.resources.manager import virtual_media
from sushy.resources.oem import fake
from sushy.resources.system import bios
from sushy.resources.system import processor
from sushy.resources.system import secure_boot
from sushy.resources.system import simple_storage
from sushy.resources.system import system
from sushy.tests.unit import base


class SystemTestCase(base.TestCase):

    def setUp(self):
        super().setUp()
        self.conn = mock.Mock()
        self.conn.get.return_value.headers = {'Allow': 'GET,HEAD'}
        with open('sushy/tests/unit/json_samples/system.json') as f:
            self.json_doc = json.load(f)

        self.conn.get.return_value.json.return_value = self.json_doc

        self.sys_inst = system.System(
            self.conn, '/redfish/v1/Systems/437XR1138R2',
            redfish_version='1.0.2')
        self.sys_inst._get_etag = mock.Mock()
        self.sys_inst._get_etag.return_value = '81802dbf61beb0bd'

    def test__parse_attributes(self):
        self.sys_inst._parse_attributes(self.json_doc)
        self.assertEqual('1.0.2', self.sys_inst.redfish_version)
        self.assertEqual('Chicago-45Z-2381', self.sys_inst.asset_tag)
        self.assertEqual('P79 v1.45 (12/06/2017)', self.sys_inst.bios_version)
        self.assertEqual('Web Front End node', self.sys_inst.description)
        self.assertEqual('web483', self.sys_inst.hostname)
        self.assertEqual('437XR1138R2', self.sys_inst.identity)
        self.assertEqual(sushy.IndicatorLED.OFF,
                         self.sys_inst.indicator_led)
        self.assertEqual('Contoso', self.sys_inst.manufacturer)
        self.assertEqual('WebFrontEnd483', self.sys_inst.name)
        self.assertEqual('224071-J23', self.sys_inst.part_number)
        self.assertEqual('437XR1138R2', self.sys_inst.serial_number)
        self.assertEqual('8675309', self.sys_inst.sku)
        self.assertEqual(sushy.SystemType.PHYSICAL, self.sys_inst.system_type)
        self.assertEqual('38947555-7742-3448-3784-823347823834',
                         self.sys_inst.uuid)
        self.assertEqual(res_cons.State.ENABLED, self.sys_inst.status.state)
        self.assertEqual(res_cons.Health.OK, self.sys_inst.status.health)
        self.assertEqual(res_cons.Health.OK,
                         self.sys_inst.status.health_rollup)
        self.assertEqual(sushy.PowerState.ON, self.sys_inst.power_state)
        self.assertEqual(96, self.sys_inst.memory_summary.size_gib)
        self.assertEqual("OK", self.sys_inst.memory_summary.health)
        self.assertIsNotNone(self.sys_inst.maintenance_window)
        self.assertEqual(1, self.sys_inst.maintenance_window
                         .maintenance_window_duration_in_seconds)
        self.assertEqual(parser.parse('2016-03-07T14:44:30-05:05'),
                         self.sys_inst.maintenance_window
                         .maintenance_window_start_time)
        for oem_vendor in self.sys_inst.oem_vendors:
            self.assertIn(oem_vendor, ('Contoso', 'Chipwise'))
        self.assertEqual(sushy.BootProgressStates.OS_RUNNING,
                         self.sys_inst.boot_progress.last_state)
        self.assertEqual(66,
                         self.sys_inst.boot_progress.last_boot_seconds_count)
        self.assertEqual(parser.parse('2017-05-03T23:12:37-05:00'),
                         self.sys_inst.boot_progress.last_state_updated_at)
        self.assertEqual("OS foo running.",
                         self.sys_inst.boot_progress.oem_last_state)

    def test__parse_attributes_return(self):
        attributes = self.sys_inst._parse_attributes(self.json_doc)

        # Test that various types are returned correctly
        self.assertEqual('Chicago-45Z-2381', attributes.get('asset_tag'))
        self.assertEqual(sushy.IndicatorLED.OFF,
                         attributes.get('indicator_led'))
        self.assertEqual({'health': res_cons.Health.OK,
                          'health_rollup': res_cons.Health.OK,
                          'state': res_cons.State.ENABLED},
                         attributes.get('status'))
        self.assertEqual({'maintenance_window_duration_in_seconds': 1,
                          'maintenance_window_start_time':
                          parser.parse('2016-03-07T14:44:30-05:05')},
                         attributes.get('maintenance_window'))
        self.assertEqual(
            {'reset': {'allowed_values':
                       ['On', 'ForceOff', 'GracefulShutdown',
                        'GracefulRestart', 'ForceRestart', 'Nmi',
                        'ForceOn', 'PushPowerButton'],
                       'operation_apply_time_support':
                       {'_maintenance_window_resource':
                        {'resource_uri':
                         '/redfish/v1/Systems/437XR1138R2'},
                        'maintenance_window_duration_in_seconds': 600,
                        'maintenance_window_start_time':
                        parser.parse('2017-05-03T23:12:37-05:00'),
                        'supported_values':
                        ['Immediate', 'AtMaintenanceWindowStart'],
                        'mapped_supported_values':
                        [res_cons.ApplyTime.IMMEDIATE,
                         res_cons.ApplyTime.AT_MAINTENANCE_WINDOW_START]},
                       'target_uri':
                       '/redfish/v1/Systems/437XR1138R2/Actions/'
                       'ComputerSystem.Reset'}},
            attributes.get('_actions'))

    def test__parse_attributes_missing_actions(self):
        self.sys_inst.json.pop('Actions')
        self.assertRaisesRegex(
            exceptions.MissingAttributeError, 'attribute Actions',
            self.sys_inst._parse_attributes, self.json_doc)

    def test__parse_attributes_missing_boot(self):
        self.sys_inst.json.pop('Boot')
        self.assertRaisesRegex(
            exceptions.MissingAttributeError, 'attribute Boot',
            self.sys_inst._parse_attributes, self.json_doc)

    def test__parse_attributes_missing_reset_target(self):
        self.sys_inst.json['Actions']['#ComputerSystem.Reset'].pop(
            'target')
        self.assertRaisesRegex(
            exceptions.MissingAttributeError,
            'attribute Actions/#ComputerSystem.Reset/target',
            self.sys_inst._parse_attributes, self.json_doc)

    def test__parse_attributes_null_memory_capacity(self):
        self.sys_inst.json['MemorySummary']['TotalSystemMemoryGiB'] = None
        self.sys_inst._parse_attributes(self.json_doc)
        self.assertIsNone(self.sys_inst.memory_summary.size_gib)

    def test__parse_attributes_bad_maintenance_window_time(self):
        self.sys_inst.json['@Redfish.MaintenanceWindow'][
            'MaintenanceWindowStartTime'] = 'bad date'
        self.assertRaisesRegex(
            exceptions.MalformedAttributeError,
            '@Redfish.MaintenanceWindow/MaintenanceWindowStartTime',
            self.sys_inst._parse_attributes, self.json_doc)

    def test_get__reset_action_element(self):
        value = self.sys_inst._get_reset_action_element()
        self.assertEqual("/redfish/v1/Systems/437XR1138R2/Actions/"
                         "ComputerSystem.Reset",
                         value.target_uri)
        self.assertEqual(["On",
                          "ForceOff",
                          "GracefulShutdown",
                          "GracefulRestart",
                          "ForceRestart",
                          "Nmi",
                          "ForceOn",
                          "PushPowerButton"
                          ],
                         value.allowed_values)

    def test_get__reset_action_element_missing_reset_action(self):
        self.sys_inst._actions.reset = None
        self.assertRaisesRegex(
            exceptions.MissingActionError, 'action #ComputerSystem.Reset',
            self.sys_inst._get_reset_action_element)

    def test_get_allowed_reset_system_values(self):
        values = self.sys_inst.get_allowed_reset_system_values()
        expected = set([sushy.ResetType.GRACEFUL_SHUTDOWN,
                        sushy.ResetType.GRACEFUL_RESTART,
                        sushy.ResetType.FORCE_RESTART,
                        sushy.ResetType.FORCE_OFF,
                        sushy.ResetType.FORCE_ON,
                        sushy.ResetType.ON,
                        sushy.ResetType.NMI,
                        sushy.ResetType.PUSH_POWER_BUTTON])
        self.assertEqual(expected, values)
        self.assertIsInstance(values, set)

    @mock.patch.object(system.LOG, 'warning', autospec=True)
    def test_get_allowed_reset_system_values_no_values_specified(
            self, mock_log):
        self.sys_inst._actions.reset.allowed_values = {}
        values = self.sys_inst.get_allowed_reset_system_values()
        # Assert it returns all values if it can't get the specific ones
        expected = set([sushy.ResetType.GRACEFUL_SHUTDOWN,
                        sushy.ResetType.GRACEFUL_RESTART,
                        sushy.ResetType.FORCE_RESTART,
                        sushy.ResetType.FORCE_OFF,
                        sushy.ResetType.FORCE_ON,
                        sushy.ResetType.ON,
                        sushy.ResetType.NMI,
                        sushy.ResetType.PUSH_POWER_BUTTON,
                        sushy.ResetType.POWER_CYCLE,
                        sushy.ResetType.SUSPEND,
                        sushy.ResetType.RESUME,
                        sushy.ResetType.PAUSE])
        self.assertEqual(expected, values)
        self.assertIsInstance(values, set)
        self.assertEqual(1, mock_log.call_count)

    def test_reset_action_operation_apply_time_support(self):
        support = self.sys_inst._actions.reset.operation_apply_time_support
        self.assertIsNotNone(support)
        self.assertEqual(['Immediate', 'AtMaintenanceWindowStart'],
                         support.supported_values)
        self.assertEqual([res_cons.ApplyTime.IMMEDIATE,
                          res_cons.ApplyTime.AT_MAINTENANCE_WINDOW_START],
                         support.mapped_supported_values)
        self.assertEqual(parser.parse('2017-05-03T23:12:37-05:00'),
                         support.maintenance_window_start_time)
        self.assertEqual(600, support.maintenance_window_duration_in_seconds)
        self.assertEqual('/redfish/v1/Systems/437XR1138R2',
                         support._maintenance_window_resource.resource_uri)

    def test_reset_system(self):
        self.sys_inst.reset_system(sushy.ResetType.FORCE_OFF)
        self.sys_inst._conn.post.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2/Actions/ComputerSystem.Reset',
            data={'ResetType': 'ForceOff'})

    def test_reset_system_invalid_value(self):
        self.assertRaises(exceptions.InvalidParameterValueError,
                          self.sys_inst.reset_system, 'invalid-value')

    def test_get_allowed_system_boot_source_values(self):
        values = self.sys_inst.get_allowed_system_boot_source_values()
        expected = set([sushy.BootSource.NONE,
                        sushy.BootSource.PXE,
                        sushy.BootSource.CD,
                        sushy.BootSource.USB,
                        sushy.BootSource.HDD,
                        sushy.BootSource.BIOS_SETUP,
                        sushy.BootSource.UTILITIES,
                        sushy.BootSource.DIAGS,
                        sushy.BootSource.SD_CARD,
                        sushy.BootSource.UEFI_TARGET,
                        sushy.BootSource.UEFI_HTTP])
        self.assertEqual(expected, values)
        self.assertIsInstance(values, set)

    @mock.patch.object(system.LOG, 'warning', autospec=True)
    def test_get_allowed_system_boot_source_values_no_values_specified(
            self, mock_log):
        self.sys_inst.boot.allowed_values = None
        values = self.sys_inst.get_allowed_system_boot_source_values()
        # Assert it returns all values if it can't get the specific ones
        expected = set([sushy.BootSource.NONE,
                        sushy.BootSource.PXE,
                        sushy.BootSource.CD,
                        sushy.BootSource.USB,
                        sushy.BootSource.HDD,
                        sushy.BootSource.BIOS_SETUP,
                        sushy.BootSource.UTILITIES,
                        sushy.BootSource.DIAGS,
                        sushy.BootSource.SD_CARD,
                        sushy.BootSource.FLOPPY,
                        sushy.BootSource.UEFI_TARGET,
                        sushy.BootSource.UEFI_SHELL,
                        sushy.BootSource.UEFI_HTTP,
                        sushy.BootSource.REMOTE_DRIVE,
                        sushy.BootSource.UEFI_BOOT_NEXT,
                        sushy.BootSource.USB_CD])
        self.assertEqual(expected, values)
        self.assertIsInstance(values, set)
        self.assertEqual(1, mock_log.call_count)

    def test_set_system_boot_options(self):
        self.sys_inst.set_system_boot_options(
            sushy.BootSource.PXE,
            enabled=sushy.BootSourceOverrideEnabled.CONTINUOUS,
            mode=sushy.BootSourceOverrideMode.UEFI)
        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideEnabled': 'Continuous',
                           'BootSourceOverrideTarget': 'Pxe',
                           'BootSourceOverrideMode': 'UEFI'}},
            etag='81802dbf61beb0bd')

    def test_set_system_boot_options_no_mode_specified(self):
        self.sys_inst.set_system_boot_options(
            sushy.BootSource.HDD,
            enabled=sushy.BootSourceOverrideEnabled.ONCE)
        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideEnabled': 'Once',
                           'BootSourceOverrideTarget': 'Hdd'}},
            etag='81802dbf61beb0bd')

    def test_set_system_boot_options_no_target_specified(self):
        self.sys_inst.set_system_boot_options(
            enabled=sushy.BootSourceOverrideEnabled.CONTINUOUS,
            mode=sushy.BootSourceOverrideMode.UEFI)
        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideEnabled': 'Continuous',
                           'BootSourceOverrideMode': 'UEFI'}},
            etag='81802dbf61beb0bd')

    def test_set_system_boot_options_no_freq_specified(self):
        self.sys_inst.set_system_boot_options(
            target=sushy.BootSource.PXE,
            mode=sushy.BootSourceOverrideMode.UEFI)
        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideTarget': 'Pxe',
                           'BootSourceOverrideMode': 'UEFI'}},
            etag='81802dbf61beb0bd')

    def test_set_system_boot_options_nothing_specified(self):
        self.sys_inst.set_system_boot_options()
        self.sys_inst._conn.patch.assert_not_called()

    def test_set_system_boot_options_invalid_target(self):
        self.assertRaises(exceptions.InvalidParameterValueError,
                          self.sys_inst.set_system_boot_source,
                          'invalid-target')

    def test_set_system_boot_options_invalid_enabled(self):
        with self.assertRaisesRegex(
            exceptions.InvalidParameterValueError,
            f'"enabled" value.*{list(sushy.BootSourceOverrideEnabled)}'):

            self.sys_inst.set_system_boot_options(
                sushy.BootSource.HDD,
                enabled='invalid-enabled')

    def test_set_system_boot_options_supermicro_usb_cd_boot(self):
        (self.json_doc["Boot"]
         ["BootSourceOverrideTarget@Redfish.AllowableValues"]).append("UsbCd")
        self.sys_inst._parse_attributes(self.json_doc)

        self.sys_inst.manufacturer = "supermicro"
        self.sys_inst.set_system_boot_options(
            target=sushy.BootSource.CD,
            enabled=sushy.BootSourceOverrideEnabled.ONCE)

        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideEnabled': 'Once',
                           'BootSourceOverrideTarget': 'UsbCd'}},
            etag='81802dbf61beb0bd')

    def test_set_system_boot_options_supermicro_usb_cd_boot_ars111glnhr(self):
        (self.json_doc["Boot"]
         ["BootSourceOverrideTarget@Redfish.AllowableValues"]).append("UsbCd")
        self.sys_inst._parse_attributes(self.json_doc)

        self.sys_inst.manufacturer = "supermicro"
        self.sys_inst.model = "ARS-111GL-NHR"
        self.sys_inst.set_system_boot_options(
            target=sushy.BootSource.CD,
            enabled=sushy.BootSourceOverrideEnabled.ONCE)

        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideEnabled': 'Once',
                           'BootSourceOverrideTarget': 'Cd'}},
            etag='81802dbf61beb0bd')

    def test_set_system_boot_options_supermicro_no_usb_cd_boot(self):

        self.sys_inst.manufacturer = "supermicro"
        self.sys_inst.set_system_boot_options(
            target=sushy.BootSource.CD,
            enabled=sushy.BootSourceOverrideEnabled.ONCE)

        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideEnabled': 'Once',
                           'BootSourceOverrideTarget': 'Cd'}},
            etag='81802dbf61beb0bd')

    def test_set_system_boot_options_settings_resource_nokia(self):
        with open('sushy/tests/unit/json_samples/settings-nokia.json') as f:
            settings_obj = json.load(f)

        self.json_doc.update(settings_obj)
        self.sys_inst._parse_attributes(self.json_doc)

        with open('sushy/tests/unit/json_samples/'
                  'settings-body-nokia.json') as f:
            settings_body = json.load(f)

        get_settings = mock.MagicMock(headers={'ETag': '"3d7b838291941d"'})
        get_settings.json.return_value = settings_body
        get_system = mock.MagicMock(headers={'ETag': '"222"'})
        self.conn.get.side_effect = [get_settings, get_system]

        self.sys_inst.set_system_boot_options(
            target=sushy.BootSource.CD,
            enabled=sushy.BootSourceOverrideEnabled.ONCE)

        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/Self/SD',
            data={'Boot': {'BootSourceOverrideEnabled': 'Once',
                           'BootSourceOverrideTarget': 'Cd'}},
            etag='"3d7b838291941d"')

    def test_set_system_boot_options_settings_resource(self):
        with open('sushy/tests/unit/json_samples/settings.json') as f:
            settings_obj = json.load(f)

        self.json_doc.update(settings_obj)
        self.sys_inst._parse_attributes(self.json_doc)

        with open('sushy/tests/unit/json_samples/'
                  'settings-body-nokia.json') as f:
            settings_body = json.load(f)

        get_settings = mock.MagicMock(headers={'ETag': '"3d7b838291941d"'})
        get_settings.json.return_value = settings_body
        get_system = mock.MagicMock(headers={'ETag': '"222"'})
        self.conn.get.side_effect = [get_settings, get_system]

        self.conn.get.return_value.json.return_value = settings_body

        self.sys_inst.set_system_boot_options(
            target=sushy.BootSource.CD,
            enabled=sushy.BootSourceOverrideEnabled.ONCE)

        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2/BIOS/Settings',
            data={'Boot': {'BootSourceOverrideEnabled': 'Once',
                           'BootSourceOverrideTarget': 'Cd'}},
            etag='"3d7b838291941d"')

    def test_set_system_boot_options_settings_resource_lenovo(self):
        self.sys_inst = system.System(
            self.conn, '/redfish/v1/Systems/1',
            redfish_version='1.0.2')

        with open('sushy/tests/unit/json_samples/'
                  'settings-lenovo-se450.json') as f:
            settings_obj = json.load(f)

        self.json_doc.update(settings_obj)
        self.sys_inst._parse_attributes(self.json_doc)

        with open('sushy/tests/unit/json_samples/'
                  'settings-body-lenovo-se450.json') as f:
            settings_body = json.load(f)

        get_settings = mock.MagicMock(headers={'ETag': '"3d7b838291941d"'})
        get_settings.json.return_value = settings_body
        get_system = mock.MagicMock(headers={'ETag': '"222"'})
        self.conn.get.side_effect = [get_settings, get_system]

        self.sys_inst.set_system_boot_options(
            target=sushy.BootSource.CD,
            enabled=sushy.BootSourceOverrideEnabled.ONCE)

        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/1',
            data={'Boot': {'BootSourceOverrideEnabled': 'Once',
                           'BootSourceOverrideTarget': 'Cd'}},
            etag='"222"')

    def test_set_system_boot_options_settings_resource_bootmode_only(self):
        self.sys_inst = system.System(
            self.conn, '/redfish/v1/Systems/1',
            redfish_version='1.0.2')

        with open('sushy/tests/unit/json_samples/'
                  'settings-nokia.json') as f:
            settings_obj = json.load(f)

        self.json_doc.update(settings_obj)
        self.sys_inst._parse_attributes(self.json_doc)

        with open('sushy/tests/unit/json_samples/'
                  'settings-body-bootsourceoverridemode-only.json') as f:
            settings_body = json.load(f)

        get_settings = mock.MagicMock(headers={'ETag': '"3d7b838291941d"'})
        get_settings.json.return_value = settings_body
        get_system = mock.MagicMock(headers={'ETag': '"222"'})
        self.conn.get.side_effect = [get_settings, get_system]

        self.sys_inst.set_system_boot_options(
            mode=sushy.BootSourceOverrideMode.UEFI)

        self.sys_inst._conn.patch.assert_called_with(
            '/redfish/v1/Systems/Self/SD',
            data={'Boot': {'BootSourceOverrideMode': 'UEFI'}},
            etag='"3d7b838291941d"')

    def test_set_system_boot_options_httpbooturi(self):
        self.sys_inst.set_system_boot_options(
            sushy.BootSource.UEFI_HTTP,
            enabled=sushy.BootSourceOverrideEnabled.ONCE,
            mode=sushy.BootSourceOverrideMode.UEFI,
            http_boot_uri='http://test.lan/test_image.iso'
            )
        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideTarget': 'UefiHttp',
                           'BootSourceOverrideEnabled': 'Once',
                           'BootSourceOverrideMode': 'UEFI',
                           'HttpBootUri': 'http://test.lan/test_image.iso'}},
            etag=mock.ANY)

    def test_set_system_boot_options_httpboot_settings(self):

        with open('sushy/tests/unit/json_samples/settings-nokia.json') as f:
            settings_obj = json.load(f)

        self.json_doc.update(settings_obj)
        self.sys_inst._parse_attributes(self.json_doc)

        with open('sushy/tests/unit/json_samples/'
                  'settings-body-nokia.json') as f:
            settings_body = json.load(f)

        get_settings = mock.MagicMock(headers={'ETag': '"3d7b838291941d"'})
        get_settings.json.return_value = settings_body
        get_system = mock.MagicMock(headers={'ETag': '"222"'})
        self.conn.get.side_effect = [get_settings, get_system]

        self.sys_inst.set_system_boot_options(
            sushy.BootSource.UEFI_HTTP,
            enabled=sushy.BootSourceOverrideEnabled.ONCE,
            http_boot_uri='http://test.lan/test_image.iso'
            )
        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/Self/SD',
            data={'Boot': {'BootSourceOverrideTarget': 'UefiHttp',
                           'BootSourceOverrideEnabled': 'Once',
                           'HttpBootUri': 'http://test.lan/test_image.iso'}},
            etag=mock.ANY)

    def test_set_system_boot_options_httpboot(self):
        self.sys_inst.set_system_boot_options(
            sushy.BootSource.UEFI_HTTP,
            enabled=sushy.BootSourceOverrideEnabled.ONCE,
            mode=sushy.BootSourceOverrideMode.UEFI
            )
        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideTarget': 'UefiHttp',
                           'BootSourceOverrideEnabled': 'Once',
                           'BootSourceOverrideMode': 'UEFI',
                           'HttpBootUri': None}},
            etag=mock.ANY)

    def test_set_system_boot_options_httpboot_unset(self):
        self.sys_inst._settings = mock.Mock()
        self.sys_inst._settings.resource_uri = 'meow'
        settings_body = json.dumps(
            {'Boot': {'HttpBootUri': 'http://foo.bar'}}
        )

        get_settings = mock.MagicMock()
        get_settings.json.return_value = settings_body
        self.conn.get.side_effect = get_settings

        self.sys_inst.set_system_boot_options(
            sushy.BootSource.HDD,
            mode=sushy.BootSourceOverrideMode.UEFI
            )
        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideTarget': 'Hdd',
                           'BootSourceOverrideMode': 'UEFI',
                           'HttpBootUri': None}},
            etag=mock.ANY)

    def test_set_system_boot_source(self):
        self.sys_inst.set_system_boot_source(
            sushy.BootSource.PXE,
            enabled=sushy.BootSourceOverrideEnabled.CONTINUOUS,
            mode=sushy.BootSourceOverrideMode.UEFI)
        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideEnabled': 'Continuous',
                           'BootSourceOverrideTarget': 'Pxe',
                           'BootSourceOverrideMode': 'UEFI'}},
            etag='81802dbf61beb0bd')

    def test_set_system_boot_source_with_etag(self):
        self.conn.get.return_value.headers = {'ETag': '"81802dbf61beb0bd"'}
        self.sys_inst.set_system_boot_source(
            sushy.BOOT_SOURCE_TARGET_PXE,
            enabled=sushy.BOOT_SOURCE_ENABLED_CONTINUOUS,
            mode=sushy.BOOT_SOURCE_MODE_UEFI)
        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideEnabled': 'Continuous',
                           'BootSourceOverrideTarget': 'Pxe',
                           'BootSourceOverrideMode': 'UEFI'}},
            etag="81802dbf61beb0bd")

    def test_set_system_boot_source_no_mode_specified(self):
        self.sys_inst.set_system_boot_source(
            sushy.BootSource.HDD,
            enabled=sushy.BootSourceOverrideEnabled.ONCE)
        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideEnabled': 'Once',
                           'BootSourceOverrideTarget': 'Hdd'}},
            etag='81802dbf61beb0bd')

    def test_set_system_boot_unsets_http_boot_uri(self):
        self.sys_inst.set_system_boot_source(
            sushy.BootSource.HDD,
            enabled=sushy.BootSourceOverrideEnabled.ONCE)
        self.sys_inst._conn.patch.assert_called_once_with(
            '/redfish/v1/Systems/437XR1138R2',
            data={'Boot': {'BootSourceOverrideEnabled': 'Once',
                           'BootSourceOverrideTarget': 'Hdd'}},
            etag=mock.ANY)

    def test_set_system_boot_source_invalid_target(self):
        self.assertRaises(exceptions.InvalidParameterValueError,
                          self.sys_inst.set_system_boot_source,
                          'invalid-target')

    def test_set_system_boot_source_invalid_enabled(self):
        with self.assertRaisesRegex(
            exceptions.InvalidParameterValueError,
            f'"enabled" value.*{list(sushy.BootSourceOverrideEnabled)}'):

            self.sys_inst.set_system_boot_source(
                sushy.BootSource.HDD,
                enabled='invalid-enabled')

    def test_set_indicator_led(self):
        with mock.patch.object(
                self.sys_inst, 'invalidate', autospec=True) as invalidate_mock:
            self.sys_inst.set_indicator_led(sushy.IndicatorLED.BLINKING)
            self.sys_inst._conn.patch.assert_called_once_with(
                '/redfish/v1/Systems/437XR1138R2',
                data={'IndicatorLED': 'Blinking'},
                etag='81802dbf61beb0bd')

            invalidate_mock.assert_called_once_with()

    def test_set_indicator_led_invalid_state(self):
        self.assertRaises(exceptions.InvalidParameterValueError,
                          self.sys_inst.set_indicator_led,
                          'spooky-glowing')

    def test__get_processor_collection_path_missing_processors_attr(self):
        self.sys_inst._json.pop('Processors')
        self.assertRaisesRegex(
            exceptions.MissingAttributeError, 'attribute Processors',
            self.sys_inst._get_processor_collection_path)

    def test_memory_summary_missing_attr(self):
        # | GIVEN |
        self.sys_inst._json['MemorySummary']['Status'].pop('HealthRollup')
        # | WHEN |
        self.sys_inst._parse_attributes(self.json_doc)
        # | THEN |
        self.assertEqual(96, self.sys_inst.memory_summary.size_gib)
        self.assertIsNone(self.sys_inst.memory_summary.health)

        # | GIVEN |
        self.sys_inst._json['MemorySummary'].pop('Status')
        # | WHEN |
        self.sys_inst._parse_attributes(self.json_doc)
        # | THEN |
        self.assertEqual(96, self.sys_inst.memory_summary.size_gib)
        self.assertIsNone(self.sys_inst.memory_summary.health)

        # | GIVEN |
        self.sys_inst._json['MemorySummary'].pop('TotalSystemMemoryGiB')
        # | WHEN |
        self.sys_inst._parse_attributes(self.json_doc)
        # | THEN |
        self.assertIsNone(self.sys_inst.memory_summary.size_gib)
        self.assertIsNone(self.sys_inst.memory_summary.health)

        # | GIVEN |
        self.sys_inst._json.pop('MemorySummary')
        # | WHEN |
        self.sys_inst._parse_attributes(self.json_doc)
        # | THEN |
        self.assertIsNone(self.sys_inst.memory_summary)

    def test_processors(self):
        # | GIVEN |
        self.conn.get.return_value.json.reset_mock()
        with open('sushy/tests/unit/json_samples/'
                  'processor_collection.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)
        # | WHEN |
        actual_processors = self.sys_inst.processors
        # | THEN |
        self.assertIsInstance(actual_processors,
                              processor.ProcessorCollection)
        self.conn.get.return_value.json.assert_called_once_with()

        # reset mock
        self.conn.get.return_value.json.reset_mock()
        # | WHEN & THEN |
        # tests for same object on invoking subsequently
        self.assertIs(actual_processors,
                      self.sys_inst.processors)
        self.conn.get.return_value.json.assert_not_called()

    def test_processors_on_refresh(self):
        # | GIVEN |
        with open('sushy/tests/unit/json_samples/'
                  'processor_collection.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)
        # | WHEN & THEN |
        self.assertIsInstance(self.sys_inst.processors,
                              processor.ProcessorCollection)

        # On refreshing the system instance...
        with open('sushy/tests/unit/json_samples/system.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)

        self.sys_inst.invalidate()
        self.sys_inst.refresh(force=False)

        # | GIVEN |
        with open('sushy/tests/unit/json_samples/'
                  'processor_collection.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)
        # | WHEN & THEN |
        self.assertIsInstance(self.sys_inst.processors,
                              processor.ProcessorCollection)

    def _setUp_processor_summary(self):
        self.conn.get.return_value.json.reset_mock()
        with open('sushy/tests/unit/json_samples/'
                  'processor_collection.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)

        # fetch processors for the first time
        self.sys_inst.processors

        successive_return_values = []
        file_names = ['sushy/tests/unit/json_samples/processor.json',
                      'sushy/tests/unit/json_samples/processor2.json']
        for file_name in file_names:
            with open(file_name) as f:
                successive_return_values.append(json.load(f))

        self.conn.get.return_value.json.side_effect = successive_return_values

    def test_processor_summary(self):
        # | GIVEN |
        self._setUp_processor_summary()
        # | WHEN |
        actual_processor_summary = self.sys_inst.processors.summary
        # | THEN |
        self.assertEqual((16, sushy.ProcessorArchitecture.X86),
                         actual_processor_summary)
        self.assertEqual(16, actual_processor_summary.count)
        self.assertEqual(sushy.ProcessorArchitecture.X86,
                         actual_processor_summary.architecture)

        # reset mock
        self.conn.get.return_value.json.reset_mock()

        # | WHEN & THEN |
        # tests for same object on invoking subsequently
        self.assertIs(actual_processor_summary,
                      self.sys_inst.processors.summary)
        self.conn.get.return_value.json.assert_not_called()

    def test_ethernet_interfaces(self):
        self.conn.get.return_value.json.reset_mock()
        eth_coll_return_value = None
        eth_return_value = None
        with open('sushy/tests/unit/json_samples/'
                  'ethernet_interfaces_collection.json') as f:
            eth_coll_return_value = json.load(f)
        with open('sushy/tests/unit/json_samples/'
                  'ethernet_interfaces.json') as f:
            eth_return_value = json.load(f)

        self.conn.get.return_value.json.side_effect = [eth_coll_return_value,
                                                       eth_return_value]

        actual_macs = self.sys_inst.ethernet_interfaces.summary
        expected_macs = (
            {'12:44:6A:3B:04:11': res_cons.State.ENABLED})
        self.assertEqual(expected_macs, actual_macs)

    def test_bios(self):
        self.conn.get.return_value.json.reset_mock()
        bios_return_value = None
        with open('sushy/tests/unit/json_samples/bios.json') as f:
            bios_return_value = json.load(f)
        self.conn.get.return_value.json.side_effect = [bios_return_value]

        self.assertIsInstance(self.sys_inst.bios, bios.Bios)
        self.assertEqual('BIOS Configuration Current Settings',
                         self.sys_inst.bios.name)

    def test_secure_boot(self):
        self.conn.get.return_value.json.reset_mock()
        with open('sushy/tests/unit/json_samples/secure_boot.json') as f:
            self.conn.get.return_value.json.side_effect = [json.load(f)]

        self.assertIsInstance(self.sys_inst.secure_boot,
                              secure_boot.SecureBoot)
        self.assertEqual('UEFI Secure Boot', self.sys_inst.secure_boot.name)

    def test_simple_storage_for_missing_attr(self):
        self.sys_inst.json.pop('SimpleStorage')
        with self.assertRaisesRegex(
                exceptions.MissingAttributeError, 'attribute SimpleStorage'):
            self.sys_inst.simple_storage

    def test_simple_storage(self):
        # | GIVEN |
        self.conn.get.return_value.json.reset_mock()
        with open('sushy/tests/unit/json_samples/'
                  'simple_storage_collection.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)
        # | WHEN |
        actual_simple_storage = self.sys_inst.simple_storage
        # | THEN |
        self.assertIsInstance(actual_simple_storage,
                              simple_storage.SimpleStorageCollection)
        self.conn.get.return_value.json.assert_called_once_with()

        # reset mock
        self.conn.get.return_value.json.reset_mock()
        # | WHEN & THEN |
        # tests for same object on invoking subsequently
        self.assertIs(actual_simple_storage,
                      self.sys_inst.simple_storage)
        self.conn.get.return_value.json.assert_not_called()

    def test_simple_storage_on_refresh(self):
        # | GIVEN |
        with open('sushy/tests/unit/json_samples/'
                  'simple_storage_collection.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)
        # | WHEN & THEN |
        self.assertIsInstance(self.sys_inst.simple_storage,
                              simple_storage.SimpleStorageCollection)

        # On refreshing the system instance...
        with open('sushy/tests/unit/json_samples/system.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)

        self.sys_inst.invalidate()
        self.sys_inst.refresh(force=False)

        # | GIVEN |
        with open('sushy/tests/unit/json_samples/'
                  'simple_storage_collection.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)
        # | WHEN & THEN |
        self.assertIsInstance(self.sys_inst.simple_storage,
                              simple_storage.SimpleStorageCollection)

    def test_storage_for_missing_attr(self):
        self.sys_inst.json.pop('SimpleStorage')
        with self.assertRaisesRegex(
                exceptions.MissingAttributeError, 'attribute Storage'):
            self.sys_inst.storage

    def test_storage(self):
        # | GIVEN |
        self.conn.get.return_value.json.reset_mock()
        with open('sushy/tests/unit/json_samples/'
                  'storage_collection.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)
        # | WHEN |
        actual_storage = self.sys_inst.simple_storage
        # | THEN |
        self.assertIsInstance(actual_storage,
                              simple_storage.SimpleStorageCollection)
        self.conn.get.return_value.json.assert_called_once_with()

        # reset mock
        self.conn.get.return_value.json.reset_mock()
        # | WHEN & THEN |
        # tests for same object on invoking subsequently
        self.assertIs(actual_storage, self.sys_inst.simple_storage)
        self.conn.get.return_value.json.assert_not_called()

    def test_storage_on_refresh(self):
        # | GIVEN |
        with open('sushy/tests/unit/json_samples/'
                  'storage_collection.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)
        # | WHEN & THEN |
        self.assertIsInstance(self.sys_inst.simple_storage,
                              simple_storage.SimpleStorageCollection)

        # On refreshing the system instance...
        with open('sushy/tests/unit/json_samples/system.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)

        self.sys_inst.invalidate()
        self.sys_inst.refresh(force=False)

        # | GIVEN |
        with open('sushy/tests/unit/json_samples/'
                  'storage_collection.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)
        # | WHEN & THEN |
        self.assertIsInstance(self.sys_inst.simple_storage,
                              simple_storage.SimpleStorageCollection)

    def test_managers(self):
        # | GIVEN |
        with open('sushy/tests/unit/json_samples/'
                  'manager.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)

        # | WHEN & THEN |
        actual_managers = self.sys_inst.managers
        self.assertIsInstance(actual_managers[0], manager.Manager)
        self.assertEqual(
            '/redfish/v1/Managers/BMC', actual_managers[0].path)

    def test_managers_retry(self):
        with open('sushy/tests/unit/json_samples/'
                  'system_managedby_missing_manager_present.json') as f:
            settings_obj = json.load(f)
        self.json_doc.update(settings_obj)
        self.sys_inst._parse_attributes(self.json_doc)

        # | GIVEN |
        with open('sushy/tests/unit/json_samples/'
                  'manager.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)

        # | WHEN & THEN |
        actual_managers = self.sys_inst.managers
        self.assertIsInstance(actual_managers[0], manager.Manager)
        self.assertEqual(
            '/redfish/v1/Managers/BMC', actual_managers[0].path)

    def test_managers_missing_ref(self):
        # | GIVEN |
        del self.json_doc['Links']['ManagedBy']

        # | WHEN & THEN |
        with self.assertRaisesRegex(
            exceptions.MissingAttributeError, 'attribute Links/ManagedBy'):
            self.sys_inst.managers

    def test_chassis(self):
        # | GIVEN |
        with open('sushy/tests/unit/json_samples/'
                  'chassis.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)

        # | WHEN & THEN |
        actual_chassis = self.sys_inst.chassis
        self.assertIsInstance(actual_chassis[0], chassis.Chassis)
        self.assertEqual(
            '/redfish/v1/Chassis/1U', actual_chassis[0].path)

    def test_get_oem_extension(self):
        # | WHEN |
        contoso_system_extn_inst = self.sys_inst.get_oem_extension('Contoso')
        # | THEN |
        self.assertIsInstance(contoso_system_extn_inst,
                              fake.FakeOEMSystemExtension)
        self.assertIs(self.sys_inst, contoso_system_extn_inst._parent_resource)
        self.assertEqual('Contoso', contoso_system_extn_inst._vendor_id)

    def test_no_virtual_media_attr(self):
        with self.assertRaisesRegex(
            exceptions.MissingAttributeError, 'attribute VirtualMedia'):
            self.sys_inst.virtual_media

    def test_boot_field_settings_fallback_allowed_values(self):
        """Test BootField falls back to Settings for allowed_values."""
        # Setup: Main Boot section missing allowed_values
        boot_data_without_allowed_values = {
            'BootSourceOverrideEnabled': 'Once',
            'BootSourceOverrideTarget': 'Pxe',
            'BootSourceOverrideMode': 'UEFI'
        }

        # Settings resource with allowed_values
        settings_data = {
            'Boot': {
                'BootSourceOverrideTarget@Redfish.AllowableValues': [
                    'None', 'Pxe', 'Cd', 'Usb', 'Hdd', 'UefiHttp'
                ]
            }
        }

        # Mock system data without allowed_values in main Boot section
        system_data = self.json_doc.copy()
        system_data['Boot'] = boot_data_without_allowed_values

        # Mock settings response
        settings_response = mock.Mock()
        settings_response.json.return_value = settings_data

        # Setup system with settings
        self.sys_inst._settings = mock.Mock()
        self.sys_inst._settings.resource_uri = '/redfish/v1/Systems/Self/SD'

        # Mock the connector to return settings data when requested
        self.conn.get.return_value = settings_response

        # Create a new boot field instance
        boot_field = system.BootField('Boot')
        boot_field_instance = boot_field._load(system_data, self.sys_inst)

        # Verify allowed_values was populated from Settings
        expected_values = ['None', 'Pxe', 'Cd', 'Usb', 'Hdd', 'UefiHttp']
        self.assertEqual(expected_values, boot_field_instance.allowed_values)

        # Verify Settings URI was called
        self.conn.get.assert_called_with('/redfish/v1/Systems/Self/SD')

    def test_boot_field_settings_fallback_all_fields(self):
        """Test BootField falls back to Settings for all missing fields."""
        # Setup: Main Boot section with minimal data
        boot_data_minimal = {}

        # Settings resource with all boot values
        settings_data = {
            'Boot': {
                'BootSourceOverrideTarget@Redfish.AllowableValues': [
                    'None', 'Pxe', 'Cd', 'UefiHttp'
                ],
                'BootSourceOverrideEnabled': 'Continuous',
                'BootSourceOverrideMode': 'Legacy',
                'BootSourceOverrideTarget': 'Cd',
                'HttpBootUri': 'https://example.com/boot.iso'
            }
        }

        # Mock system data with empty Boot section
        system_data = self.json_doc.copy()
        system_data['Boot'] = boot_data_minimal

        # Mock settings response
        settings_response = mock.Mock()
        settings_response.json.return_value = settings_data

        # Setup system with settings
        self.sys_inst._settings = mock.Mock()
        self.sys_inst._settings.resource_uri = '/redfish/v1/Systems/Self/SD'

        # Mock the connector to return settings data when requested
        self.conn.get.return_value = settings_response

        # Create a new boot field instance
        boot_field = system.BootField('Boot')
        boot_field_instance = boot_field._load(system_data, self.sys_inst)

        # Verify all values were populated from Settings
        self.assertEqual(['None', 'Pxe', 'Cd', 'UefiHttp'],
                         boot_field_instance.allowed_values)
        self.assertEqual(sushy.BootSourceOverrideEnabled.CONTINUOUS,
                         boot_field_instance.enabled)
        self.assertEqual(sushy.BootSourceOverrideMode.LEGACY,
                         boot_field_instance.mode)
        self.assertEqual(sushy.BootSource.CD, boot_field_instance.target)
        self.assertEqual('https://example.com/boot.iso',
                         boot_field_instance.http_boot_uri)

    def test_boot_field_settings_fallback_partial_override(self):
        """Test BootField only falls back for None values, not existing."""
        # Setup: Main Boot section with some values
        boot_data_partial = {
            'BootSourceOverrideEnabled': 'Once',
            'BootSourceOverrideTarget': 'Pxe',
            # Missing allowed_values, mode, and http_boot_uri
        }

        # Settings resource with values
        settings_data = {
            'Boot': {
                'BootSourceOverrideTarget@Redfish.AllowableValues': [
                    'None', 'Pxe', 'Cd'
                ],
                'BootSourceOverrideEnabled': 'Continuous',  # No override
                'BootSourceOverrideMode': 'UEFI',  # Should be used
                'HttpBootUri': 'https://example.com/boot.iso'  # Should be used
            }
        }

        # Mock system data with partial Boot section
        system_data = self.json_doc.copy()
        system_data['Boot'] = boot_data_partial

        # Mock settings response
        settings_response = mock.Mock()
        settings_response.json.return_value = settings_data

        # Setup system with settings
        self.sys_inst._settings = mock.Mock()
        self.sys_inst._settings.resource_uri = '/redfish/v1/Systems/Self/SD'

        # Mock the connector to return settings data when requested
        self.conn.get.return_value = settings_response

        # Create a new boot field instance
        boot_field = system.BootField('Boot')
        boot_field_instance = boot_field._load(system_data, self.sys_inst)

        # Verify existing values were NOT overridden
        self.assertEqual(sushy.BootSourceOverrideEnabled.ONCE,
                         boot_field_instance.enabled)
        self.assertEqual(sushy.BootSource.PXE, boot_field_instance.target)

        # Verify missing values were populated from Settings
        self.assertEqual(['None', 'Pxe', 'Cd'],
                         boot_field_instance.allowed_values)
        self.assertEqual(sushy.BootSourceOverrideMode.UEFI,
                         boot_field_instance.mode)
        self.assertEqual('https://example.com/boot.iso',
                         boot_field_instance.http_boot_uri)

    def test_boot_field_no_settings_resource(self):
        """Test BootField works normally when no Settings resource exists."""
        # Setup: System without Settings resource
        self.sys_inst._settings = None

        # Use original test data (has allowed_values in main Boot section)
        boot_field = system.BootField('Boot')
        boot_field_instance = boot_field._load(self.json_doc, self.sys_inst)

        # Verify normal loading behavior
        expected_values = ["None", "Pxe", "Cd", "Usb", "Hdd", "BiosSetup",
                           "Utilities", "Diags", "SDCard", "UefiTarget",
                           "UefiHttp"]
        self.assertEqual(expected_values, boot_field_instance.allowed_values)
        self.assertEqual(sushy.BootSourceOverrideEnabled.ONCE,
                         boot_field_instance.enabled)

    @mock.patch.object(system.LOG, 'warning', autospec=True)
    def test_boot_field_settings_fetch_error(self, mock_log):
        """Test BootField handles Settings fetch errors gracefully."""
        # Setup: Main Boot section missing allowed_values
        boot_data_without_allowed_values = {
            'BootSourceOverrideEnabled': 'Once',
            'BootSourceOverrideTarget': 'Pxe'
        }

        system_data = self.json_doc.copy()
        system_data['Boot'] = boot_data_without_allowed_values

        # Setup system with settings but make the call fail
        self.sys_inst._settings = mock.Mock()
        self.sys_inst._settings.resource_uri = '/redfish/v1/Systems/Self/SD'

        # Mock connector to raise exception on Settings fetch
        self.conn.get.side_effect = Exception("Network error")

        # Create a new boot field instance
        boot_field = system.BootField('Boot')
        boot_field_instance = boot_field._load(system_data, self.sys_inst)

        # Verify the instance was still created but without fallback values
        self.assertIsNotNone(boot_field_instance)
        self.assertIsNone(boot_field_instance.allowed_values)

        # Verify warning was logged
        mock_log.assert_called_once()
        self.assertIn('Failed to retrieve boot field values',
                      str(mock_log.call_args))

    def test_boot_field_settings_invalid_enum_values(self):
        """Test BootField handles invalid enum values gracefully."""
        # Setup: Main Boot section with minimal data
        boot_data_minimal = {}

        # Settings resource with invalid enum values
        settings_data = {
            'Boot': {
                'BootSourceOverrideEnabled': 'InvalidValue',
                'BootSourceOverrideMode': 'BadMode',
                'BootSourceOverrideTarget': 'UnknownTarget'
            }
        }

        system_data = self.json_doc.copy()
        system_data['Boot'] = boot_data_minimal

        # Mock settings response
        settings_response = mock.Mock()
        settings_response.json.return_value = settings_data

        # Setup system with settings
        self.sys_inst._settings = mock.Mock()
        self.sys_inst._settings.resource_uri = '/redfish/v1/Systems/Self/SD'

        # Mock the connector to return settings data when requested
        self.conn.get.return_value = settings_response

        # Create a new boot field instance
        boot_field = system.BootField('Boot')
        boot_field_instance = boot_field._load(system_data, self.sys_inst)

        # Verify invalid enum values resulted in None (graceful handling)
        self.assertIsNone(boot_field_instance.enabled)
        self.assertIsNone(boot_field_instance.mode)
        self.assertIsNone(boot_field_instance.target)


class SystemWithVirtualMedia(base.TestCase):

    def setUp(self):
        super().setUp()
        self.conn = mock.Mock()
        with open('sushy/tests/unit/json_samples/'
                  'systemv1_20.json') as f:
            self.json_doc = json.load(f)

        self.conn.get.return_value.json.return_value = self.json_doc

        self.sys_inst = system.System(
            self.conn, '/redfish/v1/Systems/437XR1138R2',
            redfish_version='1.0.2')

    def test_virtual_media(self):
        # | GIVEN |
        with open('sushy/tests/unit/json_samples/'
                  'virtual_mediav1_6.json') as f:
            virtual_media_json = json.load(f)

        with open('sushy/tests/unit/json_samples/'
                  'virtual_media_collectionv1_6.json') as f:
            virtual_media_collection_json = json.load(f)

        self.conn.get.return_value.json.side_effect = [
            virtual_media_collection_json, virtual_media_json]

        # | WHEN |
        actual_virtual_media = self.sys_inst.virtual_media

        # | THEN |
        self.assertIsInstance(actual_virtual_media,
                              virtual_media.VirtualMediaCollection)
        self.assertEqual(actual_virtual_media.name, 'Virtual Media Services')

        member = actual_virtual_media.get_member(
            '/redfish/v1/Systems/437XR1138R2/VirtualMedia/CD1')

        self.assertEqual(member.image_name, 'mymedia-read-only')
        self.assertTrue(member.inserted)
        self.assertFalse(member.write_protected)
        self.assertTrue(member.verify_certificate)

    def test_virtual_media_on_refresh(self):
        # | GIVEN |
        with open('sushy/tests/unit/json_samples/'
                  'virtual_media_collectionv1_6.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)

        # | WHEN & THEN |
        actual_virtual_media = self.sys_inst.virtual_media
        self.assertIsInstance(actual_virtual_media,
                              virtual_media.VirtualMediaCollection)

        with open('sushy/tests/unit/json_samples/systemv1_20.json') as f:
            self.conn.get.return_value.json.return_value = json.loads(f.read())

        self.sys_inst.invalidate()
        self.sys_inst.refresh(force=False)

        self.assertTrue(actual_virtual_media._is_stale)

        # | GIVEN |
        with open('sushy/tests/unit/json_samples/'
                  'virtual_media_collectionv1_6.json') as f:
            self.conn.get.return_value.json.return_value = json.load(f)

        # | WHEN & THEN |
        self.assertIsInstance(self.sys_inst.virtual_media,
                              virtual_media.VirtualMediaCollection)
        self.assertFalse(actual_virtual_media._is_stale)


class SystemCollectionTestCase(base.TestCase):

    def setUp(self):
        super().setUp()
        self.conn = mock.Mock()
        with open('sushy/tests/unit/json_samples/'
                  'system_collection.json') as f:
            self.json_doc = json.load(f)

        self.conn.get.return_value.json.return_value = self.json_doc

        self.sys_col = system.SystemCollection(
            self.conn, '/redfish/v1/Systems', redfish_version='1.0.2')

    def test__parse_attributes(self):
        self.sys_col._parse_attributes(self.json_doc)
        self.assertEqual('1.0.2', self.sys_col.redfish_version)
        self.assertEqual('Computer System Collection', self.sys_col.name)
        self.assertEqual(('/redfish/v1/Systems/437XR1138R2',),
                         self.sys_col.members_identities)

    @mock.patch.object(system, 'System', autospec=True)
    def test_get_member(self, mock_system):
        self.sys_col.get_member('/redfish/v1/Systems/437XR1138R2')
        mock_system.assert_called_once_with(
            self.sys_col._conn, '/redfish/v1/Systems/437XR1138R2',
            redfish_version=self.sys_col.redfish_version, registries=None,
            root=self.sys_col.root)

    @mock.patch.object(system, 'System', autospec=True)
    def test_get_members(self, mock_system):
        members = self.sys_col.get_members()
        mock_system.assert_called_once_with(
            self.sys_col._conn, '/redfish/v1/Systems/437XR1138R2',
            redfish_version=self.sys_col.redfish_version, registries=None,
            root=self.sys_col.root)
        self.assertIsInstance(members, list)
        self.assertEqual(1, len(members))
