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

-- Description: test the external RPC interfaces of SEL.
local mdb = require 'mc.mdb'
local log = require 'mc.logging'
local ctx = require 'mc.context'
local skynet = require 'skynet'
local open_local_db = require 'sensor.local_db'
local common = require 'test_sensor_common'
local ipmitool = require 'test_ipmitool'

require 'sensor.json_types.DiscreteSensorDisplay'
require 'sensor.json_types.Sensors'
require 'sensor.json_types.SensorSelInfo'

local sel_interface = {}
sel_interface.__index = sel_interface

function sel_interface.test_sel_full(bus)
    log:info('== test sel almost full start ...')

    skynet.call('sensor', 'lua', 'sel_full')
    -- 清空事件，这里会产生一条清空sel的事件
    ipmitool.sel_clear(bus)
    skynet.sleep(30)

    -- 测试SEL超限
    skynet.call('sensor', 'lua', 'sel_almost_full', true)
    skynet.call('sensor', 'lua', 'sel_almost_full', false)
    skynet.call('sensor', 'lua', 'sel_full', true)
    skynet.call('sensor', 'lua', 'sel_full', false)
    skynet.sleep(30)

    -- 资源树判断传感器的Health，预期为0
    local path = '/bmc/kepler/Systems/1/DiscreteSensors/DiscreteSensor_SelStatus_0101'
    local intf = 'bmc.kepler.Systems.DiscreteSensorDisplay'
    local mobj = mdb.get_object(bus, path, intf)
    assert(mobj.Health == 'OK', 'actual: ' .. mobj.Health)

    -- 数据库生成一条最新的SEL
    local db = open_local_db(skynet.getenv('TEST_DATA_DIR') .. '/sensor.db', nil, 'poweroff')
    local sel = db:select(db.IpmiSelList):order_by(db.IpmiSelList.RecordId, false):all()[2].__datas
    assert(sel.SelData1 == 0x05, 'actual: ' .. sel.SelData1)
    assert(sel.SelData3 == 0x5A, 'actual: ' .. sel.SelData3)
    sel = db:select(db.IpmiSelList):order_by(db.IpmiSelList.RecordId, false):all()[4].__datas
    assert(sel.SelData1 == 0x04, 'actual: ' .. sel.SelData1)
    assert(sel.SelData3 == 0xFF, 'actual: ' .. sel.SelData3)
    db.db:close()
    skynet.call('sensor', 'lua', 'sel_full', false)
end

local data_template = {
    sel_type = 0xC0,
    timestamp = 0,
    generate_id = 0x41,
    msg_version = 0x04,
    sensor_type = 0x0D,
    sensor_no = 0x18,
    event_type = 0x6F,
    event_dir = 0x01,
    data1 = 0,
    data2 = 0xFF,
    data3 = 0xFF
}

local function insert_sel(bus, sel_datas, add_count)
    for i = #sel_datas + 1, #sel_datas + add_count do
        data_template.data1 = i  -- 以data1标识不同事件
        ipmitool.add_sel(bus, data_template)
        local sel_data = common.convert_sel_data(data_template)
        table.insert(sel_datas, sel_data)
    end
end

local function insert_sp_sel(bus, sel_datas, sp_sel_data)
    ipmitool.add_sel(bus, sp_sel_data)
    local sel_data = common.convert_sel_data(sp_sel_data)
    table.insert(sel_datas, sel_data)
end

local service = 'bmc.kepler.sensor'
local path = '/bmc/kepler/sensor/MicroComponent'
local intf = 'bmc.kepler.MicroComponent.ConfigManage'
local c = ctx.new('IT', 'Admin', '127.0.0.1')
local json = require 'cjson'

local function custom_sel_mode(bus, mode)
    local custom_data = json.encode({
        ConfigData = {
            CustomSettings = {
                BMCSet_SEL_Mode = {Value = mode, Import = true}
            }
        }
    })
    local type = 'custom'
    bus:call(service, path, intf, 'Import', 'a{ss}ss', c, custom_data, type)
    skynet.sleep(10)
    local custom_ret = bus:call(service, path, intf, 'Export', 'a{ss}s', c, type)
    assert(json.decode(custom_ret).ConfigData.CustomSettings.BMCSet_SEL_Mode == mode)
