-- 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.
-- See the Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2

local lu = require('luaunit')

-- 可注入的 stub
local stub_controller_by_id = {}
local stub_sml_ctrl_operation_ret = 0
local stub_sml_get_ctrl_info_ret = nil
local stub_method_misc_vendor = true

local error_engine = require('error_engine')
local common_def = require('common_def')
local volume_collection = require('volume.volume_collection')
local array_collection = require('array.array_collection')
local volume_param_check = require('volume.volume_param_check')
local ctrl_config_backup = require('controller.ctrl_config_backup')

local function make_ctx()
    return {
        ChanType = 1,
        get_initiator = function()
            return 'test_initiator'
        end,
    }
end

TestRpcServiceController = {}

local function reload_dump_log_modules()
    package.loaded['controller.ctrl_log_dump'] = nil
    package.loaded['rpc_services.rpc_service_controller'] = nil
end

function TestRpcServiceController:setUp()
    reload_dump_log_modules()
    stub_controller_by_id = {}
    stub_sml_ctrl_operation_ret = 0
    stub_sml_get_ctrl_info_ret = nil
    stub_method_misc_vendor = true

    -- volume/array collection 的真实 singleton 依赖 storage_app_service,这里统一 stub 掉,避免构造失败
    self._orig_volume_get_instance = volume_collection.get_instance
    self._stub_volume_inst = {
        get_ctrl_volume_by_id = function()
            return nil
        end,
        on_add_volume = { emit = function() end },
        on_del_volume = { emit = function() end },
    }
    volume_collection.get_instance = function()
        return self._stub_volume_inst
    end

    self._orig_array_get_instance = array_collection.get_instance
    self._stub_array_inst = {
        get_ctrl_array_by_id = function()
            return nil
        end,
        get_ctrl_array_list_by_ctrl_id = function()
            return {}
        end,
        on_add_array = { emit = function() end },
        on_del_array = { emit = function() end },
    }
    array_collection.get_instance = function()
        return self._stub_array_inst
    end

    -- 让 get_by_controller_id 能返回我们注入的 controller(通过 patch find)
    -- stub_controller_by_id[id]=table 返回该 controller;=false 表示显式返回 nil;未设置则走原 find
    local c_ctrl = require('controller.controller_object')
    self._orig_find = c_ctrl.collection and c_ctrl.collection.find
    if c_ctrl.collection then
        c_ctrl.collection.find = function(_, pred)
            if pred and pred.Id ~= nil then
                local s = stub_controller_by_id[pred.Id]
                if s == false then
                    return nil
                end
                if type(s) == 'table' then
                    return s
                end
            end
            if self._orig_find then
                return self._orig_find(c_ctrl.collection, pred)
            end
            return nil
        end
    end

    local sml = require('sml')
    self._orig_sml_ctrl = sml.ctrl_operation
    self._orig_sml_get_info = sml.get_ctrl_info
    sml.ctrl_operation = function(ctrl_id, cmd, param)
        return stub_sml_ctrl_operation_ret
    end
    sml.get_ctrl_info = function(_, ctrl_id)
        return stub_sml_get_ctrl_info_ret
    end

    local method_misc = require('method_misc')
    self._orig_misc_vendor = method_misc.test_controller_vendor
    method_misc.test_controller_vendor = function(_, type_id, vendor)
        return stub_method_misc_vendor
    end

    -- ctrl_operate 配置类操作成功后同步配置备份,测试环境没有可用的 storageconfig db,
    -- 统一 stub get_instance,避免备份同步失败把设置成功路径误判为失败
    self._orig_ccb_get_instance = ctrl_config_backup.get_instance
    ctrl_config_backup.get_instance = function()
        return {
            update_backup_prop = function() end,
        }
    end
end

function TestRpcServiceController:tearDown()
    reload_dump_log_modules()
    local c_ctrl = require('controller.controller_object')
    if c_ctrl.collection and self._orig_find then
        c_ctrl.collection.find = self._orig_find
    end
    local sml = require('sml')
    if self._orig_sml_ctrl then
        sml.ctrl_operation = self._orig_sml_ctrl
    end
    if self._orig_sml_get_info then
        sml.get_ctrl_info = self._orig_sml_get_info
    end
    local method_misc = require('method_misc')
    if self._orig_misc_vendor then
        method_misc.test_controller_vendor = self._orig_misc_vendor
    end
    if self._orig_volume_get_instance then
        volume_collection.get_instance = self._orig_volume_get_instance
    end
    if self._orig_array_get_instance then
        array_collection.get_instance = self._orig_array_get_instance
    end
    if self._orig_ccb_get_instance then
        ctrl_config_backup.get_instance = self._orig_ccb_get_instance
    end
end

-- 覆盖 check_params_before_task: DeleteVolume 分支,volume 不存在
function TestRpcServiceController:test_check_params_before_task_delete_volume_invalid()
    local rpc = require('rpc_services.rpc_service_controller')
    local inst = rpc.get_instance and rpc.get_instance() or rpc.new(nil)
    local ctx = make_ctx()

    -- stub volume_collection 使其返回 nil
    local vc = volume_collection.get_instance()
    local orig_get_vol = vc.get_ctrl_volume_by_id
    vc.get_ctrl_volume_by_id = function(_, _)
        return nil
    end

    -- 捕获 error_engine.raise_controller_error 调用
    local orig_raise = error_engine.raise_controller_error
    local captured = {}
    error_engine.raise_controller_error = function(_, code, p1, p2)
        captured = { code = code, p1 = p1, p2 = p2 }
    end

    local controller = { Id = 1 }
    inst:check_params_before_task('DeleteVolume', ctx, controller, 123)

    lu.assertEquals(captured.code, error_engine.SML_ERR_CODE_E.SML_ERR_LD_INVALID_TARGET_ID)

    -- 还原 stub
    vc.get_ctrl_volume_by_id = orig_get_vol
    error_engine.raise_controller_error = orig_raise
end

-- 覆盖 check_params_before_task: CreateVolumeInExistingArray,阵列不存在
function TestRpcServiceController:test_check_params_before_task_create_volume_existing_array_invalid_array()
    local rpc = require('rpc_services.rpc_service_controller')
    local inst = rpc.get_instance and rpc.get_instance() or rpc.new(nil)
    local ctx = make_ctx()

    -- stub array_collection,使其找不到 array
    local ac = array_collection.get_instance()
    local orig_get_array = ac.get_ctrl_array_by_id
    ac.get_ctrl_array_by_id = function(_, _)
        return nil
    end

    -- 捕获 error_engine.raise_controller_error
    local orig_raise = error_engine.raise_controller_error
    local captured = {}
    error_engine.raise_controller_error = function(_, code, p1, p2)
        captured = { code = code, p1 = p1, p2 = p2 }
    end

    local controller = {
        Id = 1,
        CreateVolumeSupported = true,
    }

    -- 只需保证参数数量对齐,具体值无关紧要,错误会在 array 校验阶段触发
    inst:check_params_before_task(
        'CreateVolumeInExistingArray',
        ctx,
        controller,
        1, -- array_id
        0, -- block_index
        0, -- raid_type
        0, -- span_depth
        'ld', -- name
        1, -- capacity
        0, -- capacity_unit
        0, -- strip_size
        0, -- read_policy
        0, -- write_policy
        0, -- io_policy
        0, -- access_policy
        0, -- disk_cache_policy
        0, -- init_type
        0 -- accelerator
    )

    lu.assertEquals(captured.code, error_engine.SML_ERR_CODE_E.SML_ERR_CTRL_STATUS_INVALID)

    -- 还原 stub
    ac.get_ctrl_array_by_id = orig_get_array
    error_engine.raise_controller_error = orig_raise
end

-- 覆盖 check_params_before_task CreateVolumeInNewArray:CreateVolumeSupported 为 false
function TestRpcServiceController:test_check_params_create_volume_in_new_array_not_supported()
    local rpc = require('rpc_services.rpc_service_controller')
    local inst = rpc.get_instance and rpc.get_instance() or rpc.new(nil)
    local ctx = make_ctx()
    local controller = { Id = 1, CreateVolumeSupported = false }
    local orig_raise = error_engine.raise_controller_error
    local captured
    error_engine.raise_controller_error = function(_, code)
        captured = code
    end
    inst:check_params_before_task(
        'CreateVolumeInNewArray',
        ctx,
        controller,
        '\x01',
        0,
        0,
        'ld',
        1,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0
    )
    lu.assertEquals(captured, error_engine.SML_ERR_CODE_E.SML_ERR_CTRL_STATUS_INVALID)
    error_engine.raise_controller_error = orig_raise
end

-- 覆盖 check_volume_status:volume 存在但 CurrentForegroundInitState == 1
function TestRpcServiceController:test_check_params_volume_foreground_init()
    local rpc = require('rpc_services.rpc_service_controller')
    local inst = rpc.get_instance and rpc.get_instance() or rpc.new(nil)
    local ctx = make_ctx()
    local vc = volume_collection.get_instance()
    local orig_get = vc.get_ctrl_volume_by_id
    local orig_raise = error_engine.raise_controller_error
    local captured
    vc.get_ctrl_volume_by_id = function(_, _)
        return { CurrentForegroundInitState = 1 }
    end
    error_engine.raise_controller_error = function(_, code)
        captured = code
    end
    pcall(function()
        inst:check_params_before_task('DeleteVolume', ctx, { Id = 1 }, 123)
    end)
    vc.get_ctrl_volume_by_id = orig_get
    error_engine.raise_controller_error = orig_raise
    lu.assertEquals(captured, error_engine.SM_CODE_E.SM_CODE_OPERATION_IN_PROGRESS)
