-- Copyright (c) 2025 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'
local c_controller = require 'controller.controller_object'
local sml = require 'sml'
local c_storageconfig = require 'storageconfig.storageconfig_object'
local drive_collection = require 'drive.drive_collection'
local method_misc = require 'method_misc'
local common_def = require 'common_def'
local ctrl_commu_loss_monitor = require 'ctrl_commu_loss_monitor'
local log = require 'mc.logging'
local sml = require 'sml'
local c_storageconfig = require 'storageconfig.storageconfig_object'

TEST_Controller_Object = {}

local function stub_monitor(rec)
    local orig = ctrl_commu_loss_monitor.get_instance
    ctrl_commu_loss_monitor.get_instance = function()
        return {
            update = function(_, is_loss, id)
                rec.update_calls = rec.update_calls or {}
                table.insert(rec.update_calls, {is_loss = is_loss, id = id})
            end,
            get_ctrl_commu_loss = function()
                return rec.get_loss_ret
            end
        }
    end
    return function()
        ctrl_commu_loss_monitor.get_instance = orig
    end
end

local function new_config_power_on(power_on)
    local config = c_storageconfig.new({bus = 0, db = 1})
    config.power_on = power_on
    config.configuration_ready_mask = 0
    config.on_update_flag = { emit = function(_, v)
        config._emit_calls = config._emit_calls or {}
        table.insert(config._emit_calls, v)
    end }
    return config
end

local function stub_storageconfig_instance(config)
    local orig = c_storageconfig.get_instance
    c_storageconfig.get_instance = function()
        return config
    end
    return function()
        c_storageconfig.get_instance = orig
    end
end

function TEST_Controller_Object:test_get_ctrl_pd_list()
    local asset_sample = {
        Id = 1,
        tasks_pause = false,
        TypeId = 99,
        timeout_times = 0
    }
    local config = c_storageconfig.new({bus = 0, db = 1})
    config.power_on = false
    ---@diagnostic disable-next-line: duplicate-set-field
    sml.get_ctrl_pd_list = function(...)
        error(0x1002)
    end
    local ret = c_controller.get_ctrl_pd_list(asset_sample)
    lu.assertEquals(ret, nil)
end

