-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local lu = require 'luaunit'

TestPmbusPDC4000D12LCProtocol = {}
TestPmbusPDC4000D12LCMonitor = {}

local PAGE_PRIMARY = 0
local PAGE_SECONDARY = 1

local function get_pbit(val)
    return 1 << val
end

local function new_health_event()
    return {
        input_voltage_fault = 0x00,
        temper_fault = 0x00,
        output_voltage_fault = 0x00,
        output_current_fault = 0x00,
        cml_fault = 0x00,
        other_fault = 0x00,
        mfr_specific_status = 0x00,
        event = 0x00
    }
end

local function new_protocol_psu_slot()
    return {
        ['SlotNumber'] = 1,
        ['SlotI2cAddr'] = 0x58,
        ['FruI2cAddr'] = 0x50,
        ['FruChip'] = {},
        ['PsuChip'] = {
            ['bmc.kepler.Chip.BlockIO'] = {}
        }
    }
end

local function new_monitor_env()
    local psu_slot = {
        ['SlotNumber'] = 1,
        ['ps_id'] = 1,
        ['Presence'] = 1,
        ['scan_status'] = 0,
        ['prop_value'] = {},
        ['PsuChip'] = {
            ['bmc.kepler.Chip.BlockIO'] = {}
        }
    }

    function psu_slot:dal_refresh_property(prop, value)
        self.prop_value[prop] = value
        self[prop] = value
    end

    function psu_slot:get_dynamic_data(prop)
        return nil, prop
    end

    function psu_slot:sleep_ms(ms)
    end

    function psu_slot:next_tick(cb)
        return {stop = function() end}
    end

    local protocol = {}
    protocol.set_page = function(self, page)
        return true
    end
    protocol.get_health_event = function(self, page)
        return new_health_event(), false
    end

    local monitor = require 'device_tree.adapters.power_mgmt.protocol.monitor.pmbus_PDC4000D12_LC'
    local psu = monitor(psu_slot)
    psu:ctor(protocol)
    return psu, protocol
end

local psu, protocol = new_monitor_env()
local add_event = require 'device_tree.adapters.power_mgmt.add_event'
local function stub_switch_page_event(calls)
    add_event.get_instance = function()
        return {
            generate_switch_page_failure = function(state, ps_id, page)
                table.insert(calls, {state = state, ps_id = ps_id, page = page})
            end
        }
    end
end

-- Given: 协议层PAGE写入后回读值与目标页一致。
-- When: 调用 set_page(PAGE_SECONDARY)。
-- Then: 返回 true，且写入命令字为 PAGE、写入值为 PAGE_SECONDARY。
function TestPmbusPDC4000D12LCProtocol:test_set_page_success()
    local pmbus = require 'device_tree.adapters.power_mgmt.protocol.pmbus_PDC4000D12_LC'.new(new_protocol_psu_slot())

    local write_cmd = 0
    local write_data = 0
    pmbus.chip_bytewrite = function(self, cmd, data)
        write_cmd = cmd
        write_data = data
    end
    pmbus.get_page = function()
        return PAGE_SECONDARY
    end

    local ok = pmbus:set_page(PAGE_SECONDARY)
    lu.assertEquals(ok, true)
    lu.assertEquals(write_cmd, pmbus.cmd.PAGE)
    lu.assertEquals(write_data, PAGE_SECONDARY)
end

-- Given: 协议层PAGE写入后回读值与目标页不一致。
-- When: 调用 set_page(PAGE_SECONDARY)。
-- Then: 返回 nil，判定切页失败。
function TestPmbusPDC4000D12LCProtocol:test_set_page_failed_when_readback_mismatch()
    local pmbus = require 'device_tree.adapters.power_mgmt.protocol.pmbus_PDC4000D12_LC'.new(new_protocol_psu_slot())

    pmbus.chip_bytewrite = function(self, cmd, data)
    end
    pmbus.get_page = function()
        return PAGE_PRIMARY
    end

    lu.assertEquals(pmbus:set_page(PAGE_SECONDARY), nil)
end

-- Given: 协议层写PAGE命令时抛出异常。
-- When: 调用 set_page(PAGE_SECONDARY)。
-- Then: 返回 nil，判定切页失败。
function TestPmbusPDC4000D12LCProtocol:test_set_page_failed_when_write_error()
    local pmbus = require 'device_tree.adapters.power_mgmt.protocol.pmbus_PDC4000D12_LC'.new(new_protocol_psu_slot())

    pmbus.chip_bytewrite = function(self, cmd, data)
        error('write failed')
    end
    pmbus.get_page = function()
        return PAGE_SECONDARY
    end

    lu.assertEquals(pmbus:set_page(PAGE_SECONDARY), nil)