end

-- 覆盖 prep_set_properties: OOBSupport ~= 1
function TestRpcServiceController:test_ctrl_operate_oob_support_false()
    local rpc = require('rpc_services.rpc_service_controller')
    stub_controller_by_id[1] = { Id = 1, OOBSupport = 0, is_working = false }
    local ok, _ = pcall(rpc.ctrl_operate, rpc, 'SetCopyBackState', 1, make_ctx(), 1)
    lu.assertFalse(ok)
end

-- 覆盖 prep_set_properties: is_working == true
function TestRpcServiceController:test_ctrl_operate_controller_busy()
    local rpc = require('rpc_services.rpc_service_controller')
    stub_controller_by_id[1] = { Id = 1, OOBSupport = 1, is_working = true }
    local ok, _ = pcall(rpc.ctrl_operate, rpc, 'SetCopyBackState', 1, make_ctx(), 1)
    lu.assertFalse(ok)
end

-- 覆盖 controller 不存在
function TestRpcServiceController:test_ctrl_operate_controller_not_found()
    local rpc = require('rpc_services.rpc_service_controller')
    stub_controller_by_id[1] = nil
    local ok, _ = pcall(rpc.ctrl_operate, rpc, 'SetCopyBackState', 1, make_ctx(), 1)
    lu.assertFalse(ok)
end

-- 覆盖 set_copyback_state: 非法 copyback_state
function TestRpcServiceController:test_set_copyback_state_invalid_value()
    local rpc = require('rpc_services.rpc_service_controller')
    stub_controller_by_id[1] = { Id = 1, OOBSupport = 1, is_working = false, CopyBackState = 0 }
    local ok, _ = pcall(rpc.ctrl_operate, rpc, 'SetCopyBackState', 1, make_ctx(), 3)
    lu.assertFalse(ok)
end

-- 覆盖 enable_consistency_check 中 ConsistencyCheckState==1 且 delay==0xFFFFFFFF 时调用 set_consistency_check 的分支
function TestRpcServiceController:test_enable_ccheck_repeat_enable_calls_set_consistency_check()
    stub_method_misc_vendor = true
    stub_sml_ctrl_operation_ret = 0
    local rpc = require('rpc_services.rpc_service_controller')
    -- TypeId 华为; ConsistencyCheckState=1, delay=0xFFFFFFFF 会走 set_consistency_check
    stub_controller_by_id[1] = {
        Id = 1,
        OOBSupport = 1,
        is_working = false,
        TypeId = 2,
        ConsistencyCheckState = 1,
    }
    local ctx = make_ctx()
    -- EnableCCheck: period, rate, auto_repaired, delay (0xFFFFFFFF = 4294967295)
    local ok, err = pcall(rpc.ctrl_operate, rpc, 'EnableCCheck', 1, ctx, 24, 1, 0, 0xFFFFFFFF)
    -- 可能因 set_consistency_check 内部参数错位导致报错,这里只要求执行到该分支
    lu.assertTrue(ok or (type(err) == 'string' and err:find('raise_controller_error') or type(err) == 'table'))
end

-- 覆盖 get_controller_info: controller 不存在
function TestRpcServiceController:test_get_controller_info_invalid_id()
    local rpc = require('rpc_services.rpc_service_controller')
    local inst = rpc.get_instance and rpc.get_instance() or rpc.new(nil)
    stub_controller_by_id[99] = false
    local details = inst:get_controller_info(99)
    lu.assertNotNil(details)
    lu.assertEquals(type(details), 'table')
end

-- 覆盖 get_controller_info: 正常返回
function TestRpcServiceController:test_get_controller_info_success()
    local rpc = require('rpc_services.rpc_service_controller')
    local inst = rpc.get_instance and rpc.get_instance() or rpc.new(nil)
    stub_controller_by_id[1] = {
        Id = 1,
        DevBus = 0,
        DevDevice = 0,
        DevFunction = 0,
        Eid = 1,
        Phyaddr = 2,
        CtrlOption1 = 0,
        CtrlOption2 = 0,
        CtrlOption3 = 0,
        SlotId = 1,
    }
    local details = inst:get_controller_info(1)
    lu.assertNotNil(details)
    lu.assertEquals(details.DevBus, '0')
    lu.assertEquals(details.SlotId, '1')
end

-- 覆盖 ctrl_operate 成功路径后 get_ctrl_info 刷新
function TestRpcServiceController:test_ctrl_operate_success_emits_update()
    stub_sml_get_ctrl_info_ret = { some = 'info' }
    local rpc = require('rpc_services.rpc_service_controller')
    local updated
    stub_controller_by_id[1] = {
        Id = 1,
        OOBSupport = 1,
        is_working = false,
        CopyBackState = 0,
        -- ctrl_operate 成功后会 pcall(controller:fetch_controller_info()) 刷新信息
        fetch_controller_info = function(_)
            updated = stub_sml_get_ctrl_info_ret
        end,
    }
    local ok, _ = pcall(rpc.ctrl_operate, rpc, 'SetCopyBackState', 1, make_ctx(), 1)
    lu.assertTrue(ok)
    lu.assertNotNil(updated)
end

-- 覆盖 ctrl_operate 返回SML_ERR_REBOOT_REQUIRED(命令成功需OS重启生效)路径:
-- 抛错(需重启提示)前应先刷新资源树信息并同步配置类操作备份
function TestRpcServiceController:test_ctrl_operate_reboot_required_refreshes_and_backs_up()
    stub_sml_ctrl_operation_ret = common_def.SML_ERR_REBOOT_REQUIRED
    local rpc = require('rpc_services.rpc_service_controller')
    local fetch_count, backup_count
    stub_controller_by_id[1] = {
        Id = 1,
        OOBSupport = 1,
        is_working = false,
        CopyBackState = 0,
        -- REBOOT_REQUIRED抛错前应刷新控制器信息
        fetch_controller_info = function()
            fetch_count = (fetch_count or 0) + 1
        end,
    }
    -- 全量运行时前序用例类可能重载过该模块,文件顶部引用与被测模块require到的实例不一致,
    -- 桩需打在当前缓存实例上才对被测模块生效
    local ccb = require('controller.ctrl_config_backup')
    local orig_ccb_get_instance = ccb.get_instance
    -- SetCopyBackState属配置类操作,REBOOT_REQUIRED抛错前应同步配置备份
    ccb.get_instance = function()
        return {
            update_backup_prop = function()
                backup_count = (backup_count or 0) + 1
            end,
        }
    end
    local ok = pcall(rpc.ctrl_operate, rpc, 'SetCopyBackState', 1, make_ctx(), 1)
    ccb.get_instance = orig_ccb_get_instance
    lu.assertFalse(ok) -- 抛错逻辑保持不变(仍以上抛需重启生效的错误)
    lu.assertEquals(fetch_count, 1) -- 抛错前已刷新一次控制器信息且仅一次(raise后中断不再重复刷新)
    lu.assertEquals(backup_count, 1) -- 抛错前已同步配置备份
end

-- 覆盖 set_maintain_pd_fail_history 非法输入
function TestRpcServiceController:test_set_maintain_pdfail_history_invalid()
    local rpc = require('rpc_services.rpc_service_controller')
    stub_controller_by_id[1] = { Id = 1, OOBSupport = 1, is_working = false, MaintainPDFailHistrory = 0 }
    local ok, _ = pcall(rpc.ctrl_operate, rpc, 'SetMaintainPDFailHistoryState', 1, make_ctx(), 2)
    lu.assertFalse(ok)
end

-- 预留:如需覆盖 ctrl_task_operate,可在此处补充更稳定的用例

function TestRpcServiceController:test_restore_default_settings_success_and_fail()
    local rpc = require('rpc_services.rpc_service_controller')
    local ctx = make_ctx()
    local sml = require('sml')
    local common_def = require('common_def')

    local obj = { Id = 1, is_working = false }

    -- success: ret == 0
    local orig = sml.ctrl_operation
    sml.ctrl_operation = function()
        return common_def.RET_OK
    end
    local ok = pcall(rpc.restore_default_settings, obj, ctx)
    lu.assertTrue(ok)
    lu.assertFalse(obj.is_working)

    -- fail: ret != OK and != reboot required
    sml.ctrl_operation = function()
        return common_def.RET_ERR
    end
    ok = pcall(rpc.restore_default_settings, obj, ctx)
    lu.assertFalse(ok)

    sml.ctrl_operation = orig
end

function TestRpcServiceController:test_retirement_datawipe_and_getreport()
    local rpc = require('rpc_services.rpc_service_controller')
    local common_def = require('common_def')
    local sml = require('sml')
    local skynet = require('skynet')

    local obj = {
        Id = 1,
        ChipManufacturer = 'C',
        Model = 'M',
        SerialNumber = 'SN',
    }

    local orig_ctrl = sml.ctrl_operation
    sml.ctrl_operation = function()
        return common_def.RET_OK
    end

    local orig_fork_once = skynet.fork_once
    skynet.fork_once = function(fn)
        fn()
    end

    rpc.retirement(obj, 'DataWipe')
    lu.assertEquals(obj.RetirementState, 'Finish')
    lu.assertEquals(obj.Progress, 100)
    lu.assertEquals(obj.Result, 'Successful')

    local report = rpc.retirement(obj, 'GetReport')
    lu.assertEquals(report.Media, 'Flash')
    lu.assertEquals(report.Model, 'M')
    lu.assertEquals(report.Result, obj.Result)

    skynet.fork_once = orig_fork_once
    sml.ctrl_operation = orig_ctrl
end

