import { spawnSync } from 'node:child_process'; import { existsSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; const repoRoot = process.cwd(); const failures = []; const smokeSteps = [ { label: '小程序壳请求与 hash 回跳静态检查', run: checkMiniProgramShell, }, { label: 'api-server 小程序登录与会话来源测试', run: () => runCommand('cargo', [ 'test', '-p', 'api-server', 'wechat_miniprogram_login_returns_system_token_and_marks_session_source', '--manifest-path', 'server-rs/Cargo.toml', '--', '--nocapture', ]), }, { label: 'H5 auth hash 消费测试', run: () => runCommand(process.execPath, [ fileURLToPath(new URL('../node_modules/vitest/vitest.mjs', import.meta.url)), 'run', 'src/services/authService.test.ts', '-t', 'consumes auth callback hash and persists the returned access token', ]), }, ]; for (const step of smokeSteps) { console.log(`[wechat-miniprogram-auth-smoke] ${step.label}`); step.run(); } if (failures.length > 0) { console.error('\n[wechat-miniprogram-auth-smoke] 未通过:'); for (const failure of failures) { console.error(`- ${failure}`); } process.exit(1); } console.log('\n[wechat-miniprogram-auth-smoke] 通过'); function checkMiniProgramShell() { const shellPath = join(repoRoot, 'miniprogram', 'pages', 'web-view', 'index.js'); const shellTemplatePath = join(repoRoot, 'miniprogram', 'pages', 'web-view', 'index.wxml'); const authServiceTestPath = join(repoRoot, 'src', 'services', 'authService.test.ts'); ensureNeedles(shellPath, [ '/api/auth/wechat/miniprogram-login', '/api/auth/wechat/bind-phone', "'x-client-type': MINI_PROGRAM_CLIENT_TYPE", "'x-client-runtime': MINI_PROGRAM_CLIENT_RUNTIME", 'auth_provider', 'auth_token', 'auth_binding_status', 'bindingStatus', 'pending_bind_phone', 'wechatPhoneCode', ]); ensureNeedles(shellTemplatePath, ['getPhoneNumber', 'bindgetphonenumber']); // 中文注释:这里锁定 H5 消费回跳 hash 的真实测试输入,避免只检查实现文本。 ensureNeedles(authServiceTestPath, [ '#auth_provider=wechat&auth_token=jwt-callback-token&auth_binding_status=pending_bind_phone', 'consumeAuthCallbackResult()', "bindingStatus: 'pending_bind_phone'", "expect(getStoredAccessToken()).toBe('jwt-callback-token')", ]); } function ensureNeedles(relativeOrFullPath, needles) { if (!existsSync(relativeOrFullPath)) { failures.push(`缺少文件:${relativeOrFullPath}`); return; } const content = readFileSync(relativeOrFullPath, 'utf8'); for (const needle of needles) { if (!content.includes(needle)) { failures.push(`${relativeOrFullPath} 缺少内容:${needle}`); } } } function runCommand(command, args) { const result = spawnSync(command, args, { cwd: repoRoot, env: process.env, shell: false, stdio: 'inherit', }); if (result.error) { failures.push(`${command} 启动失败:${result.error.message}`); return; } if (result.signal) { failures.push(`${command} 被信号终止:${result.signal}`); return; } if ((result.status ?? 0) !== 0) { failures.push(`${command} ${args.join(' ')} 退出码 ${result.status}`); } }