【求助】account组件执行UT测试命令报错

问题描述

bingo 版本为: 0.6.162

镜像:openubmc_25.09:latest

920BS版本,account组件执行bingo test -ut -jit -r artifactory-920bs命令报错,产生的not ok的测试用例如何解决?

  • 执行bingo test -ut -jit -r artifactory-920bs命令,且存在not ok的测试用例,以图中not ok为例,指向了account_collection.lua文件的447行;

  • 在account_collection.lua文件的447行为新增的用户信息保存到db和mdb的异常情况,代码逻辑未排查出问题;

  • NOT NULL constraint failed: t_manager_account.Password报错为Lua 代码操作数据库时触发的数据库约束错误,比如向数据库表的密码属性赋值了空字符串或nil,这种情况该如何修改代码,让ut测试能够ok?

环境信息

  • 操作系统:[如 Ubuntu 24.04]

  • 软件版本:[如 OpenUBMC2509]

  • 硬件配置:[如 CPU、内存等]

重现步骤

  1. [第一步]

  2. [第二步]

  3. [第三步]

期望结果

bingo test -ut 命令可以正常通过

实际结果

[实际发生了什么,包含错误信息]

尝试过的解决方案

目前执行UT或IT请安装社区的bingo,安装方法

git clone https://gitcode.com/openUBMC/bingo.git
cd  bingo
sh install_local.sh 

答案内容由AI生成,仅供参考:

:white_check_mark: 最终结论

:cross_mark: 当前错误本质是:在未完成密码校验的前提下,过早地创建了包含 NOT NULL 字段的数据库对象,且未赋值。

:white_check_mark: 推荐修复方式:将数据库持久化操作延迟至所有校验通过之后执行,避免“半成品”对象写入数据库。

点击此处查看详细分析解答

account组件UT测试报错“NOT NULL constraint failed: t_manager_account.Password”问题分析与解决方案

根据您提供的详细错误日志和上下文信息,我们对 account 组件在执行单元测试(UT)时出现的 NOT NULL constraint failed: t_manager_account.Password 错误进行了全面分析。以下将从问题定位、根本原因、修复方案以及后续预防措施四个方面进行系统性解答。


:magnifying_glass_tilted_left: 问题定位

您执行的命令:

bingo test -ut -jit -r artifactory-920bs

导致多个测试用例失败,例如:

  • TestAccount.test_when_set_history_password_0_to_5_should_change_diff_password_success
  • TestAccount.test_when_set_history_password_2_to_1_should_change_same_password_success
  • TestAccount.test_when_set_password_in_history_password_should_set_fail

这些用例均在执行到文件:

account_collection.lua:447

时报错:

/opt/bmc/libmc/lualib/database/row.lua:138: NOT NULL constraint failed: t_manager_account.Password

报错调用栈分析(关键路径):

account_collection.lua:447 → error(ret)
account_collection.lua:441 → account_in_server:password_validator(...)
account_collection.lua:434 → 创建 account_in_db 对象时未包含 Password 字段

该错误发生在尝试向数据库表 t_manager_account 插入新用户记录时,Password 字段为 nil 或空值,但此字段设置了 NOT NULL 约束,因此 SQLite 拒绝插入并抛出异常。


:firecracker: 根本原因分析

1. 数据库约束机制说明

NOT NULL constraint failed

  • 这是 SQLite 数据库的一个完整性约束错误。
  • 当某字段被定义为 notAllowNull = true 或为主键时,不允许插入空值(nil"")。
  • 来源知识库确认:

    notAllowNull 是一个用于 MDS 模型中的属性配置,表示该字段是否允许为空。若设置为 true,则对应数据库字段添加 NOT NULL 约束。
    —— 来自文档 “Lua开发框架”

t_manager_account 表中 Password 字段不可为空

  • 实体 t_network_ipv6 的案例中明确指出存在 NOT NULL constraint failed: Id
  • 类比可推知,t_manager_account.Password 字段也应配置了 notAllowNull = true
  • 一旦尝试插入 Password = nil 或未设置该字段,即触发此错误。