function TestRpcServiceController:test_set_boot_devices_check_input_and_operations()
    local rpc = require('rpc_services.rpc_service_controller')
    local common_def = require('common_def')
    local ctrl_operation_cmd = require('controller.ctrl_operation_cmd')

    -- stub collections to validate ids
    local vc = volume_collection.get_instance()
    local orig_get_vol = vc.get_ctrl_volume_by_id
    vc.get_ctrl_volume_by_id = function(_, ctrl_id, vol_id)
        return (ctrl_id == 1 and vol_id == 1) and {} or nil
    end

    local drive_collection = require('drive.drive_collection')
    local dc = drive_collection.get_instance()
    local orig_get_drive = dc.get_drive_by_drive_id
    dc.get_drive_by_drive_id = function(_, did)
        return did == 1 and {} or nil
    end

    -- stub rpc_service_drive/volume operations
    local rpc_drive = require('rpc_services.rpc_service_drive')
    local rpc_vol = require('rpc_services.rpc_service_volume')
    local orig_drive_inst = rpc_drive.get_instance
    local orig_vol_inst = rpc_vol.get_instance
    local called = { drive = 0, vol = 0 }
    rpc_drive.get_instance = function()
        return {
            drive_operate = function(_, op, dev, ctx, cmd)
                called.drive = called.drive + 1
                lu.assertEquals(op, 'SetBootPriority')
                lu.assertNotNil(cmd)
            end,
        }
    end
    rpc_vol.get_instance = function()
        return {
            volume_operate = function(_, op, ctrl_id, vol_id, ctx, cmd)
                called.vol = called.vol + 1
                lu.assertEquals(op, 'SetBootable')
                lu.assertEquals(ctrl_id, 1)
                lu.assertEquals(vol_id, 1)
                lu.assertTrue(
                    cmd == ctrl_operation_cmd.BOOT_PRIORITY_PRIMARY
                        or cmd == ctrl_operation_cmd.BOOT_PRIORITY_SECONDARY
                        or cmd == ctrl_operation_cmd.BOOT_PRIORITY_ALL
                        or cmd == ctrl_operation_cmd.BOOT_PRIORITY_NONE
                )
            end,
        }
    end

    local obj = {
        Id = 1,
        TypeId = 0,
        BootDevices = { 'Disk1', 'Volume1' },
    }

    -- primary=secondary=None, BootDevices不同 => 两个 NONE 操作
    local ok = pcall(rpc.set_boot_devices, obj, make_ctx(), 'None', 'None')
    lu.assertTrue(ok)

    -- primary!=secondary => 一个 Disk 一个 Volume
    called.drive, called.vol = 0, 0
    ok = pcall(rpc.set_boot_devices, obj, make_ctx(), 'Disk1', 'Volume1')
    lu.assertTrue(ok)
    lu.assertEquals(called.drive, 1)
    lu.assertEquals(called.vol, 1)

    -- 非法 device_name 格式
    ok = pcall(rpc.set_boot_devices, obj, make_ctx(), 'Disk-1', 'None')
    lu.assertFalse(ok)

    -- 不支持类型
    ok = pcall(rpc.set_boot_devices, obj, make_ctx(), 'Foo1', 'None')
    lu.assertFalse(ok)

    -- id 不存在
    ok = pcall(rpc.set_boot_devices, obj, make_ctx(), 'Disk2', 'None')
    lu.assertFalse(ok)

    -- HI1880 分支:secondary 必须 None
    obj.TypeId = common_def.HI1880_SP186_M_16i
    ok = pcall(rpc.set_boot_devices, obj, make_ctx(), 'Disk1', 'Disk1')
    lu.assertFalse(ok)

    -- 让 get_operation_list 生成的 device_name 不是 Disk/Volume/None,触发 ret=RET_ERR 分支
    obj.TypeId = 0
    obj.BootDevices = { 'BadX', 'BadY' }
    ok = pcall(rpc.set_boot_devices, obj, make_ctx(), 'None', 'None')
    lu.assertFalse(ok)

    -- restore stubs
    rpc_drive.get_instance = orig_drive_inst
    rpc_vol.get_instance = orig_vol_inst
    dc.get_drive_by_drive_id = orig_get_drive
    vc.get_ctrl_volume_by_id = orig_get_vol
end

function TestRpcServiceController:test_set_boot_devices_v2_check_input_and_operations()
    local rpc = require('rpc_services.rpc_service_controller')
    local common_def = require('common_def')
    local ctrl_operation_cmd = require('controller.ctrl_operation_cmd')

    local vc = volume_collection.get_instance()
    local orig_get_vol = vc.get_ctrl_volume_by_id
    vc.get_ctrl_volume_by_id = function(_, ctrl_id, vol_id)
        return (ctrl_id == 1 and (vol_id == 1 or vol_id == 2 or vol_id == 3)) and {} or nil
    end

    local drive_collection = require('drive.drive_collection')
    local dc = drive_collection.get_instance()
    local orig_get_drive = dc.get_drive_by_drive_id
    dc.get_drive_by_drive_id = function(_, did)
        return (did == 1 or did == 2 or did == 3) and {} or nil
    end

    local rpc_service_drive = require('rpc_services.rpc_service_drive')
    local rpc_service_volume = require('rpc_services.rpc_service_volume')
    local orig_drive_inst = rpc_service_drive.get_instance
    local orig_vol_inst = rpc_service_volume.get_instance
    local called = { drive = 0, vol = 0 }
    rpc_service_drive.get_instance = function()
        return {
            drive_operate = function(_, op, dev, ctx, cmd)
                called.drive = called.drive + 1
                lu.assertEquals(op, 'SetBootPriority')
                lu.assertTrue(
                    cmd == ctrl_operation_cmd.BOOT_PRIORITY_PRIMARY
                        or cmd == ctrl_operation_cmd.BOOT_PRIORITY_SECONDARY
                        or cmd == ctrl_operation_cmd.BOOT_PRIORITY_ALL
                        or cmd == ctrl_operation_cmd.BOOT_PRIORITY_NONE
                )
            end,
        }
    end

    rpc_service_volume.get_instance = function()
        return {
            volume_operate = function(_, op, ctrl_id, vol_id, ctx, cmd)
                called.vol = called.vol + 1
                lu.assertEquals(op, 'SetBootable')
                lu.assertTrue(
                    cmd == ctrl_operation_cmd.BOOT_PRIORITY_PRIMARY
                        or cmd == ctrl_operation_cmd.BOOT_PRIORITY_SECONDARY
                        or cmd == ctrl_operation_cmd.BOOT_PRIORITY_ALL
                        or cmd == ctrl_operation_cmd.BOOT_PRIORITY_NONE
                )
            end,
        }
    end

    local obj = {
        Id = 1,
        TypeId = 0,
        SupportedBootDeviceCount = 2,
        BootDevices = { 'Disk1', 'Volume1' },
    }

    -- 空数组 => 成功
    local ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), {})
    lu.assertTrue(ok)

    -- 单个有效设备 => 一个 Disk 操作
    called.drive, called.vol = 0, 0
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Disk1' })
    lu.assertTrue(ok)

    -- 两个不同设备 => 一个 Disk 一个 Volume
    called.drive, called.vol = 0, 0
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Disk1', 'Volume1' })
    lu.assertTrue(ok)
    lu.assertEquals(called.drive, 1)
    lu.assertEquals(called.vol, 1)

    -- 数组长度超过2 => 应该失败
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Disk1', 'Volume1', 'Disk2' })
    lu.assertFalse(ok)

    -- 数组元素重复 => 应该成功
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Disk1', 'Disk1' })
    lu.assertTrue(ok)

    -- 非法 device_name 格式
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Disk-1' })
    lu.assertFalse(ok)

    -- 不支持类型
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Foo1' })
    lu.assertFalse(ok)

    -- id 不存在
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Disk5' })
    lu.assertFalse(ok)

    -- 参数不是数组 => 应该失败
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), 'Disk1')
    lu.assertFalse(ok)

    -- 参数为 nil => 应该失败
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), nil)
    lu.assertFalse(ok)

    -- 数组包含 None 值 => 应该成功
    obj.TypeId = 0
    called.drive, called.vol = 0, 0
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Disk1', 'None' })
    lu.assertTrue(ok)
    lu.assertEquals(called.drive, 1)
    lu.assertEquals(called.vol, 1)

    -- 让 get_operation_list 生成的 device_name 不是 Disk/Volume/None,触发 ret=RET_ERR 分支
    obj.TypeId = 0
    obj.BootDevices = { 'BadX', 'BadY' }
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'None', 'None' })
    lu.assertFalse(ok)

    -- HI1880 1.0分支:
    obj = {
        Id = 1,
        TypeId = common_def.HI1880_SP186_M_16i,
        SupportedBootDeviceCount = 1,
        BootDevices = { 'Disk1' },
    }

    -- 数组中有多个非 None 设备 => 应该失败
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Disk1', 'Disk2' })
    lu.assertFalse(ok)

    -- 数组中只有一个非 None 设备 => 应该成功
    called.drive, called.vol = 0, 0
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Disk1' })
    lu.assertTrue(ok)
    lu.assertEquals(called.drive, 1)

    -- 数组中只有一个 None 设备 => 应该成功
    called.drive, called.vol = 0, 0
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'None' })
    lu.assertTrue(ok)
    lu.assertEquals(called.drive, 1)

    -- HI1880 2.0分支:
    obj = {
        Id = 1,
        TypeId = common_def.HI1880_SP186_M_16i,
        SupportedBootDeviceCount = 5,
        BootDevices = { 'Disk1', 'Volume1', 'Disk2', 'None', 'None' },
    }

    -- 数组长度大于5 => 应该失败
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Disk1', 'Disk2', 'Disk3', 'None', 'None', 'None' })
    lu.assertFalse(ok)

    -- 数组中新增设备未超出上限 => 应该成功
    called.drive, called.vol = 0, 0
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Disk1', 'Disk3', 'Volume2', 'None' })
    lu.assertTrue(ok)
    lu.assertEquals(called.drive, 2)
    lu.assertEquals(called.vol, 1)

    -- 数组中新增设备超出上限 => 应该失败
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Disk1', 'Disk3', 'Volume2', 'Volume3' })
    lu.assertFalse(ok)

    -- 数组中只有 None 和 已存在的设备 => 应该成功
    called.drive, called.vol = 0, 0
    ok = pcall(rpc.set_boot_devices_v2, obj, make_ctx(), { 'Disk1', 'Volume1', 'Disk2', 'None' })
    lu.assertTrue(ok)
    lu.assertEquals(called.drive, 2)
    lu.assertEquals(called.vol, 1)

    -- restore stubs
    rpc_service_drive.get_instance = orig_drive_inst
    rpc_service_volume.get_instance = orig_vol_inst
    dc.get_drive_by_drive_id = orig_get_drive
    vc.get_ctrl_volume_by_id = orig_get_vol
