-- Copyright (c) 2025 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.

local storage_app = require('storage_app')
local file_sec = require('utils.file')
local vos = require('utils.vos')
local utils = require('mc.utils')
local controller_collection = require('controller.controller_collection')
local bus_monitor_service = require('bus_monitor_service')
local common_def = require('common_def')
local mdb_config_manage = require('mc.mdb.micro_component.config_manage')
local log = require('mc.logging')
local drive_collection = require('drive.drive_collection')
local drive_config_backup = require('drive.drive_config_backup')
local ctrl_config_backup = require('controller.ctrl_config_backup')
local lu = require('luaunit')

TestStorageApp = {}

local orig_file_sec = {}
local orig_vos = {}
local orig_utils = {}
local orig_cc_get_instance
local orig_bm_get_instance
local orig_mdb_config_on_import
local orig_mdb_config_on_export
local orig_mdb_config_on_recover
local orig_log_operation
local orig_dc_get_instance
local orig_dcb_get_instance
local orig_ccb_get_instance
local op_logs = {}

function TestStorageApp:setUp()
    orig_file_sec.check_real_path_s = file_sec.check_real_path_s
    orig_file_sec.open_s = file_sec.open_s
    orig_file_sec.check_realpath_before_open_s = file_sec.check_realpath_before_open_s
    orig_vos.system_s = vos.system_s
    orig_utils.mkdir_with_parents = utils.mkdir_with_parents
    orig_cc_get_instance = controller_collection.get_instance
    orig_bm_get_instance = bus_monitor_service.get_instance
    orig_mdb_config_on_import = mdb_config_manage.on_import
    orig_mdb_config_on_export = mdb_config_manage.on_export
    orig_mdb_config_on_recover = mdb_config_manage.on_recover
    orig_log_operation = log.operation
    orig_dc_get_instance = drive_collection.get_instance
    orig_dcb_get_instance = drive_config_backup.get_instance
    orig_ccb_get_instance = ctrl_config_backup.get_instance
    op_logs = {}
    log.operation = function(_, initiator, component, fmt, ...)
        table.insert(op_logs, { initiator = initiator, component = component, fmt = fmt, args = { ... } })
    end
end

function TestStorageApp:tearDown()
    file_sec.check_real_path_s = orig_file_sec.check_real_path_s
    file_sec.open_s = orig_file_sec.open_s
    file_sec.check_realpath_before_open_s = orig_file_sec.check_realpath_before_open_s
    vos.system_s = orig_vos.system_s
    utils.mkdir_with_parents = orig_utils.mkdir_with_parents
    controller_collection.get_instance = orig_cc_get_instance
    bus_monitor_service.get_instance = orig_bm_get_instance
    mdb_config_manage.on_import = orig_mdb_config_on_import
    mdb_config_manage.on_export = orig_mdb_config_on_export
    mdb_config_manage.on_recover = orig_mdb_config_on_recover
    log.operation = orig_log_operation
    drive_collection.get_instance = orig_dc_get_instance
    drive_config_backup.get_instance = orig_dcb_get_instance
    ctrl_config_backup.get_instance = orig_ccb_get_instance
end

-- Cover on_reboot_prepare (line 275)
function TestStorageApp:test_on_reboot_prepare()
    local ok = pcall(storage_app.on_reboot_prepare, {})
    lu.assertTrue(ok)
end

-- Cover on_reboot_cancel (line 279)
function TestStorageApp:test_on_reboot_cancel()
    local ok = pcall(storage_app.on_reboot_cancel, {})
    lu.assertTrue(ok)
end

-- Cover on_reboot_action (line 283)
function TestStorageApp:test_on_reboot_action()
    local ok = pcall(storage_app.on_reboot_action, {})
    lu.assertTrue(ok)
end

