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

local lu = require 'luaunit'

-- Mock dependencies before requiring module under test
local sleep_calls = 0
local mock_skynet = {
    sleep = function(_)
        sleep_calls = sleep_calls + 1
    end
}
package.loaded['skynet'] = mock_skynet

local mock_context = {
    new = function()
        return {}
    end
}
package.loaded['mc.context'] = mock_context

local log_calls = {}
local mock_log = {
    error = function(_, fmt, ...)
        table.insert(log_calls, { level = 'error', msg = string.format(fmt, ...) })
    end,
    notice = function(_, fmt, ...)
        table.insert(log_calls, { level = 'notice', msg = string.format(fmt, ...) })
    end
}
package.loaded['mc.logging'] = mock_log

local get_obj_calls = 0
local set_lock_calls = {}
local mock_power_lock_obj = {
    pcall = {
        SetPowerOffLock = function(_, ctx, locked, timeout, app_name, reason)
            table.insert(set_lock_calls, {
                ctx = ctx,
                locked = locked,
                timeout = timeout,
                app_name = app_name,
                reason = reason
            })
            return true
        end
    }
}

local mock_client = {
    GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return {}
    end
}
package.loaded['storage.client'] = mock_client

package.loaded['upgrade.fructrl_handle'] = nil
local fructrl_handle = require 'upgrade.fructrl_handle'

TEST_FruCtrl_Handle = {}

function TEST_FruCtrl_Handle:setUp()
    sleep_calls = 0
    log_calls = {}
    get_obj_calls = 0
    set_lock_calls = {}

    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return {}
    end

    mock_power_lock_obj.pcall.SetPowerOffLock = function(_, ctx, locked, timeout, app_name, reason)
        table.insert(set_lock_calls, {
            ctx = ctx,
            locked = locked,
            timeout = timeout,
            app_name = app_name,
            reason = reason
        })
        return true
    end
end