end

function TestRpcServiceController:test_set_work_mode_vendor_and_param_checks()
    local rpc = require('rpc_services.rpc_service_controller')
    local sml = require('sml')
    local common_def = require('common_def')
    local method_misc = require('method_misc')

    local orig_vendor = method_misc.test_controller_vendor
    local orig_ctrl = sml.ctrl_operation

    -- vendor Huawei => not support
    method_misc.test_controller_vendor = function()
        return true
    end
    local ok = pcall(rpc.set_work_mode, { Id = 1, TypeId = 0 }, make_ctx(), 0, 0)
    lu.assertFalse(ok)

    -- unsupported mode
    method_misc.test_controller_vendor = function()
        return false
    end
    ok = pcall(rpc.set_work_mode, { Id = 1, TypeId = 0, SupportedMode = { 'RAID' } }, make_ctx(), 1, 0)
    lu.assertFalse(ok)

    -- success:成功后同步对象 WorkMode 属性(供配置备份/恢复读取当前值)
    local obj = { Id = 1, TypeId = 0, SupportedMode = { 'RAID', 'HBA' } }
    sml.ctrl_operation = function()
        return common_def.RET_OK
    end
    ok = pcall(rpc.set_work_mode, obj, make_ctx(), 1, 0)
    lu.assertTrue(ok)
    lu.assertEquals(obj.WorkMode, 'HBA')

    sml.ctrl_operation = orig_ctrl
    method_misc.test_controller_vendor = orig_vendor
end

function TestRpcServiceController:test_consistency_check_enable_disable_set_and_invalid()
    local rpc = require('rpc_services.rpc_service_controller')
    local ctrl_operation_cmd = require('controller.ctrl_operation_cmd')
    local common_def = require('common_def')
    local sml = require('sml')
    local method_misc = require('method_misc')

    local orig_vendor = method_misc.test_controller_vendor
    local orig_ctrl = sml.ctrl_operation
    sml.ctrl_operation = function()
        return common_def.RET_OK
    end

    -- vendor not Huawei => not support
    method_misc.test_controller_vendor = function()
        return false
    end
    local ok = pcall(rpc.enable_consistency_check, { Id = 1, TypeId = 0 }, make_ctx(), 24, 1, 0, 0)
    lu.assertFalse(ok)

    method_misc.test_controller_vendor = function()
        return true
    end
    local obj = { Id = 1, TypeId = 0, ConsistencyCheckState = 0, PeriodOfHours = 0, DelayToStart = 0 }

    -- invalid period
    ok = pcall(rpc.enable_consistency_check, obj, make_ctx(), 1, 1, 0, 0)
    lu.assertFalse(ok)

    -- enable success with delay = 0xFFFFFFFF should normalize DelayToStart to 0
    ok = pcall(
        rpc.enable_consistency_check,
        obj,
        make_ctx(),
        ctrl_operation_cmd.CTRL_CC_PERIOD_MIN,
        ctrl_operation_cmd.CTRL_CCHECK_RATE_LOW,
        0,
        0xFFFFFFFF
    )
    lu.assertTrue(ok)
    lu.assertEquals(obj.DelayToStart, 0)

    -- set success
    ok = pcall(
        rpc.set_consistency_check,
        obj,
        make_ctx(),
        ctrl_operation_cmd.CTRL_CC_PERIOD_MIN,
        ctrl_operation_cmd.CTRL_CCHECK_RATE_MIDDLE,
        0
    )
    lu.assertTrue(ok)

    -- disable success resets DelayToStart
    obj.DelayToStart = 10
    ok = pcall(rpc.disable_consistency_check, obj, make_ctx())
    lu.assertTrue(ok)
    lu.assertEquals(obj.DelayToStart, 0)

    sml.ctrl_operation = orig_ctrl
    method_misc.test_controller_vendor = orig_vendor
end

function TestRpcServiceController:test_clear_and_import_foreign_config()
    local rpc = require('rpc_services.rpc_service_controller')
    local common_def = require('common_def')
    local sml = require('sml')

    local orig_ctrl = sml.ctrl_operation
    local obj = { Id = 1, is_working = false }

    sml.ctrl_operation = function()
        return common_def.RET_OK
    end
    lu.assertTrue(pcall(rpc.clear_foreign_config, obj, make_ctx()))
    lu.assertTrue(pcall(rpc.import_foreign_config, obj, make_ctx()))

    sml.ctrl_operation = function()
        return common_def.RET_ERR
    end
    lu.assertFalse(pcall(rpc.clear_foreign_config, obj, make_ctx()))
    lu.assertFalse(pcall(rpc.import_foreign_config, obj, make_ctx()))

    sml.ctrl_operation = orig_ctrl
end