end

-- Given: get_page_health_event 传入非法 page 参数。
-- When: 调用 get_page_health_event(99) 并写入字段。
-- Then: 字段写入到 PAGE_PRIMARY 对应缓存。
function TestPmbusPDC4000D12LCProtocol:test_get_page_health_event_invalid_page_use_primary()
    local pmbus = require 'device_tree.adapters.power_mgmt.protocol.pmbus_PDC4000D12_LC'.new(new_protocol_psu_slot())

    local health_event = pmbus:get_page_health_event(99)
    health_event.input_voltage_fault = 0x90
    lu.assertEquals(pmbus.health_event_cache[PAGE_PRIMARY].input_voltage_fault, 0x90)
end

-- Given: A/B 两页返回不同的 STATUS 数据。
-- When: 分别调用 get_health_event(PAGE_PRIMARY) 与 get_health_event(PAGE_SECONDARY)。
-- Then: A/B 返回对象隔离，且各自 event 与故障字段符合对应页数据。
function TestPmbusPDC4000D12LCProtocol:test_get_health_event_page_cache_isolated()
    local pmbus = require 'device_tree.adapters.power_mgmt.protocol.pmbus_PDC4000D12_LC'.new(new_protocol_psu_slot())

    local status_by_page = {
        [PAGE_PRIMARY] = {
            input = 0x90,
            temper = 0x00,
            vout = 0x00,
            iout = 0x00,
            cml = 0x11,
            other = 0x22,
            mfr = 0x33
        },
        [PAGE_SECONDARY] = {
            input = 0x00,
            temper = 0xf0,
            vout = 0x90,
            iout = 0x88,
            cml = 0x44,
            other = 0x55,
            mfr = 0x66
        }
    }

    local current_page = PAGE_PRIMARY
    pmbus.get_status_input = function()
        return status_by_page[current_page].input
    end
    pmbus.get_status_temperature = function()
        return status_by_page[current_page].temper
    end
    pmbus.get_status_vout = function()
        return status_by_page[current_page].vout
    end
    pmbus.get_status_iout = function()
        return status_by_page[current_page].iout
    end
    pmbus.get_status_cml = function()
        return status_by_page[current_page].cml
    end
    pmbus.get_status_other = function()
        return status_by_page[current_page].other
    end
    pmbus.get_specific = function()
        return status_by_page[current_page].mfr
    end

    current_page = PAGE_PRIMARY
    local health_event_a, failed_flag_a = pmbus:get_health_event(current_page)
    current_page = PAGE_SECONDARY
    local health_event_b, failed_flag_b = pmbus:get_health_event(current_page)

    lu.assertEquals(failed_flag_a, false)
    lu.assertEquals(failed_flag_b, false)
    lu.assertFalse(rawequal(health_event_a, health_event_b))
    lu.assertEquals(health_event_a.input_voltage_fault, 0x90)
    lu.assertEquals(health_event_a.event, 0x10)
    lu.assertEquals(health_event_b.output_voltage_fault, 0x90)
    lu.assertEquals(health_event_b.output_current_fault, 0x88)
    lu.assertEquals(health_event_b.event, 0x0e)
end

-- Given: 同一页首次读取成功，随后某子寄存器读取失败。
-- When: 连续两次调用 get_health_event(PAGE_PRIMARY)。
-- Then: 第二次 failed_flag=true，失败字段保持上次缓存值，不被清空。
local vout_ok = true
function TestPmbusPDC4000D12LCProtocol:test_get_health_event_keep_cache_when_read_failed()
    local pmbus = require 'device_tree.adapters.power_mgmt.protocol.pmbus_PDC4000D12_LC'.new(new_protocol_psu_slot())

    pmbus.get_status_input = function()
        return 0x00
    end
    pmbus.get_status_temperature = function()
        return 0x00
    end
    pmbus.get_status_vout = function()
        if vout_ok then
            return 0x90
        end
        error('read vout failed')
    end
    pmbus.get_status_iout = function()
        return 0x00
    end
    pmbus.get_status_cml = function()
        return 0x01
    end
    pmbus.get_status_other = function()
        return 0x02
    end
    pmbus.get_specific = function()
        return 0x03
    end

    local health_ok, failed_ok = pmbus:get_health_event(PAGE_PRIMARY)
    lu.assertEquals(failed_ok, false)
    lu.assertEquals(health_ok.output_voltage_fault, 0x90)
    lu.assertEquals(health_ok.event, 0x02)

    vout_ok = false
    local health_failed, failed_flag = pmbus:get_health_event(PAGE_PRIMARY)
    lu.assertEquals(failed_flag, true)
    lu.assertEquals(health_failed.output_voltage_fault, 0x90)
    lu.assertEquals(health_failed.event, 0x02)