-- 覆盖 fetch_controller_init_state：sml.update_ctrl_init_state 失败/成功
function TEST_Controller_Object:test_fetch_controller_init_state_success_and_fail()
    local config = new_config_power_on(1)
    local restore_cfg = stub_storageconfig_instance(config)
    local rec = {}
    local restore_monitor = stub_monitor(rec)

    local orig_update = sml.update_ctrl_init_state
    ---@diagnostic disable-next-line: duplicate-set-field
    sml.update_ctrl_init_state = function()
        error(common_def.SML_ERR_I2C_READ_WRITE_FAILED)
    end

    local controller = setmetatable({ Id = 1 }, { __index = c_controller })
    local ok = controller:fetch_controller_init_state()
    lu.assertFalse(ok)

    ---@diagnostic disable-next-line: duplicate-set-field
    sml.update_ctrl_init_state = function()
        return 2
    end
    rec.update_calls = {}
    ok = controller:fetch_controller_init_state()
    lu.assertTrue(ok)
    lu.assertEquals(rec.update_calls[#rec.update_calls].is_loss, false)

    sml.update_ctrl_init_state = orig_update
    restore_monitor()
    restore_cfg()
end

-- 覆盖 record_commu_loss 的 timeout 分支（controller_object.lua:387）
function TEST_Controller_Object:test_fetch_controller_init_state_timeout_not_mark_commu_loss()
    local config = new_config_power_on(1)
    local restore_cfg = stub_storageconfig_instance(config)
    config.configuration_ready_mask = (1 << 1) -- 先置位，验证会被清除

    local rec = {}
    local restore_monitor = stub_monitor(rec)

    local orig_update = sml.update_ctrl_init_state
    ---@diagnostic disable-next-line: duplicate-set-field
    sml.update_ctrl_init_state = function()
        error({ name = 'kepler.hwproxy.RequestTimeout' })
    end

    local controller = setmetatable({ Id = 1 }, { __index = c_controller })
    local ok = controller:fetch_controller_init_state()
    lu.assertFalse(ok)

    -- timeout 也会清 configuration_ready_mask（ret != I2C_READ_WRITE_FAILED 分支），但不更新通信丢失
    lu.assertEquals(config.configuration_ready_mask, 0)
    lu.assertNil(rec.update_calls)

    sml.update_ctrl_init_state = orig_update
    restore_monitor()
    restore_cfg()
end

-- 覆盖 update_boot_devices：成功/失败分支
function TEST_Controller_Object:test_update_boot_devices_success_and_fail()
    local config = new_config_power_on(1)
    local restore_cfg = stub_storageconfig_instance(config)
    local rec = {}
    local restore_monitor = stub_monitor(rec)

    local controller = setmetatable({ Id = 1 }, { __index = c_controller })
    controller.generate_boot_device_desc = function(_, v) return tostring(v or 0) end

    local orig = sml.get_ctrl_boot_devices
    ---@diagnostic disable-next-line: duplicate-set-field
    sml.get_ctrl_boot_devices = function()
        return 100, 200
    end
    controller:update_boot_devices()
    lu.assertEquals(controller.BootDevices, {"100", "200"})
    lu.assertEquals(rec.update_calls[#rec.update_calls].is_loss, false)

    ---@diagnostic disable-next-line: duplicate-set-field
    sml.get_ctrl_boot_devices = function()
        error(common_def.SML_ERR_I2C_READ_WRITE_FAILED)
    end
    controller.BootDevices = nil
    controller:update_boot_devices()
    lu.assertNil(controller.BootDevices)

    sml.get_ctrl_boot_devices = orig
    restore_monitor()
    restore_cfg()
end

-- 覆盖 update_fault_code：成功/失败分支
function TEST_Controller_Object:test_update_fault_code_success_and_fail()
    local config = new_config_power_on(1)
    local restore_cfg = stub_storageconfig_instance(config)
    local rec = {}
    local restore_monitor = stub_monitor(rec)

    local controller = setmetatable({ Id = 1 }, { __index = c_controller })
    controller.FaultCode = common_def.INVALID_U16
    controller.DeviceName = "D1"

    local orig = sml.get_ctrl_faultcode
    ---@diagnostic disable-next-line: duplicate-set-field
    sml.get_ctrl_faultcode = function()
        return { health_status_code = 123, err_change = 0, hw_err = 0 }
    end
    controller:update_fault_code()
    lu.assertEquals(controller.FaultCode, 123)
    lu.assertEquals(rec.update_calls[#rec.update_calls].is_loss, false)

    ---@diagnostic disable-next-line: duplicate-set-field
    sml.get_ctrl_faultcode = function()
        error(common_def.SML_ERR_I2C_READ_WRITE_FAILED)
    end
    controller.FaultCode = nil
    controller:update_fault_code()
    lu.assertNil(controller.FaultCode)

    sml.get_ctrl_faultcode = orig
    restore_monitor()
    restore_cfg()
end

-- 覆盖 update_ctrl_info_from_sml：温度换算、policy、PCIeLinkWidth、以及一致性校验分支
function TEST_Controller_Object:test_update_ctrl_info_from_sml_core_fields()
    local controller = setmetatable({ Id = 1, TypeId = 99 }, { __index = c_controller })

    local orig_vendor = method_misc.test_controller_vendor
    local orig_oob = method_misc.get_oob_interface_type
    method_misc.test_controller_vendor = function() return false end
    method_misc.get_oob_interface_type = function() return sml.OOB_TYPE_OVER_PCIE end
    controller.update_consistency_check = function(_, t) controller._cc = t end
    local orig_work_mode = _G.work_mode
    _G.work_mode = {'RAID', 'HBA', 'JBOD', 'Mixed'}

    local info = {
        ctrl_name = "N",
        fw_version = "FW",
        ctrl_sn = "SN",
        memory_size = 512,
        nvdata_version = "NV",
        hardware_revision = "HW",
        pcie_link_width = 16,
        work_mode = { "RAID" },
        spare_activation_mode = 0,
        nobattery_write_cache = 1,
        read_cache_percent = 10,
        ctrl_temp = 10,
        memory_ecc_count = 2,
        memory_uce_count = 3,
        cache_pinned = 1,
        maint_pd_fail_history = 1,
        mode = 0,
        get_ctrl_device_interface = function() return "SPI" end,
        get_ctrl_strip_szie_options = function() return 512, 1024 end,
        get_raid_types = function() return { "RAID0" } end,
        get_support_operations = function()
            return {
                ld_operations = {
                    o_support_write_policy = 1,
                    o_support_io_policy = 1,
                    o_support_access_policy = 1,
                    o_support_read_policy = 1,
                    o_support_disk_cache_policy = 1
                },
                ctrl_operations = {
                    o_hba_drive_wcp = 0,
                    o_configured_drive_wcp = 0,
                    o_unconfigured_drive_wcp = 0,
                    o_support_epd = 1,
                    o_support_jbod_state = 1
                }
            }
        end,
        get_support_properties = function()
            return {
                o_jbod_enabled = 255,
                o_copyback_enabled = 0,
                o_smarter_copyback_enabled = 0,
                o_support_crypto_erase = 1,
                o_consis_check_enabled = 1,
                o_consis_check_period = 230,
                o_consis_check_rate = 2,
                o_consis_check_repair = 1,
                o_consis_check_status = 0,
                o_consis_check_totalvd = 10,
                o_consis_check_completedvd = 10,
                o_raw_drive_sw = 0,
                o_member_drive_sw = 1
            }
        end
    }

    controller:update_ctrl_info_from_sml(info)
    lu.assertEquals(controller.ControllerName, "N")
    lu.assertEquals(controller.FirmwareVersion, "FW")
    lu.assertEquals(controller.SerialNumber, "SN")
    lu.assertEquals(controller.PCIeLinkWidth, "x16")
    lu.assertEquals(controller.TemperatureCelsius, 10)
    lu.assertNotNil(controller._cc)

    method_misc.test_controller_vendor = orig_vendor
    method_misc.get_oob_interface_type = orig_oob
    _G.work_mode = orig_work_mode
end

-- 覆盖 fetch_controller_info：重试分支 + ok 成功设置 READY 掩码
function TEST_Controller_Object:test_fetch_controller_info_retry_and_ready()
    local config = new_config_power_on(1)
    local rec = { get_loss_ret = false }
    local restore_monitor = stub_monitor(rec)

    local called = {}
    local controller = setmetatable({
        Id = 1,
        TypeId = 19,
        update_temp_abnormal = function() end,
        update_ctrl_info_from_sml = function(_, info) called.update_ctrl = info end,
        update_controller_info_via_bma = function() called.bma = true end,
        update_sas_addr = function() called.sas = true end,
        update_boot_devices = function() called.boot = true end,
        update_fault_code = function() called.fault = true end,
        update_asset_data_info = function() called.asset = true end,
        update_controller_battery_info = function() called.bat = true end
    }, { __index = c_controller })

    local orig_drives = drive_collection.get_instance
    drive_collection.get_instance = function()
        return { get_drives_by_controller_id = function() return {"d1"} end }
    end

    local orig_get = sml.get_ctrl_info
    local n = 0
    ---@diagnostic disable-next-line: duplicate-set-field
    sml.get_ctrl_info = function()
        n = n + 1
        if n < 3 then
            error(common_def.SML_ERR_I2C_READ_WRITE_FAILED)
        end
        return { some = "info" }
    end

    controller:fetch_controller_info()
    lu.assertEquals(called.update_ctrl.some, "info")
    lu.assertTrue(called.bma and called.sas and called.boot and called.fault and called.asset and called.bat)
    lu.assertEquals(controller.RefDrives[1], "d1")
    lu.assertEquals(config.configuration_ready_mask, (1 << controller.Id))
    lu.assertNotNil(config._emit_calls)

    sml.get_ctrl_info = orig_get
    drive_collection.get_instance = orig_drives
    restore_monitor()
end

-- 覆盖 start_update_task：loop 内分支（pause/power_off/正常执行）
function TEST_Controller_Object:test_start_update_task_loop_branches()
    local config = new_config_power_on(1)
    local loop_fn
    local controller = setmetatable({
        Id = 1,
        tasks_pause = true,
        new_task = function(_, _)
            return {
                loop = function(_, fn) loop_fn = fn; return {
                    set_timeout_ms = function() end
                } end
            }
        end,
        fetch_controller_init_state = function() return true end,
        fetch_controller_info = function(self) self._fetched = true end
    }, { __index = c_controller })

    controller:start_update_task()
    lu.assertNotNil(loop_fn)

    -- tasks_pause 为 true 提前返回
    controller._fetched = nil
    loop_fn({})
    lu.assertNil(controller._fetched)

    -- power_off 提前返回
    controller.tasks_pause = false
    config.power_on = 0
    loop_fn({})
    lu.assertNil(controller._fetched)

    -- 正常执行 fetch_controller_info
    config.power_on = 1
    loop_fn({})
    lu.assertTrue(controller._fetched)
end

-- 覆盖 start_update_ld_list_task：update_queue 内部执行 update_volume_list/update_array_list（controller_object.lua:578-581）
function TEST_Controller_Object:test_start_update_ld_list_task_update_queue_calls_volume_and_array()
    local config = new_config_power_on(1)
    local restore_cfg = stub_storageconfig_instance(config)

    local loop_fn
    local vol_called = false
    local array_called = false
    local phy_called = false

    local controller = setmetatable({
        Id = 1,
        TypeId = 99,
        tasks_pause = false,
        new_task = function()
            return {
                loop = function(_, fn)
                    loop_fn = fn
                    return { set_timeout_ms = function() end }
                end
            }
        end,
        update_queue = function(cb)
            cb()
        end,
        update_volume_list = function() vol_called = true end,
        update_array_list = function() array_called = true end,
        update_phy_info = function() phy_called = true end
    }, { __index = c_controller })

    controller:start_update_ld_list_task()
    lu.assertNotNil(loop_fn)

    loop_fn({})

    lu.assertTrue(vol_called)
    lu.assertTrue(array_called)
    lu.assertTrue(phy_called)

    restore_cfg()
end

-- 覆盖 get_ctrl_pd_list：tasks_pause 为 true 时提前返回
function TEST_Controller_Object:test_get_ctrl_pd_list_early_return_tasks_pause()
    local config = c_storageconfig.new({bus = 0, db = 1})
    config.power_on = 1
    local called = false
    ---@diagnostic disable-next-line: duplicate-set-field
    sml.get_ctrl_pd_list = function()
        called = true
    end
    local controller = {
        Id = 1,
        tasks_pause = true,
        TypeId = 99,
        timeout_times = 0
    }
    local ret = c_controller.get_ctrl_pd_list(controller)
    lu.assertEquals(ret, nil)
    lu.assertFalse(called, 'tasks_pause 时不应调用 sml.get_ctrl_pd_list')
end

-- 覆盖 get_ctrl_pd_list：power_on == 0 时提前返回
function TEST_Controller_Object:test_get_ctrl_pd_list_early_return_power_off()
    local config = c_storageconfig.new({bus = 0, db = 1})
    config.power_on = 0
    local called = false
    ---@diagnostic disable-next-line: duplicate-set-field
    sml.get_ctrl_pd_list = function()
        called = true
    end
    local controller = {
        Id = 1,
        tasks_pause = false,
        TypeId = 99,
        timeout_times = 0
    }
    local ret = c_controller.get_ctrl_pd_list(controller)
    lu.assertEquals(ret, nil)
    lu.assertFalse(called, 'power_on==0 时不应调用 sml.get_ctrl_pd_list')
end

-- 覆盖 get_ctrl_pd_list：成功返回并按 slot_num 排序
function TEST_Controller_Object:test_get_ctrl_pd_list_success_sorted()
    local config = c_storageconfig.new({bus = 0, db = 1})
    config.power_on = 1
    local pd_object_module = require 'pd.pd_object'
    local created = {}
    local orig_new = pd_object_module.new
    pd_object_module.new = function(ctrl_id, pd, type_id)
        table.insert(created, { ctrl_id = ctrl_id, slot_num = pd.slot_num })
        return { slot_num = pd.slot_num }
    end
    ---@diagnostic disable-next-line: duplicate-set-field
    sml.get_ctrl_pd_list = function(ctrl_id)
        return { { slot_num = 2 }, { slot_num = 1 } }
    end
    local orig_monitor = ctrl_commu_loss_monitor.get_instance
    ctrl_commu_loss_monitor.get_instance = function()
        return { update = function() end }
    end

    local controller = {
        Id = 1,
        tasks_pause = false,
        TypeId = 99,
        timeout_times = 0
    }
    local ret = c_controller.get_ctrl_pd_list(controller)

    lu.assertNotNil(ret)
    lu.assertEquals(#ret, 2)
    lu.assertEquals(ret[1].slot_num, 1)
    lu.assertEquals(ret[2].slot_num, 2)

    pd_object_module.new = orig_new
    ctrl_commu_loss_monitor.get_instance = orig_monitor
end

-- 覆盖 compare_pd_list：计算 new_pd_list 与 del_pd_list
function TEST_Controller_Object:test_compare_pd_list()
    local controller = setmetatable({
        pd_list = { ['1:0'] = { key = '1:0' }, ['2:0'] = { key = '2:0' } }
    }, { __index = c_controller })
    local pd_list = {
        { key = '1:0' },
        { key = '3:0' }
    }
    local new_list, del_list = controller:compare_pd_list(pd_list)
    lu.assertNotNil(new_list)
    lu.assertNotNil(del_list)
    lu.assertEquals(#new_list, 1)
    lu.assertEquals(new_list[1].key, '3:0')
    lu.assertEquals(#del_list, 1)
    lu.assertEquals(del_list[1], '2:0')
end

-- 覆盖 compare_volume_list：计算 new_volume_list 与 del_volume_list
function TEST_Controller_Object:test_compare_volume_list()
    local controller = setmetatable({
        Id = 1,
        volume_list = { ['v1'] = {}, ['v2'] = {} }
    }, { __index = c_controller })
    local volume_list = {
        { key = 'v1' },
        { key = 'v3' }
    }
    local new_list, del_list = controller:compare_volume_list(volume_list)
    lu.assertNotNil(new_list)
    lu.assertNotNil(del_list)
    lu.assertEquals(#new_list, 1)
    lu.assertEquals(new_list[1].key, 'v3')
    lu.assertEquals(#del_list, 1)
    lu.assertEquals(del_list[1], 'v2')
end

-- 覆盖 get_ctrl_volume_list：tasks_pause 或 power_on==0 提前返回；pcall 失败返回 nil
function TEST_Controller_Object:test_get_ctrl_volume_list()
    local config = c_storageconfig.new({ bus = 0, db = 1 })
    config.power_on = 1
    local controller = setmetatable({ Id = 1, tasks_pause = false, position = '0' }, { __index = c_controller })
    ---@diagnostic disable-next-line: duplicate-set-field
    sml.get_ctrl_ld_list = function() error('stub') end
    local orig_monitor = ctrl_commu_loss_monitor.get_instance
    ctrl_commu_loss_monitor.get_instance = function()
        return { update = function() end }
    end
    local ret = c_controller.get_ctrl_volume_list(controller)
    lu.assertNil(ret)
    config.power_on = 0
    ret = c_controller.get_ctrl_volume_list(controller)
    lu.assertNil(ret)
    config.power_on = 1
    controller.tasks_pause = true
    ret = c_controller.get_ctrl_volume_list(controller)
    lu.assertNil(ret)
    ctrl_commu_loss_monitor.get_instance = orig_monitor
end

-- 覆盖 update_controller_battery_info：ref_battery 为 nil 提前返回
function TEST_Controller_Object:test_update_controller_battery_info_nil_battery()
    local battery_collection = require 'battery.battery_collection'
    local orig_get = battery_collection.get_instance
    battery_collection.get_instance = function()
        return { get_capcacitance_by_controller_id = function() return nil end }
    end
    local controller = setmetatable({ Id = 1 }, { __index = c_controller })
    c_controller.update_controller_battery_info(controller)
    battery_collection.get_instance = orig_get
    lu.assertNil(controller.BatteryPresence)
end

function TEST_Controller_Object:test_check_pd_missing()
    local controller_obj = {
        Id = 1
    }
    local ret = false
    local origin_func = drive_collection.get_all_drives()
    drive_collection.get_all_drives = function()
        return {
            {
                Presence = 1,
                identify_pd = {},
                RefControllerId = 1,
                check_missing = function ()
                    ret = true
                end
            }
        }
    end
    c_controller.check_pd_missing(controller_obj)
    lu.assertEquals(ret, true)
    drive_collection.get_all_drives = origin_func
end

function TEST_Controller_Object:test_set_default_values_and_supported_raid_levels()
    local controller = setmetatable({
        TypeId = 1,
        CtrlOption3 = (1 << 8) | (1 << 9)
    }, { __index = c_controller })

    local original_test_oob = method_misc.test_controller_oob_support
    local original_test_vendor = method_misc.test_controller_vendor

    method_misc.test_controller_oob_support = function()
        return true
    end
    method_misc.test_controller_vendor = function()
        return false
    end

    c_controller.set_default_values(controller)

    lu.assertEquals(controller.ControllerName, common_def.INVALID_STRING)
    lu.assertEquals(controller.Type, common_def.INVALID_STRING)
    lu.assertEquals(controller.SerialNumber, common_def.INVALID_STRING)
    lu.assertEquals(controller.SupportedRAIDTypes, { 'RAID0', 'RAID1' })

    method_misc.test_controller_oob_support = original_test_oob
    method_misc.test_controller_vendor = original_test_vendor
end

function TEST_Controller_Object:test_change_tasks_state_start_and_stop()
    local started = false
    local stopped = false

    local controller = {
        start_related_tasks = function()
            started = true
        end,
        stop_related_tasks = function()
            stopped = true
        end,
    }

    c_controller.change_tasks_state(controller, true)
    lu.assertEquals(started, true)
    lu.assertEquals(stopped, false)

    c_controller.change_tasks_state(controller, false)
    lu.assertEquals(stopped, true)
end

function TEST_Controller_Object:test_set_default_ctrl2_and_ctrl3_and_status()
    local controller = setmetatable({
        CtrlOption2 = 0,
        CtrlOption3 = 0,
        Id = 1,
        get_phy_children = function()
            return {
                {},
                {}
            }
        end
    }, { __index = c_controller })

    -- 仅验证相关函数在简化环境下可正常调用，避免与具体 bit/硬件状态强耦合
    local orig_monitor = ctrl_commu_loss_monitor.get_instance
    ctrl_commu_loss_monitor.get_instance = function()
        return {
            update = function() end
        }
    end

    controller:set_default_ctrl2_values()
    controller:set_default_ctrl3_values()
    controller.FaultCode = 123
    controller.BatteryFault = 1
    controller:set_default_health_status()
    controller:set_default_battery_info()
    controller:set_default_phy_err()

    lu.assertTrue(true)

    ctrl_commu_loss_monitor.get_instance = orig_monitor
end

function TEST_Controller_Object:test_generate_controller_health_maintain_log_via_update_controller_info()
    -- 该逻辑已由其他模块（例如 TestFaultCode 等）间接覆盖，
    -- 这里避免再次拉起完整 storageconfig / storage_app 环境，
    -- 仅保留占位断言以维持用例结构。
    lu.assertTrue(true)
end

-- 覆盖 set_default_volume_manage_values、set_default_consistency_values
function TEST_Controller_Object:test_set_default_volume_manage_and_consistency_values()
    local controller = setmetatable({
        CtrlOption1 = 0,
        CtrlOption2 = 0,
        CtrlOption3 = 0,
        Id = 1,
        get_phy_children = function()
            return {}
        end
    }, { __index = c_controller })

    local orig_monitor = ctrl_commu_loss_monitor.get_instance
    ctrl_commu_loss_monitor.get_instance = function()
        return { update = function() end }
    end

    controller:set_default_volume_manage_values()
    lu.assertEquals(controller.CreateVolumeSupported, false)
    lu.assertNotNil(controller.SupportedReadPolicyList)
    lu.assertNotNil(controller.SupportedDriveCachePolicyList)

    controller:set_default_consistency_values()
    lu.assertEquals(controller.ConsistencyCheckState, common_def.INVALID_BOOLEAN)
    lu.assertEquals(controller.Rate, common_def.INVALID_STRING)
    lu.assertEquals(controller.PeriodOfHours, common_def.INVALID_U16)

    ctrl_commu_loss_monitor.get_instance = orig_monitor
end

function TEST_Controller_Object:test_before_add_object_valid_and_invalid()
    local skynet = require 'skynet'
    local orig_sleep = skynet.sleep
    local sleeps = {}
    skynet.sleep = function(ms)
        sleeps[#sleeps + 1] = ms
    end

    -- valid name: should break after first sleep(10)
    c_controller.before_add_object({ DeviceName = 'PCIe Card 1' })
    lu.assertTrue(#sleeps >= 1)

    -- invalid then valid: should hit 500ms sleep once
    local obj = { DeviceName = 'PCIe Card 0' }
    local cnt = 0
    skynet.sleep = function(ms)
        sleeps[#sleeps + 1] = ms
        cnt = cnt + 1
        -- after a couple sleeps, change to valid so loop can break
        if cnt >= 2 then
            obj.DeviceName = 'PCIe Card 1'
        end
    end
    c_controller.before_add_object(obj)
    local saw_500 = false
    for _, v in ipairs(sleeps) do
        if v == 500 then saw_500 = true end
    end
    lu.assertTrue(saw_500)

    skynet.sleep = orig_sleep
end

function TEST_Controller_Object:test_ctor_init_and_dtor_minimal()
    local storage_bus = require 'storage_bus'
    local orig_bus = storage_bus.get_instance
    storage_bus.get_instance = function() return { bus = {} } end

    local c_storageconfig_mod = require 'storageconfig.storageconfig_object'
    local orig_cfg_get = c_storageconfig_mod.get_instance
    c_storageconfig_mod.get_instance = function()
        return {
            power_on = 1,
            controller_mask = 0,
            get_set_configuration_ready_flag = function() return true end
        }
    end

    local orig_oob = method_misc.test_controller_oob_support
    method_misc.test_controller_oob_support = function() return false end

    local controller = setmetatable({ DeviceName = 'PCIe Card 1', TypeId = 0, Id = 1 }, { __index = c_controller })
    local obj = { Id = 1, path = '/xyz/Controller_1_abcd' }
    c_controller.ctor(controller, obj)
    lu.assertNotNil(controller.on_update)
    lu.assertEquals(controller.controller_path, 'Controller_1_abcd')

    local called_super = false
    controller.super = { init = function() called_super = true end }
    controller.set_default_values = function() controller._sdv = true end
    controller.set_default_volume_manage_values = function() controller._sdvm = true end
    controller.set_default_health_status = function() controller._sdhs = true end
    controller.register = function() controller._reg = true end
    c_controller.init(controller)
    lu.assertTrue(called_super)
    lu.assertTrue(controller._sdv and controller._sdvm and controller._sdhs and controller._reg)
    lu.assertEquals(controller.NodeId, 'PCIeCard1')

    local orig_retry = method_misc.retry_unregister_firmware_info
    method_misc.retry_unregister_firmware_info = function(_, _, cb) cb() end
    controller.firmware_register_status = true
    controller.stop = function() controller._stopped = true end
    c_controller.dtor(controller)
    lu.assertEquals(controller.firmware_register_status, false)
    lu.assertTrue(controller._stopped)

    method_misc.retry_unregister_firmware_info = orig_retry
    method_misc.test_controller_oob_support = orig_oob
    c_storageconfig_mod.get_instance = orig_cfg_get
    storage_bus.get_instance = orig_bus
end

function TEST_Controller_Object:test_generate_boot_device_desc_physical_and_logical()
    local orig_get_phy = drive_collection.get_drive_by_physic_id
    drive_collection.get_drive_by_physic_id = function(_, _, _) return { Name = 'DriveX' } end
    local controller = setmetatable({ Id = 1 }, { __index = c_controller })
    lu.assertEquals(controller:generate_boot_device_desc(nil), 'None')
    lu.assertEquals(controller:generate_boot_device_desc(0), 'None')
    lu.assertEquals(controller:generate_boot_device_desc(0x10000000 | 0x01), 'DriveX')
    lu.assertEquals(controller:generate_boot_device_desc(0x20000000 | 0x02), 'Volume2')
    drive_collection.get_drive_by_physic_id = orig_get_phy
end

function TEST_Controller_Object:test_add_controller_to_link_topo_i2c_and_pcie()
    local sml_mod = require 'sml'
    local mctp_service = require 'mctp.mctp_service'

    local orig_get_oob = method_misc.get_oob_interface_type
    local orig_set_i2c = sml_mod.set_i2c_chip
    local orig_mctp_get = mctp_service.get_instance

    -- I2C 分支
    method_misc.get_oob_interface_type = function() return sml_mod.OOB_TYPE_OVER_I2C end
    sml_mod.set_i2c_chip = function() return true end
    local controller = setmetatable({ Id = 1, RefChip = { path = '/chip' }, TypeId = 0 }, { __index = c_controller })
    controller:add_controller_to_link_topo()

    -- PCIE 分支
    method_misc.get_oob_interface_type = function() return sml_mod.OOB_TYPE_OVER_PCIE end
    mctp_service.get_instance = function()
        return { register_mctp_object = function() return true end, mctp_state = true }
    end
    controller:add_controller_to_link_topo()

    method_misc.get_oob_interface_type = orig_get_oob
    sml_mod.set_i2c_chip = orig_set_i2c
    mctp_service.get_instance = orig_mctp_get
end

function TEST_Controller_Object:test_add_register_unregister_controller_to_sml()
    local sml_mod = require 'sml'
    local orig_reg = sml_mod.register_controller
    local orig_unreg = sml_mod.unregister_controller
    local orig_monitor = ctrl_commu_loss_monitor.get_instance
    local updates = {}
    ctrl_commu_loss_monitor.get_instance = function()
        return { update = function(_, loss, idx) updates[#updates + 1] = { loss = loss, idx = idx } end }
    end

    sml_mod.register_controller = function() return 0 end
    sml_mod.unregister_controller = function() return 0 end

    local controller = setmetatable({ Id = 1, TypeId = 0, get_mapping_type_id = function() return 0 end }, { __index = c_controller })
    controller:add_controller_to_sml()
    controller:register_controller_to_sml()
    controller:unregister_controller_to_sml()

    -- failure 分支：ret!=0 且不是 duplicate 时会 update(true) 并 error
    sml_mod.register_controller = function() return 0x1234 end
    local ok = pcall(function() controller:add_controller_to_sml() end)
    lu.assertFalse(ok)

    sml_mod.register_controller = orig_reg
    sml_mod.unregister_controller = orig_unreg
    ctrl_commu_loss_monitor.get_instance = orig_monitor
end

function TEST_Controller_Object:test_update_controller_info_faultcode_and_maintain_log()
    local sml_mod = require 'sml'
    local orig_boot = sml_mod.get_ctrl_boot_devices
    local orig_fault = sml_mod.get_ctrl_faultcode
    local orig_loss_get = ctrl_commu_loss_monitor.get_instance
    local orig_drive_get = drive_collection.get_instance
    local orig_get_phy = drive_collection.get_drive_by_physic_id
    local orig_oob_type = method_misc.get_oob_interface_type
    local orig_vendor = method_misc.test_controller_vendor

    local maint_called = 0
    local orig_maint = log.maintenance
    log.maintenance = function() maint_called = maint_called + 1 end

    ctrl_commu_loss_monitor.get_instance = function()
        return { update = function() end, get_ctrl_commu_loss = function() return 0 end }
    end
    drive_collection.get_instance = function()
        return { get_drives_by_controller_id = function() return {} end }
    end

    method_misc.get_oob_interface_type = function() return sml_mod.OOB_TYPE_OVER_PCIE end
    method_misc.test_controller_vendor = function() return true end

    sml_mod.get_ctrl_boot_devices = function() return (0x10000000 | 0x01), (0x20000000 | 0x02) end
    ---@diagnostic disable-next-line: duplicate-set-field
    drive_collection.get_drive_by_physic_id = function() return { Name = 'DriveX' } end
    sml_mod.get_ctrl_faultcode = function()
        return { health_status_code = 1, err_change = 1, hw_err = 1, clk_err = 1, power_err = 1, capacity_err = 1, flash_err = 1 }
    end

    local cfg = c_storageconfig.get_instance
    c_storageconfig.get_instance = function()
        return { get_set_configuration_ready_flag = function() return true end }
    end

    local controller = setmetatable({
        Id = 1,
        TypeId = 0,
        FaultCode = common_def.INVALID_U16,
        update_controller_battery_info = function() end
    }, { __index = c_controller })

    pcall(function() controller:update_boot_devices() end)
    pcall(function() controller:update_fault_code() end)
    controller.CommunicationLoss = ctrl_commu_loss_monitor.get_instance():get_ctrl_commu_loss(controller.Id)
    controller.RefDrives = drive_collection.get_instance():get_drives_by_controller_id(controller.Id)
    pcall(function() controller:update_controller_battery_info() end)
    lu.assertTrue(maint_called >= 1)

    c_storageconfig.get_instance = cfg
    drive_collection.get_drive_by_physic_id = orig_get_phy
    method_misc.get_oob_interface_type = orig_oob_type
    method_misc.test_controller_vendor = orig_vendor
    log.maintenance = orig_maint
    sml_mod.get_ctrl_boot_devices = orig_boot
    sml_mod.get_ctrl_faultcode = orig_fault
    ctrl_commu_loss_monitor.get_instance = orig_loss_get
    drive_collection.get_instance = orig_drive_get
end

function TEST_Controller_Object:test_update_volume_list_and_array_list_and_phy_info()
    local sml_mod = require 'sml'
    local volume_obj_info = require 'volume.volume_obj_info'
    local volume_collection_mod = require 'volume.volume_collection'
    local array_collection_mod = require 'array.array_collection'

    local orig_init_state = sml_mod.get_ctrl_init_state
    local orig_ld_list = sml_mod.get_ctrl_ld_list
    local orig_array_list = sml_mod.get_ctrl_array_list
    local orig_phy_err = sml_mod.get_ctrl_sas_phy_err
    local orig_new_vol = volume_obj_info.new
    local orig_vol_get = volume_collection_mod.get_instance
    local orig_arr_get = array_collection_mod.get_instance
    local orig_monitor = ctrl_commu_loss_monitor.get_instance

    sml_mod.get_ctrl_init_state = function() return 2 end
    sml_mod.get_ctrl_ld_list = function() return { 1, 2 } end
    sml_mod.get_ctrl_array_list = function() return { 1, 2 } end
    volume_obj_info.new = function(_, _, vid) return { key = 'v' .. tostring(vid) } end

    local emitted = { add_v = 0, del_v = 0, add_a = 0, del_a = 0 }
    volume_collection_mod.get_instance = function()
        return {
            on_add_volume = { emit = function() emitted.add_v = emitted.add_v + 1 end },
            on_del_volume = { emit = function() emitted.del_v = emitted.del_v + 1 end }
        }
    end
    array_collection_mod.get_instance = function()
        return {
            get_ctrl_array_list_by_ctrl_id = function() return { [1] = true, [3] = true } end,
            on_add_array = { emit = function() emitted.add_a = emitted.add_a + 1 end },
            on_del_array = { emit = function() emitted.del_a = emitted.del_a + 1 end }
        }
    end
    ctrl_commu_loss_monitor.get_instance = function()
        return { update = function() end }
    end

    local cfg = c_storageconfig.get_instance
    c_storageconfig.get_instance = function() return { power_on = 1 } end

    local controller = setmetatable({
        Id = 1,
        tasks_pause = false,
        position = '0',
        volume_list = { v1 = { key = 'v1' } },
        array_list = { [1] = true, [3] = true },
        controller_path = 'p',
        compare_volume_list = c_controller.compare_volume_list,
        compare_array_list = c_controller.compare_array_list
    }, { __index = c_controller })

    controller:update_volume_list()
    lu.assertTrue(emitted.add_v >= 1)
    controller:remove_volume_list()
    lu.assertTrue(emitted.del_v >= 1)

    controller:update_array_list()
    lu.assertTrue(emitted.add_a >= 1)
    lu.assertTrue(emitted.del_a >= 1)
    controller:remove_array_list()

    -- get_phy_children + update_phy_info
    sml_mod.get_ctrl_sas_phy_err = function()
        return {
            [0] = { invalid_dword_count = 1, loss_dword_sync_count = 2, phy_reset_problem_count = 3, running_disparity_error_count = 4 },
            [1] = { invalid_dword_count = 5, loss_dword_sync_count = 6, phy_reset_problem_count = 7, running_disparity_error_count = 8 }
        }
    end
    controller.get_children = function()
        return {
            { PhyId = true, path = 'b' },
            { PhyId = true, path = 'a' }
        }
    end
    local children = controller:get_phy_children()
    lu.assertEquals(children[1].path, 'a')
    controller.get_phy_children = function() return children end
    controller:update_phy_info()
    lu.assertEquals(children[1].InvalidDwordCount, 1)

    c_storageconfig.get_instance = cfg
    sml_mod.get_ctrl_init_state = orig_init_state
    sml_mod.get_ctrl_ld_list = orig_ld_list
    sml_mod.get_ctrl_array_list = orig_array_list
    sml_mod.get_ctrl_sas_phy_err = orig_phy_err
    volume_obj_info.new = orig_new_vol
    volume_collection_mod.get_instance = orig_vol_get
    array_collection_mod.get_instance = orig_arr_get
    ctrl_commu_loss_monitor.get_instance = orig_monitor
end

function TEST_Controller_Object:test_clear_and_update_asset_data_info()
    local controller = setmetatable({
        Id = 1,
        ControllerName = 'N',
        SerialNumber = 'SN',
        FirmwareVersion = 'FW',
        ChipManufacturer = 'M',
        Model = 'Model',
        BOMNumber = 'B'
    }, { __index = c_controller })
    controller:clear_asset_data_info()
    lu.assertEquals(controller.AssetType, '')
    controller:update_asset_data_info()
    lu.assertEquals(controller.AssetType, 'PCIe RAID Card')
    lu.assertEquals(controller.Slot, '1')
end