end

function sel_interface.test_clear_mode(bus)
    log:info('== test clear mode start ...')
    skynet.call('sensor', 'lua', 'sel_full')
    -- 清空事件，这里会产生一条清空sel的事件
    ipmitool.sel_clear(bus)
    skynet.sleep(30)

    -- 设置数据库上限为20
    skynet.call('sensor', 'lua', 'sel_max_count', 20)

    -- 添加事件至17条(不满20条)
    local sel_datas = {}
    insert_sel(bus, sel_datas, 16)

    -- 查询数据，能查询到8条，并且其中2-8条与插入顺序一致
    local count, sel_lists = ipmitool.sel_list(bus)
    assert(count == 17, 'actual: '.. count)
    local sel_index = 1
    for i = 2, 17 do
        -- 校验主要的事件数据
        assert(sel_lists[i]:sub(11, 16) == sel_datas[sel_index], 'not matched index: ' .. i)
        sel_index = sel_index + 1
    end

    -- 继续添加事件至25条(满20条)
    -- 其中19: almost full assert 20:full assert 21:clear 22: almost full deassert 23-25:stub
    insert_sel(bus, sel_datas, 4)

    -- 查询数据，只能查询到25-20=5条，并且其中4-5条与最新插入的2条顺序一致
    count, sel_lists = ipmitool.sel_list(bus)
    assert(count == 5, 'actual: '.. count)
    sel_index = #sel_datas - 3 + 1
    for i = 3, 5 do
        -- 校验主要的事件数据
        assert(sel_lists[i]:sub(11, 16) == sel_datas[sel_index], 'not matched index: ' .. i)
        sel_index = sel_index + 1
    end

    -- 切到循环滚动模式
    custom_sel_mode(bus, 'RotateAfterFull')

    -- 查询数据，应与清空模式查询结果一致
    count, sel_lists = ipmitool.sel_list(bus)
    assert(count == 5, 'actual: '.. count)
    sel_index = #sel_datas - 3 + 1
    for i = 3, 5 do
        -- 校验主要的事件数据
        assert(sel_lists[i]:sub(11, 16) == sel_datas[sel_index], 'not matched index: ' .. i)
        sel_index = sel_index + 1
    end

    -- 切回默认模式(清空模式)
    custom_sel_mode(bus, 'ClearAfterFull')

    -- 清空事件
    ipmitool.sel_clear(bus)
    skynet.sleep(30)

    -- 设置数据库上限为默认的2000
    skynet.call('sensor', 'lua', 'sel_max_count', 2000)
end

function sel_interface.test_rotate_mode(bus)
    log:info('== test rotate mode start ...')
    skynet.call('sensor', 'lua', 'sel_full')
    -- 清空事件，这里会产生一条清空sel的事件
    ipmitool.sel_clear(bus)
    skynet.sleep(30)

    -- 设置数据库上限为20
    skynet.call('sensor', 'lua', 'sel_max_count', 20)

    -- 通过装备定制化方式切到循环滚动模式
    custom_sel_mode(bus, 'RotateAfterFull')

    -- 添加事件至17条(不满20条)
    local sel_datas = {}
    insert_sel(bus, sel_datas, 16)

    -- 查询数据，能查询到18条，并且其中2-17条与插入顺序一致
    local count, sel_lists = ipmitool.sel_list(bus)
    assert(count == 17, 'actual: '.. count)
    local sel_index = 1
    for i = 2, 17 do
        -- 校验主要的事件数据
        assert(sel_lists[i]:sub(11, 16) == sel_datas[sel_index], 'not matched index: ' .. i)
        sel_index = sel_index + 1
    end

    -- 继续添加事件至25条(满20条)
    insert_sel(bus, sel_datas, 6)

    -- 查询数据，这里能查到最大条数20条，最后5条与最新插入的5条顺序一致
    -- 19: almost full assert 20:full assert 21-25:stub
    count, sel_lists = ipmitool.sel_list(bus)
    assert(count == 20, 'actual: '.. count)
    sel_index = #sel_datas - 5 + 1
    for i = 16, 20 do
        -- 校验主要的事件数据
        assert(sel_lists[i]:sub(11, 16) == sel_datas[sel_index], 'not matched index: ' .. i)
        sel_index = sel_index + 1
    end

    -- 切回默认模式(清空模式)
    custom_sel_mode(bus, 'ClearAfterFull')

    -- 查询数据，只能查询到7条
    -- 1~5:stub 6: clear 7: almost full deassert
    count, sel_lists = ipmitool.sel_list(bus)
    assert(count == 7, 'actual: '.. count)
    sel_index = #sel_datas - 5 + 1
    for i = 1, 5 do
        -- 校验主要的事件数据
        assert(sel_lists[i]:sub(11, 16) == sel_datas[sel_index], 'not matched index: ' .. i)
        sel_index = sel_index + 1
    end

    -- 清空事件
    ipmitool.sel_clear(bus)
    skynet.sleep(30)

    -- 设置数据库上限为默认2000
    skynet.call('sensor', 'lua', 'sel_max_count', 2000)