-- Cover dump_log line 164: CTRL_LOG_BASE_PATH path exists
function TestStorageApp:test_dump_log_ctrl_log_base_exists()
    file_sec.check_real_path_s = function(path)
        if path == common_def.CTRL_LOG_BASE_PATH then
            return common_def.RET_OK
        end
        return common_def.RET_ERR
    end
    local mock_fp = { write = function() end, close = function() end }
    file_sec.open_s = function(path, mode)
        return mock_fp
    end
    file_sec.check_realpath_before_open_s = function(path)
        return common_def.RET_OK
    end
    vos.system_s = function() end
    utils.mkdir_with_parents = function() end
    controller_collection.get_instance = function()
        return { dump_controller_logs = function() end }
    end
    bus_monitor_service.get_instance = function()
        return { dump_objs_info = function() end }
    end

    local mock_self = {
        drive_collection = {
            nvme_list = {},
            get_all_drives = function()
                return {}
            end,
        },
    }
    local ok = pcall(storage_app.dump_log, mock_self, nil, '/tmp/test_output')
    lu.assertTrue(ok)
end

-- Cover dump_log: CTRL_LOG_BASE_PATH does not exist -> else branch (mkdir)
function TestStorageApp:test_dump_log_ctrl_log_base_not_exists()
    file_sec.check_real_path_s = function(path)
        return common_def.RET_ERR
    end
    local mock_fp = { write = function() end, close = function() end }
    file_sec.open_s = function(path, mode)
        return mock_fp
    end
    file_sec.check_realpath_before_open_s = function(path)
        return common_def.RET_OK
    end
    vos.system_s = function() end
    utils.mkdir_with_parents = function() end
    controller_collection.get_instance = function()
        return { dump_controller_logs = function() end }
    end
    bus_monitor_service.get_instance = function()
        return { dump_objs_info = function() end }
    end

    local mock_self = {
        drive_collection = {
            nvme_list = {},
            get_all_drives = function()
                return {}
            end,
        },
    }
    local ok = pcall(storage_app.dump_log, mock_self, nil, '/tmp/test_output')
    lu.assertTrue(ok)
end

-- Cover dump_log: open file fails -> early return
function TestStorageApp:test_dump_log_open_file_fail()
    file_sec.check_real_path_s = function(path)
        return common_def.RET_OK
    end
    file_sec.open_s = function(path, mode)
        return nil, 'permission denied'
    end
    vos.system_s = function() end
    utils.mkdir_with_parents = function() end
    controller_collection.get_instance = function()
        return { dump_controller_logs = function() end }
    end
    bus_monitor_service.get_instance = function()
        return { dump_objs_info = function() end }
    end

    local mock_self = {
        drive_collection = {
            nvme_list = {},
            get_all_drives = function()
                return {}
            end,
        },
    }
    local ok = pcall(storage_app.dump_log, mock_self, nil, '/tmp/test_output')
    lu.assertTrue(ok)
end

