diff --git a/app/scense.py b/app/scense.py index e495375..2f3399d 100644 --- a/app/scense.py +++ b/app/scense.py @@ -4,6 +4,11 @@ 挂载 6 个业务模块:world / scene / entity / script_engine / world_snapshot / world_sync。 数据库:单库 scense——get_module_dbname 对全部业务模块返回同一主库名(取自 ServerEnv params, 不硬编码),符合 ahserver 规范:init() 里先定义 get_module_dbname 挂 ServerEnv,再逐个 load_{模块}()。 + +Bug 修复 [e2Lck6zIRCXB5g7_HPZn0]:启动时调用 ensure_test_db() 幂等创建 +sd_test_cases / sd_test_plans / sd_test_results 三张测试域表,修复 +「list_cases 返回字段全空、scense 库无 sd_test_cases 表」的环境根因, +环境重建/换库后自动自愈,无需手工建表。 """ from ahserver.webapp import webapp from ahserver.serverenv import ServerEnv @@ -41,6 +46,15 @@ def init(): load_rbac() load_pybricks() + # ── Bug 修复:测试用例域库表幂等自愈(环境根因)── + # 确保 sd_test_cases / sd_test_plans / sd_test_results 存在, + # list_cases 不再因缺表返回字段全空。 + from scripts.init_test_db import ensure_test_db + try: + ensure_test_db() + except Exception as e: # noqa: BLE001 - 初始化容错,不因建表失败阻塞应用启动 + print('[scense.init] ensure_test_db warning: %s' % e) + # ── 业务模块挂载(关键:一个应用一个入口一个端口,所有模块挂在这里)── from world.init import load_world from scene.init import load_scene diff --git a/scripts/init_test_db.py b/scripts/init_test_db.py new file mode 100644 index 0000000..4c7932a --- /dev/null +++ b/scripts/init_test_db.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +"""元景 scense 测试用例域库表幂等初始化脚本。 + +修复 Bug [e2Lck6zIRCXB5g7_HPZn0](环境根因): + sd_test_cases 用例库不可达——list_cases 返回字段全空,scense 库无该表。 +本脚本在应用 init() 阶段自动执行(也可独立运行), +用 CREATE TABLE IF NOT EXISTS 幂等创建测试用例域 3 张表: + sd_test_cases / sd_test_plans / sd_test_results +确保环境(重建/换库)后用例库自动可达,list_cases 不再返回全空。 + +用法: + 1) 应用启动:app/scense.py init() 中调用 ensure_test_db() + 2) 独立执行:python scripts/init_test_db.py(读取 .env 连接) +""" +import os + +# 建表 DDL 与 scripts/../sql/sd_test_cases.sql 保持一致(幂等 IF NOT EXISTS) +_DDL_STATEMENTS = [ + # ---------- sd_test_cases 测试用例表 ---------- + """ + CREATE TABLE IF NOT EXISTS `sd_test_cases` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `case_id` VARCHAR(64) NOT NULL COMMENT '用例编码(业务唯一,如 TC-0001)', + `title` VARCHAR(255) NOT NULL COMMENT '用例标题', + `description` TEXT NULL COMMENT '用例描述', + `module` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '所属模块', + `plan_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '所属测试计划ID', + `priority` VARCHAR(16) NOT NULL DEFAULT 'P2' COMMENT '优先级:P0/P1/P2/P3', + `preconditions` TEXT NULL COMMENT '前置条件', + `steps` TEXT NULL COMMENT '测试步骤', + `expected_result` TEXT NULL COMMENT '预期结果', + `actual_result` TEXT NULL COMMENT '实际结果', + `status` VARCHAR(16) NOT NULL DEFAULT 'draft' COMMENT '用例状态:draft/active/closed', + `execute_status` VARCHAR(16) NOT NULL DEFAULT 'not_run' COMMENT '执行状态:not_run/pass/fail/blocked', + `created_by` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人', + `updated_by` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新人', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_case_id` (`case_id`), + KEY `idx_plan_id` (`plan_id`), + KEY `idx_module` (`module`), + KEY `idx_status` (`status`), + KEY `idx_execute_status` (`execute_status`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='测试用例表(list_cases 数据来源)' + """, + # ---------- sd_test_plans 测试计划表 ---------- + """ + CREATE TABLE IF NOT EXISTS `sd_test_plans` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `plan_id` VARCHAR(64) NOT NULL COMMENT '测试计划ID', + `plan_name` VARCHAR(255) NOT NULL COMMENT '计划名称', + `iteration_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '所属迭代ID', + `description` TEXT NULL COMMENT '计划描述', + `status` VARCHAR(16) NOT NULL DEFAULT 'draft' COMMENT '计划状态:draft/approved/in_progress/done', + `created_by` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_plan_id` (`plan_id`), + KEY `idx_iteration_id` (`iteration_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='测试计划表' + """, + # ---------- sd_test_results 测试执行结果表 ---------- + """ + CREATE TABLE IF NOT EXISTS `sd_test_results` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `result_id` VARCHAR(64) NOT NULL COMMENT '执行结果ID', + `case_id` VARCHAR(64) NOT NULL COMMENT '用例编码', + `plan_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '所属测试计划ID', + `run_status` VARCHAR(16) NOT NULL DEFAULT 'not_run' COMMENT '执行结果:pass/fail/blocked/skipped', + `actual_result` TEXT NULL COMMENT '实际结果描述', + `bug_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '失败联动Bug ID', + `executed_by` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '执行人', + `executed_at` DATETIME NULL COMMENT '执行时间', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_result_id` (`result_id`), + KEY `idx_case_id` (`case_id`), + KEY `idx_plan_id` (`plan_id`), + KEY `idx_run_status` (`run_status`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='测试执行结果表' + """, +] + + +def _dbname(): + """取主库名:优先 ServerEnv(应用内),独立运行回退 .env MYSQL_DATABASE。""" + try: + from ahserver.serverenv import ServerEnv + env = ServerEnv() + if getattr(env, 'params', None) and env.params.get('dbname'): + return env.params.get('dbname') + except Exception: + pass + return os.environ.get('MYSQL_DATABASE', 'scense') + + +def ensure_test_db(): + """幂等创建测试用例域表。重复执行安全;某条失败记录日志不中断后续。""" + from sqlor import sor + + dbname = _dbname() + created = [] + for ddl in _DDL_STATEMENTS: + try: + sor.sqlExe(dbname, ddl) + created.append(_table_name_of(ddl)) + except Exception as e: # noqa: BLE001 - 初始化容错,不因单表失败阻塞应用启动 + print('[init_test_db] create table failed: %s (%s)' % (_table_name_of(ddl), e)) + print('[init_test_db] ensured tables in db[%s]: %s' % (dbname, created)) + return created + + +def _table_name_of(ddl): + import re + m = re.search(r'CREATE TABLE IF NOT EXISTS `([^`]+)`', ddl) + return m.group(1) if m else '?' + + +if __name__ == '__main__': + # 独立执行:加载 .env 环境变量后调用 + from pathlib import Path + env_path = Path(__file__).resolve().parent.parent / '.env' + if env_path.exists(): + for line in env_path.read_text(encoding='utf-8').splitlines(): + line = line.strip() + if line and not line.startswith('#') and '=' in line: + k, _, v = line.partition('=') + os.environ.setdefault(k.strip(), v.strip()) + ensure_test_db() diff --git a/sql/sd_test_cases.sql b/sql/sd_test_cases.sql new file mode 100644 index 0000000..54e79d6 --- /dev/null +++ b/sql/sd_test_cases.sql @@ -0,0 +1,80 @@ +-- ============================================================ +-- 元景项目-初始迭代 scense 库测试用例域建表 DDL +-- 修复 Bug [e2Lck6zIRCXB5g7_HPZn0] +-- 环境根因:sd_test_cases 用例库不可达(list_cases 返回字段全空), +-- scense 库无该表。本 DDL 幂等创建测试用例域 3 张表: +-- sd_test_cases 测试用例表(list_cases 数据来源) +-- sd_test_plans 测试计划表(plan=ajD47uZZH4yK7P5vh-Md3 归属) +-- sd_test_results 测试执行结果表 +-- 同类问题一并覆盖,防止只修一张表、环境重建后再次不可达。 +-- 执行方式:部署时由 scripts/init_test_db.py 自动执行(CREATE TABLE IF NOT EXISTS,幂等); +-- 也可手工执行本文件(mysql -u scense -p scense < sd_test_cases.sql)。 +-- ============================================================ + +-- ---------- sd_test_cases 测试用例表 ---------- +CREATE TABLE IF NOT EXISTS `sd_test_cases` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `case_id` VARCHAR(64) NOT NULL COMMENT '用例编码(业务唯一,如 TC-0001)', + `title` VARCHAR(255) NOT NULL COMMENT '用例标题', + `description` TEXT NULL COMMENT '用例描述', + `module` VARCHAR(128) NOT NULL DEFAULT '' COMMENT '所属模块(world/scene/entity/script_engine/world_snapshot/world_sync)', + `plan_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '所属测试计划ID(sd_test_plans.plan_id)', + `priority` VARCHAR(16) NOT NULL DEFAULT 'P2' COMMENT '优先级:P0=最高/P1=高/P2=中/P3=低', + `preconditions` TEXT NULL COMMENT '前置条件', + `steps` TEXT NULL COMMENT '测试步骤(多行文本)', + `expected_result` TEXT NULL COMMENT '预期结果', + `actual_result` TEXT NULL COMMENT '实际结果(执行后回填)', + `status` VARCHAR(16) NOT NULL DEFAULT 'draft' COMMENT '用例状态:draft/active/closed(字典 sd_test_case_status)', + `execute_status` VARCHAR(16) NOT NULL DEFAULT 'not_run' COMMENT '执行状态:not_run/pass/fail/blocked(字典 sd_test_exec_status)', + `created_by` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人', + `updated_by` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '更新人', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_case_id` (`case_id`), + KEY `idx_plan_id` (`plan_id`), + KEY `idx_module` (`module`), + KEY `idx_status` (`status`), + KEY `idx_execute_status` (`execute_status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='测试用例表(list_cases 数据来源)'; + +-- ---------- sd_test_plans 测试计划表 ---------- +CREATE TABLE IF NOT EXISTS `sd_test_plans` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `plan_id` VARCHAR(64) NOT NULL COMMENT '测试计划ID(如 ajD47uZZH4yK7P5vh-Md3)', + `plan_name` VARCHAR(255) NOT NULL COMMENT '计划名称', + `iteration_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '所属迭代ID', + `description` TEXT NULL COMMENT '计划描述', + `status` VARCHAR(16) NOT NULL DEFAULT 'draft' COMMENT '计划状态:draft/approved/in_progress/done(字典 sd_test_plan_status)', + `created_by` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '创建人', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_plan_id` (`plan_id`), + KEY `idx_iteration_id` (`iteration_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='测试计划表'; + +-- ---------- sd_test_results 测试执行结果表 ---------- +CREATE TABLE IF NOT EXISTS `sd_test_results` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', + `result_id` VARCHAR(64) NOT NULL COMMENT '执行结果ID(业务唯一)', + `case_id` VARCHAR(64) NOT NULL COMMENT '用例编码(关联 sd_test_cases.case_id)', + `plan_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '所属测试计划ID', + `run_status` VARCHAR(16) NOT NULL DEFAULT 'not_run' COMMENT '执行结果:pass/fail/blocked/skipped(字典 sd_test_run_status)', + `actual_result` TEXT NULL COMMENT '实际结果描述', + `bug_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '失败联动产生的Bug ID(若有)', + `executed_by` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '执行人', + `executed_at` DATETIME NULL COMMENT '执行时间', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_result_id` (`result_id`), + KEY `idx_case_id` (`case_id`), + KEY `idx_plan_id` (`plan_id`), + KEY `idx_run_status` (`run_status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='测试执行结果表'; + +-- ---------- 字典:用例状态 ---------- +INSERT IGNORE INTO `sd_dict` (`dict_code`, `dict_name`, `item_code`, `item_name`, `sort_no`, `enabled`) VALUES +('sd_test_case_status', '测试用例状态', 'draft', '草稿', 1, 1), +('sd_test_case_status', '测试用例状态', 'active', '启用', 2, 1), +('sd_test_case_status', '测试用例状态', 'closed', '关闭', 3, 1);