end

local sp_sel_list = {
    {
        sel_type = 0x02,
        timestamp = 0,
        generate_id = 0x20,
        msg_version = 0x04,
        sensor_type = 0x23,
        sensor_no = 0x03,
        event_type = 0x6F,
        event_dir = 0x00,
        data1 = 0,
        data2 = 0x01,
        data3 = 0xFF
    },
    {
        sel_type = 0x02,
        timestamp = 0,
        generate_id = 0x20,
        msg_version = 0x04,
        sensor_type = 0x23,
        sensor_no = 0x03,
        event_type = 0x6F,
        event_dir = 0x01,
        data1 = 0,
        data2 = 0x01,
        data3 = 0xFF
    },
    {
        sel_type = 0xC0,
        timestamp = 0,
        generate_id = 0x41,
        msg_version = 0x04,
        sensor_type = 0x0D,
        sensor_no = 0x18,
        event_type = 0x6F,
        event_dir = 0x01,
        data1 = 0x0F,
        data2 = 0xFF,
        data3 = 0xFF
    }
}

local SENSORS_PATH = '/bmc/kepler/Chassis/1/Sensors'
local SENSORS_INTF = 'bmc.kepler.Chassis.Sensors'

local function check_sp_sel(res, lastRecordId)
    assert(res[1].Status == 'Deasserted', 'actual: '.. res[1].Status)
    assert(res[1].SensorType == 'Drive Slot / Bay', 'actual: '.. res[1].SensorType)
    assert(res[1].Description == 'Unknown Sensor Event Description', 'actual: '.. res[1].Description)
    assert(res[1].Level == 'Informational', 'actual: '.. res[1].Level)
    assert(res[1].SensorName == 'Unknown Sensor', 'actual: '.. res[1].SensorName)
    assert(res[1].RecordId == tostring(lastRecordId), 'actual: '.. res[1].RecordId)

    assert(res[2].Status == 'Deasserted', 'actual: '.. res[2].Status)
    assert(res[2].SensorType == 'Watchdog2', 'actual: '.. res[2].SensorType)
    assert(res[2].Description == 'Timer expired', 'actual: '.. res[2].Description)
    assert(res[2].Level == 'Informational', 'actual: '.. res[2].Level)
    assert(res[2].SensorName == 'Unknown Sensor', 'actual: '.. res[2].SensorName)
    assert(res[2].RecordId == tostring(lastRecordId - 1), 'actual: '.. res[2].RecordId)

    assert(res[3].Status == 'Asserted', 'actual: '.. res[3].Status)
    assert(res[3].SensorType == 'Watchdog2', 'actual: '.. res[3].SensorType)
    assert(res[3].Description == 'Timer expired', 'actual: '.. res[3].Description)
    assert(res[3].Level == 'Informational', 'actual: '.. res[3].Level)
    assert(res[3].SensorName == 'Unknown Sensor', 'actual: '.. res[3].Level)
    assert(res[3].RecordId == tostring(lastRecordId - 2), 'actual: '.. res[3].RecordId)