function TestRpcServiceController:test_create_and_delete_volume_error_paths()
    local rpc = require('rpc_services.rpc_service_controller')
    local common_def = require('common_def')
    local sml = require('sml')

    local orig_add = sml.add_ld_on_exist_array
    local orig_del = sml.delete_volume
    local orig_new = sml.create_ld_on_new_array
    local orig_convert_cap = volume_param_check.convert_ld_capacity_in_array
    local orig_calc_span = volume_param_check.calc_pmc_span_depth
    local orig_conv_list = volume_param_check.convert_id_list_from_drive_to_pd
    local orig_span = volume_param_check.check_and_get_ld_span_depth

    volume_param_check.convert_ld_capacity_in_array = function(_, cap)
        return cap
    end
    volume_param_check.calc_pmc_span_depth = function(_, _, d)
        return d
    end
    volume_param_check.convert_id_list_from_drive_to_pd = function(_, _, dl)
        return dl
    end
    volume_param_check.check_and_get_ld_span_depth = function(_, _, _, _, d)
        return d
    end

    local obj = { Id = 1, CreateVolumeSupported = true, is_working = false }

    -- create existing array: pd clear in progress
    sml.add_ld_on_exist_array = function()
        return common_def.SML_ERR_PD_CLEAR_IN_PROGRESS
    end
    local ok =
        pcall(rpc.create_volume_in_existing_array, obj, make_ctx(), 1, 0, 0, 0, 'ld', 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
    lu.assertFalse(ok)

    -- delete volume error
    sml.delete_volume = function()
        return common_def.RET_ERR
    end
    ok = pcall(rpc.delete_volume, obj, make_ctx(), 1)
    lu.assertFalse(ok)

    -- create new array: ret != success
    sml.create_ld_on_new_array = function()
        return common_def.RET_ERR
    end
    ok = pcall(rpc.create_volume_in_new_array, obj, make_ctx(), '\x01', 0, 0, 'ld', 1, 0, 0, 0, 0, 0, 0, 0, 0, 0)
    lu.assertFalse(ok)

    -- restore
    sml.add_ld_on_exist_array = orig_add
    sml.delete_volume = orig_del
    sml.create_ld_on_new_array = orig_new
    volume_param_check.convert_ld_capacity_in_array = orig_convert_cap
    volume_param_check.calc_pmc_span_depth = orig_calc_span
    volume_param_check.convert_id_list_from_drive_to_pd = orig_conv_list
    volume_param_check.check_and_get_ld_span_depth = orig_span
end

function TestRpcServiceController:test_create_ld_as_cachecade_invalid_and_fail()
    local rpc = require('rpc_services.rpc_service_controller')
    local common_def = require('common_def')
    local sml = require('sml')

    local orig_chk = volume_param_check.check_ld_data_created_ssd_caching
    local orig_conv = volume_param_check.convert_id_list_from_drive_to_pd
    local orig_cap = volume_param_check.convert_ld_capacity_in_array
    local orig_create = sml.create_ld_as_cachecade

    local obj = { Id = 1, is_working = false }
    volume_param_check.convert_id_list_from_drive_to_pd = function(_, _, dl)
        return dl
    end
    volume_param_check.convert_ld_capacity_in_array = function(_, cap)
        return cap
    end

    -- invalid params => error
    volume_param_check.check_ld_data_created_ssd_caching = function()
        return common_def.RET_ERR
    end
    local ok = pcall(rpc.create_ld_as_cachecade, obj, make_ctx(), { 1 }, 0, 'cc', 0, 0, 1, 0, 0, 0)
    lu.assertFalse(ok)

    -- sml call fail => error
    volume_param_check.check_ld_data_created_ssd_caching = function()
        return common_def.SM_CODE_SUCCESS
    end
    sml.create_ld_as_cachecade = function()
        return common_def.RET_ERR
    end
    ok = pcall(rpc.create_ld_as_cachecade, obj, make_ctx(), { 1 }, 0, 'cc', 0, 0, 1, 0, 0, 0)
    lu.assertFalse(ok)

    volume_param_check.check_ld_data_created_ssd_caching = orig_chk
    volume_param_check.convert_id_list_from_drive_to_pd = orig_conv
    volume_param_check.convert_ld_capacity_in_array = orig_cap
    sml.create_ld_as_cachecade = orig_create
end

function TestRpcServiceController:test_import_when_power_on_and_export()
    local rpc = require('rpc_services.rpc_service_controller')
    local inst = rpc.get_instance and rpc.get_instance() or rpc.new(nil)

    local controller_collection = require('controller.controller_collection')
    local config_util = require('handler.config_util')
    local c_drives_object = require('drives.drives_object')

    local orig_cc = controller_collection.get_instance
    local orig_need = config_util.is_need_import
    local orig_get_bmc = config_util.get_bmc_prop
    local orig_get_user = config_util.get_user_prop_val
    local orig_get_bmc_val = config_util.get_bmc_prop_val
    local orig_drives = c_drives_object.get_instance

    -- missing Id => error
    local ok = pcall(inst.import_when_power_on, inst, make_ctx(), {})
    lu.assertFalse(ok)

    -- controller invalid => error
    controller_collection.get_instance = function()
        return {
            get_by_controller_id = function()
                return nil
            end,
            get_all_controllers = function()
                return {}
            end,
        }
    end
    ok = pcall(inst.import_when_power_on, inst, make_ctx(), { Id = { Value = 1 } })
    lu.assertFalse(ok)

    -- success path: skip unknown prop, and is_need_import=false => no call
    controller_collection.get_instance = function()
        return {
            get_by_controller_id = function()
                return { Id = 1, CopyBackState = 0 }
            end,
            get_all_controllers = function()
                return { { Id = 1, Type = 't', CopyBackState = 0 } }
            end,
        }
    end
    config_util.get_bmc_prop = function(p)
        return p
    end
    config_util.get_user_prop_val = function(_, v)
        return v
    end
    config_util.get_bmc_prop_val = function(_, v)
        return v
    end
    config_util.is_need_import = function()
        return false
    end
    ok = pcall(inst.import_when_power_on, inst, make_ctx(), { Id = { Value = 1 }, Unknown = { Value = 1 } })
    lu.assertTrue(ok)

    -- export success
    c_drives_object.get_instance = function()
        return { obj = { LogAutoCollectEnable = true, LogAutoCollectInterval = 24 } }
    end
    ok = pcall(function()
        local ret = inst:export()
        lu.assertEquals(type(ret), 'table')
        lu.assertEquals(ret[1].DrivesLogAutoCollectEnabled, true)
        lu.assertEquals(ret[1].DrivesLogAutoCollectInterval, 24)
    end)
    lu.assertTrue(ok)

    controller_collection.get_instance = orig_cc
    config_util.is_need_import = orig_need
    config_util.get_bmc_prop = orig_get_bmc
    config_util.get_user_prop_val = orig_get_user
    config_util.get_bmc_prop_val = orig_get_bmc_val
    c_drives_object.get_instance = orig_drives
end

function TestRpcServiceController:test_import_export_log_auto_collect_in_raid_controller()
    local rpc = require('rpc_services.rpc_service_controller')
    local inst = rpc.get_instance and rpc.get_instance() or rpc.new(nil)
    local controller_collection = require('controller.controller_collection')
    local c_drives_object = require('drives.drives_object')
    local import_export_eng = require('handler.export_import_engine')

    local orig_cc = controller_collection.get_instance
    local orig_drives = c_drives_object.get_instance

    local drives_obj = {
        LogAutoCollectEnable = true,
        LogAutoCollectInterval = 24,
    }
    local drives_inst = {
        obj = drives_obj,
        apply_log_auto_collect_enable = function(_, val)
            if val == true or val == 1 then
                drives_obj.LogAutoCollectEnable = true
            elseif val == false or val == 0 then
                drives_obj.LogAutoCollectEnable = false
            else
                return false
            end
            return true
        end,
        apply_log_collect_interval = function(_, val)
            if val < 1 or val > 255 then
                return false
            end
            drives_obj.LogAutoCollectInterval = val
            return true
        end,
    }
    c_drives_object.get_instance = function()
        return drives_inst
    end
    controller_collection.get_instance = function()
        return {
            get_by_controller_id = function()
                return { Id = 0, CopyBackState = 0 }
            end,
            get_all_controllers = function()
                return { { Id = 0, Type = 'SAS3908', CopyBackState = 0 } }
            end,
        }
    end

    import_export_eng.register_config_dealer('RaidController', inst)
    local export_str = import_export_eng.export(make_ctx(), 'configuration')
    lu.assertNotEquals(string.find(export_str, '"DrivesLogAutoCollectEnabled":true'), nil)
    lu.assertNotEquals(string.find(export_str, '"DrivesLogAutoCollectInterval":24'), nil)

    local ok = pcall(inst.import_when_power_on, inst, make_ctx(), {
        Id = { Value = 0 },
        DrivesLogAutoCollectEnabled = { Value = false, Import = true },
        DrivesLogAutoCollectInterval = { Value = 8, Import = true },
    })
    lu.assertTrue(ok)
    lu.assertEquals(drives_obj.LogAutoCollectEnable, false)
    lu.assertEquals(drives_obj.LogAutoCollectInterval, 8)

    controller_collection.get_instance = orig_cc
    c_drives_object.get_instance = orig_drives
end

function TestRpcServiceController:test_import_log_auto_collect_skip_cases()
    local rpc = require('rpc_services.rpc_service_controller')
    local inst = rpc.get_instance and rpc.get_instance() or rpc.new(nil)
    local controller_collection = require('controller.controller_collection')
    local c_drives_object = require('drives.drives_object')

    local orig_cc = controller_collection.get_instance
    local orig_drives = c_drives_object.get_instance

    local drives_obj = {
        LogAutoCollectEnable = true,
        LogAutoCollectInterval = 24,
    }
    local drives_inst = {
        obj = drives_obj,
        apply_log_auto_collect_enable = function(_, val)
            if val == true or val == 1 then
                drives_obj.LogAutoCollectEnable = true
            elseif val == false or val == 0 then
                drives_obj.LogAutoCollectEnable = false
            else
                return false
            end
            return true
        end,
        apply_log_collect_interval = function(_, val)
            if val < 1 or val > 255 then
                return false
            end
            drives_obj.LogAutoCollectInterval = val
            return true
        end,
    }
    c_drives_object.get_instance = function()
        return drives_inst
    end
    controller_collection.get_instance = function()
        return {
            get_by_controller_id = function()
                return { Id = 0, CopyBackState = 0 }
            end,
            get_all_controllers = function()
                return { { Id = 0, Type = 'SAS3908', CopyBackState = 0 } }
            end,
        }
    end

    local ok = pcall(inst.import_when_power_on, inst, make_ctx(), {
        Id = { Value = 0 },
        DrivesLogAutoCollectEnabled = { Value = false, Import = false },
        DrivesLogAutoCollectInterval = { Value = 12, Import = false },
    })
    lu.assertTrue(ok)
    lu.assertEquals(drives_obj.LogAutoCollectEnable, true)
    lu.assertEquals(drives_obj.LogAutoCollectInterval, 24)

    ok = pcall(inst.import_when_power_on, inst, make_ctx(), {
        Id = { Value = 0 },
        DrivesLogAutoCollectEnabled = { Value = true, Import = true },
        DrivesLogAutoCollectInterval = { Value = 24, Import = true },
    })
    lu.assertTrue(ok)
    lu.assertEquals(drives_obj.LogAutoCollectEnable, true)
    lu.assertEquals(drives_obj.LogAutoCollectInterval, 24)

    c_drives_object.get_instance = function()
        return { obj = nil }
    end
    ok = pcall(inst.import_when_power_on, inst, make_ctx(), {
        Id = { Value = 0 },
        DrivesLogAutoCollectEnabled = { Value = false, Import = true },
    })
    lu.assertTrue(ok)

    c_drives_object.get_instance = function()
        return drives_inst
    end
    ok = pcall(inst.import_when_power_on, inst, make_ctx(), {
        Id = { Value = 0 },
        DrivesLogAutoCollectInterval = { Value = 0, Import = true },
    })
    lu.assertTrue(ok)
    lu.assertEquals(drives_obj.LogAutoCollectInterval, 24)

    controller_collection.get_instance = orig_cc
    c_drives_object.get_instance = orig_drives
end

function TestRpcServiceController:test_dump_log_preconditions_and_early_errors()
    reload_dump_log_modules()
    local rpc = require('rpc_services.rpc_service_controller')
    local ctrl_log_dump = require('controller.ctrl_log_dump')
    local method_misc = require('method_misc')
    local c_storageconfig = require('storageconfig.storageconfig_object')
    local common_def = require('common_def')

    local orig_is_lsi = ctrl_log_dump.is_lsi
    local orig_vendor = method_misc.test_controller_vendor
    local orig_cfg = c_storageconfig.get_instance

    local obj = { Id = 1, TypeId = 0, OOBSupport = 0, DeviceName = 'PCIe Card 1', path = '/p' }
    local ok = pcall(rpc.dump_log, obj, make_ctx())
    lu.assertFalse(ok)

    obj.OOBSupport = 1
    ctrl_log_dump.is_lsi = function()
        return true
    end
    ok = pcall(rpc.dump_log, obj, make_ctx())
    lu.assertFalse(ok)
    ctrl_log_dump.is_lsi = orig_is_lsi

    -- config nil 触发保护逻辑(此处代码本身有 and 判断问题,确保不会影响其他用例)
    method_misc.test_controller_vendor = function()
        return false
    end
    c_storageconfig.get_instance = function()
        return { obj = nil }
    end
    ok = pcall(rpc.dump_log, obj, make_ctx())
    lu.assertFalse(ok)

    -- id >= max
    c_storageconfig.get_instance = function()
        return { obj = { StorageConfigReady = common_def.STORAGE_MGNT_STATE.STORAGE_MGNT_READY } }
    end
    obj.Id = 100
    ok = pcall(rpc.dump_log, obj, make_ctx())
    lu.assertFalse(ok)

    c_storageconfig.get_instance = orig_cfg
    method_misc.test_controller_vendor = orig_vendor
end

function TestRpcServiceController:test_set_no_battery_write_cache_and_read_cache_percent_and_wcp()
    local rpc = require('rpc_services.rpc_service_controller')
    local sml = require('sml')
    local common_def = require('common_def')

    local orig_ctrl = sml.ctrl_operation
    sml.ctrl_operation = function()
        return common_def.RET_OK
    end

    local obj = { Id = 1, NoBatteryWriteCacheEnabled = 0, ReadCachePercent = 10, ConfiguredDriveWriteCachePolicy = 'X' }

    -- invalid cache_state
    lu.assertFalse(pcall(rpc.set_no_battery_write_cache, obj, make_ctx(), 2))
    -- same => return
    lu.assertTrue(pcall(rpc.set_no_battery_write_cache, obj, make_ctx(), 0))
    -- fail ret
    sml.ctrl_operation = function()
        return common_def.RET_ERR
    end
    lu.assertFalse(pcall(rpc.set_no_battery_write_cache, obj, make_ctx(), 1))
    -- success sets
    sml.ctrl_operation = function()
        return common_def.RET_OK
    end
    lu.assertTrue(pcall(rpc.set_no_battery_write_cache, obj, make_ctx(), 1))
    lu.assertEquals(obj.NoBatteryWriteCacheEnabled, 1)

    -- read cache percent invalid
    lu.assertFalse(pcall(rpc.set_read_cache_percent, obj, make_ctx(), 101))
    -- same => return
    lu.assertTrue(pcall(rpc.set_read_cache_percent, obj, make_ctx(), obj.ReadCachePercent))
    -- fail
    sml.ctrl_operation = function()
        return common_def.RET_ERR
    end
    lu.assertFalse(pcall(rpc.set_read_cache_percent, obj, make_ctx(), 20))
    -- success
    sml.ctrl_operation = function()
        return common_def.RET_OK
    end
    lu.assertTrue(pcall(rpc.set_read_cache_percent, obj, make_ctx(), 20))
    lu.assertEquals(obj.ReadCachePercent, 20)

    -- write cache policy: invalid type
    lu.assertFalse(pcall(rpc.set_write_cache_policy, obj, make_ctx(), 'Bad', 'Enabled'))
    -- invalid policy
    lu.assertFalse(pcall(rpc.set_write_cache_policy, obj, make_ctx(), 'ConfiguredDriveWriteCachePolicy', 'Bad'))
    -- same => return
    obj.ConfiguredDriveWriteCachePolicy = 'Enabled'
    lu.assertTrue(pcall(rpc.set_write_cache_policy, obj, make_ctx(), 'ConfiguredDriveWriteCachePolicy', 'Enabled'))
    -- fail
    sml.ctrl_operation = function()
        return common_def.RET_ERR
    end
    lu.assertFalse(pcall(rpc.set_write_cache_policy, obj, make_ctx(), 'ConfiguredDriveWriteCachePolicy', 'Disabled'))
    -- success
    sml.ctrl_operation = function()
        return common_def.RET_OK
    end
    lu.assertTrue(pcall(rpc.set_write_cache_policy, obj, make_ctx(), 'ConfiguredDriveWriteCachePolicy', 'Disabled'))
    lu.assertEquals(obj.ConfiguredDriveWriteCachePolicy, 'Disabled')

    sml.ctrl_operation = orig_ctrl
end

function TestRpcServiceController:test_dump_log_success_executes_dump_ctrl_log_and_tar_huawei()
    local common_def = require('common_def')
    local method_misc = require('method_misc')
    local c_storageconfig = require('storageconfig.storageconfig_object')
    local utils_core = require('utils.core')
    local utils = require('mc.utils')
    local sync_task = require('sync_task')
    local storage_bus = require('storage_bus')
    local skynet = require('skynet')
    local sml = require('sml')

    local orig_vendor = method_misc.test_controller_vendor
    local orig_cfg = c_storageconfig.get_instance
    local orig_is_dir = utils_core.is_dir
    local orig_mkdir = utils.mkdir_with_parents
    local orig_tar = utils.tar_zip
    local orig_create = sync_task.create_task
    local orig_update = sync_task.update_task_prop
    local orig_bus = storage_bus.get_instance
    local orig_fork = skynet.fork
    local orig_dump = sml.dump_ctrl_single_log
    local orig_parse = sml.parse_controller_bin_log

    local function restore()
        sml.parse_controller_bin_log = orig_parse
        sml.dump_ctrl_single_log = orig_dump
        skynet.fork = orig_fork
        storage_bus.get_instance = orig_bus
        sync_task.update_task_prop = orig_update
        sync_task.create_task = orig_create
        utils.tar_zip = orig_tar
        utils.mkdir_with_parents = orig_mkdir
        utils_core.is_dir = orig_is_dir
        c_storageconfig.get_instance = orig_cfg
        method_misc.test_controller_vendor = orig_vendor
    end

    local ok, err = pcall(function()
        reload_dump_log_modules()
        local ctrl_log_dump = require('controller.ctrl_log_dump')
        local orig_is_lsi = ctrl_log_dump.is_lsi
        ctrl_log_dump.is_lsi = function()
            return false
        end

        -- vendor: 非 LSI;Huawei=true;PMC=false
        method_misc.test_controller_vendor = function(_, _, vendor)
            if vendor == common_def.VENDER_LSI then
                return false
            end
            if vendor == common_def.VENDER_HUAWEI then
                return true
            end
            if vendor == common_def.VENDER_PMC then
                return false
            end
            return false
        end
        c_storageconfig.get_instance = function()
            return { obj = { StorageConfigReady = common_def.STORAGE_MGNT_STATE.STORAGE_MGNT_READY } }
        end
        utils_core.is_dir = function()
            return false
        end
        utils.mkdir_with_parents = function()
            return true
        end
        utils.tar_zip = function()
            return true
        end
        storage_bus.get_instance = function()
            return { bus = {} }
        end

        local progress_calls = 0
        sync_task.create_task = function()
            return 123
        end
        sync_task.update_task_prop = function(_, _)
            progress_calls = progress_calls + 1
        end

        -- 让 fork 同步执行,避免后台不跑导致覆盖不到
        skynet.fork = function(fn)
            fn()
        end

        -- 覆盖 ok/!ok 两个分支:第一个 index 抛错,其余返回 OK
        sml.dump_ctrl_single_log = function(_, _, idx, _)
            if idx == common_def.CTRL_AP_LOG_TYPE then
                error('stub dump error')
            end
            return common_def.RET_OK
        end
        sml.parse_controller_bin_log = function()
            return common_def.RET_OK
        end

        local rpc = require('rpc_services.rpc_service_controller')
        local obj = {
            Id = 1,
            TypeId = 0,
            OOBSupport = 1,
            ControllerName = 'C',
            DeviceName = 'PCIe Card 1',
            path = '/p',
        }
        local task_id = rpc.dump_log(obj, make_ctx())
        lu.assertEquals(task_id, 123)
        lu.assertTrue(progress_calls > 0)
        ctrl_log_dump.is_lsi = orig_is_lsi
    end)
    restore()
    if not ok then
        error(err)
    end
end

function TestRpcServiceController:test_dump_log_success_executes_dump_ctrl_log_and_tar_pmc()
    local common_def = require('common_def')
    local method_misc = require('method_misc')
    local c_storageconfig = require('storageconfig.storageconfig_object')
    local utils_core = require('utils.core')
    local utils = require('mc.utils')
    local sync_task = require('sync_task')
    local storage_bus = require('storage_bus')
    local skynet = require('skynet')
    local sml = require('sml')

    local orig_vendor = method_misc.test_controller_vendor
    local orig_cfg = c_storageconfig.get_instance
    local orig_is_dir = utils_core.is_dir
    local orig_mkdir = utils.mkdir_with_parents
    local orig_tar = utils.tar_zip
    local orig_create = sync_task.create_task
    local orig_update = sync_task.update_task_prop
    local orig_bus = storage_bus.get_instance
    local orig_fork = skynet.fork
    local orig_dump = sml.dump_ctrl_single_log
    local orig_parse = sml.parse_controller_bin_log

    local function restore()
        sml.parse_controller_bin_log = orig_parse
        sml.dump_ctrl_single_log = orig_dump
        skynet.fork = orig_fork
        storage_bus.get_instance = orig_bus
        sync_task.update_task_prop = orig_update
        sync_task.create_task = orig_create
        utils.tar_zip = orig_tar
        utils.mkdir_with_parents = orig_mkdir
        utils_core.is_dir = orig_is_dir
        c_storageconfig.get_instance = orig_cfg
        method_misc.test_controller_vendor = orig_vendor
    end

    local ok, err = pcall(function()
        reload_dump_log_modules()
        local ctrl_log_dump = require('controller.ctrl_log_dump')
        local orig_is_lsi = ctrl_log_dump.is_lsi
        ctrl_log_dump.is_lsi = function()
            return false
        end

        -- PMC=true, Huawei=false, LSI=false
        method_misc.test_controller_vendor = function(_, _, vendor)
            if vendor == common_def.VENDER_LSI then
                return false
            end
            if vendor == common_def.VENDER_HUAWEI then
                return false
            end
            if vendor == common_def.VENDER_PMC then
                return true
            end
            return false
        end
        c_storageconfig.get_instance = function()
            return { obj = { StorageConfigReady = common_def.STORAGE_MGNT_STATE.STORAGE_MGNT_READY } }
        end
        utils_core.is_dir = function()
            return true
        end
        utils.mkdir_with_parents = function()
            return true
        end
        utils.tar_zip = function()
            return true
        end
        storage_bus.get_instance = function()
            return { bus = {} }
        end
        sync_task.create_task = function()
            return 456
        end
        sync_task.update_task_prop = function() end
        skynet.fork = function(fn)
            fn()
        end
        sml.dump_ctrl_single_log = function()
            return common_def.RET_OK
        end
        sml.parse_controller_bin_log = function()
            return common_def.RET_OK
        end

        local rpc = require('rpc_services.rpc_service_controller')
        local obj = {
            Id = 1,
            TypeId = 0,
            OOBSupport = 1,
            ControllerName = 'C',
            DeviceName = 'PCIe Card 1',
            path = '/p',
        }
        local task_id = rpc.dump_log(obj, make_ctx())
        lu.assertEquals(task_id, 456)
        ctrl_log_dump.is_lsi = orig_is_lsi
    end)
    restore()
    if not ok then
        error(err)
    end
end

function TestRpcServiceController:test_dump_log_success_executes_dump_ctrl_log_and_tar_hrdt()
    local rpc = require('rpc_services.rpc_service_controller')
    local common_def = require('common_def')
    local method_misc = require('method_misc')
    local c_storageconfig = require('storageconfig.storageconfig_object')
    local utils_core = require('utils.core')
    local utils = require('mc.utils')
    local sync_task = require('sync_task')
    local storage_bus = require('storage_bus')
    local skynet = require('skynet')
    local sml = require('sml')

    local orig_vendor = method_misc.test_controller_vendor
    local orig_cfg = c_storageconfig.get_instance
    local orig_is_dir = utils_core.is_dir
    local orig_mkdir = utils.mkdir_with_parents
    local orig_tar = utils.tar_zip
    local orig_create = sync_task.create_task
    local orig_update = sync_task.update_task_prop
    local orig_bus = storage_bus.get_instance
    local orig_fork = skynet.fork
    local orig_dump = sml.dump_ctrl_single_log
    local orig_parse = sml.parse_controller_bin_log

    local function restore()
        sml.parse_controller_bin_log = orig_parse
        sml.dump_ctrl_single_log = orig_dump
        skynet.fork = orig_fork
        storage_bus.get_instance = orig_bus
        sync_task.update_task_prop = orig_update
        sync_task.create_task = orig_create
        utils.tar_zip = orig_tar
        utils.mkdir_with_parents = orig_mkdir
        utils_core.is_dir = orig_is_dir
        c_storageconfig.get_instance = orig_cfg
        method_misc.test_controller_vendor = orig_vendor
    end

    local ok, err = pcall(function()
        method_misc.test_controller_vendor = function(_, _, vendor)
            if vendor == common_def.VENDER_LSI then
                return false
            end
            if vendor == common_def.VENDER_HUAWEI then
                return false
            end
            if vendor == common_def.VENDER_PMC then
                return false
            end
            if vendor == common_def.VENDER_HRDT then
                return true
            end
            return false
        end
        c_storageconfig.get_instance = function()
            return { obj = { StorageConfigReady = common_def.STORAGE_MGNT_STATE.STORAGE_MGNT_READY } }
        end
        utils_core.is_dir = function()
            return true
        end
        utils.mkdir_with_parents = function()
            return true
        end
        utils.tar_zip = function()
            return true
        end
        storage_bus.get_instance = function()
            return { bus = {} }
        end
        sync_task.create_task = function()
            return 789
        end
        sync_task.update_task_prop = function() end
        skynet.fork = function(fn)
            fn()
        end
        sml.dump_ctrl_single_log = function()
            return common_def.RET_OK
        end
        sml.parse_controller_bin_log = function()
            return common_def.RET_OK
        end

        local obj = {
            Id = 1,
            TypeId = 200,
            OOBSupport = 1,
            ControllerName = 'C',
            DeviceName = 'PCIe Card 1',
            path = '/p',
        }
        local task_id = rpc.dump_log(obj, make_ctx())
        lu.assertEquals(task_id, 789)
    end)
    restore()
    if not ok then
        error(err)
    end
end

function TestRpcServiceController:test_dump_log_already_started_branch()
    local common_def = require('common_def')
    local method_misc = require('method_misc')
    local c_storageconfig = require('storageconfig.storageconfig_object')
    local utils_core = require('utils.core')
    local utils = require('mc.utils')
    local sync_task = require('sync_task')
    local storage_bus = require('storage_bus')
    local skynet = require('skynet')

    local orig_vendor = method_misc.test_controller_vendor
    local orig_cfg = c_storageconfig.get_instance
    local orig_is_dir = utils_core.is_dir
    local orig_mkdir = utils.mkdir_with_parents
    local orig_create = sync_task.create_task
    local orig_bus = storage_bus.get_instance
    local orig_fork = skynet.fork

    local function restore()
        skynet.fork = orig_fork
        storage_bus.get_instance = orig_bus
        sync_task.create_task = orig_create
        utils.mkdir_with_parents = orig_mkdir
        utils_core.is_dir = orig_is_dir
        c_storageconfig.get_instance = orig_cfg
        method_misc.test_controller_vendor = orig_vendor
    end

    local ok, err = pcall(function()
        reload_dump_log_modules()
        local ctrl_log_dump = require('controller.ctrl_log_dump')
        local orig_is_lsi = ctrl_log_dump.is_lsi
        ctrl_log_dump.is_lsi = function()
            return false
        end

        method_misc.test_controller_vendor = function(_, _, vendor)
            if vendor == common_def.VENDER_LSI then
                return false
            end
            return false
        end
        c_storageconfig.get_instance = function()
            return { obj = { StorageConfigReady = common_def.STORAGE_MGNT_STATE.STORAGE_MGNT_READY } }
        end
        utils_core.is_dir = function()
            return true
        end
        utils.mkdir_with_parents = function()
            return true
        end
        storage_bus.get_instance = function()
            return { bus = {} }
        end
        sync_task.create_task = function()
            return 1
        end

        -- fork 不执行,让 dump_flag_table[obj.Id] 保持为 1
        skynet.fork = function(_) end

        local rpc = require('rpc_services.rpc_service_controller')
        local obj =
            { Id = 1, TypeId = 0, OOBSupport = 1, ControllerName = 'C', DeviceName = 'PCIe Card 1', path = '/p' }
        local ok1, task_id = pcall(rpc.dump_log, obj, make_ctx())
        lu.assertTrue(ok1)
        lu.assertEquals(task_id, 1)

        local ok2 = pcall(rpc.dump_log, obj, make_ctx())
        lu.assertFalse(ok2)
        ctrl_log_dump.is_lsi = orig_is_lsi
    end)
    restore()
    if not ok then
        error(err)
    end
end

function TestRpcServiceController:test_dump_log_dump_ctrl_log_failure_branch()
    local common_def = require('common_def')
    local method_misc = require('method_misc')
    local c_storageconfig = require('storageconfig.storageconfig_object')
    local utils_core = require('utils.core')
    local utils = require('mc.utils')
    local sync_task = require('sync_task')
    local storage_bus = require('storage_bus')
    local skynet = require('skynet')
    local sml = require('sml')

    local orig_vendor = method_misc.test_controller_vendor
    local orig_cfg = c_storageconfig.get_instance
    local orig_is_dir = utils_core.is_dir
    local orig_tar = utils.tar_zip
    local orig_create = sync_task.create_task
    local orig_update = sync_task.update_task_prop
    local orig_bus = storage_bus.get_instance
    local orig_fork = skynet.fork
    local orig_dump = sml.dump_ctrl_single_log

    local function restore()
        sml.dump_ctrl_single_log = orig_dump
        skynet.fork = orig_fork
        storage_bus.get_instance = orig_bus
        sync_task.update_task_prop = orig_update
        sync_task.create_task = orig_create
        utils.tar_zip = orig_tar
        utils_core.is_dir = orig_is_dir
        c_storageconfig.get_instance = orig_cfg
        method_misc.test_controller_vendor = orig_vendor
    end

    local ok, err = pcall(function()
        reload_dump_log_modules()
        local ctrl_log_dump = require('controller.ctrl_log_dump')
        local orig_is_lsi = ctrl_log_dump.is_lsi
        ctrl_log_dump.is_lsi = function()
            return false
        end

        method_misc.test_controller_vendor = function(_, _, vendor)
            if vendor == common_def.VENDER_LSI then
                return false
            end
            if vendor == common_def.VENDER_PMC then
                return false
            end
            if vendor == common_def.VENDER_HUAWEI then
                return false
            end
            return false
        end
        c_storageconfig.get_instance = function()
            return { obj = { StorageConfigReady = common_def.STORAGE_MGNT_STATE.STORAGE_MGNT_READY } }
        end
        utils_core.is_dir = function()
            return true
        end
        utils.tar_zip = function()
            return false
        end
        storage_bus.get_instance = function()
            return { bus = {} }
        end
        sync_task.create_task = function()
            return 9
        end
        sync_task.update_task_prop = function() end
        skynet.fork = function(fn)
            fn()
        end
        sml.dump_ctrl_single_log = function()
            return common_def.RET_ERR
        end

        local rpc = require('rpc_services.rpc_service_controller')
        local obj =
            { Id = 1, TypeId = 0, OOBSupport = 1, ControllerName = 'C', DeviceName = 'PCIe Card 1', path = '/p' }
        local ok_call = pcall(rpc.dump_log, obj, make_ctx())
        lu.assertTrue(ok_call)
        ctrl_log_dump.is_lsi = orig_is_lsi
    end)
    restore()
    if not ok then
        error(err)
    end
end

-- 覆盖 dump_log:create_task 失败分支 (line 1513)
function TestRpcServiceController:test_dump_log_create_task_failed()
    local common_def = require('common_def')
    local method_misc = require('method_misc')
    local c_storageconfig = require('storageconfig.storageconfig_object')
    local utils_core = require('utils.core')
    local sync_task = require('sync_task')
    local storage_bus = require('storage_bus')

    local orig_vendor = method_misc.test_controller_vendor
    local orig_cfg = c_storageconfig.get_instance
    local orig_is_dir = utils_core.is_dir
    local orig_create = sync_task.create_task
    local orig_bus = storage_bus.get_instance

    local function restore()
        storage_bus.get_instance = orig_bus
        sync_task.create_task = orig_create
        utils_core.is_dir = orig_is_dir
        c_storageconfig.get_instance = orig_cfg
        method_misc.test_controller_vendor = orig_vendor
    end

    local ok, err = pcall(function()
        reload_dump_log_modules()
        local ctrl_log_dump = require('controller.ctrl_log_dump')
        local orig_is_lsi = ctrl_log_dump.is_lsi
        ctrl_log_dump.is_lsi = function()
            return false
        end

        method_misc.test_controller_vendor = function()
            return false
        end
        c_storageconfig.get_instance = function()
            return { obj = { StorageConfigReady = common_def.STORAGE_MGNT_STATE.STORAGE_MGNT_READY } }
        end
        utils_core.is_dir = function()
            return true
        end
        storage_bus.get_instance = function()
            return { bus = {} }
        end
        -- create_task 抛错触发 line 1513
        sync_task.create_task = function()
            error('create task failed')
        end

        local rpc = require('rpc_services.rpc_service_controller')
        local obj =
            { Id = 2, TypeId = 0, OOBSupport = 1, ControllerName = 'C', DeviceName = 'PCIe Card 1', path = '/p' }
        -- pcall 捕获 error(CurrentStatusNotSupportOperation())
        local ok_call = pcall(rpc.dump_log, obj, make_ctx())
        lu.assertFalse(ok_call)
        ctrl_log_dump.is_lsi = orig_is_lsi
    end)
    restore()
    if not ok then
        error(err)
    end
end

function TestRpcServiceController:test_import_per_controller_flag()
    local rpc = require('rpc_services.rpc_service_controller')
    local inst = rpc.get_instance and rpc.get_instance() or rpc.new(nil)

    -- mock mc.orm.tasks 链式调用
    local tasks = require('mc.orm.tasks')
    local orig_tasks_get_instance = tasks.get_instance
    local loop_cbs = {}
    local created_names = {}
    local task_mock = {}
    task_mock.loop = function(_, cb)
        loop_cbs[task_mock.name] = cb
        return task_mock
    end
    task_mock.set_timeout_ms = function(_, _)
        return task_mock
    end
    task_mock.stop = function() end
    tasks.get_instance = function()
        return {
            new_task = function(_, name)
                created_names[#created_names + 1] = name
                task_mock.name = name
                return task_mock
            end,
        }
    end

    -- mock storageconfig,保证 loop 回调中 StorageConfigReady 就绪
    local storageconfig = require('storageconfig.storageconfig_object')
    local orig_sc_get_instance = storageconfig.get_instance
    storageconfig.get_instance = function()
        return { obj = { StorageConfigReady = 1 } }
    end

    -- mock import_when_power_on,避免真实执行
    local orig_import_when = inst.import_when_power_on
    inst.import_when_power_on = function() end

    -- 首次导入卡0:创建 task wait_import_config:0
    inst:import(make_ctx(), { Id = { Value = 0 } })
    lu.assertEquals(#created_names, 1)
    lu.assertEquals(created_names[1], 'wait_import_config:0')

    -- 重复导入卡0:被 per-controller flag 拦截,不创建新 task
    inst:import(make_ctx(), { Id = { Value = 0 } })
    lu.assertEquals(#created_names, 1)

    -- 卡1导入:不受卡0影响
    inst:import(make_ctx(), { Id = { Value = 1 } })
    lu.assertEquals(#created_names, 2)
    lu.assertEquals(created_names[2], 'wait_import_config:1')

    -- 执行卡0的 loop 回调(StorageConfigReady=1):卡0 flag 复位 + task stop
    local stop_called = false
    task_mock.stop = function()
        stop_called = true
    end
    loop_cbs['wait_import_config:0'](task_mock)
    lu.assertTrue(stop_called)

    -- 复位后卡0可再次导入
    inst:import(make_ctx(), { Id = { Value = 0 } })
    lu.assertEquals(#created_names, 3)

    -- 恢复
    tasks.get_instance = orig_tasks_get_instance
    storageconfig.get_instance = orig_sc_get_instance
    inst.import_when_power_on = orig_import_when
end

-- 导入等待分支: 未就绪时不执行导入且互斥, 就绪后执行一次并复位标志
function TestRpcServiceController:test_import_wait_not_ready_then_ready()
    local rpc = require('rpc_services.rpc_service_controller')
    local inst = rpc.get_instance and rpc.get_instance() or rpc.new(nil)

    local tasks = require('mc.orm.tasks')
    local orig_tasks_get_instance = tasks.get_instance
    local loop_cbs = {}
    local created_names = {}
    local task_mock = {}
    task_mock.loop = function(_, cb)
        loop_cbs[task_mock.name] = cb
        return task_mock
    end
    task_mock.set_timeout_ms = function(_, _)
        return task_mock
    end
    task_mock.stop = function() end
    tasks.get_instance = function()
        return {
            new_task = function(_, name)
                created_names[#created_names + 1] = name
                task_mock.name = name
                return task_mock
            end,
        }
    end

    -- ready 状态可控的 storageconfig mock
    local storageconfig = require('storageconfig.storageconfig_object')
    local orig_sc_get_instance = storageconfig.get_instance
    local sc_mock = { obj = { StorageConfigReady = 0 } }
    storageconfig.get_instance = function()
        return sc_mock
    end

    local import_calls = 0
    local orig_import_when = inst.import_when_power_on
    inst.import_when_power_on = function()
        import_calls = import_calls + 1
    end

    -- 未就绪首轮: 只等待, 不执行导入
    inst:import(make_ctx(), { Id = { Value = 0 } })
    loop_cbs['wait_import_config:0'](task_mock)
    lu.assertEquals(import_calls, 0)

    -- 未就绪期间重复导入: 被互斥标志拦截, 不创建新 task
    inst:import(make_ctx(), { Id = { Value = 0 } })
    lu.assertEquals(#created_names, 1)

    -- 就绪后: 执行一次导入并复位标志
    sc_mock.obj.StorageConfigReady = 1
    local stop_called = false
    task_mock.stop = function()
        stop_called = true
    end
    loop_cbs['wait_import_config:0'](task_mock)
    lu.assertEquals(import_calls, 1)
    lu.assertTrue(stop_called)

    -- 复位后可再次导入
    inst:import(make_ctx(), { Id = { Value = 0 } })
    lu.assertEquals(#created_names, 2)

    tasks.get_instance = orig_tasks_get_instance
    storageconfig.get_instance = orig_sc_get_instance
    inst.import_when_power_on = orig_import_when
end

-- 导入执行异常: import_when_power_on 抛错被 pcall 捕获, 标志仍复位、task 仍停止
function TestRpcServiceController:test_import_when_power_on_error_still_reset()
    local rpc = require('rpc_services.rpc_service_controller')
    local inst = rpc.get_instance and rpc.get_instance() or rpc.new(nil)

    local tasks = require('mc.orm.tasks')
    local orig_tasks_get_instance = tasks.get_instance
    local loop_cbs = {}
    local created_names = {}
    local task_mock = {}
    task_mock.loop = function(_, cb)
        loop_cbs[task_mock.name] = cb
        return task_mock
    end
    task_mock.set_timeout_ms = function(_, _)
        return task_mock
    end
    task_mock.stop = function() end
    tasks.get_instance = function()
        return {
            new_task = function(_, name)
                created_names[#created_names + 1] = name
                task_mock.name = name
                return task_mock
            end,
        }
    end

    local storageconfig = require('storageconfig.storageconfig_object')
    local orig_sc_get_instance = storageconfig.get_instance
    storageconfig.get_instance = function()
        return { obj = { StorageConfigReady = 1 } }
    end

    local orig_import_when = inst.import_when_power_on
    inst.import_when_power_on = function()
        error('import boom')
    end

    inst:import(make_ctx(), { Id = { Value = 0 } })
    local stop_called = false
    task_mock.stop = function()
        stop_called = true
    end
    -- 异常被 pcall 捕获, 不会向外抛出
    loop_cbs['wait_import_config:0'](task_mock)
    lu.assertTrue(stop_called)

    -- 标志已复位: 可再次导入
    inst:import(make_ctx(), { Id = { Value = 0 } })
    lu.assertEquals(#created_names, 2)

    tasks.get_instance = orig_tasks_get_instance
    storageconfig.get_instance = orig_sc_get_instance
    inst.import_when_power_on = orig_import_when
end