load(); date_default_timezone_set('Asia/Shanghai'); // 加载 think-orm config(webman 用 type 字段,不是 driver) $databaseConfig = require dirname(__DIR__) . '/config/think-orm.php'; // 手工初始化 think-orm DbManager $dbManager = new \Webman\ThinkOrm\DbManager(); $dbManager->setConfig($databaseConfig); \think\Container::getInstance()->instance('think\DbManager', $dbManager); \think\Model::setDb($dbManager); use plugin\saiadmin\app\logic\user\RentalUserLogic; use plugin\saiadmin\app\validate\system\RegisterValidate; use plugin\saiadmin\app\validate\user\MemberActivationValidate; $pass = 0; $fail = 0; $skip = 0; function assertTrue(bool $cond, string $msg): void { global $pass, $fail; if ($cond) { echo "PASS: {$msg}\n"; $pass++; } else { echo "FAIL: {$msg}\n"; $fail++; } } function assertSkip(string $msg): void { global $skip; echo "SKIP: {$msg}\n"; $skip++; } // 测试中会调用业务服务的嵌套事务,外层事务确保夹具不落库。 \think\facade\Db::startTrans(); try { // 准备 $userId = (int) \think\facade\Db::table('sa_rental_user')->whereNull('delete_time')->value('id'); assertTrue($userId > 0, "存在至少一个 sa_rental_user (#{$userId}) 用于会员测试"); // 检测字段是否已加 $dbName = $databaseConfig['connections']['mysql']['database']; $colRow = \think\facade\Db::query( "SELECT COUNT(*) AS cnt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=? AND TABLE_NAME='sa_rental_user' AND COLUMN_NAME='member_expire_at'", [$dbName] )[0] ?? ['cnt' => 0]; $fieldExists = (int) $colRow['cnt'] > 0; if (!$fieldExists) { echo "INFO: sa_rental_user.member_expire_at 字段不存在,请先跑 php scripts/migrate_rental_user_member_fields.php --apply\n"; } // ============================================================ // 1. 注册校验及持久化 // ============================================================ $uniqueUsername = 'testreg' . mt_rand(10000, 99999); $validate = new RegisterValidate(); $regOk = $validate->scene('register')->check([ 'username' => $uniqueUsername, 'password' => 'P@ss1234', 'confirmPassword' => 'P@ss1234', ]); assertTrue($regOk, "注册校验通过 #{$uniqueUsername}"); // 两次密码不一致应失败 $mismatchOk = $validate->scene('register')->check([ 'username' => 'neverusedxyz', 'password' => 'P@ss1234', 'confirmPassword' => 'different', ]); assertTrue(!$mismatchOk, "两次密码不一致被 reject"); // 含下划线应失败(alphaNum 不允许 _) $underOk = $validate->scene('register')->check([ 'username' => 'with_underscore', 'password' => 'P@ss1234', 'confirmPassword' => 'P@ss1234', ]); assertTrue(!$underOk, "下划线用户名被 alphaNum 拒绝"); // 短用户名应失败 $shortOk = $validate->scene('register')->check([ 'username' => 'ab', 'password' => 'P@ss1234', 'confirmPassword' => 'P@ss1234', ]); assertTrue(!$shortOk, "短用户名(2字符)被 reject"); // 短密码应失败 $shortPwdOk = $validate->scene('register')->check([ 'username' => 'validUser1', 'password' => '123', 'confirmPassword' => '123', ]); assertTrue(!$shortPwdOk, "短密码(3字符)被 reject"); // 模拟 controller 的 INSERT(先 INSERT,再测重复用户名) $newUserId = 0; if ($regOk) { $newUserId = \think\facade\Db::table('sa_system_user')->insertGetId([ 'username' => $uniqueUsername, 'password' => password_hash('P@ss1234', PASSWORD_DEFAULT), 'status' => 1, 'is_super' => 0, 'dashboard' => 'work', 'remark' => '匿名注册', 'create_time'=> date('Y-m-d H:i:s'), 'update_time'=> date('Y-m-d H:i:s'), ]); assertTrue((int) $newUserId > 0, "sa_system_user 写入成功 (id={$newUserId})"); } // 此时用户已存在,重复用户名应被 unique 拒绝 $dupOk = $validate->scene('register')->check([ 'username' => $uniqueUsername, 'password' => 'P@ss1234', 'confirmPassword' => 'P@ss1234', ]); assertTrue(!$dupOk, "已存在用户名被 validate unique 拒绝"); // ============================================================ // 2. 会员开通 - Logic(依赖字段) // ============================================================ if (!$fieldExists) { assertSkip("会员开通 Logic 测试(依赖 member_expire_at 字段,需先跑迁移)"); } else { // 准备 Logic $logic = new RentalUserLogic(); $logic->init(['id' => 1, 'username' => 'admin', 'deptList' => [], 'roleList' => []]); $expireTime = date('Y-m-d H:i:s', strtotime('+30 days')); $result = $logic->activateMember([ 'user_id' => $userId, 'card_type' => 0, 'pay_amount' => 0, 'expire_time' => $expireTime, 'remark' => 'TEST smoke', ], 1, 'admin'); assertTrue(isset($result['merchant_order_sn']), "激活返回 OK: " . ($result['merchant_order_sn'] ?? '?')); assertTrue(($result['card_type_name'] ?? '') === '月卡', "card_type_name=月卡"); assertTrue(abs(($result['pay_amount'] ?? -1) - 0) < 0.001, "pay_amount=0"); // 验证两表 $newRecord = \think\facade\Db::table('sa_member_activation_record') ->where('user_id', $userId) ->where('remark', 'TEST smoke') ->whereNull('delete_time') ->order('id', 'desc')->find(); assertTrue($newRecord !== null, "sa_member_activation_record 写入"); if ($newRecord !== null) { assertTrue((int) $newRecord['source'] === 3, "source=3 后台开通"); assertTrue((int) $newRecord['card_type'] === 0, "card_type=0 月卡"); assertTrue((int) $newRecord['manage_agent_id'] === 1, "manage_agent_id=1 admin"); } $userRow = \think\facade\Db::table('sa_rental_user')->where('id', $userId)->whereNull('delete_time')->find(); assertTrue($userRow['member_expire_at'] === $expireTime, "sa_rental_user.member_expire_at 同步"); assertTrue((int) $userRow['member_card_type'] === 0, "sa_rental_user.member_card_type=0"); // 异常路径 $threw = false; try { $logic->activateMember([ 'user_id' => $userId, 'card_type' => 0, 'pay_amount' => 0, 'expire_time' => '2020-01-01 00:00:00', ], 1, 'admin'); } catch (\Throwable $e) { $threw = true; assertTrue(str_contains($e->getMessage(), '到期'), "过期到期时间被 Logic 拒绝: " . $e->getMessage()); } assertTrue($threw, "过期到期时间触发 Logic 异常"); $threw = false; try { $logic->activateMember([ 'user_id' => $userId, 'card_type' => 99, 'pay_amount' => 0, 'expire_time' => $expireTime, ], 1, 'admin'); } catch (\Throwable $e) { $threw = true; assertTrue(str_contains($e->getMessage(), '卡类型'), "非法卡类型被 Logic 拒绝: " . $e->getMessage()); } assertTrue($threw, "非法卡类型触发 Logic 异常"); $threw = false; try { $logic->activateMember([ 'user_id' => 999999999, 'card_type' => 0, 'pay_amount' => 0, 'expire_time' => $expireTime, ], 1, 'admin'); } catch (\Throwable $e) { $threw = true; assertTrue(str_contains($e->getMessage(), '用户不存在'), "不存在用户被 Logic 拒绝: " . $e->getMessage()); } assertTrue($threw, "不存在用户触发 Logic 异常"); $threw = false; try { $logic->activateMember([ 'user_id' => $userId, 'card_type' => 0, 'pay_amount' => -1, 'expire_time' => $expireTime, ], 1, 'admin'); } catch (\Throwable $e) { $threw = true; assertTrue(str_contains($e->getMessage(), '金额'), "负数金额被 Logic 拒绝: " . $e->getMessage()); } assertTrue($threw, "负数金额触发 Logic 异常"); // 列表查询 $listResult = $logic->indexList([ 'source' => '', 'user_id' => '', 'nickname' => '', 'phone' => '', ]); assertTrue(is_array($listResult) && isset($listResult['data']), "RentalUserLogic::indexList 返回数组含 data"); if (!empty($listResult['data'])) { assertTrue(array_key_exists('member_expire_at', $listResult['data'][0]), "列表行包含 member_expire_at 字段"); assertTrue(array_key_exists('member_active', $listResult['data'][0]), "列表行包含 member_active 字段"); } } // ============================================================ // 3. RegisterController 实例化 // ============================================================ $ctrl = new \plugin\saiadmin\app\controller\system\RegisterController(); assertTrue($ctrl instanceof \plugin\saiadmin\basic\OpenController, "RegisterController 实例化 OK"); // ============================================================ // 4. MemberActivationValidate 异常路径 // ============================================================ $memValidate = new MemberActivationValidate(); $expireTime = date('Y-m-d H:i:s', strtotime('+30 days')); $threw = !$memValidate->scene('activate')->check([ 'user_id' => $userId, 'card_type' => 99, 'pay_amount' => 0, 'expire_time' => $expireTime, ]); assertTrue($threw, "非法卡类型被 MemberActivationValidate 拒绝"); $threw = !$memValidate->scene('activate')->check([ 'user_id' => $userId, 'card_type' => 0, 'pay_amount' => -1, 'expire_time' => $expireTime, ]); assertTrue($threw, "负数金额被 MemberActivationValidate 拒绝"); $threw = !$memValidate->scene('activate')->check([ 'user_id' => 0, 'card_type' => 0, 'pay_amount' => 0, 'expire_time' => $expireTime, ]); assertTrue($threw, "user_id=0 被 MemberActivationValidate 拒绝"); } catch (\Throwable $error) { $fail++; fwrite(STDERR, 'FAIL: ' . $error->getMessage() . PHP_EOL); } finally { \think\facade\Db::rollback(); } echo "\n=== RESULT: {$pass} pass, {$fail} fail, {$skip} skip ===\n"; exit($fail > 0 ? 1 : 0);