end

local function test_query_sel_by_insufficient_privilege(bus)
    -- GetSel接口权限为ReadOnly
    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    local test_ctx = ctx.new()
    test_ctx.Auth = "1"
    test_ctx.Privilege = 'UserMgmt'
    local start = 0
    local count = 17
    local ok, err = pcall(function ()
        return mobj:GetSel_PACKED(test_ctx, start, count):unpack()
    end)
    assert(not ok)
    assert(err.name == 'InsufficientPrivilege', 'actual: ' .. err.name)
end

local function test_query_sel_with_abnormal_parameter(bus)
    -- 查询参数count大于CountRecordId，预期只能查询到17条
    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    local start = 0
    local count = 25
    local _, _, list= pcall(function ()
        return mobj:GetSel_PACKED(ctx.new(), start, count):unpack()
    end)
    local res = common.format_sel(list)
    assert(#res == 17, 'actual: '.. #res)

    -- 查询参数count为负数，预期只能查询到0条
    start = 0
    count = -1
    _, _, list= pcall(function ()
        return mobj:GetSel_PACKED(ctx.new(), start, count):unpack()
    end)
    res = common.format_sel(list)
    assert(#res == 0, 'actual: '.. #res)

    -- 查询参数start大于total，预期只能查询到0条
    start = 21
    count = 5
    _, _, list= pcall(function ()
        return mobj:GetSel_PACKED(ctx.new(), start, count):unpack()
    end)
    res = common.format_sel(list)
    assert(#res == 0, 'actual: '.. #res)
end

local function test_query_sel_in_rotate_mode(bus, sel_datas)
    -- 切到循环滚动模式
    custom_sel_mode(bus, 'RotateAfterFull')

    -- 继续添加事件至30条(满25条)
    insert_sel(bus, sel_datas, 7)
    for i = 1, #sp_sel_list do
        insert_sp_sel(bus, sel_datas, sp_sel_list[i])
    end

    -- 查询事件,这里能查到最大条数25条
    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    local start = 0
    local count = 25
    local _, total, list= pcall(function ()
        return mobj:GetSel_PACKED(ctx.new(), start, count):unpack()
    end)
    local res = common.format_sel(list)
    assert(total == 25, 'actual: '.. total)
    assert(#res == 25, 'actual: '.. #res)

    -- 查询最新特定事件，这里能查到最大条数25条，最后3条与最新插入的3条顺序一致
    start = 23
    count = 25
    _, total, list= pcall(function ()
        return mobj:GetSel_PACKED(ctx.new(), start, count):unpack()
    end)
    res = common.format_sel(list)
    assert(total == 25, 'actual: '.. total)
    assert(#res == 3, 'actual: '.. #res)
    check_sp_sel(res, total)

    -- 切回默认模式(清空模式)
    custom_sel_mode(bus, 'ClearAfterFull')

    -- 查询数据，只能查询到6条
    start = 0
    count = 25
    _, total, list= pcall(function ()
        return mobj:GetSel_PACKED(ctx.new(), start, count):unpack()
    end)
    res = common.format_sel(list)
    assert(total == 6, 'actual: '.. total)
    assert(#res == 6, 'actual: '.. #res)
end

function sel_interface.test_query_sel(bus)
    log:info('== test query sel start ...')
    skynet.call('sensor', 'lua', 'sel_full')
    -- 清空事件，这里会产生一条清空sel的事件
    ipmitool.sel_clear(bus)
    skynet.sleep(30)

    -- 设置数据库上限为25
    skynet.call('sensor', 'lua', 'sel_max_count', 25)

    -- 添加事件至17条(不满25条)
    local sel_datas = {}
    insert_sel(bus, sel_datas, 16)

    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    -- 查询数据，总数为17，能查询到17条
    local start = 0
    local count = 17
    local _, total, list= pcall(function ()
        return mobj:GetSel_PACKED(ctx.new(), start, count):unpack()
    end)
    local res = common.format_sel(list)
    assert(total == 17, 'actual: '.. total)
    assert(#res == 17, 'actual: '.. #res)

    -- 测试异常参数
    log:info('================ test query sel with abnormal parameters start ...')
    test_query_sel_with_abnormal_parameter(bus)

    -- 测试非法权限
    log:info('================ test query sel with insufficient privilege start ...')
    test_query_sel_by_insufficient_privilege(bus)

    --  继续添加3条特定事件
    for  i = 1, #sp_sel_list do
        insert_sp_sel(bus, sel_datas, sp_sel_list[i])
    end

    -- 从RecordId为18开始查询3条sel
    start = 18
    count = 3
    _, total, list = pcall(function ()
        return mobj:GetSel_PACKED(ctx.new(), start, count):unpack()
    end)
    res = common.format_sel(list)
    assert(#res == 3, 'actual: '.. #res)
    check_sp_sel(res, total)

    -- 测试循环滚动模式
    log:info('================ test query sel in rotate mode start ...')
    test_query_sel_in_rotate_mode(bus, sel_datas)

    -- 清空事件
    ipmitool.sel_clear(bus)
    skynet.sleep(30)

    -- 设置数据库上限为默认2000
    skynet.call('sensor', 'lua', 'sel_max_count', 2000)
end

local function test_export_sel_by_invalid_path(bus)
    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    ---- 测试非tmp路径
    log:info('================ test export sel with non-temporary path start ...')
    local export_path = '/opt/sensor_sel.tar.gz'
    local task_id, err = pcall(function ()
        return mobj:ExportSel_PACKED(ctx.new(), export_path):unpack()
    end)
    assert(not task_id)
    assert(err.name == 'InvalidPath', 'actual: ' .. err.name)
    ---- 测试路径绕过
    log:info('================ test export sel with bypassing path start ...')
    export_path = '/tmp/../sensor_sel.tar.gz'
    task_id, err = pcall(function ()
        return mobj:ExportSel_PACKED(ctx.new(), export_path):unpack()
    end)
    assert(not task_id)
    assert(err.name == 'InvalidPath', 'actual: ' .. err.name)
end

local function test_download_sel_by_insufficient_privilege(bus)
    -- ExportSel接口权限为ReadOnly
    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    local export_path = '/tmp/sensor_sel.tar.gz'
    local test_ctx = ctx.new()
    test_ctx.Auth = "1"
    test_ctx.Privilege = 'UserMgmt'
    local task_id, err = pcall(function ()
        return mobj:ExportSel_PACKED(test_ctx, export_path):unpack()
    end)
    assert(not task_id)
    assert(err.name == 'InsufficientPrivilege', 'actual: ' .. err.name)
end

function sel_interface.test_download_sel(bus)
    log:info('== test export sel start ...')
    -- 测试非法路径
    log:info('================ test export sel with invalid path start ...')
    test_export_sel_by_invalid_path(bus)

    -- 测试非法权限
    log:info('================ test export sel with insufficient privilege start ...')
    test_download_sel_by_insufficient_privilege(bus)

    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    -- 测试 tar.gz 包
    log:info('================ test export sel tar gz start ...')
    local export_path = '/tmp/sensor_sel.tar.gz'
    local task_id = mobj:ExportSel_PACKED(ctx.new(), export_path):unpack()
    local task_path = SENSORS_PATH .. '/TaskService/Tasks/' .. task_id
    local task_intf = 'bmc.kepler.TaskService.Task'
    common.check_with_delay(function ()
        local task_state = common.get_mdb_property(bus, SENSORS_INTF, task_path, task_intf, 'State')
        assert(task_state == 'Completed')
        local f = io.open(export_path, 'r')
        assert(f)
        f:close()
    end)

    -- 测试 tar 包
    log:info('================ test export sel tar start ...')
    export_path = '/tmp/sensor_sel.tar'
    task_id = mobj:ExportSel_PACKED(ctx.new(), export_path):unpack()
    task_path = SENSORS_PATH .. '/TaskService/Tasks/' .. task_id
    task_intf = 'bmc.kepler.TaskService.Task'
    common.check_with_delay(function ()
        local task_state = common.get_mdb_property(bus, SENSORS_INTF, task_path, task_intf, 'State')
        assert(task_state == 'Completed')
        local f = io.open(export_path, 'r')
        assert(f)
        f:close()
    end)
end

function sel_interface.test_report_sel(bus)
    log:info('== test report sel start ...')

    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    local SELINFO_PATH = '/bmc/kepler/Chassis/1/SensorSelInfo'
    local SELINFO_INTF = 'bmc.kepler.Chassis.SensorSelInfo'
    local sobj = mdb.get_object(bus, SELINFO_PATH, SELINFO_INTF)

    local tmp_seq = sobj.RecordSeq
    -- 模拟触发Inlet Temp温度过高事件
    mobj:MockSensor(ctx.new('IT', 'Admin', '127.0.0.1'), 1, 'SYS 12V_2', '10.000')
    -- 主动读取属性模拟监听到属性变更
    local sel_seq = sobj.RecordSeq
    assert(sel_seq == tmp_seq + 1, 'actual: '.. sel_seq)
    local _, total, list = pcall(function ()
        return mobj:GetReportedSel_PACKED(ctx.new(), sel_seq, 1):unpack()
    end)
    assert(total == 1, 'actual: '.. total)
    local sel = common.format_sel(list)[1]
    assert(sel.SensorName == 'SYS 12V_2', 'actual: '.. sel.SensorName)
    assert(sel.Description == 'Lower Critical going low ', 'actual: '.. sel.Description)
    assert(sel.SubjectName == 'MainBoard', 'actual: '.. sel.SubjectName)
    assert(sel.EventCode == '0x0142FFFF', 'actual: '.. sel.EventCode)
end

function sel_interface.test_clear_sel(bus)
    log:info('== test clear sel start ...')
    -- 模拟3条记录
    local sel_datas = {}
    insert_sel(bus, sel_datas, 3)
    local count = ipmitool.sel_list(bus)
    assert(count ~= 1, 'actual: '.. count)

    -- 清除sel
    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    mobj:ClearSel_PACKED(ctx.new())
    count = ipmitool.sel_list(bus)
    assert(count == 1, 'actual: '.. count)
end

function sel_interface.test_get_sel_level_from_obj(bus)
    log:info('== test get sel level from obj strat ...')

    local mobj = mdb.get_object(bus, SENSORS_PATH, SENSORS_INTF)
    local SELINFO_PATH = '/bmc/kepler/Chassis/1/SensorSelInfo'
    local SELINFO_INTF = 'bmc.kepler.Chassis.SensorSelInfo'
    local sobj = mdb.get_object(bus, SELINFO_PATH, SELINFO_INTF)

    mobj:MockSensor(ctx.new('IT', 'Admin', '127.0.0.1'), 1, 'Boot Error', '0x8001')
    local ok, _, list = pcall(function ()
        return mobj:GetReportedSel_PACKED(ctx.new(), sobj.RecordSeq, 1):unpack()
    end)
    assert(ok)
    local sel = common.format_sel(list)[1]
    assert(sel.Level == '1', 'actual: '.. sel.Level)
    assert(sel.Description == 'No bootable media', 'actual: '.. sel.Description)

    log:info('== test get sel level from obj end ...')
end

function sel_interface.test_entry(bus)
    log:info('================ test sel interface start ================')

    sel_interface.test_sel_full(bus)
    sel_interface.test_clear_mode(bus)
    sel_interface.test_rotate_mode(bus)
    sel_interface.test_query_sel(bus)
    sel_interface.test_download_sel(bus)
    sel_interface.test_report_sel(bus)
    sel_interface.test_clear_sel(bus)
    sel_interface.test_get_sel_level_from_obj(bus)

    log:info('================ test sel interface complete ================')
end

return sel_interface