Background
Follow-up to #2192 (foundation) and PR #2193 (pytest + Zuul infrastructure). Part of Tier 3 (#2199). osism/tasks/conductor/redfish.py is the Redfish data-collection module: it normalizes raw Redfish payloads and walks Systems / Chassis to extract EthernetInterfaces, NetworkAdapters, and NetworkDeviceFunctions. Small enough to cover in a single test module.
Scope
Add tests/unit/tasks/conductor/test_redfish.py covering all functions in osism/tasks/conductor/redfish.py.
Test targets
_normalize_redfish_data(data) — redfish.py:8
Pure function. Build small input dicts inline.
None value → key removed from result
dict value → JSON-serialized (json.dumps)
list value → JSON-serialized
bool True → "true"; bool False → "false"
int / float → str(value)
- Plain
str → kept as-is
- Mixed input combining all of the above → all keys present except those with
None
- Empty dict → empty dict
get_resources(hostname, resource_type) — redfish.py:38
Patch osism.tasks.conductor.redfish._get_ethernet_interfaces, ..._get_network_adapters, ..._get_network_device_functions.
resource_type="EthernetInterfaces" → delegates to _get_ethernet_interfaces and returns its result; other helpers not called
resource_type="NetworkAdapters" → delegates to _get_network_adapters
resource_type="NetworkDeviceFunctions" → delegates to _get_network_device_functions
- Unknown resource type → returns
[], warning logged
_get_ethernet_interfaces(hostname) — redfish.py:64
Patch osism.tasks.conductor.redfish.get_redfish_connection. Build a fake connection chain with MagicMock:
conn.get_system_collection().get_members() -> [system]
system.identity = "1"
system.ethernet_interfaces.get_members() -> [iface1, iface2]
iface.identity / iface.name / iface.mac_address / iface.speed_mbps / ...
Cases:
- Connection unavailable (
get_redfish_connection returns None) → returns [], error logged
- Connection raises top-level exception → returns
[], error logged
- System without
ethernet_interfaces attribute → no entries, but no error
- System with
ethernet_interfaces=None → no entries
- Two systems, each with two interfaces → 4 entries returned, each normalized via
_normalize_redfish_data
- Per-interface exception (e.g.
interface.identity raises during attribute access) → that interface skipped, warning logged, others still returned
- All optional attributes returned via
getattr(..., None) (name, description, mac_address, permanent_mac_address, speed_mbps, mtu_size, link_status, interface_enabled)
mac_address=None → key removed from result by _normalize_redfish_data
_get_network_adapters(hostname) — redfish.py:134
Same shape as _get_ethernet_interfaces but iterates chassis.network_adapters:
- No connection →
[], error logged
- Top-level exception →
[], error logged
- Chassis without
network_adapters → no entries
- One chassis with two adapters → two entries with
id, name, manufacturer, model, part_number, serial_number, firmware_version
- Per-adapter exception → skipped, warning logged
_get_network_device_functions(hostname) — redfish.py:201
Patch get_redfish_connection. Cover the nested loop (Chassis → NetworkAdapters → NetworkDeviceFunctions):
- No connection →
[], error logged
- Top-level exception →
[], error logged
- Adapter without
network_device_functions → outer for adapter in ... is exercised, inner for device_func in ... raises caught by adapter-level except (warning logged), processing continues for next adapter
- Per-device-function exception → skipped, warning logged
- Device function with
ethernet attribute → mac_address and permanent_mac_address extracted from device_func.ethernet
- Device function without
ethernet → both MACs None (then stripped by normalization)
- All entries include
adapter_id and adapter_name so callers can correlate
Mocking hints
MagicMock is sufficient for the Redfish connection chain. Set get_members.return_value=[...] per level.
- For attribute-not-present cases, use
del mock.attribute to make hasattr(...) return False. For attribute-raises cases, set mock.attribute = property(lambda self: 1/0) is overkill — instead, point getattr(...) to a Mock(side_effect=Exception("boom")) for that attribute.
_normalize_redfish_data is exercised indirectly in the three loop functions — assert on the returned dicts to confirm normalization happened (e.g. no None values, lists turned into JSON strings).
Definition of Done
Dependencies
Background
Follow-up to #2192 (foundation) and PR #2193 (pytest + Zuul infrastructure). Part of Tier 3 (#2199).
osism/tasks/conductor/redfish.pyis the Redfish data-collection module: it normalizes raw Redfish payloads and walks Systems / Chassis to extractEthernetInterfaces,NetworkAdapters, andNetworkDeviceFunctions. Small enough to cover in a single test module.Scope
Add
tests/unit/tasks/conductor/test_redfish.pycovering all functions inosism/tasks/conductor/redfish.py.Test targets
_normalize_redfish_data(data)—redfish.py:8Pure function. Build small input dicts inline.
Nonevalue → key removed from resultdictvalue → JSON-serialized (json.dumps)listvalue → JSON-serializedbool True→"true";bool False→"false"int/float→str(value)str→ kept as-isNoneget_resources(hostname, resource_type)—redfish.py:38Patch
osism.tasks.conductor.redfish._get_ethernet_interfaces,..._get_network_adapters,..._get_network_device_functions.resource_type="EthernetInterfaces"→ delegates to_get_ethernet_interfacesand returns its result; other helpers not calledresource_type="NetworkAdapters"→ delegates to_get_network_adaptersresource_type="NetworkDeviceFunctions"→ delegates to_get_network_device_functions[], warning logged_get_ethernet_interfaces(hostname)—redfish.py:64Patch
osism.tasks.conductor.redfish.get_redfish_connection. Build a fake connection chain withMagicMock:Cases:
get_redfish_connectionreturnsNone) → returns[], error logged[], error loggedethernet_interfacesattribute → no entries, but no errorethernet_interfaces=None→ no entries_normalize_redfish_datainterface.identityraises during attribute access) → that interface skipped, warning logged, others still returnedgetattr(..., None)(name,description,mac_address,permanent_mac_address,speed_mbps,mtu_size,link_status,interface_enabled)mac_address=None→ key removed from result by_normalize_redfish_data_get_network_adapters(hostname)—redfish.py:134Same shape as
_get_ethernet_interfacesbut iterateschassis.network_adapters:[], error logged[], error loggednetwork_adapters→ no entriesid,name,manufacturer,model,part_number,serial_number,firmware_version_get_network_device_functions(hostname)—redfish.py:201Patch
get_redfish_connection. Cover the nested loop (Chassis → NetworkAdapters → NetworkDeviceFunctions):[], error logged[], error loggednetwork_device_functions→ outerfor adapter in ...is exercised, innerfor device_func in ...raises caught by adapter-levelexcept(warning logged), processing continues for next adapterethernetattribute →mac_addressandpermanent_mac_addressextracted fromdevice_func.ethernetethernet→ both MACsNone(then stripped by normalization)adapter_idandadapter_nameso callers can correlateMocking hints
MagicMockis sufficient for the Redfish connection chain. Setget_members.return_value=[...]per level.del mock.attributeto makehasattr(...)returnFalse. For attribute-raises cases, setmock.attribute = property(lambda self: 1/0)is overkill — instead, pointgetattr(...)to aMock(side_effect=Exception("boom"))for that attribute._normalize_redfish_datais exercised indirectly in the three loop functions — assert on the returned dicts to confirm normalization happened (e.g. noNonevalues, lists turned into JSON strings).Definition of Done
tests/unit/tasks/conductor/test_redfish.pycreatedpytest --cov=osism.tasks.conductor.redfishshows 100 %pipenv run pytest tests/unit/tasks/conductor/test_redfish.pypasses locallyflake8,mypy,python-blackremain greenpython-osism-unit-testspassesDependencies