end

-- Given: 输入/温度/输出电压/输出电流故障位都命中告警条件。
-- When: 调用 build_event_from_health_event。
-- Then: event 组合结果为 0x1e。
function TestPmbusPDC4000D12LCProtocol:test_build_event_from_health_event()
    local pmbus = require 'device_tree.adapters.power_mgmt.protocol.pmbus_PDC4000D12_LC'.new(new_protocol_psu_slot())

    local event = pmbus:build_event_from_health_event({
        input_voltage_fault = 0x90,
        temper_fault = 0xf0,
        output_voltage_fault = 0x90,
        output_current_fault = 0x88
    })
    lu.assertEquals(event, 0x1e)
end

-- Given: set_page_with_retry 最大重试次数为 3。
-- When: 分别测试“始终失败”与“第2次成功”两种情况。
-- Then: 失败路径重试3次后返回失败；成功路径返回 E_OK 且失败次数为1。
function TestPmbusPDC4000D12LCMonitor:test_set_page_with_retry()
    psu, protocol = new_monitor_env()

    local call_count = 0
    protocol.set_page = function(self, page)
        call_count = call_count + 1
        return false
    end
    local ret, retries = psu:set_page_with_retry(PAGE_PRIMARY, 3)
    lu.assertEquals(ret, '')
    lu.assertEquals(retries, 3)
    lu.assertEquals(call_count, 3)

    call_count = 0
    protocol.set_page = function(self, page)
        call_count = call_count + 1
        return call_count >= 2
    end
    ret, retries = psu:set_page_with_retry(PAGE_PRIMARY, 3)
    lu.assertEquals(ret, nil)
    lu.assertEquals(retries, 1)
    lu.assertEquals(call_count, 2)
end

