register_and_member_activation_test.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. <?php
  2. /**
  3. * 冒烟测试:注册校验与持久化 + 会员开通/续费服务。
  4. * 通过手工注入 think-orm config + DbManager,绕开 webman worker 容器。
  5. * 若 sa_rental_user.member_expire_at 字段不存在(迁移未跑),跳过 Logic 集成测试。
  6. * 全部写入在外层事务中执行,结束后回滚。
  7. */
  8. require dirname(__DIR__) . '/vendor/autoload.php';
  9. \Dotenv\Dotenv::createUnsafeMutable(dirname(__DIR__))->load();
  10. date_default_timezone_set('Asia/Shanghai');
  11. // 加载 think-orm config(webman 用 type 字段,不是 driver)
  12. $databaseConfig = require dirname(__DIR__) . '/config/think-orm.php';
  13. // 手工初始化 think-orm DbManager
  14. $dbManager = new \Webman\ThinkOrm\DbManager();
  15. $dbManager->setConfig($databaseConfig);
  16. \think\Container::getInstance()->instance('think\DbManager', $dbManager);
  17. \think\Model::setDb($dbManager);
  18. use plugin\saiadmin\app\logic\user\RentalUserLogic;
  19. use plugin\saiadmin\app\validate\system\RegisterValidate;
  20. use plugin\saiadmin\app\validate\user\MemberActivationValidate;
  21. $pass = 0;
  22. $fail = 0;
  23. $skip = 0;
  24. function assertTrue(bool $cond, string $msg): void
  25. {
  26. global $pass, $fail;
  27. if ($cond) {
  28. echo "PASS: {$msg}\n";
  29. $pass++;
  30. } else {
  31. echo "FAIL: {$msg}\n";
  32. $fail++;
  33. }
  34. }
  35. function assertSkip(string $msg): void
  36. {
  37. global $skip;
  38. echo "SKIP: {$msg}\n";
  39. $skip++;
  40. }
  41. // 测试中会调用业务服务的嵌套事务,外层事务确保夹具不落库。
  42. \think\facade\Db::startTrans();
  43. try {
  44. // 准备
  45. $userId = (int) \think\facade\Db::table('sa_rental_user')->whereNull('delete_time')->value('id');
  46. assertTrue($userId > 0, "存在至少一个 sa_rental_user (#{$userId}) 用于会员测试");
  47. // 检测字段是否已加
  48. $dbName = $databaseConfig['connections']['mysql']['database'];
  49. $colRow = \think\facade\Db::query(
  50. "SELECT COUNT(*) AS cnt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=? AND TABLE_NAME='sa_rental_user' AND COLUMN_NAME='member_expire_at'",
  51. [$dbName]
  52. )[0] ?? ['cnt' => 0];
  53. $fieldExists = (int) $colRow['cnt'] > 0;
  54. if (!$fieldExists) {
  55. echo "INFO: sa_rental_user.member_expire_at 字段不存在,请先跑 php scripts/migrate_rental_user_member_fields.php --apply\n";
  56. }
  57. // ============================================================
  58. // 1. 注册校验及持久化
  59. // ============================================================
  60. $uniqueUsername = 'testreg' . mt_rand(10000, 99999);
  61. $validate = new RegisterValidate();
  62. $regOk = $validate->scene('register')->check([
  63. 'username' => $uniqueUsername,
  64. 'password' => 'P@ss1234',
  65. 'confirmPassword' => 'P@ss1234',
  66. ]);
  67. assertTrue($regOk, "注册校验通过 #{$uniqueUsername}");
  68. // 两次密码不一致应失败
  69. $mismatchOk = $validate->scene('register')->check([
  70. 'username' => 'neverusedxyz',
  71. 'password' => 'P@ss1234',
  72. 'confirmPassword' => 'different',
  73. ]);
  74. assertTrue(!$mismatchOk, "两次密码不一致被 reject");
  75. // 含下划线应失败(alphaNum 不允许 _)
  76. $underOk = $validate->scene('register')->check([
  77. 'username' => 'with_underscore',
  78. 'password' => 'P@ss1234',
  79. 'confirmPassword' => 'P@ss1234',
  80. ]);
  81. assertTrue(!$underOk, "下划线用户名被 alphaNum 拒绝");
  82. // 短用户名应失败
  83. $shortOk = $validate->scene('register')->check([
  84. 'username' => 'ab',
  85. 'password' => 'P@ss1234',
  86. 'confirmPassword' => 'P@ss1234',
  87. ]);
  88. assertTrue(!$shortOk, "短用户名(2字符)被 reject");
  89. // 短密码应失败
  90. $shortPwdOk = $validate->scene('register')->check([
  91. 'username' => 'validUser1',
  92. 'password' => '123',
  93. 'confirmPassword' => '123',
  94. ]);
  95. assertTrue(!$shortPwdOk, "短密码(3字符)被 reject");
  96. // 模拟 controller 的 INSERT(先 INSERT,再测重复用户名)
  97. $newUserId = 0;
  98. if ($regOk) {
  99. $newUserId = \think\facade\Db::table('sa_system_user')->insertGetId([
  100. 'username' => $uniqueUsername,
  101. 'password' => password_hash('P@ss1234', PASSWORD_DEFAULT),
  102. 'status' => 1,
  103. 'is_super' => 0,
  104. 'dashboard' => 'work',
  105. 'remark' => '匿名注册',
  106. 'create_time'=> date('Y-m-d H:i:s'),
  107. 'update_time'=> date('Y-m-d H:i:s'),
  108. ]);
  109. assertTrue((int) $newUserId > 0, "sa_system_user 写入成功 (id={$newUserId})");
  110. }
  111. // 此时用户已存在,重复用户名应被 unique 拒绝
  112. $dupOk = $validate->scene('register')->check([
  113. 'username' => $uniqueUsername,
  114. 'password' => 'P@ss1234',
  115. 'confirmPassword' => 'P@ss1234',
  116. ]);
  117. assertTrue(!$dupOk, "已存在用户名被 validate unique 拒绝");
  118. // ============================================================
  119. // 2. 会员开通 - Logic(依赖字段)
  120. // ============================================================
  121. if (!$fieldExists) {
  122. assertSkip("会员开通 Logic 测试(依赖 member_expire_at 字段,需先跑迁移)");
  123. } else {
  124. // 准备 Logic
  125. $logic = new RentalUserLogic();
  126. $logic->init(['id' => 1, 'username' => 'admin', 'deptList' => [], 'roleList' => []]);
  127. $expireTime = date('Y-m-d H:i:s', strtotime('+30 days'));
  128. $result = $logic->activateMember([
  129. 'user_id' => $userId,
  130. 'card_type' => 0,
  131. 'pay_amount' => 0,
  132. 'expire_time' => $expireTime,
  133. 'remark' => 'TEST smoke',
  134. ], 1, 'admin');
  135. assertTrue(isset($result['merchant_order_sn']), "激活返回 OK: " . ($result['merchant_order_sn'] ?? '?'));
  136. assertTrue(($result['card_type_name'] ?? '') === '月卡', "card_type_name=月卡");
  137. assertTrue(abs(($result['pay_amount'] ?? -1) - 0) < 0.001, "pay_amount=0");
  138. // 验证两表
  139. $newRecord = \think\facade\Db::table('sa_member_activation_record')
  140. ->where('user_id', $userId)
  141. ->where('remark', 'TEST smoke')
  142. ->whereNull('delete_time')
  143. ->order('id', 'desc')->find();
  144. assertTrue($newRecord !== null, "sa_member_activation_record 写入");
  145. if ($newRecord !== null) {
  146. assertTrue((int) $newRecord['source'] === 3, "source=3 后台开通");
  147. assertTrue((int) $newRecord['card_type'] === 0, "card_type=0 月卡");
  148. assertTrue((int) $newRecord['manage_agent_id'] === 1, "manage_agent_id=1 admin");
  149. }
  150. $userRow = \think\facade\Db::table('sa_rental_user')->where('id', $userId)->whereNull('delete_time')->find();
  151. assertTrue($userRow['member_expire_at'] === $expireTime, "sa_rental_user.member_expire_at 同步");
  152. assertTrue((int) $userRow['member_card_type'] === 0, "sa_rental_user.member_card_type=0");
  153. // 异常路径
  154. $threw = false;
  155. try {
  156. $logic->activateMember([
  157. 'user_id' => $userId, 'card_type' => 0, 'pay_amount' => 0,
  158. 'expire_time' => '2020-01-01 00:00:00',
  159. ], 1, 'admin');
  160. } catch (\Throwable $e) {
  161. $threw = true;
  162. assertTrue(str_contains($e->getMessage(), '到期'), "过期到期时间被 Logic 拒绝: " . $e->getMessage());
  163. }
  164. assertTrue($threw, "过期到期时间触发 Logic 异常");
  165. $threw = false;
  166. try {
  167. $logic->activateMember([
  168. 'user_id' => $userId, 'card_type' => 99, 'pay_amount' => 0,
  169. 'expire_time' => $expireTime,
  170. ], 1, 'admin');
  171. } catch (\Throwable $e) {
  172. $threw = true;
  173. assertTrue(str_contains($e->getMessage(), '卡类型'), "非法卡类型被 Logic 拒绝: " . $e->getMessage());
  174. }
  175. assertTrue($threw, "非法卡类型触发 Logic 异常");
  176. $threw = false;
  177. try {
  178. $logic->activateMember([
  179. 'user_id' => 999999999, 'card_type' => 0, 'pay_amount' => 0,
  180. 'expire_time' => $expireTime,
  181. ], 1, 'admin');
  182. } catch (\Throwable $e) {
  183. $threw = true;
  184. assertTrue(str_contains($e->getMessage(), '用户不存在'), "不存在用户被 Logic 拒绝: " . $e->getMessage());
  185. }
  186. assertTrue($threw, "不存在用户触发 Logic 异常");
  187. $threw = false;
  188. try {
  189. $logic->activateMember([
  190. 'user_id' => $userId, 'card_type' => 0, 'pay_amount' => -1,
  191. 'expire_time' => $expireTime,
  192. ], 1, 'admin');
  193. } catch (\Throwable $e) {
  194. $threw = true;
  195. assertTrue(str_contains($e->getMessage(), '金额'), "负数金额被 Logic 拒绝: " . $e->getMessage());
  196. }
  197. assertTrue($threw, "负数金额触发 Logic 异常");
  198. // 列表查询
  199. $listResult = $logic->indexList([
  200. 'source' => '', 'user_id' => '', 'nickname' => '', 'phone' => '',
  201. ]);
  202. assertTrue(is_array($listResult) && isset($listResult['data']), "RentalUserLogic::indexList 返回数组含 data");
  203. if (!empty($listResult['data'])) {
  204. assertTrue(array_key_exists('member_expire_at', $listResult['data'][0]), "列表行包含 member_expire_at 字段");
  205. assertTrue(array_key_exists('member_active', $listResult['data'][0]), "列表行包含 member_active 字段");
  206. }
  207. }
  208. // ============================================================
  209. // 3. RegisterController 实例化
  210. // ============================================================
  211. $ctrl = new \plugin\saiadmin\app\controller\system\RegisterController();
  212. assertTrue($ctrl instanceof \plugin\saiadmin\basic\OpenController, "RegisterController 实例化 OK");
  213. // ============================================================
  214. // 4. MemberActivationValidate 异常路径
  215. // ============================================================
  216. $memValidate = new MemberActivationValidate();
  217. $expireTime = date('Y-m-d H:i:s', strtotime('+30 days'));
  218. $threw = !$memValidate->scene('activate')->check([
  219. 'user_id' => $userId, 'card_type' => 99, 'pay_amount' => 0,
  220. 'expire_time' => $expireTime,
  221. ]);
  222. assertTrue($threw, "非法卡类型被 MemberActivationValidate 拒绝");
  223. $threw = !$memValidate->scene('activate')->check([
  224. 'user_id' => $userId, 'card_type' => 0, 'pay_amount' => -1,
  225. 'expire_time' => $expireTime,
  226. ]);
  227. assertTrue($threw, "负数金额被 MemberActivationValidate 拒绝");
  228. $threw = !$memValidate->scene('activate')->check([
  229. 'user_id' => 0, 'card_type' => 0, 'pay_amount' => 0,
  230. 'expire_time' => $expireTime,
  231. ]);
  232. assertTrue($threw, "user_id=0 被 MemberActivationValidate 拒绝");
  233. } catch (\Throwable $error) {
  234. $fail++;
  235. fwrite(STDERR, 'FAIL: ' . $error->getMessage() . PHP_EOL);
  236. } finally {
  237. \think\facade\Db::rollback();
  238. }
  239. echo "\n=== RESULT: {$pass} pass, {$fail} fail, {$skip} skip ===\n";
  240. exit($fail > 0 ? 1 : 0);