2. 代码逻辑缺陷分析

查看 account_collection.lua 的相关代码片段(第 434 行):

local account_in_db = self.m_table_account({ 
    Id = account_info.id, 
    UserName = account_info.name,
    RoleId = account_info.role_id 
})

:backhand_index_pointing_right: 关键问题暴露:

  • 创建 account_in_db 对象时,传入的字段不包含 Password
  • 尽管后续调用了 password_validator 方法进行密码校验,但在调用前,对象已经被部分构造,且保存操作可能隐式触发。
  • 当代码执行到 error(ret) 时(第 447 行),已经发生了 account_in_db 的插入或持久化操作,而此时 Password 仍未赋值 —— 导致数据库约束失败。

:red_exclamation_mark: 特别注意:

错误发生在 pcall 异常捕获结构内部,但在 error(ret) 前,account_in_db:delete() 被调用,但未保证删除成功或回滚整个事务。如果数据库操作已在外部提交,则即使删除也无法避免约束冲突。

此外,第 446 行尝试删除 history_password 表项,但 Password 的约束失败发生在主表 t_manager_account,因此前置删除操作无效。


3. 测试场景还原

test_when_set_history_password_0_to_5_should_change_diff_password_success 为例:

  1. 测试期望:设置历史密码检查数为 5,允许用户使用不同密码。
  2. 实际流程:
    • 调用 new_account() 创建账户;
    • new_count_to_db_and_mdb 中创建 DB 对象,缺少 Password 参数
    • 执行密码策略验证(password_validator),发生异常;
    • 捕获异常并 error(ret),引发栈回溯;
    • 此时数据库已尝试插入一条无 Password 值的记录 → 触发 NOT NULL constraint failed

:white_check_mark: 解决方案

:white_check_mark: 修正代码:确保对象创建时字段完整或延迟持久化

:white_check_mark: 方案一:【推荐】延迟数据库插入,直到所有校验通过

核心思想:不要在密码校验前创建数据库对象,避免中途失败造成非法状态。

修改 new_count_to_db_and_mdb 函数逻辑,在完成所有验证 后再 执行数据库插入。

function AccountCollection:new_count_to_db_and_mdb(ctx, account_info, account_class, is_ipmi_or_snmp, is_password_validator)
    log:info("Start to add new account to db and mdb")
    local account_type = account_info.oem and enum.AccountType.OEM:value() or enum.AccountType.Local:value()

    -- Step 1: 先进行密码校验(若需要)
    if not is_ipmi_or_snmp and is_password_validator then
        local ok, ret = pcall(function()
            -- 注意:此处 account_in_server 尚未与 DB 绑定
            local mock_account = { id = account_info.id, UserName = account_info.name }
            local validator = self.password_validator_collection:get_validator(account_type)
            -- 调用验证逻辑(不依赖 DB 持久化)
            validator:validate(ctx, account_info.name, account_info.password, true, false)
        end)
        if not ok then
            log:error('Password validation failed: ' .. tostring(ret))
            error(ret)
        end
    end

    -- Step 2: 校验通过后,才插入数据库
    local account_in_db = self.m_table_account({
        Id = account_info.id,
        UserName = account_info.name,
        RoleId = account_info.role_id,
        Password = account_info.password or ""  -- 明确赋值,防 nil
    })

    local account_in_server = account_class.new(self.db, account_in_db,
        self.password_validator_collection:get_validator(account_type), self.ipmi_channel_config)

    -- 成功返回
    return account_in_server
end

:white_check_mark: 方案二:若必须前置创建对象,则预设默认 Password

如果架构要求必须提前创建 account_in_db,则必须为 Password 提供 非空默认值,如空字符串或占位符。

local account_in_db = self.m_table_account({
    Id = account_info.id,
    UserName = account_info.name,
    RoleId = account_info.role_id,
    Password = account_info.password or ""  -- 添加此行
})

:warning: 注意:此方式治标不治本,仍存在业务逻辑与数据持久化耦合的风险,仅作为临时兼容手段。


