diff --git a/.agents/skills/ulthon-testing/SKILL.md b/.agents/skills/ulthon-testing/SKILL.md index 66d4cc4..1ec901c 100644 --- a/.agents/skills/ulthon-testing/SKILL.md +++ b/.agents/skills/ulthon-testing/SKILL.md @@ -153,13 +153,13 @@ CREATE DATABASE IF NOT EXISTS ulthon_admin_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ``` -第二步,建表 + 种子(测试库已建后执行): +第二步,建表 + 初始化数据(测试库已建后执行): ```bash php tests/setup_test_db.php ``` -这个脚本会在同进程内依次跑 `migrate:run`(系统表)、`scheme:sync`(业务表)、`seed:run`(参考数据),并自查 `information_schema`。成功退出码 0。脚本幂等,可安全重跑。 +这个脚本会在同进程内依次跑 `migrate:run`(系统表 + 全部初始数据)、`scheme:sync`(业务表),并自查 `information_schema`。成功退出码 0。脚本幂等,可安全重跑。 ### 日常运行 @@ -347,7 +347,7 @@ class XxxServiceTest extends TestCase 1. **装依赖**:`composer install`(`phpunit/phpunit: ^10` 已在 require-dev) 2. **建测试库**:用 root 连接执行 `CREATE DATABASE {env.database}_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;`(库名 = .env 的 `DATABASE` + `_test`) -3. **建表 + 种子**:`php tests/setup_test_db.php` +3. **建表 + 初始化数据**:`php tests/setup_test_db.php` 为什么建表必须跑两套机制(这是框架最重要的特点之一,缺一不可): @@ -356,7 +356,7 @@ class XxxServiceTest extends TestCase | Phinx 迁移 | `migrate:run` | `database/migrations/` | 系统表(system_admin / system_menu / system_auth_node / system_config / system_host / system_timer_* 等) | | Scheme 同步 | `scheme:sync` | `app/admin/scheme/` | 业务表(各项目自己用 PHP 8 Attribute 声明的表) | -只跑 migrate 拿不到业务表,只跑 scheme 拿不到系统表(框架无法登录鉴权)。`setup_test_db.php` 在同进程内按顺序调这三条命令(migrate:run / scheme:sync / seed:run),并对每条带 `--force-force` 跳过交互确认(非 TTY 下 `scheme:sync` 会因 confirm 抛 Aborted)。 +只跑 migrate 拿不到业务表,只跑 scheme 拿不到系统表(框架无法登录鉴权)。`setup_test_db.php` 在同进程内按顺序调这两条命令(migrate:run / scheme:sync),并对每条带 `--force-force` 跳过交互确认(非 TTY 下 `scheme:sync` 会因 confirm 抛 Aborted)。 为什么用 config 级覆盖而不是环境变量:ThinkPHP 的 Env 加载 `.env`,且 `.env` 值优先于 OS 环境变量。`putenv('DATABASE=...')`、`$env:DATABASE=...`、bash 前缀变量都无法覆盖 `.env` 里已有的连接键。唯一可靠覆盖点是 config 级,在 `App::initialize()` 之后改写 `config/database.php` 解析出的连接数组。`tests/bootstrap.php` 和 `tests/setup_test_db.php` 都用这个模式。 @@ -397,7 +397,7 @@ php tests/setup_test_db.php | 文件 | 作用 | |------|------| | `tests/bootstrap.php` | PHPUnit 引导:autoload + 容器初始化 + config 级覆盖到测试库 | -| `tests/setup_test_db.php` | 测试库建表脚本:migrate:run + scheme:sync + seed:run + 自验证 | +| `tests/setup_test_db.php` | 测试库建表脚本:migrate:run + scheme:sync + 自验证 | | `tests/setup_test_db.ps1` / `.sh` | Windows / Linux 包装器 | | `tests/README.md` | 测试库建库与建表的完整说明 | | `phpunit.xml.dist` | PHPUnit 配置:testsuite 定义,bootstrap 指向 tests/bootstrap.php | diff --git a/README.en.md b/README.en.md index 48eb8ab..f4953c7 100644 --- a/README.en.md +++ b/README.en.md @@ -43,12 +43,9 @@ DEBUG=true PREFIX=ul_ -Step 4, install the database +Step 4, install the database (create tables + initialize data) php think migrate:run -Step 5: Initialize database data -php think seed:run - Finally, run locally temporarily php think run diff --git a/README.md b/README.md index 5afaf2d..18de36d 100644 --- a/README.md +++ b/README.md @@ -45,12 +45,9 @@ DEBUG=true PREFIX=ul_ -第四步, 安装数据库 +第四步, 安装数据库(建表 + 初始化数据) php think migrate:run -第五步,初始化数据库数据 -php think seed:run - 最后,本地临时运行 php think run diff --git a/app/admin/service/AdminInitService.php b/app/admin/service/AdminInitService.php deleted file mode 100644 index 20950ee..0000000 --- a/app/admin/service/AdminInitService.php +++ /dev/null @@ -1,9 +0,0 @@ -align(); + } + + public function down() + { + // 有意留空:见类注释 down 说明 + } +} diff --git a/database/seeds/.gitkeep b/database/seeds/.gitkeep new file mode 100644 index 0000000..5c4463f --- /dev/null +++ b/database/seeds/.gitkeep @@ -0,0 +1 @@ +# 种子已退役:migrate:run 是唯一安装命令;下游业务仍可放自己的种子文件 diff --git a/database/seeds/InitBaseAdminData.php b/database/seeds/InitBaseAdminData.php deleted file mode 100644 index 30446ec..0000000 --- a/database/seeds/InitBaseAdminData.php +++ /dev/null @@ -1,52 +0,0 @@ -getOutput(); - - TriggerService::updateSysconfig(); - - $install_lock = sysconfig('base_admin_install', true, 0); - - if ($install_lock == 1) { - $output->writeln('系统已初始化,跳过当前程序'); - - return; - } - - $model_config = SystemConfig::where('group', 'system') - ->where('name', 'base_admin_install') - ->find(); - - if (empty($model_config)) { - $model_config = new SystemConfig(); - - $model_config->group = 'system'; - $model_config->name = 'base_admin_install'; - $model_config->remark = '系统初始化锁定,请勿修改,避免插入重复数据'; - } - - $model_config->value = 1; - - $model_config->save(); - - TriggerService::updateSysconfig(); - - (new AdminInitService($output))->init(); - } -} diff --git a/extend/base/admin/service/AdminInitServiceBase.php b/extend/base/admin/service/AdminInitServiceBase.php deleted file mode 100644 index f22a8c2..0000000 --- a/extend/base/admin/service/AdminInitServiceBase.php +++ /dev/null @@ -1,141 +0,0 @@ -output = $output; - } - - public function init() - { - $this->initAdmin(); - $this->initAuth(); - $this->initConfig(); - $this->initMenu(); - $this->initQuick(); - $this->initMall(); - } - - public function initMall() - { - $output = $this->output; - - $output->writeln('开始初始化商城案例'); - - $list_cate = $this->requireData('MallCate'); - - $list_goods = $this->requireData('MallGoods'); - - foreach ($list_cate as $data_cate) { - $model_cate = MallCate::create($data_cate); - - foreach ($list_goods as $data_goods) { - $data_goods['cate_id'] = $model_cate->id; - MallGoods::create($data_goods); - } - } - - $list_tag = $this->requireData('MallTag'); - - $this->installData(MallTag::class, $list_tag); - } - - public function initQuick() - { - $output = $this->output; - - $output->writeln('开始初始化快捷入口'); - - $list_quick = $this->requireData('SystemQuick'); - - $this->installData(SystemQuick::class, $list_quick); - } - - public function initMenu() - { - $output = $this->output; - - $output->writeln('开始初始化系统菜单'); - - $list_menu = $this->requireData('SystemMenu'); - - $this->installData(SystemMenu::class, $list_menu); - } - - public function initConfig() - { - $output = $this->output; - $output->writeln('开始初始化系统设置'); - - $list_config = $this->requireData('SystemConfig'); - - $this->installData(SystemConfig::class, $list_config); - } - - public function initAdmin() - { - $output = $this->output; - - $output->writeln('创建超级管理员'); - - $model_admin = SystemAdmin::find(AdminConstant::SUPER_ADMIN_ID); - - if (empty($model_admin)) { - $model_admin = new SystemAdmin(); - $model_admin->id = AdminConstant::SUPER_ADMIN_ID; - $model_admin->head_img = '/static/admin/images/head.jpg'; - $model_admin->username = 'admin'; - $model_admin->password = password(123456); - $model_admin->status = 1; - $model_admin->save(); - $output->writeln('创建超级管理员成功'); - } else { - $output->writeln('超级管理员已存在,无需初始化'); - } - } - - public function initAuth() - { - $output = $this->output; - - $output->writeln('开始初始化权限'); - - $list_auth = $this->requireData('SystemAuth'); - - $this->installData(SystemAuth::class, $list_auth); - - $output->writeln('开始初始化权限节点'); - - $list_auth_node = $this->requireData('SystemAuthNode'); - - $this->installData(SystemAuthNode::class, $list_auth_node); - } - - protected function installData($model_name, $list) - { - foreach ($list as $key => $value) { - $model_name::create($value); - } - } - - protected function requireData($table_name) - { - return include __DIR__ . '/adminInitData/' . $table_name . '.php'; - } -} diff --git a/extend/base/admin/service/InitDataAlignServiceBase.php b/extend/base/admin/service/InitDataAlignServiceBase.php new file mode 100644 index 0000000..782105b --- /dev/null +++ b/extend/base/admin/service/InitDataAlignServiceBase.php @@ -0,0 +1,262 @@ +output = $output; + } + + /** + * 按快照对齐全部初始数据域(幂等,可重复执行). + * + * @return void + */ + public function align(): void + { + $this->writeln('开始对齐初始化数据(超管/角色/权限节点/配置/快捷入口/商城演示)'); + + $this->alignAdmin(); + $this->alignAuth(); + $this->alignAuthNode(); + $this->alignConfig(); + $this->alignQuick(); + $this->alignMall(); + + $this->writeln('初始化数据对齐完成'); + } + + /** + * 超管对齐:id=1 不存在才插入,密码永不覆盖. + */ + protected function alignAdmin(): void + { + $snapshot = include __DIR__ . '/adminInitData/SystemAdmin.php'; + $now = time(); + + foreach ($snapshot as $row) { + // 原生查询不过滤软删除:软删的超管行仍占用主键,视为已存在,不复活不覆盖 + $exists = Db::name('system_admin')->where('id', $row['id'])->find(); + + if (!empty($exists)) { + $this->writeln(' - 超管 id=' . $row['id'] . ' 已存在,跳过(密码永不覆盖)'); + continue; + } + + $data = $row; + $data['create_time'] = $now; + $data['update_time'] = $now; + $data['delete_time'] = 0; + Db::name('system_admin')->insert($data); + $this->writeln(' - 新增超管 id=' . $row['id'] . ' ' . $row['username']); + } + } + + /** + * 角色对齐:按显式 id 逐行只插(快照含管理员 id=1 与游客权限 id=6). + */ + protected function alignAuth(): void + { + $snapshot = include __DIR__ . '/adminInitData/SystemAuth.php'; + $now = time(); + + foreach ($snapshot as $row) { + // 原生查询不过滤软删除:软删的角色行仍占用主键,视为已存在,不复活 + $exists = Db::name('system_auth')->where('id', $row['id'])->find(); + + if (!empty($exists)) { + $this->writeln(' - 角色 id=' . $row['id'] . ' ' . $row['title'] . ' 已存在,跳过'); + continue; + } + + $data = $row; + $data['create_time'] = $now; + $data['update_time'] = $now; + $data['delete_time'] = 0; + Db::name('system_auth')->insert($data); + $this->writeln(' - 新增角色 id=' . $row['id'] . ' ' . $row['title']); + } + } + + /** + * 权限节点对齐:快照为空数组,直接跳过(该表由 auth 注解动态扫描生成). + */ + protected function alignAuthNode(): void + { + $this->writeln(' - 权限节点由注解动态扫描生成,跳过快照对齐'); + } + + /** + * 配置对齐:按 (group, name) check-then-insert,只插缺失不覆盖 value; + * 表无唯一键,判重纯靠应用层;发生过插入则清一次 sysconfig 缓存. + */ + protected function alignConfig(): void + { + $snapshot = include __DIR__ . '/adminInitData/SystemConfig.php'; + $now = time(); + $inserted = 0; + + foreach ($snapshot as $row) { + $exists = Db::name('system_config') + ->where('group', $row['group']) + ->where('name', $row['name']) + ->find(); + + if (!empty($exists)) { + continue; + } + + $data = $row; + $data['create_time'] = $now; + $data['update_time'] = $now; + Db::name('system_config')->insert($data); + ++$inserted; + } + + $this->writeln(' - 系统配置新增 ' . $inserted . ' 条(缺失补齐,不覆盖已有 value)'); + + if ($inserted > 0) { + TriggerServiceBase::updateSysconfig(); + $this->writeln(' - 已刷新系统设置缓存'); + } + } + + /** + * 快捷入口对齐:按 href 只插缺失;软删行存在视为已存在,不复活. + */ + protected function alignQuick(): void + { + $snapshot = include __DIR__ . '/adminInitData/SystemQuick.php'; + $now = time(); + $inserted = 0; + + foreach ($snapshot as $row) { + // 原生查询不过滤软删除:软删的快捷入口仍占用 href 语义,视为已存在,不复活 + $exists = Db::name('system_quick')->where('href', $row['href'])->find(); + + if (!empty($exists)) { + continue; + } + + $data = $row; + $data['create_time'] = $now; + $data['update_time'] = $now; + $data['delete_time'] = 0; + Db::name('system_quick')->insert($data); + ++$inserted; + } + + $this->writeln(' - 快捷入口新增 ' . $inserted . ' 条(按 href 判重)'); + } + + /** + * 商城演示对齐:分表守卫(三守卫相互独立): + * - cate 空则插分类(快照无显式 id,捕获自增 id) + * - goods 空则插商品,注入分类 id(复刻旧 initMall 逻辑) + * - tag 空则插标签(快照含显式 id 1-4) + */ + protected function alignMall(): void + { + $now = time(); + + // 分类表守卫:空则插入快照分类,捕获新自增 id 供商品注入 + $cateId = 0; + if ($this->isTableEmpty('mall_cate')) { + $snapshotCate = include __DIR__ . '/adminInitData/MallCate.php'; + foreach ($snapshotCate as $row) { + $data = $row; + $data['create_time'] = $now; + $data['update_time'] = $now; + $data['delete_time'] = 0; + Db::name('mall_cate')->insert($data); + $cateId = (int) Db::name('mall_cate')->getLastInsID(); + $this->writeln(' - 新增商品分类 id=' . $cateId . ' ' . $row['title']); + } + } else { + $this->writeln(' - 商品分类表已有数据,跳过'); + } + + // 商品表守卫:空则插入快照商品,cate_id 注入分类 id(无快照分类时取库内首个分类) + if ($this->isTableEmpty('mall_goods')) { + if ($cateId === 0) { + $cateId = (int) Db::name('mall_cate')->order('id', 'asc')->value('id'); + } + + $snapshotGoods = include __DIR__ . '/adminInitData/MallGoods.php'; + foreach ($snapshotGoods as $row) { + $data = $row; + $data['cate_id'] = $cateId; + $data['create_time'] = $now; + $data['update_time'] = $now; + $data['delete_time'] = 0; + Db::name('mall_goods')->insert($data); + $this->writeln(' - 新增商品 ' . $row['title'] . ' (cate_id=' . $cateId . ')'); + } + } else { + $this->writeln(' - 商品表已有数据,跳过'); + } + + // 标签表守卫:空则插入快照标签(显式 id,快照行自带时间戳与 delete_time) + if ($this->isTableEmpty('mall_tag')) { + $snapshotTag = include __DIR__ . '/adminInitData/MallTag.php'; + foreach ($snapshotTag as $row) { + Db::name('mall_tag')->insert($row); + $this->writeln(' - 新增商品标签 id=' . $row['id'] . ' ' . $row['title']); + } + } else { + $this->writeln(' - 商品标签表已有数据,跳过'); + } + } + + /** + * 判断表是否为空(原生查询,不受任何作用域影响). + */ + protected function isTableEmpty(string $table): bool + { + return Db::name($table)->count() == 0; + } + + protected function writeln(string $message): void + { + if ($this->output !== null) { + $this->output->writeln($message); + } + } +} diff --git a/extend/base/admin/service/adminInitData/SystemAdmin.php b/extend/base/admin/service/adminInitData/SystemAdmin.php new file mode 100644 index 0000000..2a033a0 --- /dev/null +++ b/extend/base/admin/service/adminInitData/SystemAdmin.php @@ -0,0 +1,24 @@ + 1, + "head_img" => "/static/admin/images/head.jpg", + "username" => "admin", + "password" => "ac1e198122f74121a69b7bc9048561973a433daa", + "status" => 1, + ) +); + +return $ul_system_admin; diff --git a/tests/McpEndpointTest.php b/tests/McpEndpointTest.php index b940f72..fe67813 100644 --- a/tests/McpEndpointTest.php +++ b/tests/McpEndpointTest.php @@ -38,7 +38,7 @@ use think\facade\Db; * Mcp-Protocol-Version 用 initialize 返回的协商版本。 * * ===== 超管创建者 ===== - * 不造 admin id=1(seed 已存在);用全新管理员 + 全新角色(避开 SystemAuthNode 60s + * 不造 admin id=1(初始化迁移已创建);用全新管理员 + 全新角色(避开 SystemAuthNode 60s * 缓存与 adminInfo autoCache 窗口,learnings Task 4/6 的既定模式)。 * * ===== 403 分支 ===== diff --git a/tests/McpServiceTest.php b/tests/McpServiceTest.php index 711bdca..a6ce1f7 100644 --- a/tests/McpServiceTest.php +++ b/tests/McpServiceTest.php @@ -212,7 +212,7 @@ class McpServiceTest extends TestCase public function test_drift_super_admin_creator_gets_full_whitelist(): void { - // 超管(seed 的 id=1,auth_ids 空):判定链超管直通,白名单全保留 + // 超管(初始化迁移创建的 id=1,auth_ids 空):判定链超管直通,白名单全保留 // (含未注册节点也放行——超管对全量节点可见) $fakeNode = 'nonexist.fake_node/action'; $key = $this->createKey(1, [self::NODE_A, $fakeNode, self::NODE_B]); diff --git a/tests/README.md b/tests/README.md index 2ba4c88..efbbb0c 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,7 +1,8 @@ # 测试数据库初始化 本目录提供测试数据库(默认 `ulthon_admin_test`)的初始化脚本。运行一次即可在与 `.env` -同实例的 MySQL 上建出框架运行所需的**全部表**(系统表 + 业务表),并插入最小参考数据。 +同实例的 MySQL 上建出框架运行所需的**全部表**(系统表 + 业务表),并写入全部初始数据 +(超管、角色、系统配置、快捷入口、菜单等,由数据迁移随 `migrate:run` 一并交付)。 ## 测试库策略:参数化,无需专用 Docker 容器 @@ -23,7 +24,7 @@ ulthon 是框架仓库,测试库策略**与衍生项目(wkbox)不同**: | 文件 | 作用 | |------|------| -| `setup_test_db.php` | 真正的运行器(PHP)。启动 ThinkPHP 容器、读 `.env`、PDO 建测试库、config 级覆盖连接、按序调用 `migrate:run` / `scheme:sync` / `seed:run`、查 `information_schema` 自验证。 | +| `setup_test_db.php` | 真正的运行器(PHP)。启动 ThinkPHP 容器、读 `.env`、PDO 建测试库、config 级覆盖连接、按序调用 `migrate:run` / `scheme:sync`、查 `information_schema` 自验证。 | | `setup_test_db.ps1` | Windows / PowerShell 包装器:检测 php 在 PATH → 调 PHP 运行器 → 透传退出码。 | | `setup_test_db.sh` | bash / CI 包装器:逻辑同上,Linux / macOS / CI 用。 | | `bootstrap.php` | PHPUnit 引导脚本(已存在,独立维护)。连接覆盖逻辑与本脚本完全一致,二者必须保持同步——否则 PHPUnit 跑的测试库与本脚本初始化的不是同一个。 | @@ -61,10 +62,12 @@ ulthon 的建表有**两个独立的来源**,二者不能互相替代: - **只跑 `scheme:sync`**:得到业务表,但没有系统表,框架无法登录、无法鉴权。 - **必须两个都跑**,业务表通常引用系统表(如外键关联 `system_admin`)。 -`seed:run` 负责最小参考数据(超级管理员、权限节点、系统设置、菜单、快捷入口等),由 -`database/seeds/InitBaseAdminData.php` 提供,自带 install-lock,重复执行安全跳过。 +初始数据不再走 seed:`migrate:run` 建表的同时由数据迁移(`20260909130000_system_init_data.php`) +写入全部初始数据(超级管理员、角色、系统配置、快捷入口、商城演示、菜单等),数据源自 +`extend/base/admin/service/adminInitData/` 快照。seed 机制已退役,`seed:run` 为空操作, +仅保留给下游业务项目放自己的种子。 -本脚本在**同一进程**内依次调用这三条命令(见下节),确保它们都指向测试库。 +本脚本在**同一进程**内依次调用这两条命令(见下节),确保它们都指向测试库。 ## 技术细节:为什么用 config 级覆盖,而不是环境变量 @@ -79,7 +82,7 @@ ThinkPHP 的 `Env` 组件加载 `.env` 文件,且 **`.env` 的值优先于 OS **唯一可靠**的覆盖点是 **config 级**:在 `App::initialize()` 之后,直接改写 `config/database.php` 解析出的连接配置数组。因为 `Db` facade 运行时读 config, -config 被改写后,后续所有 DB 操作(包括 `migrate:run` / `scheme:sync` / `seed:run` +config 被改写后,后续所有 DB 操作(包括 `migrate:run` / `scheme:sync` 内部)都连到测试库。 `setup_test_db.php` 的核心步骤: @@ -122,7 +125,6 @@ if (stripos(\think\facade\Config::get('database.connections.main.database'), 'te // 同进程内调用 think 命令,config 覆盖对它们同样生效 \think\facade\Console::call('migrate:run', ['--force-force']); \think\facade\Console::call('scheme:sync', ['--force-force']); // --force-force 跳过交互确认 -\think\facade\Console::call('seed:run', ['--force-force']); ``` ### 关于 `--force-force`(`-ff`) @@ -131,7 +133,7 @@ if (stripos(\think\facade\Config::get('database.connections.main.database'), 'te 数据库吗?")。非 TTY 环境(脚本、CI)下 `think\console\Output::confirm` 会抛 "Aborted" 而失败。框架提供了全局 `--force-force` / `-ff` 选项(见 `extend/base/common/provider/ConsoleBase.php`、`extend/base/common/console/OutputBase.php`), -命中时 `confirm()` 直接返回默认值,跳过所有交互确认。本脚本对三条命令统一带上该参数, +命中时 `confirm()` 直接返回默认值,跳过所有交互确认。本脚本对两条命令统一带上该参数, 保证非交互可运行、可重跑。 ### 关于默认连接名 `main` @@ -144,9 +146,8 @@ if (stripos(\think\facade\Config::get('database.connections.main.database'), 'te 脚本可安全重复执行: -- `migrate:run`:已执行的迁移记录在 `ul_migrations`(phinxlog),二次运行直接 "All Done" 无操作。 +- `migrate:run`:已执行的迁移记录在 `ul_migrations`(phinxlog),二次运行直接 "All Done" 无操作(初始数据由迁移交付,同样只执行一次且幂等)。 - `scheme:sync`:无差异时打印 "未检测到 Scheme 变更";有差异时先备份原表(`ul_backup_<时间戳>_<表名>`)再改,不报错。 -- `seed:run`:`InitBaseAdminData` 自带 install-lock(`base_admin_install` 配置项),二次运行打印 "系统已初始化,跳过当前程序"。 - `CREATE DATABASE IF NOT EXISTS`:库已存在时无操作。 > 注:`scheme:sync` 每次遇到差异都会生成 `ul_backup_*` 备份表,这是框架的设计行为 diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 1277392..bf62d6d 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -30,7 +30,7 @@ * - 不 require 全局 common.php(App::initialize() + autoload 已够引导 facade, * helper.php 全局函数测试基建用不到) * - 不开事务(事务隔离由 TestCaseBase.setUp 负责) - * - 不跑 migrate/scheme/seed(由独立的 setup 脚本负责) + * - 不跑 migrate/scheme(由独立的 setup 脚本负责) * - 不销毁容器(容器跨测试复用,tearDown 只回滚事务) */ diff --git a/tests/setup_test_db.php b/tests/setup_test_db.php index 14778be..7291a1a 100644 --- a/tests/setup_test_db.php +++ b/tests/setup_test_db.php @@ -2,10 +2,10 @@ /** * 测试数据库初始化脚本(参数化版). * - * 在与 .env 同实例的 MySQL 上建出 ulthon_admin_test 库,并初始化全部表: - * 1. migrate:run 建系统/框架表(database/migrations/,如 ul_system_admin / ul_system_menu) + * 在与 .env 同实例的 MySQL 上建出 ulthon_admin_test 库,并初始化全部表与初始数据: + * 1. migrate:run 建系统/框架表(database/migrations/,如 ul_system_admin / ul_system_menu), + * 并写入全部初始数据(超管、配置、菜单等,数据迁移随迁移一同执行) * 2. scheme:sync 建业务表(app/admin/scheme/) - * 3. seed:run 插入最小参考数据(database/seeds/InitBaseAdminData) * * 与 wkbox 衍生项目的区别: * - 完全参数化:连接参数(hostname/hostport/username/password/charset/prefix/database) @@ -19,7 +19,7 @@ * 都无法覆盖已存在的 .env 键,唯一可靠覆盖点是 config 级。 * - think\Config::set 的第二参数只支持单段一级名,不能传 'database.connections.main'。 * - * 幂等:migrate:run / scheme:sync / seed:run 均可安全重复执行(详见 README)。 + * 幂等:migrate:run / scheme:sync 均可安全重复执行(详见 README)。 */ declare(strict_types=1); @@ -135,12 +135,10 @@ if (!is_string($resolvedDb) || stripos($resolvedDb, 'test') === false) { // scheme:sync 在非 TTY 下不带它会被 parent::confirm 抛 "Aborted"。 // ---------------------------------------------------------------------------- $commands = [ - // 系统/框架表(Phinx 迁移):ul_system_admin / ul_system_menu / ul_system_auth_node / ul_system_config / ... - ['migrate:run', '系统/框架表(database/migrations/)'], + // 系统/框架表 + 全部初始数据(Phinx 迁移):ul_system_admin / ul_system_menu / ul_system_config / ... + ['migrate:run', '系统/框架表 + 初始数据(database/migrations/)'], // 业务表(Scheme 同步):app/admin/scheme/ 下声明的表 ['scheme:sync', '业务表(app/admin/scheme/)'], - // 参考数据(seed):InitBaseAdminData,自带 install-lock 跳过,可安全重跑 - ['seed:run', '参考数据(database/seeds/)'], ]; $handleProp = (new \ReflectionProperty(\think\console\Output::class, 'handle')); diff --git a/tests/setup_test_db.ps1 b/tests/setup_test_db.ps1 index 306f0e1..b325629 100644 --- a/tests/setup_test_db.ps1 +++ b/tests/setup_test_db.ps1 @@ -10,10 +10,10 @@ (e.g. ulthon_admin -> ulthon_admin_test) 3. CREATE DATABASE IF NOT EXISTS on the same MySQL instance (no Docker) 4. config-level override connections.main -> test DB - 5. php think migrate:run -> system/framework tables (database/migrations/) + 5. php think migrate:run -> system/framework tables + all initial data + (database/migrations/) 6. php think scheme:sync -> business tables (app/admin/scheme/) - 7. php think seed:run -> reference data (database/seeds/) - 8. queries information_schema to verify the tables landed in the test DB + 7. queries information_schema to verify the tables landed in the test DB Fully parameterized: no hardcoded host/port/credentials. The MySQL instance is whatever .env points at (typically a single local MySQL). diff --git a/tests/setup_test_db.sh b/tests/setup_test_db.sh index b089d9b..9e09211 100644 --- a/tests/setup_test_db.sh +++ b/tests/setup_test_db.sh @@ -6,10 +6,9 @@ # 2. 派生测试库名 = {env.database} + '_test'(如 ulthon_admin -> ulthon_admin_test) # 3. 在同一 MySQL 实例上 CREATE DATABASE IF NOT EXISTS(无需专用 Docker 容器) # 4. config 级覆盖 connections.main 指向测试库 -# 5. php think migrate:run 建系统/框架表(database/migrations/) +# 5. php think migrate:run 建系统/框架表 + 全部初始数据(database/migrations/) # 6. php think scheme:sync 建业务表(app/admin/scheme/) -# 7. php think seed:run 插参考数据(database/seeds/) -# 8. 查 information_schema 验证表已落到测试库 +# 7. 查 information_schema 验证表已落到测试库 # # 完全参数化:不硬编码 host/port/credentials。MySQL 实例 = .env 指向的那个。 # 详见 tests/README.md。