-- Helper: create mock self with Impl methods that capture callbacks
local function make_mock_self_for_rpc()
    local captured = {}
    local mock = {
        rpc_service_controller = {
            ctrl_operate = function()
                return 0
            end,
        },
        controller_collection = {
            intf_map = { ['obj1'] = { Id = 0 } },
        },
    }
    -- Mock all Impl... methods to capture the callback
    mock.ImplStorageConfigReleaseStorageControllerGetControllerInfo = function(self, cb)
        table.insert(captured, cb)
    end
    mock.ImplControllerSystemsStorageControllerSetCopybackState = function(self, cb)
        table.insert(captured, { name = 'SetCopyBackState', cb = cb })
    end
    mock.ImplControllerSystemsStorageControllerSetJBODState = function(self, cb)
        table.insert(captured, { name = 'SetJBODState', cb = cb })
    end
    mock.ImplControllerSystemsStorageControllerSetSmarterCopyBackState = function(self, cb)
        table.insert(captured, { name = 'SetSmarterCopyBackState', cb = cb })
    end
    mock.ImplControllerSystemsStorageControllerSetMaintainPDFailHistoryState = function(self, cb)
        table.insert(captured, { name = 'SetMaintainPDFailHistoryState', cb = cb })
    end
    mock.ImplControllerSystemsStorageControllerRestoreDefaultSettings = function(self, cb)
        table.insert(captured, { name = 'RestoreDefaultSettings', cb = cb })
    end
    mock.ImplControllerSystemsStorageControllerSetBootDevices = function(self, cb)
        table.insert(captured, { name = 'SetBootDevices', cb = cb })
    end
    mock.ImplControllerSystemsStorageControllerSetBootDevicesV2 = function(self, cb)
        table.insert(captured, { name = 'SetBootDevicesV2', cb = cb })
    end
    mock.ImplControllerSystemsStorageControllerSetWorkMode = function(self, cb)
        table.insert(captured, { name = 'SetWorkMode', cb = cb })
    end
    mock.ImplControllerSystemsStorageControllerClearForeignConfig = function(self, cb)
        table.insert(captured, { name = 'ClearForeignConfig', cb = cb })
    end
    mock.ImplControllerSystemsStorageControllerImportForeignConfig = function(self, cb)
        table.insert(captured, { name = 'ImportForeignConfig', cb = cb })
    end
    mock.ImplControllerSystemsStorageControllerSetNoBatteryWriteCache = function(self, cb)
        table.insert(captured, { name = 'SetNoBatteryWriteCache', cb = cb })
    end
    mock.ImplControllerSystemsStorageControllerSetReadCachePercent = function(self, cb)
        table.insert(captured, { name = 'SetReadCachePercent', cb = cb })
    end
    mock.ImplControllerSystemsStorageControllerSetWriteCachePolicy = function(self, cb)
        table.insert(captured, { name = 'SetWriteCachePolicy', cb = cb })
    end
    mock.ImplControllerSystemsStorageControllerDumpLog = function(self, cb)
        table.insert(captured, { name = 'DumpLog', cb = cb })
    end
    mock.ImplControllerRetirementDataWipe = function(self, cb)
        table.insert(captured, { name = 'RetirementDataWipe', cb = cb })
    end
    mock.ImplControllerRetirementGetReport = function(self, cb)
        table.insert(captured, { name = 'RetirementGetReport', cb = cb })
    end
    mock.ImplControllerConsistencyCheckEnable = function(self, cb)
        table.insert(captured, { name = 'EnableCCheck', cb = cb })
    end
    mock.ImplControllerConsistencyCheckDisable = function(self, cb)
        table.insert(captured, { name = 'DisableCCheck', cb = cb })
    end
    mock.ImplControllerConsistencyCheckSetParameters = function(self, cb)
        table.insert(captured, { name = 'SetCCheck', cb = cb })
    end
    mock.ImplControllerConfigurationRecover = function(self, cb)
        table.insert(captured, { name = 'ConfigurationRecover', cb = cb })
    end
    return mock, captured
end

