diff --git a/tests/test_plugins/test_endpoints_disk_health.py b/tests/test_plugins/test_endpoints_disk_health.py new file mode 100644 index 00000000..1674292f --- /dev/null +++ b/tests/test_plugins/test_endpoints_disk_health.py @@ -0,0 +1,108 @@ +"""Disk health rows: a dying drive must never render as a healthy one. + +The agent reports drive health from two sources because neither covers the +fleet. MSFT_PhysicalDisk is the only one that reports NVMe - verified on a +two-NVMe box where the classic ATA SMART class returned zero instances while +both drives were present and healthy. Win32_DiskDrive covers ATA/SATA and hosts +where the Storage namespace is missing. + +That means the panel can receive the same physical drive twice, from sources +that may disagree, plus rows where health could not be read at all. Every test +here pins a way this card could start showing a failing or unreadable drive as +fine, which is the only failure mode that matters: nobody investigates a disk +that looks healthy. +""" + +from plugins.endpoints.api.routes import _diskhealth_rows + + +def _drive(deviceid, predictfailure, healthstatus='Healthy', + source='physicaldisk', model='Samsung SSD'): + return { + 'deviceid': deviceid, + 'model': model, + 'mediatype': 'SSD', + 'bustype': 'NVMe', + 'healthstatus': healthstatus, + 'predictfailure': predictfailure, + 'source': source, + } + + +def test_a_failing_drive_sorts_above_healthy_ones(): + """On a multi-disk box the bad drive must not be below the fold.""" + rows = _diskhealth_rows([ + _drive('0', False), + _drive('1', True, healthstatus='Unhealthy'), + _drive('2', False), + ]) + + assert [row['deviceid'] for row in rows] == ['1', '0', '2'] + assert rows[0]['verdict'] == 'Failure predicted' + + +def test_a_drive_that_did_not_answer_reads_as_unknown_not_ok(): + """Null predictfailure is an unread drive, not a healthy one.""" + rows = _diskhealth_rows([_drive('0', None, healthstatus='')]) + + assert rows[0]['verdict'] == 'Unknown' + assert rows[0]['healthstatus'] == 'Unknown' + + +def test_unknown_outranks_ok_so_silence_is_not_buried_under_health(): + """A drive that stopped answering is a weaker claim than one saying OK.""" + rows = _diskhealth_rows([ + _drive('0', False), + _drive('1', None), + ]) + + assert [row['verdict'] for row in rows] == ['Unknown', 'OK'] + + +def test_both_sources_survive_for_the_same_hardware(): + """Two disagreeing views of one drive is the signal, not noise. + + Collapsing them would hide the case worth looking at: one source calling a + drive healthy while the other predicts failure. + """ + rows = _diskhealth_rows([ + _drive('0', False, source='physicaldisk'), + _drive('0', True, healthstatus='Pred Fail', source='diskdrive'), + ]) + + assert len(rows) == 2 + assert {row['source'] for row in rows} == {'physicaldisk', 'diskdrive'} + assert rows[0]['verdict'] == 'Failure predicted' + + +def test_input_order_does_not_change_which_drive_is_shown_first(): + """The verdict ranking must decide the order, not arrival order.""" + healthy = _drive('0', False) + failing = _drive('1', True) + + forward = _diskhealth_rows([healthy, failing]) + backward = _diskhealth_rows([failing, healthy]) + + assert forward == backward + assert forward[0]['deviceid'] == '1' + + +def test_an_nvme_drive_is_reported_rather_than_dropped(): + """The whole reason for the second source: ATA SMART returns nothing here.""" + rows = _diskhealth_rows([_drive('0', False, source='physicaldisk')]) + + assert rows[0]['bustype'] == 'NVMe' + assert rows[0]['verdict'] == 'OK' + + +def test_a_malformed_payload_does_not_take_the_panel_down(): + rows = _diskhealth_rows([_drive('0', False), 'not a dict', None]) + + assert len(rows) == 1 + assert rows[0]['deviceid'] == '0' + + +def test_a_missing_or_wrong_shaped_payload_is_an_empty_panel(): + assert _diskhealth_rows(None) == [] + assert _diskhealth_rows({}) == [] + assert _diskhealth_rows([]) == [] diff --git a/tests/test_plugins/test_endpoints_plugin.py b/tests/test_plugins/test_endpoints_plugin.py index 979bb70b..2bfd4d9b 100644 --- a/tests/test_plugins/test_endpoints_plugin.py +++ b/tests/test_plugins/test_endpoints_plugin.py @@ -69,7 +69,7 @@ def test_endpoints_asset_panels_are_computer_scoped(): """Every asset panel targets computer assets only, with an endpoint.""" plugin = EndpointsPlugin() panels = plugin.get_asset_panels() - assert len(panels) == 12 + assert len(panels) == 13 for panel in panels: assert panel['assettypes'] == ['computer'] assert panel['endpoint'].startswith('/api/endpoints/')