:white_check_mark: 附加建议:增强异常处理机制

使用 pcallxpcall 包裹整个数据库操作,并实现事务性回滚语义:

local ok, result = pcall(function()
    -- 包括 account_in_db 创建、密码校验、mdb绑定等
end)

if not ok then
    -- 清理部分写入的数据
    if account_in_db and account_in_db.exists then
        account_in_db:delete()
    end
    error(result)
end

:wrench: 预防措施与最佳实践

措施 说明
:white_check_mark: 遵循 ORM 最佳实践 在调用 db.Table()self.m_table_xxx() 创建对象前,确保所有 NOT NULL 字段都有值。
:white_check_mark: 参考文档:ORM数据库操作说明 所有数据库操作都应考虑异常捕获(pcall),防止中断流程。
:white_check_mark: MDS模型配置检查 确保 t_manager_account 表中 Password 字段是否真的 必须非空。若允许初始为空(如待首次登录设置),应设为可空(notAllowNull=false)。
:white_check_mark: 启用调试日志 row.luaobject.lua 添加日志输出,查看具体插入数据内容。
:white_check_mark: 单元测试隔离 使用 mock 数据库或内存数据库(如 :memory:),避免状态污染。

:books: 关联知识补充(来自上下文)

1. NOT NULL Constraint 相关实体

  • Not Null Constraint:数据库规则,要求字段必须有值。
  • t_network_ipv6:曾因 Id 字段为空导致 NOT NULL constraint failed
  • object.luarow.lua 调用链中处理对象保存,是错误源头之一。
  • db.Table():创建数据库对象,若字段缺失则违反约束。

2. account 组件职责

  • 负责用户管理、密码策略、历史密码防重用等(见 /search_result/3)。
  • 密码策略相关参数包括 Custom_SamePwdCheckCountHistory Password Count 等。

:white_check_mark: 最终结论

:cross_mark: 当前错误本质是:在未完成密码校验的前提下,过早地创建了包含 NOT NULL 字段的数据库对象,且未赋值。

:white_check_mark: 推荐修复方式:将数据库持久化操作延迟至所有校验通过之后执行,避免“半成品”对象写入数据库。


:hammer_and_wrench: 后续验证建议

  1. 修改代码后重新运行 UT:
    bingo test -ut -jit -r artifactory-920bs
    
  2. 验证所有 previously failed test cases 是否变为 ok
  3. 检查日志中是否仍有 NOT NULL constraint failed 报错。
  4. 可在 account_collection.lua 插入调试日志,打印 account_info.password 值是否为 nil

:white_check_mark: 只要确保在创建数据库对象时,Password 字段已赋有效值(或延迟创建),该问题即可彻底解决。

当前信息无法判断。
你是否修改过UT代码或源代码?

可以参考 机器人 的回答,定位排查一下。

  1. 未修改过ut代码或源代码,新增用户的代码逻辑和社区account组件上代码是一致的;
  2. 参照机器人的回答,有些UT的测试项可以优化,但发现还有些not ok项未指明具体代码位置,这个如何优化?如下图:account组件执行UT测试,存在TestAccount.test_when_id_exist_and_administrator_to_operator_and_is_the_last_admin_then_recover_should_fail等项not ok

找到用例对应的测试代码位置,查看用例执行了哪些函数。 在函数里添加print(xxxxx) 打印, 观察是在哪一行出的错。 然后到源代码里对应函数,添加打印log:error(xxx)

1 个赞

好的,感谢,执行UT测试命令产生的报错已通过print(xxx)定位解决。目前发现account组件执行IT测试命令也存在报错,麻烦帮忙看下IT命令报错的原因,或者指导下定位IT命令报错的方法。报错内容如下图及it_output.txt日志见附件。
it_output.txt (36.5 KB)

IT 日志里的报错,不一定是用例执行失败。 由于IT 测试架构的约束,不能直接通过用例判断成功或失败。参考社区判断IT失败 的实现逻辑