-- Cover register_ctrl_rpc lines 461-525
function TestStorageApp:test_register_ctrl_rpc()
    mdb_config_manage.on_import = function(cb) end
    mdb_config_manage.on_export = function(cb) end
    mdb_config_manage.on_recover = function(cb) end

    local mock_self, captured = make_mock_self_for_rpc()
    storage_app.register_ctrl_rpc(mock_self)
    lu.assertTrue(#captured >= 10)
end

-- Cover register_ctrl_rpc: invoke callbacks to cover lambda bodies
function TestStorageApp:test_register_ctrl_rpc_invoke_callbacks()
    mdb_config_manage.on_import = function(cb) end
    mdb_config_manage.on_export = function(cb) end
    mdb_config_manage.on_recover = function(cb) end

    local mock_self = make_mock_self_for_rpc()
    -- Override to capture and call callbacks
    local callbacks = {}
    local function capture_cb(name)
        return function(self, cb)
            callbacks[name] = cb
        end
    end
    mock_self.ImplStorageConfigReleaseStorageControllerGetControllerInfo = capture_cb('GetControllerInfo')
    mock_self.ImplControllerSystemsStorageControllerSetCopybackState = capture_cb('SetCopyBackState')
    mock_self.ImplControllerSystemsStorageControllerSetJBODState = capture_cb('SetJBODState')
    mock_self.ImplControllerSystemsStorageControllerSetSmarterCopyBackState = capture_cb('SetSmarterCopyBackState')
    mock_self.ImplControllerSystemsStorageControllerSetMaintainPDFailHistoryState =
        capture_cb('SetMaintainPDFailHistoryState')
    mock_self.ImplControllerSystemsStorageControllerRestoreDefaultSettings = capture_cb('RestoreDefaultSettings')
    mock_self.ImplControllerSystemsStorageControllerSetBootDevices = capture_cb('SetBootDevices')
    mock_self.ImplControllerSystemsStorageControllerSetBootDevicesV2 = capture_cb('SetBootDevicesV2')
    mock_self.ImplControllerSystemsStorageControllerSetWorkMode = capture_cb('SetWorkMode')
    mock_self.ImplControllerSystemsStorageControllerClearForeignConfig = capture_cb('ClearForeignConfig')
    mock_self.ImplControllerSystemsStorageControllerImportForeignConfig = capture_cb('ImportForeignConfig')
    mock_self.ImplControllerSystemsStorageControllerSetNoBatteryWriteCache = capture_cb('SetNoBatteryWriteCache')
    mock_self.ImplControllerSystemsStorageControllerSetReadCachePercent = capture_cb('SetReadCachePercent')
    mock_self.ImplControllerSystemsStorageControllerSetWriteCachePolicy = capture_cb('SetWriteCachePolicy')
    mock_self.ImplControllerSystemsStorageControllerDumpLog = capture_cb('DumpLog')
    mock_self.ImplControllerConfigurationRecover = capture_cb('ConfigurationRecover')

    storage_app.register_ctrl_rpc(mock_self)

    -- Invoke captured callbacks to cover lambda body lines
    local mock_obj = { Id = 0, path = '/test', ComponentName = 'RAIDCard0' }
    local mock_ctx = {}
    if callbacks.SetCopyBackState then
        pcall(callbacks.SetCopyBackState, mock_obj, mock_ctx, 1)
    end
    if callbacks.SetJBODState then
        pcall(callbacks.SetJBODState, mock_obj, mock_ctx, 1)
    end
    if callbacks.SetSmarterCopyBackState then
        pcall(callbacks.SetSmarterCopyBackState, mock_obj, mock_ctx, 1)
    end
    if callbacks.SetMaintainPDFailHistoryState then
        pcall(callbacks.SetMaintainPDFailHistoryState, mock_obj, mock_ctx, 1)
    end
    if callbacks.RestoreDefaultSettings then
        pcall(callbacks.RestoreDefaultSettings, mock_obj, mock_ctx)
    end
    if callbacks.SetBootDevices then
        pcall(callbacks.SetBootDevices, mock_obj, mock_ctx, 'None', 'None')
    end
    if callbacks.SetBootDevicesV2 then
        pcall(callbacks.SetBootDevicesV2, mock_obj, mock_ctx, {})
    end
    if callbacks.SetWorkMode then
        pcall(callbacks.SetWorkMode, mock_obj, mock_ctx, 0)
    end
    if callbacks.ClearForeignConfig then
        pcall(callbacks.ClearForeignConfig, mock_obj, mock_ctx)
    end
    if callbacks.ImportForeignConfig then
        pcall(callbacks.ImportForeignConfig, mock_obj, mock_ctx)
    end
    if callbacks.SetNoBatteryWriteCache then
        pcall(callbacks.SetNoBatteryWriteCache, mock_obj, mock_ctx, 1)
    end
    if callbacks.SetReadCachePercent then
        pcall(callbacks.SetReadCachePercent, mock_obj, mock_ctx, 50)
    end
    if callbacks.SetWriteCachePolicy then
        pcall(callbacks.SetWriteCachePolicy, mock_obj, mock_ctx, 'ConfiguredDriveWriteCachePolicy', 'WriteBack')
    end
    if callbacks.DumpLog then
        pcall(callbacks.DumpLog, mock_obj, mock_ctx)
    end
    if callbacks.GetControllerInfo then
        pcall(callbacks.GetControllerInfo, mock_obj, mock_ctx, 0)
    end
    -- ConfigurationRecover 的 lambda 内部访问 controller_collection.get_instance 与 ctrl_config_backup,
    -- 这里打桩让 get_by_controller_device_name 返回 nil,覆盖"控制器不存在"提前返回分支
    controller_collection.get_instance = function()
        return {
            get_by_controller_device_name = function()
                return nil
            end,
        }
    end
    if callbacks.ConfigurationRecover then
        pcall(callbacks.ConfigurationRecover, mock_obj, mock_ctx)
    end
    lu.assertNotNil(callbacks.SetCopyBackState)
end

-- Cover backup_drive_info lines 562-609
function TestStorageApp:test_backup_drive_info()
    local saved_records = {}
    local mock_self = {
        reset_local_db = {
            DriveBackup = function(data)
                local rec = {
                    save = function()
                        table.insert(saved_records, data)
                    end,
                }
                return rec
            end,
        },
        drive_collection = {
            get_all_drives = function()
                return {
                    {
                        Id = 1,
                        Protocol = 'SAS',
                        Revision = '1.0',
                        CapacityMiB = 100,
                        Model = 'M1',
                        SerialNumber = 'SN1',
                        MediaType = 'SSD',
                        NegotiatedSpeedGbs = 6,
                        CapableSpeedGbs = 12,
                        Manufacturer = 'Huawei',
                    },
                }
            end,
        },
    }
    storage_app.backup_drive_info(mock_self)
    lu.assertEquals(#saved_records, 1)
    lu.assertEquals(saved_records[1].Id, 1)
end

-- Cover backup_drive_info: no drives
function TestStorageApp:test_backup_drive_info_empty()
    local mock_self = {
        reset_local_db = {
            DriveBackup = function(data)
                return { save = function() end }
            end,
        },
        drive_collection = {
            get_all_drives = function()
                return {}
            end,
        },
    }
    storage_app.backup_drive_info(mock_self)
    lu.assertTrue(true)
end

-- Cover register_life_cycle_retirement lines 583-595
function TestStorageApp:test_register_life_cycle_retirement()
    local callbacks = {}
    local mock_self = {
        rpc_service_controller = {
            ctrl_operate = function()
                return 0
            end,
        },
        controller_collection = { intf_map = { ['obj1'] = { Id = 0 } } },
        ImplControllerRetirementDataWipe = function(self, cb)
            callbacks.data_wipe = cb
        end,
        ImplControllerRetirementGetReport = function(self, cb)
            callbacks.get_report = cb
        end,
    }
    storage_app.register_life_cycle_retirement(mock_self)

    -- Invoke callbacks
    if callbacks.data_wipe then
        pcall(callbacks.data_wipe, 'obj1', nil)
    end
    if callbacks.get_report then
        pcall(callbacks.get_report, 'obj1', nil)
    end
    lu.assertNotNil(callbacks.data_wipe)
    lu.assertNotNil(callbacks.get_report)
end

-- Cover register_ctrl_rpc_consistency_check lines 597-616
function TestStorageApp:test_register_ctrl_rpc_consistency_check()
    local callbacks = {}
    local mock_self = {
        rpc_service_controller = {
            ctrl_operate = function()
                return 0
            end,
        },
        controller_collection = { intf_map = { ['obj1'] = { Id = 0 } } },
        ImplControllerConsistencyCheckEnable = function(self, cb)
            callbacks.enable = cb
        end,
        ImplControllerConsistencyCheckDisable = function(self, cb)
            callbacks.disable = cb
        end,
        ImplControllerConsistencyCheckSetParameters = function(self, cb)
            callbacks.set = cb
        end,
    }
    storage_app.register_ctrl_rpc_consistency_check(mock_self)

    -- Invoke callbacks
    if callbacks.enable then
        pcall(callbacks.enable, 'obj1', nil, 48, 1, 1, 1)
    end
    if callbacks.disable then
        pcall(callbacks.disable, 'obj1', nil)
    end
    if callbacks.set then
        pcall(callbacks.set, 'obj1', nil, 48, 1, 1)
    end
    lu.assertNotNil(callbacks.enable)
    lu.assertNotNil(callbacks.disable)
    lu.assertNotNil(callbacks.set)
end

-- 生成自动捕获 Impl 回调的 mock self(register_drive_rpc / register_ctrl_rpc 均只注册 Impl* 方法)
local function make_rpc_mock_self()
    local callbacks = {}
    local mock = setmetatable({}, {
        __index = function(_, name)
            if type(name) == 'string' and name:sub(1, 4) == 'Impl' then
                return function(_, cb)
                    callbacks[name] = cb
                end
            end
        end,
    })
    return mock, callbacks
end

-- Cover 硬盘配置恢复成功路径:找到硬盘且恢复成功,记录成功操作日志(storage_app.lua 470-479)
function TestStorageApp:test_register_drive_rpc_configuration_recover_success()
    drive_collection.get_instance = function()
        return {
            get_drive = function(_, name)
                return { Name = name }
            end,
        }
    end
    drive_config_backup.get_instance = function()
        return {
            recover = function()
                return true
            end,
        }
    end

    local mock_self, callbacks = make_rpc_mock_self()
    storage_app.register_drive_rpc(mock_self)

    local cb = callbacks['ImplDriveConfigurationRecover']
    lu.assertNotNil(cb)
    local ctx = {
        get_initiator = function()
            return 'test_initiator'
        end,
    }
    lu.assertTrue(pcall(cb, { ComponentName = 'Disk0' }, ctx))
    lu.assertEquals(#op_logs, 1)
    lu.assertEquals(op_logs[1].initiator, 'test_initiator')
    lu.assertEquals(op_logs[1].component, 'storage')
    lu.assertStrContains(op_logs[1].fmt, 'Recover drive %s configuration %s')
    lu.assertEquals(op_logs[1].args[1], 'Disk0')
    lu.assertEquals(op_logs[1].args[2], 'successfully')
end

-- Cover 硬盘配置恢复失败路径:recover 返回 false,记录失败操作日志并上报 partially failed
function TestStorageApp:test_register_drive_rpc_configuration_recover_failed()
    drive_collection.get_instance = function()
        return {
            get_drive = function(_, name)
                return { Name = name }
            end,
        }
    end
    drive_config_backup.get_instance = function()
        return {
            recover = function()
                return false
            end,
        }
    end

    local mock_self, callbacks = make_rpc_mock_self()
    storage_app.register_drive_rpc(mock_self)

    local cb = callbacks['ImplDriveConfigurationRecover']
    lu.assertNotNil(cb)
    local ctx = {
        get_initiator = function()
            return 'test_initiator'
        end,
    }
    local ok, err = pcall(cb, { ComponentName = 'Disk0' }, ctx)
    lu.assertFalse(ok)
    lu.assertEquals(err, 'partially failed')
    lu.assertEquals(#op_logs, 1)
    lu.assertEquals(op_logs[1].args[1], 'Disk0')
    lu.assertEquals(op_logs[1].args[2], 'failed')
end

-- Cover 硬盘配置恢复:硬盘不存在时提前返回,不记录操作日志
function TestStorageApp:test_register_drive_rpc_configuration_recover_not_found()
    drive_collection.get_instance = function()
        return {
            get_drive = function()
                return nil
            end,
        }
    end

    local mock_self, callbacks = make_rpc_mock_self()
    storage_app.register_drive_rpc(mock_self)

    local cb = callbacks['ImplDriveConfigurationRecover']
    lu.assertNotNil(cb)
    lu.assertTrue(pcall(cb, { ComponentName = 'Disk0' }, {}))
    lu.assertEquals(#op_logs, 0)
end

-- Cover RAID卡配置恢复成功路径:找到控制器且恢复成功,记录成功操作日志(storage_app.lua 592-601)
function TestStorageApp:test_register_ctrl_rpc_configuration_recover_success()
    mdb_config_manage.on_import = function(cb) end
    mdb_config_manage.on_export = function(cb) end
    mdb_config_manage.on_recover = function(cb) end
    controller_collection.get_instance = function()
        return {
            get_by_controller_device_name = function(_, name)
                return { Id = 1, DeviceName = name }
            end,
        }
    end
    ctrl_config_backup.get_instance = function()
        return {
            recover = function()
                return true
            end,
        }
    end

    local mock_self, callbacks = make_rpc_mock_self()
    storage_app.register_ctrl_rpc(mock_self)

    local cb = callbacks['ImplControllerConfigurationRecover']
    lu.assertNotNil(cb)
    local ctx = {
        get_initiator = function()
            return 'test_initiator'
        end,
    }
    lu.assertTrue(pcall(cb, { ComponentName = 'RAIDCard0' }, ctx))
    lu.assertEquals(#op_logs, 1)
    lu.assertEquals(op_logs[1].initiator, 'test_initiator')
    lu.assertEquals(op_logs[1].component, 'storage')
    lu.assertStrContains(op_logs[1].fmt, 'Recover controller %s configuration %s')
    lu.assertEquals(op_logs[1].args[1], 1)
    lu.assertEquals(op_logs[1].args[2], 'successfully')
end

-- Cover RAID卡配置恢复失败路径:recover 返回 false,记录失败操作日志并上报 partially failed
function TestStorageApp:test_register_ctrl_rpc_configuration_recover_failed()
    mdb_config_manage.on_import = function(cb) end
    mdb_config_manage.on_export = function(cb) end
    mdb_config_manage.on_recover = function(cb) end
    controller_collection.get_instance = function()
        return {
            get_by_controller_device_name = function(_, name)
                return { Id = 1, DeviceName = name }
            end,
        }
    end
    ctrl_config_backup.get_instance = function()
        return {
            recover = function()
                return false
            end,
        }
    end

    local mock_self, callbacks = make_rpc_mock_self()
    storage_app.register_ctrl_rpc(mock_self)

    local cb = callbacks['ImplControllerConfigurationRecover']
    lu.assertNotNil(cb)
    local ctx = {
        get_initiator = function()
            return 'test_initiator'
        end,
    }
    local ok, err = pcall(cb, { ComponentName = 'RAIDCard0' }, ctx)
    lu.assertFalse(ok)
    lu.assertEquals(err, 'partially failed')
    lu.assertEquals(#op_logs, 1)
    lu.assertEquals(op_logs[1].args[1], 1)
    lu.assertEquals(op_logs[1].args[2], 'failed')
end

-- mock IT 环境无 StorageConfig 单例对象,GetDriveDetails / MockRecordSpareBlock 无法通过资源树调用,
-- 这里通过注册回调直调覆盖(storage_app.lua 406-408 / 458-460)
function TestStorageApp:test_register_drive_rpc_debug_methods()
    local drive_calls = {}
    local mock_self, callbacks = make_rpc_mock_self()
    mock_self.rpc_service_drive = {
        get_drive_info_by_name = function(drive_name)
            table.insert(drive_calls, { 'GetDriveDetails', drive_name })
            return {}
        end,
        mock_record_spare_block = function(id, slc, tlc)
            table.insert(drive_calls, { 'MockRecordSpareBlock', id, slc, tlc })
            return true
        end,
    }
    storage_app.register_drive_rpc(mock_self)

    local cb = callbacks['ImplStorageConfigReleaseStorageDriveGetDriveDetails']
    lu.assertNotNil(cb)
    lu.assertTrue(pcall(cb, {}, {}, 'Disk0'))
    cb = callbacks['ImplStorageConfigRecordDriveSpareBlockMockRecordSpareBlock']
    lu.assertNotNil(cb)
    lu.assertTrue(pcall(cb, {}, {}, 1, 1, 1))
    lu.assertEquals(#drive_calls, 2)
end

-- mock IT 环境无 Controller 对象,VolumeManage 4 个方法无法通过资源树调用,
-- 这里通过注册回调直调覆盖(storage_app.lua 695-734)
function TestStorageApp:test_register_ctrl_rpc_volume_manage()
    local task_calls = {}
    local mock_self, callbacks = make_rpc_mock_self()
    local controller_obj = { Id = 1 }
    local volume_obj = { path = '/test/controller/vol' }
    mock_self.controller_collection = {
        intf_map = { [volume_obj] = controller_obj },
    }
    mock_self.rpc_service_controller = {
        ctrl_task_operate = function(operate, path, id, ctx, ...)
            table.insert(task_calls, { operate, path, id })
            return 'task-1'
        end,
    }
    storage_app.register_ctrl_rpc_volume_manage(mock_self)

    local vol_manage_names = {
        'ImplControllerVolumeManageCreateVolumeInExisingtArray',
        'ImplControllerVolumeManageDeleteVolume',
        'ImplControllerVolumeManageCreateVolumeInNewArray',
        'ImplControllerVolumeManageCreateCachecadeVolume',
    }
    for _, name in ipairs(vol_manage_names) do
        local cb = callbacks[name]
        lu.assertNotNil(cb, name)
        lu.assertTrue(pcall(cb, volume_obj, {}, 1, 2, 3))
    end
    lu.assertEquals(#task_calls, 4)
end

-- mock IT 环境无 Volume 对象,Volume 14 个方法无法通过资源树调用,
-- 这里通过注册回调直调覆盖(storage_app.lua 739-782)
function TestStorageApp:test_register_volume_rpc()
    local volume_calls = {}
    local mock_self, callbacks = make_rpc_mock_self()
    local volume_obj = { path = '/test/controller/vol', RefControllerId = 1, Id = 0 }
    mock_self.rpc_service_volume = {
        volume_operate = function(operate, ref_id, id, ctx, ...)
            table.insert(volume_calls, { operate, ref_id, id })
            return true
        end,
        volume_task_operate = function(operate, path, ref_id, id, ctx, ...)
            table.insert(volume_calls, { operate, path, ref_id, id })
            return 'task-1'
        end,
    }
    storage_app.register_volume_rpc(mock_self)

    local volume_names = {
        'ImplVolumeVolumeSetName',
        'ImplVolumeVolumeSetReadPolicy',
        'ImplVolumeVolumeSetWritePolicy',
        'ImplVolumeVolumeSetBootable',
        'ImplVolumeVolumeSetIOPolicy',
        'ImplVolumeVolumeSetBGIEnable',
        'ImplVolumeVolumeSetAccessPolicy',
        'ImplVolumeVolumeSetDiskCachePolicy',
        'ImplVolumeVolumeStartForegroundInit',
        'ImplVolumeVolumeCancelForegroundInit',
        'ImplVolumeVolumeSetCachecadeEnable',
        'ImplVolumeVolumeSetAccelerator',
        'ImplVolumeVolumeSetCapacitySize',
        'ImplVolumeVolumeSetStripSize',
    }
    for _, name in ipairs(volume_names) do
        local cb = callbacks[name]
        lu.assertNotNil(cb, name)
        lu.assertTrue(pcall(cb, volume_obj, {}, 0))
    end
    lu.assertEquals(#volume_calls, 14)
end

-- mock IT 环境无 StorageConfig 单例对象,PhyBitError MockData 无法通过资源树调用,
-- 这里通过注册回调直调覆盖(storage_app.lua 787-789)
function TestStorageApp:test_register_debug_rpc()
    local debug_calls = {}
    local mock_self, callbacks = make_rpc_mock_self()
    mock_self.diagnose_service = {
        mock_phy_data = function(controller_id, expander_id, file_path)
            table.insert(debug_calls, { controller_id, expander_id, file_path })
            return true
        end,
    }
    storage_app.register_debug_rpc(mock_self)

    local cb = callbacks['ImplStorageConfigPhyBitErrorMockData']
    lu.assertNotNil(cb)
    lu.assertTrue(pcall(cb, {}, {}, 0, 0, '/tmp/test_phy.json'))
    lu.assertEquals(#debug_calls, 1)
end