function TEST_FruCtrl_Handle:test_set_power_off_lock_returns_nil_when_no_object()
    local res = fructrl_handle.set_power_off_lock(true, 10, 'test')
    lu.assertNil(res)
    lu.assertTrue(get_obj_calls > 0)
    lu.assertEquals(#set_lock_calls, 0)
end

function TEST_FruCtrl_Handle:test_set_power_off_lock_success_on_first_object_fetch()
    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return { mock_power_lock_obj }
    end

    local res = fructrl_handle.set_power_off_lock(true, 123, 'reason')
    lu.assertTrue(res)
    lu.assertEquals(get_obj_calls, 1)
    lu.assertEquals(#set_lock_calls, 1)
    lu.assertEquals(set_lock_calls[1].locked, true)
    lu.assertEquals(set_lock_calls[1].timeout, 123)
    lu.assertEquals(set_lock_calls[1].app_name, 'storage')
    lu.assertEquals(set_lock_calls[1].reason, 'reason')
end

function TEST_FruCtrl_Handle:test_get_object_retry_then_success()
    -- first two calls empty, third returns object
    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        if get_obj_calls < 3 then
            return {}
        end
        return { mock_power_lock_obj }
    end

    local res = fructrl_handle.set_power_off_lock(false, 10, 'r')
    lu.assertTrue(res)
    lu.assertEquals(get_obj_calls, 3)
    lu.assertEquals(#set_lock_calls, 1)
end

function TEST_FruCtrl_Handle:test_set_power_off_lock_until_success_retries_and_succeeds()
    local call = 0
    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return { mock_power_lock_obj }
    end
    mock_power_lock_obj.pcall.SetPowerOffLock = function(_, ctx, locked, timeout, app_name, reason)
        call = call + 1
        table.insert(set_lock_calls, {
            ctx = ctx,
            locked = locked,
            timeout = timeout,
            app_name = app_name,
            reason = reason
        })
        if call < 3 then
            return nil
        end
        return true
    end

    local ok = fructrl_handle.set_power_off_lock_until_success(true, 600, 'rr')
    lu.assertTrue(ok)
    lu.assertEquals(#set_lock_calls, 3)
end

function TEST_FruCtrl_Handle:test_set_power_off_lock_until_success_fails_after_max_retries()
    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return { mock_power_lock_obj }
    end
    mock_power_lock_obj.pcall.SetPowerOffLock = function()
        table.insert(set_lock_calls, {})
        return nil
    end

    local ok = fructrl_handle.set_power_off_lock_until_success(false, 10, 'rr')
    lu.assertFalse(ok)
    -- MAX_RETRY_COUNT = 5, loop runs 6 times
    lu.assertEquals(#set_lock_calls, 6)
end

return TEST_FruCtrl_Handle
--[[

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

local lu = require 'luaunit'

-- Mock dependencies before requiring module under test
local sleep_calls = 0
local mock_skynet = {
    sleep = function(_)
        sleep_calls = sleep_calls + 1
    end
}
package.loaded['skynet'] = mock_skynet

local mock_context = {
    new = function()
        return {}
    end
}
package.loaded['mc.context'] = mock_context

local log_calls = {}
local mock_log = {
    error = function(_, fmt, ...)
        table.insert(log_calls, { level = 'error', msg = string.format(fmt, ...) })
    end,
    notice = function(_, fmt, ...)
        table.insert(log_calls, { level = 'notice', msg = string.format(fmt, ...) })
    end
}
package.loaded['mc.logging'] = mock_log

local get_obj_calls = 0
local set_lock_calls = {}
local mock_power_lock_obj = {
    pcall = {
        SetPowerOffLock = function(_, ctx, locked, timeout, app_name, reason)
            table.insert(set_lock_calls, {
                ctx = ctx,
                locked = locked,
                timeout = timeout,
                app_name = app_name,
                reason = reason
            })
            return true
        end
    }
}

local mock_client = {
    GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return {}
    end
}
package.loaded['storage.client'] = mock_client

package.loaded['upgrade.fructrl_handle'] = nil
local fructrl_handle = require 'upgrade.fructrl_handle'

TEST_FruCtrl_Handle = {}

function TEST_FruCtrl_Handle:setUp()
    sleep_calls = 0
    log_calls = {}
    get_obj_calls = 0
    set_lock_calls = {}

    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return {}
    end

    mock_power_lock_obj.pcall.SetPowerOffLock = function(_, ctx, locked, timeout, app_name, reason)
        table.insert(set_lock_calls, {
            ctx = ctx,
            locked = locked,
            timeout = timeout,
            app_name = app_name,
            reason = reason
        })
        return true
    end
end

function TEST_FruCtrl_Handle:test_set_power_off_lock_returns_nil_when_no_object()
    local res = fructrl_handle.set_power_off_lock(true, 10, 'test')
    lu.assertNil(res)
    lu.assertTrue(get_obj_calls > 0)
    lu.assertEquals(#set_lock_calls, 0)
end

function TEST_FruCtrl_Handle:test_set_power_off_lock_success_on_first_object_fetch()
    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return { mock_power_lock_obj }
    end

    local res = fructrl_handle.set_power_off_lock(true, 123, 'reason')
    lu.assertTrue(res)
    lu.assertEquals(get_obj_calls, 1)
    lu.assertEquals(#set_lock_calls, 1)
    lu.assertEquals(set_lock_calls[1].locked, true)
    lu.assertEquals(set_lock_calls[1].timeout, 123)
    lu.assertEquals(set_lock_calls[1].app_name, 'storage')
    lu.assertEquals(set_lock_calls[1].reason, 'reason')
end

function TEST_FruCtrl_Handle:test_get_object_retry_then_success()
    -- first two calls empty, third returns object
    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        if get_obj_calls < 3 then
            return {}
        end
        return { mock_power_lock_obj }
    end

    local res = fructrl_handle.set_power_off_lock(false, 10, 'r')
    lu.assertTrue(res)
    lu.assertEquals(get_obj_calls, 3)
    lu.assertEquals(sleep_calls, 2)
    lu.assertEquals(#set_lock_calls, 1)
end

function TEST_FruCtrl_Handle:test_set_power_off_lock_until_success_retries_and_succeeds()
    local call = 0
    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return { mock_power_lock_obj }
    end
    mock_power_lock_obj.pcall.SetPowerOffLock = function(_, ctx, locked, timeout, app_name, reason)
        call = call + 1
        table.insert(set_lock_calls, {
            ctx = ctx,
            locked = locked,
            timeout = timeout,
            app_name = app_name,
            reason = reason
        })
        if call < 3 then
            return nil
        end
        return true
    end

    local ok = fructrl_handle.set_power_off_lock_until_success(true, 600, 'rr')
    lu.assertTrue(ok)
    lu.assertEquals(#set_lock_calls, 3)
    lu.assertEquals(sleep_calls, 2)
end

function TEST_FruCtrl_Handle:test_set_power_off_lock_until_success_fails_after_max_retries()
    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return { mock_power_lock_obj }
    end
    mock_power_lock_obj.pcall.SetPowerOffLock = function()
        table.insert(set_lock_calls, {})
        return nil
    end

    local ok = fructrl_handle.set_power_off_lock_until_success(false, 10, 'rr')
    lu.assertFalse(ok)
    -- MAX_RETRY_COUNT = 5, loop runs 6 times
    lu.assertEquals(#set_lock_calls, 6)
    lu.assertEquals(sleep_calls, 6)
end

return TEST_FruCtrl_Handle

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

local lu = require 'luaunit'

-- Mock dependencies before requiring module under test
local sleep_calls = 0
local mock_skynet = {
    sleep = function(_)
        sleep_calls = sleep_calls + 1
    end
}
package.loaded['skynet'] = mock_skynet

local mock_context = {
    new = function()
        return {}
    end
}
package.loaded['mc.context'] = mock_context

local log_calls = {}
local mock_log = {
    error = function(_, fmt, ...)
        table.insert(log_calls, { level = 'error', msg = string.format(fmt, ...) })
    end,
    notice = function(_, fmt, ...)
        table.insert(log_calls, { level = 'notice', msg = string.format(fmt, ...) })
    end
}
package.loaded['mc.logging'] = mock_log

local get_obj_calls = 0
local set_lock_calls = {}
local mock_power_lock_obj = {
    pcall = {
        SetPowerOffLock = function(_, ctx, locked, timeout, app_name, reason)
            table.insert(set_lock_calls, {
                ctx = ctx,
                locked = locked,
                timeout = timeout,
                app_name = app_name,
                reason = reason
            })
            return true
        end
    }
}

local mock_client = {
    GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return {}
    end
}
package.loaded['storage.client'] = mock_client

package.loaded['upgrade.fructrl_handle'] = nil
local fructrl_handle = require 'upgrade.fructrl_handle'

TEST_FruCtrl_Handle = {}

function TEST_FruCtrl_Handle:setUp()
    sleep_calls = 0
    log_calls = {}
    get_obj_calls = 0
    set_lock_calls = {}
    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return {}
    end
    mock_power_lock_obj.pcall.SetPowerOffLock = function(_, ctx, locked, timeout, app_name, reason)
        table.insert(set_lock_calls, {
            ctx = ctx,
            locked = locked,
            timeout = timeout,
            app_name = app_name,
            reason = reason
        })
        return true
    end
end

function TEST_FruCtrl_Handle:tearDown()
    -- nothing: init.lua will restore package.loaded after suite load
end

function TEST_FruCtrl_Handle:test_set_power_off_lock_returns_nil_when_no_object()
    local res = fructrl_handle.set_power_off_lock(true, 10, 'test')
    lu.assertNil(res)
    lu.assertTrue(get_obj_calls > 0)
    lu.assertEquals(#set_lock_calls, 0)
end

function TEST_FruCtrl_Handle:test_set_power_off_lock_success_on_first_object_fetch()
    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return { mock_power_lock_obj }
    end

    local res = fructrl_handle.set_power_off_lock(true, 123, 'reason')
    lu.assertTrue(res)
    lu.assertEquals(get_obj_calls, 1)
    lu.assertEquals(#set_lock_calls, 1)
    lu.assertEquals(set_lock_calls[1].locked, true)
    lu.assertEquals(set_lock_calls[1].timeout, 123)
    lu.assertEquals(set_lock_calls[1].app_name, 'storage')
    lu.assertEquals(set_lock_calls[1].reason, 'reason')
end

function TEST_FruCtrl_Handle:test_get_object_retry_then_success()
    -- first two calls empty, third returns object
    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        if get_obj_calls < 3 then
            return {}
        end
        return { mock_power_lock_obj }
    end

    local res = fructrl_handle.set_power_off_lock(false, 10, 'r')
    lu.assertTrue(res)
    lu.assertEquals(get_obj_calls, 3)
    -- sleep is called between retries (not after last successful fetch)
    lu.assertEquals(sleep_calls, 2)
    lu.assertEquals(#set_lock_calls, 1)
end

function TEST_FruCtrl_Handle:test_set_power_off_lock_until_success_retries_and_succeeds()
    local call = 0
    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return { mock_power_lock_obj }
    end
    mock_power_lock_obj.pcall.SetPowerOffLock = function(_, ctx, locked, timeout, app_name, reason)
        call = call + 1
        table.insert(set_lock_calls, { locked = locked, timeout = timeout, app_name = app_name, reason = reason })
        if call < 3 then
            return nil
        end
        return true
    end

    local ok = fructrl_handle.set_power_off_lock_until_success(true, 600, 'rr')
    lu.assertTrue(ok)
    lu.assertEquals(#set_lock_calls, 3)
    -- until_success sleeps on failed attempts only
    lu.assertEquals(sleep_calls, 2)
end

function TEST_FruCtrl_Handle:test_set_power_off_lock_until_success_fails_after_max_retries()
    mock_client.GetPowerOffLockObjects = function()
        get_obj_calls = get_obj_calls + 1
        return { mock_power_lock_obj }
    end
    mock_power_lock_obj.pcall.SetPowerOffLock = function()
        table.insert(set_lock_calls, {})
        return nil
    end

    local ok = fructrl_handle.set_power_off_lock_until_success(false, 10, 'rr')
    lu.assertFalse(ok)
    -- should retry MAX_RETRY_COUNT+1 times (cnt increments after each attempt, loop stops when cnt > MAX_RETRY_COUNT)
    lu.assertEquals(#set_lock_calls, 6)
    -- sleeps on each failed attempt before cnt exceeds max
    lu.assertEquals(sleep_calls, 6)
end

return TEST_FruCtrl_Handle
    lu.assertEquals(result, true)
    lu.assertEquals(#client_calls, 1)
    lu.assertEquals(client_calls[1].method, 'SetPowerOffLock')
    lu.assertEquals(client_calls[1].locked, true)
    lu.assertEquals(client_calls[1].timeout, 600)
    lu.assertEquals(client_calls[1].app_name, 'storage')
    lu.assertEquals(client_calls[1].reason, 'test_reason')
end

-- 测试set_power_off_lock - 获取对象失败
function TEST_Fructrl_Handle:test_set_power_off_lock_get_obj_failed()
    -- 不设置mock对象，模拟获取失败
    mock_power_off_lock_objs = {}

    local result = fructrl_handle.set_power_off_lock(true, 600, 'test_reason')

    lu.assertNil(result)
    lu.assertEquals(#client_calls, 0)
end

-- 测试set_power_off_lock - SetPowerOffLock失败
function TEST_Fructrl_Handle:test_set_power_off_lock_set_failed()
    -- 设置mock对象，但SetPowerOffLock返回false
    mock_power_off_lock_obj = {
        pcall = {
            SetPowerOffLock = function(ctx, locked, timeout, app_name, reason)
                table.insert(client_calls, {
                    method = 'SetPowerOffLock',
                    locked = locked,
                    timeout = timeout,
                    app_name = app_name,
                    reason = reason
                })
                return false
            end
        }
    }
    table.insert(mock_power_off_lock_objs, mock_power_off_lock_obj)

    local result = fructrl_handle.set_power_off_lock(true, 600, 'test_reason')

    lu.assertNotNil(result)
    lu.assertEquals(result, false)
    lu.assertEquals(#client_calls, 1)
end

-- 测试set_power_off_lock_until_success - 成功场景（第一次成功）
function TEST_Fructrl_Handle:test_set_power_off_lock_until_success_first_success()
    -- 设置mock对象，第一次调用就成功
    local call_count = 0
    mock_power_off_lock_obj = {
        pcall = {
            SetPowerOffLock = function(ctx, locked, timeout, app_name, reason)
                call_count = call_count + 1
                table.insert(client_calls, {
                    method = 'SetPowerOffLock',
                    locked = locked,
                    timeout = timeout,
                    app_name = app_name,
                    reason = reason,
                    call_count = call_count
                })
                return true
            end
        }
    }
    table.insert(mock_power_off_lock_objs, mock_power_off_lock_obj)

    local result = fructrl_handle.set_power_off_lock_until_success(true, 600, 'test_reason')

    lu.assertEquals(result, true)
    lu.assertEquals(#client_calls, 1)
    lu.assertEquals(call_count, 1)
end

-- 测试set_power_off_lock_until_success - 重试后成功
function TEST_Fructrl_Handle:test_set_power_off_lock_until_success_retry_success()
    -- 设置mock对象，前两次失败，第三次成功
    local call_count = 0
    mock_power_off_lock_obj = {
        pcall = {
            SetPowerOffLock = function(ctx, locked, timeout, app_name, reason)
                call_count = call_count + 1
                table.insert(client_calls, {
                    method = 'SetPowerOffLock',
                    locked = locked,
                    timeout = timeout,
                    app_name = app_name,
                    reason = reason,
                    call_count = call_count
                })
                -- 前两次返回false，第三次返回true
                return call_count >= 3
            end
        }
    }
    table.insert(mock_power_off_lock_objs, mock_power_off_lock_obj)

    local result = fructrl_handle.set_power_off_lock_until_success(true, 600, 'test_reason')

    lu.assertEquals(result, true)
    lu.assertEquals(#client_calls, 3)
    lu.assertEquals(call_count, 3)
end

-- 测试set_power_off_lock_until_success - 重试后仍然失败
function TEST_Fructrl_Handle:test_set_power_off_lock_until_success_retry_failed()
    -- 设置mock对象，始终返回false
    local call_count = 0
    mock_power_off_lock_obj = {
        pcall = {
            SetPowerOffLock = function(ctx, locked, timeout, app_name, reason)
                call_count = call_count + 1
                table.insert(client_calls, {
                    method = 'SetPowerOffLock',
                    locked = locked,
                    timeout = timeout,
                    app_name = app_name,
                    reason = reason,
                    call_count = call_count
                })
                return false
            end
        }
    }
    table.insert(mock_power_off_lock_objs, mock_power_off_lock_obj)

    local result = fructrl_handle.set_power_off_lock_until_success(true, 600, 'test_reason')

    lu.assertEquals(result, false)
    -- 应该重试MAX_RETRY_COUNT次（5次）
    lu.assertEquals(#client_calls, 5)
    lu.assertEquals(call_count, 5)
end

-- 测试set_power_off_lock_until_success - 解锁场景
function TEST_Fructrl_Handle:test_set_power_off_lock_until_success_unlock()
    -- 设置mock对象
    mock_power_off_lock_obj = {
        pcall = {
            SetPowerOffLock = function(ctx, locked, timeout, app_name, reason)
                table.insert(client_calls, {
                    method = 'SetPowerOffLock',
                    locked = locked,
                    timeout = timeout,
                    app_name = app_name,
                    reason = reason
                })
                return true
            end
        }
    }
    table.insert(mock_power_off_lock_objs, mock_power_off_lock_obj)

    local result = fructrl_handle.set_power_off_lock_until_success(false, 10, 'test_reason')

    lu.assertEquals(result, true)
    lu.assertEquals(#client_calls, 1)
    lu.assertEquals(client_calls[1].locked, false)
    lu.assertEquals(client_calls[1].timeout, 10)
end

-- 测试set_power_off_lock_until_success - 获取对象失败（重试）
function TEST_Fructrl_Handle:test_set_power_off_lock_until_success_get_obj_retry()
    -- 模拟前几次获取对象失败，最后一次成功
    local get_obj_count = 0
    local original_get_objs = mock_client.GetPowerOffLockObjects
    mock_client.GetPowerOffLockObjects = function()
        get_obj_count = get_obj_count + 1
        if get_obj_count < 3 then
            return {}
        else
            -- 第三次返回对象
            mock_power_off_lock_obj = {
                pcall = {
                    SetPowerOffLock = function(ctx, locked, timeout, app_name, reason)
                        table.insert(client_calls, {
                            method = 'SetPowerOffLock',
                            locked = locked,
                            timeout = timeout,
                            app_name = app_name,
                            reason = reason
                        })
                        return true
                    end
                }
            }
            return {mock_power_off_lock_obj}
        end
    end

    local result = fructrl_handle.set_power_off_lock_until_success(true, 600, 'test_reason')

    -- 由于get_power_off_lock_obj内部有重试机制，应该最终成功
    -- 但set_power_off_lock_until_success会重试set操作
    -- 这里主要验证不会因为获取对象失败而崩溃
    lu.assertNotNil(result)
    
    -- 恢复原始函数
    mock_client.GetPowerOffLockObjects = original_get_objs
end

]]