-- Given: A页切换成功，B页切换持续失败。
-- When: 连续3次调用 update_dual_output。
-- Then: scan_status bit4 清零，health 置为 get_pbit(4)。
function TestPmbusPDC4000D12LCMonitor:test_set_page_b_fail()
    psu, protocol = new_monitor_env()

    local event_calls = {}
    stub_switch_page_event(event_calls)

    protocol.set_page = function(self, page)
        if page == PAGE_SECONDARY then
            return false
        end
        return true
    end
    for _ = 1, 3 do
        psu:update_dual_output()
    end

    -- 首次调用update_dual_output会无条件调用两次event_calls
    lu.assertEquals(#event_calls, 2)
    lu.assertEquals(event_calls[2].state, 'true')
    lu.assertEquals(event_calls[2].ps_id, 1)
    lu.assertEquals(event_calls[2].page, 'B')
    lu.assertEquals(psu.scan_status & get_pbit(4), 0)
    lu.assertEquals(psu.health, get_pbit(4))
end

-- Given: A页切换持续失败，B页切换成功。
-- When: 连续3次调用 update_dual_output。
-- Then: scan_status bit0/bit5 清零，health 置为 get_pbit(4)。
function TestPmbusPDC4000D12LCMonitor:test_set_page_a_fail()
    psu, protocol = new_monitor_env()

    local event_calls = {}
    stub_switch_page_event(event_calls)

    protocol.set_page = function(self, page)
        if page == PAGE_PRIMARY then
            return false
        end
        return true
    end
    for _ = 1, 3 do
        psu:update_dual_output()
    end

    -- 首次调用update_dual_output会无条件调用两次event_calls
    lu.assertEquals(#event_calls, 2)
    lu.assertEquals(event_calls[1].state, 'true')
    lu.assertEquals(event_calls[1].ps_id, 1)
    lu.assertEquals(event_calls[1].page, 'A')
    lu.assertEquals(psu.scan_status & get_pbit(0), 0)
    lu.assertEquals(psu.scan_status & get_pbit(5), 0)
    lu.assertEquals(psu.health, get_pbit(4))
end

-- Given: 第1轮B页切换失败，第2轮B页切换恢复成功。
-- When: 连续2次调用 update_dual_output。
-- Then: 先上报 state='true' 的失败事件，再上报 state='false' 的恢复事件。
local cycle = 1
function TestPmbusPDC4000D12LCMonitor:test_set_page_b_fail_then_recover()
    psu, protocol = new_monitor_env()

    local event_calls = {}
    stub_switch_page_event(event_calls)

    protocol.set_page = function(self, page)
        if page == PAGE_SECONDARY and cycle == 1 then
            return false
        end
        return true
    end
    psu:update_dual_output()
    cycle = 2
    psu:update_dual_output()
    -- 首次调用update_dual_output会无条件调用两次event_calls
    lu.assertEquals(#event_calls, 3)
    lu.assertEquals(event_calls[2].state, 'true')
    lu.assertEquals(event_calls[2].page, 'B')
    lu.assertEquals(event_calls[3].state, 'false')
    lu.assertEquals(event_calls[3].page, 'B')
end

-- Given: A路健康读取 failed_flag 持续为 true，B路正常。
-- When: 连续3次调用 update_dual_output。
-- Then: scan_status bit0 清零，bit4 保持置位。
function TestPmbusPDC4000D12LCMonitor:test_health_a_failed()
    psu, protocol = new_monitor_env()

    protocol.get_health_event = function(self, page)
        if page == PAGE_PRIMARY then
            return new_health_event(), true
        end
        return new_health_event(), false
    end

    for _ = 1, 3 do
        psu:update_dual_output()
    end

    lu.assertEquals(psu.scan_status & get_pbit(0), 0)
    lu.assertEquals(psu.scan_status & get_pbit(4), get_pbit(4))
end

-- Given: A路健康读取先连续失败，再恢复成功。
-- When: 先执行3轮失败，再执行1轮成功。
-- Then: bit0 先被清零，恢复后重新置位。
local a_failed = true
function TestPmbusPDC4000D12LCMonitor:test_health_a_failed_then_recover()
    psu, protocol = new_monitor_env()

    protocol.get_health_event = function(self, page)
        if page == PAGE_PRIMARY and a_failed then
            return new_health_event(), true
        end
        return new_health_event(), false
    end

    for _ = 1, 3 do
        psu:update_dual_output()
    end
    lu.assertEquals(psu.scan_status & get_pbit(0), 0)

    a_failed = false
    psu:update_dual_output()
    lu.assertEquals(psu.scan_status & get_pbit(0), get_pbit(0))
end

-- Given: B路健康读取 failed_flag 持续为 true，A路正常。
-- When: 连续3次调用 update_dual_output。
-- Then: scan_status bit4 清零，bit0 保持置位。
function TestPmbusPDC4000D12LCMonitor:test_health_b_failed()
    psu, protocol = new_monitor_env()

    protocol.get_health_event = function(self, page)
        if page == PAGE_SECONDARY then
            return new_health_event(), true
        end
        return new_health_event(), false
    end

    for _ = 1, 3 do
        psu:update_dual_output()
    end

    lu.assertEquals(psu.scan_status & get_pbit(4), 0)
    lu.assertEquals(psu.scan_status & get_pbit(0), get_pbit(0))
end

-- Given: A路输出三项参数读取先持续失败，再恢复成功。
-- When: 先执行3轮失败，再执行1轮成功。
-- Then: bit5 先被清零，恢复后重新置位。
function TestPmbusPDC4000D12LCMonitor:test_output_failed()
    psu = new_monitor_env()

    psu.get_dynamic_data = function(self, prop)
        if prop == 'output_voltage' or prop == 'output_current_amps' or prop == 'output_power_watts' then
            return '', 'failed'
        end
        return nil, prop
    end
    for _ = 1, 3 do
        psu:update_dual_output()
    end
    lu.assertEquals(psu.scan_status & get_pbit(5), 0)

    psu.get_dynamic_data = function(self, prop)
        return nil, prop
    end
    psu:update_dual_output()
    lu.assertEquals(psu.scan_status & get_pbit(5), get_pbit(5))
end

-- Given: A路输出参数读取全部成功。
-- When: 调用 update_dual_output。
-- Then: 按顺序读取 output_voltage/output_current_amps/output_power_watts，bit5 置位。
function TestPmbusPDC4000D12LCMonitor:test_output_metrics_success()
    psu = new_monitor_env()

    local read_metrics = {}
    psu.get_dynamic_data = function(self, prop)
        table.insert(read_metrics, prop)
        return nil, prop
    end
    psu:update_dual_output()

    lu.assertEquals(#read_metrics, 3)
    lu.assertEquals(read_metrics[1], 'output_voltage')
    lu.assertEquals(read_metrics[2], 'output_current_amps')
    lu.assertEquals(read_metrics[3], 'output_power_watts')
    lu.assertEquals(psu.scan_status & get_pbit(5), get_pbit(5))
end

-- Given: A路健康正常，B路存在输出欠压告警。
-- When: 调用 update_dual_output。
-- Then: 合并后 event=0x02，output_voltage_fault=0x90，health=0x02。
function TestPmbusPDC4000D12LCMonitor:test_alert_a_normal_b_undervoltage()
    psu, protocol = new_monitor_env()

    protocol.get_health_event = function(self, page)
        if page == PAGE_PRIMARY then
            return new_health_event(), false
        end
        return {
            input_voltage_fault = 0x00,
            temper_fault = 0x00,
            output_voltage_fault = 0x90,
            output_current_fault = 0x00,
            cml_fault = 0x00,
            other_fault = 0x00,
            mfr_specific_status = 0x00,
            event = 0x02
        }, false
    end

    psu:update_dual_output()

    lu.assertEquals(psu.health_event.event, 0x02)
    lu.assertEquals(psu.health_event.output_voltage_fault, 0x90)
    lu.assertEquals(psu.health, 0x02)
end

-- Given: A路存在输出过流告警，B路健康正常。
-- When: 调用 update_dual_output。
-- Then: 合并后 event=0x04，output_current_fault=0x88。
function TestPmbusPDC4000D12LCMonitor:test_alert_a_overcurrent_b_normal()
    psu, protocol = new_monitor_env()

    protocol.get_health_event = function(self, page)
        if page == PAGE_PRIMARY then
            return {
                input_voltage_fault = 0x00,
                temper_fault = 0x00,
                output_voltage_fault = 0x00,
                output_current_fault = 0x88,
                cml_fault = 0x00,
                other_fault = 0x00,
                mfr_specific_status = 0x00,
                event = 0x04
            }, false
        end
        return new_health_event(), false
    end

    psu:update_dual_output()

    lu.assertEquals(psu.health_event.event, 0x04)
    lu.assertEquals(psu.health_event.output_current_fault, 0x88)
end

-- Given: A/B 两路都存在过温状态。
-- When: 调用 update_dual_output。
-- Then: temper_fault 执行按位或合并，event 保持过温告警位。
function TestPmbusPDC4000D12LCMonitor:test_alert_ab_overtemperature()
    psu, protocol = new_monitor_env()

    protocol.get_health_event = function(self, page)
        if page == PAGE_PRIMARY then
            return {
                input_voltage_fault = 0x00,
                temper_fault = 0xf0,
                output_voltage_fault = 0x00,
                output_current_fault = 0x00,
                cml_fault = 0x00,
                other_fault = 0x00,
                mfr_specific_status = 0x00,
                event = 0x08
            }, false
        end
        return {
            input_voltage_fault = 0x00,
            temper_fault = 0x80,
            output_voltage_fault = 0x00,
            output_current_fault = 0x00,
            cml_fault = 0x00,
            other_fault = 0x00,
            mfr_specific_status = 0x00,
            event = 0x08
        }, false
    end

    psu:update_dual_output()

    lu.assertEquals(psu.health_event.temper_fault, 0xf0 | 0x80)
    lu.assertEquals(psu.health_event.event, 0x08)
end

-- Given: 仅A路存在输入欠压告警，B路正常。
-- When: 调用 update_dual_output。
-- Then: input_voltage_fault=0x90，event=0x10。
function TestPmbusPDC4000D12LCMonitor:test_alert_input_undervoltage()
    psu, protocol = new_monitor_env()

    protocol.get_health_event = function(self, page)
        if page == PAGE_PRIMARY then
            return {
                input_voltage_fault = 0x90,
                temper_fault = 0x00,
                output_voltage_fault = 0x00,
                output_current_fault = 0x00,
                cml_fault = 0x00,
                other_fault = 0x00,
                mfr_specific_status = 0x00,
                event = 0x10
            }, false
        end
        return new_health_event(), false
    end

    psu:update_dual_output()

    lu.assertEquals(psu.health_event.input_voltage_fault, 0x90)
    lu.assertEquals(psu.health_event.event, 0x10)
end

-- Given: A/B 两路健康状态和输出参数读取均成功且无告警。
-- When: 调用 update_dual_output。
-- Then: event=0、health=0，scan_status 的 bit0/bit4/bit5 全部置位。
function TestPmbusPDC4000D12LCMonitor:test_ab_normal_no_alert()
    psu = new_monitor_env()

    psu:update_dual_output()

    lu.assertEquals(psu.health_event.event, 0x00)
    lu.assertEquals(psu.health, 0x00)
    lu.assertEquals(psu.scan_status & get_pbit(0), get_pbit(0))
    lu.assertEquals(psu.scan_status & get_pbit(4), get_pbit(4))
    lu.assertEquals(psu.scan_status & get_pbit(5), get_pbit(5))
end

-- Given: A/B 两路健康读取都持续失败。
-- When: 连续3次调用 update_dual_output。
-- Then: process_communication_status 接收到的 HEALTH_UPDATE_FAIL 标记按 0,0,1 变化。
function TestPmbusPDC4000D12LCMonitor:test_process_communication_status_all_health_failed()
    psu, protocol = new_monitor_env()

    local comm_flags = {}
    psu.process_communication_status = function(self, err_type, flag)
        table.insert(comm_flags, {err_type = err_type, flag = flag})
    end

    protocol.get_health_event = function(self, page)
        return new_health_event(), true
    end

    for _ = 1, 3 do
        psu:update_dual_output()
    end

    lu.assertEquals(#comm_flags, 3)
    lu.assertEquals(comm_flags[1].flag, 0)
    lu.assertEquals(comm_flags[2].flag, 0)
    lu.assertEquals(comm_flags[3].flag, 1)
end

-- Given: communication_status 依赖健康失败与功率失败的组合条件。
-- When: 先注入功率失败，再注入健康失败，再恢复健康成功。
-- Then: 先清除 bit1 并将 input_power_watts 置0；随后 communication_status 变为1，再恢复为0。
function TestPmbusPDC4000D12LCMonitor:test_process_communication_status_need_power_and_health_failed()
    psu = new_monitor_env()

    psu.scan_status = get_pbit(1)
    psu:process_communication_status(1, 1)
    lu.assertEquals(psu.communication_status, nil)
    lu.assertEquals(psu.scan_status & get_pbit(1), 0)
    lu.assertEquals(psu.input_power_watts, 0)

    psu:process_communication_status(0, 1)
    lu.assertEquals(psu.communication_status, 1)

    psu:process_communication_status(0, 0)
    lu.assertEquals(psu.communication_status, 0)
end

-- Given: A路输入故障字段缺失，B路输入故障字段存在。
-- When: 调用 update_dual_output。
-- Then: input_voltage_fault 从B路回退获取，event 取0x10。
function TestPmbusPDC4000D12LCMonitor:test_input_fault_use_b_when_a_nil()
    psu, protocol = new_monitor_env()

    protocol.get_health_event = function(self, page)
        if page == PAGE_PRIMARY then
            return {event = 0x00}, false
        end
        return {input_voltage_fault = 0x90, event = 0x10}, false
    end

    psu:update_dual_output()

    lu.assertEquals(psu.health_event.input_voltage_fault, 0x90)
    lu.assertEquals(psu.health_event.event, 0x10)
end

-- Given: 上一轮 health_event 已有值，本轮A/B同字段都为空。
-- When: 调用 update_dual_output。
-- Then: 本轮 health_event 各字段回退保持上一轮值，health 同步为上一轮 event。
function TestPmbusPDC4000D12LCMonitor:test_merge_with_prev_when_ab_field_nil()
    psu, protocol = new_monitor_env()

    psu.health_event = {
        input_voltage_fault = 0x90,
        temper_fault = 0xa0,
        output_voltage_fault = 0x80,
        output_current_fault = 0x08,
        cml_fault = 0x11,
        other_fault = 0x22,
        mfr_specific_status = 0x33,
        event = 0x12
    }
    protocol.get_health_event = function(self, page)
        return {}, false
    end

    psu:update_dual_output()

    lu.assertEquals(psu.health_event.input_voltage_fault, 0x90)
    lu.assertEquals(psu.health_event.temper_fault, 0xa0)
    lu.assertEquals(psu.health_event.output_voltage_fault, 0x80)
    lu.assertEquals(psu.health_event.output_current_fault, 0x08)
    lu.assertEquals(psu.health_event.cml_fault, 0x11)
    lu.assertEquals(psu.health_event.other_fault, 0x22)
    lu.assertEquals(psu.health_event.mfr_specific_status, 0x33)
    lu.assertEquals(psu.health_event.event, 0x12)
    lu.assertEquals(psu.health, 0x12)
end
